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}