Last active
May 12, 2022 01:24
-
-
Save CobaltXII/f2e3c703e9c46eb3a94921e9d5741c0d to your computer and use it in GitHub Desktop.
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); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment