Source: param.js

/**
 * 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 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 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.
 */
export class ParameterWriter {
  /**
   * From a RingBuffer, build an object that can enqueue a parameter change in
   * the queue.
   * @param {RingBuffer} ringbuf A RingBuffer object of Uint8Array.
   * @constructor
   */
  constructor(ringbuf) {
    if (ringbuf.type() !== "Uint8Array") {
      throw TypeError("This class requires a ring buffer of Uint8Array");
    }
    const SIZE_ELEMENT = 5;
    this.ringbuf = ringbuf;
    this.mem = new ArrayBuffer(SIZE_ELEMENT);
    this.array = new Uint8Array(this.mem);
    this.view = new DataView(this.mem);
  }
  /*
   * Enqueue a parameter change for parameter of index `index`, with a new value
   * of `value`.
   *
   * @param {number} index The index of the parameter.
   * @param {number} value The value of the parameter.
   * @return True if enqueuing suceeded, false otherwise.
   */
  enqueue_change(index, value) {
    const SIZE_ELEMENT = 5;
    if (this.ringbuf.available_write() < SIZE_ELEMENT) {
      return false;
    }
    this.view.setUint8(0, index);
    this.view.setFloat32(1, value);
    return this.ringbuf.push(this.array) === SIZE_ELEMENT;
  }
}

/**
 * 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 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 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.
 */
export class ParameterReader {
  /**
   * @constructor
   * @param {RingBuffer} ringbuf A RingBuffer setup to hold Uint8.
   */
  constructor(ringbuf) {
    const SIZE_ELEMENT = 5;
    this.ringbuf = ringbuf;
    this.mem = new ArrayBuffer(SIZE_ELEMENT);
    this.array = new Uint8Array(this.mem);
    this.view = new DataView(this.mem);
  }
  /**
   * Attempt to dequeue a single parameter change.
   * @param {Object} o An object with two attributes: `index` and `value`.
   * @return true if a parameter change has been dequeued, false otherwise.
   */
  dequeue_change(o) {
    if (this.ringbuf.empty()) {
      return false;
    }
    const rv = this.ringbuf.pop(this.array);
    o.index = this.view.getUint8(0);
    o.value = this.view.getFloat32(1);

    return rv === this.array.length;
  }
}