audio_processor_bitcrusher/
lib.rs

1// Augmented Audio: Audio libraries and applications
2// Copyright (c) 2022 Pedro Tacla Yamada
3//
4// The MIT License (MIT)
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23
24//! Implements a simple bitcrusher based on "sample-and-hold".
25//!
26//! [`BitCrusherProcessor`] is the [`audio_processor_traits::AudioProcessor`] implementation.
27//!
28//! [`BitCrusherHandle`] is the handle with which to change parameters from any thread. A generic
29//! handle is implemented to generate generic GUIs.
30
31use audio_garbage_collector::{make_shared, Shared};
32use audio_processor_traits::atomic_float::{AtomicFloatRepresentable, AtomicValue};
33use audio_processor_traits::parameters::{
34    make_handle_ref, AudioProcessorHandleProvider, AudioProcessorHandleRef,
35};
36use audio_processor_traits::{AudioBuffer, AudioContext, AudioProcessor, Float};
37pub use generic_handle::BitCrusherHandleRef;
38
39mod generic_handle;
40
41pub type BitCrusherHandle = BitCrusherHandleImpl<f32>;
42
43pub struct BitCrusherHandleImpl<ST>
44where
45    ST: AtomicFloatRepresentable + Float,
46{
47    sample_rate: ST::AtomicType,
48    bit_rate: ST::AtomicType,
49}
50
51impl<ST> BitCrusherHandleImpl<ST>
52where
53    ST: Float + AtomicFloatRepresentable,
54    ST: From<f32>,
55    f32: From<ST>,
56{
57    pub fn sample_rate(&self) -> f32 {
58        self.sample_rate.get().into()
59    }
60
61    pub fn bit_rate(&self) -> f32 {
62        self.bit_rate.get().into()
63    }
64
65    pub fn set_sample_rate(&self, sample_rate: f32) {
66        self.sample_rate.set(sample_rate.into());
67    }
68
69    pub fn set_bit_rate(&self, bit_rate: f32) {
70        self.bit_rate.set(bit_rate.into());
71    }
72}
73
74impl<ST> Default for BitCrusherHandleImpl<ST>
75where
76    ST: Float + AtomicFloatRepresentable,
77    ST: From<f32>,
78{
79    fn default() -> Self {
80        Self {
81            sample_rate: ST::AtomicType::from(44100.0.into()),
82            bit_rate: ST::AtomicType::from(44100.0.into()),
83        }
84    }
85}
86
87pub type BitCrusherProcessor = BitCrusherProcessorImpl<f32>;
88
89pub struct BitCrusherProcessorImpl<ST = f32>
90where
91    ST: AtomicFloatRepresentable + Float,
92{
93    handle: Shared<BitCrusherHandleImpl<ST>>,
94}
95
96impl<ST> AudioProcessorHandleProvider for BitCrusherProcessorImpl<ST>
97where
98    ST: AtomicFloatRepresentable + Float + 'static,
99    ST::AtomicType: Send + Sync,
100    ST: From<f32>,
101    f32: From<ST>,
102{
103    fn generic_handle(&self) -> AudioProcessorHandleRef {
104        make_handle_ref(BitCrusherHandleRef::<ST>::new(self.handle.clone()))
105    }
106}
107
108impl<ST> BitCrusherProcessorImpl<ST>
109where
110    ST: AtomicFloatRepresentable + Float + 'static,
111    ST::AtomicType: Send + Sync,
112    ST: From<f32>,
113    f32: From<ST>,
114{
115    pub fn new(handle: Shared<BitCrusherHandleImpl<ST>>) -> Self {
116        BitCrusherProcessorImpl { handle }
117    }
118
119    pub fn handle(&self) -> &Shared<BitCrusherHandleImpl<ST>> {
120        &self.handle
121    }
122
123    fn step_size(&self) -> usize {
124        (self.handle.sample_rate() / self.handle.bit_rate()) as usize
125    }
126}
127
128impl<ST> Default for BitCrusherProcessorImpl<ST>
129where
130    ST: AtomicFloatRepresentable + Float + 'static,
131    ST::AtomicType: Send + Sync,
132    ST: From<f32>,
133    f32: From<ST>,
134{
135    fn default() -> Self {
136        Self::new(make_shared(BitCrusherHandleImpl::default()))
137    }
138}
139
140impl<ST> AudioProcessor for BitCrusherProcessorImpl<ST>
141where
142    ST: AtomicFloatRepresentable + Float + 'static,
143    ST::AtomicType: Send + Sync,
144    ST: From<f32>,
145    f32: From<ST>,
146{
147    type SampleType = ST;
148
149    fn prepare(&mut self, context: &mut AudioContext) {
150        let settings = context.settings;
151        self.handle.set_sample_rate(settings.sample_rate());
152        if (self.handle.sample_rate() - self.handle.bit_rate()).abs() < f32::EPSILON {
153            self.handle.set_bit_rate(settings.sample_rate());
154        }
155    }
156
157    fn process(&mut self, _context: &mut AudioContext, data: &mut AudioBuffer<Self::SampleType>) {
158        let step_size = self.step_size();
159
160        let mut sample_index = 0;
161        let buffer_size = data.num_samples();
162
163        while sample_index < buffer_size {
164            let first_index = sample_index;
165            let limit_index = (sample_index + step_size).min(buffer_size);
166
167            while sample_index < limit_index {
168                for channel_index in 0..data.num_channels() {
169                    let value = *data.get(channel_index, first_index);
170                    data.set(channel_index, sample_index, value);
171                }
172                sample_index += 1;
173            }
174        }
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use std::time::Duration;
181
182    use audio_processor_testing_helpers::sine_buffer;
183
184    use audio_processor_traits::AudioProcessorSettings;
185
186    use super::*;
187
188    #[test]
189    fn test_construct_bitcrusher() {
190        let _processor = BitCrusherProcessor::default();
191    }
192
193    #[test]
194    fn test_step_size_is_1_on_passthrough() {
195        let settings = AudioProcessorSettings::default();
196        let mut context = AudioContext::from(settings);
197        let mut processor = BitCrusherProcessor::default();
198        processor.prepare(&mut context);
199        assert_eq!(processor.step_size(), 1);
200    }
201
202    #[test]
203    fn test_step_size_is_2_on_lower_bitrate() {
204        let settings = AudioProcessorSettings::default();
205        let mut context = AudioContext::from(settings);
206        let mut processor = BitCrusherProcessor::default();
207        processor.prepare(&mut context);
208        processor
209            .handle()
210            .set_bit_rate(settings.sample_rate() / 2.0);
211        assert_eq!(processor.step_size(), 2);
212    }
213
214    #[test]
215    fn test_passthrough_bitcrusher() {
216        let settings = AudioProcessorSettings::default();
217        let mut context = AudioContext::from(settings);
218        let mut processor = BitCrusherProcessor::default();
219        processor.prepare(&mut context);
220
221        let input_buffer = AudioBuffer::from_interleaved(
222            1,
223            &sine_buffer(settings.sample_rate(), 440.0, Duration::from_millis(10)),
224        );
225        let mut output_buffer = input_buffer.clone();
226        processor.process(&mut context, &mut output_buffer);
227
228        assert_eq!(input_buffer.channel(0), output_buffer.channel(0));
229    }
230}