Skip to main content

rustriff_lib/domain/
tone_stack.rs

1use atomic_float::AtomicF32;
2use std::sync::atomic::Ordering;
3use std::sync::Arc;
4use tracing::error;
5
6/// Represents the tone stack with atomic bass, middle, and treble parameters for audio equalization.
7///
8/// The tone stack provides controls for adjusting low (bass), mid-range (middle), and high (treble) frequencies.
9/// It uses [`AtomicF32`] for lock-free updates, enabling low-latency parameter changes from the UI thread
10/// while the audio processing thread reads them without interruption.
11///
12/// All parameters are validated to be between 0.0 and 1.0 (inclusive); attempting to set a value outside this range will panic.
13pub struct ToneStack {
14    bass: Arc<AtomicF32>,
15    middle: Arc<AtomicF32>,
16    treble: Arc<AtomicF32>,
17}
18
19impl ToneStack {
20    /// Creates a new `ToneStack` with default parameter values.
21    ///
22    /// Bass, middle, and treble are initialized to `1.0`.
23    pub fn new() -> Self {
24        Self {
25            bass: Arc::new(AtomicF32::new(1.0)),
26            middle: Arc::new(AtomicF32::new(1.0)),
27            treble: Arc::new(AtomicF32::new(1.0)),
28        }
29    }
30
31    /// Returns a cloned [`Arc`] to the atomic bass value.
32    ///
33    /// Allows independent threads to share and read/write the bass parameter without contention.
34    pub fn bass(&self) -> Arc<AtomicF32> {
35        Arc::clone(&self.bass)
36    }
37
38    /// Returns a cloned [`Arc`] to the atomic middle value.
39    ///
40    /// Allows independent threads to share and read/write the middle parameter without contention.
41    pub fn middle(&self) -> Arc<AtomicF32> {
42        Arc::clone(&self.middle)
43    }
44
45    /// Returns a cloned [`Arc`] to the atomic treble value.
46    ///
47    /// Allows independent threads to share and read/write the treble parameter without contention.
48    pub fn treble(&self) -> Arc<AtomicF32> {
49        Arc::clone(&self.treble)
50    }
51
52    /// Sets the bass level for the tone stack.
53    ///
54    /// The bass value is atomically updated and will be read by the audio processing thread on the next sample cycle.
55    ///
56    /// # Arguments
57    ///
58    /// * `bass` - The new bass value. Must be between 0.0 and 1.0 (inclusive).
59    ///
60    /// # Panics
61    ///
62    /// Panics if `bass` is negative or greater than 1.0.
63    pub fn set_bass(&self, bass: f32) {
64        if bass.is_sign_positive() && bass <= 1.0 {
65            self.bass.store(bass, Ordering::Relaxed);
66        } else {
67            error!("Bass must be a positive number between 0 and 1");
68            panic!("Bass must be positive and between 0 and 1");
69        }
70    }
71
72    /// Sets the middle level for the tone stack.
73    ///
74    /// The middle value is atomically updated and will be read by the audio processing thread on the next sample cycle.
75    ///
76    /// # Arguments
77    ///
78    /// * `middle` - The new middle value. Must be between 0.0 and 1.0 (inclusive).
79    ///
80    /// # Panics
81    ///
82    /// Panics if `middle` is negative or greater than 1.0.
83    pub fn set_middle(&self, middle: f32) {
84        if middle.is_sign_positive() && middle <= 1.0 {
85            self.middle.store(middle, Ordering::Relaxed);
86        } else {
87            error!("Middle must be a positive number between 0 and 1");
88            panic!("Middle must be positive and between 0 and 1");
89        }
90    }
91
92    /// Sets the treble level for the tone stack.
93    ///
94    /// The treble value is atomically updated and will be read by the audio processing thread on the next sample cycle.
95    ///
96    /// # Arguments
97    ///
98    /// * `treble` - The new treble value. Must be between 0.0 and 1.0 (inclusive).
99    ///
100    /// # Panics
101    ///
102    /// Panics if `treble` is negative or greater than 1.0.
103    pub fn set_treble(&self, treble: f32) {
104        if treble.is_sign_positive() && treble <= 1.0 {
105            self.treble.store(treble, Ordering::Relaxed);
106        } else {
107            error!("Treble must be a positive number between 0 and 1");
108            panic!("Treble must be positive and between 0 and 1");
109        }
110    }
111}
112
113impl Default for ToneStack {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[cfg(test)]
124    mod success_path {
125        use super::*;
126
127        #[test]
128        fn bass_set_to_positive_value_within_range_should_succeed() {
129            let tone_stack = ToneStack::new();
130            tone_stack.set_bass(0.5);
131            assert_eq!(tone_stack.bass().load(Ordering::Relaxed), 0.5);
132        }
133        #[test]
134        fn middle_set_to_positive_value_within_range_should_succeed() {
135            let tone_stack = ToneStack::new();
136            tone_stack.set_middle(0.5);
137            assert_eq!(tone_stack.middle().load(Ordering::Relaxed), 0.5);
138        }
139        #[test]
140        fn treble_set_to_positive_value_within_range_should_succeed() {
141            let tone_stack = ToneStack::new();
142            tone_stack.set_treble(0.5);
143            assert_eq!(tone_stack.treble().load(Ordering::Relaxed), 0.5);
144        }
145    }
146
147    #[cfg(test)]
148    mod failure_path {
149        use super::*;
150
151        #[test]
152        #[should_panic(expected = "Bass must be positive and between 0 and 1")]
153        fn bass_set_to_negative_value_should_panic() {
154            let tone_stack = ToneStack::new();
155            tone_stack.set_bass(-0.5);
156        }
157        #[test]
158        #[should_panic(expected = "Bass must be positive and between 0 and 1")]
159        fn bass_set_to_value_greater_than_one_should_panic() {
160            let tone_stack = ToneStack::new();
161            tone_stack.set_bass(1.5);
162        }
163        #[test]
164        #[should_panic(expected = "Middle must be positive and between 0 and 1")]
165        fn middle_set_to_negative_value_should_panic() {
166            let tone_stack = ToneStack::new();
167            tone_stack.set_middle(-0.5);
168        }
169        #[test]
170        #[should_panic(expected = "Middle must be positive and between 0 and 1")]
171        fn middle_set_to_value_greater_than_one_should_panic() {
172            let tone_stack = ToneStack::new();
173            tone_stack.set_middle(1.5);
174        }
175        #[test]
176        #[should_panic(expected = "Treble must be positive and between 0 and 1")]
177        fn treble_set_to_negative_value_should_panic() {
178            let tone_stack = ToneStack::new();
179            tone_stack.set_treble(-0.5);
180        }
181        #[test]
182        #[should_panic(expected = "Treble must be positive and between 0 and 1")]
183        fn treble_set_to_value_greater_than_one_should_panic() {
184            let tone_stack = ToneStack::new();
185            tone_stack.set_treble(1.5);
186        }
187    }
188}