audio_core/
translate.rs

1//! Utility traits for dealing with sample translations.
2//!
3//! Primitive samples are encoded with PCM which have a midpoint of no amplitude
4//! (i.e. silence). The possible primitives are as follows:
5//!
6//! * Unsigned samples have a span from 0 as its *highest negative* amplitude to
7//!   maximum as its *highest positive* amplitude. The midpoint is defined as
8//!   its `(max + 1) / 2` such as `0x8000` for `u16` or `0x80000000` for `u32`.
9//! * Signed samples have a midpoint at 0 and utilises the full range of the
10//!   type where its minimum is the *highest negative* amplitude and its maximum
11//!   is its *highest positive* amplitude.
12//! * Float samples have a midpoint at `0.0` and utilises the range `-1.0` to
13//!   `1.0` (inclusive).
14//!
15//! These rules are applied to the following *native* Rust types:
16//!
17//! * `u8`, `u16`, `u32`, and `u64` for unsigned PCM 8 to 64 bit audio
18//!   modulation.
19//! * `i8`, `i16`, `i32`, and `i64` for signed PCM 8 to 64 bit audio modulation.
20//! * `f32` and `f64` for 32 and 64 bit PCM floating-point audio modulation.
21//!
22//! The primary traits that govern how something is translated are the
23//! [Translate] and [TryTranslate]. The first deals with non-fallible
24//! translations where conversion loss is expected (as with float-integer
25//! translations). [TryTranslate] deals with translations where an unexpected
26//! loss in precision would otherwise occur.
27//!
28//! See the documentation for each trait for more information.
29
30use core::convert::Infallible;
31
32#[cfg(test)]
33mod tests;
34
35/// Trait used for translating one sample type to another.
36///
37/// This performs infallible translations where any loss in precision is
38/// *expected* and is not supported between types which cannot be universally
39/// translated in this manner such as translations from a higher to a lower
40/// precision format.
41///
42/// # Examples
43///
44/// ```
45/// use audio::Translate;
46///
47/// assert_eq!(i16::translate(-1.0f32), i16::MIN);
48/// assert_eq!(i16::translate(0.0f32), 0);
49///
50/// assert_eq!(u16::translate(-1.0f32), u16::MIN);
51/// assert_eq!(u16::translate(0.0f32), 32768);
52/// ```
53pub trait Translate<T>: Sized {
54    /// Translate one kind of buffer to another.
55    fn translate(value: T) -> Self;
56}
57
58/// Trait for performing checked translations, where it's checked if a
59/// translation would result in loss of precision.
60///
61/// This will fail if we try to perform a translation between two integer types
62/// which are not exactly equivalent.
63///
64/// # Examples
65///
66/// ```
67/// use audio::translate::{TryTranslate, IntTranslationError};
68///
69/// assert_eq!(i16::try_translate(-1.0f32), Ok(i16::MIN));
70/// assert_eq!(i16::try_translate(i32::MIN), Ok(i16::MIN));
71/// assert_eq!(i16::try_translate(0x70000000i32), Ok(0x7000i16));
72/// assert!(matches!(i16::try_translate(0x70000001i32), Err(IntTranslationError { .. })));
73/// ```
74pub trait TryTranslate<T>: Sized {
75    /// Error kind raised from the translation.
76    type Err;
77
78    /// Perform a conversion.
79    fn try_translate(value: T) -> Result<Self, Self::Err>;
80}
81
82impl<T, U> TryTranslate<T> for U
83where
84    U: Translate<T>,
85{
86    type Err = Infallible;
87
88    #[inline]
89    fn try_translate(value: T) -> Result<Self, Self::Err> {
90        Ok(U::translate(value))
91    }
92}
93
94/// Unable to translate an integer due to loss of precision.
95#[derive(Debug, PartialEq, Eq)]
96#[non_exhaustive]
97pub struct IntTranslationError;
98
99macro_rules! identity {
100    ($ty:ty) => {
101        impl Translate<$ty> for $ty {
102            #[inline]
103            fn translate(value: $ty) -> Self {
104                value
105            }
106        }
107    };
108}
109
110macro_rules! conversions {
111    (
112        $signed:ty, $unsigned:ty,
113        {$($float:ty),* $(,)?},
114        {$([$lossless_unsigned:ty, $lossless_signed:ty, $lossless_shift:expr]),* $(,)?}
115    ) => {
116        identity!($signed);
117        identity!($unsigned);
118
119        impl Translate<$unsigned> for $signed {
120            #[inline]
121            fn translate(value: $unsigned) -> Self {
122                (value as $signed).wrapping_sub(<$signed>::MIN)
123            }
124        }
125
126        impl Translate<$signed> for $unsigned {
127            #[inline]
128            fn translate(value: $signed) -> Self {
129                value.wrapping_add(<$signed>::MIN) as $unsigned
130            }
131        }
132
133        $(
134        impl Translate<$lossless_unsigned> for $unsigned {
135            #[inline]
136            fn translate(value: $lossless_unsigned) -> Self {
137                (value as $unsigned).wrapping_shl($lossless_shift)
138            }
139        }
140
141        impl Translate<$lossless_signed> for $unsigned {
142            #[inline]
143            fn translate(value: $lossless_signed) -> Self {
144                <$unsigned>::translate(<$lossless_unsigned>::translate(value))
145            }
146        }
147
148        impl Translate<$lossless_signed> for $signed {
149            #[inline]
150            fn translate(value: $lossless_signed) -> Self {
151                (value as $signed).wrapping_shl($lossless_shift)
152            }
153        }
154
155        impl Translate<$lossless_unsigned> for $signed {
156            #[inline]
157            fn translate(value: $lossless_unsigned) -> Self {
158                <$signed>::translate(<$lossless_signed>::translate(value))
159            }
160        }
161
162        impl TryTranslate<$unsigned> for $lossless_unsigned {
163            type Err = IntTranslationError;
164
165            #[inline]
166            fn try_translate(value: $unsigned) -> Result<Self, Self::Err> {
167                if value & ((1 << $lossless_shift) - 1) != 0 {
168                    return Err(IntTranslationError);
169                }
170
171                Ok(value.wrapping_shr($lossless_shift) as $lossless_unsigned)
172            }
173        }
174
175        impl TryTranslate<$signed> for $lossless_unsigned {
176            type Err = IntTranslationError;
177
178            #[inline]
179            fn try_translate(value: $signed) -> Result<Self, Self::Err> {
180                <$lossless_unsigned>::try_translate(<$unsigned>::translate(value))
181            }
182        }
183
184        impl TryTranslate<$signed> for $lossless_signed {
185            type Err = IntTranslationError;
186
187            #[inline]
188            fn try_translate(value: $signed) -> Result<Self, Self::Err> {
189                if value & ((1 << $lossless_shift) - 1) != 0 {
190                    return Err(IntTranslationError);
191                }
192
193                Ok(value.wrapping_shr($lossless_shift) as $lossless_signed)
194            }
195        }
196
197        impl TryTranslate<$unsigned> for $lossless_signed {
198            type Err = IntTranslationError;
199
200            #[inline]
201            fn try_translate(value: $unsigned) -> Result<Self, Self::Err> {
202                <$lossless_signed>::try_translate(<$signed>::translate(value))
203            }
204        }
205        )*
206
207        $(
208        impl Translate<$signed> for $float {
209            #[inline]
210            fn translate(value: $signed) -> Self {
211                // Needs special care to avoid distortion at the cost of not
212                // covering the whole range.
213                // See: https://github.com/udoprog/audio/issues/7
214                -(value as $float) / (<$signed>::MIN as $float)
215            }
216        }
217
218        impl Translate<$float> for $signed {
219            #[inline]
220            fn translate(value: $float) -> Self {
221                // Needs special care to avoid distortion at the cost of not
222                // covering the whole range.
223                // See: https://github.com/udoprog/audio/issues/7
224                -(value * <$signed>::MIN as $float) as $signed
225            }
226        }
227
228        #[cfg(feature = "std")]
229        impl Translate<$float> for $unsigned {
230            #[inline]
231            fn translate(value: $float) -> Self {
232                // Go through signed to get the same float conversion.
233                <$unsigned>::translate(<$signed>::translate(value))
234            }
235        }
236
237        impl Translate<$unsigned> for $float {
238            #[inline]
239            fn translate(value: $unsigned) -> Self {
240                // Go through signed to get the same float conversion.
241                <$float>::translate(<$signed>::translate(value))
242            }
243        }
244        )*
245    };
246}
247
248identity!(f32);
249identity!(f64);
250
251impl Translate<f32> for f64 {
252    #[inline]
253    fn translate(value: f32) -> Self {
254        value as f64
255    }
256}
257
258impl Translate<f64> for f32 {
259    #[inline]
260    fn translate(value: f64) -> Self {
261        value as f32
262    }
263}
264
265conversions!(i64, u64, {f32, f64}, {[u8, i8, 56], [u16, i16, 48], [u32, i32, 32]});
266conversions!(i32, u32, {f32, f64}, {[u8, i8, 24], [u16, i16, 16]});
267conversions!(i16, u16, {f32, f64}, {[u8, i8, 8]});
268conversions!(i8, u8, {f32, f64}, {});