deduplicate node list
This commit is contained in:
parent
728b88365d
commit
3eba231447
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "vendor/NeuralAudio"]
|
||||
path = vendor/NeuralAudio
|
||||
url = https://github.com/mikeoliphant/NeuralAudio.git
|
||||
|
|
@ -1158,55 +1158,9 @@ impl Engine {
|
|||
|
||||
if let Some(graph) = graph {
|
||||
// Create the node based on type
|
||||
let node: Box<dyn crate::audio::node_graph::AudioNode> = match node_type.as_str() {
|
||||
"Oscillator" => Box::new(OscillatorNode::new("Oscillator".to_string())),
|
||||
"Gain" => Box::new(GainNode::new("Gain".to_string())),
|
||||
"Mixer" => Box::new(MixerNode::new("Mixer".to_string())),
|
||||
"Filter" => Box::new(FilterNode::new("Filter".to_string())),
|
||||
"SVF" => Box::new(SVFNode::new("SVF".to_string())),
|
||||
"ADSR" => Box::new(ADSRNode::new("ADSR".to_string())),
|
||||
"LFO" => Box::new(LFONode::new("LFO".to_string())),
|
||||
"NoiseGenerator" => Box::new(NoiseGeneratorNode::new("Noise".to_string())),
|
||||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".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())),
|
||||
"Compressor" => Box::new(CompressorNode::new("Compressor".to_string())),
|
||||
"Constant" => Box::new(ConstantNode::new("Constant".to_string())),
|
||||
"BpmDetector" => Box::new(BpmDetectorNode::new("BPM Detector".to_string())),
|
||||
"Beat" => Box::new(BeatNode::new("Beat".to_string())),
|
||||
"Arpeggiator" => Box::new(ArpeggiatorNode::new("Arpeggiator".to_string())),
|
||||
"Sequencer" => Box::new(SequencerNode::new("Sequencer".to_string())),
|
||||
"Script" => Box::new(ScriptNode::new("Script".to_string())),
|
||||
"EnvelopeFollower" => Box::new(EnvelopeFollowerNode::new("Envelope Follower".to_string())),
|
||||
"Limiter" => Box::new(LimiterNode::new("Limiter".to_string())),
|
||||
"Math" => Box::new(MathNode::new("Math".to_string())),
|
||||
"EQ" => Box::new(EQNode::new("EQ".to_string())),
|
||||
"Flanger" => Box::new(FlangerNode::new("Flanger".to_string())),
|
||||
"FMSynth" => Box::new(FMSynthNode::new("FM Synth".to_string())),
|
||||
"Phaser" => Box::new(PhaserNode::new("Phaser".to_string())),
|
||||
"BitCrusher" => Box::new(BitCrusherNode::new("Bit Crusher".to_string())),
|
||||
"Vocoder" => Box::new(VocoderNode::new("Vocoder".to_string())),
|
||||
"RingModulator" => Box::new(RingModulatorNode::new("Ring Modulator".to_string())),
|
||||
"SampleHold" => Box::new(SampleHoldNode::new("Sample & Hold".to_string())),
|
||||
"WavetableOscillator" => Box::new(WavetableOscillatorNode::new("Wavetable".to_string())),
|
||||
"SimpleSampler" => Box::new(SimpleSamplerNode::new("Sampler".to_string())),
|
||||
"SlewLimiter" => Box::new(SlewLimiterNode::new("Slew Limiter".to_string())),
|
||||
"MultiSampler" => Box::new(MultiSamplerNode::new("Multi Sampler".to_string())),
|
||||
"MidiInput" => Box::new(MidiInputNode::new("MIDI Input".to_string())),
|
||||
"MidiToCV" => Box::new(MidiToCVNode::new("MIDI→CV".to_string())),
|
||||
"AudioToCV" => Box::new(AudioToCVNode::new("Audio→CV".to_string())),
|
||||
"AudioInput" => Box::new(AudioInputNode::new("Audio Input".to_string())),
|
||||
"AutomationInput" => Box::new(AutomationInputNode::new("Automation".to_string())),
|
||||
"Oscilloscope" => Box::new(OscilloscopeNode::new("Oscilloscope".to_string())),
|
||||
"TemplateInput" => Box::new(TemplateInputNode::new("Template Input".to_string())),
|
||||
"TemplateOutput" => Box::new(TemplateOutputNode::new("Template Output".to_string())),
|
||||
"VoiceAllocator" => Box::new(VoiceAllocatorNode::new("VoiceAllocator".to_string(), self.sample_rate, 8192)),
|
||||
"AudioOutput" => Box::new(AudioOutputNode::new("Output".to_string())),
|
||||
_ => {
|
||||
let node = match crate::audio::node_graph::nodes::create_node(&node_type, self.sample_rate, 8192) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let _ = self.event_tx.push(AudioEvent::GraphConnectionError(
|
||||
track_id,
|
||||
format!("Unknown node type: {}", node_type)
|
||||
|
|
@ -1250,53 +1204,9 @@ impl Engine {
|
|||
let va_idx = NodeIndex::new(voice_allocator_id as usize);
|
||||
|
||||
// Create the node
|
||||
let node: Box<dyn crate::audio::node_graph::AudioNode> = match node_type.as_str() {
|
||||
"Oscillator" => Box::new(OscillatorNode::new("Oscillator".to_string())),
|
||||
"Gain" => Box::new(GainNode::new("Gain".to_string())),
|
||||
"Mixer" => Box::new(MixerNode::new("Mixer".to_string())),
|
||||
"Filter" => Box::new(FilterNode::new("Filter".to_string())),
|
||||
"SVF" => Box::new(SVFNode::new("SVF".to_string())),
|
||||
"ADSR" => Box::new(ADSRNode::new("ADSR".to_string())),
|
||||
"LFO" => Box::new(LFONode::new("LFO".to_string())),
|
||||
"NoiseGenerator" => Box::new(NoiseGeneratorNode::new("Noise".to_string())),
|
||||
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
|
||||
"Pan" => Box::new(PanNode::new("Pan".to_string())),
|
||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".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())),
|
||||
"Compressor" => Box::new(CompressorNode::new("Compressor".to_string())),
|
||||
"Constant" => Box::new(ConstantNode::new("Constant".to_string())),
|
||||
"BpmDetector" => Box::new(BpmDetectorNode::new("BPM Detector".to_string())),
|
||||
"Beat" => Box::new(BeatNode::new("Beat".to_string())),
|
||||
"Arpeggiator" => Box::new(ArpeggiatorNode::new("Arpeggiator".to_string())),
|
||||
"Sequencer" => Box::new(SequencerNode::new("Sequencer".to_string())),
|
||||
"Script" => Box::new(ScriptNode::new("Script".to_string())),
|
||||
"EnvelopeFollower" => Box::new(EnvelopeFollowerNode::new("Envelope Follower".to_string())),
|
||||
"Limiter" => Box::new(LimiterNode::new("Limiter".to_string())),
|
||||
"Math" => Box::new(MathNode::new("Math".to_string())),
|
||||
"EQ" => Box::new(EQNode::new("EQ".to_string())),
|
||||
"Flanger" => Box::new(FlangerNode::new("Flanger".to_string())),
|
||||
"FMSynth" => Box::new(FMSynthNode::new("FM Synth".to_string())),
|
||||
"Phaser" => Box::new(PhaserNode::new("Phaser".to_string())),
|
||||
"BitCrusher" => Box::new(BitCrusherNode::new("Bit Crusher".to_string())),
|
||||
"Vocoder" => Box::new(VocoderNode::new("Vocoder".to_string())),
|
||||
"RingModulator" => Box::new(RingModulatorNode::new("Ring Modulator".to_string())),
|
||||
"SampleHold" => Box::new(SampleHoldNode::new("Sample & Hold".to_string())),
|
||||
"WavetableOscillator" => Box::new(WavetableOscillatorNode::new("Wavetable".to_string())),
|
||||
"SimpleSampler" => Box::new(SimpleSamplerNode::new("Sampler".to_string())),
|
||||
"SlewLimiter" => Box::new(SlewLimiterNode::new("Slew Limiter".to_string())),
|
||||
"MultiSampler" => Box::new(MultiSamplerNode::new("Multi Sampler".to_string())),
|
||||
"MidiInput" => Box::new(MidiInputNode::new("MIDI Input".to_string())),
|
||||
"MidiToCV" => Box::new(MidiToCVNode::new("MIDI→CV".to_string())),
|
||||
"AudioToCV" => Box::new(AudioToCVNode::new("Audio→CV".to_string())),
|
||||
"AutomationInput" => Box::new(AutomationInputNode::new("Automation".to_string())),
|
||||
"Oscilloscope" => Box::new(OscilloscopeNode::new("Oscilloscope".to_string())),
|
||||
"TemplateInput" => Box::new(TemplateInputNode::new("Template Input".to_string())),
|
||||
"TemplateOutput" => Box::new(TemplateOutputNode::new("Template Output".to_string())),
|
||||
"AudioOutput" => Box::new(AudioOutputNode::new("Output".to_string())),
|
||||
_ => {
|
||||
let node = match crate::audio::node_graph::nodes::create_node(&node_type, self.sample_rate, 8192) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let _ = self.event_tx.push(AudioEvent::GraphConnectionError(
|
||||
track_id,
|
||||
format!("Unknown node type: {}", node_type)
|
||||
|
|
@ -1723,6 +1633,26 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
|
||||
Command::AmpSimLoadModel(track_id, node_id, model_path) => {
|
||||
use crate::audio::node_graph::nodes::AmpSimNode;
|
||||
|
||||
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(node_id as usize);
|
||||
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
|
||||
if let Some(amp_sim) = graph_node.node.as_any_mut().downcast_mut::<AmpSimNode>() {
|
||||
if let Err(e) = amp_sim.load_model(&model_path) {
|
||||
eprintln!("Failed to load NAM model: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::SamplerLoadSample(track_id, node_id, file_path) => {
|
||||
use crate::audio::node_graph::nodes::SimpleSamplerNode;
|
||||
|
||||
|
|
@ -3352,6 +3282,11 @@ impl EngineController {
|
|||
let _ = self.command_tx.push(Command::GraphSaveTemplatePreset(track_id, voice_allocator_id, preset_path, preset_name));
|
||||
}
|
||||
|
||||
/// Load a NAM model into an AmpSim node
|
||||
pub fn amp_sim_load_model(&mut self, track_id: TrackId, node_id: u32, model_path: String) {
|
||||
let _ = self.command_tx.push(Command::AmpSimLoadModel(track_id, node_id, model_path));
|
||||
}
|
||||
|
||||
/// Load a sample into a SimpleSampler node
|
||||
pub fn sampler_load_sample(&mut self, track_id: TrackId, node_id: u32, file_path: String) {
|
||||
let _ = self.command_tx.push(Command::SamplerLoadSample(track_id, node_id, file_path));
|
||||
|
|
|
|||
|
|
@ -917,6 +917,14 @@ impl AudioGraph {
|
|||
}
|
||||
}
|
||||
|
||||
// For AmpSim nodes, serialize the model path
|
||||
if node.node_type() == "AmpSim" {
|
||||
use crate::audio::node_graph::nodes::AmpSimNode;
|
||||
if let Some(amp_sim) = node.as_any().downcast_ref::<AmpSimNode>() {
|
||||
serialized.nam_model_path = amp_sim.model_path().map(|s| s.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Save position if available
|
||||
if let Some(pos) = self.get_node_position(node_idx) {
|
||||
serialized.set_position(pos.0, pos.1);
|
||||
|
|
@ -983,66 +991,19 @@ impl AudioGraph {
|
|||
// Create all nodes
|
||||
for serialized_node in &preset.nodes {
|
||||
// Create the node based on type
|
||||
let node: Box<dyn crate::audio::node_graph::AudioNode> = match serialized_node.node_type.as_str() {
|
||||
"Oscillator" => Box::new(OscillatorNode::new("Oscillator")),
|
||||
"Gain" => Box::new(GainNode::new("Gain")),
|
||||
"Mixer" => Box::new(MixerNode::new("Mixer")),
|
||||
"Filter" => Box::new(FilterNode::new("Filter")),
|
||||
"SVF" => Box::new(SVFNode::new("SVF")),
|
||||
"ADSR" => Box::new(ADSRNode::new("ADSR")),
|
||||
"LFO" => Box::new(LFONode::new("LFO")),
|
||||
"NoiseGenerator" => Box::new(NoiseGeneratorNode::new("Noise")),
|
||||
"Splitter" => Box::new(SplitterNode::new("Splitter")),
|
||||
"Pan" => Box::new(PanNode::new("Pan")),
|
||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer")),
|
||||
"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")),
|
||||
"Compressor" => Box::new(CompressorNode::new("Compressor")),
|
||||
"Constant" => Box::new(ConstantNode::new("Constant")),
|
||||
"Beat" => Box::new(BeatNode::new("Beat")),
|
||||
"Arpeggiator" => Box::new(ArpeggiatorNode::new("Arpeggiator")),
|
||||
"Sequencer" => Box::new(SequencerNode::new("Sequencer")),
|
||||
"Script" => Box::new(ScriptNode::new("Script")),
|
||||
"EnvelopeFollower" => Box::new(EnvelopeFollowerNode::new("Envelope Follower")),
|
||||
"Limiter" => Box::new(LimiterNode::new("Limiter")),
|
||||
"Math" => Box::new(MathNode::new("Math")),
|
||||
"EQ" => Box::new(EQNode::new("EQ")),
|
||||
"Flanger" => Box::new(FlangerNode::new("Flanger")),
|
||||
"FMSynth" => Box::new(FMSynthNode::new("FM Synth")),
|
||||
"Phaser" => Box::new(PhaserNode::new("Phaser")),
|
||||
"BitCrusher" => Box::new(BitCrusherNode::new("Bit Crusher")),
|
||||
"Vocoder" => Box::new(VocoderNode::new("Vocoder")),
|
||||
"RingModulator" => Box::new(RingModulatorNode::new("Ring Modulator")),
|
||||
"SampleHold" => Box::new(SampleHoldNode::new("Sample & Hold")),
|
||||
"WavetableOscillator" => Box::new(WavetableOscillatorNode::new("Wavetable")),
|
||||
"SimpleSampler" => Box::new(SimpleSamplerNode::new("Sampler")),
|
||||
"SlewLimiter" => Box::new(SlewLimiterNode::new("Slew Limiter")),
|
||||
"MultiSampler" => Box::new(MultiSamplerNode::new("Multi Sampler")),
|
||||
"MidiInput" => Box::new(MidiInputNode::new("MIDI Input")),
|
||||
"MidiToCV" => Box::new(MidiToCVNode::new("MIDI→CV")),
|
||||
"AudioToCV" => Box::new(AudioToCVNode::new("Audio→CV")),
|
||||
"AudioInput" => Box::new(AudioInputNode::new("Audio Input")),
|
||||
"AutomationInput" => Box::new(AutomationInputNode::new("Automation")),
|
||||
"Oscilloscope" => Box::new(OscilloscopeNode::new("Oscilloscope")),
|
||||
"TemplateInput" => Box::new(TemplateInputNode::new("Template Input")),
|
||||
"TemplateOutput" => Box::new(TemplateOutputNode::new("Template Output")),
|
||||
"VoiceAllocator" => {
|
||||
let mut va = VoiceAllocatorNode::new("VoiceAllocator", sample_rate, buffer_size);
|
||||
let mut node = crate::audio::node_graph::nodes::create_node(&serialized_node.node_type, sample_rate, buffer_size)
|
||||
.ok_or_else(|| format!("Unknown node type: {}", serialized_node.node_type))?;
|
||||
|
||||
// If there's a template graph, deserialize and set it
|
||||
// VoiceAllocator needs its template graph deserialized and set
|
||||
if serialized_node.node_type == "VoiceAllocator" {
|
||||
if let Some(ref template_preset) = serialized_node.template_graph {
|
||||
if let Some(va) = node.as_any_mut().downcast_mut::<VoiceAllocatorNode>() {
|
||||
let template_graph = Self::from_preset(template_preset, sample_rate, buffer_size, preset_base_path)?;
|
||||
*va.template_graph_mut() = template_graph;
|
||||
va.rebuild_voices();
|
||||
}
|
||||
|
||||
Box::new(va)
|
||||
}
|
||||
"AudioOutput" => Box::new(AudioOutputNode::new("Output")),
|
||||
_ => return Err(format!("Unknown node type: {}", serialized_node.node_type)),
|
||||
};
|
||||
}
|
||||
|
||||
let node_idx = graph.add_node(node);
|
||||
index_map.insert(serialized_node.id, node_idx);
|
||||
|
|
@ -1161,6 +1122,21 @@ impl AudioGraph {
|
|||
}
|
||||
}
|
||||
|
||||
// Restore NAM model for AmpSim nodes
|
||||
if let Some(ref model_path) = serialized_node.nam_model_path {
|
||||
if serialized_node.node_type == "AmpSim" {
|
||||
use crate::audio::node_graph::nodes::AmpSimNode;
|
||||
let resolved_path = resolve_sample_path(model_path);
|
||||
if let Some(graph_node) = graph.graph.node_weight_mut(node_idx) {
|
||||
if let Some(amp_sim) = graph_node.node.as_any_mut().downcast_mut::<AmpSimNode>() {
|
||||
if let Err(e) = amp_sim.load_model(&resolved_path) {
|
||||
eprintln!("Warning: failed to load NAM model {}: {}", resolved_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore position
|
||||
graph.set_node_position(node_idx, serialized_node.position.0, serialized_node.position.1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod amp_sim;
|
||||
mod adsr;
|
||||
mod arpeggiator;
|
||||
mod audio_input;
|
||||
|
|
@ -45,6 +46,7 @@ mod vocoder;
|
|||
mod voice_allocator;
|
||||
mod wavetable_oscillator;
|
||||
|
||||
pub use amp_sim::AmpSimNode;
|
||||
pub use adsr::ADSRNode;
|
||||
pub use arpeggiator::ArpeggiatorNode;
|
||||
pub use audio_input::AudioInputNode;
|
||||
|
|
@ -91,3 +93,61 @@ pub use template_io::{TemplateInputNode, TemplateOutputNode};
|
|||
pub use vocoder::VocoderNode;
|
||||
pub use voice_allocator::VoiceAllocatorNode;
|
||||
pub use wavetable_oscillator::WavetableOscillatorNode;
|
||||
|
||||
/// Create a node instance by type name string.
|
||||
///
|
||||
/// Returns `None` for unknown type names. `sample_rate` and `buffer_size`
|
||||
/// are only used by VoiceAllocator; other nodes ignore them.
|
||||
pub fn create_node(node_type: &str, sample_rate: u32, buffer_size: usize) -> Option<Box<dyn super::AudioNode>> {
|
||||
Some(match node_type {
|
||||
"Oscillator" => Box::new(OscillatorNode::new("Oscillator")),
|
||||
"Gain" => Box::new(GainNode::new("Gain")),
|
||||
"Mixer" => Box::new(MixerNode::new("Mixer")),
|
||||
"Filter" => Box::new(FilterNode::new("Filter")),
|
||||
"SVF" => Box::new(SVFNode::new("SVF")),
|
||||
"ADSR" => Box::new(ADSRNode::new("ADSR")),
|
||||
"LFO" => Box::new(LFONode::new("LFO")),
|
||||
"NoiseGenerator" => Box::new(NoiseGeneratorNode::new("Noise")),
|
||||
"Splitter" => Box::new(SplitterNode::new("Splitter")),
|
||||
"Pan" => Box::new(PanNode::new("Pan")),
|
||||
"Quantizer" => Box::new(QuantizerNode::new("Quantizer")),
|
||||
"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")),
|
||||
"Compressor" => Box::new(CompressorNode::new("Compressor")),
|
||||
"Constant" => Box::new(ConstantNode::new("Constant")),
|
||||
"BpmDetector" => Box::new(BpmDetectorNode::new("BPM Detector")),
|
||||
"Beat" => Box::new(BeatNode::new("Beat")),
|
||||
"Arpeggiator" => Box::new(ArpeggiatorNode::new("Arpeggiator")),
|
||||
"Sequencer" => Box::new(SequencerNode::new("Sequencer")),
|
||||
"Script" => Box::new(ScriptNode::new("Script")),
|
||||
"EnvelopeFollower" => Box::new(EnvelopeFollowerNode::new("Envelope Follower")),
|
||||
"Limiter" => Box::new(LimiterNode::new("Limiter")),
|
||||
"Math" => Box::new(MathNode::new("Math")),
|
||||
"EQ" => Box::new(EQNode::new("EQ")),
|
||||
"Flanger" => Box::new(FlangerNode::new("Flanger")),
|
||||
"FMSynth" => Box::new(FMSynthNode::new("FM Synth")),
|
||||
"Phaser" => Box::new(PhaserNode::new("Phaser")),
|
||||
"BitCrusher" => Box::new(BitCrusherNode::new("Bit Crusher")),
|
||||
"Vocoder" => Box::new(VocoderNode::new("Vocoder")),
|
||||
"RingModulator" => Box::new(RingModulatorNode::new("Ring Modulator")),
|
||||
"SampleHold" => Box::new(SampleHoldNode::new("Sample & Hold")),
|
||||
"WavetableOscillator" => Box::new(WavetableOscillatorNode::new("Wavetable")),
|
||||
"SimpleSampler" => Box::new(SimpleSamplerNode::new("Sampler")),
|
||||
"SlewLimiter" => Box::new(SlewLimiterNode::new("Slew Limiter")),
|
||||
"MultiSampler" => Box::new(MultiSamplerNode::new("Multi Sampler")),
|
||||
"MidiInput" => Box::new(MidiInputNode::new("MIDI Input")),
|
||||
"MidiToCV" => Box::new(MidiToCVNode::new("MIDI→CV")),
|
||||
"AudioToCV" => Box::new(AudioToCVNode::new("Audio→CV")),
|
||||
"AudioInput" => Box::new(AudioInputNode::new("Audio Input")),
|
||||
"AutomationInput" => Box::new(AutomationInputNode::new("Automation")),
|
||||
"Oscilloscope" => Box::new(OscilloscopeNode::new("Oscilloscope")),
|
||||
"TemplateInput" => Box::new(TemplateInputNode::new("Template Input")),
|
||||
"TemplateOutput" => Box::new(TemplateOutputNode::new("Template Output")),
|
||||
"VoiceAllocator" => Box::new(VoiceAllocatorNode::new("VoiceAllocator", sample_rate, buffer_size)),
|
||||
"AmpSim" => Box::new(AmpSimNode::new("Amp Sim")),
|
||||
"AudioOutput" => Box::new(AudioOutputNode::new("Output")),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,132 +16,140 @@ pub enum DataType {
|
|||
CV,
|
||||
}
|
||||
|
||||
/// Macro that defines `NodeTemplate` enum and generates metadata methods from a single table.
|
||||
///
|
||||
/// Each row: `variant, backend_name, display_label, category, in_finder;`
|
||||
///
|
||||
/// Generated methods:
|
||||
/// - `backend_type_name() -> &'static str`
|
||||
/// - `display_label() -> &'static str` (used by `node_finder_label`)
|
||||
/// - `category() -> &'static str` (used by `node_finder_categories`)
|
||||
/// - `in_finder() -> bool`
|
||||
/// - `from_backend_name(s: &str) -> Option<NodeTemplate>`
|
||||
/// - `all_finder_kinds() -> Vec<NodeTemplate>` (only variants with `in_finder = true`)
|
||||
macro_rules! node_templates {
|
||||
(
|
||||
$( $variant:ident, $backend:literal, $label:literal, $category:literal, $in_finder:literal );+
|
||||
$(;)?
|
||||
) => {
|
||||
/// Node templates - types of nodes that can be created
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum NodeTemplate {
|
||||
// Inputs
|
||||
MidiInput,
|
||||
AudioInput,
|
||||
AutomationInput,
|
||||
Beat,
|
||||
|
||||
// Generators
|
||||
Oscillator,
|
||||
WavetableOscillator,
|
||||
FmSynth,
|
||||
Noise,
|
||||
SimpleSampler,
|
||||
MultiSampler,
|
||||
|
||||
// Effects
|
||||
Filter,
|
||||
Svf,
|
||||
Gain,
|
||||
Echo,
|
||||
Reverb,
|
||||
Chorus,
|
||||
Flanger,
|
||||
Phaser,
|
||||
Distortion,
|
||||
BitCrusher,
|
||||
Compressor,
|
||||
Limiter,
|
||||
Eq,
|
||||
Pan,
|
||||
RingModulator,
|
||||
Vocoder,
|
||||
|
||||
// Utilities
|
||||
Adsr,
|
||||
Lfo,
|
||||
Mixer,
|
||||
Splitter,
|
||||
Constant,
|
||||
MidiToCv,
|
||||
AudioToCv,
|
||||
Arpeggiator,
|
||||
Sequencer,
|
||||
Math,
|
||||
SampleHold,
|
||||
SlewLimiter,
|
||||
Quantizer,
|
||||
EnvelopeFollower,
|
||||
BpmDetector,
|
||||
Mod,
|
||||
|
||||
// Scripting
|
||||
Script,
|
||||
|
||||
// Analysis
|
||||
Oscilloscope,
|
||||
|
||||
// Advanced
|
||||
VoiceAllocator,
|
||||
Group,
|
||||
|
||||
// Subgraph I/O (only visible when editing inside a container node)
|
||||
TemplateInput,
|
||||
TemplateOutput,
|
||||
|
||||
// Outputs
|
||||
AudioOutput,
|
||||
$($variant),+
|
||||
}
|
||||
|
||||
impl NodeTemplate {
|
||||
/// Returns the backend-compatible type name string (matches daw-backend match arms)
|
||||
pub fn backend_type_name(&self) -> &'static str {
|
||||
match self {
|
||||
NodeTemplate::MidiInput => "MidiInput",
|
||||
NodeTemplate::AudioInput => "AudioInput",
|
||||
NodeTemplate::AutomationInput => "AutomationInput",
|
||||
NodeTemplate::Oscillator => "Oscillator",
|
||||
NodeTemplate::WavetableOscillator => "WavetableOscillator",
|
||||
NodeTemplate::FmSynth => "FMSynth",
|
||||
NodeTemplate::Noise => "NoiseGenerator",
|
||||
NodeTemplate::SimpleSampler => "SimpleSampler",
|
||||
NodeTemplate::MultiSampler => "MultiSampler",
|
||||
NodeTemplate::Filter => "Filter",
|
||||
NodeTemplate::Svf => "SVF",
|
||||
NodeTemplate::Gain => "Gain",
|
||||
NodeTemplate::Echo => "Echo",
|
||||
NodeTemplate::Reverb => "Reverb",
|
||||
NodeTemplate::Chorus => "Chorus",
|
||||
NodeTemplate::Flanger => "Flanger",
|
||||
NodeTemplate::Phaser => "Phaser",
|
||||
NodeTemplate::Distortion => "Distortion",
|
||||
NodeTemplate::BitCrusher => "BitCrusher",
|
||||
NodeTemplate::Compressor => "Compressor",
|
||||
NodeTemplate::Limiter => "Limiter",
|
||||
NodeTemplate::Eq => "EQ",
|
||||
NodeTemplate::Pan => "Pan",
|
||||
NodeTemplate::RingModulator => "RingModulator",
|
||||
NodeTemplate::Vocoder => "Vocoder",
|
||||
NodeTemplate::Adsr => "ADSR",
|
||||
NodeTemplate::Lfo => "LFO",
|
||||
NodeTemplate::Mixer => "Mixer",
|
||||
NodeTemplate::Splitter => "Splitter",
|
||||
NodeTemplate::Constant => "Constant",
|
||||
NodeTemplate::MidiToCv => "MidiToCV",
|
||||
NodeTemplate::AudioToCv => "AudioToCV",
|
||||
NodeTemplate::Arpeggiator => "Arpeggiator",
|
||||
NodeTemplate::Sequencer => "Sequencer",
|
||||
NodeTemplate::Math => "Math",
|
||||
NodeTemplate::SampleHold => "SampleHold",
|
||||
NodeTemplate::SlewLimiter => "SlewLimiter",
|
||||
NodeTemplate::Quantizer => "Quantizer",
|
||||
NodeTemplate::EnvelopeFollower => "EnvelopeFollower",
|
||||
NodeTemplate::BpmDetector => "BpmDetector",
|
||||
NodeTemplate::Beat => "Beat",
|
||||
NodeTemplate::Script => "Script",
|
||||
NodeTemplate::Mod => "Mod",
|
||||
NodeTemplate::Oscilloscope => "Oscilloscope",
|
||||
NodeTemplate::VoiceAllocator => "VoiceAllocator",
|
||||
NodeTemplate::Group => "Group",
|
||||
NodeTemplate::TemplateInput => "TemplateInput",
|
||||
NodeTemplate::TemplateOutput => "TemplateOutput",
|
||||
NodeTemplate::AudioOutput => "AudioOutput",
|
||||
$(NodeTemplate::$variant => $backend),+
|
||||
}
|
||||
}
|
||||
|
||||
/// Display label for the node finder
|
||||
fn display_label(&self) -> &'static str {
|
||||
match self {
|
||||
$(NodeTemplate::$variant => $label),+
|
||||
}
|
||||
}
|
||||
|
||||
/// Category for the node finder
|
||||
fn category(&self) -> &'static str {
|
||||
match self {
|
||||
$(NodeTemplate::$variant => $category),+
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this node appears in the node finder
|
||||
#[allow(dead_code)]
|
||||
fn in_finder(&self) -> bool {
|
||||
match self {
|
||||
$(NodeTemplate::$variant => $in_finder),+
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a backend type name string to a NodeTemplate variant.
|
||||
///
|
||||
/// Handles canonical names from the table plus legacy aliases.
|
||||
pub fn from_backend_name(s: &str) -> Option<NodeTemplate> {
|
||||
match s {
|
||||
$($backend => Some(NodeTemplate::$variant),)+
|
||||
// Legacy / alternate aliases
|
||||
"Delay" => Some(NodeTemplate::Echo),
|
||||
"BPMDetector" => Some(NodeTemplate::BpmDetector),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// All node templates that should appear in the default node finder
|
||||
pub fn all_finder_kinds() -> Vec<NodeTemplate> {
|
||||
let mut v = Vec::new();
|
||||
$(if $in_finder { v.push(NodeTemplate::$variant); })+
|
||||
v
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
node_templates! {
|
||||
// Inputs
|
||||
MidiInput, "MidiInput", "MIDI Input", "Inputs", true;
|
||||
AudioInput, "AudioInput", "Audio Input", "Inputs", true;
|
||||
AutomationInput, "AutomationInput", "Automation Input", "Inputs", true;
|
||||
Beat, "Beat", "Beat", "Inputs", true;
|
||||
// Generators
|
||||
Oscillator, "Oscillator", "Oscillator", "Generators", true;
|
||||
WavetableOscillator,"WavetableOscillator","Wavetable Oscillator","Generators", true;
|
||||
FmSynth, "FMSynth", "FM Synth", "Generators", true;
|
||||
Noise, "NoiseGenerator", "Noise Generator", "Generators", true;
|
||||
SimpleSampler, "SimpleSampler", "Simple Sampler", "Generators", true;
|
||||
MultiSampler, "MultiSampler", "Multi Sampler", "Generators", true;
|
||||
// Effects
|
||||
Filter, "Filter", "Filter", "Effects", true;
|
||||
Svf, "SVF", "SVF", "Effects", true;
|
||||
Gain, "Gain", "Gain", "Effects", true;
|
||||
Echo, "Echo", "Echo", "Effects", true;
|
||||
Reverb, "Reverb", "Reverb", "Effects", true;
|
||||
Chorus, "Chorus", "Chorus", "Effects", true;
|
||||
Flanger, "Flanger", "Flanger", "Effects", true;
|
||||
Phaser, "Phaser", "Phaser", "Effects", true;
|
||||
Distortion, "Distortion", "Distortion", "Effects", true;
|
||||
AmpSim, "AmpSim", "Amp Sim", "Effects", true;
|
||||
BitCrusher, "BitCrusher", "Bit Crusher", "Effects", true;
|
||||
Compressor, "Compressor", "Compressor", "Effects", true;
|
||||
Limiter, "Limiter", "Limiter", "Effects", true;
|
||||
Eq, "EQ", "EQ", "Effects", true;
|
||||
Pan, "Pan", "Pan", "Effects", true;
|
||||
RingModulator, "RingModulator", "Ring Modulator", "Effects", true;
|
||||
Vocoder, "Vocoder", "Vocoder", "Effects", true;
|
||||
// Utilities
|
||||
Adsr, "ADSR", "ADSR Envelope", "Utilities", true;
|
||||
Lfo, "LFO", "LFO", "Utilities", true;
|
||||
Mixer, "Mixer", "Mixer", "Utilities", true;
|
||||
Splitter, "Splitter", "Splitter", "Utilities", true;
|
||||
Constant, "Constant", "Constant", "Utilities", true;
|
||||
MidiToCv, "MidiToCV", "MIDI to CV", "Utilities", true;
|
||||
AudioToCv, "AudioToCV", "Audio to CV", "Utilities", true;
|
||||
Arpeggiator, "Arpeggiator", "Arpeggiator", "Utilities", true;
|
||||
Sequencer, "Sequencer", "Step Sequencer", "Utilities", true;
|
||||
Math, "Math", "Math", "Utilities", true;
|
||||
SampleHold, "SampleHold", "Sample & Hold", "Utilities", true;
|
||||
SlewLimiter, "SlewLimiter", "Slew Limiter", "Utilities", true;
|
||||
Quantizer, "Quantizer", "Quantizer", "Utilities", true;
|
||||
EnvelopeFollower, "EnvelopeFollower", "Envelope Follower", "Utilities", true;
|
||||
BpmDetector, "BpmDetector", "BPM Detector", "Utilities", true;
|
||||
Mod, "Mod", "Modulator", "Utilities", true;
|
||||
// Analysis
|
||||
Oscilloscope, "Oscilloscope", "Oscilloscope", "Analysis", true;
|
||||
// Advanced
|
||||
VoiceAllocator, "VoiceAllocator", "Voice Allocator", "Advanced", true;
|
||||
Script, "Script", "Script", "Advanced", true;
|
||||
Group, "Group", "Group", "Advanced", false;
|
||||
// Subgraph I/O
|
||||
TemplateInput, "TemplateInput", "Template Input", "Subgraph I/O", false;
|
||||
TemplateOutput, "TemplateOutput", "Template Output", "Subgraph I/O", false;
|
||||
// Outputs
|
||||
AudioOutput, "AudioOutput", "Audio Output", "Outputs", true;
|
||||
}
|
||||
|
||||
/// Custom node data
|
||||
|
|
@ -166,6 +174,9 @@ pub struct NodeData {
|
|||
/// Display names of loaded samples per slot (slot_index → display name)
|
||||
#[serde(skip)]
|
||||
pub script_sample_names: HashMap<usize, String>,
|
||||
/// Display name of loaded NAM model (for AmpSim nodes)
|
||||
#[serde(default)]
|
||||
pub nam_model_name: Option<String>,
|
||||
}
|
||||
|
||||
fn default_root_note() -> u8 { 69 }
|
||||
|
|
@ -180,6 +191,7 @@ impl NodeData {
|
|||
ui_declaration: None,
|
||||
sample_slot_names: Vec::new(),
|
||||
script_sample_names: HashMap::new(),
|
||||
nam_model_name: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -265,6 +277,8 @@ pub struct GraphState {
|
|||
pub pending_draw_param_changes: Vec<(NodeId, u32, f32)>,
|
||||
/// Active sample import dialog (folder import with heuristic mapping)
|
||||
pub sample_import_dialog: Option<crate::sample_import_dialog::SampleImportDialog>,
|
||||
/// Pending AmpSim model load (node_id, backend_node_id) — triggers file dialog for .nam
|
||||
pub pending_amp_sim_load: Option<(NodeId, u32)>,
|
||||
}
|
||||
|
||||
impl Default for GraphState {
|
||||
|
|
@ -288,6 +302,7 @@ impl Default for GraphState {
|
|||
draw_vms: HashMap::new(),
|
||||
pending_draw_param_changes: Vec::new(),
|
||||
sample_import_dialog: None,
|
||||
pending_amp_sim_load: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -393,87 +408,11 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
type CategoryType = &'static str;
|
||||
|
||||
fn node_finder_label(&self, _user_state: &mut Self::UserState) -> std::borrow::Cow<'_, str> {
|
||||
match self {
|
||||
// Inputs
|
||||
NodeTemplate::MidiInput => "MIDI Input".into(),
|
||||
NodeTemplate::AudioInput => "Audio Input".into(),
|
||||
NodeTemplate::AutomationInput => "Automation Input".into(),
|
||||
// Generators
|
||||
NodeTemplate::Oscillator => "Oscillator".into(),
|
||||
NodeTemplate::WavetableOscillator => "Wavetable Oscillator".into(),
|
||||
NodeTemplate::FmSynth => "FM Synth".into(),
|
||||
NodeTemplate::Noise => "Noise Generator".into(),
|
||||
NodeTemplate::SimpleSampler => "Simple Sampler".into(),
|
||||
NodeTemplate::MultiSampler => "Multi Sampler".into(),
|
||||
// Effects
|
||||
NodeTemplate::Filter => "Filter".into(),
|
||||
NodeTemplate::Svf => "SVF".into(),
|
||||
NodeTemplate::Gain => "Gain".into(),
|
||||
NodeTemplate::Echo => "Echo".into(),
|
||||
NodeTemplate::Reverb => "Reverb".into(),
|
||||
NodeTemplate::Chorus => "Chorus".into(),
|
||||
NodeTemplate::Flanger => "Flanger".into(),
|
||||
NodeTemplate::Phaser => "Phaser".into(),
|
||||
NodeTemplate::Distortion => "Distortion".into(),
|
||||
NodeTemplate::BitCrusher => "Bit Crusher".into(),
|
||||
NodeTemplate::Compressor => "Compressor".into(),
|
||||
NodeTemplate::Limiter => "Limiter".into(),
|
||||
NodeTemplate::Eq => "EQ".into(),
|
||||
NodeTemplate::Pan => "Pan".into(),
|
||||
NodeTemplate::RingModulator => "Ring Modulator".into(),
|
||||
NodeTemplate::Vocoder => "Vocoder".into(),
|
||||
// Utilities
|
||||
NodeTemplate::Adsr => "ADSR Envelope".into(),
|
||||
NodeTemplate::Lfo => "LFO".into(),
|
||||
NodeTemplate::Mixer => "Mixer".into(),
|
||||
NodeTemplate::Splitter => "Splitter".into(),
|
||||
NodeTemplate::Constant => "Constant".into(),
|
||||
NodeTemplate::MidiToCv => "MIDI to CV".into(),
|
||||
NodeTemplate::AudioToCv => "Audio to CV".into(),
|
||||
NodeTemplate::Arpeggiator => "Arpeggiator".into(),
|
||||
NodeTemplate::Sequencer => "Step Sequencer".into(),
|
||||
NodeTemplate::Math => "Math".into(),
|
||||
NodeTemplate::SampleHold => "Sample & Hold".into(),
|
||||
NodeTemplate::SlewLimiter => "Slew Limiter".into(),
|
||||
NodeTemplate::Quantizer => "Quantizer".into(),
|
||||
NodeTemplate::EnvelopeFollower => "Envelope Follower".into(),
|
||||
NodeTemplate::BpmDetector => "BPM Detector".into(),
|
||||
NodeTemplate::Beat => "Beat".into(),
|
||||
NodeTemplate::Mod => "Modulator".into(),
|
||||
// Scripting
|
||||
NodeTemplate::Script => "Script".into(),
|
||||
// Analysis
|
||||
NodeTemplate::Oscilloscope => "Oscilloscope".into(),
|
||||
// Advanced
|
||||
NodeTemplate::VoiceAllocator => "Voice Allocator".into(),
|
||||
NodeTemplate::Group => "Group".into(),
|
||||
// Subgraph I/O
|
||||
NodeTemplate::TemplateInput => "Template Input".into(),
|
||||
NodeTemplate::TemplateOutput => "Template Output".into(),
|
||||
// Outputs
|
||||
NodeTemplate::AudioOutput => "Audio Output".into(),
|
||||
}
|
||||
self.display_label().into()
|
||||
}
|
||||
|
||||
fn node_finder_categories(&self, _user_state: &mut Self::UserState) -> Vec<&'static str> {
|
||||
match self {
|
||||
NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput | NodeTemplate::Beat => vec!["Inputs"],
|
||||
NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth
|
||||
| NodeTemplate::Noise | NodeTemplate::SimpleSampler | NodeTemplate::MultiSampler => vec!["Generators"],
|
||||
NodeTemplate::Filter | NodeTemplate::Svf | 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"],
|
||||
NodeTemplate::Adsr | NodeTemplate::Lfo | NodeTemplate::Mixer | NodeTemplate::Splitter
|
||||
| NodeTemplate::Constant | NodeTemplate::MidiToCv | NodeTemplate::AudioToCv | NodeTemplate::Arpeggiator | NodeTemplate::Sequencer | NodeTemplate::Math
|
||||
| NodeTemplate::SampleHold | NodeTemplate::SlewLimiter | NodeTemplate::Quantizer
|
||||
| NodeTemplate::EnvelopeFollower | NodeTemplate::BpmDetector | NodeTemplate::Mod => vec!["Utilities"],
|
||||
NodeTemplate::Script => vec!["Advanced"],
|
||||
NodeTemplate::Oscilloscope => vec!["Analysis"],
|
||||
NodeTemplate::VoiceAllocator | NodeTemplate::Group => vec!["Advanced"],
|
||||
NodeTemplate::TemplateInput | NodeTemplate::TemplateOutput => vec!["Subgraph I/O"],
|
||||
NodeTemplate::AudioOutput => vec!["Outputs"],
|
||||
}
|
||||
vec![self.category()]
|
||||
}
|
||||
|
||||
fn node_graph_label(&self, user_state: &mut Self::UserState) -> String {
|
||||
|
|
@ -735,6 +674,16 @@ impl NodeTemplateTrait for NodeTemplate {
|
|||
ValueType::float_param(1.0, 0.0, 1.0, "", 3, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::AmpSim => {
|
||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_input_param(node_id, "Input Gain".into(), DataType::CV,
|
||||
ValueType::float_param(1.0, 0.0, 4.0, "", 0, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Output Gain".into(), DataType::CV,
|
||||
ValueType::float_param(1.0, 0.0, 4.0, "", 1, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_input_param(node_id, "Mix".into(), DataType::CV,
|
||||
ValueType::float_param(1.0, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true);
|
||||
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
|
||||
}
|
||||
NodeTemplate::BitCrusher => {
|
||||
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true);
|
||||
graph.add_input_param(node_id, "Bit Depth".into(), DataType::CV,
|
||||
|
|
@ -1449,6 +1398,12 @@ impl NodeDataTrait for NodeData {
|
|||
&mut user_state.pending_draw_param_changes,
|
||||
);
|
||||
}
|
||||
} else if self.template == NodeTemplate::AmpSim {
|
||||
let backend_node_id = user_state.node_backend_ids.get(&node_id).copied().unwrap_or(0);
|
||||
let button_text = self.nam_model_name.as_deref().unwrap_or("Load Model...");
|
||||
if ui.button(button_text).clicked() {
|
||||
user_state.pending_amp_sim_load = Some((node_id, backend_node_id));
|
||||
}
|
||||
} else {
|
||||
ui.label("");
|
||||
}
|
||||
|
|
@ -1677,7 +1632,7 @@ impl NodeTemplateIter for VoiceAllocatorNodeTemplates {
|
|||
type Item = NodeTemplate;
|
||||
|
||||
fn all_kinds(&self) -> Vec<Self::Item> {
|
||||
let mut templates = AllNodeTemplates.all_kinds();
|
||||
let mut templates = NodeTemplate::all_finder_kinds();
|
||||
// VA nodes can't be nested — signals inside a VA are monophonic
|
||||
templates.retain(|t| *t != NodeTemplate::VoiceAllocator);
|
||||
templates.push(NodeTemplate::TemplateInput);
|
||||
|
|
@ -1690,7 +1645,7 @@ impl NodeTemplateIter for SubgraphNodeTemplates {
|
|||
type Item = NodeTemplate;
|
||||
|
||||
fn all_kinds(&self) -> Vec<Self::Item> {
|
||||
let mut templates = AllNodeTemplates.all_kinds();
|
||||
let mut templates = NodeTemplate::all_finder_kinds();
|
||||
templates.push(NodeTemplate::TemplateInput);
|
||||
templates.push(NodeTemplate::TemplateOutput);
|
||||
templates
|
||||
|
|
@ -1701,63 +1656,6 @@ impl NodeTemplateIter for AllNodeTemplates {
|
|||
type Item = NodeTemplate;
|
||||
|
||||
fn all_kinds(&self) -> Vec<Self::Item> {
|
||||
vec![
|
||||
// Inputs
|
||||
NodeTemplate::MidiInput,
|
||||
NodeTemplate::AudioInput,
|
||||
NodeTemplate::AutomationInput,
|
||||
// Generators
|
||||
NodeTemplate::Oscillator,
|
||||
NodeTemplate::WavetableOscillator,
|
||||
NodeTemplate::FmSynth,
|
||||
NodeTemplate::Noise,
|
||||
NodeTemplate::SimpleSampler,
|
||||
NodeTemplate::MultiSampler,
|
||||
// Effects
|
||||
NodeTemplate::Filter,
|
||||
NodeTemplate::Svf,
|
||||
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,
|
||||
// Utilities
|
||||
NodeTemplate::Adsr,
|
||||
NodeTemplate::Lfo,
|
||||
NodeTemplate::Mixer,
|
||||
NodeTemplate::Splitter,
|
||||
NodeTemplate::Constant,
|
||||
NodeTemplate::MidiToCv,
|
||||
NodeTemplate::AudioToCv,
|
||||
NodeTemplate::Arpeggiator,
|
||||
NodeTemplate::Sequencer,
|
||||
NodeTemplate::Math,
|
||||
NodeTemplate::SampleHold,
|
||||
NodeTemplate::SlewLimiter,
|
||||
NodeTemplate::Quantizer,
|
||||
NodeTemplate::EnvelopeFollower,
|
||||
NodeTemplate::BpmDetector,
|
||||
NodeTemplate::Beat,
|
||||
NodeTemplate::Mod,
|
||||
// Analysis
|
||||
NodeTemplate::Oscilloscope,
|
||||
// Advanced
|
||||
NodeTemplate::VoiceAllocator,
|
||||
NodeTemplate::Script,
|
||||
// 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
|
||||
NodeTemplate::AudioOutput,
|
||||
]
|
||||
NodeTemplate::all_finder_kinds()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1385,7 +1385,7 @@ impl NodeGraphPane {
|
|||
// Create nodes in frontend
|
||||
self.pending_script_resolutions.clear();
|
||||
for node in &graph_state.nodes {
|
||||
let node_template = match Self::backend_type_to_template(&node.node_type) {
|
||||
let node_template = match NodeTemplate::from_backend_name(&node.node_type) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
eprintln!("Unknown node type: {}", node.node_type);
|
||||
|
|
@ -1836,7 +1836,7 @@ impl NodeGraphPane {
|
|||
continue;
|
||||
}
|
||||
|
||||
let node_template = match Self::backend_type_to_template(&node.node_type) {
|
||||
let node_template = match NodeTemplate::from_backend_name(&node.node_type) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
eprintln!("Unknown node type: {}", node.node_type);
|
||||
|
|
@ -2101,62 +2101,6 @@ impl NodeGraphPane {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper: map backend node type string to frontend NodeTemplate
|
||||
fn backend_type_to_template(node_type: &str) -> Option<NodeTemplate> {
|
||||
match node_type {
|
||||
"MidiInput" => Some(NodeTemplate::MidiInput),
|
||||
"AudioInput" => Some(NodeTemplate::AudioInput),
|
||||
"AutomationInput" => Some(NodeTemplate::AutomationInput),
|
||||
"Oscillator" => Some(NodeTemplate::Oscillator),
|
||||
"WavetableOscillator" => Some(NodeTemplate::WavetableOscillator),
|
||||
"FMSynth" => Some(NodeTemplate::FmSynth),
|
||||
"NoiseGenerator" => Some(NodeTemplate::Noise),
|
||||
"SimpleSampler" => Some(NodeTemplate::SimpleSampler),
|
||||
"MultiSampler" => Some(NodeTemplate::MultiSampler),
|
||||
"Filter" => Some(NodeTemplate::Filter),
|
||||
"SVF" => Some(NodeTemplate::Svf),
|
||||
"Gain" => Some(NodeTemplate::Gain),
|
||||
"Echo" | "Delay" => Some(NodeTemplate::Echo),
|
||||
"Reverb" => Some(NodeTemplate::Reverb),
|
||||
"Chorus" => Some(NodeTemplate::Chorus),
|
||||
"Flanger" => Some(NodeTemplate::Flanger),
|
||||
"Phaser" => Some(NodeTemplate::Phaser),
|
||||
"Distortion" => Some(NodeTemplate::Distortion),
|
||||
"BitCrusher" => Some(NodeTemplate::BitCrusher),
|
||||
"Compressor" => Some(NodeTemplate::Compressor),
|
||||
"Limiter" => Some(NodeTemplate::Limiter),
|
||||
"EQ" => Some(NodeTemplate::Eq),
|
||||
"Pan" => Some(NodeTemplate::Pan),
|
||||
"RingModulator" => Some(NodeTemplate::RingModulator),
|
||||
"Vocoder" => Some(NodeTemplate::Vocoder),
|
||||
"ADSR" => Some(NodeTemplate::Adsr),
|
||||
"LFO" => Some(NodeTemplate::Lfo),
|
||||
"Mixer" => Some(NodeTemplate::Mixer),
|
||||
"Splitter" => Some(NodeTemplate::Splitter),
|
||||
"Constant" => Some(NodeTemplate::Constant),
|
||||
"MidiToCV" => Some(NodeTemplate::MidiToCv),
|
||||
"AudioToCV" => Some(NodeTemplate::AudioToCv),
|
||||
"Math" => Some(NodeTemplate::Math),
|
||||
"SampleHold" => Some(NodeTemplate::SampleHold),
|
||||
"SlewLimiter" => Some(NodeTemplate::SlewLimiter),
|
||||
"Quantizer" => Some(NodeTemplate::Quantizer),
|
||||
"EnvelopeFollower" => Some(NodeTemplate::EnvelopeFollower),
|
||||
"BPMDetector" => Some(NodeTemplate::BpmDetector),
|
||||
"Mod" => Some(NodeTemplate::Mod),
|
||||
"Oscilloscope" => Some(NodeTemplate::Oscilloscope),
|
||||
"Arpeggiator" => Some(NodeTemplate::Arpeggiator),
|
||||
"Sequencer" => Some(NodeTemplate::Sequencer),
|
||||
"Script" => Some(NodeTemplate::Script),
|
||||
"Beat" => Some(NodeTemplate::Beat),
|
||||
"VoiceAllocator" => Some(NodeTemplate::VoiceAllocator),
|
||||
"Group" => Some(NodeTemplate::Group),
|
||||
"TemplateInput" => Some(NodeTemplate::TemplateInput),
|
||||
"TemplateOutput" => Some(NodeTemplate::TemplateOutput),
|
||||
"AudioOutput" => Some(NodeTemplate::AudioOutput),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: add a node to the editor state and return its frontend ID
|
||||
fn add_node_to_editor(
|
||||
&mut self,
|
||||
|
|
@ -2573,6 +2517,31 @@ impl crate::panes::PaneRenderer for NodeGraphPane {
|
|||
self.handle_pending_sampler_load(load, shared);
|
||||
}
|
||||
|
||||
// Handle pending AmpSim model load from bottom_ui()
|
||||
if let Some((node_id, backend_node_id)) = self.user_state.pending_amp_sim_load.take() {
|
||||
if let Some(backend_track_id) = self.backend_track_id {
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.add_filter("NAM Model", &["nam"])
|
||||
.pick_file()
|
||||
{
|
||||
let model_name = path.file_stem()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "Model".to_string());
|
||||
if let Some(controller_arc) = &shared.audio_controller {
|
||||
let mut controller = controller_arc.lock().unwrap();
|
||||
controller.amp_sim_load_model(
|
||||
backend_track_id,
|
||||
backend_node_id,
|
||||
path.to_string_lossy().to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(node) = self.state.graph.nodes.get_mut(node_id) {
|
||||
node.user_data.nam_model_name = Some(model_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render sample import dialog if active
|
||||
if let Some(dialog) = &mut self.user_state.sample_import_dialog {
|
||||
let still_open = dialog.show(ui.ctx());
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 1eab4645f7073e752314b33946b69bfe3fbc01f9
|
||||
Loading…
Reference in New Issue