diff --git a/blarf.js b/blarf.js index 6daa23d..fb1a73f 100644 --- a/blarf.js +++ b/blarf.js @@ -16,47 +16,65 @@ var CanvCtx = Canvus.getContext("2d") var AudCtx - var AudScript + var AudScript, AudWorklet var AudHz function create_audio(hz, channels) { if(AudCtx) { AudCtx.close() + AudScript = null + AudWorklet = null } AudHz = hz + var DebugSine = 0 + AudCtx = new AudioContext({sampleRate: hz}) - AudScript = AudCtx.createScriptProcessor(1024, channels, channels) - AudScript.onaudioprocess = function(e) { - var outL = e.outputBuffer.getChannelData(0) - var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null - - var leftToWrite = outL.length - var offset = 0 - - while(AudioQueue.length && leftToWrite) { - var amount = Math.min(leftToWrite, AudioQueue[0].left.length) + + if(AudCtx.audioWorklet) { + AudCtx.audioWorklet.addModule("rawpcmworklet.js").then(function() { + AudWorklet = new AudioWorkletNode(AudCtx, "rawpcmworklet", { + outputChannelCount: [2] + }) + AudWorklet.connect(AudCtx.destination) + }) + } else { + AudScript = AudCtx.createScriptProcessor(4096, channels, channels) + AudScript.onaudioprocess = function(e) { + var outL = e.outputBuffer.getChannelData(0) + var outR = channels > 1 ? e.outputBuffer.getChannelData(1) : null - outL.set(AudioQueue[0].left.subarray(0, amount), offset) - if(outR) outR.set(AudioQueue[0].right.subarray(0, amount), offset) - - AudioQueue[0].left = AudioQueue[0].left.subarray(amount) - if(outR) AudioQueue[0].right = AudioQueue[0].right.subarray(amount) - - if(AudioQueue[0].left.length == 0) { - AudioQueue.shift() + /*for(var i = 0; i < outL.length; i++) { + outL[i] = Math.sin(440 * 2 * 3.14159 * (DebugSine / AudHz)) + DebugSine++ } - leftToWrite -= amount - offset += amount - } - - if(RenderStartTime && leftToWrite) { - buffering(1000) + AudioQueue = [] + return*/ + + var leftToWrite = outL.length + var offset = 0 + + while(AudioQueue.length && leftToWrite) { + var amount = Math.min(leftToWrite, AudioQueue[0].left.length) + + outL.set(AudioQueue[0].left.subarray(0, amount), offset) + if(outR) outR.set(AudioQueue[0].right.subarray(0, amount), offset) + + AudioQueue[0].left = AudioQueue[0].left.subarray(amount) + if(outR) AudioQueue[0].right = AudioQueue[0].right.subarray(amount) + + if(AudioQueue[0].left.length == 0) { + AudioQueue.shift() + } + + leftToWrite -= amount + offset += amount + } } + AudScript.connect(AudCtx.destination) } - AudScript.connect(AudCtx.destination) } var LastControlsInterrupt @@ -149,11 +167,6 @@ } var VideoBufferingOffset = 0 - function buffering(millis) { - //var silence = new Float32Array(millis * 48); - //AudioQueue.push({left: silence, right: silence}) - //VideoBufferingOffset += millis - } function toHex(buffer) { return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join(''); @@ -386,6 +399,12 @@ ws.onmessage = function(ev) { ebml.poosh(new Uint8Array(ev.data)) ebml.parse() + + // It would make more sense for this to be in `render` but we need the guarantee that this will run when the tab is out of focus + if(AudCtx.state == "running" && AudWorklet && AudioQueue.length) { + AudWorklet.port.postMessage({msg: "data", audio: AudioQueue}) + AudioQueue.length = 0 + } } ws.onclose = function(ev) { setTimeout(reconnect_ws, 5000) diff --git a/rawpcmworklet.js b/rawpcmworklet.js new file mode 100644 index 0000000..238dc0c --- /dev/null +++ b/rawpcmworklet.js @@ -0,0 +1,47 @@ +// To explain succinctly, the people who designed AudioWorklet and +// deprecated ScriptProcessorNode are retarded and we need a worklet +// that does basically nothing to get glitchless audio. + +class RawPCMWorklet extends AudioWorkletProcessor { + constructor() { + super() + + this.left = new Float32Array() + this.right = new Float32Array() + + this.port.onmessage = (event) => { + var newaudioframes = event.data.audio + + for(var i = 0; i < newaudioframes.length; i++) { + var newleft = new Float32Array(this.left.length + newaudioframes[i].left.length) + newleft.set(this.left, 0) + newleft.set(newaudioframes[i].left, this.left.length) + this.left = newleft + + var newright = new Float32Array(this.right.length + newaudioframes[i].right.length) + newright.set(this.right, 0) + newright.set(newaudioframes[i].right, this.right.length) + this.right = newright + } + } + } + + process(inputs, outputs, parameters) { + const output = outputs[0] + + var left = output[0] + var right = output[1] + + var available = Math.min(left.length, this.left.length) + + left.set(this.left.slice(0, available)) + right.set(this.right.slice(0, available)) + + this.left = this.left.slice(available) + this.right = this.right.slice(available) + + return true; + } +} + +registerProcessor('rawpcmworklet', RawPCMWorklet);