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}