Compare commits

..

No commits in common. "1e7001b29198cbe8a3e2c157e59604a5cdff91d2" and "c6a8b944e5092c0c6b657def81842a80c24d593e" have entirely different histories.

13 changed files with 256 additions and 279 deletions

View File

@ -501,8 +501,6 @@ impl Engine {
.store(self.playhead, Ordering::Relaxed); .store(self.playhead, Ordering::Relaxed);
// Stop all MIDI notes when seeking to prevent stuck notes // Stop all MIDI notes when seeking to prevent stuck notes
self.project.stop_all_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 // Notify disk reader to refill buffers from new position
if let Some(ref mut dr) = self.disk_reader { if let Some(ref mut dr) = self.disk_reader {
dr.send(crate::audio::disk_reader::DiskReaderCommand::Seek { frame: frames }); dr.send(crate::audio::disk_reader::DiskReaderCommand::Seek { frame: frames });
@ -1082,7 +1080,7 @@ impl Engine {
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())), "Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
"Pan" => Box::new(PanNode::new("Pan".to_string())), "Pan" => Box::new(PanNode::new("Pan".to_string())),
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())), "Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
"Echo" | "Delay" => Box::new(EchoNode::new("Echo".to_string())), "Delay" => Box::new(DelayNode::new("Delay".to_string())),
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())), "Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())), "Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())), "Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),
@ -1167,7 +1165,7 @@ impl Engine {
"Splitter" => Box::new(SplitterNode::new("Splitter".to_string())), "Splitter" => Box::new(SplitterNode::new("Splitter".to_string())),
"Pan" => Box::new(PanNode::new("Pan".to_string())), "Pan" => Box::new(PanNode::new("Pan".to_string())),
"Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())), "Quantizer" => Box::new(QuantizerNode::new("Quantizer".to_string())),
"Echo" | "Delay" => Box::new(EchoNode::new("Echo".to_string())), "Delay" => Box::new(DelayNode::new("Delay".to_string())),
"Distortion" => Box::new(DistortionNode::new("Distortion".to_string())), "Distortion" => Box::new(DistortionNode::new("Distortion".to_string())),
"Reverb" => Box::new(ReverbNode::new("Reverb".to_string())), "Reverb" => Box::new(ReverbNode::new("Reverb".to_string())),
"Chorus" => Box::new(ChorusNode::new("Chorus".to_string())), "Chorus" => Box::new(ChorusNode::new("Chorus".to_string())),

View File

@ -865,7 +865,7 @@ impl AudioGraph {
"Splitter" => Box::new(SplitterNode::new("Splitter")), "Splitter" => Box::new(SplitterNode::new("Splitter")),
"Pan" => Box::new(PanNode::new("Pan")), "Pan" => Box::new(PanNode::new("Pan")),
"Quantizer" => Box::new(QuantizerNode::new("Quantizer")), "Quantizer" => Box::new(QuantizerNode::new("Quantizer")),
"Echo" | "Delay" => Box::new(EchoNode::new("Echo")), "Delay" => Box::new(DelayNode::new("Delay")),
"Distortion" => Box::new(DistortionNode::new("Distortion")), "Distortion" => Box::new(DistortionNode::new("Distortion")),
"Reverb" => Box::new(ReverbNode::new("Reverb")), "Reverb" => Box::new(ReverbNode::new("Reverb")),
"Chorus" => Box::new(ChorusNode::new("Chorus")), "Chorus" => Box::new(ChorusNode::new("Chorus")),

View File

@ -7,8 +7,8 @@ const PARAM_WET_DRY: u32 = 2;
const MAX_DELAY_SECONDS: f32 = 2.0; const MAX_DELAY_SECONDS: f32 = 2.0;
/// Stereo echo node with feedback /// Stereo delay node with feedback
pub struct EchoNode { pub struct DelayNode {
name: String, name: String,
delay_time: f32, // seconds delay_time: f32, // seconds
feedback: f32, // 0.0 to 0.95 feedback: f32, // 0.0 to 0.95
@ -26,7 +26,7 @@ pub struct EchoNode {
parameters: Vec<Parameter>, parameters: Vec<Parameter>,
} }
impl EchoNode { impl DelayNode {
pub fn new(name: impl Into<String>) -> Self { pub fn new(name: impl Into<String>) -> Self {
let name = name.into(); let name = name.into();
@ -79,7 +79,7 @@ impl EchoNode {
} }
} }
impl AudioNode for EchoNode { impl AudioNode for DelayNode {
fn category(&self) -> NodeCategory { fn category(&self) -> NodeCategory {
NodeCategory::Effect NodeCategory::Effect
} }
@ -185,7 +185,7 @@ impl AudioNode for EchoNode {
} }
fn node_type(&self) -> &str { fn node_type(&self) -> &str {
"Echo" "Delay"
} }
fn name(&self) -> &str { fn name(&self) -> &str {

View File

@ -7,7 +7,7 @@ mod bpm_detector;
mod chorus; mod chorus;
mod compressor; mod compressor;
mod constant; mod constant;
mod echo; mod delay;
mod distortion; mod distortion;
mod envelope_follower; mod envelope_follower;
mod eq; mod eq;
@ -49,7 +49,7 @@ pub use bpm_detector::BpmDetectorNode;
pub use chorus::ChorusNode; pub use chorus::ChorusNode;
pub use compressor::CompressorNode; pub use compressor::CompressorNode;
pub use constant::ConstantNode; pub use constant::ConstantNode;
pub use echo::EchoNode; pub use delay::DelayNode;
pub use distortion::DistortionNode; pub use distortion::DistortionNode;
pub use envelope_follower::EnvelopeFollowerNode; pub use envelope_follower::EnvelopeFollowerNode;
pub use eq::EQNode; pub use eq::EQNode;

View File

@ -506,17 +506,6 @@ 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) /// 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) { 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 // Process all MIDI tracks to handle queued live input events

View File

@ -45,10 +45,6 @@ pub struct AppConfig {
#[serde(default = "defaults::debug")] #[serde(default = "defaults::debug")]
pub debug: bool, pub debug: bool,
/// Show waveforms as stacked stereo instead of combined mono
#[serde(default = "defaults::waveform_stereo")]
pub waveform_stereo: bool,
/// Theme mode ("light", "dark", or "system") /// Theme mode ("light", "dark", or "system")
#[serde(default = "defaults::theme_mode")] #[serde(default = "defaults::theme_mode")]
pub theme_mode: String, pub theme_mode: String,
@ -67,7 +63,6 @@ impl Default for AppConfig {
reopen_last_session: defaults::reopen_last_session(), reopen_last_session: defaults::reopen_last_session(),
restore_layout_from_file: defaults::restore_layout_from_file(), restore_layout_from_file: defaults::restore_layout_from_file(),
debug: defaults::debug(), debug: defaults::debug(),
waveform_stereo: defaults::waveform_stereo(),
theme_mode: defaults::theme_mode(), theme_mode: defaults::theme_mode(),
} }
} }
@ -268,6 +263,5 @@ mod defaults {
pub fn reopen_last_session() -> bool { false } pub fn reopen_last_session() -> bool { false }
pub fn restore_layout_from_file() -> bool { true } pub fn restore_layout_from_file() -> bool { true }
pub fn debug() -> bool { false } pub fn debug() -> bool { false }
pub fn waveform_stereo() -> bool { false }
pub fn theme_mode() -> String { "system".to_string() } pub fn theme_mode() -> String { "system".to_string() }
} }

View File

@ -4389,7 +4389,6 @@ impl eframe::App for EditorApp {
target_format: self.target_format, target_format: self.target_format,
pending_menu_actions: &mut pending_menu_actions, pending_menu_actions: &mut pending_menu_actions,
clipboard_manager: &mut self.clipboard_manager, clipboard_manager: &mut self.clipboard_manager,
waveform_stereo: self.config.waveform_stereo,
}; };
render_layout_node( render_layout_node(
@ -4662,8 +4661,6 @@ struct RenderContext<'a> {
pending_menu_actions: &'a mut Vec<MenuAction>, pending_menu_actions: &'a mut Vec<MenuAction>,
/// Clipboard manager for paste availability checks /// Clipboard manager for paste availability checks
clipboard_manager: &'a mut lightningbeam_core::clipboard::ClipboardManager, clipboard_manager: &'a mut lightningbeam_core::clipboard::ClipboardManager,
/// Whether to show waveforms as stacked stereo
waveform_stereo: bool,
} }
/// Recursively render a layout node with drag support /// Recursively render a layout node with drag support
@ -5144,7 +5141,6 @@ fn render_pane(
target_format: ctx.target_format, target_format: ctx.target_format,
pending_menu_actions: ctx.pending_menu_actions, pending_menu_actions: ctx.pending_menu_actions,
clipboard_manager: ctx.clipboard_manager, clipboard_manager: ctx.clipboard_manager,
waveform_stereo: ctx.waveform_stereo,
}; };
pane_instance.render_header(&mut header_ui, &mut shared); pane_instance.render_header(&mut header_ui, &mut shared);
} }
@ -5214,7 +5210,6 @@ fn render_pane(
target_format: ctx.target_format, target_format: ctx.target_format,
pending_menu_actions: ctx.pending_menu_actions, pending_menu_actions: ctx.pending_menu_actions,
clipboard_manager: ctx.clipboard_manager, clipboard_manager: ctx.clipboard_manager,
waveform_stereo: ctx.waveform_stereo,
}; };
// Render pane content (header was already rendered above) // Render pane content (header was already rendered above)

View File

@ -215,8 +215,6 @@ pub struct SharedPaneState<'a> {
pub pending_menu_actions: &'a mut Vec<crate::menu::MenuAction>, pub pending_menu_actions: &'a mut Vec<crate::menu::MenuAction>,
/// Clipboard manager for cut/copy/paste operations /// Clipboard manager for cut/copy/paste operations
pub clipboard_manager: &'a mut lightningbeam_core::clipboard::ClipboardManager, pub clipboard_manager: &'a mut lightningbeam_core::clipboard::ClipboardManager,
/// Whether to show waveforms as stacked stereo (true) or combined mono (false)
pub waveform_stereo: bool,
} }
/// Trait for pane rendering /// Trait for pane rendering

View File

@ -33,7 +33,7 @@ pub enum NodeTemplate {
// Effects // Effects
Filter, Filter,
Gain, Gain,
Echo, Delay,
Reverb, Reverb,
Chorus, Chorus,
Flanger, Flanger,
@ -89,70 +89,16 @@ pub enum UserResponse {}
impl UserResponseTrait for UserResponse {} impl UserResponseTrait for UserResponse {}
fn default_unit() -> &'static str { "" }
/// Value types for inline parameters /// Value types for inline parameters
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ValueType { pub enum ValueType {
Float { Float { value: f32 },
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 }, 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 { impl Default for ValueType {
fn default() -> Self { fn default() -> Self {
ValueType::Float { ValueType::Float { value: 0.0 }
value: 0.0,
min: 0.0,
max: 0.0,
unit: "",
backend_param_id: None,
enum_labels: None,
}
} }
} }
@ -199,7 +145,7 @@ impl NodeTemplateTrait for NodeTemplate {
// Effects // Effects
NodeTemplate::Filter => "Filter".into(), NodeTemplate::Filter => "Filter".into(),
NodeTemplate::Gain => "Gain".into(), NodeTemplate::Gain => "Gain".into(),
NodeTemplate::Echo => "Echo".into(), NodeTemplate::Delay => "Delay".into(),
NodeTemplate::Reverb => "Reverb".into(), NodeTemplate::Reverb => "Reverb".into(),
NodeTemplate::Chorus => "Chorus".into(), NodeTemplate::Chorus => "Chorus".into(),
NodeTemplate::Flanger => "Flanger".into(), NodeTemplate::Flanger => "Flanger".into(),
@ -241,7 +187,7 @@ impl NodeTemplateTrait for NodeTemplate {
NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput => vec!["Inputs"], NodeTemplate::MidiInput | NodeTemplate::AudioInput | NodeTemplate::AutomationInput => vec!["Inputs"],
NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth NodeTemplate::Oscillator | NodeTemplate::WavetableOscillator | NodeTemplate::FmSynth
| NodeTemplate::Noise | NodeTemplate::SimpleSampler | NodeTemplate::MultiSampler => vec!["Generators"], | NodeTemplate::Noise | NodeTemplate::SimpleSampler | NodeTemplate::MultiSampler => vec!["Generators"],
NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Echo | NodeTemplate::Reverb NodeTemplate::Filter | NodeTemplate::Gain | NodeTemplate::Delay | NodeTemplate::Reverb
| NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser | NodeTemplate::Distortion | NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser | NodeTemplate::Distortion
| NodeTemplate::BitCrusher | NodeTemplate::Compressor | NodeTemplate::Limiter | NodeTemplate::Eq | NodeTemplate::BitCrusher | NodeTemplate::Compressor | NodeTemplate::Limiter | NodeTemplate::Eq
| NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Vocoder => vec!["Effects"], | NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Vocoder => vec!["Effects"],
@ -271,66 +217,124 @@ impl NodeTemplateTrait for NodeTemplate {
) { ) {
match self { match self {
NodeTemplate::Oscillator => { NodeTemplate::Oscillator => {
// Connection inputs // V/Oct input (pitch control voltage)
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true); graph.add_input_param(
graph.add_input_param(node_id, "FM".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); node_id,
// Parameters "V/Oct".into(),
graph.add_input_param(node_id, "Frequency".into(), DataType::CV, DataType::CV,
ValueType::float_param(440.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true); ValueType::Float { value: 0.0 },
graph.add_input_param(node_id, "Amplitude".into(), DataType::CV, InputParamKind::ConnectionOrConstant,
ValueType::float_param(0.5, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true); 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); // FM input (frequency modulation)
graph.add_input_param(
node_id,
"FM".into(),
DataType::CV,
ValueType::Float { value: 0.0 },
InputParamKind::ConnectionOnly,
true,
);
// Audio output
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Noise => { 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); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Filter => { NodeTemplate::Filter => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
graph.add_input_param(node_id, "Cutoff CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); node_id,
// Parameters "Audio In".into(),
graph.add_input_param(node_id, "Cutoff".into(), DataType::CV, DataType::Audio,
ValueType::float_param(1000.0, 20.0, 20000.0, " Hz", 0, None), InputParamKind::ConstantOnly, true); ValueType::Float { value: 0.0 },
graph.add_input_param(node_id, "Resonance".into(), DataType::CV, InputParamKind::ConnectionOnly,
ValueType::float_param(0.0, 0.0, 1.0, "", 1, None), InputParamKind::ConstantOnly, true); 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_input_param(
node_id,
"Cutoff CV".into(),
DataType::CV,
ValueType::Float { value: 0.0 },
InputParamKind::ConnectionOnly,
true,
);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Gain => { NodeTemplate::Gain => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
graph.add_input_param(node_id, "Gain CV".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); node_id,
// Parameters "Audio In".into(),
graph.add_input_param(node_id, "Gain".into(), DataType::CV, DataType::Audio,
ValueType::float_param(0.0, -60.0, 12.0, " dB", 0, None), InputParamKind::ConstantOnly, true); 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_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Adsr => { NodeTemplate::Adsr => {
graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
node_id,
"Gate".into(),
DataType::CV,
ValueType::Float { value: 0.0 },
InputParamKind::ConnectionOnly,
true,
);
// Parameters // Parameters
graph.add_input_param(node_id, "Attack".into(), DataType::CV, graph.add_input_param(
ValueType::float_param(10.0, 0.1, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true); node_id,
graph.add_input_param(node_id, "Decay".into(), DataType::CV, "Attack".into(),
ValueType::float_param(100.0, 0.1, 2000.0, " ms", 1, None), InputParamKind::ConstantOnly, true); DataType::CV,
graph.add_input_param(node_id, "Sustain".into(), DataType::CV, ValueType::Float { value: 0.01 },
ValueType::float_param(0.7, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true); InputParamKind::ConstantOnly,
graph.add_input_param(node_id, "Release".into(), DataType::CV, true,
ValueType::float_param(200.0, 0.1, 5000.0, " ms", 3, None), 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); graph.add_output_param(node_id, "Envelope Out".into(), DataType::CV);
} }
NodeTemplate::Lfo => { 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); graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
} }
NodeTemplate::AudioOutput => { NodeTemplate::AudioOutput => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
node_id,
"Audio In".into(),
DataType::Audio,
ValueType::Float { value: 0.0 },
InputParamKind::ConnectionOnly,
true,
);
} }
NodeTemplate::AudioInput => { NodeTemplate::AudioInput => {
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
@ -338,47 +342,105 @@ impl NodeTemplateTrait for NodeTemplate {
NodeTemplate::MidiInput => { NodeTemplate::MidiInput => {
graph.add_output_param(node_id, "MIDI Out".into(), DataType::Midi); graph.add_output_param(node_id, "MIDI Out".into(), DataType::Midi);
} }
NodeTemplate::Echo => { NodeTemplate::Delay => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
node_id,
"Audio In".into(),
DataType::Audio,
ValueType::Float { value: 0.0 },
InputParamKind::ConnectionOnly,
true,
);
// Parameters // Parameters
graph.add_input_param(node_id, "Delay Time".into(), DataType::CV, graph.add_input_param(
ValueType::float_param(250.0, 1.0, 2000.0, " ms", 0, None), InputParamKind::ConstantOnly, true); node_id,
graph.add_input_param(node_id, "Feedback".into(), DataType::CV, "Delay Time".into(),
ValueType::float_param(0.3, 0.0, 0.95, "", 1, None), InputParamKind::ConstantOnly, true); DataType::CV,
graph.add_input_param(node_id, "Mix".into(), DataType::CV, ValueType::Float { value: 0.5 },
ValueType::float_param(0.5, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true); 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); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Mixer => { NodeTemplate::Mixer => {
graph.add_input_param(node_id, "Input 1".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(
graph.add_input_param(node_id, "Input 2".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); node_id,
graph.add_input_param(node_id, "Input 3".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); "Input 1".into(),
graph.add_input_param(node_id, "Input 4".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); DataType::Audio,
// Level parameters ValueType::Float { value: 0.0 },
graph.add_input_param(node_id, "Level 1".into(), DataType::CV, InputParamKind::ConnectionOnly,
ValueType::float_param(1.0, 0.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true); 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(
graph.add_input_param(node_id, "Level 3".into(), DataType::CV, node_id,
ValueType::float_param(1.0, 0.0, 1.0, "", 2, None), InputParamKind::ConstantOnly, true); "Input 2".into(),
graph.add_input_param(node_id, "Level 4".into(), DataType::CV, DataType::Audio,
ValueType::float_param(1.0, 0.0, 1.0, "", 3, None), InputParamKind::ConstantOnly, true); 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); graph.add_output_param(node_id, "Mixed Out".into(), DataType::Audio);
} }
NodeTemplate::Splitter => { NodeTemplate::Splitter => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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 1".into(), DataType::Audio);
graph.add_output_param(node_id, "Out 2".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 3".into(), DataType::Audio);
graph.add_output_param(node_id, "Out 4".into(), DataType::Audio); graph.add_output_param(node_id, "Out 4".into(), DataType::Audio);
} }
NodeTemplate::Constant => { NodeTemplate::Constant => {
graph.add_input_param(node_id, "Value".into(), DataType::CV, // No inputs - value is set via parameter
ValueType::float_param(0.0, -1.0, 1.0, "", 0, None), InputParamKind::ConstantOnly, true); 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); graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
} }
NodeTemplate::MidiToCv => { NodeTemplate::MidiToCv => {
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "V/Oct".into(), DataType::CV); 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, "Gate".into(), DataType::CV);
graph.add_output_param(node_id, "Velocity".into(), DataType::CV); graph.add_output_param(node_id, "Velocity".into(), DataType::CV);
@ -388,67 +450,56 @@ impl NodeTemplateTrait for NodeTemplate {
graph.add_output_param(node_id, "CV Out".into(), DataType::CV); graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
} }
NodeTemplate::WavetableOscillator => { NodeTemplate::WavetableOscillator => {
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::FmSynth => { NodeTemplate::FmSynth => {
graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "V/Oct".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::SimpleSampler => { NodeTemplate::SimpleSampler => {
graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "Gate".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::MultiSampler => { NodeTemplate::MultiSampler => {
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::Reverb => { NodeTemplate::Reverb | NodeTemplate::Chorus | NodeTemplate::Flanger | NodeTemplate::Phaser
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::Distortion | NodeTemplate::BitCrusher | NodeTemplate::Compressor
| NodeTemplate::Limiter | NodeTemplate::Eq | NodeTemplate::Pan | NodeTemplate::RingModulator | NodeTemplate::Limiter | NodeTemplate::Eq | NodeTemplate::Pan | NodeTemplate::RingModulator
| NodeTemplate::Vocoder => { | NodeTemplate::Vocoder => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
NodeTemplate::AudioToCv => { NodeTemplate::AudioToCv => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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, "CV Out".into(), DataType::CV); graph.add_output_param(node_id, "CV Out".into(), DataType::CV);
} }
NodeTemplate::Math => { NodeTemplate::Math => {
graph.add_input_param(node_id, "A".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOrConstant, true); 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(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_output_param(node_id, "Out".into(), DataType::CV); graph.add_output_param(node_id, "Out".into(), DataType::CV);
} }
NodeTemplate::SampleHold | NodeTemplate::SlewLimiter | NodeTemplate::Quantizer | NodeTemplate::EnvelopeFollower => { NodeTemplate::SampleHold | NodeTemplate::SlewLimiter | NodeTemplate::Quantizer | NodeTemplate::EnvelopeFollower => {
graph.add_input_param(node_id, "In".into(), DataType::CV, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "In".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Out".into(), DataType::CV); graph.add_output_param(node_id, "Out".into(), DataType::CV);
} }
NodeTemplate::BpmDetector => { NodeTemplate::BpmDetector => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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, "BPM".into(), DataType::CV); graph.add_output_param(node_id, "BPM".into(), DataType::CV);
} }
NodeTemplate::Mod => { NodeTemplate::Mod => {
graph.add_input_param(node_id, "Carrier".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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(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_output_param(node_id, "Out".into(), DataType::Audio); graph.add_output_param(node_id, "Out".into(), DataType::Audio);
} }
NodeTemplate::Oscilloscope => { NodeTemplate::Oscilloscope => {
graph.add_input_param(node_id, "Audio In".into(), DataType::Audio, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); 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(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "CV In".into(), DataType::CV, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
} }
NodeTemplate::VoiceAllocator => { NodeTemplate::VoiceAllocator => {
graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::float(0.0), InputParamKind::ConnectionOnly, true); graph.add_input_param(node_id, "MIDI In".into(), DataType::Midi, ValueType::Float { value: 0.0 }, InputParamKind::ConnectionOnly, true);
graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio); graph.add_output_param(node_id, "Audio Out".into(), DataType::Audio);
} }
} }
@ -470,49 +521,11 @@ impl WidgetValueTrait for ValueType {
_node_data: &Self::NodeData, _node_data: &Self::NodeData,
) -> Vec<Self::Response> { ) -> Vec<Self::Response> {
match self { match self {
ValueType::Float { value, min, max, unit, enum_labels, .. } => { ValueType::Float { value } => {
let has_range = *max > *min; ui.horizontal(|ui| {
if let Some(labels) = enum_labels { ui.label(param_name);
// Enum parameter: render as ComboBox dropdown ui.add(egui::DragValue::new(value).speed(0.1));
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 } => { ValueType::String { value } => {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -570,7 +583,7 @@ impl NodeTemplateIter for AllNodeTemplates {
// Effects // Effects
NodeTemplate::Filter, NodeTemplate::Filter,
NodeTemplate::Gain, NodeTemplate::Gain,
NodeTemplate::Echo, NodeTemplate::Delay,
NodeTemplate::Reverb, NodeTemplate::Reverb,
NodeTemplate::Chorus, NodeTemplate::Chorus,
NodeTemplate::Flanger, NodeTemplate::Flanger,

View File

@ -137,7 +137,7 @@ impl NodeGraphPane {
// Effects // Effects
"Filter" => graph_data::NodeTemplate::Filter, "Filter" => graph_data::NodeTemplate::Filter,
"Gain" => graph_data::NodeTemplate::Gain, "Gain" => graph_data::NodeTemplate::Gain,
"Echo" | "Delay" => graph_data::NodeTemplate::Echo, "Delay" => graph_data::NodeTemplate::Delay,
"Reverb" => graph_data::NodeTemplate::Reverb, "Reverb" => graph_data::NodeTemplate::Reverb,
"Chorus" => graph_data::NodeTemplate::Chorus, "Chorus" => graph_data::NodeTemplate::Chorus,
"Flanger" => graph_data::NodeTemplate::Flanger, "Flanger" => graph_data::NodeTemplate::Flanger,
@ -205,17 +205,13 @@ impl NodeGraphPane {
self.node_id_map.insert(frontend_id, backend_id); self.node_id_map.insert(frontend_id, backend_id);
self.backend_to_frontend_map.insert(backend_id, frontend_id); self.backend_to_frontend_map.insert(backend_id, frontend_id);
// Set parameter values from backend // Set parameter values
if let Some(node_data) = self.state.graph.nodes.get(frontend_id) { for (&param_id, &value) in &node.parameters {
let input_ids: Vec<InputId> = node_data.inputs.iter().map(|(_, id)| *id).collect(); // Find the input param in the graph and set its value
for input_id in input_ids { if let Some(_node_data) = self.state.graph.nodes.get_mut(frontend_id) {
if let Some(input_param) = self.state.graph.inputs.get_mut(input_id) { // TODO: Set parameter values on the node's input params
if let ValueType::Float { value, backend_param_id: Some(pid), .. } = &mut input_param.value { // This requires matching param_id to the input param by index
if let Some(&backend_value) = node.parameters.get(pid) { let _ = (param_id, value); // Silence unused warning for now
*value = backend_value as f32;
}
}
}
} }
} }
} }
@ -444,11 +440,11 @@ impl NodeGraphPane {
continue; continue;
} }
// Get current value and backend param ID // Get current value
let (current_value, backend_param_id) = match &input_param.value { let current_value = match &input_param.value {
ValueType::Float { value, backend_param_id, .. } => { ValueType::Float { value } => {
_checked_count += 1; _checked_count += 1;
(*value, *backend_param_id) *value
}, },
other => { other => {
_non_float_count += 1; _non_float_count += 1;
@ -472,20 +468,24 @@ impl NodeGraphPane {
if let Some(track_id) = self.track_id { if let Some(track_id) = self.track_id {
let node_id = input_param.node; let node_id = input_param.node;
// Get backend node ID and use stored param ID // Get backend node ID
if let Some(&backend_id) = self.node_id_map.get(&node_id) { if let Some(&backend_id) = self.node_id_map.get(&node_id) {
if let Some(param_id) = backend_param_id { // Get parameter index (position in node's inputs array)
eprintln!("[DEBUG] Parameter changed: node {:?} param {} from {:?} to {}", if let Some(node) = self.state.graph.nodes.get(node_id) {
backend_id, param_id, previous_value, current_value); if let Some(param_index) = node.inputs.iter().position(|(_, id)| *id == input_id) {
let action = Box::new(actions::NodeGraphAction::SetParameter( eprintln!("[DEBUG] Parameter changed: node {:?} param {} from {:?} to {}",
actions::SetParameterAction::new( backend_id, param_index, previous_value, current_value);
track_id, // Create action to update backend
backend_id, let action = Box::new(actions::NodeGraphAction::SetParameter(
param_id, actions::SetParameterAction::new(
current_value as f64, track_id,
) backend_id,
)); param_index as u32,
self.pending_action = Some(action); current_value as f64,
)
));
self.pending_action = Some(action);
}
} }
} }
} }

View File

@ -332,10 +332,10 @@ impl NodeTypeRegistry {
); );
types.insert( types.insert(
"Echo".to_string(), "Delay".to_string(),
NodeTypeInfo { NodeTypeInfo {
id: "Echo".to_string(), id: "Delay".to_string(),
display_name: "Echo".to_string(), display_name: "Delay".to_string(),
category: NodeCategory::Effects, category: NodeCategory::Effects,
inputs: vec![PortInfo { inputs: vec![PortInfo {
index: 0, index: 0,
@ -347,7 +347,7 @@ impl NodeTypeRegistry {
index: 0, index: 0,
name: "Out".to_string(), name: "Out".to_string(),
signal_type: DataType::Audio, signal_type: DataType::Audio,
description: "Echo audio output".to_string(), description: "Delayed audio output".to_string(),
}], }],
parameters: vec![ parameters: vec![
ParameterInfo { ParameterInfo {
@ -357,7 +357,7 @@ impl NodeTypeRegistry {
min: 1.0, min: 1.0,
max: 2000.0, max: 2000.0,
unit: ParameterUnit::Milliseconds, unit: ParameterUnit::Milliseconds,
description: "Echo time".to_string(), description: "Delay time".to_string(),
}, },
ParameterInfo { ParameterInfo {
id: 1, id: 1,
@ -378,7 +378,7 @@ impl NodeTypeRegistry {
description: "Dry/wet mix".to_string(), description: "Dry/wet mix".to_string(),
}, },
], ],
description: "Echo effect with feedback".to_string(), description: "Time-based delay effect".to_string(),
}, },
); );

View File

@ -928,7 +928,6 @@ impl TimelinePane {
raw_audio_cache: &std::collections::HashMap<usize, (Vec<f32>, u32, u32)>, raw_audio_cache: &std::collections::HashMap<usize, (Vec<f32>, u32, u32)>,
waveform_gpu_dirty: &mut std::collections::HashSet<usize>, waveform_gpu_dirty: &mut std::collections::HashSet<usize>,
target_format: wgpu::TextureFormat, target_format: wgpu::TextureFormat,
waveform_stereo: bool,
) -> Vec<(egui::Rect, uuid::Uuid, f64, f64)> { ) -> Vec<(egui::Rect, uuid::Uuid, f64, f64)> {
let painter = ui.painter(); let painter = ui.painter();
@ -1274,7 +1273,7 @@ impl TimelinePane {
tex_width: crate::waveform_gpu::tex_width() as f32, tex_width: crate::waveform_gpu::tex_width() as f32,
total_frames: total_frames as f32, total_frames: total_frames as f32,
segment_start_frame: 0.0, segment_start_frame: 0.0,
display_mode: if waveform_stereo { 1.0 } else { 0.0 }, display_mode: 0.0,
_pad1: [0.0, 0.0], _pad1: [0.0, 0.0],
tint_color: tint, tint_color: tint,
screen_size: [screen_size.x, screen_size.y], screen_size: [screen_size.x, screen_size.y],
@ -2155,7 +2154,7 @@ impl PaneRenderer for TimelinePane {
// Render layer rows with clipping // Render layer rows with clipping
ui.set_clip_rect(content_rect.intersect(original_clip_rect)); ui.set_clip_rect(content_rect.intersect(original_clip_rect));
let video_clip_hovers = self.render_layers(ui, content_rect, shared.theme, document, shared.active_layer_id, shared.selection, shared.midi_event_cache, shared.raw_audio_cache, shared.waveform_gpu_dirty, shared.target_format, shared.waveform_stereo); let video_clip_hovers = self.render_layers(ui, content_rect, shared.theme, document, shared.active_layer_id, shared.selection, shared.midi_event_cache, shared.raw_audio_cache, shared.waveform_gpu_dirty, shared.target_format);
// Render playhead on top (clip to timeline area) // Render playhead on top (clip to timeline area)
ui.set_clip_rect(timeline_rect.intersect(original_clip_rect)); ui.set_clip_rect(timeline_rect.intersect(original_clip_rect));

View File

@ -33,7 +33,6 @@ struct PreferencesState {
reopen_last_session: bool, reopen_last_session: bool,
restore_layout_from_file: bool, restore_layout_from_file: bool,
debug: bool, debug: bool,
waveform_stereo: bool,
theme_mode: ThemeMode, theme_mode: ThemeMode,
} }
@ -49,7 +48,6 @@ impl From<(&AppConfig, &Theme)> for PreferencesState {
reopen_last_session: config.reopen_last_session, reopen_last_session: config.reopen_last_session,
restore_layout_from_file: config.restore_layout_from_file, restore_layout_from_file: config.restore_layout_from_file,
debug: config.debug, debug: config.debug,
waveform_stereo: config.waveform_stereo,
theme_mode: theme.mode(), theme_mode: theme.mode(),
} }
} }
@ -67,7 +65,6 @@ impl Default for PreferencesState {
reopen_last_session: false, reopen_last_session: false,
restore_layout_from_file: true, restore_layout_from_file: true,
debug: false, debug: false,
waveform_stereo: false,
theme_mode: ThemeMode::System, theme_mode: ThemeMode::System,
} }
} }
@ -338,10 +335,6 @@ impl PreferencesDialog {
.default_open(false) .default_open(false)
.show(ui, |ui| { .show(ui, |ui| {
ui.checkbox(&mut self.working_prefs.debug, "Enable debug mode"); ui.checkbox(&mut self.working_prefs.debug, "Enable debug mode");
ui.checkbox(
&mut self.working_prefs.waveform_stereo,
"Show waveforms as stacked stereo",
);
}); });
} }
@ -366,7 +359,6 @@ impl PreferencesDialog {
temp_config.reopen_last_session = self.working_prefs.reopen_last_session; temp_config.reopen_last_session = self.working_prefs.reopen_last_session;
temp_config.restore_layout_from_file = self.working_prefs.restore_layout_from_file; temp_config.restore_layout_from_file = self.working_prefs.restore_layout_from_file;
temp_config.debug = self.working_prefs.debug; temp_config.debug = self.working_prefs.debug;
temp_config.waveform_stereo = self.working_prefs.waveform_stereo;
temp_config.theme_mode = self.working_prefs.theme_mode.to_string_lower(); temp_config.theme_mode = self.working_prefs.theme_mode.to_string_lower();
// Validate // Validate
@ -388,7 +380,6 @@ impl PreferencesDialog {
config.reopen_last_session = self.working_prefs.reopen_last_session; config.reopen_last_session = self.working_prefs.reopen_last_session;
config.restore_layout_from_file = self.working_prefs.restore_layout_from_file; config.restore_layout_from_file = self.working_prefs.restore_layout_from_file;
config.debug = self.working_prefs.debug; config.debug = self.working_prefs.debug;
config.waveform_stereo = self.working_prefs.waveform_stereo;
config.theme_mode = self.working_prefs.theme_mode.to_string_lower(); config.theme_mode = self.working_prefs.theme_mode.to_string_lower();
// Apply theme immediately // Apply theme immediately