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);
|
||||
// Stop all MIDI notes when seeking to prevent stuck 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
|
||||
if let Some(ref mut dr) = self.disk_reader {
|
||||
dr.send(crate::audio::disk_reader::DiskReaderCommand::Seek { frame: frames });
|
||||
|
|
@ -1080,7 +1082,7 @@ impl Engine {
|
|||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||
"Pan" => Box::new(PanNode::new("Pan".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())),
|
||||
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
||||
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
||||
|
|
@ -1165,7 +1167,7 @@ impl Engine {
|
|||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||
"Pan" => Box::new(PanNode::new("Pan".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())),
|
||||
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
|
||||
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
|
||||
|
|
|
|||
|
|
@ -865,7 +865,7 @@ impl AudioGraph {
|
|||
"Splitter" => Box::new(SplitterNode::new("Splitter")),
|
||||
"Pan" => Box::new(PanNode::new("Pan")),
|
||||
"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")),
|
||||
"Reverb" => Box::new(ReverbNode::new("Reverb")),
|
||||
"Chorus" => Box::new(ChorusNode::new("Chorus")),
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ const PARAM_WET_DRY: u32 = 2;
|
|||
|
||||
const MAX_DELAY_SECONDS: f32 = 2.0;
|
||||
|
||||
/// Stereo delay node with feedback
|
||||
pub struct DelayNode {
|
||||
/// Stereo echo node with feedback
|
||||
pub struct EchoNode {
|
||||
name: String,
|
||||
delay_time: f32, // seconds
|
||||
feedback: f32, // 0.0 to 0.95
|
||||
|
|
@ -26,7 +26,7 @@ pub struct DelayNode {
|
|||
parameters: Vec<Parameter>,
|
||||
}
|
||||
|
||||
impl DelayNode {
|
||||
impl EchoNode {
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
let name = name.into();
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ impl DelayNode {
|
|||
}
|
||||
}
|
||||
|
||||
impl AudioNode for DelayNode {
|
||||
impl AudioNode for EchoNode {
|
||||
fn category(&self) -> NodeCategory {
|
||||
NodeCategory::Effect
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ impl AudioNode for DelayNode {
|
|||
}
|
||||
|
||||
fn node_type(&self) -> &str {
|
||||
"Delay"
|
||||
"Echo"
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
|
|
@ -7,7 +7,7 @@ mod bpm_detector;
|
|||
mod chorus;
|
||||
mod compressor;
|
||||
mod constant;
|
||||
mod delay;
|
||||
mod echo;
|
||||
mod distortion;
|
||||
mod envelope_follower;
|
||||
mod eq;
|
||||
|
|
@ -49,7 +49,7 @@ pub use bpm_detector::BpmDetectorNode;
|
|||
pub use chorus::ChorusNode;
|
||||
pub use compressor::CompressorNode;
|
||||
pub use constant::ConstantNode;
|
||||
pub use delay::DelayNode;
|
||||
pub use echo::EchoNode;
|
||||
pub use distortion::DistortionNode;
|
||||
pub use envelope_follower::EnvelopeFollowerNode;
|
||||
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)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub enum NodeTemplate {
|
|||
// Effects
|
||||
Filter,
|
||||
Gain,
|
||||
Delay,
|
||||
Echo,
|
||||
Reverb,
|
||||
Chorus,
|
||||
Flanger,
|
||||
|
|
@ -89,16 +89,70 @@ pub enum UserResponse {}
|
|||
|
||||
impl UserResponseTrait for UserResponse {}
|
||||
|
||||
fn default_unit() -> &'static str { "" }
|
||||
|
||||
/// Value types for inline parameters
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
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 },
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
NodeTemplate::Filter => "Filter".into(),
|
||||
NodeTemplate::Gain => "Gain".into(),
|
||||
NodeTemplate::Delay => "Delay".into(),
|
||||
NodeTemplate::Echo => "Echo".into(),
|
||||
NodeTemplate::Reverb => "Reverb".into(),
|
||||
NodeTemplate::Chorus => "Chorus".into(),
|
||||
NodeTemplate::Flanger => "Flanger".into(),
|
||||
|
|
@ -187,7 +241,7 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput => vec!["Inputs"],
|
||||
NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth
|
||||
| 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::BitCrusher | NodeTemplate::Compressor | NodeTemplate::Limiter | NodeTemplate::Eq
|
||||
| NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Vocoder => vec!["Effects"],
|
||||
|
|
@ -217,124 +271,66 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
) {
|
||||
match self {
|
||||
NodeTemplate::Oscillator => {
|
||||
// V/Oct input (pitch control voltage)
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"V/Oct".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOrConstant,
|
||||
true,
|
||||
);
|
||||
// FM input (frequency modulation)
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"FM".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
// Audio output
|
||||
// Connection inputs
|
||||
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true);
|
||||
graph.add_input_param(node_id, "FM".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
// Parameters
|
||||
graph.add_input_param(node_id, "Frequency".into(), DataType::CV,
|
||||
ValueType::float_param(440.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Amplitude".into(), DataType::CV,
|
||||
ValueType::float_param(0.5, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Waveform".into(), DataType::CV,
|
||||
ValueType::float_param(0.0, 0.0, 3.0, "", 2, Some(&["Sine", "Saw", "Square", "Triangle"])), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
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);
|
||||
}
|
||||
NodeTemplate::Filter => {
|
||||
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,
|
||||
"Cutoff CV".into(),
|
||||
DataType::CV,
|
||||
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, "Cutoff CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
// Parameters
|
||||
graph.add_input_param(node_id, "Cutoff".into(), DataType::CV,
|
||||
ValueType::float_param(1000.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Resonance".into(), DataType::CV,
|
||||
ValueType::float_param(0.0, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Type".into(), DataType::CV,
|
||||
ValueType::float_param(0.0, 0.0, 3.0, "", 2, Some(&["LPF", "HPF", "BPF", "Notch"])), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Gain => {
|
||||
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,
|
||||
"Gain CV".into(),
|
||||
DataType::CV,
|
||||
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, "Gain CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
// Parameters
|
||||
graph.add_input_param(node_id, "Gain".into(), DataType::CV,
|
||||
ValueType::float_param(0.0, -60.0, 12.0, " dB", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Adsr => {
|
||||
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);
|
||||
// Parameters
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Attack".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.01 },
|
||||
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_input_param(node_id, "Attack".into(), DataType::CV,
|
||||
ValueType::float_param(10.0, 0.1, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Decay".into(), DataType::CV,
|
||||
ValueType::float_param(100.0, 0.1, 2000.0, " ms", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Sustain".into(), DataType::CV,
|
||||
ValueType::float_param(0.7, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, 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_output_param(node_id, "Envelope Out".into(), DataType::CV);
|
||||
}
|
||||
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);
|
||||
}
|
||||
NodeTemplate::AudioOutput => {
|
||||
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);
|
||||
}
|
||||
NodeTemplate::AudioInput => {
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
|
|
@ -342,105 +338,47 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
NodeTemplate::MidiInput => {
|
||||
graph.add_output_param(node_id, "MIDI Out".into(), DataType::Midi);
|
||||
}
|
||||
NodeTemplate::Delay => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Audio In".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
NodeTemplate::Echo => {
|
||||
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,
|
||||
"Delay Time".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.5 },
|
||||
InputParamKind::ConstantOnly,
|
||||
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_input_param(node_id, "Delay Time".into(), DataType::CV,
|
||||
ValueType::float_param(250.0, 1.0, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Feedback".into(), DataType::CV,
|
||||
ValueType::float_param(0.3, 0.0, 0.95, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Mix".into(), DataType::CV,
|
||||
ValueType::float_param(0.5, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Mixer => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Input 1".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Input 2".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
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_input_param(node_id, "Input 1".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_input_param(node_id, "Input 2".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_input_param(node_id, "Input 3".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_input_param(node_id, "Input 4".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
// Level parameters
|
||||
graph.add_input_param(node_id, "Level 1".into(), DataType::CV,
|
||||
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,
|
||||
ValueType::float_param(1.0, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Level 3".into(), DataType::CV,
|
||||
ValueType::float_param(1.0, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Level 4".into(), DataType::CV,
|
||||
ValueType::float_param(1.0, 0.0, 1.0, "", 3, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Mixed Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Splitter => {
|
||||
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, "Out 1".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 4".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Constant => {
|
||||
// No inputs - value is set via parameter
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Value".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConstantOnly,
|
||||
true,
|
||||
);
|
||||
graph.add_input_param(node_id, "Value".into(), DataType::CV,
|
||||
ValueType::float_param(0.0, -1.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||
}
|
||||
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, "Gate".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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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::Limiter | NodeTemplate::Eq | NodeTemplate::Pan | NodeTemplate::RingModulator
|
||||
| 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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, "B".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(0.0), InputParamKind::ConnectionOrConstant, true);
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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, "Modulator".into(), DataType::CV, 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(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
||||
}
|
||||
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, "CV In".into(), DataType::CV, 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(0.0), InputParamKind::ConnectionOnly, true);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -521,12 +470,50 @@ impl WidgetValueTrait for ValueType {
|
|||
_node_data: &Self::NodeData,
|
||||
) -> Vec<Self::Response> {
|
||||
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.label(param_name);
|
||||
ui.add(egui::DragValue::new(value).speed(0.1));
|
||||
});
|
||||
}
|
||||
}
|
||||
ValueType::String { value } => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(param_name);
|
||||
|
|
@ -583,7 +570,7 @@ impl NodeTemplateIter for AllNodeTemplates {
|
|||
// Effects
|
||||
NodeTemplate::Filter,
|
||||
NodeTemplate::Gain,
|
||||
NodeTemplate::Delay,
|
||||
NodeTemplate::Echo,
|
||||
NodeTemplate::Reverb,
|
||||
NodeTemplate::Chorus,
|
||||
NodeTemplate::Flanger,
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ impl NodeGraphPane {
|
|||
// Effects
|
||||
"Filter" => graph_data::NodeTemplate::Filter,
|
||||
"Gain" => graph_data::NodeTemplate::Gain,
|
||||
"Delay" => graph_data::NodeTemplate::Delay,
|
||||
"Echo" | "Delay" => graph_data::NodeTemplate::Echo,
|
||||
"Reverb" => graph_data::NodeTemplate::Reverb,
|
||||
"Chorus" => graph_data::NodeTemplate::Chorus,
|
||||
"Flanger" => graph_data::NodeTemplate::Flanger,
|
||||
|
|
@ -205,13 +205,17 @@ impl NodeGraphPane {
|
|||
self.node_id_map.insert(frontend_id, backend_id);
|
||||
self.backend_to_frontend_map.insert(backend_id, frontend_id);
|
||||
|
||||
// Set parameter values
|
||||
for (¶m_id, &value) in &node.parameters {
|
||||
// Find the input param in the graph and set its value
|
||||
if let Some(_node_data) = self.state.graph.nodes.get_mut(frontend_id) {
|
||||
// TODO: Set parameter values on the node's input params
|
||||
// This requires matching param_id to the input param by index
|
||||
let _ = (param_id, value); // Silence unused warning for now
|
||||
// Set parameter values from backend
|
||||
if let Some(node_data) = self.state.graph.nodes.get(frontend_id) {
|
||||
let input_ids: Vec<InputId> = node_data.inputs.iter().map(|(_, id)| *id).collect();
|
||||
for input_id in input_ids {
|
||||
if let Some(input_param) = self.state.graph.inputs.get_mut(input_id) {
|
||||
if let ValueType::Float { value, backend_param_id: Some(pid), .. } = &mut input_param.value {
|
||||
if let Some(&backend_value) = node.parameters.get(pid) {
|
||||
*value = backend_value as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -440,11 +444,11 @@ impl NodeGraphPane {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Get current value
|
||||
let current_value = match &input_param.value {
|
||||
ValueType::Float { value } => {
|
||||
// Get current value and backend param ID
|
||||
let (current_value, backend_param_id) = match &input_param.value {
|
||||
ValueType::Float { value, backend_param_id, .. } => {
|
||||
_checked_count += 1;
|
||||
*value
|
||||
(*value, *backend_param_id)
|
||||
},
|
||||
other => {
|
||||
_non_float_count += 1;
|
||||
|
|
@ -468,19 +472,16 @@ impl NodeGraphPane {
|
|||
if let Some(track_id) = self.track_id {
|
||||
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) {
|
||||
// Get parameter index (position in node's inputs array)
|
||||
if let Some(node) = self.state.graph.nodes.get(node_id) {
|
||||
if let Some(param_index) = node.inputs.iter().position(|(_, id)| *id == input_id) {
|
||||
if let Some(param_id) = backend_param_id {
|
||||
eprintln!("[DEBUG] Parameter changed: node {:?} param {} from {:?} to {}",
|
||||
backend_id, param_index, previous_value, current_value);
|
||||
// Create action to update backend
|
||||
backend_id, param_id, previous_value, current_value);
|
||||
let action = Box::new(actions::NodeGraphAction::SetParameter(
|
||||
actions::SetParameterAction::new(
|
||||
track_id,
|
||||
backend_id,
|
||||
param_index as u32,
|
||||
param_id,
|
||||
current_value as f64,
|
||||
)
|
||||
));
|
||||
|
|
@ -488,7 +489,6 @@ impl NodeGraphPane {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update stored value
|
||||
self.parameter_values.insert(input_id, current_value);
|
||||
|
|
|
|||
|
|
@ -332,10 +332,10 @@ impl NodeTypeRegistry {
|
|||
);
|
||||
|
||||
types.insert(
|
||||
"Delay".to_string(),
|
||||
"Echo".to_string(),
|
||||
NodeTypeInfo {
|
||||
id: "Delay".to_string(),
|
||||
display_name: "Delay".to_string(),
|
||||
id: "Echo".to_string(),
|
||||
display_name: "Echo".to_string(),
|
||||
category: NodeCategory::Effects,
|
||||
inputs: vec![PortInfo {
|
||||
index: 0,
|
||||
|
|
@ -347,7 +347,7 @@ impl NodeTypeRegistry {
|
|||
index: 0,
|
||||
name: "Out".to_string(),
|
||||
signal_type: DataType::Audio,
|
||||
description: "Delayed audio output".to_string(),
|
||||
description: "Echo audio output".to_string(),
|
||||
}],
|
||||
parameters: vec![
|
||||
ParameterInfo {
|
||||
|
|
@ -357,7 +357,7 @@ impl NodeTypeRegistry {
|
|||
min: 1.0,
|
||||
max: 2000.0,
|
||||
unit: ParameterUnit::Milliseconds,
|
||||
description: "Delay time".to_string(),
|
||||
description: "Echo time".to_string(),
|
||||
},
|
||||
ParameterInfo {
|
||||
id: 1,
|
||||
|
|
@ -378,7 +378,7 @@ impl NodeTypeRegistry {
|
|||
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