Skip to content

Commit 7a86091

Browse files
authored
fix: forward parameter changes to live stages, eliminate rebuild clicks (#217)
1 parent 153512c commit 7a86091

20 files changed

Lines changed: 468 additions & 163 deletions

benches/engine.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use rustortion::amp::chain::AmplifierChain;
77
use rustortion::amp::stages::level::LevelStage;
88
use rustortion::audio::engine::{Engine, EngineHandle};
99
use rustortion::audio::peak_meter::PeakMeter;
10+
use rustortion::audio::rt_drop::RtDropHandle;
1011
use rustortion::audio::samplers::Samplers;
11-
use rustortion::ir::load_service::ConvolverDropHandle;
1212
use rustortion::metronome::Metronome;
1313
use rustortion::tuner::Tuner;
1414

@@ -35,7 +35,7 @@ pub fn build_engine(
3535
ir_cabinet,
3636
peak_meter,
3737
metronome,
38-
ConvolverDropHandle::new().0,
38+
RtDropHandle::new().0,
3939
)
4040
.unwrap();
4141
(engine, handle)
@@ -119,7 +119,7 @@ fn bench_engine_with_ir_cabinet(c: &mut Criterion) {
119119
ir_cabinet,
120120
peak_meter,
121121
metronome,
122-
ConvolverDropHandle::new().0,
122+
RtDropHandle::new().0,
123123
)
124124
.unwrap();
125125

src/amp/chain.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,130 @@ impl AmplifierChain {
3131
stage.process_block(input);
3232
}
3333
}
34+
35+
/// Forward a parameter change to a live stage.
36+
pub fn set_parameter(
37+
&mut self,
38+
idx: usize,
39+
name: &str,
40+
value: f32,
41+
) -> Option<Result<(), &'static str>> {
42+
self.stages
43+
.get_mut(idx)
44+
.map(|stage| stage.set_parameter(name, value))
45+
}
46+
47+
/// Read a parameter from a live stage.
48+
pub fn get_parameter(&self, idx: usize, name: &str) -> Option<Result<f32, &'static str>> {
49+
self.stages.get(idx).map(|stage| stage.get_parameter(name))
50+
}
51+
52+
/// Insert a stage at the given index.
53+
pub fn insert_stage(&mut self, idx: usize, stage: Box<dyn Stage>) {
54+
let idx = idx.min(self.stages.len());
55+
self.stages.insert(idx, stage);
56+
}
57+
58+
/// Remove and return the stage at the given index.
59+
pub fn remove_stage(&mut self, idx: usize) -> Option<Box<dyn Stage>> {
60+
if idx < self.stages.len() {
61+
Some(self.stages.remove(idx))
62+
} else {
63+
None
64+
}
65+
}
66+
67+
/// Swap two stages by index.
68+
pub fn swap_stages(&mut self, a: usize, b: usize) {
69+
if a < self.stages.len() && b < self.stages.len() {
70+
self.stages.swap(a, b);
71+
}
72+
}
73+
74+
/// Replace a stage at the given index, returning the old one.
75+
pub fn replace_stage(
76+
&mut self,
77+
idx: usize,
78+
new_stage: Box<dyn Stage>,
79+
) -> Option<Box<dyn Stage>> {
80+
if idx < self.stages.len() {
81+
let old = std::mem::replace(&mut self.stages[idx], new_stage);
82+
Some(old)
83+
} else {
84+
None
85+
}
86+
}
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
use crate::amp::stages::level::LevelStage;
93+
94+
fn make_level(gain: f32) -> Box<dyn Stage> {
95+
Box::new(LevelStage::new(gain))
96+
}
97+
98+
#[test]
99+
fn set_parameter_updates_live_stage() {
100+
let mut chain = AmplifierChain::new();
101+
chain.add_stage(make_level(1.0));
102+
assert!(chain.set_parameter(0, "gain", 0.5).is_some());
103+
let out = chain.process(1.0);
104+
assert!((out - 0.5).abs() < 1e-6);
105+
}
106+
107+
#[test]
108+
fn set_parameter_out_of_bounds_returns_none() {
109+
let mut chain = AmplifierChain::new();
110+
assert!(chain.set_parameter(0, "gain", 0.5).is_none());
111+
}
112+
113+
#[test]
114+
fn insert_stage_at_position() {
115+
let mut chain = AmplifierChain::new();
116+
chain.add_stage(make_level(1.0));
117+
chain.add_stage(make_level(1.0));
118+
chain.insert_stage(1, make_level(0.5));
119+
let out = chain.process(1.0);
120+
assert!((out - 0.5).abs() < 1e-6);
121+
}
122+
123+
#[test]
124+
fn remove_stage_returns_removed() {
125+
let mut chain = AmplifierChain::new();
126+
chain.add_stage(make_level(0.5));
127+
chain.add_stage(make_level(1.0));
128+
let removed = chain.remove_stage(0);
129+
assert!(removed.is_some());
130+
let out = chain.process(1.0);
131+
assert!((out - 1.0).abs() < 1e-6);
132+
}
133+
134+
#[test]
135+
fn remove_stage_out_of_bounds() {
136+
let mut chain = AmplifierChain::new();
137+
assert!(chain.remove_stage(5).is_none());
138+
}
139+
140+
#[test]
141+
fn swap_stages_changes_order() {
142+
let mut chain = AmplifierChain::new();
143+
chain.add_stage(make_level(0.5));
144+
chain.add_stage(make_level(2.0));
145+
chain.swap_stages(0, 1);
146+
assert!((chain.get_parameter(0, "gain").unwrap().unwrap() - 2.0).abs() < 1e-6);
147+
assert!((chain.get_parameter(1, "gain").unwrap().unwrap() - 0.5).abs() < 1e-6);
148+
}
149+
150+
#[test]
151+
fn replace_stage_preserves_others() {
152+
let mut chain = AmplifierChain::new();
153+
chain.add_stage(make_level(0.5));
154+
chain.add_stage(make_level(1.0));
155+
let old = chain.replace_stage(0, make_level(0.25));
156+
assert!(old.is_some());
157+
let out = chain.process(1.0);
158+
assert!((out - 0.25).abs() < 1e-6);
159+
}
34160
}

