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::track::{Track, TrackId, TrackNode};
|
||||
use crate::command::{AudioEvent, Command, Query, QueryResponse};
|
||||
use crate::effects::{Effect, GainEffect, PanEffect, SimpleEQ};
|
||||
use petgraph::stable_graph::NodeIndex;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
|
@ -102,7 +101,6 @@ impl Engine {
|
|||
if let Some(node) = self.project.get_track_mut(id) {
|
||||
if let crate::audio::track::TrackNode::Audio(audio_track) = node {
|
||||
audio_track.clips = track.clips;
|
||||
audio_track.effects = track.effects;
|
||||
audio_track.volume = track.volume;
|
||||
audio_track.muted = track.muted;
|
||||
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) => {
|
||||
let track_id = self.project.add_group_track(name.clone(), None);
|
||||
// Notify UI about the new metatrack
|
||||
|
|
@ -743,13 +641,8 @@ impl Engine {
|
|||
Command::GraphAddNode(track_id, node_type, x, y) => {
|
||||
// Get MIDI track (graphs are only for MIDI tracks currently)
|
||||
if let Some(TrackNode::Midi(track)) = self.project.get_track_mut(track_id) {
|
||||
// Create graph if it doesn't exist
|
||||
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 {
|
||||
let graph = &mut track.instrument_graph;
|
||||
{
|
||||
// Create the node based on type
|
||||
let node: Box<dyn crate::audio::node_graph::AudioNode> = match node_type.as_str() {
|
||||
"Oscillator" => Box::new(OscillatorNode::new("Oscillator".to_string())),
|
||||
|
|
@ -810,7 +703,8 @@ impl Engine {
|
|||
|
||||
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(graph) = &mut track.instrument_graph {
|
||||
let graph = &mut track.instrument_graph;
|
||||
{
|
||||
let va_idx = NodeIndex::new(voice_allocator_id as usize);
|
||||
|
||||
// Create the node
|
||||
|
|
@ -871,7 +765,8 @@ impl Engine {
|
|||
|
||||
Command::GraphRemoveNode(track_id, node_index) => {
|
||||
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);
|
||||
graph.remove_node(node_idx);
|
||||
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) => {
|
||||
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 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) => {
|
||||
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);
|
||||
|
||||
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) => {
|
||||
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 to_idx = NodeIndex::new(to as usize);
|
||||
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) => {
|
||||
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);
|
||||
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
|
||||
graph_node.node.set_parameter(param_id, value);
|
||||
|
|
@ -945,7 +844,8 @@ impl Engine {
|
|||
|
||||
Command::GraphSetMidiTarget(track_id, node_index, enabled) => {
|
||||
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);
|
||||
graph.set_midi_target(node_idx, enabled);
|
||||
}
|
||||
|
|
@ -954,7 +854,8 @@ impl Engine {
|
|||
|
||||
Command::GraphSetOutputNode(track_id, node_index) => {
|
||||
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);
|
||||
graph.set_output_node(Some(node_idx));
|
||||
}
|
||||
|
|
@ -963,7 +864,7 @@ impl Engine {
|
|||
|
||||
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(ref graph) = track.instrument_graph {
|
||||
let graph = &track.instrument_graph;
|
||||
// Serialize the graph to a preset
|
||||
let mut preset = graph.to_preset(&preset_name);
|
||||
preset.metadata.description = description;
|
||||
|
|
@ -986,7 +887,6 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::GraphLoadPreset(track_id, preset_path) => {
|
||||
// Read and deserialize the preset
|
||||
|
|
@ -1001,7 +901,7 @@ impl Engine {
|
|||
Ok(graph) => {
|
||||
// Replace the track's graph
|
||||
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));
|
||||
// Emit preset loaded event after everything is loaded
|
||||
let _ = self.event_tx.push(AudioEvent::GraphPresetLoaded(track_id));
|
||||
|
|
@ -1036,7 +936,7 @@ impl Engine {
|
|||
use crate::audio::node_graph::nodes::VoiceAllocatorNode;
|
||||
|
||||
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);
|
||||
|
||||
// Get the VoiceAllocator node and serialize its template
|
||||
|
|
@ -1059,13 +959,12 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::SamplerLoadSample(track_id, node_id, file_path) => {
|
||||
use crate::audio::node_graph::nodes::SimpleSamplerNode;
|
||||
|
||||
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);
|
||||
|
||||
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) => {
|
||||
use crate::audio::node_graph::nodes::MultiSamplerNode;
|
||||
|
||||
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);
|
||||
|
||||
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) => {
|
||||
use crate::audio::node_graph::nodes::MultiSamplerNode;
|
||||
|
||||
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);
|
||||
|
||||
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) => {
|
||||
use crate::audio::node_graph::nodes::MultiSamplerNode;
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
fn handle_query(&mut self, query: Query) {
|
||||
let response = match query {
|
||||
Query::GetGraphState(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");
|
||||
match preset.to_json() {
|
||||
Ok(json) => QueryResponse::GraphState(Ok(json)),
|
||||
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 {
|
||||
QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id)))
|
||||
}
|
||||
}
|
||||
Query::GetTemplateState(track_id, voice_allocator_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);
|
||||
if let Some(graph_node) = graph.get_graph_node_mut(node_idx) {
|
||||
// Downcast to VoiceAllocatorNode
|
||||
|
|
@ -1197,16 +1084,16 @@ impl Engine {
|
|||
} else {
|
||||
QueryResponse::GraphState(Err("Voice allocator node not found".to_string()))
|
||||
}
|
||||
} else {
|
||||
QueryResponse::GraphState(Err("Graph not found".to_string()))
|
||||
}
|
||||
} else {
|
||||
QueryResponse::GraphState(Err(format!("Track {} not found or is not a MIDI track", track_id)))
|
||||
}
|
||||
}
|
||||
Query::GetOscilloscopeData(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!(
|
||||
"Failed to get oscilloscope data from track {} node {}",
|
||||
track_id, node_id
|
||||
|
|
@ -1437,26 +1324,6 @@ impl EngineController {
|
|||
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
|
||||
pub fn get_playhead_samples(&self) -> u64 {
|
||||
self.playhead.load(Ordering::Relaxed)
|
||||
|
|
@ -1770,7 +1637,7 @@ impl EngineController {
|
|||
}
|
||||
|
||||
/// 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
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -499,6 +499,11 @@ impl InstrumentGraph {
|
|||
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
|
||||
/// Note: Due to lifetime constraints with trait objects, this returns a mutable reference
|
||||
/// 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>> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ impl TriggerMode {
|
|||
}
|
||||
|
||||
/// Circular buffer for storing audio samples
|
||||
struct CircularBuffer {
|
||||
pub struct CircularBuffer {
|
||||
buffer: Vec<f32>,
|
||||
write_pos: 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 {
|
||||
name: String,
|
||||
time_scale: f32, // Milliseconds to display (10-1000ms)
|
||||
|
|
@ -86,8 +86,9 @@ pub struct OscilloscopeNode {
|
|||
sample_counter: usize, // Counter for V/oct triggering
|
||||
trigger_period: usize, // Period in samples for V/oct triggering
|
||||
|
||||
// Shared buffer for reading from Tauri commands
|
||||
buffer: Arc<Mutex<CircularBuffer>>,
|
||||
// Shared buffers for reading from Tauri commands
|
||||
buffer: Arc<Mutex<CircularBuffer>>, // Audio buffer
|
||||
cv_buffer: Arc<Mutex<CircularBuffer>>, // CV buffer
|
||||
|
||||
inputs: Vec<NodePort>,
|
||||
outputs: Vec<NodePort>,
|
||||
|
|
@ -101,6 +102,7 @@ impl OscilloscopeNode {
|
|||
let inputs = vec![
|
||||
NodePort::new("Audio In", SignalType::Audio, 0),
|
||||
NodePort::new("V/oct", SignalType::CV, 1),
|
||||
NodePort::new("CV In", SignalType::CV, 2),
|
||||
];
|
||||
|
||||
let outputs = vec![
|
||||
|
|
@ -123,6 +125,7 @@ impl OscilloscopeNode {
|
|||
sample_counter: 0,
|
||||
trigger_period: 480, // Default to ~100Hz at 48kHz
|
||||
buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
|
||||
cv_buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
|
||||
inputs,
|
||||
outputs,
|
||||
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
|
||||
pub fn clear_buffer(&self) {
|
||||
if let Ok(mut buffer) = self.buffer.lock() {
|
||||
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)
|
||||
|
|
@ -155,23 +170,6 @@ impl OscilloscopeNode {
|
|||
fn voct_to_frequency(voct: f32) -> f32 {
|
||||
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 {
|
||||
|
|
@ -242,11 +240,19 @@ impl AudioNode for OscilloscopeNode {
|
|||
// Pass through audio (copy input to output)
|
||||
output[..len].copy_from_slice(&input[..len]);
|
||||
|
||||
// Capture samples to buffer
|
||||
// Capture audio samples to buffer
|
||||
if let Ok(mut buffer) = self.buffer.lock() {
|
||||
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)
|
||||
if !input.is_empty() {
|
||||
self.last_sample = input[0];
|
||||
|
|
@ -279,6 +285,7 @@ impl AudioNode for OscilloscopeNode {
|
|||
sample_counter: 0,
|
||||
trigger_period: 480,
|
||||
buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
|
||||
cv_buffer: Arc::new(Mutex::new(CircularBuffer::new(BUFFER_SIZE))),
|
||||
inputs: self.inputs.clone(),
|
||||
outputs: self.outputs.clone(),
|
||||
parameters: self.parameters.clone(),
|
||||
|
|
@ -288,4 +295,8 @@ impl AudioNode for OscilloscopeNode {
|
|||
fn get_oscilloscope_data(&self, sample_count: usize) -> Option<Vec<f32>> {
|
||||
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() {
|
||||
let cv = inputs[1][frame]; // CV is mono
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// Splitter node - copies input to multiple outputs for parallel routing
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use super::buffer_pool::BufferPool;
|
||||
use super::clip::Clip;
|
||||
use super::midi::MidiClip;
|
||||
use super::midi::{MidiClip, MidiEvent};
|
||||
use super::pool::AudioPool;
|
||||
use super::track::{AudioTrack, Metatrack, MidiTrack, RenderContext, TrackId, TrackNode};
|
||||
use crate::effects::Effect;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Project manages the hierarchical track structure
|
||||
|
|
@ -199,12 +198,18 @@ impl Project {
|
|||
}
|
||||
|
||||
/// 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(ref graph) = track.instrument_graph {
|
||||
let graph = &track.instrument_graph;
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
pub fn render(
|
||||
&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
|
||||
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()) {
|
||||
*out_sample += group_sample * group.volume;
|
||||
}
|
||||
|
|
@ -439,30 +401,21 @@ impl Project {
|
|||
}
|
||||
|
||||
/// 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) {
|
||||
// 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) {
|
||||
// Create a MIDI event and queue it to the instrument
|
||||
let event = crate::audio::midi::MidiEvent {
|
||||
timestamp: 0, // Immediate playback
|
||||
status: 0x90, // Note on
|
||||
data1: note,
|
||||
data2: velocity,
|
||||
};
|
||||
track.instrument.queue_event(event);
|
||||
let event = MidiEvent::note_on(0, 0, note, velocity);
|
||||
track.queue_live_midi(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
// 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) {
|
||||
// Create a MIDI event and queue it to the instrument
|
||||
let event = crate::audio::midi::MidiEvent {
|
||||
timestamp: 0, // Immediate playback
|
||||
status: 0x80, // Note off
|
||||
data1: note,
|
||||
data2: 0,
|
||||
};
|
||||
track.instrument.queue_event(event);
|
||||
let event = MidiEvent::note_off(0, 0, note, 0);
|
||||
track.queue_live_midi(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use super::automation::{AutomationLane, AutomationLaneId, ParameterId};
|
||||
use super::clip::Clip;
|
||||
use super::midi::MidiClip;
|
||||
use super::midi::{MidiClip, MidiEvent};
|
||||
use super::node_graph::InstrumentGraph;
|
||||
use super::pool::AudioPool;
|
||||
use crate::effects::{Effect, SimpleSynth};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Track ID type
|
||||
|
|
@ -134,7 +133,6 @@ pub struct Metatrack {
|
|||
pub id: TrackId,
|
||||
pub name: String,
|
||||
pub children: Vec<TrackId>,
|
||||
pub effects: Vec<Box<dyn Effect>>,
|
||||
pub volume: f32,
|
||||
pub muted: bool,
|
||||
pub solo: bool,
|
||||
|
|
@ -156,7 +154,6 @@ impl Metatrack {
|
|||
id,
|
||||
name,
|
||||
children: Vec::new(),
|
||||
effects: Vec::new(),
|
||||
volume: 1.0,
|
||||
muted: false,
|
||||
solo: false,
|
||||
|
|
@ -240,16 +237,6 @@ impl Metatrack {
|
|||
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
|
||||
pub fn set_volume(&mut self, volume: f32) {
|
||||
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 id: TrackId,
|
||||
pub name: String,
|
||||
pub clips: Vec<MidiClip>,
|
||||
pub instrument: SimpleSynth,
|
||||
pub effects: Vec<Box<dyn Effect>>,
|
||||
pub instrument_graph: InstrumentGraph,
|
||||
pub volume: f32,
|
||||
pub muted: bool,
|
||||
pub solo: bool,
|
||||
/// Automation lanes for this track
|
||||
pub automation_lanes: HashMap<AutomationLaneId, AutomationLane>,
|
||||
next_automation_id: AutomationLaneId,
|
||||
/// Optional instrument graph (replaces SimpleSynth when present)
|
||||
pub instrument_graph: Option<InstrumentGraph>,
|
||||
/// Queue for live MIDI input (virtual keyboard, MIDI controllers)
|
||||
live_midi_queue: Vec<MidiEvent>,
|
||||
}
|
||||
|
||||
impl MidiTrack {
|
||||
/// Create a new MIDI track with default settings
|
||||
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 {
|
||||
id,
|
||||
name,
|
||||
clips: Vec::new(),
|
||||
instrument: SimpleSynth::new(),
|
||||
effects: Vec::new(),
|
||||
instrument_graph: InstrumentGraph::new(default_sample_rate, default_buffer_size),
|
||||
volume: 1.0,
|
||||
muted: false,
|
||||
solo: false,
|
||||
automation_lanes: HashMap::new(),
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn add_clip(&mut self, clip: MidiClip) {
|
||||
self.clips.push(clip);
|
||||
|
|
@ -393,8 +372,20 @@ impl MidiTrack {
|
|||
}
|
||||
|
||||
/// 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) {
|
||||
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
|
||||
|
|
@ -402,27 +393,14 @@ impl MidiTrack {
|
|||
pub fn process_live_input(
|
||||
&mut self,
|
||||
output: &mut [f32],
|
||||
sample_rate: u32,
|
||||
channels: u32,
|
||||
_sample_rate: u32,
|
||||
_channels: u32,
|
||||
) {
|
||||
// Generate audio - use instrument graph if available, otherwise SimpleSynth
|
||||
if let Some(graph) = &mut self.instrument_graph {
|
||||
// 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();
|
||||
// Generate audio using instrument graph with live MIDI events
|
||||
self.instrument_graph.process(output, &self.live_midi_queue);
|
||||
|
||||
// Process graph with MIDI events
|
||||
graph.process(output, &events);
|
||||
} 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);
|
||||
}
|
||||
// Clear the queue after processing
|
||||
self.live_midi_queue.clear();
|
||||
|
||||
// Apply track volume (no automation during live input)
|
||||
for sample in output.iter_mut() {
|
||||
|
|
@ -455,22 +433,8 @@ impl MidiTrack {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate audio - use instrument graph if available, otherwise SimpleSynth
|
||||
if let Some(graph) = &mut self.instrument_graph {
|
||||
// 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);
|
||||
}
|
||||
// Generate audio using instrument graph
|
||||
self.instrument_graph.process(output, &midi_events);
|
||||
|
||||
// Evaluate and apply automation
|
||||
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 id: TrackId,
|
||||
pub name: String,
|
||||
pub clips: Vec<Clip>,
|
||||
pub effects: Vec<Box<dyn Effect>>,
|
||||
pub volume: f32,
|
||||
pub muted: bool,
|
||||
pub solo: bool,
|
||||
|
|
@ -526,7 +489,6 @@ impl AudioTrack {
|
|||
id,
|
||||
name,
|
||||
clips: Vec::new(),
|
||||
effects: Vec::new(),
|
||||
volume: 1.0,
|
||||
muted: false,
|
||||
solo: false,
|
||||
|
|
@ -560,25 +522,6 @@ impl AudioTrack {
|
|||
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
|
||||
pub fn add_clip(&mut self, clip: 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
|
||||
let effective_volume = self.evaluate_automation_at_time(playhead_seconds);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
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
|
||||
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
|
||||
/// Create a new metatrack with a name
|
||||
CreateMetatrack(String),
|
||||
|
|
@ -211,11 +201,20 @@ pub enum Query {
|
|||
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
|
||||
#[derive(Debug)]
|
||||
pub enum QueryResponse {
|
||||
/// Graph state as JSON string
|
||||
GraphState(Result<String, String>),
|
||||
/// Oscilloscope data samples
|
||||
OscilloscopeData(Result<Vec<f32>, String>),
|
||||
OscilloscopeData(Result<OscilloscopeData, String>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ pub use audio::{
|
|||
TrackNode,
|
||||
};
|
||||
pub use audio::node_graph::{GraphPreset, InstrumentGraph, PresetMetadata, SerializedConnection, SerializedNode};
|
||||
pub use command::{AudioEvent, Command};
|
||||
pub use effects::{Effect, GainEffect, PanEffect, SimpleEQ, SimpleSynth};
|
||||
pub use command::{AudioEvent, Command, OscilloscopeData};
|
||||
pub use io::{load_midi_file, AudioFile, WaveformPeak, WavWriter};
|
||||
|
||||
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),
|
||||
"mute" => controller.set_track_mute(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)),
|
||||
}
|
||||
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]
|
||||
pub async fn audio_create_track(
|
||||
state: tauri::State<'_, Arc<Mutex<AudioState>>>,
|
||||
name: String,
|
||||
track_type: String,
|
||||
instrument: Option<String>,
|
||||
) -> Result<u32, String> {
|
||||
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 {
|
||||
match track_type.as_str() {
|
||||
"audio" => controller.create_audio_track(name),
|
||||
"midi" => {
|
||||
// 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)
|
||||
},
|
||||
"midi" => controller.create_midi_track(name),
|
||||
_ => return Err(format!("Unknown track type: {}", track_type)),
|
||||
}
|
||||
Ok(track_id)
|
||||
|
|
@ -1137,7 +1115,7 @@ pub async fn get_oscilloscope_data(
|
|||
track_id: u32,
|
||||
node_id: u32,
|
||||
sample_count: usize,
|
||||
) -> Result<Vec<f32>, String> {
|
||||
) -> Result<daw_backend::OscilloscopeData, String> {
|
||||
let mut audio_state = state.lock().unwrap();
|
||||
|
||||
if let Some(controller) = &mut audio_state.controller {
|
||||
|
|
|
|||
|
|
@ -199,7 +199,6 @@ pub fn run() {
|
|||
audio::audio_seek,
|
||||
audio::audio_test_beep,
|
||||
audio::audio_set_track_parameter,
|
||||
audio::audio_get_available_instruments,
|
||||
audio::audio_create_track,
|
||||
audio::audio_load_file,
|
||||
audio::audio_add_clip,
|
||||
|
|
|
|||
Loading…
Reference in New Issue