1pub mod commands;
2pub mod config;
3pub mod domain;
4pub mod infrastructure;
5pub mod services;
6
7#[cfg(test)]
8pub mod tests;
9
10use crate::commands::analyzer::{
11 get_live_spectrum, get_spectrum_contract, start_live_spectrum_stream,
12 stop_live_spectrum_stream, SpectrumStreamState,
13};
14use crate::commands::channels::{
15 add_channel, get_all_channels, get_channel_id, remove_channel, set_channel_id,
16};
17use crate::commands::default_controls::{
18 get_amp_config, set_bass, set_gain, set_master_volume, set_middle, set_tone_stack, set_treble,
19 set_volume, toggle_on_off,
20};
21use crate::commands::effect_commands::cabinet_ir::{
22 get_all_ir_profiles, remove_ir_profile, upload_ir_profile,
23};
24use crate::commands::effect_commands::delay::{set_delay_delay_time, set_delay_level};
25use crate::commands::effect_commands::hc_distortion::{
26 set_hc_distortion_level, set_hc_distortion_threshold,
27};
28use crate::commands::effect_commands::sc_distortion::{
29 set_sc_distortion_level, set_sc_distortion_smoothing, set_sc_distortion_threshold,
30};
31use crate::commands::latency_testing::{
32 measure_all_dsp_algorithmic_latency, measure_all_dsp_cpu_timings, measure_buffer_latency,
33 measure_round_trip_latency, test_gain_latency,
34};
35use crate::commands::loopback::start_loopback;
36use crate::commands::settings::{
37 get_buffer_size_frames, get_input_device_list, get_output_device_list, set_buffer_size_frames,
38 set_input_device, set_output_device,
39};
40use crate::config::{get_default_ir_file, init_tracing};
41use crate::infrastructure::file_loader::FileLoader;
42use crate::infrastructure::persistence::json_amp_config_repository::JsonFileAmpConfigRepository;
43use crate::services::amp_config_service::AmpConfigPersistenceService;
44use crate::services::audio_service::AudioService;
45use crate::services::device_service::DeviceService;
46use crate::services::file_service::FileService;
47use commands::effect_commands::effects::{
48 add_effect, apply_effect_order_change, remove_effect, toggle_effect,
49};
50use cpal::default_host;
51use cpal::traits::{DeviceTrait, HostTrait};
52use cpal::{BufferSize, StreamConfig};
53use std::sync::Mutex;
54use tauri::Manager;
55use tracing::{error, info};
56
57const AMP_CONFIG_FILE_NAME: &str = "amp-config.json";
58
59#[cfg_attr(mobile, tauri::mobile_entry_point)]
60pub fn run() {
61 init_tracing();
62
63 let host = default_host();
64 let input = host.default_input_device().unwrap();
65 let output = host.default_output_device().unwrap();
66
67 let input_name = input
68 .description()
69 .map(|d| d.name().to_string())
70 .unwrap_or_else(|_| "Unknown".to_string());
71 let output_name = output
72 .description()
73 .map(|d| d.name().to_string())
74 .unwrap_or_else(|_| "Unknown".to_string());
75
76 info!("Input device: {}", input_name);
77 info!("Output device: {}", output_name);
78
79 let input_supported = input
80 .default_input_config()
81 .map_err(|e| format!("Failed to get input device config: {}", e))
82 .unwrap();
83 let output_supported = output
84 .default_output_config()
85 .map_err(|e| format!("Failed to get output device config: {}", e))
86 .unwrap();
87 info!(
88 "Input config - Channels: {}, Sample Rate: {} Hz",
89 input_supported.channels(),
90 input_supported.sample_rate()
91 );
92 info!(
93 "Output config - Channels: {}, Sample Rate: {} Hz",
94 output_supported.channels(),
95 output_supported.sample_rate()
96 );
97
98 let normalize_channels = |channels: u16| -> u16 {
99 match channels {
100 0 => {
101 error!("Device reported 0 channels, defaulting to stereo");
102 2
103 }
104 1 => 1,
105 _ => {
106 if channels > 2 {
107 info!(
108 "Device reported {} channels, normalizing to stereo for stability",
109 channels
110 );
111 2
112 } else {
113 channels
114 }
115 }
116 }
117 };
118
119 let input_channels = normalize_channels(input_supported.channels());
120 let output_channels = normalize_channels(output_supported.channels());
121
122 let input_config = StreamConfig {
123 channels: input_channels,
124 sample_rate: input_supported.sample_rate(),
125 buffer_size: BufferSize::Default,
126 };
127 let output_config = StreamConfig {
128 channels: output_channels,
129 sample_rate: output_supported.sample_rate(),
130 buffer_size: BufferSize::Default,
131 };
132
133 info!(
134 "Configured stream - Input: {} ch @ {} Hz, Output: {} ch @ {} Hz",
135 input_config.channels,
136 input_config.sample_rate,
137 output_config.channels,
138 output_config.sample_rate
139 );
140
141 let audio_service = AudioService::new(input, output, input_config, output_config);
142
143 tauri::Builder::default()
144 .manage(Mutex::new(audio_service))
145 .manage(SpectrumStreamState::default())
146 .manage(DeviceService::new(host))
147 .plugin(tauri_plugin_opener::init())
148 .setup(|app| {
149 let config_dir = app
150 .path()
151 .app_config_dir()
152 .or_else(|_| app.path().app_data_dir())
153 .map_err(|e| format!("Failed to resolve app config/data directory: {e}"))?;
154
155 let config_path = config_dir.join(AMP_CONFIG_FILE_NAME);
156 info!("Using persisted amp config path: {}", config_path.display());
157
158 let amp_config_persistence_service = AmpConfigPersistenceService::new(Box::new(
159 JsonFileAmpConfigRepository::new(config_path),
160 ));
161
162 {
163 let audio_service_state = app.state::<Mutex<AudioService>>();
164 let mut audio_service = audio_service_state
165 .lock()
166 .map_err(|_| "Failed to lock audio service during startup")?;
167
168 match amp_config_persistence_service.load_amp_config() {
169 Ok(Some(config)) => {
170 info!("Loaded persisted amplifier configuration");
171 audio_service.apply_amp_config(config);
172 }
173 Ok(None) => info!("No persisted amplifier configuration found"),
174 Err(err) => error!("Failed to load persisted amplifier configuration: {err}"),
175 }
176 }
177
178 let resource_root = app.path().resource_dir().unwrap_or_else(|_| {
179 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources")
180 });
181 info!("Using resource root: {}", resource_root.display());
182
183 let custom_ir_directory = config_dir.join("default_ir_custom");
184 info!(
185 "Using custom IR directory: {}",
186 custom_ir_directory.display()
187 );
188 std::env::set_var("RUSTRIFF_CUSTOM_IR_DIR", &custom_ir_directory);
189
190 let file_service = FileService::new(
191 Box::new(FileLoader::new()),
192 resource_root,
193 custom_ir_directory,
194 );
195 app.manage(file_service);
196
197 app.manage(Mutex::new(amp_config_persistence_service));
198 Ok(())
199 })
200 .invoke_handler(tauri::generate_handler![
201 get_default_ir_file,
202 start_loopback,
203 set_gain,
204 get_input_device_list,
205 get_output_device_list,
206 set_input_device,
207 set_output_device,
208 set_master_volume,
209 toggle_on_off,
210 get_amp_config,
211 set_bass,
212 set_tone_stack,
213 set_middle,
214 set_treble,
215 set_volume,
216 set_channel_id,
217 get_channel_id,
218 add_channel,
219 get_all_channels,
220 remove_channel,
221 get_buffer_size_frames,
222 set_buffer_size_frames,
223 test_gain_latency,
224 measure_all_dsp_cpu_timings,
225 measure_all_dsp_algorithmic_latency,
226 measure_buffer_latency,
227 measure_round_trip_latency,
228 toggle_effect,
229 set_hc_distortion_threshold,
230 set_hc_distortion_level,
231 add_effect,
232 remove_effect,
233 apply_effect_order_change,
234 get_all_ir_profiles,
235 upload_ir_profile,
236 remove_ir_profile,
237 set_delay_delay_time,
238 set_delay_level,
239 get_live_spectrum,
240 get_spectrum_contract,
241 start_live_spectrum_stream,
242 stop_live_spectrum_stream,
243 set_sc_distortion_threshold,
244 set_sc_distortion_level,
245 set_sc_distortion_smoothing,
246 ])
247 .run(tauri::generate_context!())
248 .expect("error while running tauri application");
249}