Remove old SimpleSynth and effect system

This commit is contained in:
Skyler Lehmkuhl 2025-10-28 20:19:08 -04:00
parent 2cdde33e37
commit d7dc423fe3
14 changed files with 224 additions and 524 deletions

View File

@ -7,7 +7,6 @@ use crate::audio::project::Project;
use crate::audio::recording::RecordingState; use crate::audio::recording::RecordingState;
use crate::audio::track::{Track, TrackId, TrackNode}; use crate::audio::track::{Track, TrackId, TrackNode};
use crate::command::{AudioEvent, Command, Query, QueryResponse}; use crate::command::{AudioEvent, Command, Query, QueryResponse};
use crate::effects::{Effect, GainEffect, PanEffect, SimpleEQ};
use petgraph::stable_graph::NodeIndex; use petgraph::stable_graph::NodeIndex;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -102,7 +101,6 @@ impl Engine {
if let Some(node) = self.project.get_track_mut(id) { if let Some(node) = self.project.get_track_mut(id) {
if let crate::audio::track::TrackNode::Audio(audio_track) = node { if let crate::audio::track::TrackNode::Audio(audio_track) = node {
audio_track.clips = track.clips; audio_track.clips = track.clips;
audio_track.effects = track.effects;
audio_track.volume = track.volume; audio_track.volume = track.volume;
audio_track.muted = track.muted; audio_track.muted = track.muted;
audio_track.solo = track.solo; audio_track.solo = track.solo;
@ -325,106 +323,6 @@ impl Engine {
} }
} }
} }
Command::AddGainEffect(track_id, gain_db) => {
// Get the track node and handle audio tracks, MIDI tracks, and groups
match self.project.get_track_mut(track_id) {
Some(crate::audio::track::TrackNode::Audio(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "Gain") {
effect.set_parameter(0, gain_db);
} else {
track.add_effect(Box::new(GainEffect::with_gain_db(gain_db)));
}
}
Some(crate::audio::track::TrackNode::Midi(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "Gain") {
effect.set_parameter(0, gain_db);
} else {
track.add_effect(Box::new(GainEffect::with_gain_db(gain_db)));
}
}
Some(crate::audio::track::TrackNode::Group(group)) => {
if let Some(effect) = group.effects.iter_mut().find(|e| e.name() == "Gain") {
effect.set_parameter(0, gain_db);
} else {
group.add_effect(Box::new(GainEffect::with_gain_db(gain_db)));
}
}
None => {}
}
}
Command::AddPanEffect(track_id, pan) => {
match self.project.get_track_mut(track_id) {
Some(crate::audio::track::TrackNode::Audio(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "Pan") {
effect.set_parameter(0, pan);
} else {
track.add_effect(Box::new(PanEffect::with_pan(pan)));
}
}
Some(crate::audio::track::TrackNode::Midi(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "Pan") {
effect.set_parameter(0, pan);
} else {
track.add_effect(Box::new(PanEffect::with_pan(pan)));
}
}
Some(crate::audio::track::TrackNode::Group(group)) => {
if let Some(effect) = group.effects.iter_mut().find(|e| e.name() == "Pan") {
effect.set_parameter(0, pan);
} else {
group.add_effect(Box::new(PanEffect::with_pan(pan)));
}
}
None => {}
}
}
Command::AddEQEffect(track_id, low_db, mid_db, high_db) => {
match self.project.get_track_mut(track_id) {
Some(crate::audio::track::TrackNode::Audio(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "SimpleEQ") {
effect.set_parameter(0, low_db);
effect.set_parameter(1, mid_db);
effect.set_parameter(2, high_db);
} else {
let mut eq = SimpleEQ::new();
eq.set_parameter(0, low_db);
eq.set_parameter(1, mid_db);
eq.set_parameter(2, high_db);
track.add_effect(Box::new(eq));
}
}
Some(crate::audio::track::TrackNode::Midi(track)) => {
if let Some(effect) = track.effects.iter_mut().find(|e| e.name() == "SimpleEQ") {
effect.set_parameter(0, low_db);
effect.set_parameter(1, mid_db);
effect.set_parameter(2, high_db);
} else {
let mut eq = SimpleEQ::new();
eq.set_parameter(0, low_db);
eq.set_parameter(1, mid_db);
eq.set_parameter(2, high_db);
track.add_effect(Box::new(eq));
}
}
Some(crate::audio::track::TrackNode::Group(group)) => {
if let Some(effect) = group.effects.iter_mut().find(|e| e.name() == "SimpleEQ") {
effect.set_parameter(0, low_db);
effect.set_parameter(1, mid_db);
effect.set_parameter(2, high_db);
} else {
let mut eq = SimpleEQ::new();
eq.set_parameter(0, low_db);
eq.set_parameter(1, mid_db);
eq.set_parameter(2, high_db);
group.add_effect(Box::new(eq));
}
}
None => {}
}
}
Command::ClearEffects(track_id) => {
let _ = self.project.clear_effects(track_id);
}
Command::CreateMetatrack(name) => { Command::CreateMetatrack(name) => {
let track_id = self.project.add_group_track(name.clone(), None); let track_id = self.project.add_group_track(name.clone(), None);
// Notify UI about the new metatrack // Notify UI about the new metatrack
@ -743,13 +641,8 @@ impl Engine {
Command::GraphAddNode(track_id, node_type, x, y) => { Command::GraphAddNode(track_id, node_type, x, y) => {
// Get MIDI track (graphs are only for MIDI tracks currently) // Get MIDI track (graphs are only for MIDI tracks currently)
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
// Create graph if it doesn't exist let graph = &mut track.instrument_graph;
if track.instrument_graph.is_none() { {
// Use large buffer to accommodate any audio callback size
track.instrument_graph = Some(InstrumentGraph::new(self.sample_rate, 8192));
}
if let Some(graph) = &mut track.instrument_graph {
// Create the node based on type // Create the node based on type
let node: Box<dyn crate::audio::node_graph::AudioNode> = match node_type.as_str() { let node: Box<dyn crate::audio::node_graph::AudioNode> = match node_type.as_str() {
"Oscillator" => Box::new(OscillatorNode::new("Oscillator".to_string())), "Oscillator" => Box::new(OscillatorNode::new("Oscillator".to_string())),
@ -810,7 +703,8 @@ impl Engine {
Command::GraphAddNodeToTemplate(track_id, voice_allocator_id, node_type, _x, _y) => { Command::GraphAddNodeToTemplate(track_id, voice_allocator_id, node_type, _x, _y) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let va_idx = NodeIndex::new(voice_allocator_id as usize); let va_idx = NodeIndex::new(voice_allocator_id as usize);
// Create the node // Create the node
@ -871,7 +765,8 @@ impl Engine {
Command::GraphRemoveNode(track_id, node_index) => { Command::GraphRemoveNode(track_id, node_index) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let node_idx = NodeIndex::new(node_index as usize); let node_idx = NodeIndex::new(node_index as usize);
graph.remove_node(node_idx); graph.remove_node(node_idx);
let _ = self.event_tx.push(AudioEvent::GraphStateChanged(track_id)); let _ = self.event_tx.push(AudioEvent::GraphStateChanged(track_id));
@ -881,7 +776,8 @@ impl Engine {
Command::GraphConnect(track_id, from, from_port, to, to_port) => { Command::GraphConnect(track_id, from, from_port, to, to_port) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let from_idx = NodeIndex::new(from as usize); let from_idx = NodeIndex::new(from as usize);
let to_idx = NodeIndex::new(to as usize); let to_idx = NodeIndex::new(to as usize);
@ -902,7 +798,8 @@ impl Engine {
Command::GraphConnectInTemplate(track_id, voice_allocator_id, from, from_port, to, to_port) => { Command::GraphConnectInTemplate(track_id, voice_allocator_id, from, from_port, to, to_port) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let va_idx = NodeIndex::new(voice_allocator_id as usize); let va_idx = NodeIndex::new(voice_allocator_id as usize);
match graph.connect_in_voice_allocator_template(va_idx, from, from_port, to, to_port) { match graph.connect_in_voice_allocator_template(va_idx, from, from_port, to, to_port) {
@ -923,7 +820,8 @@ impl Engine {
Command::GraphDisconnect(track_id, from, from_port, to, to_port) => { Command::GraphDisconnect(track_id, from, from_port, to, to_port) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let from_idx = NodeIndex::new(from as usize); let from_idx = NodeIndex::new(from as usize);
let to_idx = NodeIndex::new(to as usize); let to_idx = NodeIndex::new(to as usize);
graph.disconnect(from_idx, from_port, to_idx, to_port); graph.disconnect(from_idx, from_port, to_idx, to_port);
@ -934,7 +832,8 @@ impl Engine {
Command::GraphSetParameter(track_id, node_index, param_id, value) => { Command::GraphSetParameter(track_id, node_index, param_id, value) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let node_idx = NodeIndex::new(node_index as usize); let node_idx = NodeIndex::new(node_index as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
graph_node.node.set_parameter(param_id, value); graph_node.node.set_parameter(param_id, value);
@ -945,7 +844,8 @@ impl Engine {
Command::GraphSetMidiTarget(track_id, node_index, enabled) => { Command::GraphSetMidiTarget(track_id, node_index, enabled) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let node_idx = NodeIndex::new(node_index as usize); let node_idx = NodeIndex::new(node_index as usize);
graph.set_midi_target(node_idx, enabled); graph.set_midi_target(node_idx, enabled);
} }
@ -954,7 +854,8 @@ impl Engine {
Command::GraphSetOutputNode(track_id, node_index) => { Command::GraphSetOutputNode(track_id, node_index) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(graph) = &mut track.instrument_graph { let graph = &mut track.instrument_graph;
{
let node_idx = NodeIndex::new(node_index as usize); let node_idx = NodeIndex::new(node_index as usize);
graph.set_output_node(Some(node_idx)); graph.set_output_node(Some(node_idx));
} }
@ -963,7 +864,7 @@ impl Engine {
Command::GraphSavePreset(track_id, preset_path, preset_name, description, tags) => { Command::GraphSavePreset(track_id, preset_path, preset_name, description, tags) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref graph) = track.instrument_graph { let graph = &track.instrument_graph;
// Serialize the graph to a preset // Serialize the graph to a preset
let mut preset = graph.to_preset(&preset_name); let mut preset = graph.to_preset(&preset_name);
preset.metadata.description = description; preset.metadata.description = description;
@ -986,7 +887,6 @@ impl Engine {
} }
} }
} }
}
Command::GraphLoadPreset(track_id, preset_path) => { Command::GraphLoadPreset(track_id, preset_path) => {
// Read and deserialize the preset // Read and deserialize the preset
@ -1001,7 +901,7 @@ impl Engine {
Ok(graph) => { Ok(graph) => {
// Replace the track's graph // Replace the track's graph
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
track.instrument_graph = Some(graph); track.instrument_graph = graph;
let _ = self.event_tx.push(AudioEvent::GraphStateChanged(track_id)); let _ = self.event_tx.push(AudioEvent::GraphStateChanged(track_id));
// Emit preset loaded event after everything is loaded // Emit preset loaded event after everything is loaded
let _ = self.event_tx.push(AudioEvent::GraphPresetLoaded(track_id)); let _ = self.event_tx.push(AudioEvent::GraphPresetLoaded(track_id));
@ -1036,7 +936,7 @@ impl Engine {
use crate::audio::node_graph::nodes::VoiceAllocatorNode; use crate::audio::node_graph::nodes::VoiceAllocatorNode;
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref graph) = track.instrument_graph { let graph = &track.instrument_graph;
let va_idx = NodeIndex::new(voice_allocator_id as usize); let va_idx = NodeIndex::new(voice_allocator_id as usize);
// Get the VoiceAllocator node and serialize its template // Get the VoiceAllocator node and serialize its template
@ -1059,13 +959,12 @@ impl Engine {
} }
} }
} }
}
Command::SamplerLoadSample(track_id, node_id, file_path) => { Command::SamplerLoadSample(track_id, node_id, file_path) => {
use crate::audio::node_graph::nodes::SimpleSamplerNode; use crate::audio::node_graph::nodes::SimpleSamplerNode;
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref mut graph) = track.instrument_graph { let graph = &mut track.instrument_graph;
let node_idx = NodeIndex::new(node_id as usize); let node_idx = NodeIndex::new(node_id as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
@ -1082,13 +981,12 @@ impl Engine {
} }
} }
} }
}
Command::MultiSamplerAddLayer(track_id, node_id, file_path, key_min, key_max, root_key, velocity_min, velocity_max) => { Command::MultiSamplerAddLayer(track_id, node_id, file_path, key_min, key_max, root_key, velocity_min, velocity_max) => {
use crate::audio::node_graph::nodes::MultiSamplerNode; use crate::audio::node_graph::nodes::MultiSamplerNode;
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref mut graph) = track.instrument_graph { let graph = &mut track.instrument_graph;
let node_idx = NodeIndex::new(node_id as usize); let node_idx = NodeIndex::new(node_id as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
@ -1105,13 +1003,12 @@ impl Engine {
} }
} }
} }
}
Command::MultiSamplerUpdateLayer(track_id, node_id, layer_index, key_min, key_max, root_key, velocity_min, velocity_max) => { Command::MultiSamplerUpdateLayer(track_id, node_id, layer_index, key_min, key_max, root_key, velocity_min, velocity_max) => {
use crate::audio::node_graph::nodes::MultiSamplerNode; use crate::audio::node_graph::nodes::MultiSamplerNode;
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref mut graph) = track.instrument_graph { let graph = &mut track.instrument_graph;
let node_idx = NodeIndex::new(node_id as usize); let node_idx = NodeIndex::new(node_id as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
@ -1128,13 +1025,12 @@ impl Engine {
} }
} }
} }
}
Command::MultiSamplerRemoveLayer(track_id, node_id, layer_index) => { Command::MultiSamplerRemoveLayer(track_id, node_id, layer_index) => {
use crate::audio::node_graph::nodes::MultiSamplerNode; use crate::audio::node_graph::nodes::MultiSamplerNode;
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref mut graph) = track.instrument_graph { let graph = &mut track.instrument_graph;
let node_idx = NodeIndex::new(node_id as usize); let node_idx = NodeIndex::new(node_id as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
@ -1153,34 +1049,25 @@ impl Engine {
} }
} }
} }
}
/// Handle synchronous queries from the UI thread /// Handle synchronous queries from the UI thread
fn handle_query(&mut self, query: Query) { fn handle_query(&mut self, query: Query) {
let response = match query { let response = match query {
Query::GetGraphState(track_id) => { Query::GetGraphState(track_id) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track(track_id) {
if let Some(ref graph) = track.instrument_graph { let graph = &track.instrument_graph;
let preset = graph.to_preset("temp"); let preset = graph.to_preset("temp");
match preset.to_json() { match preset.to_json() {
Ok(json) => QueryResponse::GraphState(Ok(json)), Ok(json) => QueryResponse::GraphState(Ok(json)),
Err(e) => QueryResponse::GraphState(Err(format!("Failed to serialize graph: {:?}", e))), Err(e) => QueryResponse::GraphState(Err(format!("Failed to serialize graph: {:?}", e))),
} }
} else {
// Empty graph
let empty_preset = crate::audio::node_graph::preset::GraphPreset::new("empty");
match empty_preset.to_json() {
Ok(json) => QueryResponse::GraphState(Ok(json)),
Err(_) => QueryResponse::GraphState(Err("Failed to serialize empty graph".to_string())),
}
}
} else { } else {
QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id))) QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id)))
} }
} }
Query::GetTemplateState(track_id, voice_allocator_id) => { Query::GetTemplateState(track_id, voice_allocator_id) => {
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) { if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
if let Some(ref mut graph) = track.instrument_graph { let graph = &mut track.instrument_graph;
let node_idx = NodeIndex::new(voice_allocator_id as usize); let node_idx = NodeIndex::new(voice_allocator_id as usize);
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) { if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
// Downcast to VoiceAllocatorNode // Downcast to VoiceAllocatorNode
@ -1197,16 +1084,16 @@ impl Engine {
} else { } else {
QueryResponse::GraphState(Err("Voice allocator node not found".to_string())) QueryResponse::GraphState(Err("Voice allocator node not found".to_string()))
} }
} else {
QueryResponse::GraphState(Err("Graph not found".to_string()))
}
} else { } else {
QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id))) QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id)))
} }
} }
Query::GetOscilloscopeData(track_id, node_id, sample_count) => { Query::GetOscilloscopeData(track_id, node_id, sample_count) => {
match self.project.get_oscilloscope_data(track_id, node_id, sample_count) { match self.project.get_oscilloscope_data(track_id, node_id, sample_count) {
Some(data) => QueryResponse::OscilloscopeData(Ok(data)), Some((audio, cv)) => {
use crate::command::OscilloscopeData;
QueryResponse::OscilloscopeData(Ok(OscilloscopeData { audio, cv }))
}
None => QueryResponse::OscilloscopeData(Err(format!( None => QueryResponse::OscilloscopeData(Err(format!(
"Failed to get oscilloscope data from track {} node {}", "Failed to get oscilloscope data from track {} node {}",
track_id, node_id track_id, node_id
@ -1437,26 +1324,6 @@ impl EngineController {
let _ = self.command_tx.push(Command::MoveClip(track_id, clip_id, new_start_time)); let _ = self.command_tx.push(Command::MoveClip(track_id, clip_id, new_start_time));
} }
/// Add or update gain effect on track
pub fn add_gain_effect(&mut self, track_id: TrackId, gain_db: f32) {
let _ = self.command_tx.push(Command::AddGainEffect(track_id, gain_db));
}
/// Add or update pan effect on track
pub fn add_pan_effect(&mut self, track_id: TrackId, pan: f32) {
let _ = self.command_tx.push(Command::AddPanEffect(track_id, pan));
}
/// Add or update EQ effect on track
pub fn add_eq_effect(&mut self, track_id: TrackId, low_db: f32, mid_db: f32, high_db: f32) {
let _ = self.command_tx.push(Command::AddEQEffect(track_id, low_db, mid_db, high_db));
}
/// Clear all effects from a track
pub fn clear_effects(&mut self, track_id: TrackId) {
let _ = self.command_tx.push(Command::ClearEffects(track_id));
}
/// Get current playhead position in samples /// Get current playhead position in samples
pub fn get_playhead_samples(&self) -> u64 { pub fn get_playhead_samples(&self) -> u64 {
self.playhead.load(Ordering::Relaxed) self.playhead.load(Ordering::Relaxed)
@ -1770,7 +1637,7 @@ impl EngineController {
} }
/// Query oscilloscope data from a node /// Query oscilloscope data from a node
pub fn query_oscilloscope_data(&mut self, track_id: TrackId, node_id: u32, sample_count: usize) -> Result<Vec<f32>, String> { pub fn query_oscilloscope_data(&mut self, track_id: TrackId, node_id: u32, sample_count: usize) -> Result<crate::command::OscilloscopeData, String> {
// Send query // Send query
if let Err(_) = self.query_tx.push(Query::GetOscilloscopeData(track_id, node_id, sample_count)) { if let Err(_) = self.query_tx.push(Query::GetOscilloscopeData(track_id, node_id, sample_count)) {
return Err("Failed to send query - queue full".to_string()); return Err("Failed to send query - queue full".to_string());

View File

@ -499,6 +499,11 @@ impl InstrumentGraph {
self.get_node(idx).and_then(|node| node.get_oscilloscope_data(sample_count)) self.get_node(idx).and_then(|node| node.get_oscilloscope_data(sample_count))
} }
/// Get oscilloscope CV data from a specific node
pub fn get_oscilloscope_cv_data(&self, idx: NodeIndex, sample_count: usize) -> Option<Vec<f32>> {
self.get_node(idx).and_then(|node| node.get_oscilloscope_cv_data(sample_count))
}
/// Get node mutably by index /// Get node mutably by index
/// Note: Due to lifetime constraints with trait objects, this returns a mutable reference /// Note: Due to lifetime constraints with trait objects, this returns a mutable reference
/// to the GraphNode, from which you can access the node /// to the GraphNode, from which you can access the node

View File

@ -64,4 +64,10 @@ pub trait AudioNode: Send {
fn get_oscilloscope_data(&self, _sample_count: usize) -> Option<Vec<f32>> { fn get_oscilloscope_data(&self, _sample_count: usize) -> Option<Vec<f32>> {
None None
} }
/// Get oscilloscope CV data if this is an oscilloscope node
/// Returns None for non-oscilloscope nodes
fn get_oscilloscope_cv_data(&self, _sample_count: usize) -> Option<Vec<f32>> {
None
}
} }

View File

@ -330,61 +330,6 @@ impl MultiSamplerNode {
} }
} }
} }
/// Calculate playback speed from pitch difference
fn calculate_speed(&self, layer: &SampleLayer, note: u8) -> f32 {
let semitone_diff = note as i16 - layer.root_key as i16;
2.0_f32.powf(semitone_diff as f32 / 12.0)
}
/// Read sample at playhead with linear interpolation
fn read_sample(&self, playhead: f32, sample: &[f32]) -> f32 {
if sample.is_empty() || playhead < 0.0 {
return 0.0;
}
let index = playhead.floor() as usize;
if index >= sample.len() {
return 0.0;
}
let frac = playhead - playhead.floor();
let sample1 = sample[index];
let sample2 = if index + 1 < sample.len() {
sample[index + 1]
} else {
0.0
};
sample1 + (sample2 - sample1) * frac
}
/// Process envelope for a voice
fn process_envelope(&self, voice: &mut Voice, sample_rate: f32) -> f32 {
match voice.envelope_phase {
EnvelopePhase::Attack => {
let attack_samples = self.attack_time * sample_rate;
voice.envelope_value += 1.0 / attack_samples;
if voice.envelope_value >= 1.0 {
voice.envelope_value = 1.0;
voice.envelope_phase = EnvelopePhase::Sustain;
}
}
EnvelopePhase::Sustain => {
voice.envelope_value = 1.0;
}
EnvelopePhase::Release => {
let release_samples = self.release_time * sample_rate;
voice.envelope_value -= 1.0 / release_samples;
if voice.envelope_value <= 0.0 {
voice.envelope_value = 0.0;
voice.is_active = false;
}
}
}
voice.envelope_value.clamp(0.0, 1.0)
}
} }
impl AudioNode for MultiSamplerNode { impl AudioNode for MultiSamplerNode {

View File

@ -28,7 +28,7 @@ impl TriggerMode {
} }
/// Circular buffer for storing audio samples /// Circular buffer for storing audio samples
struct CircularBuffer { pub struct CircularBuffer {
buffer: Vec<f32>, buffer: Vec<f32>,
write_pos: usize, write_pos: usize,
capacity: usize, capacity: usize,
@ -75,7 +75,7 @@ impl CircularBuffer {
} }
} }
/// Oscilloscope node for visualizing audio signals /// Oscilloscope node for visualizing audio and CV signals
pub struct OscilloscopeNode { pub struct OscilloscopeNode {
name: String, name: String,
time_scale: f32, // Milliseconds to display (10-1000ms) time_scale: f32, // Milliseconds to display (10-1000ms)
@ -86,8 +86,9 @@ pub struct OscilloscopeNode {
sample_counter: usize, // Counter for V/oct triggering sample_counter: usize, // Counter for V/oct triggering
trigger_period: usize, // Period in samples for V/oct triggering trigger_period: usize, // Period in samples for V/oct triggering
// Shared buffer for reading from Tauri commands // Shared buffers for reading from Tauri commands
buffer: Arc<Mutex<CircularBuffer>>, buffer: Arc<Mutex<CircularBuffer>>, // Audio buffer
cv_buffer: Arc<Mutex<CircularBuffer>>, // CV buffer
inputs: Vec<NodePort>, inputs: Vec<NodePort>,
outputs: Vec<NodePort>, outputs: Vec<NodePort>,
@ -101,6 +102,7 @@ impl OscilloscopeNode {
let inputs = vec![ let inputs = vec![
NodePort::new("Audio In", SignalType::Audio, 0), NodePort::new("Audio In", SignalType::Audio, 0),
NodePort::new("V/oct", SignalType::CV, 1), NodePort::new("V/oct", SignalType::CV, 1),
NodePort::new("CV In", SignalType::CV, 2),
]; ];
let outputs = vec![ let outputs = vec![
@ -123,6 +125,7 @@ impl OscilloscopeNode {
sample_counter: 0, sample_counter: 0,
trigger_period: 480, // Default to ~100Hz at 48kHz trigger_period: 480, // Default to ~100Hz at 48kHz
buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))), buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
cv_buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
inputs, inputs,
outputs, outputs,
parameters, parameters,
@ -143,11 +146,23 @@ impl OscilloscopeNode {
} }
} }
/// Read CV samples from the CV buffer (for Tauri commands)
pub fn read_cv_samples(&self, count: usize) -> Vec<f32> {
if let Ok(buffer) = self.cv_buffer.lock() {
buffer.read(count)
} else {
vec![0.0; count]
}
}
/// Clear the buffer /// Clear the buffer
pub fn clear_buffer(&self) { pub fn clear_buffer(&self) {
if let Ok(mut buffer) = self.buffer.lock() { if let Ok(mut buffer) = self.buffer.lock() {
buffer.clear(); buffer.clear();
} }
if let Ok(mut cv_buffer) = self.cv_buffer.lock() {
cv_buffer.clear();
}
} }
/// Convert V/oct to frequency in Hz (matches oscillator convention) /// Convert V/oct to frequency in Hz (matches oscillator convention)
@ -155,23 +170,6 @@ impl OscilloscopeNode {
fn voct_to_frequency(voct: f32) -> f32 { fn voct_to_frequency(voct: f32) -> f32 {
440.0 * 2.0_f32.powf(voct) 440.0 * 2.0_f32.powf(voct)
} }
/// Check if trigger condition is met
fn is_triggered(&self, current_sample: f32) -> bool {
match self.trigger_mode {
TriggerMode::FreeRunning => true,
TriggerMode::RisingEdge => {
self.last_sample <= self.trigger_level && current_sample > self.trigger_level
}
TriggerMode::FallingEdge => {
self.last_sample >= self.trigger_level && current_sample < self.trigger_level
}
TriggerMode::VoltPerOctave => {
// Trigger at the start of each period
self.sample_counter == 0
}
}
}
} }
impl AudioNode for OscilloscopeNode { impl AudioNode for OscilloscopeNode {
@ -242,11 +240,19 @@ impl AudioNode for OscilloscopeNode {
// Pass through audio (copy input to output) // Pass through audio (copy input to output)
output[..len].copy_from_slice(&input[..len]); output[..len].copy_from_slice(&input[..len]);
// Capture samples to buffer // Capture audio samples to buffer
if let Ok(mut buffer) = self.buffer.lock() { if let Ok(mut buffer) = self.buffer.lock() {
buffer.write(&input[..len]); buffer.write(&input[..len]);
} }
// Capture CV samples if CV input is connected (input 2)
if inputs.len() > 2 && !inputs[2].is_empty() {
let cv_input = inputs[2];
if let Ok(mut cv_buffer) = self.cv_buffer.lock() {
cv_buffer.write(&cv_input[..len.min(cv_input.len())]);
}
}
// Update last sample for trigger detection (use left channel, frame 0) // Update last sample for trigger detection (use left channel, frame 0)
if !input.is_empty() { if !input.is_empty() {
self.last_sample = input[0]; self.last_sample = input[0];
@ -279,6 +285,7 @@ impl AudioNode for OscilloscopeNode {
sample_counter: 0, sample_counter: 0,
trigger_period: 480, trigger_period: 480,
buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))), buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
cv_buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
inputs: self.inputs.clone(), inputs: self.inputs.clone(),
outputs: self.outputs.clone(), outputs: self.outputs.clone(),
parameters: self.parameters.clone(), parameters: self.parameters.clone(),
@ -288,4 +295,8 @@ impl AudioNode for OscilloscopeNode {
fn get_oscilloscope_data(&self, sample_count: usize) -> Option<Vec<f32>> { fn get_oscilloscope_data(&self, sample_count: usize) -> Option<Vec<f32>> {
Some(self.read_samples(sample_count)) Some(self.read_samples(sample_count))
} }
fn get_oscilloscope_cv_data(&self, sample_count: usize) -> Option<Vec<f32>> {
Some(self.read_cv_samples(sample_count))
}
} }

View File

@ -120,7 +120,7 @@ impl AudioNode for PanNode {
if inputs.len() > 1 && frame < inputs[1].len() { if inputs.len() > 1 && frame < inputs[1].len() {
let cv = inputs[1][frame]; // CV is mono let cv = inputs[1][frame]; // CV is mono
// CV is 0-1, map to -1 to +1 range // CV is 0-1, map to -1 to +1 range
pan += (cv * 2.0 - 1.0); pan += cv * 2.0 - 1.0;
pan = pan.clamp(-1.0, 1.0); pan = pan.clamp(-1.0, 1.0);
} }

View File

@ -1,4 +1,4 @@
use crate::audio::node_graph::{AudioNode, NodeCategory, NodePort, Parameter, ParameterUnit, SignalType}; use crate::audio::node_graph::{AudioNode, NodeCategory, NodePort, Parameter, SignalType};
use crate::audio::midi::MidiEvent; use crate::audio::midi::MidiEvent;
/// Splitter node - copies input to multiple outputs for parallel routing /// Splitter node - copies input to multiple outputs for parallel routing

View File

@ -1,9 +1,8 @@
use super::buffer_pool::BufferPool; use super::buffer_pool::BufferPool;
use super::clip::Clip; use super::clip::Clip;
use super::midi::MidiClip; use super::midi::{MidiClip, MidiEvent};
use super::pool::AudioPool; use super::pool::AudioPool;
use super::track::{AudioTrack, Metatrack, MidiTrack, RenderContext, TrackId, TrackNode}; use super::track::{AudioTrack, Metatrack, MidiTrack, RenderContext, TrackId, TrackNode};
use crate::effects::Effect;
use std::collections::HashMap; use std::collections::HashMap;
/// Project manages the hierarchical track structure /// Project manages the hierarchical track structure
@ -199,12 +198,18 @@ impl Project {
} }
/// Get oscilloscope data from a node in a track's graph /// Get oscilloscope data from a node in a track's graph
pub fn get_oscilloscope_data(&self, track_id: TrackId, node_id: u32, sample_count: usize) -> Option<Vec<f32>> { pub fn get_oscilloscope_data(&self, track_id: TrackId, node_id: u32, sample_count: usize) -> Option<(Vec<f32>, Vec<f32>)> {
if let Some(TrackNode::Midi(track)) = self.tracks.get(&track_id) { if let Some(TrackNode::Midi(track)) = self.tracks.get(&track_id) {
if let Some(ref graph) = track.instrument_graph { let graph = &track.instrument_graph;
let node_idx = petgraph::stable_graph::NodeIndex::new(node_id as usize); let node_idx = petgraph::stable_graph::NodeIndex::new(node_id as usize);
return graph.get_oscilloscope_data(node_idx, sample_count);
} // Get audio data
let audio = graph.get_oscilloscope_data(node_idx, sample_count)?;
// Get CV data (may be empty if no CV input or not an oscilloscope node)
let cv = graph.get_oscilloscope_cv_data(node_idx, sample_count).unwrap_or_default();
return Some((audio, cv));
} }
None None
} }
@ -244,44 +249,6 @@ impl Project {
} }
} }
/// Add an effect to a track (audio, MIDI, or group)
pub fn add_effect(&mut self, track_id: TrackId, effect: Box<dyn Effect>) -> Result<(), &'static str> {
match self.tracks.get_mut(&track_id) {
Some(TrackNode::Audio(track)) => {
track.add_effect(effect);
Ok(())
}
Some(TrackNode::Midi(track)) => {
track.add_effect(effect);
Ok(())
}
Some(TrackNode::Group(group)) => {
group.add_effect(effect);
Ok(())
}
None => Err("Track not found"),
}
}
/// Clear effects from a track
pub fn clear_effects(&mut self, track_id: TrackId) -> Result<(), &'static str> {
match self.tracks.get_mut(&track_id) {
Some(TrackNode::Audio(track)) => {
track.clear_effects();
Ok(())
}
Some(TrackNode::Midi(track)) => {
track.clear_effects();
Ok(())
}
Some(TrackNode::Group(group)) => {
group.clear_effects();
Ok(())
}
None => Err("Track not found"),
}
}
/// Render all root tracks into the output buffer /// Render all root tracks into the output buffer
pub fn render( pub fn render(
&mut self, &mut self,
@ -399,13 +366,8 @@ impl Project {
); );
} }
// Apply group effects
if let Some(TrackNode::Group(group)) = self.tracks.get_mut(&track_id) {
for effect in &mut group.effects {
effect.process(&mut group_buffer, ctx.channels as usize, ctx.sample_rate);
}
// Apply group volume and mix into output // Apply group volume and mix into output
if let Some(TrackNode::Group(group)) = self.tracks.get_mut(&track_id) {
for (out_sample, group_sample) in output.iter_mut().zip(group_buffer.iter()) { for (out_sample, group_sample) in output.iter_mut().zip(group_buffer.iter()) {
*out_sample += group_sample * group.volume; *out_sample += group_sample * group.volume;
} }
@ -439,30 +401,21 @@ impl Project {
} }
/// Send a live MIDI note on event to a track's instrument /// Send a live MIDI note on event to a track's instrument
/// Note: With node-based instruments, MIDI events are handled during the process() call
pub fn send_midi_note_on(&mut self, track_id: TrackId, note: u8, velocity: u8) { pub fn send_midi_note_on(&mut self, track_id: TrackId, note: u8, velocity: u8) {
// Queue the MIDI note-on event to the track's live MIDI queue
if let Some(TrackNode::Midi(track)) = self.tracks.get_mut(&track_id) { if let Some(TrackNode::Midi(track)) = self.tracks.get_mut(&track_id) {
// Create a MIDI event and queue it to the instrument let event = MidiEvent::note_on(0, 0, note, velocity);
let event = crate::audio::midi::MidiEvent { track.queue_live_midi(event);
timestamp: 0, // Immediate playback
status: 0x90, // Note on
data1: note,
data2: velocity,
};
track.instrument.queue_event(event);
} }
} }
/// Send a live MIDI note off event to a track's instrument /// Send a live MIDI note off event to a track's instrument
pub fn send_midi_note_off(&mut self, track_id: TrackId, note: u8) { pub fn send_midi_note_off(&mut self, track_id: TrackId, note: u8) {
// Queue the MIDI note-off event to the track's live MIDI queue
if let Some(TrackNode::Midi(track)) = self.tracks.get_mut(&track_id) { if let Some(TrackNode::Midi(track)) = self.tracks.get_mut(&track_id) {
// Create a MIDI event and queue it to the instrument let event = MidiEvent::note_off(0, 0, note, 0);
let event = crate::audio::midi::MidiEvent { track.queue_live_midi(event);
timestamp: 0, // Immediate playback
status: 0x80, // Note off
data1: note,
data2: 0,
};
track.instrument.queue_event(event);
} }
} }
} }

View File

@ -1,9 +1,8 @@
use super::automation::{AutomationLane, AutomationLaneId, ParameterId}; use super::automation::{AutomationLane, AutomationLaneId, ParameterId};
use super::clip::Clip; use super::clip::Clip;
use super::midi::MidiClip; use super::midi::{MidiClip, MidiEvent};
use super::node_graph::InstrumentGraph; use super::node_graph::InstrumentGraph;
use super::pool::AudioPool; use super::pool::AudioPool;
use crate::effects::{Effect, SimpleSynth};
use std::collections::HashMap; use std::collections::HashMap;
/// Track ID type /// Track ID type
@ -134,7 +133,6 @@ pub struct Metatrack {
pub id: TrackId, pub id: TrackId,
pub name: String, pub name: String,
pub children: Vec<TrackId>, pub children: Vec<TrackId>,
pub effects: Vec<Box<dyn Effect>>,
pub volume: f32, pub volume: f32,
pub muted: bool, pub muted: bool,
pub solo: bool, pub solo: bool,
@ -156,7 +154,6 @@ impl Metatrack {
id, id,
name, name,
children: Vec::new(), children: Vec::new(),
effects: Vec::new(),
volume: 1.0, volume: 1.0,
muted: false, muted: false,
solo: false, solo: false,
@ -240,16 +237,6 @@ impl Metatrack {
self.children.retain(|&id| id != track_id); self.children.retain(|&id| id != track_id);
} }
/// Add an effect to the group's effect chain
pub fn add_effect(&mut self, effect: Box<dyn Effect>) {
self.effects.push(effect);
}
/// Clear all effects from the group
pub fn clear_effects(&mut self) {
self.effects.clear();
}
/// Set group volume /// Set group volume
pub fn set_volume(&mut self, volume: f32) { pub fn set_volume(&mut self, volume: f32) {
self.volume = volume.max(0.0); self.volume = volume.max(0.0);
@ -297,38 +284,40 @@ impl Metatrack {
} }
} }
/// MIDI track with MIDI clips and a virtual instrument /// MIDI track with MIDI clips and a node-based instrument
pub struct MidiTrack { pub struct MidiTrack {
pub id: TrackId, pub id: TrackId,
pub name: String, pub name: String,
pub clips: Vec<MidiClip>, pub clips: Vec<MidiClip>,
pub instrument: SimpleSynth, pub instrument_graph: InstrumentGraph,
pub effects: Vec<Box<dyn Effect>>,
pub volume: f32, pub volume: f32,
pub muted: bool, pub muted: bool,
pub solo: bool, pub solo: bool,
/// Automation lanes for this track /// Automation lanes for this track
pub automation_lanes: HashMap<AutomationLaneId, AutomationLane>, pub automation_lanes: HashMap<AutomationLaneId, AutomationLane>,
next_automation_id: AutomationLaneId, next_automation_id: AutomationLaneId,
/// Optional instrument graph (replaces SimpleSynth when present) /// Queue for live MIDI input (virtual keyboard, MIDI controllers)
pub instrument_graph: Option<InstrumentGraph>, live_midi_queue: Vec<MidiEvent>,
} }
impl MidiTrack { impl MidiTrack {
/// Create a new MIDI track with default settings /// Create a new MIDI track with default settings
pub fn new(id: TrackId, name: String) -> Self { pub fn new(id: TrackId, name: String) -> Self {
// Use default sample rate and a large buffer size that can accommodate any callback
let default_sample_rate = 48000;
let default_buffer_size = 8192;
Self { Self {
id, id,
name, name,
clips: Vec::new(), clips: Vec::new(),
instrument: SimpleSynth::new(), instrument_graph: InstrumentGraph::new(default_sample_rate, default_buffer_size),
effects: Vec::new(),
volume: 1.0, volume: 1.0,
muted: false, muted: false,
solo: false, solo: false,
automation_lanes: HashMap::new(), automation_lanes: HashMap::new(),
next_automation_id: 0, next_automation_id: 0,
instrument_graph: None, live_midi_queue: Vec::new(),
} }
} }
@ -357,16 +346,6 @@ impl MidiTrack {
self.automation_lanes.remove(&lane_id).is_some() self.automation_lanes.remove(&lane_id).is_some()
} }
/// Add an effect to the track's effect chain
pub fn add_effect(&mut self, effect: Box<dyn Effect>) {
self.effects.push(effect);
}
/// Clear all effects from the track
pub fn clear_effects(&mut self) {
self.effects.clear();
}
/// Add a MIDI clip to this track /// Add a MIDI clip to this track
pub fn add_clip(&mut self, clip: MidiClip) { pub fn add_clip(&mut self, clip: MidiClip) {
self.clips.push(clip); self.clips.push(clip);
@ -393,8 +372,20 @@ impl MidiTrack {
} }
/// Stop all currently playing notes on this track's instrument /// Stop all currently playing notes on this track's instrument
/// Note: With node-based instruments, stopping is handled by ceasing MIDI input
pub fn stop_all_notes(&mut self) { pub fn stop_all_notes(&mut self) {
self.instrument.all_notes_off(); // No-op: Node-based instruments stop when they receive no MIDI input
// Individual synthesizer nodes handle note-off events appropriately
}
/// Queue a live MIDI event (from virtual keyboard or MIDI controller)
pub fn queue_live_midi(&mut self, event: MidiEvent) {
self.live_midi_queue.push(event);
}
/// Clear the live MIDI queue
pub fn clear_live_midi_queue(&mut self) {
self.live_midi_queue.clear();
} }
/// Process only live MIDI input (queued events) without rendering clips /// Process only live MIDI input (queued events) without rendering clips
@ -402,27 +393,14 @@ impl MidiTrack {
pub fn process_live_input( pub fn process_live_input(
&mut self, &mut self,
output: &mut [f32], output: &mut [f32],
sample_rate: u32, _sample_rate: u32,
channels: u32, _channels: u32,
) { ) {
// Generate audio - use instrument graph if available, otherwise SimpleSynth // Generate audio using instrument graph with live MIDI events
if let Some(graph) = &mut self.instrument_graph { self.instrument_graph.process(output, &self.live_midi_queue);
// Get pending MIDI events from SimpleSynth (they're queued there by send_midi_note_on/off)
// We need to drain them so they're not processed again
let events: Vec<crate::audio::midi::MidiEvent> =
self.instrument.pending_events.drain(..).collect();
// Process graph with MIDI events // Clear the queue after processing
graph.process(output, &events); self.live_midi_queue.clear();
} else {
// Fallback to SimpleSynth (which processes queued events)
self.instrument.process(output, channels as usize, sample_rate);
}
// Apply effect chain
for effect in &mut self.effects {
effect.process(output, channels as usize, sample_rate);
}
// Apply track volume (no automation during live input) // Apply track volume (no automation during live input)
for sample in output.iter_mut() { for sample in output.iter_mut() {
@ -455,22 +433,8 @@ impl MidiTrack {
} }
} }
// Generate audio - use instrument graph if available, otherwise SimpleSynth // Generate audio using instrument graph
if let Some(graph) = &mut self.instrument_graph { self.instrument_graph.process(output, &midi_events);
// Use node graph for audio generation
graph.process(output, &midi_events);
} else {
// Fallback to SimpleSynth
for event in &midi_events {
self.instrument.queue_event(*event);
}
self.instrument.process(output, channels as usize, sample_rate);
}
// Apply effect chain
for effect in &mut self.effects {
effect.process(output, channels as usize, sample_rate);
}
// Evaluate and apply automation // Evaluate and apply automation
let effective_volume = self.evaluate_automation_at_time(playhead_seconds); let effective_volume = self.evaluate_automation_at_time(playhead_seconds);
@ -505,12 +469,11 @@ impl MidiTrack {
} }
} }
/// Audio track with clips and effect chain /// Audio track with clips
pub struct AudioTrack { pub struct AudioTrack {
pub id: TrackId, pub id: TrackId,
pub name: String, pub name: String,
pub clips: Vec<Clip>, pub clips: Vec<Clip>,
pub effects: Vec<Box<dyn Effect>>,
pub volume: f32, pub volume: f32,
pub muted: bool, pub muted: bool,
pub solo: bool, pub solo: bool,
@ -526,7 +489,6 @@ impl AudioTrack {
id, id,
name, name,
clips: Vec::new(), clips: Vec::new(),
effects: Vec::new(),
volume: 1.0, volume: 1.0,
muted: false, muted: false,
solo: false, solo: false,
@ -560,25 +522,6 @@ impl AudioTrack {
self.automation_lanes.remove(&lane_id).is_some() self.automation_lanes.remove(&lane_id).is_some()
} }
/// Add an effect to the track's effect chain
pub fn add_effect(&mut self, effect: Box<dyn Effect>) {
self.effects.push(effect);
}
/// Remove an effect from the chain by index
pub fn remove_effect(&mut self, index: usize) -> Option<Box<dyn Effect>> {
if index < self.effects.len() {
Some(self.effects.remove(index))
} else {
None
}
}
/// Clear all effects from the track
pub fn clear_effects(&mut self) {
self.effects.clear();
}
/// Add a clip to this track /// Add a clip to this track
pub fn add_clip(&mut self, clip: Clip) { pub fn add_clip(&mut self, clip: Clip) {
self.clips.push(clip); self.clips.push(clip);
@ -634,11 +577,6 @@ impl AudioTrack {
} }
} }
// Apply effect chain
for effect in &mut self.effects {
effect.process(output, channels as usize, sample_rate);
}
// Evaluate and apply automation // Evaluate and apply automation
let effective_volume = self.evaluate_automation_at_time(playhead_seconds); let effective_volume = self.evaluate_automation_at_time(playhead_seconds);

View File

@ -1,3 +1,3 @@
pub mod types; pub mod types;
pub use types::{AudioEvent, Command, Query, QueryResponse}; pub use types::{AudioEvent, Command, OscilloscopeData, Query, QueryResponse};

View File

@ -30,16 +30,6 @@ pub enum Command {
/// Move a clip to a new timeline position /// Move a clip to a new timeline position
MoveClip(TrackId, ClipId, f64), MoveClip(TrackId, ClipId, f64),
// Effect management commands
/// Add or update gain effect on track (gain in dB)
AddGainEffect(TrackId, f32),
/// Add or update pan effect on track (-1.0 = left, 0.0 = center, 1.0 = right)
AddPanEffect(TrackId, f32),
/// Add or update EQ effect on track (low_db, mid_db, high_db)
AddEQEffect(TrackId, f32, f32, f32),
/// Clear all effects from a track
ClearEffects(TrackId),
// Metatrack management commands // Metatrack management commands
/// Create a new metatrack with a name /// Create a new metatrack with a name
CreateMetatrack(String), CreateMetatrack(String),
@ -211,11 +201,20 @@ pub enum Query {
GetOscilloscopeData(TrackId, u32, usize), GetOscilloscopeData(TrackId, u32, usize),
} }
/// Oscilloscope data from a node
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct OscilloscopeData {
/// Audio samples
pub audio: Vec<f32>,
/// CV samples (may be empty if no CV input)
pub cv: Vec<f32>,
}
/// Responses to synchronous queries /// Responses to synchronous queries
#[derive(Debug)] #[derive(Debug)]
pub enum QueryResponse { pub enum QueryResponse {
/// Graph state as JSON string /// Graph state as JSON string
GraphState(Result<String, String>), GraphState(Result<String, String>),
/// Oscilloscope data samples /// Oscilloscope data samples
OscilloscopeData(Result<Vec<f32>, String>), OscilloscopeData(Result<OscilloscopeData, String>),
} }

View File

@ -18,8 +18,7 @@ pub use audio::{
TrackNode, TrackNode,
}; };
pub use audio::node_graph::{GraphPreset, InstrumentGraph, PresetMetadata, SerializedConnection, SerializedNode}; pub use audio::node_graph::{GraphPreset, InstrumentGraph, PresetMetadata, SerializedConnection, SerializedNode};
pub use command::{AudioEvent, Command}; pub use command::{AudioEvent, Command, OscilloscopeData};
pub use effects::{Effect, GainEffect, PanEffect, SimpleEQ, SimpleSynth};
pub use io::{load_midi_file, AudioFile, WaveformPeak, WavWriter}; pub use io::{load_midi_file, AudioFile, WaveformPeak, WavWriter};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

View File

@ -216,13 +216,6 @@ pub async fn audio_set_track_parameter(
"volume" => controller.set_track_volume(track_id, value), "volume" => controller.set_track_volume(track_id, value),
"mute" => controller.set_track_mute(track_id, value > 0.5), "mute" => controller.set_track_mute(track_id, value > 0.5),
"solo" => controller.set_track_solo(track_id, value > 0.5), "solo" => controller.set_track_solo(track_id, value > 0.5),
"pan" => {
// Pan effect - would need to add this via effects system
controller.add_pan_effect(track_id, value);
}
"gain_db" => {
controller.add_gain_effect(track_id, value);
}
_ => return Err(format!("Unknown parameter: {}", parameter)), _ => return Err(format!("Unknown parameter: {}", parameter)),
} }
Ok(()) Ok(())
@ -231,19 +224,11 @@ pub async fn audio_set_track_parameter(
} }
} }
#[tauri::command]
pub async fn audio_get_available_instruments() -> Result<Vec<String>, String> {
// Return list of available instruments
// For now, only SimpleSynth is available
Ok(vec!["SimpleSynth".to_string()])
}
#[tauri::command] #[tauri::command]
pub async fn audio_create_track( pub async fn audio_create_track(
state: tauri::State<'_, Arc<Mutex<AudioState>>>, state: tauri::State<'_, Arc<Mutex<AudioState>>>,
name: String, name: String,
track_type: String, track_type: String,
instrument: Option<String>,
) -> Result<u32, String> { ) -> Result<u32, String> {
let mut audio_state = state.lock().unwrap(); let mut audio_state = state.lock().unwrap();
@ -254,14 +239,7 @@ pub async fn audio_create_track(
if let Some(controller) = &mut audio_state.controller { if let Some(controller) = &mut audio_state.controller {
match track_type.as_str() { match track_type.as_str() {
"audio" => controller.create_audio_track(name), "audio" => controller.create_audio_track(name),
"midi" => { "midi" => controller.create_midi_track(name),
// Validate instrument for MIDI tracks
let inst = instrument.unwrap_or_else(|| "SimpleSynth".to_string());
if inst != "SimpleSynth" {
return Err(format!("Unknown instrument: {}", inst));
}
controller.create_midi_track(name)
},
_ => return Err(format!("Unknown track type: {}", track_type)), _ => return Err(format!("Unknown track type: {}", track_type)),
} }
Ok(track_id) Ok(track_id)
@ -1137,7 +1115,7 @@ pub async fn get_oscilloscope_data(
track_id: u32, track_id: u32,
node_id: u32, node_id: u32,
sample_count: usize, sample_count: usize,
) -> Result<Vec<f32>, String> { ) -> Result<daw_backend::OscilloscopeData, String> {
let mut audio_state = state.lock().unwrap(); let mut audio_state = state.lock().unwrap();
if let Some(controller) = &mut audio_state.controller { if let Some(controller) = &mut audio_state.controller {

View File

@ -199,7 +199,6 @@ pub fn run() {
audio::audio_seek, audio::audio_seek,
audio::audio_test_beep, audio::audio_test_beep,
audio::audio_set_track_parameter, audio::audio_set_track_parameter,
audio::audio_get_available_instruments,
audio::audio_create_track, audio::audio_create_track,
audio::audio_load_file, audio::audio_load_file,
audio::audio_add_clip, audio::audio_add_clip,