@@ -13,21 +13,52 @@ use super::events::process_transcription_events;
1313use crate :: ptt:: handlers:: { handle_ptt_pressed, handle_ptt_released} ;
1414use crate :: workers;
1515
16+ /// Type alias for the audio callback closure.
17+ pub type AudioCallback = Box < dyn FnMut ( & [ f32 ] ) + Send > ;
18+
19+ /// Grouped transmit channels for the audio callback.
20+ pub struct CallbackTx {
21+ /// RMS audio level → TUI gauge (0..100).
22+ pub tx_level : mpsc:: SyncSender < u16 > ,
23+ /// Log lines → TUI status panel.
24+ pub tx_log : mpsc:: SyncSender < String > ,
25+ /// Spectrum bars (EQ) → TUI visualizer.
26+ pub tx_spec : mpsc:: SyncSender < Vec < u16 > > ,
27+ /// Live preview text → TUI preview area.
28+ pub tx_preview : mpsc:: SyncSender < String > ,
29+ /// VAD open/closed → TUI indicator.
30+ pub tx_vad : mpsc:: SyncSender < bool > ,
31+ }
32+
33+ /// Parameters required to construct the audio callback.
34+ pub struct AudioCallbackParams {
35+ /// EQ configuration for spectrum computation.
36+ pub eq_cfg : EqConfig ,
37+ /// Voice Activity Detection thresholds.
38+ pub vad : VadThresholds ,
39+ /// Whisper worker/transcriber handle.
40+ pub transcriber : workers:: whisper:: WhisperHandle ,
41+ /// Output destination for final transcriptions.
42+ pub sink : Arc < dyn OutputSink > ,
43+ /// Sound effects player for PTT/VAD cues.
44+ pub sfx : Arc < SfxPlayer > ,
45+ /// Shared PTT hold flag (hotkey state).
46+ pub hold_flag : Arc < AtomicBool > ,
47+ /// Grouped outbound channels to the TUI.
48+ pub tx : CallbackTx ,
49+ }
50+
1651/// Create the audio frame callback with PTT state machine.
17- #[ allow( clippy:: too_many_arguments, clippy:: type_complexity) ]
18- pub fn create_audio_callback (
19- eq_cfg : EqConfig ,
20- vad_config : & VadThresholds ,
21- transcriber : workers:: whisper:: WhisperHandle ,
22- sink : Arc < Box < dyn OutputSink > > ,
23- sfx : Arc < SfxPlayer > ,
24- hold_flag : Arc < AtomicBool > ,
25- tx_level : mpsc:: SyncSender < u16 > ,
26- tx_log : mpsc:: SyncSender < String > ,
27- tx_spec : mpsc:: SyncSender < Vec < u16 > > ,
28- tx_preview : mpsc:: SyncSender < String > ,
29- tx_vad : mpsc:: SyncSender < bool > ,
30- ) -> Box < dyn FnMut ( & [ f32 ] ) + Send > {
52+ pub fn create_audio_callback ( params : AudioCallbackParams ) -> AudioCallback {
53+ let AudioCallbackParams {
54+ eq_cfg,
55+ vad,
56+ transcriber,
57+ sink,
58+ sfx,
59+ hold_flag,
60+ tx,
61+ } = params;
3162 // Clone inner transcriber handle (needed for closure capture; Arc is cheap).
3263 let trans_clone = transcriber. inner_clone ( ) ;
3364
@@ -37,10 +68,10 @@ pub fn create_audio_callback(
3768 let mut held_prev = false ;
3869 // VAD gate: hysteresis-based voice activity detection (prevents flickering).
3970 let mut vad = VadGate :: new (
40- vad_config . start_db ,
41- vad_config . stop_db ,
42- vad_config . min_duration_ms ,
43- vad_config . max_silence_ms ,
71+ vad . start_db ,
72+ vad . stop_db ,
73+ vad . min_duration_ms ,
74+ vad . max_silence_ms ,
4475 100.0 , // Sample rate in Hz (used for timing calculations).
4576 ) ;
4677
@@ -53,7 +84,7 @@ pub fn create_audio_callback(
5384 // RMS = sqrt(sum(samples²) / count); normalized to [0, 100] for gauge display.
5485 let rms = ( frame. iter ( ) . map ( |s| s * s) . sum :: < f32 > ( ) / frame. len ( ) as f32 ) . sqrt ( ) ;
5586 let level = ( rms * 100.0 ) . clamp ( 0.0 , 100.0 ) as u16 ;
56- let _ = tx_level. try_send ( level) ;
87+ let _ = tx . tx_level . try_send ( level) ;
5788 // Convert to dB for VAD (20 * log10(rms)); max(1e-8) prevents log(0) = -inf.
5889 let level_db = 20.0 * ( rms. max ( 1e-8 ) ) . log10 ( ) ;
5990
@@ -73,17 +104,17 @@ pub fn create_audio_callback(
73104 // Hotkey is released (PTT not held).
74105 if held_prev {
75106 // JUST released (transition): finalize the segment (flush transcriber, emit Final).
76- handle_ptt_released ( & trans_clone, & sink, & tx_log, & tx_preview, & sfx) ;
107+ handle_ptt_released ( & trans_clone, & sink, & tx . tx_log , & tx . tx_preview , & sfx) ;
77108 sfx. play_release ( ) ;
78109 held_prev = false ;
79110 }
80111 // CRITICAL: Always drain events even when not held, to catch asynchronous Final events
81112 // from the inference thread (they arrive later, after hotkey is released).
82- process_transcription_events ( & trans_clone, & sink, & tx_log, & tx_preview, & sfx) ;
113+ process_transcription_events ( & trans_clone, & sink, & tx . tx_log , & tx . tx_preview , & sfx) ;
83114
84115 // Show flat/empty visualizer when idle (only send if state changed to avoid spam).
85116 if !last_spec_was_zero {
86- let _ = tx_spec. try_send ( zero_spectrum. clone ( ) ) ;
117+ let _ = tx . tx_spec . try_send ( zero_spectrum. clone ( ) ) ;
87118 last_spec_was_zero = true ;
88119 }
89120 return ;
@@ -92,7 +123,7 @@ pub fn create_audio_callback(
92123 // Hotkey is held (PTT active).
93124 if !held_prev {
94125 // JUST pressed (transition): start a new dictation session.
95- handle_ptt_pressed ( & tx_log, & tx_preview) ;
126+ handle_ptt_pressed ( & tx . tx_log , & tx . tx_preview ) ;
96127 sfx. play_press ( ) ;
97128 // Reset VAD timers (don't carry over from previous session; fresh start).
98129 vad. reset ( ) ;
@@ -119,19 +150,19 @@ pub fn create_audio_callback(
119150 level_db = %level_db,
120151 "VAD gate state changed"
121152 ) ;
122- let _ = tx_log. try_send ( format ! (
153+ let _ = tx . tx_log . try_send ( format ! (
123154 "VAD {} at {:.1} dB" ,
124155 if gate_open { "OPEN" } else { "CLOSED" } ,
125156 level_db
126157 ) ) ;
127- let _ = tx_vad. try_send ( gate_open) ;
158+ let _ = tx . tx_vad . try_send ( gate_open) ;
128159 }
129160
130161 // Update EQ visualizer and transcription based on VAD state.
131162 if open_now {
132163 // Compute bars only when VAD is open (reduces CPU when gate is closed; skip FFT).
133164 let out = compute_bars ( frame, & eq_cfg, & mut eq_state) ;
134- let _ = tx_spec. try_send ( out) ;
165+ let _ = tx . tx_spec . try_send ( out) ;
135166 last_spec_was_zero = false ;
136167
137168 // Push audio to transcriber (non-blocking lock; ignore if poisoned).
@@ -140,14 +171,14 @@ pub fn create_audio_callback(
140171 && let Err ( e) = t. push_audio ( frame)
141172 {
142173 error ! ( error = %e, "failed to push audio to transcriber (backpressure)" ) ;
143- let _ = tx_log. try_send ( format ! ( "push_audio error: {}" , e) ) ;
174+ let _ = tx . tx_log . try_send ( format ! ( "push_audio error: {}" , e) ) ;
144175 }
145176 // Drain Partial events (show live preview while speaking).
146- process_transcription_events ( & trans_clone, & sink, & tx_log, & tx_preview, & sfx) ;
177+ process_transcription_events ( & trans_clone, & sink, & tx . tx_log , & tx . tx_preview , & sfx) ;
147178 } else {
148179 // Gate closed: show empty spectrum only if it changed (skip compute to save CPU).
149180 if !last_spec_was_zero {
150- let _ = tx_spec. try_send ( zero_spectrum. clone ( ) ) ;
181+ let _ = tx . tx_spec . try_send ( zero_spectrum. clone ( ) ) ;
151182 last_spec_was_zero = true ;
152183 }
153184 }
0 commit comments