116 lines
4.1 KiB
JavaScript
116 lines
4.1 KiB
JavaScript
//"use strict";
|
|
|
|
var LibraryRWebAudio = {
|
|
$RWA: {
|
|
/* add 10 ms of silence on start, seems to prevent underrun on start/unpausing (terrible crackling in firefox) */
|
|
MIN_START_OFFSET_SEC: 0.01,
|
|
/* firefox and safari need more latency (transparent to audio driver) */
|
|
EXTRA_LATENCY_SEC_NONCHROME: 0.01,
|
|
EXTRA_LATENCY_SEC_CHROME: 0,
|
|
PLATFORM_EMSCRIPTEN_BROWSER_CHROMIUM: 1,
|
|
context: null,
|
|
contextRunning: false,
|
|
nonblock: false,
|
|
endTime: 0,
|
|
latency: 0,
|
|
virtualBufferFrames: 0,
|
|
currentTimeDiff: 0,
|
|
extraLatencySec: 0
|
|
},
|
|
|
|
/* AudioContext.currentTime can be inaccurate: https://bugzilla.mozilla.org/show_bug.cgi?id=901247 */
|
|
$RWebAudioGetCurrentTime: function() {
|
|
return performance.now() / 1000 - RWA.currentTimeDiff;
|
|
},
|
|
|
|
$RWebAudioStateChangeCB: function() {
|
|
RWA.contextRunning = RWA.context.state == "running";
|
|
},
|
|
|
|
RWebAudioResumeCtx: function() {
|
|
if (!RWA.contextRunning) RWA.context.resume();
|
|
return RWA.contextRunning;
|
|
},
|
|
|
|
RWebAudioInit__deps: ["$RWebAudioStateChangeCB", "platform_emscripten_get_browser"],
|
|
RWebAudioInit: function(latency) {
|
|
var ac = window.AudioContext || window.webkitAudioContext;
|
|
if (!ac) return 0;
|
|
|
|
RWA.context = new ac();
|
|
RWA.currentTimeDiff = performance.now() / 1000 - RWA.context.currentTime;
|
|
RWA.nonblock = false;
|
|
RWA.endTime = 0;
|
|
RWA.latency = latency;
|
|
RWA.virtualBufferFrames = Math.round(RWA.latency * RWA.context.sampleRate / 1000);
|
|
RWA.context.addEventListener("statechange", RWebAudioStateChangeCB);
|
|
RWebAudioStateChangeCB();
|
|
RWA.extraLatencySec = (_platform_emscripten_get_browser() == RWA.PLATFORM_EMSCRIPTEN_BROWSER_CHROMIUM) ? RWA.EXTRA_LATENCY_SEC_CHROME : RWA.EXTRA_LATENCY_SEC_NONCHROME;
|
|
|
|
return 1;
|
|
},
|
|
|
|
RWebAudioSampleRate: function() {
|
|
return RWA.context.sampleRate;
|
|
},
|
|
|
|
RWebAudioQueueBuffer__deps: ["$RWebAudioGetCurrentTime", "RWebAudioResumeCtx", "RWebAudioWriteAvailFrames"],
|
|
RWebAudioQueueBuffer: function(num_frames, left, right) {
|
|
if (RWA.nonblock && _RWebAudioWriteAvailFrames() < num_frames) return 0;
|
|
if (!_RWebAudioResumeCtx()) return 0;
|
|
|
|
var buffer = RWA.context.createBuffer(2, num_frames, RWA.context.sampleRate);
|
|
buffer.getChannelData(0).set(HEAPF32.subarray(left >> 2, (left >> 2) + num_frames));
|
|
buffer.getChannelData(1).set(HEAPF32.subarray(right >> 2, (right >> 2) + num_frames));
|
|
var bufferSource = RWA.context.createBufferSource();
|
|
bufferSource.buffer = buffer;
|
|
bufferSource.connect(RWA.context.destination);
|
|
|
|
var currentTime = RWebAudioGetCurrentTime();
|
|
/* when empty, start rounded up to nearest 1 ms, add MIN_START_OFFSET_SEC */
|
|
var startTime = RWA.endTime > currentTime ? RWA.endTime : Math.ceil(currentTime * 1000) / 1000 + RWA.MIN_START_OFFSET_SEC;
|
|
RWA.endTime = startTime + buffer.duration;
|
|
bufferSource.start(startTime + RWA.extraLatencySec);
|
|
|
|
return num_frames;
|
|
},
|
|
|
|
RWebAudioStop: function() {
|
|
return true;
|
|
},
|
|
|
|
RWebAudioStart: function() {
|
|
return true;
|
|
},
|
|
|
|
RWebAudioSetNonblockState: function(state) {
|
|
RWA.nonblock = state;
|
|
},
|
|
|
|
RWebAudioFree__deps: ["$RWebAudioStateChangeCB"],
|
|
RWebAudioFree: function() {
|
|
RWA.context.removeEventListener("statechange", RWebAudioStateChangeCB);
|
|
RWA.context.close();
|
|
RWA.contextRunning = false;
|
|
},
|
|
|
|
RWebAudioBufferSizeFrames: function() {
|
|
return RWA.virtualBufferFrames;
|
|
},
|
|
|
|
RWebAudioWriteAvailFrames__deps: ["$RWebAudioGetCurrentTime"],
|
|
RWebAudioWriteAvailFrames: function() {
|
|
var avail = Math.round(((RWA.latency / 1000) - RWA.endTime + RWebAudioGetCurrentTime()) * RWA.context.sampleRate);
|
|
if (avail <= 0) return 0;
|
|
if (avail >= RWA.virtualBufferFrames) return RWA.virtualBufferFrames;
|
|
return avail;
|
|
},
|
|
|
|
RWebAudioRecalibrateTime: function() {
|
|
if (RWA.contextRunning) RWA.currentTimeDiff = performance.now() / 1000 - RWA.context.currentTime;
|
|
}
|
|
};
|
|
|
|
autoAddDeps(LibraryRWebAudio, '$RWA');
|
|
addToLibrary(LibraryRWebAudio);
|