audio-limiter

limiter audio worklet node

Usage no npm install needed!

<script type="module">
  import audioLimiter from 'https://cdn.skypack.dev/audio-limiter';
</script>

README

Audio Limiter Node


This library extending built in browser AudioContext and OfflineAudioContext by createLimiter() function.

DEMO PAGE

Installation

npm install audio-limiter

Usage

By calling createLimiter on AudioContext / OffLineAudioContext

import "audio-limiter" 
// call this import in any file on your project 
// recommendation - do it in main file
// this file will patch AudioContext prototype

crateAudio = async () => {
    const context = new AudioContext();
    const buffer = await fetch("./your-audio.mp3")
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => context.decodeAudioData(arrayBuffer); 
    
    const source = context.createBufferSource();
    source.buffer = buffer;

    // and now most important 
    const limiter = await context.createLimiter();
    // connect
    source.connect(limiter)
    limiter.connect(context.destination);
    
    source.start(0);
} 

By creating new instance of LimiterNode

Whats allows connecting node in sync code

import LimiterNode from "audio-limiter"

const context = new AudioContext();
const limiter = new LimiterNode(context, {time: 0.005});
const oscillator = new OscillatorNode(context)
const gain = new GainNode(context);

gain.gain.value = 10; // should be strongly overdrive
oscillator.start(0);

oscillator.connect(gain)
gain.connect(limiter);
limiter.connect(context.destination);

limiter.isReady.then(() => {
    limiter.attack.setValueAtTime(0.1, 10)// set value 100ms in 10 second
    limiter.bypass.setValueAtTime(1, 20)// any not zero value will activate bypass
})


Creating LimiterNode always is asynchronous under the hood. If you need to be shure that everything inside LimiterNode is configured then you can wait for it by calling isReady. This will return Promise witch will be resolved when limiter worklet will be connected. before that LimiterNode is bypassed. But you don't have to wait to make you connections with other AudioNodes.

const context = new AudioContext();
const limiter = new LimiterNode(context)
await limiter.isReady;
// now signal is processed

Configuration

Time - [in seconds] - Latency time witch limiter will delay output. Any non zero attack/release time will lag envelope behind current processed audio. To attenuate right part of signal this limiter implements delay buffer. This value should be minium equal to attack time. If value of time will be less that attack then limiter will act on the past envelope change. By default value is 5ms. This means that audio is delayed by 5 ms and in this range is analyzed envelope.

const context = new OfflineAudioContext(OfflineAudioContextOptions)
//time - min: 0, max: 10s
const limiter = await context.createLimiter({time: 0.005})

Number of channels - default value is 2. you can change it by define it.

const context = new OfflineAudioContext(OfflineAudioContextOptions)
const limiter = await context.createLimiter({
        channelCount: 1, 
        time: 0.005 // 5ms
    })

Predefined configs that you can't change

const limiter = await context.createLimiter({
        channelCountMode: "explicit" 
        numberOfInputs: 1,
        numberOfOutputs: 1
    })

Parameters

import LimiterNode from "audio-limiter"
//static getter will return list of aviable parameters
LimiterNode.parametersList: {
    name: string, 
    minValue: number, 
    maxValue: number, 
    defaultValue: number
}[] 

Attack - [in seconds] defining how quickly the detection responds to rising amplitudes.

const limiter = await context.createLimiter();
//by access params map

const attack = limiter.parameters.get("attack");

attack.value = .001// 1ms - by default 0
attack.setValueAtTime(0.001, context.currentTime);

//or by shortcut getter 
limiter.attack.setValueAtTime(.001, context.currentTime)
limiter.attack.minValue // 0
limiter.attack.maxValue // 2s
limiter.attack.defaultValue // 0

Release - [in seconds] - defining how quickly the detection responds to falling amplitudes.

limiter.parameters.get("release").setValueAtTime(.001, context.currentTime)
//or
limiter.release.setValueAtTime(.001, context.currentTime)
limiter.release.minValue // 0
limiter.release.maxValue // 2
limiter.release.defaultValue // 0.1 - 100 ms

Threshold - [in dB] - level to witch will be limiting audio. Be default is -2dB this means that max amplitude value will be ~0.79... Set it to 0 if you expect sample value +- 1.

limiter.threshold.setValueAtTime(2, 0);
limiter.threshold.minValue // -100
limiter.threshold.maxValue // 0
limiter.threshold.defaultValue // -2dB

Pre Gain - [in dB] - just like apply gain before limiter node. can be useful in kind of normalization process.

limiter.preGain.setValueAtTime(2, 0);
limiter.preGain.minValue // -100
limiter.preGain.maxValue // 100
limiter.preGain.defaultValue // 0 dB

Post Gain - [in dB] - post limiting gain. Be careful if you expect not clipping data. If you set threshold -2dB then max post gain can be set to +2dB.

limiter.postGain.setValueAtTime(2, 0);
limiter.postGain.minValue // -100
limiter.postGain.maxValue // 100
limiter.postGain.defaultValue // 0 dB

Bypass - [boolean number] - Every value other than 0 is tracked as truth. If you pass truthy value to bypass param then signal will not be limited. But, delay buffer will still by the same.

limiter.bypass.setValueAtTime(1, 0);
limiter.bypass.minValue // 0
limiter.bypass.maxValue // 1
limiter.bypass.defaultValue // 0

What if I don't want modify AudioContext prototype ??

No problem. Don't import main library file. Just register limiter worklet processor and add module. You fill find it in library dist folder.

const context = new AudioContext();
const path = [
    "node_models",
    "limiter-audio-node",
    "dist",
    "limiter-audio-worklet-processor.js"
].join("/");

await context.audioWorklet.addMode(path);
const limiter = new LimiterAudioWorkletNode(context, 'limiter-processor', options);

Stack

Built with:

  • TypeScript
  • Webpack 5

Test:

  • Karma
  • Jasmine

MIT