This Web Assembly Ring Buffer can handles an input buffer from specific size and give you an output buffer with any sizes that you want

Features

  • Manipulate Input and Output PCM Data
  • Dynamic buffer sizes
  • Wasm Ring Buffer Queue implemented in C++
  • High processment

Install

npm install wasm-ring-buffer

Usage

import WasmRingBuffer from 'wasm-ring-buffer';

How does it work?

The current AudioWorkletProccess only process 128 bytes for each; so if you need a buffer with your own size, you need to use a "Ring Buffer" to manipulate it. So this library does it for you. We enqueue the AudioWorkletProccess buffer into a Circular Linked List(FIFO), and dequeue with your own size.

Requeriments

For browser definitions a WebAssembly implementation can not run in the Main Thread, you can use only inside of WebWorkers or AudioWorkletNode

Scaffold

- wasm-ring-buffer
  - example
    - using-react
  - src
     node.h
     queue.h
     ring-buffer.wasmmodule.js
     ring-buffer.wasmmodule.wasm
     wasm-ring-buffer.cpp
  index.js
  index.d.ts

Example

AudioContext + AudioWorklet

const inputAudioContext = new AudioContext({ sampleRate: 8000 });
      inputAudioContext.audioWorklet
      .addModule('your-worklet-processor.js')
      .then(() => {
        navigator.mediaDevices
          .getUserMedia({ audio: true })
          .then(stream => {
            const microphone = inputAudioContext.createMediaStreamSource(stream);
            const audioWorkletNode = new AudioWorkletNode(
              inputAudioContext,
              'your-worklet-processor',
              {
                channelCount : 1,
                processorOptions: { //Passing the arguments to processor
                  bufferSize: 160, //output buffer size
                  capacity:2046 // max fifo capacity
                },
              },
            );
            audioWorkletNode.port.onmessage = ({ data }) => {
                console.log('Your own buffer >> ', data); //Receiving data from worklet thread
            };
            microphone.connect(audioWorkletNode).connect(inputAudioContext.destination);
          });
      });

your-worklet-processor.js

import WasmRingBuffer from 'wasm-ring-buffer/index.js';
import { LOG_TABLE } from './constants.js';


class YourWorkletProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    this._bufferSize = options.processorOptions.bufferSize;
    this._capacity = options.processorOptions.capacity;
    this._ringBuffer = new WasmRingBuffer(this._capacity, this._bufferSize);
  }

  float32ToInt16(float32array) {
    let l = float32array.length;
    const buffer = new Int16Array(l);
    while (l--) {
      buffer[l] = Math.min(1, float32array[l]) * 0x7fff;
    }
    return buffer;
  }

  alawEncode(sample) {
    let compandedValue;
    sample = sample === -32768 ? -32767 : sample;
    const sign = (~sample >> 8) & 0x80;
    if (!sign) {
      sample *= -1;
    }
    if (sample > 32635) {
      sample = 32635;
    }
    if (sample >= 256) {
      const exponent = LOG_TABLE[(sample >> 8) & 0x7f];
      const mantissa = (sample >> (exponent + 3)) & 0x0f;
      compandedValue = (exponent << 4) | mantissa;
    } else {
      compandedValue = sample >> 4;
    }
    return compandedValue ^ (sign ^ 0x55);
  }

  linearToAlaw(int16array) {
    const aLawSamples = new Uint8Array(int16array.length);
    for (let i = 0; i < int16array.length; i++) {
      aLawSamples[i] = this.alawEncode(int16array[i]);
    }
    return aLawSamples;
  }

  process(inputs) {
    const input = inputs[0]; // channel 1
    const output = new Float32Array(this._bufferSize);
    this._ringBuffer.enqueue(input[0]); //storing

    while (this._ringBuffer.size() >= this._bufferSize) {
      this._ringBuffer.dequeue(output); //retrieving 
      const int16array = this.float32ToInt16(output); 
      const payload = this.linearToAlaw(int16array); 
      const sharedPayload = new Uint8Array(new SharedArrayBuffer(payload.length)); // sharing buffer memory
      sharedPayload.set(payload, 0);
      this.port.postMessage(sharedPayload); //Sending data to main thread
    }

    return true;
  }
}

registerProcessor(`your-worklet-processor`, YourWorkletProcessor);

Full code is avalaible in project-folder > example > using-react