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