Skip to main content

ff_format/frame/
audio.rs

1//! Audio frame type.
2//!
3//! This module provides [`AudioFrame`] for working with decoded audio frames.
4//!
5//! # Examples
6//!
7//! ```
8//! use ff_format::{AudioFrame, SampleFormat, Rational, Timestamp};
9//!
10//! // Create a stereo F32 audio frame with 1024 samples
11//! let channels = 2u32;
12//! let samples = 1024usize;
13//! let sample_rate = 48000u32;
14//!
15//! let frame = AudioFrame::empty(
16//!     samples,
17//!     channels,
18//!     sample_rate,
19//!     SampleFormat::F32,
20//! ).unwrap();
21//!
22//! assert_eq!(frame.samples(), 1024);
23//! assert_eq!(frame.channels(), 2);
24//! assert_eq!(frame.sample_rate(), 48000);
25//! assert!(!frame.format().is_planar());
26//! ```
27
28use std::fmt;
29use std::time::Duration;
30
31use crate::error::FrameError;
32use crate::{SampleFormat, Timestamp};
33
34/// A decoded audio frame.
35///
36/// This structure holds audio sample data and metadata for a segment of audio.
37/// It supports both packed (interleaved) formats where all channels are
38/// interleaved in a single buffer, and planar formats where each channel
39/// is stored in a separate buffer.
40///
41/// # Memory Layout
42///
43/// For packed (interleaved) formats (I16, F32, etc.):
44/// - Single plane containing interleaved samples: L R L R L R ...
45/// - Total size: `samples * channels * bytes_per_sample`
46///
47/// For planar formats (I16p, F32p, etc.):
48/// - One plane per channel
49/// - Each plane size: `samples * bytes_per_sample`
50///
51/// # Examples
52///
53/// ```
54/// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
55///
56/// // Create a stereo F32 frame with 1024 samples at 48kHz
57/// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
58///
59/// assert_eq!(frame.samples(), 1024);
60/// assert_eq!(frame.channels(), 2);
61/// assert_eq!(frame.sample_rate(), 48000);
62/// assert_eq!(frame.format(), SampleFormat::F32);
63///
64/// // Duration of this frame: 1024 / 48000 ≈ 21.33ms
65/// let duration = frame.duration();
66/// assert!((duration.as_secs_f64() - 0.02133).abs() < 0.001);
67/// ```
68#[derive(Clone)]
69pub struct AudioFrame {
70    /// Sample data for each plane (1 for packed, channels for planar)
71    planes: Vec<Vec<u8>>,
72    /// Number of samples per channel
73    samples: usize,
74    /// Number of audio channels
75    channels: u32,
76    /// Sample rate in Hz
77    sample_rate: u32,
78    /// Sample format
79    format: SampleFormat,
80    /// Presentation timestamp
81    timestamp: Timestamp,
82}
83
84impl AudioFrame {
85    /// Creates a new audio frame with the specified parameters.
86    ///
87    /// # Arguments
88    ///
89    /// * `planes` - Audio sample data (1 plane for packed, channels for planar)
90    /// * `samples` - Number of samples per channel
91    /// * `channels` - Number of audio channels
92    /// * `sample_rate` - Sample rate in Hz
93    /// * `format` - Sample format
94    /// * `timestamp` - Presentation timestamp
95    ///
96    /// # Errors
97    ///
98    /// Returns [`FrameError::InvalidPlaneCount`] if the number of planes doesn't
99    /// match the format (1 for packed, channels for planar).
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use ff_format::{AudioFrame, SampleFormat, Timestamp};
105    ///
106    /// // Create a mono F32 frame with 1024 samples
107    /// let samples = 1024;
108    /// let bytes_per_sample = 4; // F32
109    /// let data = vec![0u8; samples * bytes_per_sample];
110    ///
111    /// let frame = AudioFrame::new(
112    ///     vec![data],
113    ///     samples,
114    ///     1,
115    ///     48000,
116    ///     SampleFormat::F32,
117    ///     Timestamp::default(),
118    /// ).unwrap();
119    ///
120    /// assert_eq!(frame.samples(), 1024);
121    /// assert_eq!(frame.channels(), 1);
122    /// ```
123    pub fn new(
124        planes: Vec<Vec<u8>>,
125        samples: usize,
126        channels: u32,
127        sample_rate: u32,
128        format: SampleFormat,
129        timestamp: Timestamp,
130    ) -> Result<Self, FrameError> {
131        let expected_planes = if format.is_planar() {
132            channels as usize
133        } else {
134            1
135        };
136
137        if planes.len() != expected_planes {
138            return Err(FrameError::InvalidPlaneCount {
139                expected: expected_planes,
140                actual: planes.len(),
141            });
142        }
143
144        Ok(Self {
145            planes,
146            samples,
147            channels,
148            sample_rate,
149            format,
150            timestamp,
151        })
152    }
153
154    /// Creates an empty audio frame with the specified parameters.
155    ///
156    /// The frame will have properly sized planes filled with zeros.
157    ///
158    /// # Arguments
159    ///
160    /// * `samples` - Number of samples per channel
161    /// * `channels` - Number of audio channels
162    /// * `sample_rate` - Sample rate in Hz
163    /// * `format` - Sample format
164    ///
165    /// # Errors
166    ///
167    /// Returns [`FrameError::UnsupportedSampleFormat`] if the format is
168    /// [`SampleFormat::Other`], as the memory layout cannot be determined.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use ff_format::{AudioFrame, SampleFormat};
174    ///
175    /// // Create a stereo I16 frame
176    /// let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
177    /// assert_eq!(frame.samples(), 1024);
178    /// assert_eq!(frame.channels(), 2);
179    /// assert_eq!(frame.num_planes(), 1); // Packed format
180    /// ```
181    pub fn empty(
182        samples: usize,
183        channels: u32,
184        sample_rate: u32,
185        format: SampleFormat,
186    ) -> Result<Self, FrameError> {
187        if matches!(format, SampleFormat::Other(_)) {
188            return Err(FrameError::UnsupportedSampleFormat(format));
189        }
190
191        let planes = Self::allocate_planes(samples, channels, format);
192
193        Ok(Self {
194            planes,
195            samples,
196            channels,
197            sample_rate,
198            format,
199            timestamp: Timestamp::default(),
200        })
201    }
202
203    /// Creates a silent audio frame with 1024 zero-filled samples.
204    ///
205    /// `pts_ms` is the presentation timestamp in milliseconds.
206    /// Both planar formats (one plane per channel) and packed formats (single
207    /// interleaved plane) are supported.
208    #[doc(hidden)]
209    #[must_use]
210    pub fn new_silent(sample_rate: u32, channels: u32, format: SampleFormat, pts_ms: i64) -> Self {
211        let samples = 1024usize;
212        let bps = format.bytes_per_sample();
213        let planes = if format.is_planar() {
214            (0..channels as usize)
215                .map(|_| vec![0u8; samples * bps])
216                .collect()
217        } else {
218            vec![vec![0u8; samples * channels as usize * bps]]
219        };
220        let timestamp = Timestamp::from_millis(pts_ms, crate::Rational::new(1, 1000));
221        Self {
222            planes,
223            samples,
224            channels,
225            sample_rate,
226            format,
227            timestamp,
228        }
229    }
230
231    /// Allocates planes for the given parameters.
232    fn allocate_planes(samples: usize, channels: u32, format: SampleFormat) -> Vec<Vec<u8>> {
233        let bytes_per_sample = format.bytes_per_sample();
234
235        if format.is_planar() {
236            // Planar: one plane per channel
237            let plane_size = samples * bytes_per_sample;
238            (0..channels).map(|_| vec![0u8; plane_size]).collect()
239        } else {
240            // Packed: single plane with interleaved samples
241            let total_size = samples * channels as usize * bytes_per_sample;
242            vec![vec![0u8; total_size]]
243        }
244    }
245
246    // ==========================================================================
247    // Metadata Accessors
248    // ==========================================================================
249
250    /// Returns the number of samples per channel.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use ff_format::{AudioFrame, SampleFormat};
256    ///
257    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
258    /// assert_eq!(frame.samples(), 1024);
259    /// ```
260    #[must_use]
261    #[inline]
262    pub const fn samples(&self) -> usize {
263        self.samples
264    }
265
266    /// Returns the number of audio channels.
267    ///
268    /// The return type is `u32` to match `FFmpeg`'s `AVFrame::ch_layout.nb_channels`
269    /// and professional audio APIs (Core Audio, WASAPI, JACK, Dolby Atmos).
270    ///
271    /// # Integration
272    ///
273    /// Playback libraries such as `rodio` and `cpal` accept channel counts as
274    /// `u16`. Cast with `frame.channels() as u16`; the truncation is always safe
275    /// because no real-world format exceeds `u16::MAX` (65 535) channels.
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use ff_format::{AudioFrame, SampleFormat};
281    ///
282    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
283    /// assert_eq!(frame.channels(), 2);
284    /// ```
285    #[must_use]
286    #[inline]
287    pub const fn channels(&self) -> u32 {
288        self.channels
289    }
290
291    /// Returns the sample rate in Hz.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// use ff_format::{AudioFrame, SampleFormat};
297    ///
298    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
299    /// assert_eq!(frame.sample_rate(), 48000);
300    /// ```
301    #[must_use]
302    #[inline]
303    pub const fn sample_rate(&self) -> u32 {
304        self.sample_rate
305    }
306
307    /// Returns the sample format.
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use ff_format::{AudioFrame, SampleFormat};
313    ///
314    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
315    /// assert_eq!(frame.format(), SampleFormat::F32);
316    /// assert!(frame.format().is_float());
317    /// ```
318    #[must_use]
319    #[inline]
320    pub const fn format(&self) -> SampleFormat {
321        self.format
322    }
323
324    /// Returns the presentation timestamp.
325    ///
326    /// # Examples
327    ///
328    /// ```
329    /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
330    ///
331    /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
332    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
333    /// frame.set_timestamp(ts);
334    /// assert_eq!(frame.timestamp(), ts);
335    /// ```
336    #[must_use]
337    #[inline]
338    pub const fn timestamp(&self) -> Timestamp {
339        self.timestamp
340    }
341
342    /// Sets the presentation timestamp.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use ff_format::{AudioFrame, SampleFormat, Timestamp, Rational};
348    ///
349    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
350    /// let ts = Timestamp::new(48000, Rational::new(1, 48000));
351    /// frame.set_timestamp(ts);
352    /// assert_eq!(frame.timestamp(), ts);
353    /// ```
354    #[inline]
355    pub fn set_timestamp(&mut self, timestamp: Timestamp) {
356        self.timestamp = timestamp;
357    }
358
359    /// Returns the duration of this audio frame.
360    ///
361    /// The duration is calculated as `samples / sample_rate`.
362    ///
363    /// # Examples
364    ///
365    /// ```
366    /// use ff_format::{AudioFrame, SampleFormat};
367    ///
368    /// // 1024 samples at 48kHz = ~21.33ms
369    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
370    /// let duration = frame.duration();
371    /// assert!((duration.as_secs_f64() - 0.02133).abs() < 0.001);
372    ///
373    /// // 48000 samples at 48kHz = 1 second
374    /// let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
375    /// assert_eq!(frame.duration().as_secs(), 1);
376    /// ```
377    #[must_use]
378    #[allow(clippy::cast_precision_loss)] // Audio frame sample counts are well within f64's precision
379    pub fn duration(&self) -> Duration {
380        if self.sample_rate == 0 {
381            log::warn!(
382                "duration unavailable, sample_rate is 0, returning zero \
383                 samples={} fallback=Duration::ZERO",
384                self.samples
385            );
386            return Duration::ZERO;
387        }
388        let secs = self.samples as f64 / f64::from(self.sample_rate);
389        Duration::from_secs_f64(secs)
390    }
391
392    // ==========================================================================
393    // Plane Data Access
394    // ==========================================================================
395
396    /// Returns the number of planes in this frame.
397    ///
398    /// - Packed formats: 1 plane (interleaved channels)
399    /// - Planar formats: 1 plane per channel
400    ///
401    /// # Examples
402    ///
403    /// ```
404    /// use ff_format::{AudioFrame, SampleFormat};
405    ///
406    /// // Packed format - 1 plane
407    /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
408    /// assert_eq!(packed.num_planes(), 1);
409    ///
410    /// // Planar format - 1 plane per channel
411    /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
412    /// assert_eq!(planar.num_planes(), 2);
413    /// ```
414    #[must_use]
415    #[inline]
416    pub fn num_planes(&self) -> usize {
417        self.planes.len()
418    }
419
420    /// Returns a slice of all plane data.
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// use ff_format::{AudioFrame, SampleFormat};
426    ///
427    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
428    /// let planes = frame.planes();
429    /// assert_eq!(planes.len(), 2);
430    /// ```
431    #[must_use]
432    #[inline]
433    pub fn planes(&self) -> &[Vec<u8>] {
434        &self.planes
435    }
436
437    /// Returns the data for a specific plane, or `None` if the index is out of bounds.
438    ///
439    /// For packed formats, use `plane(0)`. For planar formats, use `plane(channel_index)`.
440    ///
441    /// # Arguments
442    ///
443    /// * `index` - The plane index (0 for packed, channel index for planar)
444    ///
445    /// # Examples
446    ///
447    /// ```
448    /// use ff_format::{AudioFrame, SampleFormat};
449    ///
450    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
451    ///
452    /// // Access left channel (plane 0)
453    /// assert!(frame.plane(0).is_some());
454    ///
455    /// // Access right channel (plane 1)
456    /// assert!(frame.plane(1).is_some());
457    ///
458    /// // No third channel
459    /// assert!(frame.plane(2).is_none());
460    /// ```
461    #[must_use]
462    #[inline]
463    pub fn plane(&self, index: usize) -> Option<&[u8]> {
464        self.planes.get(index).map(Vec::as_slice)
465    }
466
467    /// Returns mutable access to a specific plane's data.
468    ///
469    /// # Arguments
470    ///
471    /// * `index` - The plane index
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// use ff_format::{AudioFrame, SampleFormat};
477    ///
478    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
479    /// if let Some(data) = frame.plane_mut(0) {
480    ///     // Modify left channel
481    ///     data[0] = 128;
482    /// }
483    /// ```
484    #[must_use]
485    #[inline]
486    pub fn plane_mut(&mut self, index: usize) -> Option<&mut [u8]> {
487        self.planes.get_mut(index).map(Vec::as_mut_slice)
488    }
489
490    /// Returns the channel data for planar formats.
491    ///
492    /// This is an alias for [`plane()`](Self::plane) that's more semantically
493    /// meaningful for audio data.
494    ///
495    /// # Arguments
496    ///
497    /// * `channel` - The channel index (0 = left, 1 = right, etc.)
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// use ff_format::{AudioFrame, SampleFormat};
503    ///
504    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
505    ///
506    /// // Get left channel data
507    /// let left = frame.channel(0).unwrap();
508    /// assert_eq!(left.len(), 1024 * 4); // 1024 samples * 4 bytes
509    /// ```
510    #[must_use]
511    #[inline]
512    pub fn channel(&self, channel: usize) -> Option<&[u8]> {
513        self.plane(channel)
514    }
515
516    /// Returns mutable access to channel data for planar formats.
517    ///
518    /// # Arguments
519    ///
520    /// * `channel` - The channel index
521    #[must_use]
522    #[inline]
523    pub fn channel_mut(&mut self, channel: usize) -> Option<&mut [u8]> {
524        self.plane_mut(channel)
525    }
526
527    // ==========================================================================
528    // Contiguous Data Access
529    // ==========================================================================
530
531    /// Returns the raw sample data as a contiguous byte slice.
532    ///
533    /// For packed formats (e.g. [`SampleFormat::F32`], [`SampleFormat::I16`]), this returns
534    /// the interleaved sample bytes. For planar formats (e.g. [`SampleFormat::F32p`],
535    /// [`SampleFormat::I16p`]), this returns an empty slice — use [`channel()`](Self::channel)
536    /// or [`channel_as_f32()`](Self::channel_as_f32) to access individual channel planes instead.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use ff_format::{AudioFrame, SampleFormat};
542    ///
543    /// // Packed format - returns interleaved sample bytes
544    /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
545    /// assert_eq!(packed.data().len(), 1024 * 2 * 4);
546    ///
547    /// // Planar format - returns empty slice; use channel() instead
548    /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
549    /// assert!(planar.data().is_empty());
550    /// let left = planar.channel(0).unwrap();
551    /// assert_eq!(left.len(), 1024 * 4);
552    /// ```
553    #[must_use]
554    #[inline]
555    pub fn data(&self) -> &[u8] {
556        if self.format.is_packed() && self.planes.len() == 1 {
557            &self.planes[0]
558        } else {
559            &[]
560        }
561    }
562
563    /// Returns mutable access to the raw sample data.
564    ///
565    /// For packed formats, returns the interleaved sample bytes as a mutable slice.
566    /// For planar formats, returns an empty mutable slice — use
567    /// [`channel_mut()`](Self::channel_mut) to modify individual channel planes instead.
568    ///
569    /// # Examples
570    ///
571    /// ```
572    /// use ff_format::{AudioFrame, SampleFormat};
573    ///
574    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
575    /// let data = frame.data_mut();
576    /// data[0] = 128;
577    /// ```
578    #[must_use]
579    #[inline]
580    pub fn data_mut(&mut self) -> &mut [u8] {
581        if self.format.is_packed() && self.planes.len() == 1 {
582            &mut self.planes[0]
583        } else {
584            &mut []
585        }
586    }
587
588    // ==========================================================================
589    // Typed Sample Access
590    // ==========================================================================
591    //
592    // These methods provide zero-copy typed access to audio sample data.
593    // They use unsafe code to reinterpret byte buffers as typed slices.
594    //
595    // SAFETY: The data buffers are allocated with proper size and the
596    // underlying Vec<u8> is guaranteed to be properly aligned for the
597    // platform's requirements. We verify format matches before casting.
598
599    /// Returns the sample data as an f32 slice.
600    ///
601    /// This only works if the format is [`SampleFormat::F32`] (packed).
602    /// For planar F32p format, use [`channel_as_f32()`](Self::channel_as_f32).
603    ///
604    /// # Safety Note
605    ///
606    /// This method reinterprets the raw bytes as f32 values. It requires
607    /// proper alignment and format matching.
608    ///
609    /// # Examples
610    ///
611    /// ```
612    /// use ff_format::{AudioFrame, SampleFormat};
613    ///
614    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
615    /// if let Some(samples) = frame.as_f32() {
616    ///     assert_eq!(samples.len(), 1024 * 2); // samples * channels
617    /// }
618    /// ```
619    #[must_use]
620    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
621    pub fn as_f32(&self) -> Option<&[f32]> {
622        if self.format != SampleFormat::F32 {
623            return None;
624        }
625
626        let bytes = self.data();
627        if bytes.is_empty() {
628            return None;
629        }
630        // SAFETY: We verified the format is F32, and the data was allocated
631        // for F32 samples. Vec<u8> is aligned to at least 1 byte, but in practice
632        // most allocators align to at least 8/16 bytes which is sufficient for f32.
633        let ptr = bytes.as_ptr().cast::<f32>();
634        let len = bytes.len() / std::mem::size_of::<f32>();
635        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
636    }
637
638    /// Returns mutable access to sample data as an f32 slice.
639    ///
640    /// Only works for [`SampleFormat::F32`] (packed).
641    #[must_use]
642    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
643    pub fn as_f32_mut(&mut self) -> Option<&mut [f32]> {
644        if self.format != SampleFormat::F32 {
645            return None;
646        }
647
648        let bytes = self.data_mut();
649        if bytes.is_empty() {
650            return None;
651        }
652        let ptr = bytes.as_mut_ptr().cast::<f32>();
653        let len = bytes.len() / std::mem::size_of::<f32>();
654        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
655    }
656
657    /// Returns the sample data as an i16 slice.
658    ///
659    /// This only works if the format is [`SampleFormat::I16`] (packed).
660    /// For planar I16p format, use [`channel_as_i16()`](Self::channel_as_i16).
661    ///
662    /// # Examples
663    ///
664    /// ```
665    /// use ff_format::{AudioFrame, SampleFormat};
666    ///
667    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
668    /// if let Some(samples) = frame.as_i16() {
669    ///     assert_eq!(samples.len(), 1024 * 2);
670    /// }
671    /// ```
672    #[must_use]
673    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
674    pub fn as_i16(&self) -> Option<&[i16]> {
675        if self.format != SampleFormat::I16 {
676            return None;
677        }
678
679        let bytes = self.data();
680        if bytes.is_empty() {
681            return None;
682        }
683        let ptr = bytes.as_ptr().cast::<i16>();
684        let len = bytes.len() / std::mem::size_of::<i16>();
685        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
686    }
687
688    /// Returns mutable access to sample data as an i16 slice.
689    ///
690    /// Only works for [`SampleFormat::I16`] (packed).
691    #[must_use]
692    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
693    pub fn as_i16_mut(&mut self) -> Option<&mut [i16]> {
694        if self.format != SampleFormat::I16 {
695            return None;
696        }
697
698        let bytes = self.data_mut();
699        if bytes.is_empty() {
700            return None;
701        }
702        let ptr = bytes.as_mut_ptr().cast::<i16>();
703        let len = bytes.len() / std::mem::size_of::<i16>();
704        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
705    }
706
707    /// Returns a specific channel's data as an f32 slice.
708    ///
709    /// Works for planar F32p format.
710    ///
711    /// # Arguments
712    ///
713    /// * `channel` - The channel index
714    ///
715    /// # Examples
716    ///
717    /// ```
718    /// use ff_format::{AudioFrame, SampleFormat};
719    ///
720    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
721    /// if let Some(left) = frame.channel_as_f32(0) {
722    ///     assert_eq!(left.len(), 1024);
723    /// }
724    /// ```
725    #[must_use]
726    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
727    pub fn channel_as_f32(&self, channel: usize) -> Option<&[f32]> {
728        if self.format != SampleFormat::F32p {
729            return None;
730        }
731
732        self.channel(channel).map(|bytes| {
733            let ptr = bytes.as_ptr().cast::<f32>();
734            let len = bytes.len() / std::mem::size_of::<f32>();
735            unsafe { std::slice::from_raw_parts(ptr, len) }
736        })
737    }
738
739    /// Returns mutable access to a channel's data as an f32 slice.
740    ///
741    /// Works for planar F32p format.
742    #[must_use]
743    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
744    pub fn channel_as_f32_mut(&mut self, channel: usize) -> Option<&mut [f32]> {
745        if self.format != SampleFormat::F32p {
746            return None;
747        }
748
749        self.channel_mut(channel).map(|bytes| {
750            let ptr = bytes.as_mut_ptr().cast::<f32>();
751            let len = bytes.len() / std::mem::size_of::<f32>();
752            unsafe { std::slice::from_raw_parts_mut(ptr, len) }
753        })
754    }
755
756    /// Returns a specific channel's data as an i16 slice.
757    ///
758    /// Works for planar I16p format.
759    ///
760    /// # Arguments
761    ///
762    /// * `channel` - The channel index
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use ff_format::{AudioFrame, SampleFormat};
768    ///
769    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
770    /// if let Some(left) = frame.channel_as_i16(0) {
771    ///     assert_eq!(left.len(), 1024);
772    /// }
773    /// ```
774    #[must_use]
775    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
776    pub fn channel_as_i16(&self, channel: usize) -> Option<&[i16]> {
777        if self.format != SampleFormat::I16p {
778            return None;
779        }
780
781        self.channel(channel).map(|bytes| {
782            let ptr = bytes.as_ptr().cast::<i16>();
783            let len = bytes.len() / std::mem::size_of::<i16>();
784            unsafe { std::slice::from_raw_parts(ptr, len) }
785        })
786    }
787
788    /// Returns mutable access to a channel's data as an i16 slice.
789    ///
790    /// Works for planar I16p format.
791    #[must_use]
792    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
793    pub fn channel_as_i16_mut(&mut self, channel: usize) -> Option<&mut [i16]> {
794        if self.format != SampleFormat::I16p {
795            return None;
796        }
797
798        self.channel_mut(channel).map(|bytes| {
799            let ptr = bytes.as_mut_ptr().cast::<i16>();
800            let len = bytes.len() / std::mem::size_of::<i16>();
801            unsafe { std::slice::from_raw_parts_mut(ptr, len) }
802        })
803    }
804
805    // ==========================================================================
806    // Utility Methods
807    // ==========================================================================
808
809    /// Returns the total size in bytes of all sample data.
810    ///
811    /// # Examples
812    ///
813    /// ```
814    /// use ff_format::{AudioFrame, SampleFormat};
815    ///
816    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
817    /// assert_eq!(frame.total_size(), 1024 * 2 * 4);
818    /// ```
819    #[must_use]
820    pub fn total_size(&self) -> usize {
821        self.planes.iter().map(Vec::len).sum()
822    }
823
824    /// Returns the size in bytes of a single sample (one channel).
825    ///
826    /// # Examples
827    ///
828    /// ```
829    /// use ff_format::{AudioFrame, SampleFormat};
830    ///
831    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
832    /// assert_eq!(frame.bytes_per_sample(), 4);
833    ///
834    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
835    /// assert_eq!(frame.bytes_per_sample(), 2);
836    /// ```
837    #[must_use]
838    #[inline]
839    pub fn bytes_per_sample(&self) -> usize {
840        self.format.bytes_per_sample()
841    }
842
843    /// Returns the total number of samples across all channels (`samples * channels`).
844    ///
845    /// # Examples
846    ///
847    /// ```
848    /// use ff_format::{AudioFrame, SampleFormat};
849    ///
850    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
851    /// assert_eq!(frame.sample_count(), 2048);
852    /// ```
853    #[must_use]
854    #[inline]
855    pub fn sample_count(&self) -> usize {
856        self.samples * self.channels as usize
857    }
858
859    // ==========================================================================
860    // PCM Conversion
861    // ==========================================================================
862
863    /// Converts the audio frame to interleaved 32-bit float PCM.
864    ///
865    /// All [`SampleFormat`] variants are supported. Planar formats are transposed
866    /// to interleaved layout (L0 R0 L1 R1 ...). Returns an empty `Vec` for
867    /// [`SampleFormat::Other`].
868    ///
869    /// # Scaling
870    ///
871    /// | Source format | Normalization |
872    /// |---|---|
873    /// | U8  | `(sample − 128) / 128.0` → `[−1.0, 1.0]` |
874    /// | I16 | `sample / 32767.0` → `[−1.0, 1.0]` |
875    /// | I32 | `sample / 2147483647.0` → `[−1.0, 1.0]` |
876    /// | F32 | identity |
877    /// | F64 | narrowed to f32 (`as f32`) |
878    ///
879    /// # Examples
880    ///
881    /// ```
882    /// use ff_format::{AudioFrame, SampleFormat};
883    ///
884    /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
885    /// let pcm = frame.to_f32_interleaved();
886    /// assert_eq!(pcm.len(), 8); // 4 samples × 2 channels
887    /// ```
888    #[must_use]
889    #[allow(
890        clippy::cast_possible_truncation,
891        clippy::cast_precision_loss,
892        clippy::too_many_lines
893    )]
894    pub fn to_f32_interleaved(&self) -> Vec<f32> {
895        let total = self.sample_count();
896        if total == 0 {
897            return Vec::new();
898        }
899
900        match self.format {
901            SampleFormat::F32 => self.as_f32().map(<[f32]>::to_vec).unwrap_or_default(),
902            SampleFormat::F32p => {
903                let mut out = vec![0f32; total];
904                let ch_count = self.channels as usize;
905                for ch in 0..ch_count {
906                    if let Some(plane) = self.channel_as_f32(ch) {
907                        for (i, &s) in plane.iter().enumerate() {
908                            out[i * ch_count + ch] = s;
909                        }
910                    }
911                }
912                out
913            }
914            SampleFormat::F64 => {
915                let bytes = self.data();
916                bytes
917                    .chunks_exact(8)
918                    .map(|b| {
919                        f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]) as f32
920                    })
921                    .collect()
922            }
923            SampleFormat::F64p => {
924                let mut out = vec![0f32; total];
925                let ch_count = self.channels as usize;
926                for ch in 0..ch_count {
927                    if let Some(bytes) = self.channel(ch) {
928                        for (i, b) in bytes.chunks_exact(8).enumerate() {
929                            out[i * ch_count + ch] = f64::from_le_bytes([
930                                b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
931                            ]) as f32;
932                        }
933                    }
934                }
935                out
936            }
937            SampleFormat::I16 => {
938                let bytes = self.data();
939                bytes
940                    .chunks_exact(2)
941                    .map(|b| f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX))
942                    .collect()
943            }
944            SampleFormat::I16p => {
945                let mut out = vec![0f32; total];
946                let ch_count = self.channels as usize;
947                for ch in 0..ch_count {
948                    if let Some(bytes) = self.channel(ch) {
949                        for (i, b) in bytes.chunks_exact(2).enumerate() {
950                            out[i * ch_count + ch] =
951                                f32::from(i16::from_le_bytes([b[0], b[1]])) / f32::from(i16::MAX);
952                        }
953                    }
954                }
955                out
956            }
957            SampleFormat::I32 => {
958                let bytes = self.data();
959                bytes
960                    .chunks_exact(4)
961                    .map(|b| i32::from_le_bytes([b[0], b[1], b[2], b[3]]) as f32 / i32::MAX as f32)
962                    .collect()
963            }
964            SampleFormat::I32p => {
965                let mut out = vec![0f32; total];
966                let ch_count = self.channels as usize;
967                for ch in 0..ch_count {
968                    if let Some(bytes) = self.channel(ch) {
969                        for (i, b) in bytes.chunks_exact(4).enumerate() {
970                            out[i * ch_count + ch] = i32::from_le_bytes([b[0], b[1], b[2], b[3]])
971                                as f32
972                                / i32::MAX as f32;
973                        }
974                    }
975                }
976                out
977            }
978            SampleFormat::U8 => {
979                let bytes = self.data();
980                bytes
981                    .iter()
982                    .map(|&b| (f32::from(b) - 128.0) / 128.0)
983                    .collect()
984            }
985            SampleFormat::U8p => {
986                let mut out = vec![0f32; total];
987                let ch_count = self.channels as usize;
988                for ch in 0..ch_count {
989                    if let Some(bytes) = self.channel(ch) {
990                        for (i, &b) in bytes.iter().enumerate() {
991                            out[i * ch_count + ch] = (f32::from(b) - 128.0) / 128.0;
992                        }
993                    }
994                }
995                out
996            }
997            SampleFormat::Other(_) => Vec::new(),
998        }
999    }
1000
1001    /// Converts the audio frame to interleaved 16-bit signed integer PCM.
1002    ///
1003    /// Suitable for use with `rodio::buffer::SamplesBuffer<i16>`. All
1004    /// [`SampleFormat`] variants are supported. Returns an empty `Vec` for
1005    /// [`SampleFormat::Other`].
1006    ///
1007    /// # Scaling
1008    ///
1009    /// | Source format | Conversion |
1010    /// |---|---|
1011    /// | I16 | identity |
1012    /// | I32 | `sample >> 16` (high 16 bits) |
1013    /// | U8  | `(sample − 128) << 8` |
1014    /// | F32/F64 | `clamp(−1, 1) × 32767`, truncated |
1015    ///
1016    /// # Examples
1017    ///
1018    /// ```
1019    /// use ff_format::{AudioFrame, SampleFormat};
1020    ///
1021    /// let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
1022    /// let pcm = frame.to_i16_interleaved();
1023    /// assert_eq!(pcm.len(), 8);
1024    /// ```
1025    #[must_use]
1026    #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] // float→i16 and i32→i16 are intentional truncations
1027    pub fn to_i16_interleaved(&self) -> Vec<i16> {
1028        let total = self.sample_count();
1029        if total == 0 {
1030            return Vec::new();
1031        }
1032
1033        match self.format {
1034            SampleFormat::I16 => self.as_i16().map(<[i16]>::to_vec).unwrap_or_default(),
1035            SampleFormat::I16p => {
1036                let mut out = vec![0i16; total];
1037                let ch_count = self.channels as usize;
1038                for ch in 0..ch_count {
1039                    if let Some(plane) = self.channel_as_i16(ch) {
1040                        for (i, &s) in plane.iter().enumerate() {
1041                            out[i * ch_count + ch] = s;
1042                        }
1043                    }
1044                }
1045                out
1046            }
1047            SampleFormat::F32 => {
1048                let bytes = self.data();
1049                bytes
1050                    .chunks_exact(4)
1051                    .map(|b| {
1052                        let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
1053                        (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16
1054                    })
1055                    .collect()
1056            }
1057            SampleFormat::F32p => {
1058                let mut out = vec![0i16; total];
1059                let ch_count = self.channels as usize;
1060                for ch in 0..ch_count {
1061                    if let Some(bytes) = self.channel(ch) {
1062                        for (i, b) in bytes.chunks_exact(4).enumerate() {
1063                            let s = f32::from_le_bytes([b[0], b[1], b[2], b[3]]);
1064                            out[i * ch_count + ch] =
1065                                (s.clamp(-1.0, 1.0) * f32::from(i16::MAX)) as i16;
1066                        }
1067                    }
1068                }
1069                out
1070            }
1071            SampleFormat::F64 => {
1072                let bytes = self.data();
1073                bytes
1074                    .chunks_exact(8)
1075                    .map(|b| {
1076                        let s =
1077                            f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
1078                        (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16
1079                    })
1080                    .collect()
1081            }
1082            SampleFormat::F64p => {
1083                let mut out = vec![0i16; total];
1084                let ch_count = self.channels as usize;
1085                for ch in 0..ch_count {
1086                    if let Some(bytes) = self.channel(ch) {
1087                        for (i, b) in bytes.chunks_exact(8).enumerate() {
1088                            let s = f64::from_le_bytes([
1089                                b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
1090                            ]);
1091                            out[i * ch_count + ch] =
1092                                (s.clamp(-1.0, 1.0) * f64::from(i16::MAX)) as i16;
1093                        }
1094                    }
1095                }
1096                out
1097            }
1098            SampleFormat::I32 => {
1099                let bytes = self.data();
1100                bytes
1101                    .chunks_exact(4)
1102                    .map(|b| (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16)
1103                    .collect()
1104            }
1105            SampleFormat::I32p => {
1106                let mut out = vec![0i16; total];
1107                let ch_count = self.channels as usize;
1108                for ch in 0..ch_count {
1109                    if let Some(bytes) = self.channel(ch) {
1110                        for (i, b) in bytes.chunks_exact(4).enumerate() {
1111                            out[i * ch_count + ch] =
1112                                (i32::from_le_bytes([b[0], b[1], b[2], b[3]]) >> 16) as i16;
1113                        }
1114                    }
1115                }
1116                out
1117            }
1118            SampleFormat::U8 => {
1119                let bytes = self.data();
1120                bytes.iter().map(|&b| (i16::from(b) - 128) << 8).collect()
1121            }
1122            SampleFormat::U8p => {
1123                let mut out = vec![0i16; total];
1124                let ch_count = self.channels as usize;
1125                for ch in 0..ch_count {
1126                    if let Some(bytes) = self.channel(ch) {
1127                        for (i, &b) in bytes.iter().enumerate() {
1128                            out[i * ch_count + ch] = (i16::from(b) - 128) << 8;
1129                        }
1130                    }
1131                }
1132                out
1133            }
1134            SampleFormat::Other(_) => Vec::new(),
1135        }
1136    }
1137}
1138
1139impl fmt::Debug for AudioFrame {
1140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1141        f.debug_struct("AudioFrame")
1142            .field("samples", &self.samples)
1143            .field("channels", &self.channels)
1144            .field("sample_rate", &self.sample_rate)
1145            .field("format", &self.format)
1146            .field("timestamp", &self.timestamp)
1147            .field("num_planes", &self.planes.len())
1148            .field(
1149                "plane_sizes",
1150                &self.planes.iter().map(Vec::len).collect::<Vec<_>>(),
1151            )
1152            .finish()
1153    }
1154}
1155
1156impl fmt::Display for AudioFrame {
1157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158        let duration_ms = self.duration().as_secs_f64() * 1000.0;
1159        write!(
1160            f,
1161            "AudioFrame({} samples, {}ch, {}Hz, {} @ {}, {:.2}ms)",
1162            self.samples, self.channels, self.sample_rate, self.format, self.timestamp, duration_ms
1163        )
1164    }
1165}
1166
1167impl Default for AudioFrame {
1168    /// Returns a default empty mono F32 frame with 0 samples.
1169    fn default() -> Self {
1170        Self {
1171            planes: vec![vec![]],
1172            samples: 0,
1173            channels: 1,
1174            sample_rate: 48000,
1175            format: SampleFormat::F32,
1176            timestamp: Timestamp::default(),
1177        }
1178    }
1179}
1180
1181#[cfg(test)]
1182#[allow(clippy::unwrap_used, clippy::redundant_closure_for_method_calls)]
1183mod tests {
1184    use super::*;
1185    use crate::Rational;
1186
1187    // ==========================================================================
1188    // Construction Tests
1189    // ==========================================================================
1190
1191    #[test]
1192    fn test_new_packed_f32() {
1193        let samples = 1024;
1194        let channels = 2u32;
1195        let bytes_per_sample = 4;
1196        let data = vec![0u8; samples * channels as usize * bytes_per_sample];
1197
1198        let frame = AudioFrame::new(
1199            vec![data],
1200            samples,
1201            channels,
1202            48000,
1203            SampleFormat::F32,
1204            Timestamp::default(),
1205        )
1206        .unwrap();
1207
1208        assert_eq!(frame.samples(), 1024);
1209        assert_eq!(frame.channels(), 2);
1210        assert_eq!(frame.sample_rate(), 48000);
1211        assert_eq!(frame.format(), SampleFormat::F32);
1212        assert_eq!(frame.num_planes(), 1);
1213    }
1214
1215    #[test]
1216    fn test_new_planar_f32p() {
1217        let samples = 1024;
1218        let channels = 2u32;
1219        let bytes_per_sample = 4;
1220        let plane_size = samples * bytes_per_sample;
1221
1222        let planes = vec![vec![0u8; plane_size], vec![0u8; plane_size]];
1223
1224        let frame = AudioFrame::new(
1225            planes,
1226            samples,
1227            channels,
1228            48000,
1229            SampleFormat::F32p,
1230            Timestamp::default(),
1231        )
1232        .unwrap();
1233
1234        assert_eq!(frame.samples(), 1024);
1235        assert_eq!(frame.channels(), 2);
1236        assert_eq!(frame.format(), SampleFormat::F32p);
1237        assert_eq!(frame.num_planes(), 2);
1238    }
1239
1240    #[test]
1241    fn test_new_invalid_plane_count_packed() {
1242        // Packed format should have 1 plane, but we provide 2
1243        let result = AudioFrame::new(
1244            vec![vec![0u8; 100], vec![0u8; 100]],
1245            100,
1246            2,
1247            48000,
1248            SampleFormat::F32,
1249            Timestamp::default(),
1250        );
1251
1252        assert!(result.is_err());
1253        assert_eq!(
1254            result.unwrap_err(),
1255            FrameError::InvalidPlaneCount {
1256                expected: 1,
1257                actual: 2
1258            }
1259        );
1260    }
1261
1262    #[test]
1263    fn test_new_invalid_plane_count_planar() {
1264        // Planar format with 2 channels should have 2 planes, but we provide 1
1265        let result = AudioFrame::new(
1266            vec![vec![0u8; 100]],
1267            100,
1268            2,
1269            48000,
1270            SampleFormat::F32p,
1271            Timestamp::default(),
1272        );
1273
1274        assert!(result.is_err());
1275        assert_eq!(
1276            result.unwrap_err(),
1277            FrameError::InvalidPlaneCount {
1278                expected: 2,
1279                actual: 1
1280            }
1281        );
1282    }
1283
1284    #[test]
1285    fn test_empty_packed() {
1286        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1287
1288        assert_eq!(frame.samples(), 1024);
1289        assert_eq!(frame.channels(), 2);
1290        assert_eq!(frame.sample_rate(), 48000);
1291        assert_eq!(frame.format(), SampleFormat::F32);
1292        assert_eq!(frame.num_planes(), 1);
1293        assert_eq!(frame.total_size(), 1024 * 2 * 4);
1294    }
1295
1296    #[test]
1297    fn test_empty_planar() {
1298        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1299
1300        assert_eq!(frame.num_planes(), 2);
1301        assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
1302        assert_eq!(frame.plane(1).map(|p| p.len()), Some(1024 * 4));
1303        assert_eq!(frame.total_size(), 1024 * 2 * 4);
1304    }
1305
1306    #[test]
1307    fn test_empty_i16() {
1308        let frame = AudioFrame::empty(1024, 2, 44100, SampleFormat::I16).unwrap();
1309
1310        assert_eq!(frame.bytes_per_sample(), 2);
1311        assert_eq!(frame.total_size(), 1024 * 2 * 2);
1312    }
1313
1314    #[test]
1315    fn test_empty_other_format_error() {
1316        let result = AudioFrame::empty(1024, 2, 48000, SampleFormat::Other(999));
1317
1318        assert!(result.is_err());
1319        assert_eq!(
1320            result.unwrap_err(),
1321            FrameError::UnsupportedSampleFormat(SampleFormat::Other(999))
1322        );
1323    }
1324
1325    #[test]
1326    fn test_default() {
1327        let frame = AudioFrame::default();
1328
1329        assert_eq!(frame.samples(), 0);
1330        assert_eq!(frame.channels(), 1);
1331        assert_eq!(frame.sample_rate(), 48000);
1332        assert_eq!(frame.format(), SampleFormat::F32);
1333    }
1334
1335    // ==========================================================================
1336    // Metadata Tests
1337    // ==========================================================================
1338
1339    #[test]
1340    fn test_duration() {
1341        // 1024 samples at 48kHz = 0.02133... seconds
1342        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1343        let duration = frame.duration();
1344        assert!((duration.as_secs_f64() - 0.021_333_333).abs() < 0.000_001);
1345
1346        // 48000 samples at 48kHz = 1 second
1347        let frame = AudioFrame::empty(48000, 2, 48000, SampleFormat::F32).unwrap();
1348        assert_eq!(frame.duration().as_secs(), 1);
1349    }
1350
1351    #[test]
1352    fn test_duration_zero_sample_rate() {
1353        let frame = AudioFrame::new(
1354            vec![vec![]],
1355            0,
1356            1,
1357            0,
1358            SampleFormat::F32,
1359            Timestamp::default(),
1360        )
1361        .unwrap();
1362
1363        assert_eq!(frame.duration(), Duration::ZERO);
1364    }
1365
1366    #[test]
1367    fn test_set_timestamp() {
1368        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1369        let ts = Timestamp::new(48000, Rational::new(1, 48000));
1370
1371        frame.set_timestamp(ts);
1372        assert_eq!(frame.timestamp(), ts);
1373    }
1374
1375    // ==========================================================================
1376    // Plane Access Tests
1377    // ==========================================================================
1378
1379    #[test]
1380    fn test_plane_access_packed() {
1381        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1382
1383        assert!(frame.plane(0).is_some());
1384        assert!(frame.plane(1).is_none());
1385    }
1386
1387    #[test]
1388    fn test_plane_access_planar() {
1389        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1390
1391        assert!(frame.plane(0).is_some());
1392        assert!(frame.plane(1).is_some());
1393        assert!(frame.plane(2).is_none());
1394    }
1395
1396    #[test]
1397    fn test_plane_mut_access() {
1398        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1399
1400        if let Some(data) = frame.plane_mut(0) {
1401            data[0] = 255;
1402        }
1403
1404        assert_eq!(frame.plane(0).unwrap()[0], 255);
1405    }
1406
1407    #[test]
1408    fn test_channel_access() {
1409        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1410
1411        let left = frame.channel(0).unwrap();
1412        let right = frame.channel(1).unwrap();
1413
1414        assert_eq!(left.len(), 1024 * 4);
1415        assert_eq!(right.len(), 1024 * 4);
1416    }
1417
1418    // ==========================================================================
1419    // Data Access Tests
1420    // ==========================================================================
1421
1422    #[test]
1423    fn test_data_packed() {
1424        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1425        assert!(!frame.data().is_empty());
1426        assert_eq!(frame.data().len(), 1024 * 2 * 4);
1427    }
1428
1429    #[test]
1430    fn test_data_planar_returns_none() {
1431        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1432        assert!(frame.data().is_empty());
1433    }
1434
1435    #[test]
1436    fn test_data_mut() {
1437        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1438
1439        frame.data_mut()[0] = 123;
1440
1441        assert_eq!(frame.data()[0], 123);
1442    }
1443
1444    // ==========================================================================
1445    // Typed Access Tests
1446    // ==========================================================================
1447
1448    #[test]
1449    fn test_as_f32() {
1450        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1451        let samples = frame.as_f32().unwrap();
1452        assert_eq!(samples.len(), 1024 * 2);
1453    }
1454
1455    #[test]
1456    fn test_as_f32_wrong_format() {
1457        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
1458        assert!(frame.as_f32().is_none());
1459    }
1460
1461    #[test]
1462    fn test_as_f32_mut() {
1463        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1464
1465        if let Some(samples) = frame.as_f32_mut() {
1466            samples[0] = 1.0;
1467            samples[1] = -1.0;
1468        }
1469
1470        let samples = frame.as_f32().unwrap();
1471        assert!((samples[0] - 1.0).abs() < f32::EPSILON);
1472        assert!((samples[1] - (-1.0)).abs() < f32::EPSILON);
1473    }
1474
1475    #[test]
1476    fn test_as_i16() {
1477        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
1478        let samples = frame.as_i16().unwrap();
1479        assert_eq!(samples.len(), 1024 * 2);
1480    }
1481
1482    #[test]
1483    fn test_as_i16_wrong_format() {
1484        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1485        assert!(frame.as_i16().is_none());
1486    }
1487
1488    #[test]
1489    fn test_channel_as_f32() {
1490        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1491
1492        let left = frame.channel_as_f32(0).unwrap();
1493        let right = frame.channel_as_f32(1).unwrap();
1494
1495        assert_eq!(left.len(), 1024);
1496        assert_eq!(right.len(), 1024);
1497    }
1498
1499    #[test]
1500    fn test_channel_as_f32_wrong_format() {
1501        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1502        assert!(frame.channel_as_f32(0).is_none()); // F32 is packed, not F32p
1503    }
1504
1505    #[test]
1506    fn test_channel_as_f32_mut() {
1507        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1508
1509        if let Some(left) = frame.channel_as_f32_mut(0) {
1510            left[0] = 0.5;
1511        }
1512
1513        assert!((frame.channel_as_f32(0).unwrap()[0] - 0.5).abs() < f32::EPSILON);
1514    }
1515
1516    #[test]
1517    fn test_channel_as_i16() {
1518        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
1519
1520        let left = frame.channel_as_i16(0).unwrap();
1521        assert_eq!(left.len(), 1024);
1522    }
1523
1524    #[test]
1525    fn test_channel_as_i16_mut() {
1526        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
1527
1528        if let Some(left) = frame.channel_as_i16_mut(0) {
1529            left[0] = 32767;
1530        }
1531
1532        assert_eq!(frame.channel_as_i16(0).unwrap()[0], 32767);
1533    }
1534
1535    // ==========================================================================
1536    // Utility Tests
1537    // ==========================================================================
1538
1539    #[test]
1540    fn test_total_size() {
1541        // Packed stereo F32: 1024 samples * 2 channels * 4 bytes
1542        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1543        assert_eq!(frame.total_size(), 1024 * 2 * 4);
1544
1545        // Planar stereo F32p: 2 planes * 1024 samples * 4 bytes
1546        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
1547        assert_eq!(frame.total_size(), 1024 * 4 * 2);
1548    }
1549
1550    #[test]
1551    fn test_bytes_per_sample() {
1552        assert_eq!(
1553            AudioFrame::empty(1024, 2, 48000, SampleFormat::U8)
1554                .unwrap()
1555                .bytes_per_sample(),
1556            1
1557        );
1558        assert_eq!(
1559            AudioFrame::empty(1024, 2, 48000, SampleFormat::I16)
1560                .unwrap()
1561                .bytes_per_sample(),
1562            2
1563        );
1564        assert_eq!(
1565            AudioFrame::empty(1024, 2, 48000, SampleFormat::F32)
1566                .unwrap()
1567                .bytes_per_sample(),
1568            4
1569        );
1570        assert_eq!(
1571            AudioFrame::empty(1024, 2, 48000, SampleFormat::F64)
1572                .unwrap()
1573                .bytes_per_sample(),
1574            8
1575        );
1576    }
1577
1578    // ==========================================================================
1579    // Clone Tests
1580    // ==========================================================================
1581
1582    #[test]
1583    fn test_clone() {
1584        let mut original = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1585        original.set_timestamp(Timestamp::new(1000, Rational::new(1, 1000)));
1586
1587        // Modify some data
1588        if let Some(data) = original.plane_mut(0) {
1589            data[0] = 42;
1590        }
1591
1592        let cloned = original.clone();
1593
1594        // Verify metadata matches
1595        assert_eq!(cloned.samples(), original.samples());
1596        assert_eq!(cloned.channels(), original.channels());
1597        assert_eq!(cloned.sample_rate(), original.sample_rate());
1598        assert_eq!(cloned.format(), original.format());
1599        assert_eq!(cloned.timestamp(), original.timestamp());
1600
1601        // Verify data was cloned
1602        assert_eq!(cloned.plane(0).unwrap()[0], 42);
1603
1604        // Verify it's a deep clone
1605        let mut cloned = cloned;
1606        if let Some(data) = cloned.plane_mut(0) {
1607            data[0] = 99;
1608        }
1609        assert_eq!(original.plane(0).unwrap()[0], 42);
1610        assert_eq!(cloned.plane(0).unwrap()[0], 99);
1611    }
1612
1613    // ==========================================================================
1614    // Display/Debug Tests
1615    // ==========================================================================
1616
1617    #[test]
1618    fn test_debug() {
1619        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1620        let debug = format!("{frame:?}");
1621        assert!(debug.contains("AudioFrame"));
1622        assert!(debug.contains("1024"));
1623        assert!(debug.contains("48000"));
1624        assert!(debug.contains("F32"));
1625    }
1626
1627    #[test]
1628    fn test_display() {
1629        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1630        let display = format!("{frame}");
1631        assert!(display.contains("1024 samples"));
1632        assert!(display.contains("2ch"));
1633        assert!(display.contains("48000Hz"));
1634        assert!(display.contains("flt")); // F32 displays as "flt"
1635    }
1636
1637    // ==========================================================================
1638    // All Sample Formats Tests
1639    // ==========================================================================
1640
1641    #[test]
1642    fn test_all_packed_formats() {
1643        let formats = [
1644            SampleFormat::U8,
1645            SampleFormat::I16,
1646            SampleFormat::I32,
1647            SampleFormat::F32,
1648            SampleFormat::F64,
1649        ];
1650
1651        for format in formats {
1652            let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
1653            assert_eq!(frame.num_planes(), 1);
1654            assert!(!frame.data().is_empty());
1655        }
1656    }
1657
1658    #[test]
1659    fn test_all_planar_formats() {
1660        let formats = [
1661            SampleFormat::U8p,
1662            SampleFormat::I16p,
1663            SampleFormat::I32p,
1664            SampleFormat::F32p,
1665            SampleFormat::F64p,
1666        ];
1667
1668        for format in formats {
1669            let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
1670            assert_eq!(frame.num_planes(), 2);
1671            assert!(frame.data().is_empty());
1672            assert!(frame.channel(0).is_some());
1673            assert!(frame.channel(1).is_some());
1674        }
1675    }
1676
1677    #[test]
1678    fn test_mono_planar() {
1679        let frame = AudioFrame::empty(1024, 1, 48000, SampleFormat::F32p).unwrap();
1680        assert_eq!(frame.num_planes(), 1);
1681        assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
1682    }
1683
1684    #[test]
1685    fn test_surround_planar() {
1686        // 5.1 surround sound
1687        let frame = AudioFrame::empty(1024, 6, 48000, SampleFormat::F32p).unwrap();
1688        assert_eq!(frame.num_planes(), 6);
1689        for i in 0..6 {
1690            assert!(frame.plane(i).is_some());
1691        }
1692        assert!(frame.plane(6).is_none());
1693    }
1694
1695    // ==========================================================================
1696    // data() / data_mut() Tests
1697    // ==========================================================================
1698
1699    #[test]
1700    fn data_packed_should_return_sample_bytes() {
1701        let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32).unwrap();
1702        // 4 samples * 2 channels * 4 bytes = 32
1703        assert_eq!(frame.data().len(), 32);
1704    }
1705
1706    #[test]
1707    fn data_planar_should_return_empty_slice() {
1708        let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
1709        assert!(frame.data().is_empty());
1710    }
1711
1712    #[test]
1713    fn data_mut_packed_should_allow_mutation() {
1714        let mut frame = AudioFrame::empty(4, 1, 48000, SampleFormat::I16).unwrap();
1715        frame.data_mut()[0] = 0x42;
1716        frame.data_mut()[1] = 0x00;
1717        assert_eq!(frame.data()[0], 0x42);
1718    }
1719
1720    #[test]
1721    fn data_mut_planar_should_return_empty_slice() {
1722        let mut frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
1723        assert!(frame.data_mut().is_empty());
1724    }
1725
1726    // ==========================================================================
1727    // PCM Conversion Tests
1728    // ==========================================================================
1729
1730    #[test]
1731    fn sample_count_should_return_samples_times_channels() {
1732        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
1733        assert_eq!(frame.sample_count(), 2048);
1734
1735        let mono = AudioFrame::empty(512, 1, 44100, SampleFormat::I16).unwrap();
1736        assert_eq!(mono.sample_count(), 512);
1737    }
1738
1739    #[test]
1740    fn to_f32_interleaved_f32p_should_transpose_to_interleaved() {
1741        // Stereo F32p: L=[1.0, 2.0], R=[3.0, 4.0]
1742        // Expected interleaved: [1.0, 3.0, 2.0, 4.0]
1743        let left: Vec<u8> = [1.0f32, 2.0f32]
1744            .iter()
1745            .flat_map(|f| f.to_le_bytes())
1746            .collect();
1747        let right: Vec<u8> = [3.0f32, 4.0f32]
1748            .iter()
1749            .flat_map(|f| f.to_le_bytes())
1750            .collect();
1751
1752        let frame = AudioFrame::new(
1753            vec![left, right],
1754            2,
1755            2,
1756            48000,
1757            SampleFormat::F32p,
1758            Timestamp::default(),
1759        )
1760        .unwrap();
1761
1762        let pcm = frame.to_f32_interleaved();
1763        assert_eq!(pcm.len(), 4);
1764        assert!((pcm[0] - 1.0).abs() < f32::EPSILON); // L0
1765        assert!((pcm[1] - 3.0).abs() < f32::EPSILON); // R0
1766        assert!((pcm[2] - 2.0).abs() < f32::EPSILON); // L1
1767        assert!((pcm[3] - 4.0).abs() < f32::EPSILON); // R1
1768    }
1769
1770    #[test]
1771    fn to_f32_interleaved_i16p_should_scale_to_minus_one_to_one() {
1772        // i16::MAX → ~1.0,  i16::MIN → ~-1.0,  0 → 0.0
1773        let make_i16_bytes = |v: i16| v.to_le_bytes().to_vec();
1774
1775        let left: Vec<u8> = [i16::MAX, 0i16]
1776            .iter()
1777            .flat_map(|&v| make_i16_bytes(v))
1778            .collect();
1779        let right: Vec<u8> = [i16::MIN, 0i16]
1780            .iter()
1781            .flat_map(|&v| make_i16_bytes(v))
1782            .collect();
1783
1784        let frame = AudioFrame::new(
1785            vec![left, right],
1786            2,
1787            2,
1788            48000,
1789            SampleFormat::I16p,
1790            Timestamp::default(),
1791        )
1792        .unwrap();
1793
1794        let pcm = frame.to_f32_interleaved();
1795        // i16 is asymmetric: MIN=-32768, MAX=32767, so MIN/MAX ≈ -1.00003
1796        // Values should be very close to [-1.0, 1.0]
1797        for &s in &pcm {
1798            assert!(s >= -1.001 && s <= 1.001, "out of range: {s}");
1799        }
1800        assert!((pcm[0] - 1.0).abs() < 0.0001); // i16::MAX → ~1.0
1801        assert!((pcm[1] - (-1.0)).abs() < 0.001); // i16::MIN → ~-1.00003
1802    }
1803
1804    #[test]
1805    fn to_f32_interleaved_unknown_should_return_empty() {
1806        // AudioFrame::new() (unlike empty()) accepts Other(_): Other is treated
1807        // as packed so expected_planes = 1.
1808        let frame = AudioFrame::new(
1809            vec![vec![0u8; 16]],
1810            4,
1811            1,
1812            48000,
1813            SampleFormat::Other(999),
1814            Timestamp::default(),
1815        )
1816        .unwrap();
1817        assert_eq!(frame.to_f32_interleaved(), Vec::<f32>::new());
1818    }
1819
1820    #[test]
1821    fn to_i16_interleaved_i16p_should_transpose_to_interleaved() {
1822        let left: Vec<u8> = [100i16, 200i16]
1823            .iter()
1824            .flat_map(|v| v.to_le_bytes())
1825            .collect();
1826        let right: Vec<u8> = [300i16, 400i16]
1827            .iter()
1828            .flat_map(|v| v.to_le_bytes())
1829            .collect();
1830
1831        let frame = AudioFrame::new(
1832            vec![left, right],
1833            2,
1834            2,
1835            48000,
1836            SampleFormat::I16p,
1837            Timestamp::default(),
1838        )
1839        .unwrap();
1840
1841        let pcm = frame.to_i16_interleaved();
1842        assert_eq!(pcm, vec![100, 300, 200, 400]);
1843    }
1844
1845    #[test]
1846    fn to_i16_interleaved_f32_should_scale_and_clamp() {
1847        // 1.0 → i16::MAX, -1.0 → -i16::MAX, 2.0 → clamped to i16::MAX
1848        let samples: &[f32] = &[1.0, -1.0, 2.0, -2.0];
1849        let bytes: Vec<u8> = samples.iter().flat_map(|f| f.to_le_bytes()).collect();
1850
1851        let frame = AudioFrame::new(
1852            vec![bytes],
1853            4,
1854            1,
1855            48000,
1856            SampleFormat::F32,
1857            Timestamp::default(),
1858        )
1859        .unwrap();
1860
1861        let pcm = frame.to_i16_interleaved();
1862        assert_eq!(pcm.len(), 4);
1863        assert_eq!(pcm[0], i16::MAX);
1864        assert_eq!(pcm[1], -i16::MAX);
1865        // Clamped values
1866        assert_eq!(pcm[2], i16::MAX);
1867        assert_eq!(pcm[3], -i16::MAX);
1868    }
1869}