Skip to content

Commit

Permalink
Merge pull request #60 from ircam-ismm/feature/legacy-decode-audio-data
Browse files Browse the repository at this point in the history
feat: add legacy decodeAudioData signature
  • Loading branch information
b-ma authored Dec 29, 2023
2 parents 740eff3 + 3fe7daa commit 5e20023
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 7 deletions.
34 changes: 34 additions & 0 deletions .scripts/wpt-mock/XMLHttpRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const fs = require('node:fs');
const path = require('node:path');

// to be passed to wtp-runner step
// window.XMLHttpRequest = XMLHttpRequest;
module.exports = function createXMLHttpRequest(basepath) {
return class XMLHttpRequest {
constructor() {
this._pathname;
this.onload;
this.onerror;
this.response;
}

open(_protocol, url) {
this._pathname = url;
}

send() {
let buffer;

try {
const pathname = path.join(basepath, this._pathname);
buffer = fs.readFileSync(pathname).buffer;
} catch (err) {
this.onerror(err);
return;
}

this.response = buffer;
this.onload();
}
}
}
87 changes: 87 additions & 0 deletions .scripts/wpt-mock/wpt-buffer-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const path = require('node:path');

const createXMLHttpRequest = require('./XMLHttpRequest.js');
const { OfflineAudioContext } = require('../../index.cjs');

// create a XMLHttpRequest to be passed to the runner
// can be configured to handle the difference between process.cwd() and given path
// window.XMLHttpRequest = createXMLHttpRequest(rootURL (?))
const XMLHttpRequest = createXMLHttpRequest(path.join('examples', 'samples'));
// maybe should be passed to wtp-runner setup too
// window.alert = console.log.bind(console);
const alert = console.log.bind(console);

// this is the BufferLoader from the wpt suite
function BufferLoader(context, urlList, callback) {
this.context = context;
this.urlList = urlList;
this.onload = callback;
this.bufferList = new Array();
this.loadCount = 0;
}

BufferLoader.prototype.loadBuffer = function(url, index) {
// Load buffer asynchronously
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";

var loader = this;

request.onload = function() {
loader.context.decodeAudioData(request.response, decodeSuccessCallback, decodeErrorCallback);
};

request.onerror = function() {
alert('BufferLoader: XHR error');
};

var decodeSuccessCallback = function(buffer) {
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
};

var decodeErrorCallback = function() {
alert('decodeErrorCallback: decode error');
};

request.send();
}

BufferLoader.prototype.load = function() {
for (var i = 0; i < this.urlList.length; ++i)
this.loadBuffer(this.urlList[i], i);
}

// ----------------------------------------------
// testing
// ----------------------------------------------

const offlineContext = new OfflineAudioContext({
numberOfChannels: 1,
length: 1,
sampleRate: 48000,
});

const okFiles = [path.join('sample.wav')];
const err1Files = [path.join('corrupt.wav')];
const err2Files = [path.join('donotexists.wav')];

{
// should work
const loader = new BufferLoader(offlineContext, okFiles, audioBuffer => console.log(audioBuffer));
loader.load();
}

{
// should fail - decode error
const loader = new BufferLoader(offlineContext, err1Files, audioBuffer => console.log(audioBuffer));
loader.load();
}

{
// should fail - file not found
const loader = new BufferLoader(offlineContext, err2Files, audioBuffer => console.log(audioBuffer));
loader.load();
}
50 changes: 50 additions & 0 deletions examples/decoding-legacy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import fs from 'node:fs';
import path from 'node:path';
import { AudioContext, OfflineAudioContext } from '../index.mjs';

const latencyHint = process.env.WEB_AUDIO_LATENCY === 'playback' ? 'playback' : 'interactive';
const audioContext = new AudioContext({ latencyHint });

const offlineContext = new OfflineAudioContext({
numberOfChannels: 1,
length: 1,
sampleRate: audioContext.sampleRate
});

const okFile = path.join('examples', 'samples', 'sample.wav');
const errFile = path.join('examples', 'samples', 'corrupt.wav');

function decodeSuccess(buffer) {
console.log(`decodeSuccess`);
const src = audioContext.createBufferSource();
src.buffer = buffer;
src.connect(audioContext.destination);
src.start();
}

function decodeError(err) {
console.log(`decodeError callback: ${err.message}`);
}

{
// audioContext decode success
const okArrayBuffer = fs.readFileSync(okFile).buffer;
audioContext.decodeAudioData(okArrayBuffer, decodeSuccess, decodeError);
// audioContext decode error
const errArrayBuffer = fs.readFileSync(errFile).buffer;
audioContext.decodeAudioData(errArrayBuffer, decodeSuccess, decodeError);
}

await new Promise(resolve => setTimeout(resolve, 3000));

{
// offlineContext decode success
const okArrayBuffer = fs.readFileSync(okFile).buffer;
offlineContext.decodeAudioData(okArrayBuffer, decodeSuccess, decodeError);
// offlineContext decode error
const errArrayBuffer = fs.readFileSync(errFile).buffer;
offlineContext.decodeAudioData(errArrayBuffer, decodeSuccess, decodeError);
}

await new Promise(resolve => setTimeout(resolve, 3000));
await audioContext.close();
22 changes: 19 additions & 3 deletions js/AudioContext.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { isFunction } = require('./lib/utils.js');

let contextId = 0;

const kProcessId = Symbol('processId');
Expand Down Expand Up @@ -45,15 +47,29 @@ module.exports = function(NativeAudioContext) {
}
}

decodeAudioData(audioData) {
// This is not exactly what the spec says, but if we reject the promise
// when `decodeErrorCallback` is present the program will crash in an
// unexpected manner
// cf. https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata
decodeAudioData(audioData, decodeSuccessCallback, decodeErrorCallback) {
if (!(audioData instanceof ArrayBuffer)) {
throw new TypeError(`Failed to execute 'decodeAudioData': parameter 1 is not of type 'ArrayBuffer'`);
}

try {
const audioBuffer = super.decodeAudioData(audioData);
return Promise.resolve(audioBuffer);

if (isFunction(decodeSuccessCallback)) {
decodeSuccessCallback(audioBuffer);
} else {
return Promise.resolve(audioBuffer);
}
} catch (err) {
return Promise.reject(err);
if (isFunction(decodeErrorCallback)) {
decodeErrorCallback(err);
} else {
return Promise.reject(err);
}
}
}
}
Expand Down
21 changes: 17 additions & 4 deletions js/OfflineAudioContext.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { NotSupportedError } = require('./lib/errors.js');
const { isPlainObject, isPositiveInt, isPositiveNumber } = require('./lib/utils.js');
const { isFunction, isPlainObject, isPositiveInt, isPositiveNumber } = require('./lib/utils.js');

module.exports = function patchOfflineAudioContext(NativeOfflineAudioContext) {
class OfflineAudioContext extends NativeOfflineAudioContext {
Expand Down Expand Up @@ -36,16 +36,29 @@ module.exports = function patchOfflineAudioContext(NativeOfflineAudioContext) {
}
}

decodeAudioData(audioData) {
// This is not exactly what the spec says, but if we reject the promise
// when `decodeErrorCallback` is present the program will crash in an
// unexpected manner
// cf. https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata
decodeAudioData(audioData, decodeSuccessCallback, decodeErrorCallback) {
if (!(audioData instanceof ArrayBuffer)) {
throw new TypeError(`Failed to execute 'decodeAudioData': parameter 1 is not of type 'ArrayBuffer'`);
}

try {
const audioBuffer = super.decodeAudioData(audioData);
return Promise.resolve(audioBuffer);

if (isFunction(decodeSuccessCallback)) {
decodeSuccessCallback(audioBuffer);
} else {
return Promise.resolve(audioBuffer);
}
} catch (err) {
return Promise.reject(err);
if (isFunction(decodeErrorCallback)) {
decodeErrorCallback(err);
} else {
return Promise.reject(err);
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions js/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ exports.isPositiveInt = function isPositiveInt(n) {
exports.isPositiveNumber = function isPositiveNumber(n) {
return Number(n) === n && 0 < n;
};

exports.isFunction = function isFunction(val) {
return Object.prototype.toString.call(val) == '[object Function]' ||
Object.prototype.toString.call(val) == '[object AsyncFunction]';
}

0 comments on commit 5e20023

Please sign in to comment.