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}, {});