Group nodes
This commit is contained in:
parent
ffe7799b6a
commit
0bd933fd45
|
|
@ -1426,6 +1426,34 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
|
||||
Command::GraphSetGroups(track_id, groups) => {
|
||||
let graph = match self.project.get_track_mut(track_id) {
|
||||
Some(TrackNode::Midi(track)) => Some(&mut track.instrument_graph),
|
||||
Some(TrackNode::Audio(track)) => Some(&mut track.effects_graph),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(graph) = graph {
|
||||
graph.set_frontend_groups(groups);
|
||||
}
|
||||
}
|
||||
|
||||
Command::GraphSetGroupsInTemplate(track_id, voice_allocator_id, groups) => {
|
||||
use crate::audio::node_graph::nodes::VoiceAllocatorNode;
|
||||
let graph = match self.project.get_track_mut(track_id) {
|
||||
Some(TrackNode::Midi(track)) => Some(&mut track.instrument_graph),
|
||||
Some(TrackNode::Audio(track)) => Some(&mut track.effects_graph),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(graph) = graph {
|
||||
let node_idx = NodeIndex::new(voice_allocator_id as usize);
|
||||
if let Some(graph_node) = graph.get_node_mut(node_idx) {
|
||||
if let Some(va_node) = graph_node.as_any_mut().downcast_mut::<VoiceAllocatorNode>() {
|
||||
va_node.template_graph_mut().set_frontend_groups(groups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::GraphSavePreset(track_id, preset_path, preset_name, description, tags) => {
|
||||
let graph = match self.project.get_track(track_id) {
|
||||
Some(TrackNode::Midi(track)) => Some(&track.instrument_graph),
|
||||
|
|
@ -3029,6 +3057,16 @@ impl EngineController {
|
|||
let _ = self.command_tx.push(Command::GraphSetOutputNode(track_id, node_id));
|
||||
}
|
||||
|
||||
/// Set frontend-only group definitions on a track's graph
|
||||
pub fn graph_set_groups(&mut self, track_id: TrackId, groups: Vec<crate::audio::node_graph::preset::SerializedGroup>) {
|
||||
let _ = self.command_tx.push(Command::GraphSetGroups(track_id, groups));
|
||||
}
|
||||
|
||||
/// Set frontend-only group definitions on a VA template graph
|
||||
pub fn graph_set_groups_in_template(&mut self, track_id: TrackId, voice_allocator_id: u32, groups: Vec<crate::audio::node_graph::preset::SerializedGroup>) {
|
||||
let _ = self.command_tx.push(Command::GraphSetGroupsInTemplate(track_id, voice_allocator_id, groups));
|
||||
}
|
||||
|
||||
/// Save the current graph as a preset
|
||||
pub fn graph_save_preset(&mut self, track_id: TrackId, preset_path: String, preset_name: String, description: String, tags: Vec<String>) {
|
||||
let _ = self.command_tx.push(Command::GraphSavePreset(track_id, preset_path, preset_name, description, tags));
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ pub struct AudioGraph {
|
|||
|
||||
/// Cached topological sort order (invalidated on graph mutation)
|
||||
topo_cache: Option<Vec<NodeIndex>>,
|
||||
|
||||
/// Frontend-only group definitions (stored opaquely for persistence)
|
||||
frontend_groups: Vec<crate::audio::node_graph::preset::SerializedGroup>,
|
||||
}
|
||||
|
||||
impl AudioGraph {
|
||||
|
|
@ -117,6 +120,7 @@ impl AudioGraph {
|
|||
node_positions: std::collections::HashMap::new(),
|
||||
playback_time: 0.0,
|
||||
topo_cache: None,
|
||||
frontend_groups: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -645,6 +649,10 @@ impl AudioGraph {
|
|||
self.graph.node_weight(idx).map(|n| &*n.node)
|
||||
}
|
||||
|
||||
pub fn get_node_mut(&mut self, idx: NodeIndex) -> Option<&mut (dyn AudioNode + 'static)> {
|
||||
self.graph.node_weight_mut(idx).map(|n| &mut *n.node)
|
||||
}
|
||||
|
||||
/// Get oscilloscope data from a specific node
|
||||
pub fn get_oscilloscope_data(&self, idx: NodeIndex, sample_count: usize) -> Option<Vec<f32>> {
|
||||
self.get_node(idx).and_then(|node| node.get_oscilloscope_data(sample_count))
|
||||
|
|
@ -729,9 +737,17 @@ impl AudioGraph {
|
|||
}
|
||||
}
|
||||
|
||||
// Clone frontend groups
|
||||
new_graph.frontend_groups = self.frontend_groups.clone();
|
||||
|
||||
new_graph
|
||||
}
|
||||
|
||||
/// Set frontend-only group definitions (stored opaquely for persistence)
|
||||
pub fn set_frontend_groups(&mut self, groups: Vec<crate::audio::node_graph::preset::SerializedGroup>) {
|
||||
self.frontend_groups = groups;
|
||||
}
|
||||
|
||||
/// Serialize the graph to a preset
|
||||
pub fn to_preset(&self, name: impl Into<String>) -> crate::audio::node_graph::preset::GraphPreset {
|
||||
use crate::audio::node_graph::preset::{GraphPreset, SerializedConnection, SerializedNode};
|
||||
|
|
@ -897,6 +913,9 @@ impl AudioGraph {
|
|||
// Output node
|
||||
preset.output_node = self.output_node.map(|idx| idx.index() as u32);
|
||||
|
||||
// Frontend groups (stored opaquely)
|
||||
preset.groups = self.frontend_groups.clone();
|
||||
|
||||
preset
|
||||
}
|
||||
|
||||
|
|
@ -1118,6 +1137,9 @@ impl AudioGraph {
|
|||
}
|
||||
}
|
||||
|
||||
// Restore frontend groups (stored opaquely)
|
||||
graph.frontend_groups = preset.groups.clone();
|
||||
|
||||
Ok(graph)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ pub mod preset;
|
|||
|
||||
pub use graph::{Connection, GraphNode, AudioGraph};
|
||||
pub use node_trait::{AudioNode, cv_input_or_default};
|
||||
pub use preset::{GraphPreset, PresetMetadata, SerializedConnection, SerializedNode};
|
||||
pub use preset::{GraphPreset, PresetMetadata, SerializedConnection, SerializedNode, SerializedGroup, SerializedBoundaryConnection};
|
||||
pub use types::{ConnectionError, NodeCategory, NodePort, Parameter, ParameterUnit, SignalType};
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ pub struct GraphPreset {
|
|||
|
||||
/// Which node index is the audio output (None if not set)
|
||||
pub output_node: Option<u32>,
|
||||
|
||||
/// Frontend-only group definitions (backend stores opaquely, does not interpret)
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub groups: Vec<SerializedGroup>,
|
||||
}
|
||||
|
||||
/// Metadata about the preset
|
||||
|
|
@ -121,6 +125,32 @@ pub struct SerializedNode {
|
|||
pub sample_data: Option<SampleData>,
|
||||
}
|
||||
|
||||
/// Serialized group definition (frontend-only visual grouping, stored opaquely by backend)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SerializedGroup {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub member_nodes: Vec<u32>,
|
||||
pub position: (f32, f32),
|
||||
pub boundary_inputs: Vec<SerializedBoundaryConnection>,
|
||||
pub boundary_outputs: Vec<SerializedBoundaryConnection>,
|
||||
/// Parent group ID for nested groups (None = top-level group)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub parent_group_id: Option<u32>,
|
||||
}
|
||||
|
||||
/// Serialized boundary connection for group definitions
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SerializedBoundaryConnection {
|
||||
pub external_node: u32,
|
||||
pub external_port: usize,
|
||||
pub internal_node: u32,
|
||||
pub internal_port: usize,
|
||||
pub port_name: String,
|
||||
/// Signal type as string ("Audio", "Midi", "CV")
|
||||
pub data_type: String,
|
||||
}
|
||||
|
||||
/// Serialized connection between nodes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SerializedConnection {
|
||||
|
|
@ -152,6 +182,7 @@ impl GraphPreset {
|
|||
connections: Vec::new(),
|
||||
midi_targets: Vec::new(),
|
||||
output_node: None,
|
||||
groups: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,11 @@ pub enum Command {
|
|||
/// Set which node is the audio output (track_id, node_index)
|
||||
GraphSetOutputNode(TrackId, u32),
|
||||
|
||||
/// Set frontend-only group definitions on a track's graph (track_id, serialized groups)
|
||||
GraphSetGroups(TrackId, Vec<crate::audio::node_graph::preset::SerializedGroup>),
|
||||
/// Set frontend-only group definitions on a VA template graph (track_id, voice_allocator_id, serialized groups)
|
||||
GraphSetGroupsInTemplate(TrackId, u32, Vec<crate::audio::node_graph::preset::SerializedGroup>),
|
||||
|
||||
/// Save current graph as a preset (track_id, preset_path, preset_name, description, tags)
|
||||
GraphSavePreset(TrackId, String, String, String, Vec<String>),
|
||||
/// Load a preset into a track's graph (track_id, preset_path)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ name = "accesskit"
|
|||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_atspi_common"
|
||||
|
|
@ -145,6 +149,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
|
@ -1798,6 +1803,7 @@ checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e"
|
|||
dependencies = [
|
||||
"bytemuck",
|
||||
"emath",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1851,6 +1857,8 @@ dependencies = [
|
|||
"log",
|
||||
"nohash-hasher",
|
||||
"profiling",
|
||||
"ron",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
|
@ -1943,9 +1951,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "egui_node_graph2"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/PVDoriginal/egui_node_graph2#a25a90822d8f9c956e729f3907aad98f59fa46bc"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"serde",
|
||||
"slotmap",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
|
|
@ -1964,6 +1972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2022,6 +2031,17 @@ dependencies = [
|
|||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumn"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.110",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.33.3"
|
||||
|
|
@ -2038,6 +2058,7 @@ dependencies = [
|
|||
"nohash-hasher",
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3411,6 +3432,7 @@ dependencies = [
|
|||
name = "lightningbeam-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"base64 0.21.7",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
|
|
@ -5293,6 +5315,19 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.10.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
|
|
@ -5629,6 +5664,7 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ pub struct GraphResponse<UserResponse: UserResponseTrait, NodeData: NodeDataTrai
|
|||
pub cursor_in_editor: bool,
|
||||
/// Is the mouse currently hovering the node finder?
|
||||
pub cursor_in_finder: bool,
|
||||
/// Screen-space rects of all rendered nodes (for hit-testing)
|
||||
pub node_rects: NodeRects,
|
||||
}
|
||||
impl<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> Default
|
||||
for GraphResponse<UserResponse, NodeData>
|
||||
|
|
@ -89,6 +91,7 @@ impl<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> Default
|
|||
node_responses: Default::default(),
|
||||
cursor_in_editor: false,
|
||||
cursor_in_finder: false,
|
||||
node_rects: NodeRects::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -507,8 +510,8 @@ where
|
|||
);
|
||||
|
||||
self.selected_nodes = node_rects
|
||||
.into_iter()
|
||||
.filter_map(|(node_id, rect)| {
|
||||
.iter()
|
||||
.filter_map(|(&node_id, &rect)| {
|
||||
if selection_rect.intersects(rect) {
|
||||
Some(node_id)
|
||||
} else {
|
||||
|
|
@ -568,6 +571,7 @@ where
|
|||
node_responses: delayed_responses,
|
||||
cursor_in_editor,
|
||||
cursor_in_finder,
|
||||
node_rects,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,4 +215,9 @@ impl GraphBackend for AudioGraphBackend {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn query_template_state(&self, voice_allocator_id: u32) -> Result<String, String> {
|
||||
let mut controller = self.audio_controller.lock().unwrap();
|
||||
controller.query_template_state(self.track_id, voice_allocator_id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ pub trait GraphBackend: Send {
|
|||
input_node: BackendNodeId,
|
||||
input_port: usize,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// Get the state of a VoiceAllocator's template graph as JSON
|
||||
fn query_template_state(&self, voice_allocator_id: u32) -> Result<String, String>;
|
||||
}
|
||||
|
||||
/// Serializable graph state (for presets and save/load)
|
||||
|
|
|
|||
|
|
@ -863,7 +863,7 @@ impl NodeTemplateIter for AllNodeTemplates {
|
|||
NodeTemplate::Oscilloscope,
|
||||
// Advanced
|
||||
NodeTemplate::VoiceAllocator,
|
||||
NodeTemplate::Group,
|
||||
// Note: Group is not in the node finder — groups are created via Ctrl+G selection.
|
||||
// Note: TemplateInput/TemplateOutput are excluded from the default finder.
|
||||
// They are added dynamically when editing inside a subgraph.
|
||||
// Outputs
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue