1pub const MAX_SAMPLE_RATE: u32 = 384_000;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum PcmEncoding {
10 Unsigned8,
12 Signed16,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct PcmFormat {
19 sample_rate: u32,
20 encoding: PcmEncoding,
21}
22
23impl PcmFormat {
24 pub const fn new(sample_rate: u32, encoding: PcmEncoding) -> Result<Self, PcmError> {
31 if sample_rate == 0 || sample_rate > MAX_SAMPLE_RATE {
32 return Err(PcmError::UnsupportedSampleRate(sample_rate));
33 }
34 Ok(Self {
35 sample_rate,
36 encoding,
37 })
38 }
39
40 #[must_use]
42 pub const fn sample_rate(self) -> u32 {
43 self.sample_rate
44 }
45
46 #[must_use]
48 pub const fn encoding(self) -> PcmEncoding {
49 self.encoding
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55#[non_exhaustive]
56pub enum PcmSamples {
57 Unsigned8(Vec<u8>),
59 Signed16(Vec<i16>),
61}
62
63impl PcmSamples {
64 #[must_use]
66 pub fn len(&self) -> usize {
67 match self {
68 Self::Unsigned8(samples) => samples.len(),
69 Self::Signed16(samples) => samples.len(),
70 }
71 }
72
73 #[must_use]
75 pub fn is_empty(&self) -> bool {
76 self.len() == 0
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct Pcm {
83 format: PcmFormat,
84 samples: PcmSamples,
85}
86
87impl Pcm {
88 pub fn new(format: PcmFormat, samples: PcmSamples) -> Result<Self, PcmError> {
94 if !matches!(
95 (format.encoding, &samples),
96 (PcmEncoding::Unsigned8, PcmSamples::Unsigned8(_))
97 | (PcmEncoding::Signed16, PcmSamples::Signed16(_))
98 ) {
99 return Err(PcmError::EncodingMismatch);
100 }
101 Ok(Self { format, samples })
102 }
103
104 #[must_use]
106 pub const fn format(&self) -> PcmFormat {
107 self.format
108 }
109
110 #[must_use]
112 pub const fn samples(&self) -> &PcmSamples {
113 &self.samples
114 }
115
116 #[must_use]
118 pub fn into_samples(self) -> PcmSamples {
119 self.samples
120 }
121
122 pub fn to_i16(&self, target_rate: u32) -> Result<Vec<i16>, PcmError> {
128 let input = match &self.samples {
129 PcmSamples::Unsigned8(samples) => samples
130 .iter()
131 .map(|sample| (i16::from(*sample) - 128) << 8)
132 .collect(),
133 PcmSamples::Signed16(samples) => samples.clone(),
134 };
135 let mut resampler = LinearResampler::new(self.format.sample_rate, target_rate)?;
136 Ok(resampler.push_i16(&input))
137 }
138
139 #[must_use]
141 pub fn from_i16(format: PcmFormat, samples: Vec<i16>) -> Self {
142 let samples = match format.encoding {
143 PcmEncoding::Unsigned8 => PcmSamples::Unsigned8(
144 samples
145 .into_iter()
146 .map(|sample| {
147 let shifted = (i32::from(sample) + 32_768) >> 8;
148 u8::try_from(shifted).unwrap_or(if shifted < 0 { 0 } else { u8::MAX })
149 })
150 .collect(),
151 ),
152 PcmEncoding::Signed16 => PcmSamples::Signed16(samples),
153 };
154 Self { format, samples }
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
160#[non_exhaustive]
161pub enum PcmError {
162 #[error("unsupported linear PCM sample rate {0} Hz; expected 1..={MAX_SAMPLE_RATE}")]
164 UnsupportedSampleRate(u32),
165 #[error("linear PCM format and sample representation do not match")]
167 EncodingMismatch,
168}
169
170pub fn resample_i16(
179 samples: &[i16],
180 source_rate: u32,
181 target_rate: u32,
182) -> Result<Vec<i16>, PcmError> {
183 let mut resampler = LinearResampler::new(source_rate, target_rate)?;
184 Ok(resampler.push_i16(samples))
185}
186
187#[derive(Debug, Clone)]
189pub struct LinearResampler {
190 source_rate: u32,
191 target_rate: u32,
192 previous: Option<i16>,
193 source_index: u64,
194 next_numerator: u64,
195}
196
197impl LinearResampler {
198 pub const fn new(source_rate: u32, target_rate: u32) -> Result<Self, PcmError> {
204 if source_rate == 0 || source_rate > MAX_SAMPLE_RATE {
205 return Err(PcmError::UnsupportedSampleRate(source_rate));
206 }
207 if target_rate == 0 || target_rate > MAX_SAMPLE_RATE {
208 return Err(PcmError::UnsupportedSampleRate(target_rate));
209 }
210 Ok(Self {
211 source_rate,
212 target_rate,
213 previous: None,
214 source_index: 0,
215 next_numerator: 0,
216 })
217 }
218
219 #[must_use]
221 pub fn push_i16(&mut self, samples: &[i16]) -> Vec<i16> {
222 let estimate = (samples
223 .len()
224 .saturating_mul(usize::try_from(self.target_rate).unwrap_or(usize::MAX))
225 / usize::try_from(self.source_rate).unwrap_or(1))
226 .saturating_add(1);
227 let mut output = Vec::with_capacity(estimate.min(1_048_576));
228 for ¤t in samples {
229 let Some(previous) = self.previous else {
230 output.push(current);
231 self.previous = Some(current);
232 self.next_numerator = u64::from(self.source_rate);
233 continue;
234 };
235 self.source_index = self.source_index.saturating_add(1);
236 let interval_start = self
237 .source_index
238 .saturating_sub(1)
239 .saturating_mul(u64::from(self.target_rate));
240 let interval_end = self
241 .source_index
242 .saturating_mul(u64::from(self.target_rate));
243 while self.next_numerator <= interval_end {
244 let fraction_numerator =
245 u32::try_from(self.next_numerator.saturating_sub(interval_start))
246 .unwrap_or(self.target_rate);
247 let span = f64::from(self.target_rate);
248 let fraction = f64::from(fraction_numerator) / span;
249 let value =
250 f64::from(previous) + (f64::from(current) - f64::from(previous)) * fraction;
251 output.push(round_i16(value));
252 self.next_numerator = self
253 .next_numerator
254 .saturating_add(u64::from(self.source_rate));
255 }
256 self.previous = Some(current);
257 }
258 output
259 }
260}
261
262#[allow(
263 clippy::cast_possible_truncation,
264 reason = "the rounded value is clamped to the i16 domain before conversion"
265)]
266fn round_i16(value: f64) -> i16 {
267 let rounded = value
268 .round()
269 .clamp(f64::from(i16::MIN), f64::from(i16::MAX));
270 i16::try_from(rounded as i32).unwrap_or(if rounded.is_sign_negative() {
271 i16::MIN
272 } else {
273 i16::MAX
274 })
275}