Last active
February 16, 2018 00:43
-
-
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.
This file contains hidden or 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
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