use std::{marker::PhantomData, mem::MaybeUninit, sync::Arc};
use maudio_sys::ffi as sys;
use crate::{
audio::sample_rate::SampleRate,
engine::{
node_graph::{
nodes::{private_node, AsNodePtr, NodeRef},
AsNodeGraphPtr, NodeGraph,
},
AllocationCallbacks,
},
AsRawRef, Binding, MaResult,
};
pub struct DelayNode<'a> {
inner: *mut sys::ma_delay_node,
alloc_cb: Option<Arc<AllocationCallbacks>>,
_marker: PhantomData<&'a NodeGraph>,
}
impl Binding for DelayNode<'_> {
type Raw = *mut sys::ma_delay_node;
fn from_ptr(_raw: Self::Raw) -> Self {
unimplemented!()
}
fn to_raw(&self) -> Self::Raw {
self.inner
}
}
#[doc(hidden)]
impl AsNodePtr for DelayNode<'_> {
type __PtrProvider = private_node::DelayNodeProvider;
}
impl<'a> DelayNode<'a> {
pub fn wet(&self) -> f32 {
n_delay_ffi::ma_delay_node_get_wet(self)
}
pub fn set_wet(&mut self, wet: f32) {
n_delay_ffi::ma_delay_node_set_wet(self, wet);
}
pub fn dry(&self) -> f32 {
n_delay_ffi::ma_delay_node_get_dry(self)
}
pub fn set_dry(&mut self, dry: f32) {
n_delay_ffi::ma_delay_node_set_dry(self, dry);
}
pub fn decay_frames(&self) -> f32 {
n_delay_ffi::ma_delay_node_get_decay(self)
}
pub fn set_decay_frames(&mut self, decay: f32) {
n_delay_ffi::ma_delay_node_set_decay(self, decay);
}
pub fn as_node(&self) -> NodeRef<'a> {
assert!(!self.to_raw().is_null());
let ptr = self.to_raw().cast::<sys::ma_node>();
NodeRef::from_ptr(ptr)
}
fn new_with_cfg_alloc_internal<N: AsNodeGraphPtr + ?Sized>(
node_graph: &N,
config: &DelayNodeBuilder<N>,
alloc: Option<Arc<AllocationCallbacks>>,
) -> MaResult<Self> {
let alloc_cb: *const sys::ma_allocation_callbacks =
alloc.clone().map_or(core::ptr::null(), |c| c.as_raw_ptr());
let mut mem: Box<std::mem::MaybeUninit<sys::ma_delay_node>> =
Box::new(MaybeUninit::uninit());
n_delay_ffi::ma_delay_node_init(
node_graph,
config.as_raw_ptr(),
alloc_cb,
mem.as_mut_ptr(),
)?;
let inner: *mut sys::ma_delay_node = Box::into_raw(mem) as *mut sys::ma_delay_node;
Ok(Self {
inner,
alloc_cb: alloc,
_marker: PhantomData,
})
}
#[inline]
fn alloc_cb_ptr(&self) -> *const sys::ma_allocation_callbacks {
match &self.alloc_cb {
Some(cb) => cb.as_raw_ptr(),
None => core::ptr::null(),
}
}
}
pub(crate) mod n_delay_ffi {
use crate::{
engine::node_graph::{
nodes::effects::delay::DelayNode, private_node_graph, AsNodeGraphPtr,
},
Binding, MaResult, MaudioError,
};
use maudio_sys::ffi as sys;
#[inline]
pub fn ma_delay_node_init<N: AsNodeGraphPtr + ?Sized>(
node_graph: &N,
config: *const sys::ma_delay_node_config,
alloc_cb: *const sys::ma_allocation_callbacks,
node: *mut sys::ma_delay_node,
) -> MaResult<()> {
let res = unsafe {
sys::ma_delay_node_init(
private_node_graph::node_graph_ptr(node_graph),
config,
alloc_cb,
node,
)
};
MaudioError::check(res)
}
#[inline]
pub fn ma_delay_node_uninit(node: &mut DelayNode) {
unsafe { sys::ma_delay_node_uninit(node.to_raw(), node.alloc_cb_ptr()) }
}
#[inline]
pub fn ma_delay_node_set_wet(node: &mut DelayNode, wet: f32) {
unsafe {
sys::ma_delay_node_set_wet(node.to_raw(), wet);
}
}
pub fn ma_delay_node_get_wet(node: &DelayNode) -> f32 {
unsafe { sys::ma_delay_node_get_wet(node.to_raw() as *const _) }
}
pub fn ma_delay_node_set_dry(node: &mut DelayNode, dry: f32) {
unsafe {
sys::ma_delay_node_set_dry(node.to_raw(), dry);
}
}
pub fn ma_delay_node_get_dry(node: &DelayNode) -> f32 {
unsafe { sys::ma_delay_node_get_dry(node.to_raw() as *const _) }
}
pub fn ma_delay_node_set_decay(node: &mut DelayNode, decay: f32) {
unsafe {
sys::ma_delay_node_set_decay(node.to_raw(), decay);
}
}
pub fn ma_delay_node_get_decay(node: &DelayNode) -> f32 {
unsafe { sys::ma_delay_node_get_decay(node.to_raw() as *const _) }
}
}
impl Drop for DelayNode<'_> {
fn drop(&mut self) {
n_delay_ffi::ma_delay_node_uninit(self);
drop(unsafe { Box::from_raw(self.to_raw()) });
}
}
pub struct DelayNodeBuilder<'a, N: AsNodeGraphPtr + ?Sized> {
inner: sys::ma_delay_node_config,
node_graph: &'a N,
}
impl<N: AsNodeGraphPtr + ?Sized> AsRawRef for DelayNodeBuilder<'_, N> {
type Raw = sys::ma_delay_node_config;
fn as_raw(&self) -> &Self::Raw {
&self.inner
}
}
impl<'a, N: AsNodeGraphPtr + ?Sized> DelayNodeBuilder<'a, N> {
pub fn new(
node_graph: &'a N,
channels: u32,
sample_rate: SampleRate,
delay_frames: u32,
decay: f32,
) -> Self {
let inner = unsafe {
sys::ma_delay_node_config_init(channels, sample_rate.into(), delay_frames, decay)
};
Self { inner, node_graph }
}
pub fn wet(&mut self, wet: f32) -> &mut Self {
self.inner.delay.wet = wet;
self
}
pub fn dry(&mut self, dry: f32) -> &mut Self {
self.inner.delay.dry = dry;
self
}
pub fn mix(&mut self, mix: f32) -> &mut Self {
let mix = mix.clamp(0.0, 1.0);
self.inner.delay.wet = mix;
self.inner.delay.dry = 1.0 - mix;
self
}
pub fn decay(&mut self, decay: f32) -> &mut Self {
self.inner.delay.decay = decay;
self
}
pub fn delay_start(&mut self, yes: bool) -> &mut Self {
let delay_start = yes as u32;
self.inner.delay.delayStart = delay_start;
self
}
pub fn start_frame(&mut self, frame: u32) -> &mut Self {
self.inner.delay.delayInFrames = frame;
self
}
pub fn delay_milli(&mut self, millis: u32) -> &mut Self {
self.inner.delay.delayInFrames = self.millis_to_frames(millis);
self
}
pub fn start_milli(&mut self, millis: u32) -> &mut Self {
self.inner.delay.delayInFrames = self.millis_to_frames(millis);
self
}
pub fn build(&self) -> MaResult<DelayNode<'a>> {
if self.inner.delay.channels == 0 {
return Err(crate::MaudioError::from_ma_result(
sys::ma_result_MA_INVALID_ARGS,
));
}
DelayNode::new_with_cfg_alloc_internal(self.node_graph, self, None)
}
#[inline]
fn millis_to_frames(&self, millis: u32) -> u32 {
let sr = self.inner.delay.sampleRate;
(millis * sr + 500) / 1000
}
}
#[cfg(test)]
mod test {
use crate::engine::{node_graph::nodes::private_node, Engine, EngineOps};
use super::*;
fn assert_approx_eq(a: f32, b: f32, eps: f32) {
assert!(
(a - b).abs() <= eps,
"expected {a} ≈ {b} (eps={eps}), diff={}",
(a - b).abs()
);
}
#[test]
fn test_delay_node_test_basic_init() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0)
.build()
.unwrap();
let _ = delay.wet();
let _ = delay.dry();
let _ = delay.decay_frames();
let _ = delay.as_node();
}
#[test]
fn test_delay_node_test_set_get_wet_roundtrip() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0)
.build()
.unwrap();
delay.set_wet(0.25);
assert_approx_eq(delay.wet(), 0.25, 1e-6);
delay.set_wet(1.5);
assert_approx_eq(delay.wet(), 1.5, 1e-6);
}
#[test]
fn test_delay_node_test_set_get_dry_roundtrip() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0)
.build()
.unwrap();
delay.set_dry(0.75);
assert_approx_eq(delay.dry(), 0.75, 1e-6);
delay.set_dry(-0.5);
assert_approx_eq(delay.dry(), -0.5, 1e-6);
}
#[test]
fn test_delay_node_test_set_get_decay_roundtrip() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0)
.build()
.unwrap();
delay.set_decay_frames(0.0);
assert_approx_eq(delay.decay_frames(), 0.0, 1e-6);
delay.set_decay_frames(0.4);
assert_approx_eq(delay.decay_frames(), 0.4, 1e-6);
delay.set_decay_frames(1.1);
assert_approx_eq(delay.decay_frames(), 1.1, 1e-6);
}
#[test]
fn test_delay_node_test_as_node_is_non_null() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0)
.build()
.unwrap();
let node_ref = delay.as_node();
assert!(!private_node::node_ptr(&node_ref).is_null());
let _ = node_ref;
}
#[test]
fn test_delay_node_test_mix_clamps_and_overwrites_wet_dry() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut b = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0);
b.wet(0.123).dry(0.456);
b.mix(-1.0);
assert_approx_eq(b.as_raw().delay.wet, 0.0, 1e-6);
assert_approx_eq(b.as_raw().delay.dry, 1.0, 1e-6);
b.mix(2.0);
assert_approx_eq(b.as_raw().delay.wet, 1.0, 1e-6);
assert_approx_eq(b.as_raw().delay.dry, 0.0, 1e-6);
b.mix(0.25);
assert_approx_eq(b.as_raw().delay.wet, 0.25, 1e-6);
assert_approx_eq(b.as_raw().delay.dry, 0.75, 1e-6);
}
#[test]
fn test_delay_node_test_delay_milli_rounding() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut b = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0);
b.delay_milli(1);
assert_eq!(b.as_raw().delay.delayInFrames, 48);
let mut b = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr44100, 0, 0.0);
b.delay_milli(1);
assert_eq!(b.as_raw().delay.delayInFrames, 44);
b.delay_milli(2);
assert_eq!(b.as_raw().delay.delayInFrames, 88);
b.delay_milli(3);
assert_eq!(b.as_raw().delay.delayInFrames, 132);
}
#[test]
fn test_delay_node_test_start_milli_sets_start_frame_not_flag() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut b = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0);
b.start_milli(10);
assert_eq!(b.as_raw().delay.delayStart, 1);
assert_eq!(b.as_raw().delay.delayInFrames, 480);
}
#[test]
fn test_delay_node_test_create_drop_many_times() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
for _ in 0..1_000 {
let _delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0)
.wet(0.5)
.dry(0.5)
.build()
.unwrap();
}
}
#[test]
fn test_delay_node_test_set_get_stress() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let mut delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 10, 0.25)
.build()
.unwrap();
for i in 0..10_000 {
let w = (i as f32) * 0.0001;
let d = 1.0 - w;
let dec = (i as f32) * 0.00001;
delay.set_wet(w);
delay.set_dry(d);
delay.set_decay_frames(dec);
let _ = delay.wet();
let _ = delay.dry();
let _ = delay.decay_frames();
}
}
#[test]
fn test_delay_node_test_as_node_pointer_stable() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0)
.build()
.unwrap();
let a = private_node::node_ptr(&delay.as_node());
let b = private_node::node_ptr(&delay.as_node());
assert_eq!(a, b);
assert!(!a.is_null());
}
#[test]
fn test_delay_node_test_large_delay_frames_no_ub() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let res =
DelayNodeBuilder::new(&node_graph, 2, SampleRate::Sr48000, 48_000 * 2, 0.5).build();
let _ = res.ok();
}
#[test]
fn test_delay_node_test_drop_before_engine_is_safe() {
let engine = Engine::new_for_tests().unwrap();
let node_graph = engine.as_node_graph().unwrap();
let delay = DelayNodeBuilder::new(&node_graph, 1, SampleRate::Sr48000, 0, 0.0)
.build()
.unwrap();
drop(delay);
drop(engine);
}
}