Skip to main content

rustriff_lib/commands/effect_commands/
sc_distortion.rs

1use crate::commands::helpers::persist_amp_config;
2use crate::services::amp_config_service::AmpConfigPersistenceService;
3use crate::services::audio_service::AudioService;
4use std::sync::Mutex;
5use tracing::info;
6
7/// # Sets the Clipping Threshold on an SC Distortion Effect
8///
9/// Adjusts the Drive parameter: lower thresholds produce heavier distortion.
10///
11/// # Arguments
12/// * `effect_id` — ID of the SCDistortion effect to modify
13/// * `threshold` — Clipping level in range `(0.0, 1.0]`
14///                 * Values < 0.001 are clamped to 0.001
15///                 * Values > 1.0 are clamped to 1.0
16///
17/// # Returns
18/// * `Ok(())` — Threshold updated successfully
19/// * `Err(String)` — Error if:
20///   - Effect not found or parameter update fails
21///   - Threshold is NaN or infinite (audio thread safety)
22///
23/// # Validation
24///
25/// This command validates the threshold before forwarding to the audio thread:
26/// - Rejects NaN and infinite values (would panic in audio processor)
27/// - Clamps finite values to safe range `[0.001, 1.0]`
28/// - Prevents audio thread crashes from invalid clamp operations
29
30#[tauri::command]
31pub fn set_sc_distortion_threshold(
32    audio_service: tauri::State<Mutex<AudioService>>,
33    persistence_service: tauri::State<Mutex<AmpConfigPersistenceService>>,
34    effect_id: u32,
35    threshold: f32,
36) -> Result<(), String> {
37    if !threshold.is_finite() {
38        return Err(format!(
39            "Invalid threshold: {} (must be finite, not NaN or infinite)",
40            threshold
41        ));
42    }
43
44    let safe_threshold = threshold.clamp(0.001, 1.0);
45
46    let service = audio_service
47        .lock()
48        .map_err(|_| "Failed to lock audio service".to_string())?;
49    let channel = service
50        .channels()
51        .iter()
52        .find(|c| c.id() == *service.current_channel_id())
53        .ok_or("No active channel")?;
54    channel.set_effect_param(effect_id, "threshold", safe_threshold)?;
55    info!(
56        channel_id = *service.current_channel_id(),
57        effect_id,
58        threshold = safe_threshold,
59        "SCDistortion threshold updated"
60    );
61    persist_amp_config(&service, &persistence_service);
62    Ok(())
63}
64
65/// # Sets the Output Level (Boost) on an SC Distortion Effect
66///
67/// Adjusts the Level parameter: controls output amplitude after clipping.
68///
69/// # Arguments
70/// * `effect_id` — ID of the SCDistortion effect to modify
71/// * `level` — Normalised level in range `[0.0, 1.0]`
72///            * `0.0` = unity gain (no boost)
73///            * `1.0` = ×2.0 boost
74///            * Values outside range are clamped
75///
76/// # Returns
77/// * `Ok(())` — Level updated successfully
78/// * `Err(String)` — Error if:
79///   - Effect not found or parameter update fails
80///   - Level is NaN or infinite (audio thread safety)
81///
82/// # Validation
83///
84/// This command validates the level before forwarding to the audio thread:
85/// - Rejects NaN and infinite values (would create invalid gain multiplier)
86/// - Clamps finite values to range `[0.0, 1.0]`
87/// - Maps to internal gain `[1.0, 2.0]` after validation
88/// - Prevents audio thread crashes from invalid gain operations
89
90#[tauri::command]
91pub fn set_sc_distortion_level(
92    audio_service: tauri::State<Mutex<AudioService>>,
93    persistence_service: tauri::State<Mutex<AmpConfigPersistenceService>>,
94    effect_id: u32,
95    level: f32,
96) -> Result<(), String> {
97    // Validate level before forwarding to audio thread
98    if !level.is_finite() {
99        return Err(format!(
100            "Invalid level: {} (must be finite, not NaN or infinite)",
101            level
102        ));
103    }
104
105    // Clamp to safe range [0.0, 1.0] then map to internal gain [1.0, 2.0]
106    let safe_level = level.clamp(0.0, 1.0);
107    let gain = 1.0 + safe_level;
108
109    let service = audio_service
110        .lock()
111        .map_err(|_| "Failed to lock audio service".to_string())?;
112    let channel = service
113        .channels()
114        .iter()
115        .find(|c| c.id() == *service.current_channel_id())
116        .ok_or("No active channel")?;
117    channel.set_effect_param(effect_id, "level", gain)?;
118    info!(
119        channel_id = *service.current_channel_id(),
120        effect_id,
121        level = safe_level,
122        gain,
123        "SCDistortion level updated"
124    );
125    persist_amp_config(&service, &persistence_service);
126    Ok(())
127}
128
129/// # Sets the Smoothing Parameter on an SC Distortion Effect
130///
131/// Adjusts the Smoothing parameter: controls curve towards clipping threshold
132///
133/// # Arguments
134/// * `effect_id` — ID of the SCDistortion effect to modify
135/// * `smoothing` — Smoothing in range `[1.0, 10.0]`
136///            * `1.0` = hardest curving smoothing
137///            * `10.0` = least curving smoothing
138///            * Values outside range are clamped
139///
140/// # Returns
141/// * `Ok(())` — Level updated successfully
142/// * `Err(String)` — Error if:
143///   - Effect not found or parameter update fails
144///   - Smoothing is NaN or infinite (audio thread safety)
145///
146/// # Validation
147///
148/// This command validates the smoothing before forwarding to the audio thread:
149/// - Rejects NaN and infinite values (would create invalid gain multiplier)
150/// - Clamps finite values to range `[1.0, 10.0]`
151/// - Prevents audio thread crashes from invalid gain operations
152#[tauri::command]
153pub fn set_sc_distortion_smoothing(
154    audio_service: tauri::State<Mutex<AudioService>>,
155    persistence_service: tauri::State<Mutex<AmpConfigPersistenceService>>,
156    effect_id: u32,
157    smoothing: f32,
158) -> Result<(), String> {
159    if !smoothing.is_finite() {
160        return Err(format!(
161            "Invalid smoothing: {} (must be finite, not NaN or infinite)",
162            smoothing
163        ));
164    }
165
166    let safe_smoothing = smoothing.clamp(1.0, 10.0);
167
168    let service = audio_service
169        .lock()
170        .map_err(|_| "Failed to lock audio service".to_string())?;
171    let channel = service
172        .channels()
173        .iter()
174        .find(|c| c.id() == *service.current_channel_id())
175        .ok_or("No active channel")?;
176    channel.set_effect_param(effect_id, "smoothing", safe_smoothing)?;
177    info!(
178        channel_id = *service.current_channel_id(),
179        effect_id,
180        smoothing = safe_smoothing,
181        "SCDistortion smoothing updated"
182    );
183    persist_amp_config(&service, &persistence_service);
184    Ok(())
185}