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}