Check and Windows preview / check (anvil) (push) Successful in 2m0s
Check and Windows preview / check (crucible) (push) Successful in 1m29s
Check and Windows preview / check (lathe) (push) Successful in 1m27s
Check and Windows preview / check (patina) (push) Successful in 1m53s
Check and Windows preview / check (rivet) (push) Successful in 1m29s
Check and Windows preview / check (temper) (push) Successful in 1m29s
Check and Windows preview / check (weld) (push) Successful in 2m38s
Check and Windows preview / previews (push) Successful in 1m28s
Independent plugin release / plan (push) Successful in 8s
Check and Windows preview / windows (push) Successful in 3m14s
Independent plugin release / build (push) Successful in 3m50s
Independent plugin release / publish (push) Successful in 9s
Weld hears the other bass layer through the sidechain and fits polarity plus up to four RBJ allpass sections to the measured cross-spectrum, so the layers sum instead of cancelling. No latency; the layer on its own keeps its sound. - Worker thread: Welch cross-spectrum and coherence, Nelder-Mead fit with 1/f weighting, a per-band loss penalty and model selection by share of the achievable gain; GCC-PHAT lag detection flags a late layer. - Audio thread: allocation-free chain with a 40 ms warm-up and 40 ms crossfade per new fit; mono blocks go to the worker through a lock-free queue. - Follow / Learn / Hold; the fit is persisted and restored without a crossfade. - Editor: per-band loss and recovery against a perfect alignment, phase before and after, recovered dB, time offset and status. - Tests for flat magnitude, crossfade, fit targets, Follow hysteresis, Learn, silence, realtime allocation and the persisted fit in the host.
71 lines
2.3 KiB
Rust
71 lines
2.3 KiB
Rust
use std::{
|
|
alloc::{GlobalAlloc, Layout, System},
|
|
cell::Cell,
|
|
};
|
|
use weld_core::{Engine, Fit, Listen, Section, Settings};
|
|
|
|
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_fits_crossfades_and_a_full_queue_do_not_allocate_or_free() {
|
|
let mut engine = Engine::new(48000., 2).unwrap();
|
|
let mut settings = Settings::default();
|
|
engine.set_settings(settings).unwrap();
|
|
let link = engine.link();
|
|
let fits: Vec<Fit> = (0..4)
|
|
.map(|k| Fit {
|
|
flip: k % 2 == 1,
|
|
count: k,
|
|
sections: [Section {
|
|
fc: 200. * (k + 1) as f32,
|
|
q: 0.7,
|
|
}; 4],
|
|
})
|
|
.collect();
|
|
CHECK.with(|v| v.set(true));
|
|
// Nothing takes blocks, so the queue fills up and later blocks are dropped.
|
|
for i in 0..96000 {
|
|
if i % 4800 == 0 {
|
|
settings.output = ((i / 4800) % 5) as f32 - 2.;
|
|
settings.listen = [Listen::Layer, Listen::Sum, Listen::SumDry][(i / 4800) % 3];
|
|
engine.set_settings(settings).unwrap();
|
|
link.push_fit(fits[(i / 4800) % 4]);
|
|
}
|
|
let t = i as f32 / 48000.;
|
|
let x = (std::f32::consts::TAU * 55. * t).sin() * 0.5;
|
|
std::hint::black_box(engine.process_sample([x, -x], [x * 0.5, x]));
|
|
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);
|
|
assert!(link.dropped() > 0);
|
|
}
|