Lightningbeam/daw-backend/src/audio/node_graph/nodes/math.rs

165 lines
4.7 KiB
Rust

use crate::audio::node_graph::{AudioNode, NodeCategory, NodePort, Parameter, ParameterUnit, SignalType};
use crate::audio::midi::MidiEvent;
const PARAM_OPERATION: u32 = 0;
/// Mathematical and logical operations on CV signals
/// Operations:
/// 0 = Add, 1 = Subtract, 2 = Multiply, 3 = Divide
/// 4 = Min, 5 = Max, 6 = Average
/// 7 = Invert (1.0 - x), 8 = Absolute Value
/// 9 = Clamp (0.0 to 1.0), 10 = Wrap (-1.0 to 1.0)
/// 11 = Greater Than, 12 = Less Than, 13 = Equal (with tolerance)
pub struct MathNode {
name: String,
operation: u32,
inputs: Vec<NodePort>,
outputs: Vec<NodePort>,
parameters: Vec<Parameter>,
}
impl MathNode {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
let inputs = vec![
NodePort::new("CV In A", SignalType::CV, 0),
NodePort::new("CV In B", SignalType::CV, 1),
];
let outputs = vec![
NodePort::new("CV Out", SignalType::CV, 0),
];
let parameters = vec![
Parameter::new(PARAM_OPERATION, "Operation", 0.0, 13.0, 0.0, ParameterUnit::Generic),
];
Self {
name,
operation: 0,
inputs,
outputs,
parameters,
}
}
fn apply_operation(&self, a: f32, b: f32) -> f32 {
match self.operation {
0 => a + b, // Add
1 => a - b, // Subtract
2 => a * b, // Multiply
3 => if b.abs() > 0.0001 { a / b } else { 0.0 }, // Divide (with protection)
4 => a.min(b), // Min
5 => a.max(b), // Max
6 => (a + b) * 0.5, // Average
7 => 1.0 - a, // Invert (ignores b)
8 => a.abs(), // Absolute Value (ignores b)
9 => a.clamp(0.0, 1.0), // Clamp to 0-1 (ignores b)
10 => { // Wrap -1 to 1
let mut result = a;
while result > 1.0 {
result -= 2.0;
}
while result < -1.0 {
result += 2.0;
}
result
},
11 => if a > b { 1.0 } else { 0.0 }, // Greater Than
12 => if a < b { 1.0 } else { 0.0 }, // Less Than
13 => if (a - b).abs() < 0.01 { 1.0 } else { 0.0 }, // Equal (with tolerance)
_ => a, // Unknown operation - pass through
}
}
}
impl AudioNode for MathNode {
fn category(&self) -> NodeCategory {
NodeCategory::Utility
}
fn inputs(&self) -> &[NodePort] {
&self.inputs
}
fn outputs(&self) -> &[NodePort] {
&self.outputs
}
fn parameters(&self) -> &[Parameter] {
&self.parameters
}
fn set_parameter(&mut self, id: u32, value: f32) {
match id {
PARAM_OPERATION => self.operation = (value as u32).clamp(0, 13),
_ => {}
}
}
fn get_parameter(&self, id: u32) -> f32 {
match id {
PARAM_OPERATION => self.operation as f32,
_ => 0.0,
}
}
fn process(
&mut self,
inputs: &[&[f32]],
outputs: &mut [&mut [f32]],
_midi_inputs: &[&[MidiEvent]],
_midi_outputs: &mut [&mut Vec<MidiEvent>],
_sample_rate: u32,
) {
if outputs.is_empty() {
return;
}
let output = &mut outputs[0];
let length = output.len();
// Process each sample
for i in 0..length {
// Get input A (or 0.0 if not connected)
let a = if !inputs.is_empty() && i < inputs[0].len() {
inputs[0][i]
} else {
0.0
};
// Get input B (or 0.0 if not connected)
let b = if inputs.len() > 1 && i < inputs[1].len() {
inputs[1][i]
} else {
0.0
};
output[i] = self.apply_operation(a, b);
}
}
fn reset(&mut self) {
// No state to reset
}
fn node_type(&self) -> &str {
"Math"
}
fn name(&self) -> &str {
&self.name
}
fn clone_node(&self) -> Box<dyn AudioNode> {
Box::new(Self {
name: self.name.clone(),
operation: self.operation,
inputs: self.inputs.clone(),
outputs: self.outputs.clone(),
parameters: self.parameters.clone(),
})
}
}