Node graph improvements
This commit is contained in:
parent
c58192a7da
commit
d7176a13b7
|
|
@ -28,10 +28,14 @@ pub enum NodeTemplate {
|
|||
// Effects
|
||||
Filter,
|
||||
Gain,
|
||||
Delay,
|
||||
|
||||
// Utilities
|
||||
Adsr,
|
||||
Lfo,
|
||||
Mixer,
|
||||
Splitter,
|
||||
Constant,
|
||||
|
||||
// Outputs
|
||||
AudioOutput,
|
||||
|
|
@ -101,8 +105,12 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
NodeTemplate::Noise => "Noise".into(),
|
||||
NodeTemplate::Filter => "Filter".into(),
|
||||
NodeTemplate::Gain => "Gain".into(),
|
||||
NodeTemplate::Delay => "Delay".into(),
|
||||
NodeTemplate::Adsr => "ADSR".into(),
|
||||
NodeTemplate::Lfo => "LFO".into(),
|
||||
NodeTemplate::Mixer => "Mixer".into(),
|
||||
NodeTemplate::Splitter => "Splitter".into(),
|
||||
NodeTemplate::Constant => "Constant".into(),
|
||||
NodeTemplate::AudioOutput => "Audio Output".into(),
|
||||
}
|
||||
}
|
||||
|
|
@ -111,8 +119,9 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
match self {
|
||||
NodeTemplate::MidiInput | NodeTemplate::AudioInput => vec!["Inputs"],
|
||||
NodeTemplate::Oscillator | NodeTemplate::Noise => vec!["Generators"],
|
||||
NodeTemplate::Filter | NodeTemplate::Gain => vec!["Effects"],
|
||||
NodeTemplate::Adsr | NodeTemplate::Lfo => vec!["Utilities"],
|
||||
NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Delay => vec!["Effects"],
|
||||
NodeTemplate::Adsr | NodeTemplate::Lfo | NodeTemplate::Mixer
|
||||
| NodeTemplate::Splitter | NodeTemplate::Constant => vec!["Utilities"],
|
||||
NodeTemplate::AudioOutput => vec!["Outputs"],
|
||||
}
|
||||
}
|
||||
|
|
@ -133,34 +142,34 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
) {
|
||||
match self {
|
||||
NodeTemplate::Oscillator => {
|
||||
// FM input
|
||||
// V/Oct input (pitch control voltage)
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"FM".into(),
|
||||
DataType::Audio,
|
||||
"V/Oct".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
// Frequency parameter
|
||||
// FM input (frequency modulation)
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Freq".into(),
|
||||
"FM".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 440.0 },
|
||||
InputParamKind::ConstantOnly,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
// 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 => {
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Filter => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"In".into(),
|
||||
"Audio In".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
|
|
@ -168,18 +177,18 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
);
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Cutoff".into(),
|
||||
"Cutoff CV".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 1000.0 },
|
||||
InputParamKind::ConnectionOrConstant,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Gain => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"In".into(),
|
||||
"Audio In".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
|
|
@ -187,32 +196,65 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
);
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Gain".into(),
|
||||
"Gain CV".into(),
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 1.0 },
|
||||
InputParamKind::ConnectionOrConstant,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
true,
|
||||
);
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::Audio);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::Adsr => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"Gate".into(),
|
||||
DataType::Midi,
|
||||
DataType::CV,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
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 => {
|
||||
graph.add_output_param(node_id, "Out".into(), DataType::CV);
|
||||
graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
|
||||
}
|
||||
NodeTemplate::AudioOutput => {
|
||||
graph.add_input_param(
|
||||
node_id,
|
||||
"In".into(),
|
||||
"Audio In".into(),
|
||||
DataType::Audio,
|
||||
ValueType::Float { value: 0.0 },
|
||||
InputParamKind::ConnectionOnly,
|
||||
|
|
@ -220,10 +262,107 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
);
|
||||
}
|
||||
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 => {
|
||||
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::Filter,
|
||||
NodeTemplate::Gain,
|
||||
NodeTemplate::Delay,
|
||||
NodeTemplate::Adsr,
|
||||
NodeTemplate::Lfo,
|
||||
NodeTemplate::Mixer,
|
||||
NodeTemplate::Splitter,
|
||||
NodeTemplate::Constant,
|
||||
NodeTemplate::AudioOutput,
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl NodeGraphPane {
|
|||
|
||||
// Calculate zoom center (same as nodes - they zoom relative to viewport center)
|
||||
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
|
||||
// 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
|
||||
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
|
||||
let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
|
||||
let modifiers = ui.input(|i| i.modifiers);
|
||||
let has_scroll = scroll_delta != egui::Vec2::ZERO;
|
||||
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
|
||||
let zoom_before = self.state.pan_zoom.zoom;
|
||||
let pan_before = self.state.pan_zoom.pan;
|
||||
|
|
@ -176,8 +195,18 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
// Override library's default scroll behavior:
|
||||
// - Library uses scroll for zoom
|
||||
// - We want: scroll = pan, ctrl+scroll = zoom
|
||||
if has_scroll && ui.rect_contains_pointer(rect) {
|
||||
if !has_ctrl {
|
||||
if has_scroll {
|
||||
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
|
||||
// Undo the zoom and apply pan
|
||||
if self.state.pan_zoom.zoom != zoom_before {
|
||||
|
|
@ -188,7 +217,6 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
// Apply pan
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue