216 lines
11 KiB
Rust
216 lines
11 KiB
Rust
use std::time::Instant;
|
|
|
|
use iced::Task;
|
|
|
|
use super::{decode_region_audio, Editor, Message, StatusLevel};
|
|
use crate::engine::{EngineCommand, EngineEvent};
|
|
use crate::region::{Region, TakeFolder};
|
|
use crate::waveform::WaveformPeaks;
|
|
|
|
impl Editor {
|
|
pub(crate) fn handle_engine_tick(&mut self) -> Task<Message> {
|
|
if let Some((ref msg, level, t)) = self.status_message {
|
|
if t.elapsed().as_secs() >= 5 {
|
|
self.last_status = Some((msg.clone(), level));
|
|
self.status_message = None;
|
|
}
|
|
}
|
|
if let Some(ref engine) = self.engine {
|
|
for event in engine.poll_events() {
|
|
match event {
|
|
EngineEvent::TransportPosition(pos) => {
|
|
self.current_position = pos;
|
|
let sample_pos = pos.to_samples_mapped(
|
|
&self.tempo_map, self.project_config.sample_rate,
|
|
self.time_signature_numerator as u32,
|
|
);
|
|
for track in &mut self.tracks {
|
|
if track.automation_mode.reads() {
|
|
for lane in &track.automation_lanes {
|
|
if let Some(val) = lane.value_at(sample_pos) {
|
|
match &lane.target {
|
|
crate::automation::AutomationTarget::Volume => track.volume = val,
|
|
crate::automation::AutomationTarget::Pan => track.pan = val,
|
|
crate::automation::AutomationTarget::Mute => track.muted = val > 0.5,
|
|
crate::automation::AutomationTarget::ModuleParam { module_id, key } => {
|
|
self.module_params.values.insert((*module_id, key.clone()), val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EngineEvent::Error(e) => {
|
|
debug_log!("engine error: {}", e);
|
|
self.status_message = Some((e, StatusLevel::Error, Instant::now()));
|
|
}
|
|
EngineEvent::BusCreated => {}
|
|
EngineEvent::GraphRebuilt => {}
|
|
EngineEvent::ModuleLoaded { bus_name, module_id, module_type, plugin_name, has_gui, gui_descriptor } => {
|
|
if self.routing.handle_module_loaded(&bus_name, module_id, module_type, plugin_name, &mut self.tracks) {
|
|
self.dirty = true;
|
|
}
|
|
self.module_gui.handle_module_loaded(module_id, has_gui, gui_descriptor);
|
|
}
|
|
EngineEvent::ContractViolation { module_id: _module_id, module_name: _module_name, avg_ns: _avg_ns, budget_ns: _budget_ns } => {
|
|
debug_log!("contract violation: module {}({}) {}ns / {}ns budget",
|
|
_module_name, _module_id, _avg_ns, _budget_ns);
|
|
}
|
|
EngineEvent::BufferAutoIncreased { new_size, latency_ms: _latency_ms, reason: _reason } => {
|
|
debug_log!("buffer auto-increased to {} ({:.1}ms): {}",
|
|
new_size, _latency_ms, _reason);
|
|
self.project_config.output_buffer_size = new_size as u32;
|
|
self.dirty = true;
|
|
}
|
|
EngineEvent::BufferNegotiation { module_id: _module_id, required_samples: _required_samples, required_ms: _required_ms, current_samples: _current_samples, current_ms: _current_ms } => {
|
|
debug_log!("buffer negotiation: module {} needs {} samples ({:.1}ms), current {} ({:.1}ms)",
|
|
_module_id, _required_samples, _required_ms, _current_samples, _current_ms);
|
|
}
|
|
EngineEvent::ModuleDisabled { module_id: _module_id, reason: _reason } => {
|
|
debug_log!("module {} disabled: {}", _module_id, _reason);
|
|
}
|
|
EngineEvent::AudioConfigResolved {
|
|
output_device: _output_device, input_device: _input_device, sample_rate: _sample_rate,
|
|
} => {
|
|
debug_log!("[audio] output='{}' input='{}' rate={}Hz",
|
|
_output_device,
|
|
if _input_device.is_empty() { "none" } else { &_input_device },
|
|
_sample_rate);
|
|
}
|
|
EngineEvent::RecordingComplete {
|
|
bus_name, file_path, start_sample,
|
|
length_samples, start_time, duration,
|
|
} => {
|
|
let region = Region::with_audio(
|
|
start_time,
|
|
duration,
|
|
file_path.clone(),
|
|
start_sample,
|
|
length_samples,
|
|
);
|
|
let region_id = region.id;
|
|
|
|
let abs_path = self.project_path.join(&file_path);
|
|
if let Some((audio_l, audio_r)) = decode_region_audio(&abs_path, self.project_config.sample_rate) {
|
|
self.waveform_cache.insert(
|
|
region_id,
|
|
WaveformPeaks::from_stereo(&audio_l, &audio_r),
|
|
);
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: bus_name.clone(),
|
|
region_id,
|
|
start_sample,
|
|
audio_l,
|
|
audio_r,
|
|
fade_in_samples: 0,
|
|
fade_out_samples: 0,
|
|
});
|
|
}
|
|
|
|
for track in &mut self.tracks {
|
|
if track.bus_name == bus_name {
|
|
track.regions.push(region);
|
|
self.dirty = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
EngineEvent::ModuleParamDescriptors { module_id, descriptors } => {
|
|
for desc in &descriptors {
|
|
let default_val = match &desc.kind {
|
|
oxforge::mdk::ParamKind::Float { default, .. } => *default,
|
|
oxforge::mdk::ParamKind::Bool { default } => if *default { 1.0 } else { 0.0 },
|
|
oxforge::mdk::ParamKind::Choice { default, .. } => *default as f32,
|
|
oxforge::mdk::ParamKind::Int { default, .. } => *default as f32,
|
|
};
|
|
self.module_params.values
|
|
.entry((module_id, desc.key.clone()))
|
|
.or_insert(default_val);
|
|
}
|
|
self.module_params.descriptors.insert(module_id, descriptors);
|
|
}
|
|
EngineEvent::ModuleParamChanged { module_id, key, value } => {
|
|
self.module_params.values.insert((module_id, key), value);
|
|
}
|
|
EngineEvent::PluginsDiscovered { plugins } => {
|
|
debug_log!("[plugins] discovered {} plugins", plugins.len());
|
|
self.discovered_plugins = plugins;
|
|
}
|
|
EngineEvent::TakeRecordingComplete { bus_name, takes } => {
|
|
let mut region_ids = Vec::new();
|
|
for (i, take) in takes.iter().enumerate() {
|
|
let region = Region::with_audio(
|
|
take.start_time,
|
|
take.duration,
|
|
take.file_path.clone(),
|
|
take.start_sample,
|
|
take.length_samples,
|
|
);
|
|
let region_id = region.id;
|
|
region_ids.push(region_id);
|
|
|
|
let abs_path = self.project_path.join(&take.file_path);
|
|
if let Some((audio_l, audio_r)) = decode_region_audio(&abs_path, self.project_config.sample_rate) {
|
|
self.waveform_cache.insert(
|
|
region_id,
|
|
WaveformPeaks::from_stereo(&audio_l, &audio_r),
|
|
);
|
|
if i == takes.len() - 1 {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: bus_name.clone(),
|
|
region_id,
|
|
start_sample: take.start_sample,
|
|
audio_l,
|
|
audio_r,
|
|
fade_in_samples: 0,
|
|
fade_out_samples: 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
for track in &mut self.tracks {
|
|
if track.bus_name == bus_name {
|
|
track.regions.push(region);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if region_ids.len() > 1 {
|
|
let folder = TakeFolder::new(region_ids);
|
|
for track in &mut self.tracks {
|
|
if track.bus_name == bus_name {
|
|
track.take_folders.push(folder);
|
|
self.dirty = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EngineEvent::MeterUpdate { bus_peaks, master_peak } => {
|
|
for (name, l, r) in bus_peaks {
|
|
self.meter_levels.insert(name, (l, r));
|
|
}
|
|
self.master_meter = master_peak;
|
|
}
|
|
EngineEvent::ModuleGuiDescriptorReady { module_id, descriptor } => {
|
|
self.module_gui.handle_gui_descriptor_ready(module_id, descriptor);
|
|
}
|
|
EngineEvent::ModuleGuiReady => {}
|
|
EngineEvent::ModuleErrorReport { .. } => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
let gui_tasks = self.module_gui.tick(
|
|
self.engine.as_ref(),
|
|
&mut self.module_params,
|
|
&self.routing.module_names,
|
|
);
|
|
if !gui_tasks.is_empty() {
|
|
return Task::batch(gui_tasks);
|
|
}
|
|
Task::none()
|
|
}
|
|
}
|