Files
bass-foundry/plugins/patina/core/tests/realtime.rs
T
nvrl 53bc26d14d feat(patina): Tone guard for bass lines
Patina detects the played note (30-500 Hz) and remembers, over about
4 s, how high each spectral bump is at a fixed frequency and relative
to the note. Bumps that follow the note (a boosted harmonic the patch
is built on) get less cut; bumps fixed in Hz (formants, room modes)
are cut as before. On a saw line F1-F2 with harmonic 6 boosted, the
cut on that harmonic drops from 5.78 dB to 0.44 dB at 100 %, while a
fixed formant at 1.5 kHz stays at 5.78 dB. One held note is cut as
normal, since the two cases cannot be told apart.

- New parameter tone-guard (0-100 %, default 0), appended at the end;
  0 % is the old behaviour and old settings load unchanged.
- New Bass line preset.
- Editor: Tone guard control, NOTE readout and a strip over the
  spectrum where cuts are held back.
- No added latency, no allocation on the audio thread; patina-core now
  depends on common-dsp.
2026-11-06 12:24:11 +01:00

61 lines
2.0 KiB
Rust

use patina_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_automation_and_analysis_do_not_allocate_or_free() {
let mut engine = Engine::new(48000., 2).unwrap();
let mut settings = Settings::preset(1);
settings.tone_guard = 80.;
engine.set_settings(settings).unwrap();
CHECK.with(|v| v.set(true));
for i in 0..48000 {
if i % 512 == 0 {
settings.focus[0].enabled = true;
settings.focus[0].frequency = 1000. + (i % 10000) as f32;
settings.focus[0].sensitivity = 6.;
settings.low = 120. + (i % 100) as f32;
engine.set_settings(settings).unwrap();
}
// A pitched input, so Tone guard estimates the pitch and updates its maps.
let x = 0.3
* (i as f32 * std::f32::consts::TAU * 55. / 48000.)
.sin()
.signum();
std::hint::black_box(engine.process_sample([x, -x], Monitor { delta: i > 24000 }));
std::hint::black_box(engine.take_analyzer());
}
engine.reset();
CHECK.with(|v| v.set(false));
assert_eq!(OPERATIONS.with(Cell::get), 0);
}