Skip to content

Instantly share code, notes, and snippets.

@funnbot
Last active February 16, 2018 00:43
Show Gist options
  • Save funnbot/2b7f3d76b322c1cd62936f6bd809fa56 to your computer and use it in GitHub Desktop.
Save funnbot/2b7f3d76b322c1cd62936f6bd809fa56 to your computer and use it in GitHub Desktop.
Heres the piano thing i made, it turns text to some thing, returns PCM buffer so figure out how to play it yourself.
const tone = require("tonegenerator")
const notes = {
"c": [16.35, 32.70, 65.41, 130.81, 261.63, 523.25, 1046.50, 2093.00],
"c#": [17.32, 34.65, 69.30, 138.59, 277.18, 554.37, 1108.73, 2217.46],
"d": [18.35, 36.71, 73.42, 146.83, 293.66, 587.33, 1174.66, 2349.32],
"d#": [19.45, 38.89, 77.78, 155.56, 311.13, 622.25, 1244.51, 2489.02],
"e": [20.60, 41.20, 82.41, 164.81, 329.63, 659.26, 1318.51, 2637.02],
"e#": [21.83, 43.65, 87.31, 174.61, 349.23, 698.46, 1396.91, 2793.83],
"f": [21.83, 43.65, 87.31, 174.61, 349.23, 698.46, 1396.91, 2793.83],
"f#": [23.12, 46.25, 92.50, 185.00, 369.99, 739.99, 1479.98, 2959.96],
"g": [24.50, 49.00, 98.00, 196.00, 392.00, 783.99, 1567.98, 3135.96],
"g#": [25.96, 51.91, 103.83, 207.65, 415.30, 830.61, 1661.22, 3322.44],
"a": [27.50, 55.00, 110.00, 220.00, 440.00, 880.00, 1760.00, 3520.00],
"a#": [29.14, 58.27, 116.54, 233.08, 466.16, 932.33, 1864.66, 3729.31],
"b": [30.87, 61.74, 123.47, 246.94, 493.88, 987.77, 1975.53, 3951.07],
"b#": [16.35, 32.70, 65.41, 130.81, 261.63, 523.25, 1046.50, 2093.00],
}
const shapes = [
"triangle",
"square",
"sine",
"saw"
]
module.exports = function(text) {
const input = new InputStream(text);
const tokenizer = new Tokenizer(input);
const parser = new Parse(tokenizer);
const tracks = CreateTracks(parser);
const track = AllignTracks(tracks);
const buffer = TrackToBuffer(track);
return buffer;
}
function TrackToBuffer(track) {
const data = Uint8Array.from(track, function(val) {
return val + 128
})
return Buffer.from(data)
}
function AllignTracks(tracks) {
let track = [];
let longest = 0;
for (let i = 0; i < tracks.length; i++)
if (tracks[i].length > tracks[longest].length) longest = i;
for (let i = 0; i < tracks[longest].length; i++) {
let val = 0;
for (let j = 0; j < tracks.length; j++) {
val += tracks[j][i] || 0;
}
track.push(val);
}
return track;
}
function CreateTracks(input) {
const tracks = [
[]
];
let trk = 0;
while (!input.eof()) {
const token = input.next();
if (token.type === "track") {
trk++;
if (!tracks[trk]) tracks[trk] = [];
continue;
}
tracks[trk] = tracks[trk].concat(tone(token))
}
return tracks;
}
function Parse(input) {
let octave = 5;
let shape = "triangle"
function next() {
const token = input.next();
if (token.type === "note") {
const sharp = token.sharp ? "#" : "";
return {
freq: notes[token.note + sharp][octave],
lengthInSecs: token.length / 8,
shape
};
} else if (token.type === "rest") {
return { freq: 20000, volume: 30, lengthInSecs: token.length / 8}
} else if (token.type === "octave") {
octave = token.num;
return next();
} else if (token.type === "tone") {
shape = shapes[token.shape];
return next();
}
return token;
}
return { next, eof: input.eof };
}
function Tokenizer(input) {
function next() {
const char = input.next();
if (isRest(char)) {
const chars = char + read(isRest);
const length = restLength(chars);
return { type: "rest", length };
} else if (isNote(char)) {
const chars = char + read(isMatch(char));
const length = noteLength(char, chars);
const note = char.toLowerCase();
var sharp = isSharp(input.peek());
if (sharp) input.next();
return { type: "note", note, sharp, length };
} else if (isOctave(char))
return { type: "octave", num: parseInt(char) };
else if (isShape(char))
return { type: "tone", shape: "{}[]".indexOf(char) };
else if (isBreak(char)) return next();
else if (isTrack(char)) return { type: "track" };
else if (char === " ") return next();
return { type: "error", msg: "Invalid Character: " + char };
}
function read(action) {
let res = "";
while (!input.eof() && action(input.peek())) {
res += input.next();
}
return res;
}
function noteLength(note, chars) {
const l = note.toLowerCase();
const u = note.toUpperCase();
let length = 0;
for (let i = 0; i < chars.length; i++) {
if (chars[i] === l) length++;
else if (chars[i] === u) length += 4;
}
return length;
}
function restLength(chars) {
let length = 0;
for (let i = 0; i < chars.length; i++) {
if (chars[i] === "-") length++;
else if (chars[i] === "_") length += 4;
}
return length;
}
const isShape = c => "{}[]".includes(c);
const isRest = c => "-_".includes(c);
const isBreak = c => c === ".";
const isSharp = c => c === "#";
const isNote = c => "abcdefgABCDEFG".includes(c);
const isTrack = c => c === "|";
const isOctave = c => "12345678".includes(c);
const isMatch = function(m) {
m = m.toLowerCase();
return c => m === c.toLowerCase();
}
return { next, eof: input.eof };
}
function InputStream(text) {
let pos = 0;
function next() {
return text[pos++];
}
function peek() {
return text[pos];
}
function eof() {
return peek() === undefined;
}
return { next, peek, eof };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment