use std::{fmt, sync::Arc, time::Duration};
use kithara_bufpool::{PcmBuf, PcmPool};
use crate::gapless::GaplessInfo;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct DecoderTrackInfo {
pub gapless: Option<GaplessInfo>,
}
#[derive(Debug, Clone, Default)]
pub struct TrackMetadata {
pub album: Option<String>,
pub artist: Option<String>,
pub artwork: Option<Arc<Vec<u8>>>,
pub title: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PcmSpec {
pub channels: u16,
pub sample_rate: u32,
}
impl fmt::Display for PcmSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} Hz, {} channels", self.sample_rate, self.channels)
}
}
impl From<&PcmMeta> for kithara_stream::ChunkPosition {
fn from(meta: &PcmMeta) -> Self {
Self {
sample_rate: meta.spec.sample_rate,
frame_offset: meta.frame_offset,
frames: u64::from(meta.frames),
source_bytes: meta.source_bytes,
source_byte_offset: meta.source_byte_offset,
end_position_ns: u64::try_from(meta.end_timestamp.as_nanos()).unwrap_or(u64::MAX),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PcmMeta {
pub end_timestamp: Duration,
pub timestamp: Duration,
pub segment_index: Option<u32>,
pub source_byte_offset: Option<u64>,
pub variant_index: Option<usize>,
pub spec: PcmSpec,
pub frames: u32,
pub epoch: u64,
pub frame_offset: u64,
pub source_bytes: u64,
}
#[derive(Debug)]
pub struct PcmChunk {
pub pcm: PcmBuf,
pub meta: PcmMeta,
}
impl Default for PcmChunk {
fn default() -> Self {
Self {
pcm: PcmPool::default().get(),
meta: PcmMeta::default(),
}
}
}
impl Clone for PcmChunk {
fn clone(&self) -> Self {
let mut new_pcm = PcmPool::default().get();
new_pcm.extend_from_slice(&self.pcm);
Self {
pcm: new_pcm,
meta: self.meta,
}
}
}
impl PcmChunk {
#[must_use]
pub fn new(meta: PcmMeta, pcm: PcmBuf) -> Self {
Self { pcm, meta }
}
#[must_use]
pub fn frames(&self) -> usize {
let channels = self.meta.spec.channels as usize;
self.pcm.len().checked_div(channels).unwrap_or(0)
}
#[must_use]
pub fn samples(&self) -> &[f32] {
&self.pcm
}
#[must_use]
pub fn spec(&self) -> PcmSpec {
self.meta.spec
}
}
impl AsRef<[f32]> for PcmChunk {
fn as_ref(&self) -> &[f32] {
&self.pcm
}
}
#[cfg(test)]
mod tests {
use kithara_test_utils::kithara;
use super::*;
fn test_chunk(spec: PcmSpec, pcm: Vec<f32>) -> PcmChunk {
PcmChunk::new(
PcmMeta {
spec,
..Default::default()
},
PcmPool::default().attach(pcm),
)
}
#[kithara::test]
#[case(44100, 2, "44100 Hz, 2 channels")]
#[case(48000, 1, "48000 Hz, 1 channels")]
#[case(96000, 6, "96000 Hz, 6 channels")]
#[case(192000, 8, "192000 Hz, 8 channels")]
#[case(0, 0, "0 Hz, 0 channels")]
fn test_pcm_spec_display(
#[case] sample_rate: u32,
#[case] channels: u16,
#[case] expected: &str,
) {
let spec = PcmSpec {
channels,
sample_rate,
};
assert_eq!(format!("{}", spec), expected);
}
#[kithara::test]
fn test_pcm_spec_clone() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let cloned = spec;
assert_eq!(spec, cloned);
}
#[kithara::test]
#[case(44100, 2, 44100, 2, true)]
#[case(44100, 2, 48000, 2, false)]
#[case(44100, 2, 44100, 1, false)]
#[case(0, 0, 0, 0, true)]
fn test_pcm_spec_partial_eq(
#[case] sr1: u32,
#[case] ch1: u16,
#[case] sr2: u32,
#[case] ch2: u16,
#[case] should_equal: bool,
) {
let spec1 = PcmSpec {
channels: ch1,
sample_rate: sr1,
};
let spec2 = PcmSpec {
channels: ch2,
sample_rate: sr2,
};
assert_eq!(spec1 == spec2, should_equal);
}
#[kithara::test]
fn test_pcm_spec_debug() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let debug_str = format!("{:?}", spec);
assert!(debug_str.contains("PcmSpec"));
assert!(debug_str.contains("44100"));
assert!(debug_str.contains("2"));
}
#[kithara::test]
#[case(44100, 2)]
#[case(48000, 1)]
#[case(96000, 6)]
fn test_pcm_spec_copy_trait(#[case] sample_rate: u32, #[case] channels: u16) {
let spec = PcmSpec {
channels,
sample_rate,
};
let copied = spec;
assert_eq!(spec, copied);
}
#[kithara::test]
fn test_pcm_meta_default() {
let meta = PcmMeta::default();
assert_eq!(meta.spec, PcmSpec::default());
assert_eq!(meta.frame_offset, 0);
assert_eq!(meta.timestamp, Duration::ZERO);
assert_eq!(meta.segment_index, None);
assert_eq!(meta.variant_index, None);
assert_eq!(meta.epoch, 0);
}
#[kithara::test]
fn test_pcm_meta_copy() {
let meta = PcmMeta {
spec: PcmSpec {
channels: 2,
sample_rate: 44100,
},
frame_offset: 1000,
timestamp: Duration::from_millis(22),
end_timestamp: Duration::from_millis(22),
segment_index: Some(5),
variant_index: Some(2),
epoch: 3,
frames: 0,
source_bytes: 0,
source_byte_offset: None,
};
let copied = meta;
assert_eq!(meta, copied);
}
#[kithara::test]
fn test_pcm_meta_with_spec() {
let spec = PcmSpec {
channels: 2,
sample_rate: 48000,
};
let meta = PcmMeta {
spec,
..Default::default()
};
assert_eq!(meta.spec, spec);
assert_eq!(meta.frame_offset, 0);
}
#[kithara::test]
fn test_pcm_meta_partial_eq() {
let a = PcmMeta {
spec: PcmSpec {
channels: 2,
sample_rate: 44100,
},
frame_offset: 100,
timestamp: Duration::from_millis(2),
end_timestamp: Duration::from_millis(2),
segment_index: Some(1),
variant_index: Some(0),
epoch: 1,
frames: 0,
source_bytes: 0,
source_byte_offset: None,
};
let mut b = a;
assert_eq!(a, b);
b.frame_offset = 200;
assert_ne!(a, b);
}
#[kithara::test]
fn test_pcm_chunk_new() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let pcm = vec![0.1f32, 0.2, 0.3, 0.4];
let chunk = test_chunk(spec, pcm.clone());
assert_eq!(chunk.spec(), spec);
assert_eq!(&chunk.pcm[..], &pcm[..]);
}
#[kithara::test]
#[case(vec![0.0, 1.0, 2.0, 3.0], 2, 2)]
#[case(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2, 3)]
#[case(vec![0.0], 1, 1)]
#[case(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 6, 1)]
#[case(vec![], 2, 0)]
fn test_frames_calculation(
#[case] pcm: Vec<f32>,
#[case] channels: u16,
#[case] expected_frames: usize,
) {
let spec = PcmSpec {
channels,
sample_rate: 44100,
};
let chunk = test_chunk(spec, pcm);
assert_eq!(chunk.frames(), expected_frames);
}
#[kithara::test]
fn test_frames_zero_channels() {
let spec = PcmSpec {
channels: 0,
sample_rate: 44100,
};
let chunk = test_chunk(spec, vec![0.0, 1.0, 2.0, 3.0]);
assert_eq!(chunk.frames(), 0);
}
#[kithara::test]
fn test_samples_access() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let pcm = vec![0.1, 0.2, 0.3, 0.4];
let chunk = test_chunk(spec, pcm.clone());
let samples: &[f32] = &chunk.pcm;
assert_eq!(samples.len(), 4);
assert_eq!(samples, &pcm[..]);
}
#[kithara::test]
fn test_pcm_chunk_clone() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let pcm = vec![0.1, 0.2, 0.3, 0.4];
let chunk = test_chunk(spec, pcm);
let cloned = chunk.clone();
assert_eq!(cloned.spec(), chunk.spec());
assert_eq!(cloned.pcm, chunk.pcm);
}
#[kithara::test]
fn test_pcm_chunk_debug() {
let spec = PcmSpec {
channels: 2,
sample_rate: 44100,
};
let pcm = vec![0.1f32, 0.2];
let chunk = test_chunk(spec, pcm);
let debug_str = format!("{:?}", chunk);
assert!(debug_str.contains("PcmChunk"));
}
}