Skip to content

Instantly share code, notes, and snippets.

@CobaltXII
Last active May 12, 2022 01:24
Show Gist options
  • Save CobaltXII/f2e3c703e9c46eb3a94921e9d5741c0d to your computer and use it in GitHub Desktop.
Save CobaltXII/f2e3c703e9c46eb3a94921e9d5741c0d to your computer and use it in GitHub Desktop.
// 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