Trait synfx_dsp_jit::DSPNodeType
source · [−]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
sourcefn name(&self) -> &str
fn name(&self) -> &str
The name of this DSP node, by this name it can be called from the crate::ast::ASTFun.
sourcefn function_ptr(&self) -> *const u8
fn function_ptr(&self) -> *const u8
The function pointer that should be inserted.
sourcefn has_return_value(&self) -> bool
fn has_return_value(&self) -> bool
Should return true if the function for DSPNodeType::function_ptr returns something.
Provided Methods
sourcefn documentation(&self) -> &str
fn documentation(&self) -> &str
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.
sourcefn input_names(&self, _index: usize) -> Option<&str>
fn input_names(&self, _index: usize) -> Option<&str>
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.
sourcefn output_names(&self, _index: usize) -> Option<&str>
fn output_names(&self, _index: usize) -> Option<&str>
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.
sourcefn input_index_by_name(&self, name: &str) -> Option<usize>
fn input_index_by_name(&self, name: &str) -> Option<usize>
Returns the index of the output by it’s name.
sourcefn output_index_by_name(&self, name: &str) -> Option<usize>
fn output_index_by_name(&self, name: &str) -> Option<usize>
Returns the index of the output by it’s name.
sourcefn input_count(&self) -> usize
fn input_count(&self) -> usize
Number of input ports
sourcefn output_count(&self) -> usize
fn output_count(&self) -> usize
Number of output ports
sourcefn is_stateful(&self) -> bool
fn is_stateful(&self) -> bool
Returns true if this node type requires state.
sourcefn signature(&self, _i: usize) -> Option<DSPNodeSigBit>
fn signature(&self, _i: usize) -> Option<DSPNodeSigBit>
Should return the signature type for input parameter i
.
sourcefn reset_state(&self, _dsp_state: *mut DSPState, _state_ptr: *mut u8)
fn reset_state(&self, _dsp_state: *mut DSPState, _state_ptr: *mut u8)
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.
sourcefn allocate_state(&self) -> Option<*mut u8>
fn allocate_state(&self) -> Option<*mut u8>
Allocates a new piece of state for this DSPNodeType. Must be deallocated using DSPNodeType::deallocate_state.
sourcefn deallocate_state(&self, _ptr: *mut u8)
fn deallocate_state(&self, _ptr: *mut u8)
Deallocates the private state of this DSPNodeType.