Instantly share code, notes, and snippets.

# CobaltXII/synth.js

Last active May 12, 2022 01:24
Star You must be signed in to star a gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 // this function returns the frequency in Hz of a given note in a given octave // i used this page to get the formula: // https://pages.mtu.edu/~suits/NoteFreqCalcs.html // and i used this page to get the reference frequency: // https://pages.mtu.edu/~suits/notefreqs.html function getNoteFrequency(note, octave) { // capital letter means it's a sharp // this notation is what piano letter notes uses var notes = ['c', 'C', 'd', 'D', 'e', 'f', 'F', 'g', 'G', 'a', 'A', 'b']; var C0 = 16.35; var a = Math.pow(2, 1 / 12); return C0 * Math.pow(a, notes.indexOf(note) + octave * notes.length); } // this function will parse a piano letter notes // the output will be an array of arrays // each sub array contains notes to be played simultaneously function parsePianoLetterNotes(txt) { var output = []; var lines = txt.split('\n'); var x = 0; var y = 0; for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (line == '') { // for some reason the piano letter notes // groups it all up into groups of 26 y += 26; } else { x = y; // get index of | symbol var bar = line.indexOf('|'); // decode octave var octave = line[bar - 1] - '0'; // iterate over 26 notes for (var j = bar + 1; j < bar + 27; j++) { var note = line[j]; // create new element if doesn't exist if (x >= output.length) { output.push([]); } // ignore rests if (note != '-') { output[x].push([note, octave]); } x++; } } } return output; } // this converts a song to C++ code // assuming the functions playNote() and delay() exist function convertSongToCxx(song) { var output = ''; for (var i = 0; i < song.length; i++) { // the thing is that at the moment the synth doesn't know how to play multiple notes // so we're just going to play the first one (if any) and hopefully it's ok var notes = song[i]; if (notes.length > 0) { var note = notes[0]; var freq = Math.round(getNoteFrequency(note[0], note[1])); output += `playNote(\${freq}, ms);`; output += '\n'; } else { // ok there's no note so just delay output += `delay(ms);`; output += '\n'; } } return output; } // text input box var input = document.createElement('textarea'); input.rows = 25; input.cols = 100; input.style.resize = 'none'; input.spellcheck = false; // https://pianoletternotes.blogspot.com/2019/07/tetris-theme-b.html input.value = `RH:4|d-A-g-gd-da-f-fd-da-f-fc-c| LH:2|g---g---f---f---d---d---c-| RH:4|-e-efed-A-g-gd-da-f-fd-da-| LH:2|--c---g---g---f---f---d---| RH:4|f-fc-c-e-efed-A-g-gd-da-f-| LH:2|d---c---c---g---g---f---f-| RH:4|fd-da-f-fc-c-e-efed-A-g-gd| LH:2|--d---d---c---c---g---g---| RH:4|-da-f-fd-da-f-fc-c-e-efe--| LH:2|f---f---d---d---c---c-----|`; // button var btn = document.createElement('button'); btn.innerText = 'convert to C++'; // output code var txt = document.createElement('code'); txt.innerText = 'paste piano notes into box above and hit the button boss'; // create the web page document.body.innerHTML = ''; document.body.appendChild(input); document.body.appendChild(document.createElement('br')); document.body.appendChild(document.createElement('br')); document.body.appendChild(btn); document.body.appendChild(document.createElement('br')); document.body.appendChild(document.createElement('br')); document.body.appendChild(txt); // register callback btn.onclick = function() { txt.innerText = convertSongToCxx(parsePianoLetterNotes(input.value)); }; // helper code: generate sine table function sinTable(samples, bits) { var table = '{'; for (var i = 0; i < samples; i++) { table += Math.round((Math.sin(2 * Math.PI * i / samples) + 1) / 2 * (Math.pow(2, bits) - 1)); if (i != samples - 1) table += ', '; } return table + '}'; } // ok now it's actual synth code float h0 = 0.75; float h1 = 0.50; float a = 0.2; float d = 0.2; float s = 0.4; float r = 0.2; function envelope(t) { if (t <= 0) { return 0; } else if (t <= a) { return t / a * h0; } else if (t <= a + d) { return h0 + (h1 - h0) * (t - a) / d; } else if (t <= a + d + s) { return h1; } else if (t <= a + d + s + r) { return h1 - (t - a - d - s) / r * h1; } } /* // c++ void playNote(int freq_hz, int duration_ms) { // math time: calculate the period of the wave in us int period_us = 1000000 / freq_hz; // calculate how many periods will elapse over the duration int periods = duration_ms * 1000 / period_us; // calculate half of a period in us int half_period_us = period_us >> 1; for (int i = 0; i < periods; i++) { digitalWrite(SPEAKER, HIGH); delayMicroseconds(half_period_us); digitalWrite(SPEAKER, LOW); delayMicroseconds(half_period_us); } } // idea is that all currently playing notes will be stored in some kind of array // every loop tick it will sample these all and play them using fast PWM struct Note { int start_ms; int freq; }; Note notes[MAX_NOTES]; void playNote(); ... int ms = millis(); int total = 0; for (int i = 0; i < MAX_NOTES; i++) { Note& n = notes[i]; if (n.start_ms < 0) continue; int t = ms - n.start_ms; int sample = (t * n.freq / 1000 * 0xFF) & 0xFF; int adsr = envelope(t); int wave = sample * adsr / 0xFF; total += wave; } setPWM(total); */