Skip to content

Instantly share code, notes, and snippets.

@kigiri
Created April 4, 2020 08:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kigiri/751c6870dd406fb1be7ad38c38fdce12 to your computer and use it in GitHub Desktop.
Save kigiri/751c6870dd406fb1be7ad38c38fdce12 to your computer and use it in GitHub Desktop.
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