Skip to main content

nice_plug_core/
util.rs

1//! General conversion functions and utilities.
2
3mod stft;
4pub mod window;
5
6pub use atomic_float::{AtomicF32, AtomicF64};
7
8pub use stft::StftHelper;
9
10/// A good default gain threshold value in decibels where a signal is considered "silent".
11///
12/// Equal to `-100.0`.
13pub const MINUS_INFINITY_DB: f32 = -100.0;
14/// A good default gain threshold value in raw amplitude where a signal is considered "silent".
15///
16/// Equal to `1e-5` (The equivalant of [`MINUS_INFINITY_DB`] (-100.0 dB))
17pub const MINUS_INFINITY_GAIN: f32 = 1e-5; // 10f32.powf(MINUS_INFINITY_DB / 20)
18
19#[deprecated(since = "0.4.1", note = "Use KEYS instead")]
20/// The list of MIDI note names in an octave.
21pub const NOTES: [&str; 12] = [
22    "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
23];
24
25/// The list of MIDI key names in an octave.
26pub const KEYS: [&str; 12] = [
27    "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
28];
29
30/// Temporarily allow allocations within `func` if nice-plug was configured with the
31/// `assert_process_allocs` feature.
32#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
33pub fn permit_alloc<T, F: FnOnce() -> T>(func: F) -> T {
34    nice_assert_no_alloc::permit_alloc(func)
35}
36
37/// Temporarily allow allocations within `func` if nice-plug was configured with the
38/// `assert_process_allocs` feature.
39#[cfg(not(all(debug_assertions, feature = "assert_process_allocs")))]
40pub fn permit_alloc<T, F: FnOnce() -> T>(func: F) -> T {
41    func()
42}
43
44/// Convert decibels to a voltage gain ratio, treating anything below -100 dB as minus infinity.
45#[inline]
46pub fn db_to_gain(dbs: f32) -> f32 {
47    if dbs > MINUS_INFINITY_DB {
48        10.0f32.powf(dbs * 0.05)
49    } else {
50        0.0
51    }
52}
53
54/// Convert a voltage gain ratio to decibels. Gain ratios that aren't positive will be treated as
55/// [`MINUS_INFINITY_DB`].
56#[inline]
57pub fn gain_to_db(gain: f32) -> f32 {
58    f32::max(gain, MINUS_INFINITY_GAIN).log10() * 20.0
59}
60
61/// An approximation of [`db_to_gain()`] using `exp()`. Does not treat values below
62/// [`MINUS_INFINITY_DB`] as 0.0 gain to avoid branching. As a result this function will thus also
63/// never return 0.0 for normal input values. Will run faster on most architectures, but the result
64/// may be slightly different.
65#[inline]
66pub fn db_to_gain_fast(dbs: f32) -> f32 {
67    const CONVERSION_FACTOR: f32 = std::f32::consts::LN_10 / 20.0;
68    (dbs * CONVERSION_FACTOR).exp()
69}
70
71/// [`db_to_gain_fast()`], but this version does truncate values below [`MINUS_INFINITY_DB`] to 0.0.
72/// Bikeshedding over a better name is welcome.
73#[inline]
74pub fn db_to_gain_fast_branching(dbs: f32) -> f32 {
75    if dbs > MINUS_INFINITY_DB {
76        db_to_gain_fast(dbs)
77    } else {
78        0.0
79    }
80}
81
82/// An approximation of [`gain_to_db()`] using `ln()`. Will run faster on most architectures, but
83/// the result may be slightly different.
84#[inline]
85pub fn gain_to_db_fast(gain: f32) -> f32 {
86    const CONVERSION_FACTOR: f32 = std::f32::consts::LOG10_E * 20.0;
87    f32::max(gain, MINUS_INFINITY_GAIN).ln() * CONVERSION_FACTOR
88}
89
90/// [`db_to_gain_fast()`], but the minimum gain value is set to [`f32::EPSILON`]instead of
91/// [`MINUS_INFINITY_GAIN`]. Useful in conjunction with [`db_to_gain_fast()`].
92#[inline]
93pub fn gain_to_db_fast_epsilon(gain: f32) -> f32 {
94    const CONVERSION_FACTOR: f32 = std::f32::consts::LOG10_E * 20.0;
95    f32::max(gain, MINUS_INFINITY_GAIN).ln() * CONVERSION_FACTOR
96}
97
98/// Convert a MIDI note ID to a frequency at A4 = 440 Hz equal temperament and middle C = note 60 =
99/// C4.
100#[inline]
101#[deprecated(since = "0.4.1", note = "Use midi_key_to_freq instead")]
102pub fn midi_note_to_freq(note: u8) -> f32 {
103    f32_midi_key_to_freq(note as f32)
104}
105
106/// The same as [`midi_note_to_freq()`], but for arbitrary note numbers including those outside of
107/// the MIDI range. This also supports fractional note numbers, which is useful when working with
108/// cents.
109#[inline]
110#[deprecated(since = "0.4.1", note = "Use f32_midi_key_to_freq instead")]
111pub fn f32_midi_note_to_freq(note: f32) -> f32 {
112    f32_midi_key_to_freq(note)
113}
114
115/// The inverse of [`f32_midi_note_to_freq()`]. This returns a fractional note number. Round to a
116/// whole number, subtract that from the result, and multiply the fractional part by 100 to get the
117/// number of cents.
118#[inline]
119#[deprecated(since = "0.4.1", note = "Use freq_to_midi_key instead")]
120pub fn freq_to_midi_note(freq: f32) -> f32 {
121    freq_to_midi_key(freq)
122}
123
124/// Convert a MIDI key number to a frequency at A4 = 440 Hz equal temperament and middle C = note 60 =
125/// C4.
126#[inline]
127pub fn midi_key_to_freq(key: u8) -> f32 {
128    f32_midi_key_to_freq(key as f32)
129}
130
131/// The same as [`midi_key_to_freq()`], but for arbitrary note numbers including those outside of
132/// the MIDI range. This also supports fractional note numbers, which is useful when working with
133/// cents.
134#[inline]
135pub fn f32_midi_key_to_freq(key: f32) -> f32 {
136    2.0f32.powf((key - 69.0) / 12.0) * 440.0
137}
138
139/// The inverse of [`f32_midi_key_to_freq()`]. This returns a fractional key number. Round to a
140/// whole number, subtract that from the result, and multiply the fractional part by 100 to get the
141/// number of cents.
142#[inline]
143pub fn freq_to_midi_key(freq: f32) -> f32 {
144    ((freq / 440.0).log2() * 12.0) + 69.0
145}
146
147#[cfg(test)]
148mod tests {
149    mod db_gain_conversion {
150        use super::super::*;
151
152        #[test]
153        fn test_db_to_gain_positive() {
154            assert_eq!(db_to_gain(3.0), 1.4125376);
155        }
156
157        #[test]
158        fn test_db_to_gain_negative() {
159            assert_eq!(db_to_gain(-3.0), 1.4125376f32.recip());
160        }
161
162        #[test]
163        fn test_db_to_gain_minus_infinity() {
164            assert_eq!(db_to_gain(-100.0), 0.0);
165        }
166
167        #[test]
168        fn test_gain_to_db_positive() {
169            assert_eq!(gain_to_db(4.0), 12.041201);
170        }
171
172        #[test]
173        fn test_gain_to_db_negative() {
174            assert_eq!(gain_to_db(0.25), -12.041201);
175        }
176
177        #[test]
178        fn test_gain_to_db_minus_infinity_zero() {
179            assert_eq!(gain_to_db(0.0), MINUS_INFINITY_DB);
180        }
181
182        #[test]
183        fn test_gain_to_db_minus_infinity_negative() {
184            assert_eq!(gain_to_db(-2.0), MINUS_INFINITY_DB);
185        }
186    }
187
188    mod fast_db_gain_conversion {
189        use super::super::*;
190
191        #[test]
192        fn test_db_to_gain_positive() {
193            approx::assert_relative_eq!(
194                db_to_gain(3.0),
195                db_to_gain_fast_branching(3.0),
196                epsilon = 1e-7
197            );
198        }
199
200        #[test]
201        fn test_db_to_gain_negative() {
202            approx::assert_relative_eq!(
203                db_to_gain(-3.0),
204                db_to_gain_fast_branching(-3.0),
205                epsilon = 1e-7
206            );
207        }
208
209        #[test]
210        fn test_db_to_gain_minus_infinity() {
211            approx::assert_relative_eq!(
212                db_to_gain(-100.0),
213                db_to_gain_fast_branching(-100.0),
214                epsilon = 1e-7
215            );
216        }
217
218        #[test]
219        fn test_gain_to_db_positive() {
220            approx::assert_relative_eq!(gain_to_db(4.0), gain_to_db_fast(4.0), epsilon = 1e-7);
221        }
222
223        #[test]
224        fn test_gain_to_db_negative() {
225            approx::assert_relative_eq!(gain_to_db(0.25), gain_to_db_fast(0.25), epsilon = 1e-7);
226        }
227
228        #[test]
229        fn test_gain_to_db_minus_infinity_zero() {
230            approx::assert_relative_eq!(gain_to_db(0.0), gain_to_db_fast(0.0), epsilon = 1e-7);
231        }
232
233        #[test]
234        fn test_gain_to_db_minus_infinity_negative() {
235            approx::assert_relative_eq!(gain_to_db(-2.0), gain_to_db_fast(-2.0), epsilon = 1e-7);
236        }
237    }
238}