Add parameters to audio nodes and rename Delay node to Echo
This commit is contained in:
parent
6fcee92d59
commit
1e7001b291
|
|
@ -501,6 +501,8 @@ impl Engine {
|
||||||
.store(self.playhead, Ordering::Relaxed);
|
.store(self.playhead, Ordering::Relaxed);
|
||||||
// Stop all MIDI notes when seeking to prevent stuck notes
|
// Stop all MIDI notes when seeking to prevent stuck notes
|
||||||
self.project.stop_all_notes();
|
self.project.stop_all_notes();
|
||||||
|
// Reset all node graphs to clear effect buffers (echo, reverb, etc.)
|
||||||
|
self.project.reset_all_graphs();
|
||||||
// Notify disk reader to refill buffers from new position
|
// Notify disk reader to refill buffers from new position
|
||||||
if let Some(ref mut dr) = self.disk_reader {
|
if let Some(ref mut dr) = self.disk_reader {
|
||||||
dr.send(crate::audio::disk_reader::DiskReaderCommand::Seek { frame: frames });
|
dr.send(crate::audio::disk_reader::DiskReaderCommand::Seek { frame: frames });
|
||||||
|
|
@ -1080,7 +1082,7 @@ impl Engine {
|
||||||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||||
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
||||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
|
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
|
||||||
"Delay" => Box::new(DelayNode::new("Delay".to_string())),
|
"Echo" | "Delay" => Box::new(EchoNode::new("Echo".to_string())),
|
||||||
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
|
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
|
||||||
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
||||||
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
||||||
|
|
@ -1165,7 +1167,7 @@ impl Engine {
|
||||||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||||
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
||||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
|
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
|
||||||
"Delay" => Box::new(DelayNode::new("Delay".to_string())),
|
"Echo" | "Delay" => Box::new(EchoNode::new("Echo".to_string())),
|
||||||
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
|
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
|
||||||
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
||||||
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
||||||
|
|
|
||||||
|
|
@ -865,7 +865,7 @@ impl AudioGraph {
|
||||||
"Splitter" => Box::new(SplitterNode::new("Splitter")),
|
"Splitter" => Box::new(SplitterNode::new("Splitter")),
|
||||||
"Pan" => Box::new(PanNode::new("Pan")),
|
"Pan" => Box::new(PanNode::new("Pan")),
|
||||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer")),
|
"Quantizer" => Box::new(QuantizerNode::new("Quantizer")),
|
||||||
"Delay" => Box::new(DelayNode::new("Delay")),
|
"Echo" | "Delay" => Box::new(EchoNode::new("Echo")),
|
||||||
"Distortion" => Box::new(DistortionNode::new("Distortion")),
|
"Distortion" => Box::new(DistortionNode::new("Distortion")),
|
||||||
"Reverb" => Box::new(ReverbNode::new("Reverb")),
|
"Reverb" => Box::new(ReverbNode::new("Reverb")),
|
||||||
"Chorus" => Box::new(ChorusNode::new("Chorus")),
|
"Chorus" => Box::new(ChorusNode::new("Chorus")),
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ const PARAM_WET_DRY: u32 = 2;
|
||||||
|
|
||||||
const MAX_DELAY_SECONDS: f32 = 2.0;
|
const MAX_DELAY_SECONDS: f32 = 2.0;
|
||||||
|
|
||||||
/// Stereo delay node with feedback
|
/// Stereo echo node with feedback
|
||||||
pub struct DelayNode {
|
pub struct EchoNode {
|
||||||
name: String,
|
name: String,
|
||||||
delay_time: f32, // seconds
|
delay_time: f32, // seconds
|
||||||
feedback: f32, // 0.0 to 0.95
|
feedback: f32, // 0.0 to 0.95
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct DelayNode {
|
||||||
parameters: Vec<Parameter>,
|
parameters: Vec<Parameter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DelayNode {
|
impl EchoNode {
|
||||||
pub fn new(name: impl Into<String>) -> Self {
|
pub fn new(name: impl Into<String>) -> Self {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl DelayNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioNode for DelayNode {
|
impl AudioNode for EchoNode {
|
||||||
fn category(&self) -> NodeCategory {
|
fn category(&self) -> NodeCategory {
|
||||||
NodeCategory::Effect
|
NodeCategory::Effect
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +185,7 @@ impl AudioNode for DelayNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_type(&self) -> &str {
|
fn node_type(&self) -> &str {
|
||||||
"Delay"
|
"Echo"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
|
|
@ -7,7 +7,7 @@ mod bpm_detector;
|
||||||
mod chorus;
|
mod chorus;
|
||||||
mod compressor;
|
mod compressor;
|
||||||
mod constant;
|
mod constant;
|
||||||
mod delay;
|
mod echo;
|
||||||
mod distortion;
|
mod distortion;
|
||||||
mod envelope_follower;
|
mod envelope_follower;
|
||||||
mod eq;
|
mod eq;
|
||||||
|
|
@ -49,7 +49,7 @@ pub use bpm_detector::BpmDetectorNode;
|
||||||
pub use chorus::ChorusNode;
|
pub use chorus::ChorusNode;
|
||||||
pub use compressor::CompressorNode;
|
pub use compressor::CompressorNode;
|
||||||
pub use constant::ConstantNode;
|
pub use constant::ConstantNode;
|
||||||
pub use delay::DelayNode;
|
pub use echo::EchoNode;
|
||||||
pub use distortion::DistortionNode;
|
pub use distortion::DistortionNode;
|
||||||
pub use envelope_follower::EnvelopeFollowerNode;
|
pub use envelope_follower::EnvelopeFollowerNode;
|
||||||
pub use eq::EQNode;
|
pub use eq::EQNode;
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,17 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset all node graphs (clears effect buffers on seek)
|
||||||
|
pub fn reset_all_graphs(&mut self) {
|
||||||
|
for track in self.tracks.values_mut() {
|
||||||
|
match track {
|
||||||
|
TrackNode::Audio(t) => t.effects_graph.reset(),
|
||||||
|
TrackNode::Midi(t) => t.instrument_graph.reset(),
|
||||||
|
TrackNode::Group(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Process live MIDI input from all MIDI tracks (called even when not playing)
|
/// Process live MIDI input from all MIDI tracks (called even when not playing)
|
||||||
pub fn process_live_midi(&mut self, output: &mut [f32], sample_rate: u32, channels: u32) {
|
pub fn process_live_midi(&mut self, output: &mut [f32], sample_rate: u32, channels: u32) {
|
||||||
// Process all MIDI tracks to handle queued live input events
|
// Process all MIDI tracks to handle queued live input events
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ pub enum NodeTemplate {
|
||||||
// Effects
|
// Effects
|
||||||
Filter,
|
Filter,
|
||||||
Gain,
|
Gain,
|
||||||
Delay,
|
Echo,
|
||||||
Reverb,
|
Reverb,
|
||||||
Chorus,
|
Chorus,
|
||||||
Flanger,
|
Flanger,
|
||||||
|
|
@ -89,16 +89,70 @@ pub enum UserResponse {}
|
||||||
|
|
||||||
impl UserResponseTrait for UserResponse {}
|
impl UserResponseTrait for UserResponse {}
|
||||||
|
|
||||||
|
fn default_unit() -> &'static str { "" }
|
||||||
|
|
||||||
/// Value types for inline parameters
|
/// Value types for inline parameters
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum ValueType {
|
pub enum ValueType {
|
||||||
Float { value: f32 },
|
Float {
|
||||||
|
value: f32,
|
||||||
|
#[serde(skip, default)]
|
||||||
|
min: f32,
|
||||||
|
#[serde(skip, default)]
|
||||||
|
max: f32,
|
||||||
|
#[serde(skip, default = "default_unit")]
|
||||||
|
unit: &'static str,
|
||||||
|
#[serde(skip)]
|
||||||
|
backend_param_id: Option<u32>,
|
||||||
|
#[serde(skip)]
|
||||||
|
enum_labels: Option<&'static [&'static str]>,
|
||||||
|
},
|
||||||
String { value: String },
|
String { value: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValueType {
|
||||||
|
/// Plain float value (for connection inputs, no parameter metadata)
|
||||||
|
pub fn float(value: f32) -> Self {
|
||||||
|
ValueType::Float {
|
||||||
|
value,
|
||||||
|
min: 0.0,
|
||||||
|
max: 0.0,
|
||||||
|
unit: "",
|
||||||
|
backend_param_id: None,
|
||||||
|
enum_labels: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Float parameter with full metadata for inline editing
|
||||||
|
pub fn float_param(
|
||||||
|
value: f32,
|
||||||
|
min: f32,
|
||||||
|
max: f32,
|
||||||
|
unit: &'static str,
|
||||||
|
param_id: u32,
|
||||||
|
enum_labels: Option<&'static [&'static str]>,
|
||||||
|
) -> Self {
|
||||||
|
ValueType::Float {
|
||||||
|
value,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
unit,
|
||||||
|
backend_param_id: Some(param_id),
|
||||||
|
enum_labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ValueType {
|
impl Default for ValueType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ValueType::Float { value: 0.0 }
|
ValueType::Float {
|
||||||
|
value: 0.0,
|
||||||
|
min: 0.0,
|
||||||
|
max: 0.0,
|
||||||
|
unit: "",
|
||||||
|
backend_param_id: None,
|
||||||
|
enum_labels: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +199,7 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
// Effects
|
// Effects
|
||||||
NodeTemplate::Filter => "Filter".into(),
|
NodeTemplate::Filter => "Filter".into(),
|
||||||
NodeTemplate::Gain => "Gain".into(),
|
NodeTemplate::Gain => "Gain".into(),
|
||||||
NodeTemplate::Delay => "Delay".into(),
|
NodeTemplate::Echo => "Echo".into(),
|
||||||
NodeTemplate::Reverb => "Reverb".into(),
|
NodeTemplate::Reverb => "Reverb".into(),
|
||||||
NodeTemplate::Chorus => "Chorus".into(),
|
NodeTemplate::Chorus => "Chorus".into(),
|
||||||
NodeTemplate::Flanger => "Flanger".into(),
|
NodeTemplate::Flanger => "Flanger".into(),
|
||||||
|
|
@ -187,7 +241,7 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput => vec!["Inputs"],
|
NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput => vec!["Inputs"],
|
||||||
NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth
|
NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth
|
||||||
| NodeTemplate::Noise | NodeTemplate::SimpleSampler | NodeTemplate::MultiSampler => vec!["Generators"],
|
| NodeTemplate::Noise | NodeTemplate::SimpleSampler | NodeTemplate::MultiSampler => vec!["Generators"],
|
||||||
NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Delay | NodeTemplate::Reverb
|
NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Echo | NodeTemplate::Reverb
|
||||||
| NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser | NodeTemplate::Distortion
|
| NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser | NodeTemplate::Distortion
|
||||||
| NodeTemplate::BitCrusher | NodeTemplate::Compressor | NodeTemplate::Limiter | NodeTemplate::Eq
|
| NodeTemplate::BitCrusher | NodeTemplate::Compressor | NodeTemplate::Limiter | NodeTemplate::Eq
|
||||||
| NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Vocoder => vec!["Effects"],
|
| NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Vocoder => vec!["Effects"],
|
||||||
|
|
@ -217,124 +271,66 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
NodeTemplate::Oscillator => {
|
NodeTemplate::Oscillator => {
|
||||||
// V/Oct input (pitch control voltage)
|
// Connection inputs
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true);
|
||||||
node_id,
|
graph.add_input_param(node_id, "FM".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
"V/Oct".into(),
|
// Parameters
|
||||||
DataType::CV,
|
graph.add_input_param(node_id, "Frequency".into(), DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::float_param(440.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
InputParamKind::ConnectionOrConstant,
|
graph.add_input_param(node_id, "Amplitude".into(), DataType::CV,
|
||||||
true,
|
ValueType::float_param(0.5, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
);
|
graph.add_input_param(node_id, "Waveform".into(), DataType::CV,
|
||||||
// FM input (frequency modulation)
|
ValueType::float_param(0.0, 0.0, 3.0, "", 2, Some(&["Sine", "Saw", "Square", "Triangle"])), InputParamKind::ConstantOnly, true);
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"FM".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
// Audio output
|
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Noise => {
|
NodeTemplate::Noise => {
|
||||||
|
graph.add_input_param(node_id, "Color".into(), DataType::CV,
|
||||||
|
ValueType::float_param(0.0, 0.0, 2.0, "", 0, Some(&["White", "Pink", "Brown"])), InputParamKind::ConstantOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Filter => {
|
NodeTemplate::Filter => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
graph.add_input_param(node_id, "Cutoff CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
"Audio In".into(),
|
// Parameters
|
||||||
DataType::Audio,
|
graph.add_input_param(node_id, "Cutoff".into(), DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::float_param(1000.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
InputParamKind::ConnectionOnly,
|
graph.add_input_param(node_id, "Resonance".into(), DataType::CV,
|
||||||
true,
|
ValueType::float_param(0.0, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
);
|
graph.add_input_param(node_id, "Type".into(), DataType::CV,
|
||||||
graph.add_input_param(
|
ValueType::float_param(0.0, 0.0, 3.0, "", 2, Some(&["LPF", "HPF", "BPF", "Notch"])), InputParamKind::ConstantOnly, true);
|
||||||
node_id,
|
|
||||||
"Cutoff CV".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Gain => {
|
NodeTemplate::Gain => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
graph.add_input_param(node_id, "Gain CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
"Audio In".into(),
|
// Parameters
|
||||||
DataType::Audio,
|
graph.add_input_param(node_id, "Gain".into(), DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::float_param(0.0, -60.0, 12.0, " dB", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Gain CV".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Adsr => {
|
NodeTemplate::Adsr => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
|
||||||
"Gate".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
// Parameters
|
// Parameters
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Attack".into(), DataType::CV,
|
||||||
node_id,
|
ValueType::float_param(10.0, 0.1, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
"Attack".into(),
|
graph.add_input_param(node_id, "Decay".into(), DataType::CV,
|
||||||
DataType::CV,
|
ValueType::float_param(100.0, 0.1, 2000.0, " ms", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
ValueType::Float { value: 0.01 },
|
graph.add_input_param(node_id, "Sustain".into(), DataType::CV,
|
||||||
InputParamKind::ConstantOnly,
|
ValueType::float_param(0.7, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||||
true,
|
graph.add_input_param(node_id, "Release".into(), DataType::CV,
|
||||||
);
|
ValueType::float_param(200.0, 0.1, 5000.0, " ms", 3, None), InputParamKind::ConstantOnly, true);
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Decay".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.1 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Sustain".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.7 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Release".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.2 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Envelope Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "Envelope Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::Lfo => {
|
NodeTemplate::Lfo => {
|
||||||
|
// Parameters
|
||||||
|
graph.add_input_param(node_id, "Rate".into(), DataType::CV,
|
||||||
|
ValueType::float_param(1.0, 0.01, 20.0, " Hz", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
|
graph.add_input_param(node_id, "Waveform".into(), DataType::CV,
|
||||||
|
ValueType::float_param(0.0, 0.0, 3.0, "", 1, Some(&["Sine", "Triangle", "Square", "Saw"])), InputParamKind::ConstantOnly, true);
|
||||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::AudioOutput => {
|
NodeTemplate::AudioOutput => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
|
||||||
"Audio In".into(),
|
|
||||||
DataType::Audio,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
NodeTemplate::AudioInput => {
|
NodeTemplate::AudioInput => {
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
|
|
@ -342,105 +338,47 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
NodeTemplate::MidiInput => {
|
NodeTemplate::MidiInput => {
|
||||||
graph.add_output_param(node_id, "MIDI Out".into(), DataType::Midi);
|
graph.add_output_param(node_id, "MIDI Out".into(), DataType::Midi);
|
||||||
}
|
}
|
||||||
NodeTemplate::Delay => {
|
NodeTemplate::Echo => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
|
||||||
"Audio In".into(),
|
|
||||||
DataType::Audio,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
// Parameters
|
// Parameters
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Delay Time".into(), DataType::CV,
|
||||||
node_id,
|
ValueType::float_param(250.0, 1.0, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
"Delay Time".into(),
|
graph.add_input_param(node_id, "Feedback".into(), DataType::CV,
|
||||||
DataType::CV,
|
ValueType::float_param(0.3, 0.0, 0.95, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
ValueType::Float { value: 0.5 },
|
graph.add_input_param(node_id, "Mix".into(), DataType::CV,
|
||||||
InputParamKind::ConstantOnly,
|
ValueType::float_param(0.5, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Feedback".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.5 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Wet/Dry".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.5 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Mixer => {
|
NodeTemplate::Mixer => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Input 1".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
graph.add_input_param(node_id, "Input 2".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
"Input 1".into(),
|
graph.add_input_param(node_id, "Input 3".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
DataType::Audio,
|
graph.add_input_param(node_id, "Input 4".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
ValueType::Float { value: 0.0 },
|
// Level parameters
|
||||||
InputParamKind::ConnectionOnly,
|
graph.add_input_param(node_id, "Level 1".into(), DataType::CV,
|
||||||
true,
|
ValueType::float_param(1.0, 0.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
);
|
graph.add_input_param(node_id, "Level 2".into(), DataType::CV,
|
||||||
graph.add_input_param(
|
ValueType::float_param(1.0, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
node_id,
|
graph.add_input_param(node_id, "Level 3".into(), DataType::CV,
|
||||||
"Input 2".into(),
|
ValueType::float_param(1.0, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||||
DataType::Audio,
|
graph.add_input_param(node_id, "Level 4".into(), DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::float_param(1.0, 0.0, 1.0, "", 3, None), InputParamKind::ConstantOnly, true);
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Input 3".into(),
|
|
||||||
DataType::Audio,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_input_param(
|
|
||||||
node_id,
|
|
||||||
"Input 4".into(),
|
|
||||||
DataType::Audio,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Mixed Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Mixed Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Splitter => {
|
NodeTemplate::Splitter => {
|
||||||
graph.add_input_param(
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
node_id,
|
|
||||||
"Audio In".into(),
|
|
||||||
DataType::Audio,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConnectionOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "Out 1".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Out 1".into(), DataType::Audio);
|
||||||
graph.add_output_param(node_id, "Out 2".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Out 2".into(), DataType::Audio);
|
||||||
graph.add_output_param(node_id, "Out 3".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Out 3".into(), DataType::Audio);
|
||||||
graph.add_output_param(node_id, "Out 4".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Out 4".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Constant => {
|
NodeTemplate::Constant => {
|
||||||
// No inputs - value is set via parameter
|
graph.add_input_param(node_id, "Value".into(), DataType::CV,
|
||||||
graph.add_input_param(
|
ValueType::float_param(0.0, -1.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
node_id,
|
|
||||||
"Value".into(),
|
|
||||||
DataType::CV,
|
|
||||||
ValueType::Float { value: 0.0 },
|
|
||||||
InputParamKind::ConstantOnly,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::MidiToCv => {
|
NodeTemplate::MidiToCv => {
|
||||||
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "V/Oct".into(), DataType::CV);
|
graph.add_output_param(node_id, "V/Oct".into(), DataType::CV);
|
||||||
graph.add_output_param(node_id, "Gate".into(), DataType::CV);
|
graph.add_output_param(node_id, "Gate".into(), DataType::CV);
|
||||||
graph.add_output_param(node_id, "Velocity".into(), DataType::CV);
|
graph.add_output_param(node_id, "Velocity".into(), DataType::CV);
|
||||||
|
|
@ -450,56 +388,67 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::WavetableOscillator => {
|
NodeTemplate::WavetableOscillator => {
|
||||||
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::FmSynth => {
|
NodeTemplate::FmSynth => {
|
||||||
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::SimpleSampler => {
|
NodeTemplate::SimpleSampler => {
|
||||||
graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::MultiSampler => {
|
NodeTemplate::MultiSampler => {
|
||||||
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Reverb | NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser
|
NodeTemplate::Reverb => {
|
||||||
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
|
// Parameters
|
||||||
|
graph.add_input_param(node_id, "Room Size".into(), DataType::CV,
|
||||||
|
ValueType::float_param(0.5, 0.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true);
|
||||||
|
graph.add_input_param(node_id, "Damping".into(), DataType::CV,
|
||||||
|
ValueType::float_param(0.5, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||||
|
graph.add_input_param(node_id, "Wet/Dry".into(), DataType::CV,
|
||||||
|
ValueType::float_param(0.3, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||||
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
|
}
|
||||||
|
NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser
|
||||||
| NodeTemplate::Distortion | NodeTemplate::BitCrusher | NodeTemplate::Compressor
|
| NodeTemplate::Distortion | NodeTemplate::BitCrusher | NodeTemplate::Compressor
|
||||||
| NodeTemplate::Limiter | NodeTemplate::Eq | NodeTemplate::Pan | NodeTemplate::RingModulator
|
| NodeTemplate::Limiter | NodeTemplate::Eq | NodeTemplate::Pan | NodeTemplate::RingModulator
|
||||||
| NodeTemplate::Vocoder => {
|
| NodeTemplate::Vocoder => {
|
||||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::AudioToCv => {
|
NodeTemplate::AudioToCv => {
|
||||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::Math => {
|
NodeTemplate::Math => {
|
||||||
graph.add_input_param(node_id, "A".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOrConstant, true);
|
graph.add_input_param(node_id, "A".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true);
|
||||||
graph.add_input_param(node_id, "B".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOrConstant, true);
|
graph.add_input_param(node_id, "B".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true);
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::SampleHold | NodeTemplate::SlewLimiter | NodeTemplate::Quantizer | NodeTemplate::EnvelopeFollower => {
|
NodeTemplate::SampleHold | NodeTemplate::SlewLimiter | NodeTemplate::Quantizer | NodeTemplate::EnvelopeFollower => {
|
||||||
graph.add_input_param(node_id, "In".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "In".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::BpmDetector => {
|
NodeTemplate::BpmDetector => {
|
||||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "BPM".into(), DataType::CV);
|
graph.add_output_param(node_id, "BPM".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::Mod => {
|
NodeTemplate::Mod => {
|
||||||
graph.add_input_param(node_id, "Carrier".into(), DataType::Audio, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Carrier".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_input_param(node_id, "Modulator".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Modulator".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Oscilloscope => {
|
NodeTemplate::Oscilloscope => {
|
||||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_input_param(node_id, "CV In".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "CV In".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
}
|
}
|
||||||
NodeTemplate::VoiceAllocator => {
|
NodeTemplate::VoiceAllocator => {
|
||||||
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
|
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -521,12 +470,50 @@ impl WidgetValueTrait for ValueType {
|
||||||
_node_data: &Self::NodeData,
|
_node_data: &Self::NodeData,
|
||||||
) -> Vec<Self::Response> {
|
) -> Vec<Self::Response> {
|
||||||
match self {
|
match self {
|
||||||
ValueType::Float { value } => {
|
ValueType::Float { value, min, max, unit, enum_labels, .. } => {
|
||||||
|
let has_range = *max > *min;
|
||||||
|
if let Some(labels) = enum_labels {
|
||||||
|
// Enum parameter: render as ComboBox dropdown
|
||||||
|
let mut selected = (*value as usize).min(labels.len().saturating_sub(1));
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(param_name);
|
||||||
|
egui::ComboBox::from_id_salt(param_name)
|
||||||
|
.selected_text(labels.get(selected).copied().unwrap_or("?"))
|
||||||
|
.width(90.0)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for (i, label) in labels.iter().enumerate() {
|
||||||
|
ui.selectable_value(&mut selected, i, *label);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*value = selected as f32;
|
||||||
|
} else if has_range {
|
||||||
|
// Ranged parameter: render clamped DragValue with unit suffix
|
||||||
|
let range = *max - *min;
|
||||||
|
let speed = if range > 1000.0 {
|
||||||
|
// Logarithmic-ish speed for large ranges (frequency, time)
|
||||||
|
(*value).max(1.0) * 0.01
|
||||||
|
} else {
|
||||||
|
range / 300.0
|
||||||
|
};
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(param_name);
|
||||||
|
let mut dv = egui::DragValue::new(value)
|
||||||
|
.speed(speed)
|
||||||
|
.range(*min..=*max);
|
||||||
|
if !unit.is_empty() {
|
||||||
|
dv = dv.suffix(*unit);
|
||||||
|
}
|
||||||
|
ui.add(dv);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Plain float (no metadata)
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(param_name);
|
ui.label(param_name);
|
||||||
ui.add(egui::DragValue::new(value).speed(0.1));
|
ui.add(egui::DragValue::new(value).speed(0.1));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ValueType::String { value } => {
|
ValueType::String { value } => {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(param_name);
|
ui.label(param_name);
|
||||||
|
|
@ -583,7 +570,7 @@ impl NodeTemplateIter for AllNodeTemplates {
|
||||||
// Effects
|
// Effects
|
||||||
NodeTemplate::Filter,
|
NodeTemplate::Filter,
|
||||||
NodeTemplate::Gain,
|
NodeTemplate::Gain,
|
||||||
NodeTemplate::Delay,
|
NodeTemplate::Echo,
|
||||||
NodeTemplate::Reverb,
|
NodeTemplate::Reverb,
|
||||||
NodeTemplate::Chorus,
|
NodeTemplate::Chorus,
|
||||||
NodeTemplate::Flanger,
|
NodeTemplate::Flanger,
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ impl NodeGraphPane {
|
||||||
// Effects
|
// Effects
|
||||||
"Filter" => graph_data::NodeTemplate::Filter,
|
"Filter" => graph_data::NodeTemplate::Filter,
|
||||||
"Gain" => graph_data::NodeTemplate::Gain,
|
"Gain" => graph_data::NodeTemplate::Gain,
|
||||||
"Delay" => graph_data::NodeTemplate::Delay,
|
"Echo" | "Delay" => graph_data::NodeTemplate::Echo,
|
||||||
"Reverb" => graph_data::NodeTemplate::Reverb,
|
"Reverb" => graph_data::NodeTemplate::Reverb,
|
||||||
"Chorus" => graph_data::NodeTemplate::Chorus,
|
"Chorus" => graph_data::NodeTemplate::Chorus,
|
||||||
"Flanger" => graph_data::NodeTemplate::Flanger,
|
"Flanger" => graph_data::NodeTemplate::Flanger,
|
||||||
|
|
@ -205,13 +205,17 @@ impl NodeGraphPane {
|
||||||
self.node_id_map.insert(frontend_id, backend_id);
|
self.node_id_map.insert(frontend_id, backend_id);
|
||||||
self.backend_to_frontend_map.insert(backend_id, frontend_id);
|
self.backend_to_frontend_map.insert(backend_id, frontend_id);
|
||||||
|
|
||||||
// Set parameter values
|
// Set parameter values from backend
|
||||||
for (¶m_id, &value) in &node.parameters {
|
if let Some(node_data) = self.state.graph.nodes.get(frontend_id) {
|
||||||
// Find the input param in the graph and set its value
|
let input_ids: Vec<InputId> = node_data.inputs.iter().map(|(_, id)| *id).collect();
|
||||||
if let Some(_node_data) = self.state.graph.nodes.get_mut(frontend_id) {
|
for input_id in input_ids {
|
||||||
// TODO: Set parameter values on the node's input params
|
if let Some(input_param) = self.state.graph.inputs.get_mut(input_id) {
|
||||||
// This requires matching param_id to the input param by index
|
if let ValueType::Float { value, backend_param_id: Some(pid), .. } = &mut input_param.value {
|
||||||
let _ = (param_id, value); // Silence unused warning for now
|
if let Some(&backend_value) = node.parameters.get(pid) {
|
||||||
|
*value = backend_value as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -440,11 +444,11 @@ impl NodeGraphPane {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current value
|
// Get current value and backend param ID
|
||||||
let current_value = match &input_param.value {
|
let (current_value, backend_param_id) = match &input_param.value {
|
||||||
ValueType::Float { value } => {
|
ValueType::Float { value, backend_param_id, .. } => {
|
||||||
_checked_count += 1;
|
_checked_count += 1;
|
||||||
*value
|
(*value, *backend_param_id)
|
||||||
},
|
},
|
||||||
other => {
|
other => {
|
||||||
_non_float_count += 1;
|
_non_float_count += 1;
|
||||||
|
|
@ -468,19 +472,16 @@ impl NodeGraphPane {
|
||||||
if let Some(track_id) = self.track_id {
|
if let Some(track_id) = self.track_id {
|
||||||
let node_id = input_param.node;
|
let node_id = input_param.node;
|
||||||
|
|
||||||
// Get backend node ID
|
// Get backend node ID and use stored param ID
|
||||||
if let Some(&backend_id) = self.node_id_map.get(&node_id) {
|
if let Some(&backend_id) = self.node_id_map.get(&node_id) {
|
||||||
// Get parameter index (position in node's inputs array)
|
if let Some(param_id) = backend_param_id {
|
||||||
if let Some(node) = self.state.graph.nodes.get(node_id) {
|
|
||||||
if let Some(param_index) = node.inputs.iter().position(|(_, id)| *id == input_id) {
|
|
||||||
eprintln!("[DEBUG] Parameter changed: node {:?} param {} from {:?} to {}",
|
eprintln!("[DEBUG] Parameter changed: node {:?} param {} from {:?} to {}",
|
||||||
backend_id, param_index, previous_value, current_value);
|
backend_id, param_id, previous_value, current_value);
|
||||||
// Create action to update backend
|
|
||||||
let action = Box::new(actions::NodeGraphAction::SetParameter(
|
let action = Box::new(actions::NodeGraphAction::SetParameter(
|
||||||
actions::SetParameterAction::new(
|
actions::SetParameterAction::new(
|
||||||
track_id,
|
track_id,
|
||||||
backend_id,
|
backend_id,
|
||||||
param_index as u32,
|
param_id,
|
||||||
current_value as f64,
|
current_value as f64,
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
@ -488,7 +489,6 @@ impl NodeGraphPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update stored value
|
// Update stored value
|
||||||
self.parameter_values.insert(input_id, current_value);
|
self.parameter_values.insert(input_id, current_value);
|
||||||
|
|
|
||||||
|
|
@ -332,10 +332,10 @@ impl NodeTypeRegistry {
|
||||||
);
|
);
|
||||||
|
|
||||||
types.insert(
|
types.insert(
|
||||||
"Delay".to_string(),
|
"Echo".to_string(),
|
||||||
NodeTypeInfo {
|
NodeTypeInfo {
|
||||||
id: "Delay".to_string(),
|
id: "Echo".to_string(),
|
||||||
display_name: "Delay".to_string(),
|
display_name: "Echo".to_string(),
|
||||||
category: NodeCategory::Effects,
|
category: NodeCategory::Effects,
|
||||||
inputs: vec![PortInfo {
|
inputs: vec![PortInfo {
|
||||||
index: 0,
|
index: 0,
|
||||||
|
|
@ -347,7 +347,7 @@ impl NodeTypeRegistry {
|
||||||
index: 0,
|
index: 0,
|
||||||
name: "Out".to_string(),
|
name: "Out".to_string(),
|
||||||
signal_type: DataType::Audio,
|
signal_type: DataType::Audio,
|
||||||
description: "Delayed audio output".to_string(),
|
description: "Echo audio output".to_string(),
|
||||||
}],
|
}],
|
||||||
parameters: vec![
|
parameters: vec![
|
||||||
ParameterInfo {
|
ParameterInfo {
|
||||||
|
|
@ -357,7 +357,7 @@ impl NodeTypeRegistry {
|
||||||
min: 1.0,
|
min: 1.0,
|
||||||
max: 2000.0,
|
max: 2000.0,
|
||||||
unit: ParameterUnit::Milliseconds,
|
unit: ParameterUnit::Milliseconds,
|
||||||
description: "Delay time".to_string(),
|
description: "Echo time".to_string(),
|
||||||
},
|
},
|
||||||
ParameterInfo {
|
ParameterInfo {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
@ -378,7 +378,7 @@ impl NodeTypeRegistry {
|
||||||
description: "Dry/wet mix".to_string(),
|
description: "Dry/wet mix".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
description: "Time-based delay effect".to_string(),
|
description: "Echo effect with feedback".to_string(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue