wam-community
Version:
A collection of prebuilt Web Audio Modules ready for use
147 lines (124 loc) • 5.07 kB
JavaScript
/** @typedef {import('@webaudiomodules/api').AudioWorkletGlobalScope} AudioWorkletGlobalScope */
/** @typedef {typeof import('./types').RingBuffer} RingBufferConstructor */
/** @typedef {import('./types').RingBuffer} RingBuffer */
/** @typedef {import('./types').TypedArray} TypedArray */
/** @typedef {import('./types').TypedArrayConstructor} TypedArrayConstructor */
/** @typedef {import('./types').WamArrayRingBuffer} IWamArrayRingBuffer */
/** @typedef {typeof import('./types').WamArrayRingBuffer} WamArrayRingBufferConstructor */
/** @typedef {import('./types').WamSDKBaseModuleScope} WamSDKBaseModuleScope */
/**
* @param {string} [moduleId]
* @returns {WamArrayRingBufferConstructor}
*/
const getWamArrayRingBuffer = (moduleId) => {
/** @type {AudioWorkletGlobalScope} */
// @ts-ignore
const audioWorkletGlobalScope = globalThis;
/**
* @implements {IWamArrayRingBuffer}
*/
class WamArrayRingBuffer {
/**
* Default number of arrays for which memory will be allocated.
*
* @type {number}
*/
static DefaultArrayCapacity = 2;
/**
* Returns a SharedArrayBuffer large enough to safely store the
* specified number of arrays of the specified length. Specify
* `maxArrayCapacity` to support storing more than
* `DefaultArrayCapacity` arrays in the buffer.
*
* @param {RingBufferConstructor} RingBuffer
* @param {number} arrayLength
* @param {TypedArrayConstructor} arrayType
* @param {number} [maxArrayCapacity=undefined]
* @returns {SharedArrayBuffer}
*/
static getStorageForEventCapacity(RingBuffer, arrayLength, arrayType, maxArrayCapacity = undefined) {
if (maxArrayCapacity === undefined) maxArrayCapacity = WamArrayRingBuffer.DefaultArrayCapacity;
else maxArrayCapacity = Math.max(maxArrayCapacity, WamArrayRingBuffer.DefaultArrayCapacity);
if (!arrayType.BYTES_PER_ELEMENT) {
throw new Error('Pass in a ArrayBuffer subclass');
}
const capacity = arrayLength * maxArrayCapacity;
return RingBuffer.getStorageForCapacity(capacity, arrayType);
}
/**
* Provides methods for writing / reading arrays to / from a
* RingBuffer. Specify `maxArrayCapacity` to support storing more
* than `DefaultArrayCapacity` arrays in the buffer.
*
* @param {RingBufferConstructor} RingBuffer
* @param {SharedArrayBuffer} sab
* @param {number} arrayLength
* @param {TypedArrayConstructor} arrayType
* @param {number} [maxArrayCapacity=undefined]
*/
constructor(RingBuffer, sab, arrayLength, arrayType, maxArrayCapacity = undefined) {
if (!arrayType.BYTES_PER_ELEMENT) {
throw new Error('Pass in a ArrayBuffer subclass');
}
/** @type {number} */
this._arrayLength = arrayLength;
/** @type {TypedArrayConstructor} */
this._arrayType = arrayType;
/** @type {number} */
this._arrayElementSizeBytes = arrayType.BYTES_PER_ELEMENT;
/** @type {number} */
this._arraySizeBytes = this._arrayLength * this._arrayElementSizeBytes;
/** @type {SharedArrayBuffer} */
this._sab = sab;
if (maxArrayCapacity === undefined) maxArrayCapacity = WamArrayRingBuffer.DefaultArrayCapacity;
else maxArrayCapacity = Math.max(maxArrayCapacity, WamArrayRingBuffer.DefaultArrayCapacity);
/** @type {TypedArray} */
this._arrayArray = new arrayType(this._arrayLength);
/** @type {RingBuffer} */
this._rb = new RingBuffer(this._sab, arrayType);
}
/**
* Attempt to write array to the ring buffer, returning whether
* or not it was successfully written.
*
* @param {TypedArray} array
* @returns {boolean}
*/
write(array) {
if (array.length !== this._arrayLength) return false;
const elementsAvailable = this._rb.availableWrite;
if (elementsAvailable < this._arrayLength) return false;
let success = true;
const elementsWritten = this._rb.push(array);
if (elementsWritten != this._arrayLength) success = false;
return success;
}
/**
* Attempt to read array from the ring buffer, returning whether
* or not it was successfully read. If `newest` is true, skips
* all pending arrays but the most recently written one.
*
* @param {TypedArray} array
* @param {boolean} newest
* @returns {boolean}
*/
read(array, newest) {
if (array.length !== this._arrayLength) return false;
const elementsAvailable = this._rb.availableRead;
if (elementsAvailable < this._arrayLength) return false;
// skip all but most recently written array?
if (newest && elementsAvailable > this._arrayLength) this._rb.pop(elementsAvailable - this._arrayLength);
let success = false;
const elementsRead = this._rb.pop(array);
if (elementsRead === this._arrayLength) success = true;
return success;
}
}
if (audioWorkletGlobalScope.AudioWorkletProcessor) {
/** @type {WamSDKBaseModuleScope} */
const ModuleScope = audioWorkletGlobalScope.webAudioModules.getModuleScope(moduleId);
if (!ModuleScope.WamArrayRingBuffer) ModuleScope.WamArrayRingBuffer = WamArrayRingBuffer;
}
return WamArrayRingBuffer;
};
export default getWamArrayRingBuffer;