Skip to content

Commit 5918753

Browse files
authored
Merge pull request #1 from OpenSauce/refactor-recorder
Create recorder.rs, spawn recorder in new thread
2 parents e267578 + 9ca1e66 commit 5918753

File tree

5 files changed

+152
-63
lines changed

5 files changed

+152
-63
lines changed

Cargo.lock

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ rubato = "0.16"
1313
serde = { version = "1.0", features = ["derive"] }
1414
serde_json = "1.0"
1515
serde_derive = "1.0"
16+
crossbeam = "0.8"

src/main.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ use std::{
1414

1515
mod amp;
1616
mod processor;
17+
mod recorder;
1718

1819
use amp::{Amp, AmpConfig};
1920
use clap::Parser;
2021
use processor::Processor;
22+
use recorder::Recorder;
2123

2224
#[derive(Parser, Debug)]
2325
#[command(name = "rustortion")]
@@ -43,10 +45,6 @@ fn main() {
4345

4446
let config = load_amp_config(&args.preset_path).expect("Failed to load preset file");
4547

46-
if recording {
47-
std::fs::create_dir_all("./recordings").unwrap();
48-
}
49-
5048
println!(
5149
"🔥 Rustortion: {}",
5250
if recording { "🛑 Recording!" } else { "" }
@@ -57,7 +55,13 @@ fn main() {
5755
let sample_rate = client.sample_rate() as f32;
5856
let amp = Amp::new(config, sample_rate);
5957

60-
let (processor, writer) = Processor::new(&client, amp, recording);
58+
let recorder = if recording {
59+
Some(Recorder::new(sample_rate as u32, "./recordings").expect("failed to start recorder"))
60+
} else {
61+
None
62+
};
63+
let tx = recorder.as_ref().map(|r| r.sender());
64+
let processor = Processor::new(&client, amp, tx);
6165
let process = processor.into_process_handler();
6266

6367
let _active_client = client
@@ -66,26 +70,25 @@ fn main() {
6670

6771
let running = Arc::new(AtomicBool::new(true));
6872
let r = Arc::clone(&running);
69-
let writer_clone = writer.clone();
7073

7174
ctrlc::set_handler(move || {
7275
println!("\nCtrl+C received, shutting down...");
7376

74-
if let Some(writer_arc) = &writer_clone {
75-
if let Ok(mut maybe_writer) = writer_arc.lock() {
76-
if let Some(writer) = maybe_writer.take() {
77-
writer.finalize().expect("Failed to finalize WAV file");
78-
}
79-
}
80-
}
81-
8277
r.store(false, Ordering::SeqCst);
8378
})
8479
.expect("Error setting Ctrl+C handler");
8580

8681
while running.load(Ordering::SeqCst) {
8782
thread::sleep(Duration::from_secs(1));
8883
}
84+
85+
_active_client
86+
.deactivate()
87+
.expect("Failed to deactivate JACK client");
88+
89+
if let Some(rec) = recorder {
90+
rec.stop(); // join disk thread
91+
}
8992
}
9093

9194
fn load_amp_config(path: &str) -> std::io::Result<AmpConfig> {

src/processor.rs

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
use crate::amp::Amp;
2-
use hound::WavWriter;
2+
use crate::recorder::{AudioBlock, BLOCK_FRAMES};
3+
use crossbeam::channel::Sender;
34
use jack::{AudioIn, AudioOut, Client, Control, Port, ProcessScope};
45
use rubato::{
56
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
67
};
7-
use std::fs::File;
8-
use std::io::BufWriter;
9-
use std::sync::{Arc, Mutex};
10-
11-
pub type RecordingWriter = Option<Arc<Mutex<Option<WavWriter<BufWriter<File>>>>>>;
128

139
pub struct Processor {
1410
amp: Amp,
15-
writer: RecordingWriter,
11+
tx: Option<Sender<AudioBlock>>,
1612
in_port: Port<AudioIn>,
1713
out_l: Port<AudioOut>,
1814
out_r: Port<AudioOut>,
@@ -21,7 +17,7 @@ pub struct Processor {
2117
}
2218

2319
impl Processor {
24-
pub fn new(client: &Client, amp: Amp, recording: bool) -> (Self, RecordingWriter) {
20+
pub fn new(client: &Client, amp: Amp, tx: Option<Sender<AudioBlock>>) -> Self {
2521
let in_port = client.register_port("in", AudioIn).unwrap();
2622
let out_l = client.register_port("out_l", AudioOut).unwrap();
2723
let out_r = client.register_port("out_r", AudioOut).unwrap();
@@ -30,26 +26,6 @@ impl Processor {
3026
let _ = client.connect_ports_by_name("rustortion:out_l", "system:playback_1");
3127
let _ = client.connect_ports_by_name("rustortion:out_r", "system:playback_2");
3228

33-
let sample_rate = client.sample_rate() as f32;
34-
35-
let writer = if recording {
36-
let spec = hound::WavSpec {
37-
channels: 2,
38-
sample_rate: sample_rate as u32,
39-
bits_per_sample: 16,
40-
sample_format: hound::SampleFormat::Int,
41-
};
42-
let filename = format!(
43-
"./recordings/recording_{}.wav",
44-
chrono::Local::now().format("%Y%m%d_%H%M%S")
45-
);
46-
println!("Recording to: {}", filename);
47-
let writer = hound::WavWriter::create(filename, spec).unwrap();
48-
Some(Arc::new(Mutex::new(Some(writer))))
49-
} else {
50-
None
51-
};
52-
5329
let channels = 1;
5430
let oversample_factor: f32 = 2.0;
5531

@@ -88,26 +64,23 @@ impl Processor {
8864
)
8965
.unwrap();
9066

91-
(
92-
Self {
93-
amp,
94-
writer: writer.clone(),
95-
in_port,
96-
out_l,
97-
out_r,
98-
upsampler,
99-
downsampler,
100-
},
101-
writer,
102-
)
67+
Self {
68+
amp,
69+
tx,
70+
in_port,
71+
out_l,
72+
out_r,
73+
upsampler,
74+
downsampler,
75+
}
10376
}
10477

10578
pub fn into_process_handler(
10679
self,
10780
) -> impl FnMut(&Client, &ProcessScope) -> Control + Send + 'static {
10881
let Processor {
10982
mut amp,
110-
writer,
83+
tx,
11184
in_port,
11285
mut out_l,
11386
mut out_r,
@@ -167,15 +140,13 @@ impl Processor {
167140
out_buf_r[i] = out_sample;
168141
}
169142

170-
if let Some(mut writer_mutex) = writer.as_ref().map(|w| w.lock().unwrap()) {
171-
if let Some(ref mut writer) = *writer_mutex {
172-
for &s in final_samples.iter().take(frames_to_copy) {
173-
let sample_i16 =
174-
(s * i16::MAX as f32).clamp(i16::MIN as f32, i16::MAX as f32) as i16;
175-
writer.write_sample(sample_i16).unwrap(); // left
176-
writer.write_sample(sample_i16).unwrap(); // right
177-
}
143+
if let Some(ref tx) = tx {
144+
let mut block = AudioBlock::with_capacity(BLOCK_FRAMES * 2);
145+
for &s in final_samples.iter().take(BLOCK_FRAMES) {
146+
let v = (s * i16::MAX as f32).clamp(i16::MIN as f32, i16::MAX as f32) as i16;
147+
block.extend_from_slice(&[v, v]);
178148
}
149+
let _ = tx.try_send(block); // never blocks
179150
}
180151

181152
Control::Continue

src/recorder.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use crossbeam::channel::{Receiver, Sender, bounded};
2+
use hound::WavWriter;
3+
use std::{fs, thread};
4+
5+
/// One block = inter‑leaved stereo samples (L R L R …).
6+
pub type AudioBlock = Vec<i16>;
7+
pub const BLOCK_FRAMES: usize = 256; // tweak latency here
8+
9+
/// Handle returned to the caller.
10+
pub struct Recorder {
11+
tx: Sender<AudioBlock>,
12+
handle: thread::JoinHandle<()>,
13+
}
14+
15+
impl Recorder {
16+
pub fn new(sample_rate: u32, record_dir: &str) -> std::io::Result<Self> {
17+
let (tx, rx) = bounded::<AudioBlock>(32); // 32×256 ≈ 5 s @48 kHz
18+
fs::create_dir_all(record_dir)?;
19+
20+
let filename = format!(
21+
"{record_dir}/recording_{}.wav",
22+
chrono::Local::now().format("%Y%m%d_%H%M%S")
23+
);
24+
println!("Recording to: {filename}");
25+
26+
let handle = thread::spawn(move || run_writer_thread(sample_rate, filename, rx));
27+
28+
Ok(Self { tx, handle })
29+
}
30+
31+
pub fn sender(&self) -> Sender<AudioBlock> {
32+
self.tx.clone()
33+
}
34+
35+
pub fn stop(self) {
36+
drop(self.tx);
37+
self.handle.join().expect("Unable to join thread");
38+
}
39+
}
40+
41+
fn run_writer_thread(sample_rate: u32, filename: String, rx: Receiver<AudioBlock>) {
42+
let spec = hound::WavSpec {
43+
channels: 2,
44+
sample_rate,
45+
bits_per_sample: 16,
46+
sample_format: hound::SampleFormat::Int,
47+
};
48+
let mut writer = WavWriter::create(filename, spec).unwrap();
49+
50+
for block in rx {
51+
for sample in &block {
52+
writer.write_sample(*sample).unwrap();
53+
}
54+
}
55+
56+
writer.finalize().expect("Failed to finalise WAV file");
57+
}

0 commit comments

Comments
 (0)