1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Decibel -- Quick conversion utilities for decibel values.
// Copyright (c) 2016 Kevin Brothaler and the Decibel project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// A copy of the License has been included in the root of the repository.
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Conversion utilities to convert between amplitudes and decibels.
//!
//! See also: [Decibel][1] and [dBFS][2]
//! [1]: https://en.wikipedia.org/wiki/Decibel
//! [2]: https://en.wikipedia.org/wiki/DBFS

#![warn(missing_docs)]

extern crate num;

use num::traits::Float;

/// An amplitude value.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct AmplitudeRatio<T: Float>(pub T);

/// A decibel value.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DecibelRatio<T: Float>(pub T);

/// We require this to work around an issue with the constants.
pub trait Constants: Float {
    /// Returns the value "10".
    fn get_10() -> Self;
    /// Returns the value "20".
    fn get_20() -> Self;
}

impl Constants for f32 {
    fn get_10() -> f32 { 10f32 }
    fn get_20() -> f32 { 20f32 }
}

impl Constants for f64 {
    fn get_10() -> f64 { 10f64 }
    fn get_20() -> f64 { 20f64 }
}

/// Converts from an amplitude into a decibel value.
///
/// When used for normalized amplitudes in the range of 0 to 1, this will give
/// the value in dBFS (decibels relative to full scale).
///
/// # Example
///
/// ```rust
/// extern crate decibel;
///
/// use decibel::{AmplitudeRatio, DecibelRatio};
///
/// fn main() {
///		// An amplitude halfway between 1 and zero should be close to -6 dBFS.
/// 	let result: DecibelRatio<_> = AmplitudeRatio(0.5).into();
/// 	let expected_decibels = -6.02059991327962;
///		assert!(result.decibel_value() >= expected_decibels - 0.001
/// 		 && result.decibel_value() <= expected_decibels + 0.001);
/// }
impl<T: Float+Constants> From<AmplitudeRatio<T>> for DecibelRatio<T> {
    #[inline]
    fn from(amplitude: AmplitudeRatio<T>) -> DecibelRatio<T> {
        DecibelRatio(Float::log10(amplitude.amplitude_value()) * Constants::get_20())
    }
}

/// Converts from a decibel value into an amplitude.
///
/// # Example
///
/// Let's say we want to scale our audio by 10dB. To figure out how much we
/// need to scale each sample by, let's convert this into an amplitude ratio:
///
/// ```rust
/// extern crate decibel;
///
/// use decibel::{AmplitudeRatio, DecibelRatio};
///
/// fn main() {
///		// A +10dB gain should require us to scale each sample by around
/// 	// 3.1622776601683795.
/// 	let result: AmplitudeRatio<_> = DecibelRatio(10.0).into();
/// 	let expected_amplitude = 3.1622776601683795;
///		assert!(result.amplitude_value() >= expected_amplitude - 0.001
/// 		 && result.amplitude_value() <= expected_amplitude + 0.001);
/// }
/// ```
///
/// To scale our audio by 10dB, we need to scale each sample by approximately
/// 3.162 times.
impl<T: Float+Constants> From<DecibelRatio<T>> for AmplitudeRatio<T> {
    #[inline]
    fn from(decibels: DecibelRatio<T>) -> AmplitudeRatio<T> {
        AmplitudeRatio(T::powf(Constants::get_10(), decibels.decibel_value() / Constants::get_20()))
    }
}

impl<T: Float> AmplitudeRatio<T> {
    /// Returns the wrapped amplitude value.
    #[inline]
    pub fn amplitude_value(&self) -> T {
        self.0
    }
}

impl<T: Float> DecibelRatio<T> {
    /// Returns the wrapped decibel value.
    #[inline]
    pub fn decibel_value(&self) -> T {
        self.0
    }
}

#[cfg(test)]
mod test {    
    use std::{f32, f64};
    use AmplitudeRatio;
    use DecibelRatio;

    #[test]
    fn test_decibels_to_amplitude_with_different_values_f32() {
        // A dB of 0 should map to an amplitude of 1.0.
        test_decibels_to_amplitude_f32(0.0, 1.0);
        test_decibels_to_amplitude_f64(0.0, 1.0);

        // A dB of around -6dB should be around an amplitude of 0.5.
        test_decibels_to_amplitude_f32(-6.02059991327962, 0.5);
        test_decibels_to_amplitude_f64(-6.02059991327962, 0.5);

        // A dB of around +6dB should be an amplitude of around 2.0.
        test_decibels_to_amplitude_f32(6.02059991327962, 2.0);
        test_decibels_to_amplitude_f64(6.02059991327962, 2.0);

        // +1 or -1 in a 16-bit signed sample should be approximately -90.3dB.
        test_decibels_to_amplitude_f32(-90.30873362169473, 1.0 / 32767.0);
        test_decibels_to_amplitude_f64(-90.30873362169473, 1.0 / 32767.0);

        // A dB of negative infinity should map to an amplitude of zero.
        test_decibels_to_amplitude_f32(f32::NEG_INFINITY, 0.0);
        test_decibels_to_amplitude_f64(f64::NEG_INFINITY, 0.0);
    }

    fn test_decibels_to_amplitude_f32(decibels: f32, expected_amplitude: f32) {
        let result: AmplitudeRatio<_> = DecibelRatio(decibels).into();
        assert!(result.amplitude_value() >= expected_amplitude - 0.001 &&
                result.amplitude_value() <= expected_amplitude + 0.001);
    }

    fn test_decibels_to_amplitude_f64(decibels: f64, expected_amplitude: f64) {
        let result: AmplitudeRatio<_> = DecibelRatio(decibels).into();
        assert!(result.amplitude_value() >= expected_amplitude - 0.001 &&
                result.amplitude_value() <= expected_amplitude + 0.001);
    }

    #[test]
    fn test_amplitude_to_decibels_with_different_values() {
        // An amplitude at the peak should be 0 dBFS.
        test_amplitude_to_decibels_f32(1.0, 0.0);        
        test_amplitude_to_decibels_f64(1.0, 0.0);        

        // An amplitude halfway between 1 and zero should be close to -6 dBFS.
        test_amplitude_to_decibels_f32(0.5, -6.02059991327962);
        test_amplitude_to_decibels_f64(0.5, -6.02059991327962);

        // A double amplitude should be close to +6 dBFS.
        // Note that we can't actually get an amplitude higher than 1 from the
        // hardware if 1 represents the max amplitude possible.
        test_amplitude_to_decibels_f32(2.0, 6.02059991327962);
        test_amplitude_to_decibels_f64(2.0, 6.02059991327962);

        // +1 or -1 in a 16-bit signed sample should be approximately -90.3 dBFS.
        test_amplitude_to_decibels_f32(1.0 / 32767.0, -90.30873362169473);
        test_amplitude_to_decibels_f64(1.0 / 32767.0, -90.30873362169473);

        // 0 is a special case. We should get an infinity.
        test_amplitude_to_decibels_f32(0.0, f32::NEG_INFINITY);
        test_amplitude_to_decibels_f64(0.0, f64::NEG_INFINITY);
    }   

    fn test_amplitude_to_decibels_f32(amplitude: f32, expected_decibels: f32) {
        let result: DecibelRatio<_> = AmplitudeRatio(amplitude).into();
                assert!(result.decibel_value() >= expected_decibels - 0.001 &&
                        result.decibel_value() <= expected_decibels + 0.001);
    }

    fn test_amplitude_to_decibels_f64(amplitude: f64, expected_decibels: f64) {
        let result: DecibelRatio<_> = AmplitudeRatio(amplitude).into();
                assert!(result.decibel_value() >= expected_decibels - 0.001 &&
                        result.decibel_value() <= expected_decibels + 0.001);
    } 
}