Created
April 4, 2020 08:30
-
-
Save kigiri/751c6870dd406fb1be7ad38c38fdce12 to your computer and use it in GitHub Desktop.
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 parseNote = noteStr => { | |
const [ note, rest = '' ] = noteStr.split('.') | |
const duration = durations[rest.slice(0, -1)] / tempo | |
return { | |
duration, | |
frequency: notes[note], | |
type: types[rest[rest.length - 1]] || types[0], | |
} | |
} | |
const writeString = (v,o,t) => [...t].forEach((c,i)=>v.setUint8(o+i,c.charCodeAt(0))) | |
const encodeWAV = async (input) => { | |
const notes = input.split('+').map(parseNote) | |
const duration = notes.reduce((a,n) => a + n.duration, 0) | |
const offline = new OfflineAudioContext(1, 44100*duration, 44100) | |
let delay = offline.currentTime | |
for (const note of notes) { | |
const o = offline.createOscillator() | |
const g = offline.createGain() | |
o.type = note.type | |
o.frequency.value = note.frequency | |
o.connect(g) | |
g.connect(offline.destination) | |
o.start(delay) | |
g.gain.exponentialRampToValueAtTime(1e-9, delay += note.duration) | |
} | |
const samples = (await offline.startRendering()).getChannelData(0) | |
const buffer = new ArrayBuffer(44 + samples.length * 2) | |
const view = new DataView(buffer) | |
writeString(view, 0, 'RIFF') // RIFF identifier | |
view.setUint32(4, 32 + samples.length * 2, true) // file length | |
writeString(view, 8, 'WAVE') // RIFF type | |
writeString(view, 12, 'fmt ') // format chunk identifier | |
view.setUint32(16, 16, true) // format chunk length | |
view.setUint16(20, 1, true) // sample format (raw) | |
view.setUint16(22, 1, true) // channel count | |
view.setUint32(24, 44100, true) // sample rate | |
view.setUint32(28, 44100 * 4, true) // byte rate (sample rate * block align) | |
view.setUint16(32, 4, true) // block align (channel count * bytes per sample) | |
view.setUint16(34, 16, true) // bits per sample | |
writeString(view, 36, 'data') // data chunk identifier | |
view.setUint32(40, samples.length * 2, true) // data chunk length | |
var offset = 44 // floatTo16BitPCM | |
for (var i = 0; i < samples.length; i++, offset += 2) { | |
var s = Math.max(-1, Math.min(1, samples[i])) | |
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true) | |
} | |
const reader = new FileReader() | |
const onload = new Promise(s => reader.onload = s) | |
reader.readAsDataURL(new Blob([view], { type: 'audio/wav' })) | |
const a = document.createElement('audio') | |
a.src = (await onload).target.result | |
a.type = 'audio/wav' | |
return a | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment