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::collections::VecDeque;
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 DelayMode {
29 #[default]
31 Normal,
32 PingPong,
34}
35
36#[derive(Clone, Debug)]
38pub struct DelayConfig {
39 pub delay_ms: f64,
41 pub feedback: f64,
43 pub mix: f64,
45 pub mode: DelayMode,
47 pub damping: f64,
49}
50
51impl Default for DelayConfig {
52 fn default() -> Self {
53 Self {
54 delay_ms: 250.0,
55 feedback: 0.5,
56 mix: 0.5,
57 mode: DelayMode::Normal,
58 damping: 0.0,
59 }
60 }
61}
62
63impl DelayConfig {
64 #[must_use]
66 pub fn new(delay_ms: f64) -> Self {
67 Self {
68 delay_ms,
69 ..Default::default()
70 }
71 }
72
73 #[must_use]
75 pub fn with_feedback(mut self, feedback: f64) -> Self {
76 self.feedback = feedback.clamp(0.0, 0.99);
77 self
78 }
79
80 #[must_use]
82 pub fn with_mix(mut self, mix: f64) -> Self {
83 self.mix = mix.clamp(0.0, 1.0);
84 self
85 }
86
87 #[must_use]
89 pub fn with_mode(mut self, mode: DelayMode) -> Self {
90 self.mode = mode;
91 self
92 }
93
94 #[must_use]
96 pub fn ping_pong(mut self) -> Self {
97 self.mode = DelayMode::PingPong;
98 self
99 }
100
101 #[must_use]
103 pub fn with_damping(mut self, damping: f64) -> Self {
104 self.damping = damping.clamp(0.0, 1.0);
105 self
106 }
107}
108
109#[derive(Clone, Debug)]
111struct DelayLine {
112 buffer: VecDeque<f64>,
114 delay_samples: usize,
116 lp_state: f64,
118 damping: f64,
120}
121
122impl DelayLine {
123 fn new(delay_ms: f64, sample_rate: f64, damping: f64) -> Self {
125 let delay_samples = ((delay_ms * 0.001 * sample_rate) as usize).max(1);
126 let mut buffer = VecDeque::with_capacity(delay_samples + 1);
127
128 for _ in 0..delay_samples {
130 buffer.push_back(0.0);
131 }
132
133 Self {
134 buffer,
135 delay_samples,
136 lp_state: 0.0,
137 damping,
138 }
139 }
140
141 fn process(&mut self, input: f64, feedback: f64) -> f64 {
143 let output = self.buffer.pop_front().unwrap_or(0.0);
145
146 let damped = if self.damping > 0.0 {
148 self.lp_state = self.lp_state + self.damping * (output - self.lp_state);
149 output - self.damping * (output - self.lp_state)
150 } else {
151 output
152 };
153
154 self.buffer.push_back(input + damped * feedback);
156
157 damped
158 }
159
160 fn reset(&mut self) {
162 self.buffer.clear();
163 for _ in 0..self.delay_samples {
164 self.buffer.push_back(0.0);
165 }
166 self.lp_state = 0.0;
167 }
168
169 #[allow(dead_code)]
171 fn delay_samples(&self) -> usize {
172 self.delay_samples
173 }
174}
175
176struct DelayState {
178 delay_lines: Vec<DelayLine>,
180 ping_pong_state: bool,
182}
183
184impl DelayState {
185 fn new(config: &DelayConfig, sample_rate: f64, channels: usize) -> Self {
187 let delay_lines = (0..channels)
188 .map(|_| DelayLine::new(config.delay_ms, sample_rate, config.damping))
189 .collect();
190
191 Self {
192 delay_lines,
193 ping_pong_state: false,
194 }
195 }
196
197 fn process(&mut self, samples: &mut [Vec<f64>], config: &DelayConfig) {
199 let sample_count = samples.get(0).map_or(0, Vec::len);
200 let channels = samples.len();
201
202 for i in 0..sample_count {
203 match config.mode {
204 DelayMode::Normal => {
205 for ch in 0..channels {
207 if i < samples[ch].len() && ch < self.delay_lines.len() {
208 let dry = samples[ch][i];
209 let wet = self.delay_lines[ch].process(dry, config.feedback);
210 samples[ch][i] = dry * (1.0 - config.mix) + wet * config.mix;
211 }
212 }
213 }
214 DelayMode::PingPong => {
215 if channels >= 2 {
217 let (left_dry, right_dry) = if i < samples[0].len() && i < samples[1].len()
218 {
219 (samples[0][i], samples[1][i])
220 } else {
221 (0.0, 0.0)
222 };
223
224 let (left_wet, right_wet) = if self.ping_pong_state {
226 (
227 self.delay_lines[0].process(right_dry, config.feedback),
228 self.delay_lines[1].process(left_dry, config.feedback),
229 )
230 } else {
231 (
232 self.delay_lines[0].process(left_dry, config.feedback),
233 self.delay_lines[1].process(right_dry, config.feedback),
234 )
235 };
236
237 if i < samples[0].len() {
238 samples[0][i] = left_dry * (1.0 - config.mix) + left_wet * config.mix;
239 }
240 if i < samples[1].len() {
241 samples[1][i] = right_dry * (1.0 - config.mix) + right_wet * config.mix;
242 }
243
244 self.ping_pong_state = !self.ping_pong_state;
246 } else {
247 for ch in 0..channels {
249 if i < samples[ch].len() && ch < self.delay_lines.len() {
250 let dry = samples[ch][i];
251 let wet = self.delay_lines[ch].process(dry, config.feedback);
252 samples[ch][i] = dry * (1.0 - config.mix) + wet * config.mix;
253 }
254 }
255 }
256 }
257 }
258 }
259 }
260
261 fn reset(&mut self) {
263 for line in &mut self.delay_lines {
264 line.reset();
265 }
266 self.ping_pong_state = false;
267 }
268}
269
270pub struct DelayFilter {
288 id: NodeId,
289 name: String,
290 state: NodeState,
291 config: DelayConfig,
292 delay_state: Option<DelayState>,
293 inputs: Vec<InputPort>,
294 outputs: Vec<OutputPort>,
295}
296
297impl DelayFilter {
298 #[must_use]
300 pub fn new(id: NodeId, name: impl Into<String>, config: DelayConfig) -> Self {
301 let audio_format = PortFormat::Audio(AudioPortFormat::any());
302
303 Self {
304 id,
305 name: name.into(),
306 state: NodeState::Idle,
307 config,
308 delay_state: None,
309 inputs: vec![InputPort::new(PortId(0), "input", PortType::Audio)
310 .with_format(audio_format.clone())],
311 outputs: vec![
312 OutputPort::new(PortId(0), "output", PortType::Audio).with_format(audio_format)
313 ],
314 }
315 }
316
317 #[must_use]
319 pub fn config(&self) -> &DelayConfig {
320 &self.config
321 }
322
323 pub fn set_config(&mut self, config: DelayConfig) {
325 self.config = config;
326 self.delay_state = None; }
328
329 pub fn set_delay_time(&mut self, delay_ms: f64) {
331 self.config.delay_ms = delay_ms;
332 self.delay_state = None; }
334
335 pub fn set_feedback(&mut self, feedback: f64) {
337 self.config.feedback = feedback.clamp(0.0, 0.99);
338 }
339
340 pub fn set_mix(&mut self, mix: f64) {
342 self.config.mix = mix.clamp(0.0, 1.0);
343 }
344
345 fn frame_to_samples(frame: &AudioFrame) -> Vec<Vec<f64>> {
347 let channels = frame.channels.count();
348 let sample_count = frame.sample_count();
349
350 if sample_count == 0 {
351 return vec![Vec::new(); channels];
352 }
353
354 let mut output = vec![Vec::with_capacity(sample_count); channels];
355
356 match &frame.samples {
357 AudioBuffer::Interleaved(data) => {
358 Self::convert_interleaved(data, frame.format, channels, &mut output);
359 }
360 AudioBuffer::Planar(planes) => {
361 Self::convert_planar(planes, frame.format, &mut output);
362 }
363 }
364
365 output
366 }
367
368 fn convert_interleaved(
370 data: &Bytes,
371 format: SampleFormat,
372 channels: usize,
373 output: &mut [Vec<f64>],
374 ) {
375 let bytes_per_sample = format.bytes_per_sample();
376 if bytes_per_sample == 0 || channels == 0 {
377 return;
378 }
379
380 let sample_count = data.len() / (bytes_per_sample * channels);
381
382 for i in 0..sample_count {
383 for ch in 0..channels {
384 let offset = (i * channels + ch) * bytes_per_sample;
385 if offset + bytes_per_sample <= data.len() {
386 let sample =
387 Self::bytes_to_f64(&data[offset..offset + bytes_per_sample], format);
388 output[ch].push(sample);
389 }
390 }
391 }
392 }
393
394 fn convert_planar(planes: &[Bytes], format: SampleFormat, output: &mut [Vec<f64>]) {
396 let bytes_per_sample = format.bytes_per_sample();
397 if bytes_per_sample == 0 {
398 return;
399 }
400
401 for (ch, plane) in planes.iter().enumerate() {
402 if ch >= output.len() {
403 break;
404 }
405 let sample_count = plane.len() / bytes_per_sample;
406 for i in 0..sample_count {
407 let offset = i * bytes_per_sample;
408 if offset + bytes_per_sample <= plane.len() {
409 let sample =
410 Self::bytes_to_f64(&plane[offset..offset + bytes_per_sample], format);
411 output[ch].push(sample);
412 }
413 }
414 }
415 }
416
417 fn bytes_to_f64(bytes: &[u8], format: SampleFormat) -> f64 {
419 match format {
420 SampleFormat::U8 => {
421 if bytes.is_empty() {
422 return 0.0;
423 }
424 (f64::from(bytes[0]) - 128.0) / 128.0
425 }
426 SampleFormat::S16 => {
427 if bytes.len() < 2 {
428 return 0.0;
429 }
430 let sample = i16::from_le_bytes([bytes[0], bytes[1]]);
431 f64::from(sample) / f64::from(i16::MAX)
432 }
433 SampleFormat::S32 => {
434 if bytes.len() < 4 {
435 return 0.0;
436 }
437 let sample = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
438 f64::from(sample) / f64::from(i32::MAX)
439 }
440 SampleFormat::F32 => {
441 if bytes.len() < 4 {
442 return 0.0;
443 }
444 f64::from(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
445 }
446 SampleFormat::F64 => {
447 if bytes.len() < 8 {
448 return 0.0;
449 }
450 f64::from_le_bytes([
451 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
452 ])
453 }
454 _ => 0.0,
455 }
456 }
457
458 fn samples_to_frame(
460 samples: Vec<Vec<f64>>,
461 format: SampleFormat,
462 sample_rate: u32,
463 channels: ChannelLayout,
464 ) -> AudioFrame {
465 let channel_count = channels.count();
466 if samples.is_empty() || samples[0].is_empty() || channel_count == 0 {
467 return AudioFrame::new(format, sample_rate, channels);
468 }
469
470 let sample_count = samples[0].len();
471 let bytes_per_sample = format.bytes_per_sample();
472 let mut buffer = BytesMut::with_capacity(sample_count * channel_count * bytes_per_sample);
473
474 for i in 0..sample_count {
475 for ch in 0..channel_count {
476 let sample = if ch < samples.len() && i < samples[ch].len() {
477 samples[ch][i]
478 } else {
479 0.0
480 };
481 Self::f64_to_bytes(sample, format, &mut buffer);
482 }
483 }
484
485 let mut frame = AudioFrame::new(format, sample_rate, channels);
486 frame.samples = AudioBuffer::Interleaved(buffer.freeze());
487 frame
488 }
489
490 fn f64_to_bytes(sample: f64, format: SampleFormat, buffer: &mut BytesMut) {
492 let clamped = sample.clamp(-1.0, 1.0);
493
494 match format {
495 SampleFormat::U8 => {
496 let value = ((clamped * 128.0) + 128.0) as u8;
497 buffer.extend_from_slice(&[value]);
498 }
499 SampleFormat::S16 => {
500 let value = (clamped * f64::from(i16::MAX)) as i16;
501 buffer.extend_from_slice(&value.to_le_bytes());
502 }
503 SampleFormat::S32 => {
504 let value = (clamped * f64::from(i32::MAX)) as i32;
505 buffer.extend_from_slice(&value.to_le_bytes());
506 }
507 SampleFormat::F32 => {
508 #[allow(clippy::cast_possible_truncation)]
509 let value = clamped as f32;
510 buffer.extend_from_slice(&value.to_le_bytes());
511 }
512 SampleFormat::F64 => {
513 buffer.extend_from_slice(&clamped.to_le_bytes());
514 }
515 _ => {}
516 }
517 }
518}
519
520impl Node for DelayFilter {
521 fn id(&self) -> NodeId {
522 self.id
523 }
524
525 fn name(&self) -> &str {
526 &self.name
527 }
528
529 fn node_type(&self) -> NodeType {
530 NodeType::Filter
531 }
532
533 fn state(&self) -> NodeState {
534 self.state
535 }
536
537 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
538 if !self.state.can_transition_to(state) {
539 return Err(GraphError::InvalidStateTransition {
540 node: self.id,
541 from: self.state.to_string(),
542 to: state.to_string(),
543 });
544 }
545 self.state = state;
546 Ok(())
547 }
548
549 fn inputs(&self) -> &[InputPort] {
550 &self.inputs
551 }
552
553 fn outputs(&self) -> &[OutputPort] {
554 &self.outputs
555 }
556
557 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
558 let frame = match input {
559 Some(FilterFrame::Audio(frame)) => frame,
560 Some(_) => {
561 return Err(GraphError::PortTypeMismatch {
562 expected: "Audio".to_string(),
563 actual: "Video".to_string(),
564 });
565 }
566 None => return Ok(None),
567 };
568
569 if self.delay_state.is_none() {
571 let channels = frame.channels.count();
572 self.delay_state = Some(DelayState::new(
573 &self.config,
574 f64::from(frame.sample_rate),
575 channels,
576 ));
577 }
578
579 let mut samples = Self::frame_to_samples(&frame);
581
582 if let Some(ref mut delay_state) = self.delay_state {
584 delay_state.process(&mut samples, &self.config);
585 }
586
587 let output_frame = Self::samples_to_frame(
589 samples,
590 frame.format,
591 frame.sample_rate,
592 frame.channels.clone(),
593 );
594
595 Ok(Some(FilterFrame::Audio(output_frame)))
596 }
597
598 fn reset(&mut self) -> GraphResult<()> {
599 if let Some(ref mut state) = self.delay_state {
600 state.reset();
601 }
602 self.set_state(NodeState::Idle)
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use super::*;
609
610 #[test]
611 fn test_delay_mode_default() {
612 assert_eq!(DelayMode::default(), DelayMode::Normal);
613 }
614
615 #[test]
616 fn test_delay_config() {
617 let config = DelayConfig::new(500.0)
618 .with_feedback(0.7)
619 .with_mix(0.3)
620 .with_damping(0.2);
621
622 assert!((config.delay_ms - 500.0).abs() < f64::EPSILON);
623 assert!((config.feedback - 0.7).abs() < f64::EPSILON);
624 assert!((config.mix - 0.3).abs() < f64::EPSILON);
625 assert!((config.damping - 0.2).abs() < f64::EPSILON);
626 }
627
628 #[test]
629 fn test_feedback_clamping() {
630 let config = DelayConfig::new(250.0).with_feedback(1.5);
631 assert!((config.feedback - 0.99).abs() < f64::EPSILON);
632
633 let config = DelayConfig::new(250.0).with_feedback(-0.5);
634 assert!(config.feedback.abs() < f64::EPSILON);
635 }
636
637 #[test]
638 fn test_mix_clamping() {
639 let config = DelayConfig::new(250.0).with_mix(1.5);
640 assert!((config.mix - 1.0).abs() < f64::EPSILON);
641
642 let config = DelayConfig::new(250.0).with_mix(-0.5);
643 assert!(config.mix.abs() < f64::EPSILON);
644 }
645
646 #[test]
647 fn test_ping_pong_mode() {
648 let config = DelayConfig::new(250.0).ping_pong();
649 assert_eq!(config.mode, DelayMode::PingPong);
650 }
651
652 #[test]
653 fn test_delay_line() {
654 let mut line = DelayLine::new(10.0, 48000.0, 0.0);
655
656 let output = line.process(1.0, 0.0);
658 assert!(output.abs() < f64::EPSILON);
659
660 for _ in 0..500 {
662 line.process(0.0, 0.0);
663 }
664
665 line.reset();
667 let output = line.process(0.5, 0.0);
668 assert!(output.abs() < f64::EPSILON);
669 }
670
671 #[test]
672 fn test_delay_line_with_damping() {
673 let mut line = DelayLine::new(10.0, 48000.0, 0.5);
674
675 for _ in 0..100 {
677 let output = line.process(0.5, 0.5);
678 assert!(output.is_finite());
679 }
680 }
681
682 #[test]
683 fn test_delay_filter_creation() {
684 let config = DelayConfig::new(250.0);
685 let filter = DelayFilter::new(NodeId(1), "delay", config);
686
687 assert_eq!(filter.id(), NodeId(1));
688 assert_eq!(filter.name(), "delay");
689 assert_eq!(filter.node_type(), NodeType::Filter);
690 }
691
692 #[test]
693 fn test_delay_filter_ports() {
694 let config = DelayConfig::default();
695 let filter = DelayFilter::new(NodeId(0), "test", config);
696
697 assert_eq!(filter.inputs().len(), 1);
698 assert_eq!(filter.outputs().len(), 1);
699 assert_eq!(filter.inputs()[0].port_type, PortType::Audio);
700 }
701
702 #[test]
703 fn test_set_parameters() {
704 let config = DelayConfig::new(250.0);
705 let mut filter = DelayFilter::new(NodeId(0), "test", config);
706
707 filter.set_delay_time(500.0);
708 assert!((filter.config().delay_ms - 500.0).abs() < f64::EPSILON);
709
710 filter.set_feedback(0.8);
711 assert!((filter.config().feedback - 0.8).abs() < f64::EPSILON);
712
713 filter.set_mix(0.7);
714 assert!((filter.config().mix - 0.7).abs() < f64::EPSILON);
715 }
716
717 #[test]
718 fn test_process_none() {
719 let config = DelayConfig::default();
720 let mut filter = DelayFilter::new(NodeId(0), "test", config);
721
722 let result = filter.process(None).expect("process should succeed");
723 assert!(result.is_none());
724 }
725
726 #[test]
727 fn test_process_audio() {
728 let config = DelayConfig::new(10.0).with_feedback(0.5).with_mix(0.5);
729 let mut filter = DelayFilter::new(NodeId(0), "test", config);
730
731 let mut frame = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Stereo);
732 let mut samples = BytesMut::new();
733 for _ in 0..200 {
734 samples.extend_from_slice(&0.5f32.to_le_bytes()); samples.extend_from_slice(&0.5f32.to_le_bytes()); }
737 frame.samples = AudioBuffer::Interleaved(samples.freeze());
738
739 let result = filter
740 .process(Some(FilterFrame::Audio(frame)))
741 .expect("process should succeed");
742 assert!(result.is_some());
743 }
744
745 #[test]
746 fn test_state_transitions() {
747 let config = DelayConfig::default();
748 let mut filter = DelayFilter::new(NodeId(0), "test", config);
749
750 assert!(filter.set_state(NodeState::Processing).is_ok());
751 assert_eq!(filter.state(), NodeState::Processing);
752
753 assert!(filter.reset().is_ok());
754 assert_eq!(filter.state(), NodeState::Idle);
755 }
756
757 #[test]
758 fn test_delay_state_reset() {
759 let config = DelayConfig::new(250.0);
760 let mut state = DelayState::new(&config, 48000.0, 2);
761
762 let mut samples = vec![vec![0.5; 100], vec![0.5; 100]];
764 state.process(&mut samples, &config);
765
766 state.reset();
768
769 assert!(!state.ping_pong_state);
771 }
772}