Skip to content

Commit 153512c

Browse files
authored
fix: move IR loading off RT thread and drain engine messages (#213) (#215)
1 parent b7447f6 commit 153512c

10 files changed

Lines changed: 526 additions & 165 deletions

File tree

benches/common/mod.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use hound::{WavSpec, WavWriter};
2-
use rustortion::ir::cabinet::IrCabinet;
2+
use rustortion::ir::cabinet::{ConvolverType, DEFAULT_MAX_IR_MS, IrCabinet};
3+
use rustortion::ir::convolver::Convolver;
4+
use rustortion::ir::loader::IrLoader;
35
use std::fs;
46
use std::path::Path;
57

@@ -12,11 +14,18 @@ pub fn create_test_cabinet(ir_length: usize, sample_rate: usize) -> IrCabinet {
1214
create_synthetic_ir(&ir_path, ir_length, sample_rate as u32);
1315
}
1416

15-
let mut cabinet = IrCabinet::new(&ir_dir, sample_rate).unwrap();
16-
cabinet
17-
.select_ir(&format!("test_ir_{ir_length}.wav"))
17+
let max_ir_samples = (sample_rate * DEFAULT_MAX_IR_MS) / 1000;
18+
let mut cabinet = IrCabinet::new(ConvolverType::Fir, max_ir_samples);
19+
20+
let loader = IrLoader::new(&ir_dir, sample_rate).unwrap();
21+
let ir_samples = loader
22+
.load_by_name(&format!("test_ir_{ir_length}.wav"))
1823
.unwrap();
1924

25+
let mut convolver = Convolver::new_fir(max_ir_samples);
26+
convolver.set_ir(&ir_samples).unwrap();
27+
cabinet.swap_convolver(convolver);
28+
2029
cabinet
2130
}
2231

benches/engine.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustortion::amp::stages::level::LevelStage;
88
use rustortion::audio::engine::{Engine, EngineHandle};
99
use rustortion::audio::peak_meter::PeakMeter;
1010
use rustortion::audio::samplers::Samplers;
11+
use rustortion::ir::load_service::ConvolverDropHandle;
1112
use rustortion::metronome::Metronome;
1213
use rustortion::tuner::Tuner;
1314

@@ -28,7 +29,15 @@ pub fn build_engine(
2829
let (peak_meter, _) = PeakMeter::new(SAMPLE_RATE);
2930
let samplers = Samplers::new(buffer_size, oversample, SAMPLE_RATE).unwrap();
3031
let metronome = Metronome::new(120.0, SAMPLE_RATE);
31-
let (engine, handle) = Engine::new(tuner, samplers, ir_cabinet, peak_meter, metronome).unwrap();
32+
let (engine, handle) = Engine::new(
33+
tuner,
34+
samplers,
35+
ir_cabinet,
36+
peak_meter,
37+
metronome,
38+
ConvolverDropHandle::new().0,
39+
)
40+
.unwrap();
3241
(engine, handle)
3342
}
3443

