Node graph improvements
This commit is contained in:
parent
c58192a7da
commit
d7176a13b7
|
|
@ -28,10 +28,14 @@ pub enum NodeTemplate {
|
||||||
// Effects
|
// Effects
|
||||||
Filter,
|
Filter,
|
||||||
Gain,
|
Gain,
|
||||||
|
Delay,
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
Adsr,
|
Adsr,
|
||||||
Lfo,
|
Lfo,
|
||||||
|
Mixer,
|
||||||
|
Splitter,
|
||||||
|
Constant,
|
||||||
|
|
||||||
// Outputs
|
// Outputs
|
||||||
AudioOutput,
|
AudioOutput,
|
||||||
|
|
@ -101,8 +105,12 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
NodeTemplate::Noise => "Noise".into(),
|
NodeTemplate::Noise => "Noise".into(),
|
||||||
NodeTemplate::Filter => "Filter".into(),
|
NodeTemplate::Filter => "Filter".into(),
|
||||||
NodeTemplate::Gain => "Gain".into(),
|
NodeTemplate::Gain => "Gain".into(),
|
||||||
|
NodeTemplate::Delay => "Delay".into(),
|
||||||
NodeTemplate::Adsr => "ADSR".into(),
|
NodeTemplate::Adsr => "ADSR".into(),
|
||||||
NodeTemplate::Lfo => "LFO".into(),
|
NodeTemplate::Lfo => "LFO".into(),
|
||||||
|
NodeTemplate::Mixer => "Mixer".into(),
|
||||||
|
NodeTemplate::Splitter => "Splitter".into(),
|
||||||
|
NodeTemplate::Constant => "Constant".into(),
|
||||||
NodeTemplate::AudioOutput => "Audio Output".into(),
|
NodeTemplate::AudioOutput => "Audio Output".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,8 +119,9 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
match self {
|
match self {
|
||||||
NodeTemplate::MidiInput | NodeTemplate::AudioInput => vec!["Inputs"],
|
NodeTemplate::MidiInput | NodeTemplate::AudioInput => vec!["Inputs"],
|
||||||
NodeTemplate::Oscillator | NodeTemplate::Noise => vec!["Generators"],
|
NodeTemplate::Oscillator | NodeTemplate::Noise => vec!["Generators"],
|
||||||
NodeTemplate::Filter | NodeTemplate::Gain => vec!["Effects"],
|
NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Delay => vec!["Effects"],
|
||||||
NodeTemplate::Adsr | NodeTemplate::Lfo => vec!["Utilities"],
|
NodeTemplate::Adsr | NodeTemplate::Lfo | NodeTemplate::Mixer
|
||||||
|
| NodeTemplate::Splitter | NodeTemplate::Constant => vec!["Utilities"],
|
||||||
NodeTemplate::AudioOutput => vec!["Outputs"],
|
NodeTemplate::AudioOutput => vec!["Outputs"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,34 +142,34 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
NodeTemplate::Oscillator => {
|
NodeTemplate::Oscillator => {
|
||||||
// FM input
|
// V/Oct input (pitch control voltage)
|
||||||
graph.add_input_param(
|
graph.add_input_param(
|
||||||
node_id,
|
node_id,
|
||||||
"FM".into(),
|
"V/Oct".into(),
|
||||||
DataType::Audio,
|
DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
// Frequency parameter
|
// FM input (frequency modulation)
|
||||||
graph.add_input_param(
|
graph.add_input_param(
|
||||||
node_id,
|
node_id,
|
||||||
"Freq".into(),
|
"FM".into(),
|
||||||
DataType::CV,
|
DataType::CV,
|
||||||
ValueType::Float { value: 440.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConstantOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
// Audio output
|
// Audio output
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::Noise => {
|
NodeTemplate::Noise => {
|
||||||
graph.add_output_param(node_id, "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,
|
node_id,
|
||||||
"In".into(),
|
"Audio In".into(),
|
||||||
DataType::Audio,
|
DataType::Audio,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
|
|
@ -168,18 +177,18 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
);
|
);
|
||||||
graph.add_input_param(
|
graph.add_input_param(
|
||||||
node_id,
|
node_id,
|
||||||
"Cutoff".into(),
|
"Cutoff CV".into(),
|
||||||
DataType::CV,
|
DataType::CV,
|
||||||
ValueType::Float { value: 1000.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOrConstant,
|
InputParamKind::ConnectionOnly,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
graph.add_output_param(node_id, "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,
|
node_id,
|
||||||
"In".into(),
|
"Audio In".into(),
|
||||||
DataType::Audio,
|
DataType::Audio,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
|
|
@ -187,32 +196,65 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
);
|
);
|
||||||
graph.add_input_param(
|
graph.add_input_param(
|
||||||
node_id,
|
node_id,
|
||||||
"Gain".into(),
|
"Gain CV".into(),
|
||||||
DataType::CV,
|
DataType::CV,
|
||||||
ValueType::Float { value: 1.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOrConstant,
|
InputParamKind::ConnectionOnly,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
graph.add_output_param(node_id, "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,
|
node_id,
|
||||||
"Gate".into(),
|
"Gate".into(),
|
||||||
DataType::Midi,
|
DataType::CV,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
// 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_output_param(node_id, "Envelope Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
NodeTemplate::Lfo => {
|
NodeTemplate::Lfo => {
|
||||||
graph.add_output_param(node_id, "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,
|
node_id,
|
||||||
"In".into(),
|
"Audio In".into(),
|
||||||
DataType::Audio,
|
DataType::Audio,
|
||||||
ValueType::Float { value: 0.0 },
|
ValueType::Float { value: 0.0 },
|
||||||
InputParamKind::ConnectionOnly,
|
InputParamKind::ConnectionOnly,
|
||||||
|
|
@ -220,10 +262,107 @@ impl NodeTemplateTrait for NodeTemplate {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
NodeTemplate::AudioInput => {
|
NodeTemplate::AudioInput => {
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||||
}
|
}
|
||||||
NodeTemplate::MidiInput => {
|
NodeTemplate::MidiInput => {
|
||||||
graph.add_output_param(node_id, "Out".into(), DataType::Midi);
|
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,
|
||||||
|
);
|
||||||
|
// 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_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_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_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_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -298,8 +437,12 @@ impl NodeTemplateIter for AllNodeTemplates {
|
||||||
NodeTemplate::Noise,
|
NodeTemplate::Noise,
|
||||||
NodeTemplate::Filter,
|
NodeTemplate::Filter,
|
||||||
NodeTemplate::Gain,
|
NodeTemplate::Gain,
|
||||||
|
NodeTemplate::Delay,
|
||||||
NodeTemplate::Adsr,
|
NodeTemplate::Adsr,
|
||||||
NodeTemplate::Lfo,
|
NodeTemplate::Lfo,
|
||||||
|
NodeTemplate::Mixer,
|
||||||
|
NodeTemplate::Splitter,
|
||||||
|
NodeTemplate::Constant,
|
||||||
NodeTemplate::AudioOutput,
|
NodeTemplate::AudioOutput,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ impl NodeGraphPane {
|
||||||
|
|
||||||
// Calculate zoom center (same as nodes - they zoom relative to viewport center)
|
// Calculate zoom center (same as nodes - they zoom relative to viewport center)
|
||||||
let half_size = rect.size() / 2.0;
|
let half_size = rect.size() / 2.0;
|
||||||
let zoom_center = rect.min.to_vec2() + half_size - pan;
|
let zoom_center = rect.min.to_vec2() + half_size + pan;
|
||||||
|
|
||||||
// Calculate grid bounds in graph space
|
// Calculate grid bounds in graph space
|
||||||
// Screen to graph: (screen_pos - zoom_center) / zoom
|
// Screen to graph: (screen_pos - zoom_center) / zoom
|
||||||
|
|
@ -151,12 +151,31 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
||||||
|
|
||||||
// Allocate the rect and render the graph editor within it
|
// Allocate the rect and render the graph editor within it
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_ui_at_rect(rect, |ui| {
|
||||||
|
// Disable debug warning for unaligned widgets (happens when zoomed)
|
||||||
|
ui.style_mut().debug.show_unaligned = false;
|
||||||
|
|
||||||
// Check for scroll input to override library's default zoom behavior
|
// Check for scroll input to override library's default zoom behavior
|
||||||
let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
|
|
||||||
let modifiers = ui.input(|i| i.modifiers);
|
let modifiers = ui.input(|i| i.modifiers);
|
||||||
let has_scroll = scroll_delta != egui::Vec2::ZERO;
|
|
||||||
let has_ctrl = modifiers.ctrl || modifiers.command;
|
let has_ctrl = modifiers.ctrl || modifiers.command;
|
||||||
|
|
||||||
|
// When ctrl is held, check for raw scroll events in the events list
|
||||||
|
let scroll_delta = if has_ctrl {
|
||||||
|
// Sum up scroll events from the raw event list
|
||||||
|
ui.input(|i| {
|
||||||
|
let mut total_scroll = egui::Vec2::ZERO;
|
||||||
|
for event in &i.events {
|
||||||
|
if let egui::Event::MouseWheel { delta, .. } = event {
|
||||||
|
total_scroll += *delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_scroll
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ui.input(|i| i.smooth_scroll_delta)
|
||||||
|
};
|
||||||
|
let has_scroll = scroll_delta != egui::Vec2::ZERO;
|
||||||
|
|
||||||
|
|
||||||
// Save current zoom to detect if library changed it
|
// Save current zoom to detect if library changed it
|
||||||
let zoom_before = self.state.pan_zoom.zoom;
|
let zoom_before = self.state.pan_zoom.zoom;
|
||||||
let pan_before = self.state.pan_zoom.pan;
|
let pan_before = self.state.pan_zoom.pan;
|
||||||
|
|
@ -176,8 +195,18 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
||||||
// Override library's default scroll behavior:
|
// Override library's default scroll behavior:
|
||||||
// - Library uses scroll for zoom
|
// - Library uses scroll for zoom
|
||||||
// - We want: scroll = pan, ctrl+scroll = zoom
|
// - We want: scroll = pan, ctrl+scroll = zoom
|
||||||
if has_scroll && ui.rect_contains_pointer(rect) {
|
if has_scroll {
|
||||||
if !has_ctrl {
|
if has_ctrl {
|
||||||
|
// Ctrl+scroll: zoom (explicitly handle it instead of relying on library)
|
||||||
|
// First undo any zoom the library applied
|
||||||
|
if self.state.pan_zoom.zoom != zoom_before {
|
||||||
|
let undo_zoom = zoom_before / self.state.pan_zoom.zoom;
|
||||||
|
self.state.zoom(ui, undo_zoom);
|
||||||
|
}
|
||||||
|
// Now apply zoom based on scroll
|
||||||
|
let zoom_delta = (scroll_delta.y * 0.002).exp();
|
||||||
|
self.state.zoom(ui, zoom_delta);
|
||||||
|
} else {
|
||||||
// Scroll without ctrl: library zoomed, but we want pan instead
|
// Scroll without ctrl: library zoomed, but we want pan instead
|
||||||
// Undo the zoom and apply pan
|
// Undo the zoom and apply pan
|
||||||
if self.state.pan_zoom.zoom != zoom_before {
|
if self.state.pan_zoom.zoom != zoom_before {
|
||||||
|
|
@ -188,7 +217,6 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
||||||
// Apply pan
|
// Apply pan
|
||||||
self.state.pan_zoom.pan = pan_before + scroll_delta;
|
self.state.pan_zoom.pan = pan_before + scroll_delta;
|
||||||
}
|
}
|
||||||
// If ctrl is held, library already zoomed correctly, so do nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw menu button in top-left corner
|
// Draw menu button in top-left corner
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue