1#![forbid(unsafe_code)]
7#![allow(clippy::cast_sign_loss)]
8#![allow(clippy::cast_possible_wrap)]
9#![allow(clippy::cast_lossless)]
10#![allow(clippy::needless_pass_by_value)]
11#![allow(clippy::needless_range_loop)]
12#![allow(clippy::get_first)]
13#![allow(clippy::doc_markdown)]
14
15use bytes::{Bytes, BytesMut};
16use std::f64::consts::PI;
17
18use crate::error::{GraphError, GraphResult};
19use crate::frame::FilterFrame;
20use crate::node::{Node, NodeId, NodeState, NodeType};
21use crate::port::{AudioPortFormat, InputPort, OutputPort, PortFormat, PortId, PortType};
22
23use oximedia_audio::{AudioBuffer, AudioFrame, ChannelLayout};
24use oximedia_core::SampleFormat;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
28pub enum ResampleQuality {
29 Fast,
31 #[default]
33 Medium,
34 High,
36 VeryHigh,
38}
39
40impl ResampleQuality {
41 #[must_use]
43 pub fn taps(self) -> usize {
44 match self {
45 Self::Fast => 8,
46 Self::Medium => 16,
47 Self::High => 32,
48 Self::VeryHigh => 64,
49 }
50 }
51
52 #[must_use]
54 pub fn kaiser_beta(self) -> f64 {
55 match self {
56 Self::Fast => 5.0,
57 Self::Medium => 6.0,
58 Self::High => 8.0,
59 Self::VeryHigh => 10.0,
60 }
61 }
62}
63
64#[derive(Clone, Debug)]
66pub struct ResampleConfig {
67 pub input_rate: u32,
69 pub output_rate: u32,
71 pub quality: ResampleQuality,
73 pub anti_alias: bool,
75}
76
77impl Default for ResampleConfig {
78 fn default() -> Self {
79 Self {
80 input_rate: 48000,
81 output_rate: 44100,
82 quality: ResampleQuality::Medium,
83 anti_alias: true,
84 }
85 }
86}
87
88impl ResampleConfig {
89 #[must_use]
91 pub fn new(input_rate: u32, output_rate: u32) -> Self {
92 Self {
93 input_rate,
94 output_rate,
95 ..Default::default()
96 }
97 }
98
99 #[must_use]
101 pub fn with_quality(mut self, quality: ResampleQuality) -> Self {
102 self.quality = quality;
103 self
104 }
105
106 #[must_use]
108 pub fn with_anti_alias(mut self, enabled: bool) -> Self {
109 self.anti_alias = enabled;
110 self
111 }
112}
113
114#[derive(Clone, Debug)]
116struct SincKernel {
117 coefficients: Vec<f64>,
119 taps: usize,
121 beta: f64,
123 cutoff: f64,
125}
126
127impl SincKernel {
128 fn new(taps: usize, beta: f64, cutoff: f64) -> Self {
130 let coefficients = Self::compute_coefficients(taps, beta, cutoff);
131 Self {
132 coefficients,
133 taps,
134 beta,
135 cutoff,
136 }
137 }
138
139 fn compute_coefficients(taps: usize, beta: f64, cutoff: f64) -> Vec<f64> {
141 let half_taps = taps / 2;
142 let mut coeffs = Vec::with_capacity(taps);
143
144 for i in 0..taps {
145 let x = (i as f64 - half_taps as f64) / half_taps as f64;
146 let sinc = Self::sinc(x * cutoff * half_taps as f64);
147 let window = Self::kaiser_window(x, beta);
148 coeffs.push(sinc * window * cutoff);
149 }
150
151 let sum: f64 = coeffs.iter().sum();
153 if sum.abs() > f64::EPSILON {
154 for coeff in &mut coeffs {
155 *coeff /= sum;
156 }
157 }
158
159 coeffs
160 }
161
162 fn sinc(x: f64) -> f64 {
164 if x.abs() < f64::EPSILON {
165 1.0
166 } else {
167 let px = PI * x;
168 px.sin() / px
169 }
170 }
171
172 fn kaiser_window(x: f64, beta: f64) -> f64 {
174 if x.abs() >= 1.0 {
175 return 0.0;
176 }
177 let arg = 1.0 - x * x;
178 if arg < 0.0 {
179 return 0.0;
180 }
181 Self::bessel_i0(beta * arg.sqrt()) / Self::bessel_i0(beta)
182 }
183
184 fn bessel_i0(x: f64) -> f64 {
186 let mut sum = 1.0;
187 let mut term = 1.0;
188 let x_half = x / 2.0;
189
190 for k in 1..=25 {
191 term *= x_half * x_half / (k * k) as f64;
192 sum += term;
193 if term < f64::EPSILON * sum {
194 break;
195 }
196 }
197
198 sum
199 }
200
201 fn interpolate(&self, samples: &[f64], position: f64) -> f64 {
203 let base_index = position.floor() as isize;
204 let frac = position - position.floor();
205 let half_taps = (self.taps / 2) as isize;
206
207 let mut result = 0.0;
208 for (tap, coeff) in self.coefficients.iter().enumerate() {
209 let tap_offset = tap as isize - half_taps;
210 let sample_index = base_index + tap_offset;
211
212 if sample_index >= 0 && (sample_index as usize) < samples.len() {
213 let phase_adjust = Self::sinc((tap_offset as f64 - frac) * self.cutoff);
215 let window = Self::kaiser_window(
216 (tap as f64 - self.taps as f64 / 2.0) / (self.taps as f64 / 2.0),
217 self.beta,
218 );
219 result += samples[sample_index as usize] * coeff * phase_adjust * window;
220 }
221 }
222
223 result
224 }
225}
226
227#[derive(Clone, Debug)]
229struct ResampleState {
230 input_buffer: Vec<Vec<f64>>,
232 position: f64,
234 ratio: f64,
236 kernel: SincKernel,
238 channels: usize,
240}
241
242impl ResampleState {
243 fn new(config: &ResampleConfig, channels: usize) -> Self {
245 let ratio = config.input_rate as f64 / config.output_rate as f64;
246 let cutoff = if config.anti_alias && ratio > 1.0 {
247 1.0 / ratio * 0.95 } else {
249 0.95
250 };
251
252 let kernel = SincKernel::new(config.quality.taps(), config.quality.kaiser_beta(), cutoff);
253
254 Self {
255 input_buffer: vec![Vec::new(); channels],
256 position: 0.0,
257 ratio,
258 kernel,
259 channels,
260 }
261 }
262
263 fn process(&mut self, input: &[Vec<f64>]) -> Vec<Vec<f64>> {
265 if input.is_empty() || input[0].is_empty() {
266 return vec![Vec::new(); self.channels];
267 }
268
269 for (ch, samples) in input.iter().enumerate() {
271 if ch < self.channels {
272 self.input_buffer[ch].extend_from_slice(samples);
273 }
274 }
275
276 let available_input = self.input_buffer[0].len() as f64 - self.position;
278 let output_samples = (available_input / self.ratio).floor() as usize;
279
280 if output_samples == 0 {
281 return vec![Vec::new(); self.channels];
282 }
283
284 let mut output = vec![Vec::with_capacity(output_samples); self.channels];
286
287 for out_idx in 0..output_samples {
288 let in_pos = self.position + out_idx as f64 * self.ratio;
289
290 for ch in 0..self.channels {
291 let sample = self.kernel.interpolate(&self.input_buffer[ch], in_pos);
292 output[ch].push(sample);
293 }
294 }
295
296 self.position += output_samples as f64 * self.ratio;
298 let consumed = self.position.floor() as usize;
299
300 let keep = self.kernel.taps;
302 if consumed > keep {
303 let trim = consumed - keep;
304 for ch in 0..self.channels {
305 if trim < self.input_buffer[ch].len() {
306 self.input_buffer[ch].drain(0..trim);
307 }
308 }
309 self.position -= trim as f64;
310 }
311
312 output
313 }
314
315 fn flush(&mut self) -> Vec<Vec<f64>> {
317 let padding = self.kernel.taps;
319 for ch in 0..self.channels {
320 self.input_buffer[ch].extend(vec![0.0; padding]);
321 }
322
323 let input: Vec<Vec<f64>> = vec![Vec::new(); self.channels];
325 let output = self.process(&input);
326
327 for ch in 0..self.channels {
329 self.input_buffer[ch].clear();
330 }
331 self.position = 0.0;
332
333 output
334 }
335}
336
337pub struct ResampleFilter {
352 id: NodeId,
353 name: String,
354 state: NodeState,
355 config: ResampleConfig,
356 resample_state: Option<ResampleState>,
357 inputs: Vec<InputPort>,
358 outputs: Vec<OutputPort>,
359}
360
361impl ResampleFilter {
362 #[must_use]
364 pub fn new(id: NodeId, name: impl Into<String>, config: ResampleConfig) -> Self {
365 let input_format =
366 PortFormat::Audio(AudioPortFormat::any().with_sample_rate(config.input_rate));
367 let output_format =
368 PortFormat::Audio(AudioPortFormat::any().with_sample_rate(config.output_rate));
369
370 Self {
371 id,
372 name: name.into(),
373 state: NodeState::Idle,
374 config,
375 resample_state: None,
376 inputs: vec![
377 InputPort::new(PortId(0), "input", PortType::Audio).with_format(input_format)
378 ],
379 outputs: vec![
380 OutputPort::new(PortId(0), "output", PortType::Audio).with_format(output_format)
381 ],
382 }
383 }
384
385 #[must_use]
387 pub fn config(&self) -> &ResampleConfig {
388 &self.config
389 }
390
391 pub fn set_config(&mut self, config: ResampleConfig) {
393 self.config = config;
394 self.resample_state = None; }
396
397 fn frame_to_samples(frame: &AudioFrame) -> Vec<Vec<f64>> {
399 let channels = frame.channels.count();
400 let sample_count = frame.sample_count();
401
402 if sample_count == 0 {
403 return vec![Vec::new(); channels];
404 }
405
406 let mut output = vec![Vec::with_capacity(sample_count); channels];
407
408 match &frame.samples {
409 AudioBuffer::Interleaved(data) => {
410 Self::convert_interleaved_to_f64(data, frame.format, channels, &mut output);
411 }
412 AudioBuffer::Planar(planes) => {
413 Self::convert_planar_to_f64(planes, frame.format, &mut output);
414 }
415 }
416
417 output
418 }
419
420 fn convert_interleaved_to_f64(
422 data: &Bytes,
423 format: SampleFormat,
424 channels: usize,
425 output: &mut [Vec<f64>],
426 ) {
427 let bytes_per_sample = format.bytes_per_sample();
428 if bytes_per_sample == 0 || channels == 0 {
429 return;
430 }
431
432 let sample_count = data.len() / (bytes_per_sample * channels);
433
434 for i in 0..sample_count {
435 for ch in 0..channels {
436 let offset = (i * channels + ch) * bytes_per_sample;
437 if offset + bytes_per_sample <= data.len() {
438 let sample =
439 Self::bytes_to_f64(&data[offset..offset + bytes_per_sample], format);
440 output[ch].push(sample);
441 }
442 }
443 }
444 }
445
446 fn convert_planar_to_f64(planes: &[Bytes], format: SampleFormat, output: &mut [Vec<f64>]) {
448 let bytes_per_sample = format.bytes_per_sample();
449 if bytes_per_sample == 0 {
450 return;
451 }
452
453 for (ch, plane) in planes.iter().enumerate() {
454 if ch >= output.len() {
455 break;
456 }
457 let sample_count = plane.len() / bytes_per_sample;
458 for i in 0..sample_count {
459 let offset = i * bytes_per_sample;
460 if offset + bytes_per_sample <= plane.len() {
461 let sample =
462 Self::bytes_to_f64(&plane[offset..offset + bytes_per_sample], format);
463 output[ch].push(sample);
464 }
465 }
466 }
467 }
468
469 fn bytes_to_f64(bytes: &[u8], format: SampleFormat) -> f64 {
471 match format {
472 SampleFormat::U8 => {
473 if bytes.is_empty() {
474 return 0.0;
475 }
476 (f64::from(bytes[0]) - 128.0) / 128.0
477 }
478 SampleFormat::S16 => {
479 if bytes.len() < 2 {
480 return 0.0;
481 }
482 let sample = i16::from_le_bytes([bytes[0], bytes[1]]);
483 f64::from(sample) / f64::from(i16::MAX)
484 }
485 SampleFormat::S32 => {
486 if bytes.len() < 4 {
487 return 0.0;
488 }
489 let sample = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
490 f64::from(sample) / f64::from(i32::MAX)
491 }
492 SampleFormat::F32 => {
493 if bytes.len() < 4 {
494 return 0.0;
495 }
496 f64::from(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
497 }
498 SampleFormat::F64 => {
499 if bytes.len() < 8 {
500 return 0.0;
501 }
502 f64::from_le_bytes([
503 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
504 ])
505 }
506 _ => 0.0,
507 }
508 }
509
510 fn samples_to_frame(
512 samples: Vec<Vec<f64>>,
513 format: SampleFormat,
514 sample_rate: u32,
515 channels: ChannelLayout,
516 ) -> AudioFrame {
517 let channel_count = channels.count();
518 if samples.is_empty() || samples[0].is_empty() || channel_count == 0 {
519 return AudioFrame::new(format, sample_rate, channels);
520 }
521
522 let sample_count = samples[0].len();
523 let bytes_per_sample = format.bytes_per_sample();
524 let mut buffer = BytesMut::with_capacity(sample_count * channel_count * bytes_per_sample);
525
526 for i in 0..sample_count {
528 for ch in 0..channel_count {
529 let sample = if ch < samples.len() && i < samples[ch].len() {
530 samples[ch][i]
531 } else {
532 0.0
533 };
534 Self::f64_to_bytes(sample, format, &mut buffer);
535 }
536 }
537
538 let mut frame = AudioFrame::new(format, sample_rate, channels);
539 frame.samples = AudioBuffer::Interleaved(buffer.freeze());
540 frame
541 }
542
543 fn f64_to_bytes(sample: f64, format: SampleFormat, buffer: &mut BytesMut) {
545 let clamped = sample.clamp(-1.0, 1.0);
547
548 match format {
549 SampleFormat::U8 => {
550 let value = ((clamped * 128.0) + 128.0) as u8;
551 buffer.extend_from_slice(&[value]);
552 }
553 SampleFormat::S16 => {
554 let value = (clamped * f64::from(i16::MAX)) as i16;
555 buffer.extend_from_slice(&value.to_le_bytes());
556 }
557 SampleFormat::S32 => {
558 let value = (clamped * f64::from(i32::MAX)) as i32;
559 buffer.extend_from_slice(&value.to_le_bytes());
560 }
561 SampleFormat::F32 => {
562 #[allow(clippy::cast_possible_truncation)]
563 let value = clamped as f32;
564 buffer.extend_from_slice(&value.to_le_bytes());
565 }
566 SampleFormat::F64 => {
567 buffer.extend_from_slice(&clamped.to_le_bytes());
568 }
569 _ => {}
570 }
571 }
572}
573
574impl Node for ResampleFilter {
575 fn id(&self) -> NodeId {
576 self.id
577 }
578
579 fn name(&self) -> &str {
580 &self.name
581 }
582
583 fn node_type(&self) -> NodeType {
584 NodeType::Filter
585 }
586
587 fn state(&self) -> NodeState {
588 self.state
589 }
590
591 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
592 if !self.state.can_transition_to(state) {
593 return Err(GraphError::InvalidStateTransition {
594 node: self.id,
595 from: self.state.to_string(),
596 to: state.to_string(),
597 });
598 }
599 self.state = state;
600 Ok(())
601 }
602
603 fn inputs(&self) -> &[InputPort] {
604 &self.inputs
605 }
606
607 fn outputs(&self) -> &[OutputPort] {
608 &self.outputs
609 }
610
611 fn initialize(&mut self) -> GraphResult<()> {
612 Ok(())
614 }
615
616 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
617 let frame = match input {
618 Some(FilterFrame::Audio(frame)) => frame,
619 Some(_) => {
620 return Err(GraphError::PortTypeMismatch {
621 expected: "Audio".to_string(),
622 actual: "Video".to_string(),
623 });
624 }
625 None => return Ok(None),
626 };
627
628 if self.resample_state.is_none() {
630 let channels = frame.channels.count();
631 self.resample_state = Some(ResampleState::new(&self.config, channels));
632 }
633
634 let state = match self.resample_state.as_mut() {
635 Some(s) => s,
636 None => return Ok(None),
637 };
638
639 let input_samples = Self::frame_to_samples(&frame);
641
642 let output_samples = state.process(&input_samples);
644
645 let output_frame = Self::samples_to_frame(
647 output_samples,
648 frame.format,
649 self.config.output_rate,
650 frame.channels.clone(),
651 );
652
653 Ok(Some(FilterFrame::Audio(output_frame)))
654 }
655
656 fn flush(&mut self) -> GraphResult<Vec<FilterFrame>> {
657 if let Some(state) = self.resample_state.as_mut() {
658 let output_samples = state.flush();
659 if !output_samples.is_empty() && !output_samples[0].is_empty() {
660 let frame = Self::samples_to_frame(
661 output_samples,
662 SampleFormat::F32,
663 self.config.output_rate,
664 ChannelLayout::from_count(state.channels),
665 );
666 return Ok(vec![FilterFrame::Audio(frame)]);
667 }
668 }
669 Ok(Vec::new())
670 }
671
672 fn reset(&mut self) -> GraphResult<()> {
673 self.resample_state = None;
674 self.set_state(NodeState::Idle)
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681
682 #[test]
683 fn test_resample_quality() {
684 assert_eq!(ResampleQuality::Fast.taps(), 8);
685 assert_eq!(ResampleQuality::Medium.taps(), 16);
686 assert_eq!(ResampleQuality::High.taps(), 32);
687 assert_eq!(ResampleQuality::VeryHigh.taps(), 64);
688 }
689
690 #[test]
691 fn test_resample_config() {
692 let config = ResampleConfig::new(48000, 44100)
693 .with_quality(ResampleQuality::High)
694 .with_anti_alias(true);
695
696 assert_eq!(config.input_rate, 48000);
697 assert_eq!(config.output_rate, 44100);
698 assert_eq!(config.quality, ResampleQuality::High);
699 assert!(config.anti_alias);
700 }
701
702 #[test]
703 fn test_sinc_function() {
704 let sinc_0 = SincKernel::sinc(0.0);
705 assert!((sinc_0 - 1.0).abs() < f64::EPSILON);
706
707 let sinc_1 = SincKernel::sinc(1.0);
708 assert!(sinc_1.abs() < 0.01);
709 }
710
711 #[test]
712 fn test_kaiser_window() {
713 let center = SincKernel::kaiser_window(0.0, 6.0);
714 assert!((center - 1.0).abs() < 0.01);
715
716 let edge = SincKernel::kaiser_window(1.0, 6.0);
717 assert!(edge.abs() < f64::EPSILON);
718 }
719
720 #[test]
721 fn test_bessel_i0() {
722 let i0_0 = SincKernel::bessel_i0(0.0);
723 assert!((i0_0 - 1.0).abs() < f64::EPSILON);
724 }
725
726 #[test]
727 fn test_resample_filter_creation() {
728 let config = ResampleConfig::new(48000, 44100);
729 let filter = ResampleFilter::new(NodeId(1), "test_resample", config);
730
731 assert_eq!(filter.id(), NodeId(1));
732 assert_eq!(filter.name(), "test_resample");
733 assert_eq!(filter.node_type(), NodeType::Filter);
734 assert_eq!(filter.state(), NodeState::Idle);
735 }
736
737 #[test]
738 fn test_resample_filter_ports() {
739 let config = ResampleConfig::new(48000, 44100);
740 let filter = ResampleFilter::new(NodeId(0), "test", config);
741
742 assert_eq!(filter.inputs().len(), 1);
743 assert_eq!(filter.outputs().len(), 1);
744 assert_eq!(filter.inputs()[0].port_type, PortType::Audio);
745 assert_eq!(filter.outputs()[0].port_type, PortType::Audio);
746 }
747
748 #[test]
749 fn test_bytes_to_f64_u8() {
750 let sample = ResampleFilter::bytes_to_f64(&[128], SampleFormat::U8);
751 assert!(sample.abs() < 0.01);
752
753 let sample = ResampleFilter::bytes_to_f64(&[255], SampleFormat::U8);
754 assert!((sample - 0.992).abs() < 0.01);
755 }
756
757 #[test]
758 fn test_bytes_to_f64_s16() {
759 let sample = ResampleFilter::bytes_to_f64(&[0, 0], SampleFormat::S16);
760 assert!(sample.abs() < f64::EPSILON);
761
762 let sample = ResampleFilter::bytes_to_f64(&[0xFF, 0x7F], SampleFormat::S16);
763 assert!((sample - 1.0).abs() < 0.001);
764 }
765
766 #[test]
767 fn test_bytes_to_f64_f32() {
768 let value: f32 = 0.5;
769 let bytes = value.to_le_bytes();
770 let sample = ResampleFilter::bytes_to_f64(&bytes, SampleFormat::F32);
771 assert!((sample - 0.5).abs() < f64::EPSILON);
772 }
773
774 #[test]
775 fn test_resample_process_empty() {
776 let config = ResampleConfig::new(48000, 44100);
777 let mut filter = ResampleFilter::new(NodeId(0), "test", config);
778
779 let result = filter.process(None).expect("process should succeed");
780 assert!(result.is_none());
781 }
782
783 #[test]
784 fn test_resample_process_audio() {
785 let config = ResampleConfig::new(48000, 48000); let mut filter = ResampleFilter::new(NodeId(0), "test", config);
787
788 let mut frame = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Mono);
789 let mut samples = BytesMut::new();
791 for i in 0..1024 {
792 let sample = (i as f32 * 0.001).sin();
793 samples.extend_from_slice(&sample.to_le_bytes());
794 }
795 frame.samples = AudioBuffer::Interleaved(samples.freeze());
796
797 let result = filter
798 .process(Some(FilterFrame::Audio(frame)))
799 .expect("process should succeed");
800 assert!(result.is_some());
801 }
802
803 #[test]
804 fn test_state_transitions() {
805 let config = ResampleConfig::new(48000, 44100);
806 let mut filter = ResampleFilter::new(NodeId(0), "test", config);
807
808 assert!(filter.set_state(NodeState::Processing).is_ok());
809 assert_eq!(filter.state(), NodeState::Processing);
810
811 assert!(filter.set_state(NodeState::Done).is_ok());
812 assert_eq!(filter.state(), NodeState::Done);
813
814 assert!(filter.reset().is_ok());
815 assert_eq!(filter.state(), NodeState::Idle);
816 }
817
818 #[test]
819 fn test_sinc_kernel_creation() {
820 let kernel = SincKernel::new(16, 6.0, 0.95);
821 assert_eq!(kernel.taps, 16);
822 assert_eq!(kernel.coefficients.len(), 16);
823
824 let sum: f64 = kernel.coefficients.iter().sum();
826 assert!((sum - 1.0).abs() < 0.01);
827 }
828
829 #[test]
830 fn test_resample_state_creation() {
831 let config = ResampleConfig::new(48000, 44100);
832 let state = ResampleState::new(&config, 2);
833
834 assert_eq!(state.channels, 2);
835 assert!(state.ratio > 1.0); }
837
838 #[test]
839 fn test_f64_conversion_roundtrip() {
840 let original: f64 = 0.5;
841 let mut buffer = BytesMut::new();
842
843 ResampleFilter::f64_to_bytes(original, SampleFormat::F32, &mut buffer);
844 let converted = ResampleFilter::bytes_to_f64(&buffer, SampleFormat::F32);
845
846 assert!((original - converted).abs() < 0.0001);
847 }
848
849 #[test]
850 fn test_sample_clamping() {
851 let mut buffer = BytesMut::new();
852
853 ResampleFilter::f64_to_bytes(2.0, SampleFormat::F32, &mut buffer);
855 let clamped = ResampleFilter::bytes_to_f64(&buffer, SampleFormat::F32);
856 assert!((clamped - 1.0).abs() < f64::EPSILON);
857
858 buffer.clear();
859 ResampleFilter::f64_to_bytes(-2.0, SampleFormat::F32, &mut buffer);
860 let clamped = ResampleFilter::bytes_to_f64(&buffer, SampleFormat::F32);
861 assert!((clamped + 1.0).abs() < f64::EPSILON);
862 }
863
864 #[test]
865 fn test_resample_different_rates() {
866 let config = ResampleConfig::new(48000, 24000); let mut filter = ResampleFilter::new(NodeId(0), "test", config);
868
869 let mut frame = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Mono);
870 let mut samples = BytesMut::new();
871 for i in 0..4800 {
872 let sample = (i as f64 * 0.01).sin() as f32;
873 samples.extend_from_slice(&sample.to_le_bytes());
874 }
875 frame.samples = AudioBuffer::Interleaved(samples.freeze());
876
877 let result = filter
878 .process(Some(FilterFrame::Audio(frame)))
879 .expect("process should succeed");
880 assert!(result.is_some());
881
882 if let Some(FilterFrame::Audio(output)) = result {
883 let output_count = output.sample_count();
885 assert!(output_count > 0);
886 assert!(output_count < 4800);
887 }
888 }
889
890 #[test]
891 fn test_flush() {
892 let config = ResampleConfig::new(48000, 44100);
893 let mut filter = ResampleFilter::new(NodeId(0), "test", config);
894
895 let mut frame = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Mono);
897 let mut samples = BytesMut::new();
898 for _ in 0..1024 {
899 samples.extend_from_slice(&0.5f32.to_le_bytes());
900 }
901 frame.samples = AudioBuffer::Interleaved(samples.freeze());
902
903 let _ = filter.process(Some(FilterFrame::Audio(frame)));
904
905 let flushed = filter.flush().expect("flush should succeed");
907 assert!(flushed.len() <= 1);
909 }
910}