1use std::{fmt, sync::Arc, time::Duration};
2
3use kithara_bufpool::{PcmBuf, PcmPool};
4
5use crate::gapless::GaplessInfo;
6
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
14#[non_exhaustive]
15pub struct DecoderTrackInfo {
16 pub gapless: Option<GaplessInfo>,
18}
19
20#[derive(Debug, Clone, Default)]
27pub struct TrackMetadata {
28 pub album: Option<String>,
30 pub artist: Option<String>,
32 pub artwork: Option<Arc<Vec<u8>>>,
34 pub title: Option<String>,
36}
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
46pub struct PcmSpec {
47 pub channels: u16,
48 pub sample_rate: u32,
49}
50
51impl fmt::Display for PcmSpec {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "{} Hz, {} channels", self.sample_rate, self.channels)
54 }
55}
56
57impl From<&PcmMeta> for kithara_stream::ChunkPosition {
58 fn from(meta: &PcmMeta) -> Self {
59 Self {
60 sample_rate: meta.spec.sample_rate,
61 frame_offset: meta.frame_offset,
62 frames: u64::from(meta.frames),
63 source_bytes: meta.source_bytes,
64 source_byte_offset: meta.source_byte_offset,
65 end_position_ns: u64::try_from(meta.end_timestamp.as_nanos()).unwrap_or(u64::MAX),
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
80pub struct PcmMeta {
81 pub end_timestamp: Duration,
89 pub timestamp: Duration,
91 pub segment_index: Option<u32>,
93 pub source_byte_offset: Option<u64>,
101 pub variant_index: Option<usize>,
103 pub spec: PcmSpec,
105 pub frames: u32,
110 pub epoch: u64,
112 pub frame_offset: u64,
114 pub source_bytes: u64,
125}
126
127#[derive(Debug)]
137pub struct PcmChunk {
138 pub pcm: PcmBuf,
139 pub meta: PcmMeta,
140}
141
142impl Default for PcmChunk {
143 fn default() -> Self {
144 Self {
145 pcm: PcmPool::default().get(),
146 meta: PcmMeta::default(),
147 }
148 }
149}
150
151impl Clone for PcmChunk {
152 fn clone(&self) -> Self {
157 let mut new_pcm = PcmPool::default().get();
158 new_pcm.extend_from_slice(&self.pcm);
159 Self {
160 pcm: new_pcm,
161 meta: self.meta,
162 }
163 }
164}
165
166impl PcmChunk {
167 #[must_use]
169 pub fn new(meta: PcmMeta, pcm: PcmBuf) -> Self {
170 Self { pcm, meta }
171 }
172
173 #[must_use]
177 pub fn frames(&self) -> usize {
178 let channels = self.meta.spec.channels as usize;
179 self.pcm.len().checked_div(channels).unwrap_or(0)
180 }
181
182 #[must_use]
188 pub fn samples(&self) -> &[f32] {
189 &self.pcm
190 }
191
192 #[must_use]
194 pub fn spec(&self) -> PcmSpec {
195 self.meta.spec
196 }
197}
198
199impl AsRef<[f32]> for PcmChunk {
200 fn as_ref(&self) -> &[f32] {
201 &self.pcm
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use kithara_test_utils::kithara;
208
209 use super::*;
210
211 fn test_chunk(spec: PcmSpec, pcm: Vec<f32>) -> PcmChunk {
212 PcmChunk::new(
213 PcmMeta {
214 spec,
215 ..Default::default()
216 },
217 PcmPool::default().attach(pcm),
218 )
219 }
220
221 #[kithara::test]
222 #[case(44100, 2, "44100 Hz, 2 channels")]
223 #[case(48000, 1, "48000 Hz, 1 channels")]
224 #[case(96000, 6, "96000 Hz, 6 channels")]
225 #[case(192000, 8, "192000 Hz, 8 channels")]
226 #[case(0, 0, "0 Hz, 0 channels")]
227 fn test_pcm_spec_display(
228 #[case] sample_rate: u32,
229 #[case] channels: u16,
230 #[case] expected: &str,
231 ) {
232 let spec = PcmSpec {
233 channels,
234 sample_rate,
235 };
236 assert_eq!(format!("{}", spec), expected);
237 }
238
239 #[kithara::test]
240 fn test_pcm_spec_clone() {
241 let spec = PcmSpec {
242 channels: 2,
243 sample_rate: 44100,
244 };
245 let cloned = spec;
246 assert_eq!(spec, cloned);
247 }
248
249 #[kithara::test]
250 #[case(44100, 2, 44100, 2, true)]
251 #[case(44100, 2, 48000, 2, false)]
252 #[case(44100, 2, 44100, 1, false)]
253 #[case(0, 0, 0, 0, true)]
254 fn test_pcm_spec_partial_eq(
255 #[case] sr1: u32,
256 #[case] ch1: u16,
257 #[case] sr2: u32,
258 #[case] ch2: u16,
259 #[case] should_equal: bool,
260 ) {
261 let spec1 = PcmSpec {
262 channels: ch1,
263 sample_rate: sr1,
264 };
265 let spec2 = PcmSpec {
266 channels: ch2,
267 sample_rate: sr2,
268 };
269 assert_eq!(spec1 == spec2, should_equal);
270 }
271
272 #[kithara::test]
273 fn test_pcm_spec_debug() {
274 let spec = PcmSpec {
275 channels: 2,
276 sample_rate: 44100,
277 };
278 let debug_str = format!("{:?}", spec);
279 assert!(debug_str.contains("PcmSpec"));
280 assert!(debug_str.contains("44100"));
281 assert!(debug_str.contains("2"));
282 }
283
284 #[kithara::test]
285 #[case(44100, 2)]
286 #[case(48000, 1)]
287 #[case(96000, 6)]
288 fn test_pcm_spec_copy_trait(#[case] sample_rate: u32, #[case] channels: u16) {
289 let spec = PcmSpec {
290 channels,
291 sample_rate,
292 };
293 let copied = spec;
294 assert_eq!(spec, copied);
295 }
296
297 #[kithara::test]
298 fn test_pcm_meta_default() {
299 let meta = PcmMeta::default();
300 assert_eq!(meta.spec, PcmSpec::default());
301 assert_eq!(meta.frame_offset, 0);
302 assert_eq!(meta.timestamp, Duration::ZERO);
303 assert_eq!(meta.segment_index, None);
304 assert_eq!(meta.variant_index, None);
305 assert_eq!(meta.epoch, 0);
306 }
307
308 #[kithara::test]
309 fn test_pcm_meta_copy() {
310 let meta = PcmMeta {
311 spec: PcmSpec {
312 channels: 2,
313 sample_rate: 44100,
314 },
315 frame_offset: 1000,
316 timestamp: Duration::from_millis(22),
317 end_timestamp: Duration::from_millis(22),
318 segment_index: Some(5),
319 variant_index: Some(2),
320 epoch: 3,
321 frames: 0,
322 source_bytes: 0,
323 source_byte_offset: None,
324 };
325 let copied = meta;
326 assert_eq!(meta, copied);
327 }
328
329 #[kithara::test]
330 fn test_pcm_meta_with_spec() {
331 let spec = PcmSpec {
332 channels: 2,
333 sample_rate: 48000,
334 };
335 let meta = PcmMeta {
336 spec,
337 ..Default::default()
338 };
339 assert_eq!(meta.spec, spec);
340 assert_eq!(meta.frame_offset, 0);
341 }
342
343 #[kithara::test]
344 fn test_pcm_meta_partial_eq() {
345 let a = PcmMeta {
346 spec: PcmSpec {
347 channels: 2,
348 sample_rate: 44100,
349 },
350 frame_offset: 100,
351 timestamp: Duration::from_millis(2),
352 end_timestamp: Duration::from_millis(2),
353 segment_index: Some(1),
354 variant_index: Some(0),
355 epoch: 1,
356 frames: 0,
357 source_bytes: 0,
358 source_byte_offset: None,
359 };
360 let mut b = a;
361 assert_eq!(a, b);
362 b.frame_offset = 200;
363 assert_ne!(a, b);
364 }
365
366 #[kithara::test]
367 fn test_pcm_chunk_new() {
368 let spec = PcmSpec {
369 channels: 2,
370 sample_rate: 44100,
371 };
372 let pcm = vec![0.1f32, 0.2, 0.3, 0.4];
373 let chunk = test_chunk(spec, pcm.clone());
374
375 assert_eq!(chunk.spec(), spec);
376 assert_eq!(&chunk.pcm[..], &pcm[..]);
377 }
378
379 #[kithara::test]
380 #[case(vec![0.0, 1.0, 2.0, 3.0], 2, 2)]
381 #[case(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2, 3)]
382 #[case(vec![0.0], 1, 1)]
383 #[case(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 6, 1)]
384 #[case(vec![], 2, 0)]
385 fn test_frames_calculation(
386 #[case] pcm: Vec<f32>,
387 #[case] channels: u16,
388 #[case] expected_frames: usize,
389 ) {
390 let spec = PcmSpec {
391 channels,
392 sample_rate: 44100,
393 };
394 let chunk = test_chunk(spec, pcm);
395 assert_eq!(chunk.frames(), expected_frames);
396 }
397
398 #[kithara::test]
399 fn test_frames_zero_channels() {
400 let spec = PcmSpec {
401 channels: 0,
402 sample_rate: 44100,
403 };
404 let chunk = test_chunk(spec, vec![0.0, 1.0, 2.0, 3.0]);
405 assert_eq!(chunk.frames(), 0);
406 }
407
408 #[kithara::test]
409 fn test_samples_access() {
410 let spec = PcmSpec {
411 channels: 2,
412 sample_rate: 44100,
413 };
414 let pcm = vec![0.1, 0.2, 0.3, 0.4];
415 let chunk = test_chunk(spec, pcm.clone());
416
417 let samples: &[f32] = &chunk.pcm;
418 assert_eq!(samples.len(), 4);
419 assert_eq!(samples, &pcm[..]);
420 }
421
422 #[kithara::test]
423 fn test_pcm_chunk_clone() {
424 let spec = PcmSpec {
425 channels: 2,
426 sample_rate: 44100,
427 };
428 let pcm = vec![0.1, 0.2, 0.3, 0.4];
429 let chunk = test_chunk(spec, pcm);
430 let cloned = chunk.clone();
431
432 assert_eq!(cloned.spec(), chunk.spec());
433 assert_eq!(cloned.pcm, chunk.pcm);
434 }
435
436 #[kithara::test]
437 fn test_pcm_chunk_debug() {
438 let spec = PcmSpec {
439 channels: 2,
440 sample_rate: 44100,
441 };
442 let pcm = vec![0.1f32, 0.2];
443 let chunk = test_chunk(spec, pcm);
444 let debug_str = format!("{:?}", chunk);
445
446 assert!(debug_str.contains("PcmChunk"));
447 }
448}