Skip to content

Commit

Permalink
feat(poc): Added render and download button to the scheduled-sequenci…
Browse files Browse the repository at this point in the history
…ng demo
  • Loading branch information
meszaros-lajos-gyorgy committed Feb 7, 2019
1 parent ca925c9 commit 096cfa7
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 8 deletions.
4 changes: 2 additions & 2 deletions poc/offline-audio-context/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@ const forceDownload = (blob, filename = 'output.wav') => {
document.body.removeChild(link)
}

const makeDownload = (abuffer, totalSamples) => {
forceDownload(bufferToWave(abuffer, totalSamples), 'dummy.wav')
const makeDownload = (abuffer, totalSamples, filename) => {
forceDownload(bufferToWave(abuffer, totalSamples), `${filename}-${Date.now()}.wav`)
}
2 changes: 1 addition & 1 deletion poc/offline-audio-context/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const demo = () => {

render(a, ctx)
ctx.startRendering().then(abuffer => {
makeDownload(abuffer, ctx.length)
makeDownload(abuffer, ctx.length, 'offline-audio-context')
}).catch(e => {
console.error(e)
})
Expand Down
128 changes: 128 additions & 0 deletions poc/scheduled-sequencing/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/* global Blob, URL */

/*
function floatTo16BitPCM(output, offset, input) {
for (let i = 0; i < input.length; i++, offset += 2) {
let s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function writeString(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function encodeWAV(samples) {
let buffer = new ArrayBuffer(44 + samples.length * 2);
let view = new DataView(buffer);
// RIFF identifier
writeString(view, 0, 'RIFF');
// RIFF chunk length
view.setUint32(4, 36 + samples.length * 2, true);
// RIFF type
writeString(view, 8, 'WAVE');
// format chunk identifier
writeString(view, 12, 'fmt ');
// format chunk length
view.setUint32(16, 16, true);
// sample format (raw)
view.setUint16(20, 1, true);
// channel count
view.setUint16(22, numChannels, true);
// sample rate
view.setUint32(24, sampleRate, true);
// byte rate (sample rate * block align)
view.setUint32(28, sampleRate * 4, true);
// block align (channel count * bytes per sample)
view.setUint16(32, numChannels * 2, true);
// bits per sample
view.setUint16(34, 16, true);
// data chunk identifier
writeString(view, 36, 'data');
// data chunk length
view.setUint32(40, samples.length * 2, true);
floatTo16BitPCM(view, 44, samples);
return view;
}
*/

// Convert an AudioBuffer to a Blob using WAVE representation
const bufferToWave = (abuffer, len) => {
const numOfChan = abuffer.numberOfChannels

const length = len * numOfChan * 2 + 44

const buffer = new ArrayBuffer(length)

const view = new DataView(buffer)

const channels = []; let i; let sample

let offset = 0

let pos = 0

// write WAVE header
setUint32(0x46464952) // "RIFF"
setUint32(length - 8) // file length - 8
setUint32(0x45564157) // "WAVE"

setUint32(0x20746d66) // "fmt " chunk
setUint32(16) // length = 16
setUint16(1) // PCM (uncompressed)
setUint16(numOfChan)
setUint32(abuffer.sampleRate)
setUint32(abuffer.sampleRate * 2 * numOfChan) // avg. bytes/sec
setUint16(numOfChan * 2) // block-align
setUint16(16) // 16-bit (hardcoded in this demo)

setUint32(0x61746164) // "data" - chunk
setUint32(length - pos - 4) // chunk length

// write interleaved data
for (i = 0; i < abuffer.numberOfChannels; i++) { channels.push(abuffer.getChannelData(i)) }

while (pos < length) {
for (i = 0; i < numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset])) // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0 // scale to 16-bit signed int
view.setInt16(pos, sample, true) // write 16-bit sample
pos += 2
}
offset++ // next source sample
}

// create Blob
return new Blob([buffer], { type: 'audio/wav' })

function setUint16 (data) {
view.setUint16(pos, data, true)
pos += 2
}

function setUint32 (data) {
view.setUint32(pos, data, true)
pos += 4
}
}

const forceDownload = (blob, filename = 'output.wav') => {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
document.body.appendChild(link)
link.style = 'display:none'
link.href = url
link.download = filename
link.click()
URL.revokeObjectURL(url)
document.body.removeChild(link)
}

const makeDownload = (abuffer, totalSamples, filename) => {
forceDownload(bufferToWave(abuffer, totalSamples), `${filename}-${Date.now()}.wav`)
}
4 changes: 3 additions & 1 deletion poc/scheduled-sequencing/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
<a href="/">demo index</a> » <b>scheduled sequencing</b>
</nav>
<h2>Play a few notes, which were pre-scheduled and not triggered via setTimeout</h2>
<button>play audio demo</button>
<button id="play">play audio demo</button>
<button id="render">render and download audio demo</button>
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>
<script src="../common/virtual-webaudio.js"></script>
<script src="../common/helpers.js"></script>
<script src="./helpers.js"></script>
<script src="./script.js"></script>
<script src="/reload/reload.js"></script>
</body>
Expand Down
18 changes: 14 additions & 4 deletions poc/scheduled-sequencing/script.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global virtualWebaudio, AudioContext, once */
/* global virtualWebaudio, AudioContext, OfflineAudioContext, once, makeDownload */

const { VirtualAudioContext, render } = virtualWebaudio

Expand Down Expand Up @@ -35,10 +35,20 @@ const create = () => {

const a = create()

const demo = () => {
const ctx = new AudioContext()
const playDemo = () => {
render(a, new AudioContext())
}

const renderDemo = () => {
const ctx = new OfflineAudioContext(1, 44100 * 5, 44100)

render(a, ctx)
ctx.startRendering().then(abuffer => {
makeDownload(abuffer, ctx.length, 'scheduled-playback')
}).catch(e => {
console.error(e)
})
}

document.getElementsByTagName('button')[0].addEventListener('click', once(demo))
document.getElementById('play').addEventListener('click', once(playDemo))
document.getElementById('render').addEventListener('click', once(renderDemo))

0 comments on commit 096cfa7

Please sign in to comment.