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 { 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() } }