@@ -104,8 +113,15 @@ fn bench_engine_with_ir_cabinet(c: &mut Criterion) {
104113
let ir_cabinet = Some(create_test_cabinet(20000, SAMPLE_RATE));
105114
let (peak_meter, _) = PeakMeter::new(SAMPLE_RATE);
106115
let metronome = Metronome::new(120.0, SAMPLE_RATE);
107-
let (mut engine, _) =
108-
Engine::new(tuner, samplers, ir_cabinet, peak_meter, metronome).unwrap();
116+
let (mut engine, _) = Engine::new(
117+
tuner,
118+
samplers,
119+
ir_cabinet,
120+
peak_meter,
121+
metronome,
122+
ConvolverDropHandle::new().0,
123+
)
124+
.unwrap();
109125

110126
let input = vec![0.5f32; BUFFER_SIZE];
111127
let mut output = vec![0.0f32; BUFFER_SIZE];

src/audio/engine.rs

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ use crate::audio::pitch_shifter::PitchShifter;
99
use crate::audio::recorder::Recorder;
1010
use crate::audio::samplers::Samplers;
1111
use crate::ir::cabinet::IrCabinet;
12+
use crate::ir::convolver::Convolver;
13+
use crate::ir::load_service::ConvolverDropHandle;
1214
use crate::metronome::Metronome;
1315
use crate::tuner::Tuner;
1416

17+
pub struct PreparedIr {
18+
pub name: String,
19+
pub convolver: Convolver,
20+
}
21+
1522
pub enum EngineMessage {
1623
SetAmpChain(Box<AmplifierChain>),
1724
SetInputFilters(Option<Box<dyn Stage>>, Option<Box<dyn Stage>>),
1825
StartRecording(Recorder),
1926
StopRecording,
20-
SetIrCabinet(Option<String>),
27+
SwapIrConvolver(Box<PreparedIr>),
28+
ClearIr,
2129
SetIrBypass(bool),
2230
SetIrGain(f32),
2331
SetTunerEnabled(bool),
@@ -31,6 +39,8 @@ pub struct Engine {
3139
ir_cabinet: Option<IrCabinet>,
3240
/// Channel for updating the amplifier chain.
3341
engine_receiver: Receiver<EngineMessage>,
42+
/// Handle for sending old convolvers off the RT thread for deallocation.
43+
convolver_drop: ConvolverDropHandle,
3444
samplers: Samplers,
3545
tuner: Tuner,
3646
recorder: Option<Recorder>,
@@ -41,6 +51,7 @@ pub struct Engine {
4151
input_lowpass: Option<Box<dyn Stage>>,
4252
}
4353

54+
#[derive(Clone)]
4455
pub struct EngineHandle {
4556
engine_sender: Sender<EngineMessage>,
4657
}
@@ -52,6 +63,7 @@ impl Engine {
5263
ir_cabinet: Option<IrCabinet>,
5364
peak_meter: PeakMeter,
5465
metronome: Metronome,
66+
convolver_drop: ConvolverDropHandle,
5567
) -> Result<(Self, EngineHandle)> {
5668
let (engine_sender, engine_receiver) = bounded::<EngineMessage>(10);
5769

@@ -60,6 +72,7 @@ impl Engine {
6072
chain: Box::new(AmplifierChain::new()),
6173
ir_cabinet,
6274
engine_receiver,
75+
convolver_drop,
6376
samplers,
6477
tuner,
6578
recorder: None,
@@ -173,7 +186,7 @@ impl Engine {
173186
}
174187

175188
pub fn handle_messages(&mut self) {
176-
if let Ok(message) = self.engine_receiver.try_recv() {
189+
while let Ok(message) = self.engine_receiver.try_recv() {
177190
match message {
178191
EngineMessage::SetAmpChain(chain) => {
179192
self.chain = chain;
@@ -184,15 +197,17 @@ impl Engine {
184197
self.input_lowpass = lp;
185198
debug!("Updated input filters");
186199
}
187-
EngineMessage::SetIrCabinet(ir_name) => {
188-
if let Some(ref mut cab) = self.ir_cabinet
189-
&& let Some(name) = ir_name
190-
{
191-
if let Err(e) = cab.select_ir(&name) {
192-
error!("Failed to set IR: {e}");
193-
} else {
194-
debug!("IR Cabinet set to: {name}");
195-
}
200+
EngineMessage::SwapIrConvolver(prepared) => {
201+
if let Some(ref mut cab) = self.ir_cabinet {
202+
debug!("IR convolver swapped: {}", prepared.name);
203+
let old = cab.swap_convolver(prepared.convolver);
204+
self.convolver_drop.retire(old);
205+
}
206+
}
207+
EngineMessage::ClearIr => {
208+
if let Some(ref mut cab) = self.ir_cabinet {
209+
cab.clear_convolver();
210+
debug!("IR cleared");
196211
}
197212
}
198213
EngineMessage::SetIrBypass(bypass) => {
@@ -211,44 +226,56 @@ impl Engine {
211226
self.tuner.set_enabled(enabled);
212227
}
213228
EngineMessage::StartRecording(recorder) => {
214-
if self.recorder.is_some() {
215-
debug!("Recorder already active, ignoring start request");
216-
return;
217-
}
218-
219-
debug!("Recorder updated");
220-
self.recorder = Some(recorder);
229+
self.handle_start_recording(recorder);
221230
}
222231
EngineMessage::StopRecording => {
223-
if self.recorder.is_none() {
224-
debug!("No active recorder to stop");
225-
return;
226-
}
227-
228-
debug!("Stopping recorder");
229-
if let Some(recorder) = self.recorder.take()
230-
&& let Err(e) = recorder.stop()
231-
{
232-
error!("Failed to stop recorder: {e}");
233-
}
234-
235-
self.recorder = None;
232+
self.handle_stop_recording();
236233
}
237234
EngineMessage::SetPitchShift(semitones) => {
238-
if semitones == 0 {
239-
self.pitch_shifter = None;
240-
debug!("Pitch shift disabled (bypass)");
241-
} else if let Some(ref mut shifter) = self.pitch_shifter {
242-
shifter.set_semitones(semitones as f32);
243-
debug!("Pitch shift set to {semitones} semitones");
244-
} else {
245-
self.pitch_shifter = Some(PitchShifter::new(semitones as f32));
246-
debug!("Pitch shift set to {semitones} semitones");
247-
}
235+
self.handle_pitch_shift(semitones);
248236
}
249237
}
250238
}
251239
}
240+
241+
fn handle_start_recording(&mut self, recorder: Recorder) {
242+
if self.recorder.is_some() {
243+
debug!("Recorder already active, ignoring start request");
244+
return;
245+
}
246+
247+
debug!("Recorder updated");
248+
self.recorder = Some(recorder);
249+
}
250+
251+
fn handle_stop_recording(&mut self) {
252+
if self.recorder.is_none() {
253+
debug!("No active recorder to stop");
254+
return;
255+
}
256+
257+
debug!("Stopping recorder");
258+
if let Some(recorder) = self.recorder.take()
259+
&& let Err(e) = recorder.stop()
260+
{
261+
error!("Failed to stop recorder: {e}");
262+
}
263+
264+
self.recorder = None;
265+
}
266+
267+
fn handle_pitch_shift(&mut self, semitones: i32) {
268+
if semitones == 0 {
269+
self.pitch_shifter = None;
270+
debug!("Pitch shift disabled (bypass)");
271+
} else if let Some(ref mut shifter) = self.pitch_shifter {
272+
shifter.set_semitones(semitones as f32);
273+
debug!("Pitch shift set to {semitones} semitones");
274+
} else {
275+
self.pitch_shifter = Some(PitchShifter::new(semitones as f32));
276+
debug!("Pitch shift set to {semitones} semitones");
277+
}
278+
}
252279
}
253280

254281
impl Drop for Engine {
@@ -269,11 +296,15 @@ impl EngineHandle {
269296
});
270297
}
271298

272-
pub fn set_ir_cabinet(&self, ir_name: Option<String>) {
273-
let update = EngineMessage::SetIrCabinet(ir_name);
299+
pub fn swap_ir_convolver(&self, prepared: PreparedIr) {
300+
let update = EngineMessage::SwapIrConvolver(Box::new(prepared));
274301
self.send(update);
275302
}
276303

304+
pub fn clear_ir(&self) {
305+
self.send(EngineMessage::ClearIr);
306+
}
307+
277308
pub fn set_ir_bypass(&self, bypass: bool) {
278309
let update = EngineMessage::SetIrBypass(bypass);
279310
self.send(update);

src/audio/manager.rs

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ use anyhow::{Context, Result};
55
use jack::{AsyncClient, Client, ClientOptions};
66
use log::{error, info, warn};
77

8-
use std::path::Path;
9-
108
use crate::amp::stages::clipper;
119
use crate::audio::engine::Engine;
1210
use crate::audio::engine::EngineHandle;
1311
use crate::audio::jack::{NotificationHandler, ProcessHandler};
1412
use crate::audio::peak_meter::{PeakMeter, PeakMeterHandle};
1513
use crate::audio::samplers::Samplers;
16-
use crate::ir::cabinet::IrCabinet;
14+
use crate::ir::cabinet::{ConvolverType, DEFAULT_MAX_IR_MS, IrCabinet};
15+
use crate::ir::load_service::{self, ConvolverDropHandle, IrLoadHandle};
16+
use crate::ir::loader::IrLoader;
1717
use crate::metronome::Metronome;
1818
use crate::settings::{AudioSettings, Settings};
1919
use crate::tuner::{Tuner, TunerHandle};
@@ -26,6 +26,7 @@ pub struct Manager {
2626
peak_meter_handle: PeakMeterHandle,
2727
xrun_count: Arc<AtomicU64>,
2828
available_irs: Vec<String>,
29+
ir_load_handle: Option<IrLoadHandle>,
2930
}
3031

3132
impl Manager {
@@ -48,21 +49,45 @@ impl Manager {
4849
let mut metronome = Metronome::new(120.0, sample_rate);
4950
metronome.load_wav_file("click.wav");
5051

51-
let ir_cabinet = match IrCabinet::new(Path::new(&settings.ir_dir), sample_rate) {
52-
Ok(cab) => Some(cab),
53-
Err(e) => {
54-
warn!("Failed to load IR Cabinet: {e}");
55-
None
56-
}
57-
};
52+
let convolver_type = ConvolverType::default();
53+
let max_ir_samples = (sample_rate * DEFAULT_MAX_IR_MS) / 1000;
54+
55+
let (ir_loader, available_irs) =
56+
match IrLoader::new(std::path::Path::new(&settings.ir_dir), sample_rate) {
57+
Ok(loader) => {
58+
let names = loader.available_ir_names();
59+
(Some(loader), names)
60+
}
61+
Err(e) => {
62+
warn!("Failed to load IR directory: {e}");
63+
(None, Vec::new())
64+
}
65+
};
66+
67+
let ir_cabinet = Some(IrCabinet::new(convolver_type, max_ir_samples));
68+
69+
let (convolver_drop_handle, convolver_drop_rx) = ConvolverDropHandle::new();
70+
71+
let (engine, engine_handle) = Engine::new(
72+
tuner,
73+
samplers,
74+
ir_cabinet,
75+
peak_meter,
76+
metronome,
77+
convolver_drop_handle,
78+
)?;
5879

59-
let available_irs = ir_cabinet
60-
.as_ref()
61-
.map(IrCabinet::available_ir_names)
62-
.unwrap_or_default();
80+
let ir_load_handle = ir_loader.map(|loader| {
81+
load_service::spawn(
82+
loader,
83+
engine_handle.clone(),
84+
sample_rate,
85+
DEFAULT_MAX_IR_MS,
86+
convolver_type,
87+
convolver_drop_rx,
88+
)
89+
});
6390

64-
let (engine, engine_handle) =
65-
Engine::new(tuner, samplers, ir_cabinet, peak_meter, metronome)?;
6691
let jack_handler =
6792
ProcessHandler::new(&client, engine).context("failed to create process handler")?;
6893

@@ -81,6 +106,7 @@ impl Manager {
81106
peak_meter_handle,
82107
xrun_count,
83108
available_irs,
109+
ir_load_handle,
84110
};
85111

86112
manager.connect_ports(&settings.audio);
@@ -180,6 +206,24 @@ impl Manager {
180206
self.available_irs.clone()
181207
}
182208

209+
pub fn request_ir_load(&self, name: &str) {
210+
if let Some(ref handle) = self.ir_load_handle {
211+
handle.request_load(name);
212+
}
213+
}
214+
215+
pub fn clear_ir(&self) {
216+
self.engine_handle.clear_ir();
217+
}
218+
219+
pub fn preload_irs(&self, names: &[String]) {
220+
if let Some(ref handle) = self.ir_load_handle {
221+
for name in names {
222+
handle.preload(name);
223+
}
224+
}
225+
}
226+
183227
pub fn sample_rate(&self) -> usize {
184228
self.active_client.as_client().sample_rate() as usize
185229
}

0 commit comments

Comments
 (0)