RetroArch/emscripten/library_rwebaudio.js

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);