Skip to content

vinimontanheiro/wasm-ring-buffer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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