pub trait DSPNodeType: Sync + Send {
Show 15 methods fn name(&self) -> &str; fn function_ptr(&self) -> *const u8; fn has_return_value(&self) -> bool; fn documentation(&self) -> &str { ... } fn input_names(&self, _index: usize) -> Option<&str> { ... } fn output_names(&self, _index: usize) -> Option<&str> { ... } fn input_index_by_name(&self, name: &str) -> Option<usize> { ... } fn output_index_by_name(&self, name: &str) -> Option<usize> { ... } fn input_count(&self) -> usize { ... } fn output_count(&self) -> usize { ... } fn is_stateful(&self) -> bool { ... } fn signature(&self, _i: usize) -> Option<DSPNodeSigBit> { ... } fn reset_state(&self, _dsp_state: *mut DSPState, _state_ptr: *mut u8) { ... } fn allocate_state(&self) -> Option<*mut u8> { ... } fn deallocate_state(&self, _ptr: *mut u8) { ... }
}
Expand description

This trait allows you to define your own DSP stateful and stateless primitives. Among defining a few important properties for the compiler, it handles allocation and deallocation of the state that belongs to a DSPNodeType.

Stateless DSP Nodes/Primitives

Here is a simple example how to define a stateless DSP function:

 use std::rc::Rc;
 use std::cell::RefCell;
 use synfx_dsp_jit::{DSPNodeType, DSPNodeSigBit, DSPNodeTypeLibrary};

 let lib = Rc::new(RefCell::new(DSPNodeTypeLibrary::new()));

 pub struct MyPrimitive;

 extern "C" fn my_primitive_function(a: f64, b: f64) -> f64 {
    (2.0 * a * b.cos()).sin()
 }

 impl DSPNodeType for MyPrimitive {
     // make a name, so you can refer to it via `ASTNode::Call("my_prim", ...)`.
     fn name(&self) -> &str { "my_prim" }

     // Provide a pointer:
     fn function_ptr(&self) -> *const u8 { my_primitive_function as *const u8 }

     // Define the function signature for the JIT compiler:
     fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
         match i {
             0 | 1 => Some(DSPNodeSigBit::Value),
             _ => None, // Return None to signal we only take 2 parameters
         }
     }

     // Tell the JIT compiler that you return a value:
     fn has_return_value(&self) -> bool { true }

     // The other trait functions do not need to be provided, because this is
     // a stateless primitive.
 }

 lib.borrow_mut().add(std::sync::Arc::new(MyPrimitive {}));

 use synfx_dsp_jit::{ASTFun, JIT, DSPNodeContext};
 let ctx = DSPNodeContext::new_ref();
 let jit = JIT::new(lib.clone(), ctx.clone());

 use synfx_dsp_jit::build::*;
 let mut fun = jit.compile(ASTFun::new(
     op_add(call("my_prim", 0, &[var("in1"), var("in2")]), literal(10.0))))
     .expect("no compile error");

 fun.init(44100.0, None);

 let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 1.5);

 assert!((ret - 10.1410029).abs() < 0.000001);

 ctx.borrow_mut().free();

Stateful DSP Nodes/Primitives

Here is a simple example how to define a stateful DSP function, in this example just an accumulator.

There is a little helper macro that might help you: crate::stateful_dsp_node_type

 use std::rc::Rc;
 use std::cell::RefCell;
 use synfx_dsp_jit::{DSPNodeType, DSPState, DSPNodeSigBit, DSPNodeTypeLibrary};

 let lib = Rc::new(RefCell::new(DSPNodeTypeLibrary::new()));

 pub struct MyPrimitive;

 struct MyPrimAccumulator {
     count: f64,
 }

 // Be careful defining the signature of this primitive, there is no safety net here!
 // Check twice with DSPNodeType::signature()!
 extern "C" fn my_primitive_accum(add: f64, state: *mut u8) -> f64 {
     let state = unsafe { &mut *(state as *mut MyPrimAccumulator) };
     state.count += add;
     state.count
 }

 impl DSPNodeType for MyPrimitive {
     // make a name, so you can refer to it via `ASTNode::Call("my_prim", ...)`.
     fn name(&self) -> &str { "accum" }

     // Provide a pointer:
     fn function_ptr(&self) -> *const u8 { my_primitive_accum as *const u8 }

     // Define the function signature for the JIT compiler. Be really careful though,
     // There is no safety net here.
     fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
         match i {
             0 => Some(DSPNodeSigBit::Value),
             1 => Some(DSPNodeSigBit::NodeStatePtr),
             _ => None, // Return None to signal we only take 1 parameter
         }
     }

     // Tell the JIT compiler that you return a value:
     fn has_return_value(&self) -> bool { true }

     // Specify how to reset the state:
     fn reset_state(&self, _dsp_state: *mut DSPState, state_ptr: *mut u8) {
         unsafe { (*(state_ptr as *mut MyPrimAccumulator)).count = 0.0 };
     }

     // Allocate our state:
     fn allocate_state(&self) -> Option<*mut u8> {
         Some(Box::into_raw(Box::new(MyPrimAccumulator { count: 0.0 })) as *mut u8)
     }

     // Deallocate our state:
     fn deallocate_state(&self, ptr: *mut u8) {
         unsafe { Box::from_raw(ptr as *mut MyPrimAccumulator) };
     }
 }

 lib.borrow_mut().add(std::sync::Arc::new(MyPrimitive {}));

 use synfx_dsp_jit::{ASTFun, JIT, DSPNodeContext};
 let ctx = DSPNodeContext::new_ref();
 let jit = JIT::new(lib.clone(), ctx.clone());

 use synfx_dsp_jit::build::*;
 let mut fun =
     jit.compile(ASTFun::new(call("accum", 0, &[var("in1")]))).expect("no compile error");

 fun.init(44100.0, None);

 let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
 assert!((ret - 1.0).abs() < 0.000001);

 let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
 assert!((ret - 2.0).abs() < 0.000001);

 let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
 assert!((ret - 3.0).abs() < 0.000001);

 // You can cause a reset eg. with fun.set_sample_rate() or fun.reset():
 fun.reset();

 // Counting will restart:
 let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
 assert!((ret - 1.0).abs() < 0.000001);

 ctx.borrow_mut().free();

Required Methods

The name of this DSP node, by this name it can be called from the crate::ast::ASTFun.

The function pointer that should be inserted.

Should return true if the function for DSPNodeType::function_ptr returns something.

Provided Methods

Document what this node does and how to use it. Format should be in Markdown.

Documenting the node will make it easier for library implementors and even eventual end users to figure out what this node does and how to use it.

For instance, this text should define what the input and output parameters do. And also define which value ranges these operate in.

Returns the name of each input port of this node. Choose descriptive but short names. These names will be used by compiler frontends to identify the ports, and it will make it easier to stay compatible if indices change.

Returns the name of each output port of this node. Choose descriptive but short names. These names will be used by compiler frontends to identify the ports, and it will make it easier to stay compatible if indices change.

Returns the index of the output by it’s name.

Returns the index of the output by it’s name.

Number of input ports

Number of output ports

Returns true if this node type requires state.

Should return the signature type for input parameter i.

Will be called when the node state should be resetted. This should be used to store the sample rate for instance or do other sample rate dependent recomputations. Also things delay lines should zero their buffers.

Allocates a new piece of state for this DSPNodeType. Must be deallocated using DSPNodeType::deallocate_state.

Deallocates the private state of this DSPNodeType.

Implementors