175 lines
5.3 KiB
JavaScript
175 lines
5.3 KiB
JavaScript
// port of https://github.com/wareya/LimiterTest/blob/main/Main.gd
|
|
|
|
class Limiter extends AudioWorkletProcessor {
|
|
constructor(options) {
|
|
super();
|
|
|
|
this.srate = sampleRate;
|
|
|
|
this.pre_gain = 1.0;
|
|
this.post_gain = 1.0;
|
|
this.limit = 0.8912509381337456; // -1 decibels
|
|
|
|
if (false) {
|
|
this.attack = 0.0010;
|
|
this.sustain = 0.0400;
|
|
this.release = 0.0400;
|
|
} else { // ultrafast
|
|
this.attack = 0.0006;
|
|
this.sustain = 0.0060;
|
|
this.release = 0.0300;
|
|
}
|
|
|
|
this.release_memory = 0.0;
|
|
|
|
this.box_blur = 0;
|
|
// this.amp = 1.0; // not stateful
|
|
this.ref_amp = 1.0;
|
|
|
|
this.memory_cursor = 0;
|
|
this.sustained_amp = null;
|
|
this.sample_memory = null;
|
|
|
|
this.amp_memory_cursor = 0;
|
|
this.amp_memory = null;
|
|
this.amp_min_buckets = null;
|
|
this.amp_bucket_size = 0;
|
|
|
|
this.peaks = null;
|
|
this.delays = [];
|
|
|
|
const toSamples = (t) => Math.max(Math.round(this.srate * t), 1);
|
|
|
|
let memory_count = toSamples(this.attack);
|
|
this.sample_memory = new Float32Array(memory_count);
|
|
this.sustained_amp = new Float32Array(memory_count);
|
|
|
|
let amp_count = toSamples(this.attack + this.sustain);
|
|
this.amp_bucket_size = Math.sqrt(amp_count) | 0;
|
|
this.amp_memory = new Float32Array(amp_count);
|
|
this.amp_memory.fill(1.0);
|
|
|
|
let amp_bucket_count = amp_count / this.amp_bucket_size | 0;
|
|
this.amp_min_buckets = new Float32Array(amp_bucket_count);
|
|
this.amp_min_buckets.fill(1.0);
|
|
|
|
this.peaks = new Float32Array(128); // guess the buffer size
|
|
this.delays = [this.sample_memory];
|
|
}
|
|
|
|
process(inputs, outputs, parameters) {
|
|
const memory_count = this.sample_memory.length; // this shows up a lot
|
|
|
|
const input = inputs[0];
|
|
const output = outputs[0];
|
|
|
|
if (input.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
while (this.delays.length < input.length) {
|
|
this.delays.push(new Float32Array(memory_count));
|
|
}
|
|
|
|
const blockSize = input[0].length;
|
|
if (blockSize > 0 && this.peaks.length !== blockSize) {
|
|
this.peaks = new Float32Array(blockSize);
|
|
}
|
|
this.peaks.fill(0.0);
|
|
|
|
for (let c = 0; c < input.length; c++) {
|
|
const source = input[c];
|
|
for (let i = 0; i < blockSize; i++) {
|
|
this.peaks[i] = Math.max(this.peaks[i], Math.abs(source[i]));
|
|
}
|
|
}
|
|
|
|
const delay_cursor = this.memory_cursor;
|
|
|
|
for (let i = 0; i < blockSize; i++) {
|
|
const sample = this.peaks[i] * this.pre_gain;
|
|
|
|
// do the release curve
|
|
this.release_memory = Math.max(0.0, this.release_memory - 1.0 / this.srate);
|
|
let amp = 1.0;
|
|
if (this.release > 0.0) {
|
|
let t = 1.0 - this.release_memory / this.release;
|
|
amp = (1.0 - t) * this.ref_amp + t;
|
|
}
|
|
|
|
// reset sustain if we exceed the limit
|
|
let ref_val = Math.abs(amp * sample);
|
|
if (ref_val >= this.limit) {
|
|
let amount = this.limit / ref_val;
|
|
amp *= amount;
|
|
ref_val *= amount;
|
|
this.ref_amp = amp;
|
|
this.release_memory = this.release;
|
|
}
|
|
|
|
this.amp_memory[this.amp_memory_cursor] = amp;
|
|
|
|
// update affected bucket
|
|
let bucket = this.amp_memory_cursor / this.amp_bucket_size | 0;
|
|
this.amp_min_buckets[bucket] = 1.0;
|
|
let end = Math.min((bucket + 1) * this.amp_bucket_size, this.amp_memory.length);
|
|
for (let j = bucket * this.amp_bucket_size; j < end; j++) {
|
|
this.amp_min_buckets[bucket] = Math.min(this.amp_min_buckets[bucket], this.amp_memory[j]);
|
|
}
|
|
|
|
// now get the current sustain value
|
|
let min_amp = 1.0;
|
|
for (let j = 0; j < this.amp_min_buckets.length; j++) {
|
|
let past_amp = this.amp_min_buckets[j];
|
|
min_amp = Math.min(min_amp, past_amp);
|
|
}
|
|
|
|
this.amp_memory_cursor += 1;
|
|
this.amp_memory_cursor %= this.amp_memory.length;
|
|
|
|
// update the sustain buffer
|
|
this.box_blur -= this.sustained_amp[this.memory_cursor] * 32767 | 0;
|
|
this.sustained_amp[this.memory_cursor] = min_amp;
|
|
this.box_blur += this.sustained_amp[this.memory_cursor] * 32767 | 0;
|
|
|
|
// update the sample memory buffer
|
|
// NOTE(notwa): we instead do this later for each channel.
|
|
//let ret_sample = this.sustain + this.attack > 0.0 ? this.sample_memory[this.memory_cursor] : sample;
|
|
//this.sample_memory[this.memory_cursor] = sample;
|
|
|
|
this.memory_cursor += 1;
|
|
this.memory_cursor %= memory_count;
|
|
|
|
this.peaks[i] = this.box_blur / 32767 / memory_count;
|
|
}
|
|
|
|
const final_gain = this.pre_gain * this.post_gain;
|
|
for (let c = 0; c < input.length; c++) {
|
|
const source = input[c];
|
|
const target = output[c];
|
|
if (this.sustain + this.attack <= 0.0) {
|
|
for (let i = 0; i < blockSize; i++) {
|
|
// return the limited sample
|
|
target[i] = source[i] * this.peaks[i] * final_gain;
|
|
}
|
|
} else {
|
|
const delay = this.delays[c];
|
|
let local_cursor = delay_cursor;
|
|
for (let i = 0; i < blockSize; i++) {
|
|
const ret_sample = delay[local_cursor];
|
|
delay[local_cursor] = source[i];
|
|
|
|
local_cursor += 1;
|
|
local_cursor %= memory_count;
|
|
|
|
// return the limited sample
|
|
target[i] = ret_sample * this.peaks[i] * final_gain;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // docs say to return false for processors with no reverb tails
|
|
}
|
|
}
|
|
|
|
registerProcessor("limiter", Limiter);
|