src/audio/engine.rs

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use crate::amp::stages::Stage;
77
use crate::audio::peak_meter::PeakMeter;
88
use crate::audio::pitch_shifter::PitchShifter;
99
use crate::audio::recorder::Recorder;
10+
use crate::audio::rt_drop::RtDropHandle;
1011
use crate::audio::samplers::Samplers;
1112
use crate::ir::cabinet::IrCabinet;
1213
use crate::ir::convolver::Convolver;
13-
use crate::ir::load_service::ConvolverDropHandle;
1414
use crate::metronome::Metronome;
1515
use crate::tuner::Tuner;
1616

@@ -22,6 +22,11 @@ pub struct PreparedIr {
2222
pub enum EngineMessage {
2323
SetAmpChain(Box<AmplifierChain>),
2424
SetInputFilters(Option<Box<dyn Stage>>, Option<Box<dyn Stage>>),
25+
SetParameter(usize, &'static str, f32),
26+
ReplaceStage(usize, Box<dyn Stage>),
27+
AddStage(usize, Box<dyn Stage>),
28+
RemoveStage(usize),
29+
SwapStages(usize, usize),
2530
StartRecording(Recorder),
2631
StopRecording,
2732
SwapIrConvolver(Box<PreparedIr>),
@@ -39,8 +44,8 @@ pub struct Engine {
3944
ir_cabinet: Option<IrCabinet>,
4045
/// Channel for updating the amplifier chain.
4146
engine_receiver: Receiver<EngineMessage>,
42-
/// Handle for sending old convolvers off the RT thread for deallocation.
43-
convolver_drop: ConvolverDropHandle,
47+
/// Handle for sending arbitrary objects off the RT thread for deallocation.
48+
rt_drop: RtDropHandle,
4449
samplers: Samplers,
4550
tuner: Tuner,
4651
recorder: Option<Recorder>,
@@ -63,16 +68,16 @@ impl Engine {
6368
ir_cabinet: Option<IrCabinet>,
6469
peak_meter: PeakMeter,
6570
metronome: Metronome,
66-
convolver_drop: ConvolverDropHandle,
71+
rt_drop: RtDropHandle,
6772
) -> Result<(Self, EngineHandle)> {
68-
let (engine_sender, engine_receiver) = bounded::<EngineMessage>(10);
73+
let (engine_sender, engine_receiver) = bounded::<EngineMessage>(32);
6974

7075
Ok((
7176
Self {
7277
chain: Box::new(AmplifierChain::new()),
7378
ir_cabinet,
7479
engine_receiver,
75-
convolver_drop,
80+
rt_drop,
7681
samplers,
7782
tuner,
7883
recorder: None,
@@ -188,10 +193,44 @@ impl Engine {
188193
pub fn handle_messages(&mut self) {
189194
while let Ok(message) = self.engine_receiver.try_recv() {
190195
match message {
191-
EngineMessage::SetAmpChain(chain) => {
192-
self.chain = chain;
196+
EngineMessage::SetAmpChain(new_chain) => {
197+
let old = std::mem::replace(&mut self.chain, new_chain);
198+
self.rt_drop.retire(old);
193199
debug!("Received new amplifier chain");
194200
}
201+
EngineMessage::SetParameter(idx, name, value) => {
202+
if let Some(result) = self.chain.set_parameter(idx, name, value) {
203+
if let Err(e) = result {
204+
error!("Failed to set parameter '{name}' on stage {idx}: {e}");
205+
}
206+
} else {
207+
error!("SetParameter: stage index {idx} out of bounds");
208+
}
209+
}
210+
EngineMessage::ReplaceStage(idx, new_stage) => {
211+
if let Some(old) = self.chain.replace_stage(idx, new_stage) {
212+
self.rt_drop.retire(old);
213+
debug!("Replaced stage at index {idx}");
214+
} else {
215+
error!("ReplaceStage: stage index {idx} out of bounds");
216+
}
217+
}
218+
EngineMessage::AddStage(idx, stage) => {
219+
self.chain.insert_stage(idx, stage);
220+
debug!("Added stage at index {idx}");
221+
}
222+
EngineMessage::RemoveStage(idx) => {
223+
if let Some(old) = self.chain.remove_stage(idx) {
224+
self.rt_drop.retire(old);
225+
debug!("Removed stage at index {idx}");
226+
} else {
227+
error!("RemoveStage: stage index {idx} out of bounds");
228+
}
229+
}
230+
EngineMessage::SwapStages(a, b) => {
231+
self.chain.swap_stages(a, b);
232+
debug!("Swapped stages {a} and {b}");
233+
}
195234
EngineMessage::SetInputFilters(hp, lp) => {
196235
self.input_highpass = hp;
197236
self.input_lowpass = lp;
@@ -201,7 +240,7 @@ impl Engine {
201240
if let Some(ref mut cab) = self.ir_cabinet {
202241
debug!("IR convolver swapped: {}", prepared.name);
203242
let old = cab.swap_convolver(prepared.convolver);
204-
self.convolver_drop.retire(old);
243+
self.rt_drop.retire(Box::new(old));
205244
}
206245
}
207246
EngineMessage::ClearIr => {
@@ -320,6 +359,26 @@ impl EngineHandle {
320359
self.send(update);
321360
}
322361

362+
pub fn set_parameter(&self, stage_idx: usize, name: &'static str, value: f32) {
363+
self.send(EngineMessage::SetParameter(stage_idx, name, value));
364+
}
365+
366+
pub fn replace_stage(&self, idx: usize, stage: Box<dyn Stage>) {
367+
self.send(EngineMessage::ReplaceStage(idx, stage));
368+
}
369+
370+
pub fn add_stage(&self, idx: usize, stage: Box<dyn Stage>) {
371+
self.send(EngineMessage::AddStage(idx, stage));
372+
}
373+
374+
pub fn remove_stage(&self, idx: usize) {
375+
self.send(EngineMessage::RemoveStage(idx));
376+
}
377+
378+
pub fn swap_stages(&self, a: usize, b: usize) {
379+
self.send(EngineMessage::SwapStages(a, b));
380+
}
381+
323382
pub fn set_amp_chain(&self, new_chain: AmplifierChain) {
324383
let update = EngineMessage::SetAmpChain(Box::new(new_chain));
325384
self.send(update);

src/audio/manager.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use crate::audio::engine::Engine;
1010
use crate::audio::engine::EngineHandle;
1111
use crate::audio::jack::{NotificationHandler, ProcessHandler};
1212
use crate::audio::peak_meter::{PeakMeter, PeakMeterHandle};
13+
use crate::audio::rt_drop::RtDropHandle;
1314
use crate::audio::samplers::Samplers;
1415
use crate::ir::cabinet::{ConvolverType, DEFAULT_MAX_IR_MS, IrCabinet};
15-
use crate::ir::load_service::{self, ConvolverDropHandle, IrLoadHandle};
16+
use crate::ir::load_service::{self, IrLoadHandle};
1617
use crate::ir::loader::IrLoader;
1718
use crate::metronome::Metronome;
1819
use crate::settings::{AudioSettings, Settings};
@@ -66,25 +67,29 @@ impl Manager {
6667

6768
let ir_cabinet = Some(IrCabinet::new(convolver_type, max_ir_samples));
6869

69-
let (convolver_drop_handle, convolver_drop_rx) = ConvolverDropHandle::new();
70+
let (rt_drop_handle, rt_drop_rx) = RtDropHandle::new();
7071

7172
let (engine, engine_handle) = Engine::new(
7273
tuner,
7374
samplers,
7475
ir_cabinet,
7576
peak_meter,
7677
metronome,
77-
convolver_drop_handle,
78+
rt_drop_handle,
7879
)?;
7980

81+
let _rt_drop_thread = std::thread::Builder::new()
82+
.name("rt-drop-service".into())
83+
.spawn(move || rt_drop_rx.run())
84+
.expect("Failed to spawn RT drop service thread");
85+
8086
let ir_load_handle = ir_loader.map(|loader| {
8187
load_service::spawn(
8288
loader,
8389
engine_handle.clone(),
8490
sample_rate,
8591
DEFAULT_MAX_IR_MS,
8692
convolver_type,
87-
convolver_drop_rx,
8893
)
8994
});
9095

src/audio/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ pub mod peak_meter;
55
pub mod pitch_shifter;
66
pub mod ports;
77
pub mod recorder;
8+
pub mod rt_drop;
89
pub mod samplers;

0 commit comments

Comments
 (0)