Remove old SimpleSynth and effect system
This commit is contained in:
parent
2cdde33e37
commit
d7dc423fe3
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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>),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue