// 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);