shady_audio/
lib.rs

1//! # Description
2//! A crate which simplifies the data management of audio sources to be easily able
3//! to retrieve the frequency powers of the source.
4//!
5//! ### [cpal]
6//!
7//! This crate also re-exports [cpal] so there's no need to add [cpal] exclusively
8//! to your dependency list.
9//!
10//! # How to get started
11//! The main usage can be seen in the example below.
12//! Take a look to the available methods of [ShadyAudio] if you would like to change some properties of it (like frequency range or amount of bars).
13//!
14//! # Example
15//! ```rust
16//! use std::num::NonZeroUsize;
17//!
18//! use shady_audio::{ShadyAudio, fetcher::DummyFetcher, config::ShadyAudioConfig};
19//!
20//! let mut audio = {
21//!     // A fetcher feeds new samples to `ShadyAudio` which processes it
22//!     let fetcher = DummyFetcher::new();
23//!
24//!     // configure the behaviour of `ShadyAudio`
25//!     let config = ShadyAudioConfig {
26//!         amount_bars: NonZeroUsize::new(10).unwrap(),
27//!         ..Default::default()
28//!     };
29//!
30//!     ShadyAudio::new(fetcher, config).unwrap()
31//! };
32//!
33//! // just retrieve the bars.
34//! // ShadyAudio takes care of the rest:
35//! //   - fetching new samples from the fetcher
36//! //   - normalize the values within the range [0, 1]
37//! //   - etc.
38//! assert_eq!(audio.get_bars().len(), 10);
39//!
40//! // change the amount of bars you'd like to have
41//! audio.set_bars(NonZeroUsize::new(20).unwrap());
42//! assert_eq!(audio.get_bars().len(), 20);
43//! ```
44pub mod config;
45pub mod fetcher;
46
47mod equalizer;
48mod error;
49mod fft;
50
51type Hz = u32;
52
53/// The minimal frequency which humans can here (roughly)
54/// See: <https://en.wikipedia.org/wiki/Hearing_range>
55pub const MIN_HUMAN_FREQUENCY: Hz = 20;
56
57/// The maximal frequency which humans can here (roughly)
58/// See: <https://en.wikipedia.org/wiki/Hearing_range>
59pub const MAX_HUMAN_FREQUENCY: Hz = 20_000;
60
61/// The default sample rate for a fetcher.
62/// Fetchers are allowed to use this for orientation.
63pub const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100);
64
65pub use cpal;
66
67use config::ShadyAudioConfig;
68use cpal::SampleRate;
69use equalizer::Equalizer;
70use fetcher::Fetcher;
71use fft::FftCalculator;
72use std::{
73    num::{NonZeroU32, NonZeroUsize},
74    ops::Range,
75};
76
77/// Contains all possible errors/issues with [ShadyAudio].
78#[derive(thiserror::Error, Debug, Clone)]
79pub enum Error {
80    /// Occurs, if you've set [`ShadyAudioConfig::freq_range`] to an empty range.
81    ///
82    /// # Example
83    /// ```rust
84    /// use shady_audio::{Error, config::ShadyAudioConfig};
85    /// use std::num::NonZeroU32;
86    ///
87    /// let invalid_range = NonZeroU32::new(10).unwrap()..NonZeroU32::new(10).unwrap();
88    /// assert!(invalid_range.is_empty(), "`start` and `end` are equal");
89    ///
90    /// let config = ShadyAudioConfig {
91    ///     freq_range: invalid_range.clone(),
92    ///     ..Default::default()
93    /// };
94    ///
95    /// // the range isn't allowed to be empty!
96    /// assert!(config.validate().is_err());
97    /// ```
98    #[error("Frequency range can't be empty but you gave: {0:?}")]
99    EmptyFreqRange(Range<NonZeroU32>),
100}
101
102struct State {
103    amount_bars: usize,
104    sample_rate: SampleRate,
105    freq_range: Range<Hz>,
106    sensitivity: f32,
107}
108
109/// The main struct to interact with the crate.
110pub struct ShadyAudio {
111    state: State,
112    sample_buffer: Vec<f32>,
113
114    fetcher: Box<dyn Fetcher>,
115    fft: FftCalculator,
116    equalizer: Equalizer,
117}
118
119impl ShadyAudio {
120    /// Create a new instance of this struct by providing the (audio) fetcher and the config.
121    ///
122    /// # Example
123    /// ```
124    /// use shady_audio::{ShadyAudio, fetcher::DummyFetcher, config::ShadyAudioConfig};
125    ///
126    /// let shady_audio = ShadyAudio::new(DummyFetcher::new(), ShadyAudioConfig::default());
127    /// ```
128    pub fn new(fetcher: Box<dyn Fetcher>, config: ShadyAudioConfig) -> Result<Self, Vec<Error>> {
129        config.validate()?;
130
131        let state = State {
132            amount_bars: usize::from(config.amount_bars),
133            sample_rate: fetcher.sample_rate(),
134            freq_range: Hz::from(config.freq_range.start)..Hz::from(config.freq_range.end),
135            sensitivity: 1.,
136        };
137
138        let sample_buffer = Vec::with_capacity(state.sample_rate.0 as usize);
139        let fft = FftCalculator::new(state.sample_rate);
140        let equalizer = Equalizer::new(
141            state.amount_bars,
142            state.freq_range.clone(),
143            fft.size(),
144            state.sample_rate,
145            Some(state.sensitivity),
146        );
147
148        Ok(Self {
149            state,
150            fetcher,
151            fft,
152            sample_buffer,
153            equalizer,
154        })
155    }
156
157    /// Return the bars with their values.
158    ///
159    /// Each bar value tries to stay within the range `[0, 1]` but it could happen that there are some spikes which go above 1.
160    /// However it will slowly normalize itself back to 1.
161    #[inline]
162    pub fn get_bars(&mut self) -> &[f32] {
163        self.fetcher.fetch_samples(&mut self.sample_buffer);
164        let fft_out = self.fft.process(&self.sample_buffer);
165        let bars = self.equalizer.process(fft_out);
166
167        self.sample_buffer.clear();
168        bars
169    }
170
171    /// Set the length of the returned slice of [`Self::get_bars`].
172    ///
173    /// # Example
174    /// ```
175    /// use shady_audio::{ShadyAudio, fetcher::DummyFetcher, config::ShadyAudioConfig};
176    /// use std::num::NonZeroUsize;
177    ///
178    /// let mut shady_audio = ShadyAudio::new(DummyFetcher::new(), ShadyAudioConfig::default()).unwrap();
179    ///
180    /// // tell `shady-audio` to compute only for four bars
181    /// let amount_bars = 4;
182    /// shady_audio.set_bars(NonZeroUsize::new(amount_bars).unwrap());
183    ///
184    /// assert_eq!(shady_audio.get_bars().len(), amount_bars);
185    /// ```
186    #[inline]
187    pub fn set_bars(&mut self, amount_bars: NonZeroUsize) {
188        self.state.amount_bars = usize::from(amount_bars);
189
190        self.state.sensitivity = self.equalizer.sensitivity();
191
192        self.equalizer = Equalizer::new(
193            self.state.amount_bars,
194            self.state.freq_range.clone(),
195            self.fft.size(),
196            self.state.sample_rate,
197            Some(self.state.sensitivity),
198        );
199    }
200
201    /// Change the fetcher.
202    ///
203    /// # Example
204    /// ```
205    /// use shady_audio::{ShadyAudio, fetcher::DummyFetcher, config::ShadyAudioConfig};
206    ///
207    /// let mut shady_audio = ShadyAudio::new(DummyFetcher::new(), ShadyAudioConfig::default()).unwrap();
208    ///
209    /// let another_fetcher = DummyFetcher::new();
210    /// shady_audio.set_fetcher(another_fetcher);
211    /// ```
212    #[inline]
213    pub fn set_fetcher(&mut self, fetcher: Box<dyn Fetcher>) {
214        self.fetcher = fetcher;
215    }
216
217    /// Update the frequency range where `shady-audio` should process.
218    ///
219    /// Retunrs `Err` if the given range [is empty](https://doc.rust-lang.org/std/ops/struct.Range.html#method.is_empty) otherwise `Ok`.
220    ///
221    /// # Example
222    /// ```
223    /// use shady_audio::{ShadyAudio, fetcher::DummyFetcher, config::ShadyAudioConfig};
224    /// use std::num::NonZeroU32;
225    ///
226    /// let mut shady_audio = ShadyAudio::new(DummyFetcher::new(), ShadyAudioConfig::default()).unwrap();
227    ///
228    /// // tell `shady-audio` to just create the bars for the frequencies from 1kHz to 15kHz.
229    /// shady_audio.set_freq_range(NonZeroU32::new(1_000).unwrap()..NonZeroU32::new(15_000).unwrap()).unwrap();
230    ///
231    /// // empty ranges are not allowed!
232    /// assert!(shady_audio.set_freq_range(NonZeroU32::new(5).unwrap()..NonZeroU32::new(5).unwrap()).is_err());
233    /// ```
234    #[inline]
235    pub fn set_freq_range(&mut self, freq_range: Range<NonZeroU32>) -> Result<(), Error> {
236        if freq_range.is_empty() {
237            return Err(Error::EmptyFreqRange(freq_range));
238        }
239        let freq_range = u32::from(freq_range.start)..u32::from(freq_range.end);
240
241        self.state.freq_range = freq_range;
242        self.equalizer = Equalizer::new(
243            self.state.amount_bars,
244            self.state.freq_range.clone(),
245            self.fft.size(),
246            self.state.sample_rate,
247            Some(self.state.sensitivity),
248        );
249
250        Ok(())
251    }
252}