Skip to main content

rustriff_lib/domain/
effect.rs

1use crate::domain::audio_processor::AudioProcessor;
2use crate::domain::dto::effect::effect_dto::EffectDto;
3use atomic_float::AtomicF32;
4use std::collections::HashMap;
5use std::sync::atomic::{AtomicBool, AtomicU32};
6use std::sync::Arc;
7
8/// A trait defining the shared behavior for audio effects within the signal chain.
9///
10/// The `Effect` trait extends [`AudioProcessor`], providing metadata and state management
11/// (such as enabling or bypassing the effect) in addition to the core signal processing
12/// capabilities.
13///
14/// # Requirements
15///
16/// Types implementing `Effect` must also implement `AudioProcessor`, which provides the
17/// primary [`process`](AudioProcessor::process) method used for manipulating audio samples.
18pub trait Effect: AudioProcessor + Send + Sync {
19    /// Returns the unique numeric identifier for this specific effect instance.
20    fn id(&self) -> u32;
21
22    /// Returns the human-readable name of the effect (e.g., "Overdrive", "Delay").
23    fn name(&self) -> &str;
24
25    /// Returns `true` if the effect is currently enabled and processing audio.
26    ///
27    /// If `false`, the effect should ideally be bypassed to save CPU or maintain
28    /// signal transparency.
29    fn is_active(&self) -> bool {
30        self.active_flag()
31            .load(std::sync::atomic::Ordering::Relaxed)
32    }
33
34    /// Sets whether the effect is active or bypassed.
35    ///
36    /// * `active` - `true` to enable the effect, `false` to bypass it.
37    fn set_active(&self, active: bool) {
38        self.active_flag()
39            .store(active, std::sync::atomic::Ordering::Relaxed);
40    }
41
42    /// Returns a color code (hex) associated with this effect for UI representation.
43    fn get_color(&self) -> String;
44
45    /// Converts this effect into its serialisable [`EffectDto`] representation.
46    ///
47    /// Each concrete effect type returns the correct variant of the tagged union,
48    /// carrying its own specific parameters alongside the shared fields.
49    fn to_dto(&self) -> EffectDto;
50
51    /// Returns the shared `Arc<AtomicBool>` that drives `process_if_active`.
52    /// Command handlers write to it; the audio thread reads it lock-free.
53    fn active_flag(&self) -> Arc<AtomicBool>;
54
55    /// Returns named f32 parameter Arcs shared with the audio thread.
56    /// Defaults to an empty map — override for effects with extra parameters.
57    ///
58    /// Implementing this is the **only** change required to make a new effect's
59    /// parameters controllable from commands — no downcasting anywhere.
60    fn f32_params(&self) -> HashMap<&'static str, Arc<AtomicF32>> {
61        HashMap::new()
62    }
63
64    /// Returns named u32 parameter Arcs shared with the audio thread.
65    /// Defaults to an empty map — override for effects with extra parameters.
66    ///
67    /// Implementing this is the **only** change required to make a new effect's
68    /// parameters controllable from commands — no downcasting anywhere.
69    fn u32_params(&self) -> HashMap<&'static str, Arc<AtomicU32>> {
70        HashMap::new()
71    }
72
73    /// Processes a single audio sample only if the effect is currently active.
74    ///
75    /// This is the primary entry point for a signal chain. If [`is_active`](Self::is_active)
76    /// returns `true`, the sample is passed to the underlying `process` method.
77    /// Otherwise, the input sample is returned unchanged (unity gain bypass).
78    ///
79    /// # Parameters
80    /// * `sample`: The input floating-point audio sample
81    /// # Returns
82    ///
83    /// The processed sample if active, or the original sample if bypassed.
84    fn process_if_active(&mut self, sample: f32) -> f32 {
85        if self.is_active() {
86            self.process(sample)
87        } else {
88            sample
89        }
90    }
91}