ringbuf.js
Version:
Wait-free thread-safe single-consumer single-producer ring buffer using SharedArrayBuffer.
525 lines (509 loc) • 20.6 kB
text/typescript
/**
* Represents a base constructor type for typed arrays.
* This type defines the various constructor signatures that typed arrays can have.
*
* @template T - The specific typed array type (e.g., Int8Array, Uint8Array).
*/
type TypedArrayConstructorBase<T> = {
/**
* Constructs a new typed array with no elements.
* @returns A new typed array instance.
*/
new (): T;
/**
* Constructs a new typed array with the specified length.
* @param length - The length of the new typed array.
* @returns A new typed array instance.
*/
new (length: number): T;
/**
* Constructs a new typed array from an array-like or iterable object.
* @param typedArray - An array-like or iterable object to initialize the typed array.
* @returns A new typed array instance.
*/
new (typedArray: ArrayLike<number> | Iterable<number>): T;
/**
* Constructs a new typed array from an object.
* @param object - An object to initialize the typed array.
* @returns A new typed array instance.
*/
new (object: object): T;
/**
* Constructs a new typed array from an ArrayBuffer.
* @param buffer - The ArrayBuffer to use as the storage for the typed array.
* @returns A new typed array instance.
*/
new (buffer: ArrayBufferLike): T;
/**
* Constructs a new typed array from an ArrayBuffer with a specified byte offset.
* @param buffer - The ArrayBuffer to use as the storage for the typed array.
* @param byteOffset - The offset, in bytes, to the first element in the array.
* @returns A new typed array instance.
*/
new (buffer: ArrayBufferLike, byteOffset: number): T;
/**
* Constructs a new typed array from an ArrayBuffer with a specified byte offset and length.
* @param buffer - The ArrayBuffer to use as the storage for the typed array.
* @param byteOffset - The offset, in bytes, to the first element in the array.
* @param length - The number of elements in the array.
* @returns A new typed array instance.
*/
new (buffer: ArrayBufferLike, byteOffset: number, length: number): T;
/**
* The number of bytes per element in the typed array.
*/
BYTES_PER_ELEMENT: number;
};
/**
* Represents a constructor type for various typed arrays.
* This type is a union of constructors for different typed arrays
* such as Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
* Int32Array, Uint32Array, Float32Array, and Float64Array.
*/
type TypedArrayConstructor =
| TypedArrayConstructorBase<Int8Array>
| TypedArrayConstructorBase<Uint8Array>
| TypedArrayConstructorBase<Uint8ClampedArray>
| TypedArrayConstructorBase<Int16Array>
| TypedArrayConstructorBase<Uint16Array>
| TypedArrayConstructorBase<Int32Array>
| TypedArrayConstructorBase<Uint32Array>
| TypedArrayConstructorBase<Float32Array>
| TypedArrayConstructorBase<Float64Array>;
/**
* Represents a union type for various typed arrays.
* This type includes all standard typed arrays such as:
* - Int8Array: An array of 8-bit signed integers.
* - Uint8Array: An array of 8-bit unsigned integers.
* - Uint8ClampedArray: An array of 8-bit unsigned integers clamped to 0-255.
* - Int16Array: An array of 16-bit signed integers.
* - Uint16Array: An array of 16-bit unsigned integers.
* - Int32Array: An array of 32-bit signed integers.
* - Uint32Array: An array of 32-bit unsigned integers.
* - Float32Array: An array of 32-bit floating point numbers.
* - Float64Array: An array of 64-bit floating point numbers.
*/
type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;
/**
* Represents a callback function used in the writeCallback method of the RingBuffer class.
* This callback is responsible for writing data to two separate storage buffers.
*
* @param storageA - The first storage buffer as a typed array.
* @param storageB - The second storage buffer as a typed array.
* @returns The number of elements that have been written to the storage buffers.
*/
type RingBufferWriteCallback = (storageA: TypedArray, storageB: TypedArray) => number;
/**
* Represents a callback function used in the writeCallbackWithOffset method of the RingBuffer class.
* This callback is responsible for writing data to the ring buffer at specified offsets.
*
* @param storage - The internal storage of the ring buffer as a typed array.
* @param offsetStartWritingFrom - The offset to start writing from in the storage.
* @param numElementsToWriteAtOffset - The number of elements to write at the first offset.
* @param offsetStartWritingFromB - The second offset to start writing from in the storage.
* @param numElementsToWriteAtOffsetB - The number of elements to write at the second offset.
* @returns The number of elements that have been written to the storage.
*/
type RingBufferWriteCallbackWithOffset = (
storage: TypedArray,
offsetStartWritingFrom: number,
numElementsToWriteAtOffset: number,
offsetStartWritingFromB: number,
numElementsToWriteAtOffsetB: number
) => number;
/**
* The base RingBuffer class
*
* A Single Producer - Single Consumer thread-safe wait-free ring buffer.
*
* The producer and the consumer can be on separate threads, but cannot change roles,
* except with external synchronization.
*/
declare class RingBuffer {
/** Allocate the SharedArrayBuffer for a RingBuffer, based on the type and
* capacity required
* @param capacity The number of elements the ring buffer will be
* able to hold.
* @param type A typed array constructor, the type that this ring
* buffer will hold.
* @return A SharedArrayBuffer of the right size.
*/
static getStorageForCapacity(capacity: number, type: TypedArrayConstructor): SharedArrayBuffer;
private _type;
private _capacity;
private buf;
private write_ptr;
private read_ptr;
private storage;
/**
* @param sab A SharedArrayBuffer obtained by calling
* {@link RingBuffer.getStorageForCapacity}.
* @param type A typed array constructor, the type that this ring
* buffer will hold.
*/
constructor(sab: SharedArrayBuffer, type: TypedArrayConstructor);
/**
* @return the type of the underlying ArrayBuffer for this RingBuffer. This
* allows implementing crude type checking.
*/
type(): string;
/**
* Push elements to the ring buffer.
* @param elements A typed array of the same type as passed in the ctor, to be written to the queue.
* @param length If passed, the maximum number of elements to push.
* If not passed, all elements in the input array are pushed.
* @param offset If passed, a starting index in elements from which
* the elements are read. If not passed, elements are read from index 0.
* @return the number of elements written to the queue.
*/
push(elements: TypedArray, length?: number, offset?: number): number;
/**
* Write bytes to the ring buffer using callbacks. This create wrapper
* objects and can GC, so it's best to no use this variant from a real-time
* thread such as an AudioWorklerProcessor `process` method.
* The callback is passed two typed arrays of the same type, to be filled.
* This allows skipping copies if the API that produces the data writes is
* passed arrays to write to, such as `AudioData.copyTo`.
* @param amount The maximum number of elements to write to the ring
* buffer. If amount is more than the number of slots available for writing,
* then the number of slots available for writing will be made available: no
* overwriting of elements can happen.
* @param cb A callback with two parameters, that are two typed
* array of the correct type, in which the data need to be copied. If the
* callback doesn't return anything, it is assumed all the elements
* have been written to. Otherwise, it is assumed that the returned number is
* the number of elements that have been written to, and those elements have
* been written started at the beginning of the requested buffer space.
*
* @return The number of elements written to the queue.
*/
writeCallback(amount: number, cb: RingBufferWriteCallback): number;
/**
* Write bytes to the ring buffer using a callback.
*
* This allows skipping copies if the API that produces the data writes is
* passed arrays to write to, such as `AudioData.copyTo`.
*
* @param amount The maximum number of elements to write to the ring
* buffer. If amount is more than the number of slots available for writing,
* then the number of slots available for writing will be made available: no
* overwriting of elements can happen.
* @param cb A callback with five parameters:
*
* (1) The internal storage of the ring buffer as a typed array
* (2) An offset to start writing from
* (3) A number of elements to write at this offset
* (4) Another offset to start writing from
* (5) A number of elements to write at this second offset
*
* If the callback doesn't return anything, it is assumed all the elements
* have been written to. Otherwise, it is assumed that the returned number is
* the number of elements that have been written to, and those elements have
* been written started at the beginning of the requested buffer space.
* @return The number of elements written to the queue.
*/
writeCallbackWithOffset(amount: number, cb: RingBufferWriteCallbackWithOffset): number;
/**
* Read up to `elements.length` elements from the ring buffer. `elements` is a typed
* array of the same type as passed in the ctor.
* Returns the number of elements read from the queue, they are placed at the
* beginning of the array passed as parameter.
* @param elements An array in which the elements read from the
* queue will be written, starting at the beginning of the array.
* @param length If passed, the maximum number of elements to pop. If
* not passed, up to elements.length are popped.
* @param offset If passed, an index in elements in which the data is
* written to. `elements.length - offset` must be greater or equal to
* `length`.
* @return The number of elements read from the queue.
*/
pop(elements: TypedArray, length?: number, offset?: number): number;
/**
* @return True if the ring buffer is empty false otherwise. This can be late
* on the reader side: it can return true even if something has just been
* pushed.
*/
empty(): boolean;
/**
* @return True if the ring buffer is full, false otherwise. This can be late
* on the write side: it can return true when something has just been popped.
*/
full(): boolean;
/**
* @return The usable capacity for the ring buffer: the number of elements
* that can be stored.
*/
capacity(): number;
/**
* @return The number of elements available for reading. This can be late, and
* report less elements that is actually in the queue, when something has just
* been enqueued.
*/
availableRead(): number;
/**
* Compatibility alias for availableRead().
*
* @return The number of elements available for reading. This can be late, and
* report less elements that is actually in the queue, when something has just
* been enqueued.
*
* @deprecated
*/
available_read(): number;
/**
* @return The number of elements available for writing. This can be late, and
* report less elements that is actually available for writing, when something
* has just been dequeued.
*/
availableWrite(): number;
/**
* Compatibility alias for availableWrite.
*
* @return The number of elements available for writing. This can be late, and
* report less elements that is actually available for writing, when something
* has just been dequeued.
*
* @deprecated
*/
available_write(): number;
/**
* @return Number of elements available for reading, given a read and write
* pointer.
* @private
*/
private _available_read;
/**
* @return Number of elements available from writing, given a read and write
* pointer.
* @private
*/
private _available_write;
/**
* @return The size of the storage for elements not accounting the space for
* the index, counting the empty slot.
* @private
*/
private _storage_capacity;
/**
* Copy `size` elements from `input`, starting at offset `offset_input`, to
* `output`, starting at offset `offset_output`.
* @param input The array to copy from
* @param offset_input The index at which to start the copy
* @param output The array to copy to
* @param offset_output The index at which to start copying the elements to
* @param size The number of elements to copy
* @private
*/
private _copy;
}
/**
* Interleaved -> Planar audio buffer conversion
*
* This is useful to get data from a codec, the network, or anything that is
* interleaved, into a planar format, for example a Web Audio API AudioBuffer or
* the output parameter of an AudioWorkletProcessor.
*
* @param input is an array of n*128 frames arrays, interleaved,
* where n is the channel count.
* @param output is an array of 128-frames arrays.
*/
declare function deinterleave(input: Float32Array, output: Float32Array[]): void;
/**
* Planar -> Interleaved audio buffer conversion
*
* This function is useful to get data from the Web Audio API (that uses a
* planar format), into something that a codec or network streaming library
* would expect.
*
* @param input An array of n*128 frames Float32Array that hold the audio data.
* @param output A Float32Array that is n*128 elements long.
*/
declare function interleave(input: Float32Array[], output: Float32Array): void;
/**
* Send interleaved audio frames to another thread, wait-free.
*
* These classes allow communicating between a non-real time thread (browser
* main thread or worker) and a real-time thread (in an AudioWorkletProcessor).
* Write and Reader cannot change role after setup, unless externally
* synchronized.
*
* GC _can_ happen during the initial construction of this object when hopefully
* no audio is being output. This depends on how implementations schedule GC
* passes. After the setup phase no GC is triggered on either side of the queue.
*/
declare class AudioWriter {
private ringbuf;
/**
* From a RingBuffer, build an object that can enqueue enqueue audio in a ring
* buffer.
*/
constructor(ringbuf: RingBuffer);
/**
* Enqueue a buffer of interleaved audio into the ring buffer.
*
* Care should be taken to enqueue a number of samples that is a multiple of the
* channel count of the audio stream.
*
* @param buf An array of interleaved audio frames.
*
* @return The number of samples that have been successfully written to the
* queue. `buf` is not written to during this call, so the samples that
* haven't been written to the queue are still available.
*/
enqueue(buf: Float32Array): number;
/**
* @deprecated Use availableWrite() instead. This method is deprecated and will be removed in future versions.
*/
available_write(): number;
/**
* @return The free space in the ring buffer. This is the amount of samples
* that can be queued, with a guarantee of success.
*/
availableWrite(): number;
}
/**
* Receive interleaved audio frames to another thread, wait-free.
*
* GC _can_ happen during the initial construction of this object when hopefully
* no audio is being output. This depends on how implementations schedule GC
* passes. After the setup phase no GC is triggered on either side of the queue.
*/
declare class AudioReader {
private ringbuf;
/**
* From a RingBuffer, build an object that can dequeue audio in a ring
* buffer.
*/
constructor(ringbuf: RingBuffer);
/**
* Attempt to dequeue at most `buf.length` samples from the queue. This
* returns the number of samples dequeued. If greater than 0, the samples are
* at the beginning of `buf`.
*
* Care should be taken to dequeue a number of samples that is a multiple of the
* channel count of the audio stream.
*
* @param buf A buffer in which to copy the dequeued
* interleaved audio frames.
* @return The number of samples dequeued.
*/
dequeue(buf: Float32Array): number;
/**
* @deprecated Use availableRead() instead. This method is deprecated and will be removed in future versions.
*/
available_read(): number;
/**
* Query the occupied space in the queue.
*
* @return The amount of samples that can be read with a guarantee of success.
*/
availableRead(): number;
}
/**
* Send parameter changes, lock free, no gc, between a UI thread (browser
* main thread or worker) and a real-time thread (in an AudioWorkletProcessor).
* Write and Reader cannot change roles after setup, unless externally
* synchronized.
*
* GC _can_ happen during the initial construction of this object when hopefully
* no audio is being output. This depends on the implementation.
*
* Parameter changes are like in the VST framework: an index and a float value
* (no restriction on the value).
*
* This class supports up to 256 parameters, but this is easy to extend if
* needed.
*
* An element is an index, that is an unsigned byte, and a float32, which is 4
* bytes.
*/
declare class ParameterWriter {
private ringbuf;
private mem;
private array;
private view;
/**
* From a RingBuffer, build an object that can enqueue a parameter change in
* the queue.
* @param ringbuf A RingBuffer object of Uint8Array.
*/
constructor(ringbuf: RingBuffer);
/**
* Enqueue a parameter change for parameter of index `index`, with a new value
* of `value`.
*
* @param index The index of the parameter.
* @param value The value of the parameter.
* @return True if enqueuing succeeded, false otherwise.
*/
enqueueChange(index: number, value: number): boolean;
/**
* Enqueue a parameter change for parameter of index `index`, with a new value
* of `value`.
*
* @param index The index of the parameter.
* @param value The value of the parameter.
* @return True if enqueuing succeeded, false otherwise.
*
* @deprecated
*/
enqueue_change(index: number, value: number): boolean;
}
/**
* Receive parameter changes, lock free, no gc, between a UI thread (browser
* main thread or worker) and a real-time thread (in an AudioWorkletProcessor).
* Write and Reader cannot change roles after setup, unless externally
* synchronized.
*
* GC _can_ happen during the initial construction of this object when hopefully
* no audio is being output. This depends on the implementation.
*
* Parameter changes are like in the VST framework: an index and a float value
* (no restriction on the value).
*
* This class supports up to 256 parameters, but this is easy to extend if
* needed.
*
* An element is an index, that is an unsigned byte, and a float32, which is 4
* bytes.
*/
declare class ParameterReader {
private ringbuf;
private mem;
private array;
private view;
/**
* @param ringbuf A RingBuffer setup to hold Uint8.
*/
constructor(ringbuf: RingBuffer);
/**
* Attempt to dequeue a single parameter change.
* @param o An object with two attributes: `index` and `value`.
* @return true if a parameter change has been dequeued, false otherwise.
*/
dequeueChange(o: {
index: number;
value: number;
}): boolean;
/**
* Attempt to dequeue a single parameter change.
* @param o An object with two attributes: `index` and `value`.
* @return true if a parameter change has been dequeued, false otherwise.
*
* @deprecated
*/
dequeue_change(o: {
index: number;
value: number;
}): boolean;
}
export { AudioReader, AudioWriter, ParameterReader, ParameterWriter, RingBuffer, deinterleave, interleave };