From 502f7afe8f56792790cd1e8eab135c8154b981a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 23 Feb 2026 21:53:53 +0100 Subject: [PATCH] Feat: fixing stderr catching and scope not drawing completely --- src/bin/desktop/main.rs | 11 +++++++++++ src/engine/audio.rs | 10 ++++++---- src/init.rs | 5 +++++ src/main.rs | 9 +++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/bin/desktop/main.rs b/src/bin/desktop/main.rs index 22f83e5..4e16f59 100644 --- a/src/bin/desktop/main.rs +++ b/src/bin/desktop/main.rs @@ -158,6 +158,7 @@ struct CagireDesktop { _stream: Option, _analysis_handle: Option, midi_rx: Receiver, + stream_error_rx: crossbeam_channel::Receiver, current_font: FontChoice, zoom_factor: f32, fullscreen: bool, @@ -201,6 +202,7 @@ impl CagireDesktop { _stream: b.stream, _analysis_handle: b.analysis_handle, midi_rx: b.midi_rx, + stream_error_rx: b.stream_error_rx, current_font, zoom_factor, fullscreen: false, @@ -235,6 +237,9 @@ impl CagireDesktop { max_voices: self.app.audio.config.max_voices, }; + let (new_error_tx, new_error_rx) = crossbeam_channel::bounded(16); + self.stream_error_rx = new_error_rx; + let mut restart_samples = Vec::new(); for path in &self.app.audio.config.sample_paths { let index = doux::sampling::scan_samples_dir(path); @@ -257,6 +262,7 @@ impl CagireDesktop { Arc::clone(&self.metrics), restart_samples, Arc::clone(&self.audio_sample_pos), + new_error_tx, ) { Ok((new_stream, info, new_analysis, registry)) => { self._stream = Some(new_stream); @@ -350,6 +356,11 @@ impl CagireDesktop { impl eframe::App for CagireDesktop { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.handle_audio_restart(); + + while let Ok(err) = self.stream_error_rx.try_recv() { + self.app.ui.flash(&err, 3000, cagire::state::FlashKind::Error); + } + self.update_metrics(); ctx.input(|i| { diff --git a/src/engine/audio.rs b/src/engine/audio.rs index 139df71..41dbb70 100644 --- a/src/engine/audio.rs +++ b/src/engine/audio.rs @@ -251,7 +251,7 @@ use cpal::traits::{DeviceTrait, StreamTrait}; #[cfg(feature = "cli")] use cpal::Stream; #[cfg(feature = "cli")] -use crossbeam_channel::Receiver; +use crossbeam_channel::{Receiver, Sender}; #[cfg(feature = "cli")] use doux::{Engine, EngineMetrics}; @@ -274,6 +274,7 @@ pub struct AudioStreamInfo { } #[cfg(feature = "cli")] +#[allow(clippy::too_many_arguments)] pub fn build_stream( config: &AudioStreamConfig, audio_rx: Receiver, @@ -282,6 +283,7 @@ pub fn build_stream( metrics: Arc, initial_samples: Vec, audio_sample_pos: Arc, + error_tx: Sender, ) -> Result< ( Stream, @@ -375,15 +377,15 @@ pub fn build_stream( engine.metrics.load.set_buffer_time(buffer_time_ns); engine.process_block(data, &[], &[]); - scope_buffer.write(&engine.output); + scope_buffer.write(data); // Feed mono mix to analysis thread via ring buffer (non-blocking) - for chunk in engine.output.chunks(channels) { + for chunk in data.chunks(channels) { let mono = chunk.iter().sum::() / channels as f32; let _ = fft_producer.try_push(mono); } }, - |err| eprintln!("stream error: {err}"), + move |err| { let _ = error_tx.try_send(format!("stream error: {err}")); }, None, ) .map_err(|e| format!("Failed to build stream: {e}"))?; diff --git a/src/init.rs b/src/init.rs index 9445542..9e119cc 100644 --- a/src/init.rs +++ b/src/init.rs @@ -42,6 +42,7 @@ pub struct Init { pub stream: Option, pub analysis_handle: Option, pub midi_rx: Receiver, + pub stream_error_rx: crossbeam_channel::Receiver, #[cfg(feature = "desktop")] pub settings: Settings, #[cfg(feature = "desktop")] @@ -188,6 +189,8 @@ pub fn init(args: InitArgs) -> Init { seq_config, ); + let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16); + let stream_config = AudioStreamConfig { output_device: app.audio.config.output_device.clone(), channels: app.audio.config.channels, @@ -203,6 +206,7 @@ pub fn init(args: InitArgs) -> Init { Arc::clone(&metrics), initial_samples, Arc::clone(&audio_sample_pos), + stream_error_tx, ) { Ok((s, info, analysis, registry)) => { app.audio.config.sample_rate = info.sample_rate; @@ -246,6 +250,7 @@ pub fn init(args: InitArgs) -> Init { stream, analysis_handle, midi_rx, + stream_error_rx, #[cfg(feature = "desktop")] settings, #[cfg(feature = "desktop")] diff --git a/src/main.rs b/src/main.rs index 09ff919..0b2cf7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,7 @@ fn main() -> io::Result<()> { let mut _stream = b.stream; let mut _analysis_handle = b.analysis_handle; let mut midi_rx = b.midi_rx; + let mut stream_error_rx = b.stream_error_rx; enable_raw_mode()?; io::stdout().execute(EnableBracketedPaste)?; @@ -110,6 +111,9 @@ fn main() -> io::Result<()> { max_voices: app.audio.config.max_voices, }; + let (new_error_tx, new_error_rx) = crossbeam_channel::bounded(16); + stream_error_rx = new_error_rx; + let mut restart_samples = Vec::new(); for path in &app.audio.config.sample_paths { let index = doux::sampling::scan_samples_dir(path); @@ -132,6 +136,7 @@ fn main() -> io::Result<()> { Arc::clone(&metrics), restart_samples, Arc::clone(&audio_sample_pos), + new_error_tx, ) { Ok((new_stream, info, new_analysis, registry)) => { _stream = Some(new_stream); @@ -161,6 +166,10 @@ fn main() -> io::Result<()> { } } + while let Ok(err) = stream_error_rx.try_recv() { + app.ui.flash(&err, 3000, state::FlashKind::Error); + } + app.playback.playing = playing.load(Ordering::Relaxed); while let Ok(midi_cmd) = midi_rx.try_recv() {