rustriff_lib/commands/
analyzer.rs1use crate::domain::dto::spectrum_contract_dto::SpectrumContractDto;
2use crate::domain::dto::spectrum_snapshot_dto::SpectrumSnapshotDto;
3use crate::services::analyzers::spectrum_analyzer_service::SpectrumAnalyzerService;
4use crate::services::audio_service::AudioService;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex};
7use tauri::Emitter;
8use tokio::time::{interval, Duration};
9
10const LIVE_SPECTRUM_EVENT: &str = "live-spectrum";
12const STREAM_INTERVAL_MS: u64 = 16;
14
15struct StreamTask {
20 handle: tauri::async_runtime::JoinHandle<()>,
21 shutdown: Arc<AtomicBool>,
22}
23
24#[derive(Default)]
29pub struct SpectrumStreamState {
30 task: Mutex<Option<StreamTask>>,
31}
32
33const MIN_ANALYZER_FREQ_HZ: f32 = 20.0;
35const MAX_ANALYZER_FREQ_HZ: f32 = 20_000.0;
37const MIN_DB: f32 = -90.0;
39const MAX_DB: f32 = 6.0;
41
42#[tauri::command]
50pub async fn get_live_spectrum(
51 audio_service: tauri::State<'_, Mutex<AudioService>>,
52) -> Result<SpectrumSnapshotDto, String> {
53 let tap = {
54 let service = audio_service
55 .lock()
56 .map_err(|_| "Failed to lock audio service".to_string())?;
57 service.spectrum_tap().clone()
58 };
59
60 tauri::async_runtime::spawn_blocking(move || SpectrumAnalyzerService::analyze_tap(tap.as_ref()))
61 .await
62 .map_err(|e| format!("FFT analysis task failed: {e}"))
63}
64
65#[tauri::command]
67pub fn get_spectrum_contract() -> SpectrumContractDto {
68 SpectrumContractDto {
69 live_spectrum_event: LIVE_SPECTRUM_EVENT.to_string(),
70 min_db: MIN_DB,
71 max_db: MAX_DB,
72 min_frequency_hz: MIN_ANALYZER_FREQ_HZ,
73 max_frequency_hz: MAX_ANALYZER_FREQ_HZ,
74 }
75}
76
77#[tauri::command]
87pub fn start_live_spectrum_stream(
88 window: tauri::Window,
89 audio_service: tauri::State<'_, Mutex<AudioService>>,
90 stream_state: tauri::State<'_, SpectrumStreamState>,
91) -> Result<(), String> {
92 let tap: Arc<_> = {
93 let service = audio_service
94 .lock()
95 .map_err(|_| "Failed to lock audio service".to_string())?;
96 service.spectrum_tap().clone()
97 };
98
99 let shutdown = Arc::new(AtomicBool::new(false));
100
101 {
102 let mut guard = stream_state
103 .task
104 .lock()
105 .map_err(|_| "Failed to lock spectrum stream state".to_string())?;
106
107 if let Some(previous) = guard.take() {
108 previous.shutdown.store(true, Ordering::Relaxed);
109 previous.handle.abort();
110 }
111
112 let task_shutdown = Arc::clone(&shutdown);
113 let handle = tauri::async_runtime::spawn(async move {
114 let mut ticker = interval(Duration::from_millis(STREAM_INTERVAL_MS));
115 loop {
116 ticker.tick().await;
117 if task_shutdown.load(Ordering::Relaxed) {
118 break;
119 }
120
121 let tap_ref = Arc::clone(&tap);
122 let snapshot = tauri::async_runtime::spawn_blocking(move || {
123 SpectrumAnalyzerService::analyze_tap(tap_ref.as_ref())
124 })
125 .await;
126
127 match snapshot {
128 Ok(data) => {
129 if window.emit(LIVE_SPECTRUM_EVENT, data).is_err() {
130 break;
131 }
132 }
133 Err(_) => break,
134 }
135 }
136 });
137
138 guard.replace(StreamTask { handle, shutdown });
139 }
140
141 Ok(())
142}
143
144#[tauri::command]
149pub fn stop_live_spectrum_stream(
150 stream_state: tauri::State<'_, SpectrumStreamState>,
151) -> Result<(), String> {
152 if let Some(task) = stream_state
153 .task
154 .lock()
155 .map_err(|_| "Failed to lock spectrum stream state".to_string())?
156 .take()
157 {
158 task.shutdown.store(true, Ordering::Relaxed);
159 task.handle.abort();
160 }
161
162 Ok(())
163}