Skip to main content

ff_format/frame/audio/
buffer.rs

1//! Plane, channel, and typed sample access for [`AudioFrame`].
2
3use crate::SampleFormat;
4
5use super::AudioFrame;
6
7impl AudioFrame {
8    // ==========================================================================
9    // Plane Data Access
10    // ==========================================================================
11
12    /// Returns the number of planes in this frame.
13    ///
14    /// - Packed formats: 1 plane (interleaved channels)
15    /// - Planar formats: 1 plane per channel
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// use ff_format::{AudioFrame, SampleFormat};
21    ///
22    /// // Packed format - 1 plane
23    /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
24    /// assert_eq!(packed.num_planes(), 1);
25    ///
26    /// // Planar format - 1 plane per channel
27    /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
28    /// assert_eq!(planar.num_planes(), 2);
29    /// ```
30    #[must_use]
31    #[inline]
32    pub fn num_planes(&self) -> usize {
33        self.planes.len()
34    }
35
36    /// Returns a slice of all plane data.
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use ff_format::{AudioFrame, SampleFormat};
42    ///
43    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
44    /// let planes = frame.planes();
45    /// assert_eq!(planes.len(), 2);
46    /// ```
47    #[must_use]
48    #[inline]
49    pub fn planes(&self) -> &[Vec<u8>] {
50        &self.planes
51    }
52
53    /// Returns the data for a specific plane, or `None` if the index is out of bounds.
54    ///
55    /// For packed formats, use `plane(0)`. For planar formats, use `plane(channel_index)`.
56    ///
57    /// # Arguments
58    ///
59    /// * `index` - The plane index (0 for packed, channel index for planar)
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use ff_format::{AudioFrame, SampleFormat};
65    ///
66    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
67    ///
68    /// // Access left channel (plane 0)
69    /// assert!(frame.plane(0).is_some());
70    ///
71    /// // Access right channel (plane 1)
72    /// assert!(frame.plane(1).is_some());
73    ///
74    /// // No third channel
75    /// assert!(frame.plane(2).is_none());
76    /// ```
77    #[must_use]
78    #[inline]
79    pub fn plane(&self, index: usize) -> Option<&[u8]> {
80        self.planes.get(index).map(Vec::as_slice)
81    }
82
83    /// Returns mutable access to a specific plane's data.
84    ///
85    /// # Arguments
86    ///
87    /// * `index` - The plane index
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use ff_format::{AudioFrame, SampleFormat};
93    ///
94    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
95    /// if let Some(data) = frame.plane_mut(0) {
96    ///     // Modify left channel
97    ///     data[0] = 128;
98    /// }
99    /// ```
100    #[must_use]
101    #[inline]
102    pub fn plane_mut(&mut self, index: usize) -> Option<&mut [u8]> {
103        self.planes.get_mut(index).map(Vec::as_mut_slice)
104    }
105
106    /// Returns the channel data for planar formats.
107    ///
108    /// This is an alias for [`plane()`](Self::plane) that's more semantically
109    /// meaningful for audio data.
110    ///
111    /// # Arguments
112    ///
113    /// * `channel` - The channel index (0 = left, 1 = right, etc.)
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use ff_format::{AudioFrame, SampleFormat};
119    ///
120    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
121    ///
122    /// // Get left channel data
123    /// let left = frame.channel(0).unwrap();
124    /// assert_eq!(left.len(), 1024 * 4); // 1024 samples * 4 bytes
125    /// ```
126    #[must_use]
127    #[inline]
128    pub fn channel(&self, channel: usize) -> Option<&[u8]> {
129        self.plane(channel)
130    }
131
132    /// Returns mutable access to channel data for planar formats.
133    ///
134    /// # Arguments
135    ///
136    /// * `channel` - The channel index
137    #[must_use]
138    #[inline]
139    pub fn channel_mut(&mut self, channel: usize) -> Option<&mut [u8]> {
140        self.plane_mut(channel)
141    }
142
143    // ==========================================================================
144    // Contiguous Data Access
145    // ==========================================================================
146
147    /// Returns the raw sample data as a contiguous byte slice.
148    ///
149    /// For packed formats (e.g. [`SampleFormat::F32`], [`SampleFormat::I16`]), this returns
150    /// the interleaved sample bytes. For planar formats (e.g. [`SampleFormat::F32p`],
151    /// [`SampleFormat::I16p`]), this returns an empty slice — use [`channel()`](Self::channel)
152    /// or [`channel_as_f32()`](Self::channel_as_f32) to access individual channel planes instead.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use ff_format::{AudioFrame, SampleFormat};
158    ///
159    /// // Packed format - returns interleaved sample bytes
160    /// let packed = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
161    /// assert_eq!(packed.data().len(), 1024 * 2 * 4);
162    ///
163    /// // Planar format - returns empty slice; use channel() instead
164    /// let planar = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
165    /// assert!(planar.data().is_empty());
166    /// let left = planar.channel(0).unwrap();
167    /// assert_eq!(left.len(), 1024 * 4);
168    /// ```
169    #[must_use]
170    #[inline]
171    pub fn data(&self) -> &[u8] {
172        if self.format.is_packed() && self.planes.len() == 1 {
173            &self.planes[0]
174        } else {
175            &[]
176        }
177    }
178
179    /// Returns mutable access to the raw sample data.
180    ///
181    /// For packed formats, returns the interleaved sample bytes as a mutable slice.
182    /// For planar formats, returns an empty mutable slice — use
183    /// [`channel_mut()`](Self::channel_mut) to modify individual channel planes instead.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use ff_format::{AudioFrame, SampleFormat};
189    ///
190    /// let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
191    /// let data = frame.data_mut();
192    /// data[0] = 128;
193    /// ```
194    #[must_use]
195    #[inline]
196    pub fn data_mut(&mut self) -> &mut [u8] {
197        if self.format.is_packed() && self.planes.len() == 1 {
198            &mut self.planes[0]
199        } else {
200            &mut []
201        }
202    }
203
204    // ==========================================================================
205    // Typed Sample Access
206    // ==========================================================================
207    //
208    // These methods provide zero-copy typed access to audio sample data.
209    // They use unsafe code to reinterpret byte buffers as typed slices.
210    //
211    // SAFETY: The data buffers are allocated with proper size and the
212    // underlying Vec<u8> is guaranteed to be properly aligned for the
213    // platform's requirements. We verify format matches before casting.
214
215    /// Returns the sample data as an f32 slice.
216    ///
217    /// This only works if the format is [`SampleFormat::F32`] (packed).
218    /// For planar F32p format, use [`channel_as_f32()`](Self::channel_as_f32).
219    ///
220    /// # Safety Note
221    ///
222    /// This method reinterprets the raw bytes as f32 values. It requires
223    /// proper alignment and format matching.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use ff_format::{AudioFrame, SampleFormat};
229    ///
230    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
231    /// if let Some(samples) = frame.as_f32() {
232    ///     assert_eq!(samples.len(), 1024 * 2); // samples * channels
233    /// }
234    /// ```
235    #[must_use]
236    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
237    pub fn as_f32(&self) -> Option<&[f32]> {
238        if self.format != SampleFormat::F32 {
239            return None;
240        }
241
242        let bytes = self.data();
243        if bytes.is_empty() {
244            return None;
245        }
246        // SAFETY: We verified the format is F32, and the data was allocated
247        // for F32 samples. Vec<u8> is aligned to at least 1 byte, but in practice
248        // most allocators align to at least 8/16 bytes which is sufficient for f32.
249        let ptr = bytes.as_ptr().cast::<f32>();
250        let len = bytes.len() / std::mem::size_of::<f32>();
251        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
252    }
253
254    /// Returns mutable access to sample data as an f32 slice.
255    ///
256    /// Only works for [`SampleFormat::F32`] (packed).
257    #[must_use]
258    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
259    pub fn as_f32_mut(&mut self) -> Option<&mut [f32]> {
260        if self.format != SampleFormat::F32 {
261            return None;
262        }
263
264        let bytes = self.data_mut();
265        if bytes.is_empty() {
266            return None;
267        }
268        let ptr = bytes.as_mut_ptr().cast::<f32>();
269        let len = bytes.len() / std::mem::size_of::<f32>();
270        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
271    }
272
273    /// Returns the sample data as an i16 slice.
274    ///
275    /// This only works if the format is [`SampleFormat::I16`] (packed).
276    /// For planar I16p format, use [`channel_as_i16()`](Self::channel_as_i16).
277    ///
278    /// # Examples
279    ///
280    /// ```
281    /// use ff_format::{AudioFrame, SampleFormat};
282    ///
283    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
284    /// if let Some(samples) = frame.as_i16() {
285    ///     assert_eq!(samples.len(), 1024 * 2);
286    /// }
287    /// ```
288    #[must_use]
289    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
290    pub fn as_i16(&self) -> Option<&[i16]> {
291        if self.format != SampleFormat::I16 {
292            return None;
293        }
294
295        let bytes = self.data();
296        if bytes.is_empty() {
297            return None;
298        }
299        let ptr = bytes.as_ptr().cast::<i16>();
300        let len = bytes.len() / std::mem::size_of::<i16>();
301        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
302    }
303
304    /// Returns mutable access to sample data as an i16 slice.
305    ///
306    /// Only works for [`SampleFormat::I16`] (packed).
307    #[must_use]
308    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
309    pub fn as_i16_mut(&mut self) -> Option<&mut [i16]> {
310        if self.format != SampleFormat::I16 {
311            return None;
312        }
313
314        let bytes = self.data_mut();
315        if bytes.is_empty() {
316            return None;
317        }
318        let ptr = bytes.as_mut_ptr().cast::<i16>();
319        let len = bytes.len() / std::mem::size_of::<i16>();
320        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
321    }
322
323    /// Returns a specific channel's data as an f32 slice.
324    ///
325    /// Works for planar F32p format.
326    ///
327    /// # Arguments
328    ///
329    /// * `channel` - The channel index
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use ff_format::{AudioFrame, SampleFormat};
335    ///
336    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
337    /// if let Some(left) = frame.channel_as_f32(0) {
338    ///     assert_eq!(left.len(), 1024);
339    /// }
340    /// ```
341    #[must_use]
342    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
343    pub fn channel_as_f32(&self, channel: usize) -> Option<&[f32]> {
344        if self.format != SampleFormat::F32p {
345            return None;
346        }
347
348        self.channel(channel).map(|bytes| {
349            let ptr = bytes.as_ptr().cast::<f32>();
350            let len = bytes.len() / std::mem::size_of::<f32>();
351            // SAFETY: We verified the format is F32p, so each channel plane was
352            // allocated for F32 samples. Alignment is sufficient as above.
353            unsafe { std::slice::from_raw_parts(ptr, len) }
354        })
355    }
356
357    /// Returns mutable access to a channel's data as an f32 slice.
358    ///
359    /// Works for planar F32p format.
360    #[must_use]
361    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
362    pub fn channel_as_f32_mut(&mut self, channel: usize) -> Option<&mut [f32]> {
363        if self.format != SampleFormat::F32p {
364            return None;
365        }
366
367        self.channel_mut(channel).map(|bytes| {
368            let ptr = bytes.as_mut_ptr().cast::<f32>();
369            let len = bytes.len() / std::mem::size_of::<f32>();
370            // SAFETY: Same as channel_as_f32, plus we hold &mut self so no
371            // aliasing can occur.
372            unsafe { std::slice::from_raw_parts_mut(ptr, len) }
373        })
374    }
375
376    /// Returns a specific channel's data as an i16 slice.
377    ///
378    /// Works for planar I16p format.
379    ///
380    /// # Arguments
381    ///
382    /// * `channel` - The channel index
383    ///
384    /// # Examples
385    ///
386    /// ```
387    /// use ff_format::{AudioFrame, SampleFormat};
388    ///
389    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
390    /// if let Some(left) = frame.channel_as_i16(0) {
391    ///     assert_eq!(left.len(), 1024);
392    /// }
393    /// ```
394    #[must_use]
395    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
396    pub fn channel_as_i16(&self, channel: usize) -> Option<&[i16]> {
397        if self.format != SampleFormat::I16p {
398            return None;
399        }
400
401        self.channel(channel).map(|bytes| {
402            let ptr = bytes.as_ptr().cast::<i16>();
403            let len = bytes.len() / std::mem::size_of::<i16>();
404            // SAFETY: We verified the format is I16p, so each channel plane was
405            // allocated for I16 samples. Alignment is sufficient as above.
406            unsafe { std::slice::from_raw_parts(ptr, len) }
407        })
408    }
409
410    /// Returns mutable access to a channel's data as an i16 slice.
411    ///
412    /// Works for planar I16p format.
413    #[must_use]
414    #[allow(unsafe_code, clippy::cast_ptr_alignment)]
415    pub fn channel_as_i16_mut(&mut self, channel: usize) -> Option<&mut [i16]> {
416        if self.format != SampleFormat::I16p {
417            return None;
418        }
419
420        self.channel_mut(channel).map(|bytes| {
421            let ptr = bytes.as_mut_ptr().cast::<i16>();
422            let len = bytes.len() / std::mem::size_of::<i16>();
423            // SAFETY: Same as channel_as_i16, plus we hold &mut self so no
424            // aliasing can occur.
425            unsafe { std::slice::from_raw_parts_mut(ptr, len) }
426        })
427    }
428}
429
430#[cfg(test)]
431#[allow(clippy::unwrap_used)]
432mod tests {
433    use super::*;
434
435    // ==========================================================================
436    // Plane Access Tests
437    // ==========================================================================
438
439    #[test]
440    fn plane_access_packed_should_have_one_plane() {
441        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
442        assert!(frame.plane(0).is_some());
443        assert!(frame.plane(1).is_none());
444    }
445
446    #[test]
447    fn plane_access_planar_should_have_one_plane_per_channel() {
448        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
449        assert!(frame.plane(0).is_some());
450        assert!(frame.plane(1).is_some());
451        assert!(frame.plane(2).is_none());
452    }
453
454    #[test]
455    fn plane_mut_should_allow_byte_modification() {
456        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
457        if let Some(data) = frame.plane_mut(0) {
458            data[0] = 255;
459        }
460        assert_eq!(frame.plane(0).unwrap()[0], 255);
461    }
462
463    #[test]
464    fn channel_access_planar_should_return_per_channel_bytes() {
465        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
466        let left = frame.channel(0).unwrap();
467        let right = frame.channel(1).unwrap();
468        assert_eq!(left.len(), 1024 * 4);
469        assert_eq!(right.len(), 1024 * 4);
470    }
471
472    // ==========================================================================
473    // Contiguous Data Access Tests
474    // ==========================================================================
475
476    #[test]
477    fn data_packed_should_return_sample_bytes() {
478        let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32).unwrap();
479        // 4 samples * 2 channels * 4 bytes = 32
480        assert_eq!(frame.data().len(), 32);
481    }
482
483    #[test]
484    fn data_packed_should_return_all_interleaved_bytes() {
485        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
486        assert!(!frame.data().is_empty());
487        assert_eq!(frame.data().len(), 1024 * 2 * 4);
488    }
489
490    #[test]
491    fn data_planar_should_return_empty_slice() {
492        let frame = AudioFrame::empty(4, 2, 48000, SampleFormat::F32p).unwrap();
493        assert!(frame.data().is_empty());
494    }
495
496    #[test]
497    fn data_planar_f32p_should_return_empty_slice() {
498        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
499        assert!(frame.data().is_empty());
500    }
501
502    #[test]
503    fn data_mut_packed_should_allow_mutation() {
504        let mut frame = AudioFrame::empty(4, 1, 48000, SampleFormat::I16).unwrap();
505        frame.data_mut()[0] = 0x42;
506        frame.data_mut()[1] = 0x00;
507        assert_eq!(frame.data()[0], 0x42);
508    }
509
510    #[test]
511    fn data_mut_planar_should_return_empty_slice() {
512        let mut frame = AudioFrame::empty(4, 2, 48000, SampleFormat::I16p).unwrap();
513        assert!(frame.data_mut().is_empty());
514    }
515
516    #[test]
517    fn data_mut_packed_should_persist_change() {
518        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
519        frame.data_mut()[0] = 123;
520        assert_eq!(frame.data()[0], 123);
521    }
522
523    // ==========================================================================
524    // Typed Access Tests
525    // ==========================================================================
526
527    #[test]
528    fn as_f32_packed_should_return_typed_slice() {
529        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
530        let samples = frame.as_f32().unwrap();
531        assert_eq!(samples.len(), 1024 * 2);
532    }
533
534    #[test]
535    fn as_f32_wrong_format_should_return_none() {
536        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
537        assert!(frame.as_f32().is_none());
538    }
539
540    #[test]
541    fn as_f32_mut_should_allow_typed_modification() {
542        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
543        if let Some(samples) = frame.as_f32_mut() {
544            samples[0] = 1.0;
545            samples[1] = -1.0;
546        }
547        let samples = frame.as_f32().unwrap();
548        assert!((samples[0] - 1.0).abs() < f32::EPSILON);
549        assert!((samples[1] - (-1.0)).abs() < f32::EPSILON);
550    }
551
552    #[test]
553    fn as_i16_packed_should_return_typed_slice() {
554        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16).unwrap();
555        let samples = frame.as_i16().unwrap();
556        assert_eq!(samples.len(), 1024 * 2);
557    }
558
559    #[test]
560    fn as_i16_wrong_format_should_return_none() {
561        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
562        assert!(frame.as_i16().is_none());
563    }
564
565    #[test]
566    fn channel_as_f32_planar_should_return_typed_slice_per_channel() {
567        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
568        let left = frame.channel_as_f32(0).unwrap();
569        let right = frame.channel_as_f32(1).unwrap();
570        assert_eq!(left.len(), 1024);
571        assert_eq!(right.len(), 1024);
572    }
573
574    #[test]
575    fn channel_as_f32_packed_format_should_return_none() {
576        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
577        assert!(frame.channel_as_f32(0).is_none()); // F32 is packed, not F32p
578    }
579
580    #[test]
581    fn channel_as_f32_mut_should_allow_typed_channel_modification() {
582        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32p).unwrap();
583        if let Some(left) = frame.channel_as_f32_mut(0) {
584            left[0] = 0.5;
585        }
586        assert!((frame.channel_as_f32(0).unwrap()[0] - 0.5).abs() < f32::EPSILON);
587    }
588
589    #[test]
590    fn channel_as_i16_planar_should_return_typed_slice() {
591        let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
592        let left = frame.channel_as_i16(0).unwrap();
593        assert_eq!(left.len(), 1024);
594    }
595
596    #[test]
597    fn channel_as_i16_mut_should_allow_typed_channel_modification() {
598        let mut frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::I16p).unwrap();
599        if let Some(left) = frame.channel_as_i16_mut(0) {
600            left[0] = 32767;
601        }
602        assert_eq!(frame.channel_as_i16(0).unwrap()[0], 32767);
603    }
604
605    // ==========================================================================
606    // All Sample Formats Tests
607    // ==========================================================================
608
609    #[test]
610    fn all_packed_formats_should_have_one_plane_and_nonempty_data() {
611        let formats = [
612            SampleFormat::U8,
613            SampleFormat::I16,
614            SampleFormat::I32,
615            SampleFormat::F32,
616            SampleFormat::F64,
617        ];
618        for format in formats {
619            let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
620            assert_eq!(frame.num_planes(), 1);
621            assert!(!frame.data().is_empty());
622        }
623    }
624
625    #[test]
626    fn all_planar_formats_should_have_one_plane_per_channel_and_empty_data() {
627        let formats = [
628            SampleFormat::U8p,
629            SampleFormat::I16p,
630            SampleFormat::I32p,
631            SampleFormat::F32p,
632            SampleFormat::F64p,
633        ];
634        for format in formats {
635            let frame = AudioFrame::empty(1024, 2, 48000, format).unwrap();
636            assert_eq!(frame.num_planes(), 2);
637            assert!(frame.data().is_empty());
638            assert!(frame.channel(0).is_some());
639            assert!(frame.channel(1).is_some());
640        }
641    }
642
643    #[test]
644    fn mono_planar_should_have_one_plane_with_correct_size() {
645        let frame = AudioFrame::empty(1024, 1, 48000, SampleFormat::F32p).unwrap();
646        assert_eq!(frame.num_planes(), 1);
647        assert_eq!(frame.plane(0).map(|p| p.len()), Some(1024 * 4));
648    }
649
650    #[test]
651    fn surround_51_planar_should_have_six_planes() {
652        let frame = AudioFrame::empty(1024, 6, 48000, SampleFormat::F32p).unwrap();
653        assert_eq!(frame.num_planes(), 6);
654        for i in 0..6 {
655            assert!(frame.plane(i).is_some());
656        }
657        assert!(frame.plane(6).is_none());
658    }
659}