Quench splits the signal with a Linkwitz-Riley crossover and lets the sub pass unclipped. The top is clipped symmetrically at the ceiling minus the sub's held peak, so it does not pump at the sub's rate and the sum stays under the ceiling. What the clip removes is high-passed at the split, so its products stay out of the low band. Two correction passes and an oversampled hard clip hold the ceiling; a safety clip catches the downsampler's ringing. - Controls: Drive, Ceiling, Split, Hold, Soft, Bypass. - Listen to Quench, a plain clipper at the same settings, or the delta. - Editor meter compares how much the low band changed for Quench and for a plain clip, with a scope of the output, the sub and the top's room. - Measured: sub level +0.04 dB against -2.2 dB for a plain clip, low-band products -35 dB against -16 dB, at 0.5-1.2 dB less RMS. - Registered in the catalog, workflows, editor-check, README, chain and development docs, and the website copy.
66 lines
2.2 KiB
Rust
66 lines
2.2 KiB
Rust
use quench_core::{Engine, Monitor, Settings};
|
|
use std::{
|
|
alloc::{GlobalAlloc, Layout, System},
|
|
cell::Cell,
|
|
};
|
|
|
|
struct CheckedAllocator;
|
|
thread_local! {
|
|
static CHECK:Cell<bool>=const {Cell::new(false)};
|
|
static OPERATIONS:Cell<usize>=const {Cell::new(0)};
|
|
}
|
|
fn record() {
|
|
if CHECK.try_with(Cell::get).unwrap_or(false) {
|
|
let _ = OPERATIONS.try_with(|n| n.set(n.get() + 1));
|
|
}
|
|
}
|
|
unsafe impl GlobalAlloc for CheckedAllocator {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
record();
|
|
unsafe { System.alloc(layout) }
|
|
}
|
|
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
|
|
record();
|
|
unsafe { System.dealloc(pointer, layout) }
|
|
}
|
|
unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, size: usize) -> *mut u8 {
|
|
record();
|
|
unsafe { System.realloc(pointer, layout, size) }
|
|
}
|
|
}
|
|
#[global_allocator]
|
|
static ALLOCATOR: CheckedAllocator = CheckedAllocator;
|
|
|
|
#[test]
|
|
fn processing_settings_monitors_and_analysis_do_not_allocate_or_free() {
|
|
let mut engine = Engine::new(48000., 2).unwrap();
|
|
let mut settings = Settings {
|
|
drive: 12.,
|
|
..Settings::default()
|
|
};
|
|
engine.set_settings(settings).unwrap();
|
|
let monitors = [Monitor::Quench, Monitor::Plain, Monitor::Delta];
|
|
CHECK.with(|v| v.set(true));
|
|
for i in 0..96000 {
|
|
if i % 4800 == 0 {
|
|
let step = i / 4800;
|
|
settings.split = 60. + 20. * (step % 6) as f32;
|
|
settings.hold = 20. + 40. * (step % 3) as f32;
|
|
settings.soft = 25. * (step % 5) as f32;
|
|
settings.ceiling = -3. * (step % 3) as f32;
|
|
settings.bypass = step % 7 == 6;
|
|
engine.set_settings(settings).unwrap();
|
|
}
|
|
let t = i as f32 / 48000.;
|
|
let x = (std::f32::consts::TAU * 45. * t).sin() * 0.5
|
|
+ (std::f32::consts::TAU * 733. * t).sin() * 0.5;
|
|
std::hint::black_box(engine.process_sample([x, -x], monitors[(i / 9600) % 3]));
|
|
if i % 1600 == 0 {
|
|
std::hint::black_box(engine.take_analyzer());
|
|
}
|
|
}
|
|
engine.reset();
|
|
CHECK.with(|v| v.set(false));
|
|
assert_eq!(OPERATIONS.with(Cell::get), 0);
|
|
}
|