1use super::*;
2#[cfg(target_os = "linux")]
3use crate::hw::alsa::MidiHub;
4#[cfg(target_os = "macos")]
5use crate::hw::coremidi::MidiHub;
6#[cfg(target_os = "freebsd")]
7use crate::hw::oss::MidiHub;
8#[cfg(target_os = "openbsd")]
9use crate::hw::sndio::{HwDriver, HwOptions, MidiHub};
10#[cfg(target_os = "windows")]
11use crate::hw::wasapi::MidiHub;
12#[cfg(target_os = "openbsd")]
13use crate::workers::sndio_worker::HwWorker;
14use crate::{
15 history::{History, UndoEntry},
16 message::{Action, HwMidiEvent, Message, ProcessTask, SessionSlotState},
17 midi::io::MidiEvent,
18 osc::{OscArg, OscServer, build_error_packet, build_osc_packet},
19 state::State,
20 workers::worker::Worker,
21};
22use mixosc::parameters::{OscValue, build_set};
23use std::{
24 collections::{HashMap, VecDeque},
25 net::{SocketAddr, UdpSocket},
26 path::{Path, PathBuf},
27 sync::{Arc, atomic::Ordering},
28 time::{Duration, Instant},
29};
30use tokio::sync::Notify;
31use tokio::sync::mpsc::{Receiver, Sender, channel};
32use tracing::error;
33
34impl Engine {
35 pub fn state(&self) -> Arc<State> {
36 self.state.clone()
37 }
38
39 pub(crate) fn timing_at_sample(&self, sample: usize) -> (f64, u16, u16) {
40 let bpm = self
41 .tempo_points
42 .iter()
43 .filter(|p| p.sample <= sample)
44 .max_by_key(|p| p.sample)
45 .map(|p| p.bpm)
46 .unwrap_or(self.tempo_bpm)
47 .max(1.0);
48 let (num, den) = self
49 .time_signature_points
50 .iter()
51 .filter(|p| p.sample <= sample)
52 .max_by_key(|p| p.sample)
53 .map(|p| (p.numerator.max(1), p.denominator.max(1)))
54 .unwrap_or((self.tsig_num.max(1), self.tsig_denom.max(1)));
55 (bpm, num, den)
56 }
57
58 pub(crate) fn update_global_tempo_from_map(&mut self) {
59 let (bpm, num, den) = self.timing_at_sample(0);
60 self.tempo_bpm = bpm;
61 self.tsig_num = num;
62 self.tsig_denom = den;
63 self.bump_prepare_generation();
64 }
65
66 pub(crate) fn meter_linear_to_db(peak: f32) -> f32 {
67 if peak <= 1.0e-6 {
68 -90.0
69 } else {
70 (20.0 * peak.log10()).clamp(-90.0, 20.0)
71 }
72 }
73
74 pub(crate) fn meter_db_to_linear(db: f32) -> f32 {
75 if db <= -90.0 {
76 0.0
77 } else {
78 10.0_f32.powf(db / 20.0)
79 }
80 }
81
82 pub(crate) const METER_PUBLISH_INTERVAL: Duration = Duration::from_millis(50);
83 pub(crate) const METER_DECAY_AFTER_STOP: Duration = Duration::from_secs(1);
84 pub(crate) const SESSION_RUNTIME_REPORT_INTERVAL: Duration = Duration::from_millis(50);
85 pub(crate) const TRACK_PROCESS_TIMEOUT: Duration = Duration::from_millis(250);
86 #[cfg(unix)]
87 pub(crate) const HW_OUT_METER_LINEAR_EPSILON: f32 = 0.0025;
88
89 #[cfg(unix)]
90 pub(crate) fn session_plugins_dir(&self) -> Option<PathBuf> {
91 self.session_dir.as_ref().map(|d| d.join("plugins"))
92 }
93
94 pub(crate) fn session_audio_dir(&self) -> Option<PathBuf> {
95 self.session_dir.as_ref().map(|d| d.join("audio"))
96 }
97
98 pub(crate) fn session_midi_dir(&self) -> Option<PathBuf> {
99 self.session_dir.as_ref().map(|d| d.join("midi"))
100 }
101
102 pub(crate) fn session_peaks_dir(&self) -> Option<PathBuf> {
103 self.session_dir.as_ref().map(|d| d.join("peaks"))
104 }
105
106 pub(crate) fn ensure_session_subdirs(&self) {
107 if let Some(root) = &self.session_dir {
108 let _ = std::fs::create_dir_all(root.join("plugins"));
109 let _ = std::fs::create_dir_all(root.join("audio"));
110 let _ = std::fs::create_dir_all(root.join("midi"));
111 let _ = std::fs::create_dir_all(root.join("peaks"));
112 }
113 }
114
115 pub fn new(rx: Receiver<Message>, tx: Sender<Message>) -> Self {
116 let (meter_snapshot_producer, _) =
117 crate::triple_buffer::triple_buffer(crate::meter::MeterSnapshot::default());
118 let (transport_snapshot_producer, _) =
119 crate::triple_buffer::triple_buffer(crate::meter::TransportSnapshot::default());
120 let (session_runtime_snapshot_producer, _) =
121 crate::triple_buffer::triple_buffer(crate::meter::SessionRuntimeSnapshot::default());
122 Self::new_with_snapshots(
123 rx,
124 tx,
125 meter_snapshot_producer,
126 transport_snapshot_producer,
127 session_runtime_snapshot_producer,
128 )
129 }
130
131 pub fn new_with_snapshots(
132 rx: Receiver<Message>,
133 tx: Sender<Message>,
134 meter_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
135 crate::meter::MeterSnapshot,
136 >,
137 transport_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
138 crate::meter::TransportSnapshot,
139 >,
140 session_runtime_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
141 crate::meter::SessionRuntimeSnapshot,
142 >,
143 ) -> Self {
144 let state = Arc::new(State::default());
145 let initial_state_snapshot = state.lock().snapshot();
146 let state_snapshot = Arc::new(crate::state::StateSlot::from_pointee(
147 initial_state_snapshot.clone(),
148 ));
149 let collector = basedrop::Collector::new();
152 let hw_ports = Arc::new(arc_swap::ArcSwap::from_pointee(
153 crate::plan_builder::HwPorts {
154 buffer_size: 1024,
155 ..Default::default()
156 },
157 ));
158 let initial_plan =
159 { crate::render_plan::RenderPlan::compile(&initial_state_snapshot, &[], &[], 1024) };
160 let plan_slot = Arc::new(crate::render_plan::PlanSlot::from_pointee(
161 basedrop::Owned::new(&collector.handle(), initial_plan),
162 ));
163 let plan_builder = crate::plan_builder::PlanBuilder::spawn(
164 state_snapshot.clone(),
165 hw_ports.clone(),
166 plan_slot.clone(),
167 collector,
168 );
169 let executor = crate::executor::CycleExecutor::new(plan_slot.clone());
170 Self {
171 rx,
172 tx,
173 clients: vec![],
174 state,
175 state_snapshot,
176 workers: vec![],
177 hw_driver: None,
178 hw_driver_info: None,
179 hw_input_ports: Vec::new(),
180 hw_output_ports: Vec::new(),
181 #[cfg(unix)]
182 jack_runtime: None,
183 midi_hub: Some(MidiHub::default()),
184 auto_open_midi_devices: true,
185 hw_worker: None,
186 osc_server: None,
187 osc_reply_socket: None,
188 osc_reply_target: None,
189 mixosc_socket: None,
190 pending_hw_midi_events: vec![],
191 pending_hw_midi_events_by_device: HashMap::new(),
192 pending_hw_midi_out_events: vec![],
193 pending_hw_midi_out_events_by_device: vec![],
194 active_hw_notes_by_track: HashMap::new(),
195 active_hw_notes_cycle_start: HashMap::new(),
196 midi_hw_in_routes: vec![],
197 midi_hw_out_routes: vec![],
198 midi_hw_thru_routes: vec![],
199 ready_workers: vec![],
200 pending_requests: VecDeque::new(),
201 awaiting_hwfinished: false,
202 handling_hwfinished: false,
203 transport_panic_flush_pending: false,
204 transport_restart_pending: false,
205 notified_loop_wrap_sample: None,
206 transport_sample: 0,
207 transport_sample_snapshot: Arc::new(crate::track::TransportSampleSnapshot::new()),
208 prepare_generation: 1,
209 hw_input_latency_frames: 0,
210 hw_output_latency_frames: 0,
211 loop_enabled: false,
212 loop_range_samples: None,
213 metronome_enabled: false,
214 tempo_bpm: 120.0,
215 tsig_num: 4,
216 tsig_denom: 4,
217 tempo_points: vec![crate::message::TempoPoint {
218 sample: 0,
219 bpm: 120.0,
220 }],
221 time_signature_points: vec![crate::message::TimeSignaturePoint {
222 sample: 0,
223 numerator: 4,
224 denominator: 4,
225 }],
226 punch_enabled: false,
227 punch_range_samples: None,
228 audio_recordings: std::collections::HashMap::new(),
229 midi_recordings: std::collections::HashMap::new(),
230 completed_audio_recordings: Vec::new(),
231 completed_midi_recordings: Vec::new(),
232 playing: false,
233 transport_running: false,
234 clip_playback_enabled: true,
235 session_clip_playback_enabled: false,
236 session_transport_sample: 0,
237 session_scene_queue: None,
238 session_scene_queue_length_samples: 0,
239 session_current_scene: None,
240 session_current_scene_previous_scene: None,
241 session_current_scene_start_sample: 0,
242 session_current_scene_length_samples: 0,
243 session_completed_clip_passes: Vec::new(),
244 session_reported_clip_passes: std::collections::HashSet::new(),
245 record_enabled: false,
246 step_recording_enabled: false,
247 session_dir: None,
248 hw_out_level_db: 0.0,
249 hw_out_balance: 0.0,
250 hw_out_muted: false,
251 last_hw_out_meter_publish: None,
252 #[cfg(unix)]
253 last_hw_out_meter_linear: vec![],
254 hw_out_peak_hold_linear: vec![],
255 #[cfg(unix)]
256 hw_out_meter_publish_phase: false,
257 last_track_meter_publish: None,
258 last_meter_snapshot_publish: None,
259 last_session_report_publish: None,
260 track_meter_linear_by_track: HashMap::new(),
261 meter_decay_after_stop: None,
262 meter_snapshot_producer,
263 transport_snapshot_producer,
264 session_runtime_snapshot_producer,
265 executor,
266 plan_builder,
267 plan_slot,
268 hw_ports,
269 pending_node_jobs: VecDeque::new(),
270 latest_hw_out_meter_db: Arc::new(Vec::new()),
271 latest_track_meter_snapshot: Arc::new(Vec::new()),
272 history: History::default(),
273 history_group: None,
274 history_suspended: false,
275 offline_bounce_jobs: HashMap::new(),
276 pending_bounce_starts: Vec::new(),
277 bounce_worker_tracks: HashMap::new(),
278 pending_midi_learn: None,
279 pending_global_midi_learn: None,
280 pending_session_midi_learn: None,
281 audio_preview: None,
282 global_midi_learn_play_pause: None,
283 global_midi_learn_stop: None,
284 global_midi_learn_record_toggle: None,
285 session_midi_learn_slots: HashMap::new(),
286 session_midi_learn_scenes: HashMap::new(),
287 session_midi_learn_stop_track: HashMap::new(),
288 session_midi_learn_stop_all: None,
289 midi_cc_gate: HashMap::new(),
290 modulators: Vec::new(),
291 modulator_values: None,
292 mixosc_last_values: HashMap::new(),
293 #[cfg(target_os = "windows")]
294 _windows_timer_guard: crate::enable_windows_high_resolution_timer(),
295 node_result_notify: Arc::new(Notify::new()),
296 }
297 }
298
299 pub(crate) fn publish_state_snapshot(&self) {
300 let snapshot = self.state.lock().snapshot();
301 self.state_snapshot.store(Arc::new(snapshot));
302 }
303
304 pub(crate) fn hw_driver_cycle_samples(&self) -> Option<usize> {
305 self.hw_driver_info.map(|info| info.cycle_samples)
306 }
307
308 #[cfg(unix)]
309 pub(crate) fn jack_cycle_samples(&self) -> Option<usize> {
310 self.jack_runtime.as_ref().map(|j| j.buffer_size)
311 }
312
313 #[cfg(not(unix))]
314 pub(crate) fn jack_cycle_samples(&self) -> Option<usize> {
315 None
316 }
317
318 pub(crate) fn current_cycle_samples(&self) -> usize {
319 self.hw_driver_cycle_samples()
320 .or_else(|| self.jack_cycle_samples())
321 .unwrap_or(0)
322 }
323
324 pub(crate) fn sample_rate(&self) -> f64 {
325 if let Some(info) = self.hw_driver_info {
326 info.sample_rate as f64
327 } else {
328 #[cfg(unix)]
329 {
330 self.jack_runtime
331 .as_ref()
332 .map(|j| j.sample_rate as f64)
333 .unwrap_or(48_000.0)
334 }
335 #[cfg(not(unix))]
336 {
337 48_000.0
338 }
339 }
340 }
341
342 pub(crate) async fn set_hw_playing(&mut self, playing: bool) {
343 if let Some(worker) = &self.hw_worker {
344 let _ = worker.tx.send(Message::HWSetPlaying(playing)).await;
347 } else if let Some(driver) = self.hw_driver.as_mut() {
348 driver.set_playing(playing);
349 }
350 }
351
352 pub(crate) fn active_transport_sample(&self) -> usize {
353 if self.session_clip_playback_enabled && self.playing {
354 self.session_transport_sample
355 } else {
356 self.transport_sample
357 }
358 }
359
360 pub(crate) fn compute_modulator_values(
361 &self,
362 sample: usize,
363 ) -> Arc<std::collections::HashMap<usize, f32>> {
364 let sample_rate = self.sample_rate();
365 let (bpm, tsig_num, tsig_denom) = self.timing_at_sample(sample);
366 let values: std::collections::HashMap<usize, f32> = self
367 .modulators
368 .iter()
369 .filter(|m| m.enabled)
370 .map(|m| {
371 (
372 m.id,
373 m.value_at(sample, sample_rate, bpm, tsig_num, tsig_denom),
374 )
375 })
376 .collect();
377 Arc::new(values)
378 }
379
380 pub(crate) fn apply_modulators(&mut self, sample: usize) -> Vec<Action> {
381 use crate::modulator::ModulatorTarget;
382 let values = self.compute_modulator_values(sample);
383 self.modulator_values = Some(values.clone());
384 let mut echoes = Vec::new();
385 let mut per_track: HashMap<String, (Option<f32>, Option<f32>)> = HashMap::new();
386 let mut clap_params: HashMap<(String, usize, u32), f64> = HashMap::new();
387 let mut vst3_params: HashMap<(String, usize, u32), f32> = HashMap::new();
388 #[cfg(unix)]
389 let mut lv2_params: HashMap<(String, usize, u32), f32> = HashMap::new();
390 let mut midi_cc_events: HashMap<String, Vec<MidiEvent>> = HashMap::new();
391
392 let map_f32 = |value: f32, min: f32, max: f32| -> f32 {
393 crate::modulator::map_value(value, min, max)
394 };
395 let map_f64 = |value: f32, min: f64, max: f64| -> f64 {
396 crate::modulator::map_value_f64(value, min, max)
397 };
398
399 for m in &self.modulators {
400 if !m.enabled {
401 continue;
402 }
403 let Some(&value) = values.get(&m.id) else {
404 continue;
405 };
406 for target in &m.targets {
407 match target {
408 ModulatorTarget::TrackVolume {
409 track_name,
410 min,
411 max,
412 } => {
413 let clamped = map_f32(value, *min, *max);
414 per_track.entry(track_name.clone()).or_default().0 = Some(clamped);
415 }
416 ModulatorTarget::TrackBalance {
417 track_name,
418 min,
419 max,
420 } => {
421 let clamped = map_f32(value, *min, *max);
422 per_track.entry(track_name.clone()).or_default().1 = Some(clamped);
423 }
424 ModulatorTarget::HwOutVolume { min, max } => {
425 let clamped = map_f32(value, *min, *max);
426 if (self.hw_out_level_db - clamped).abs() > f32::EPSILON {
427 self.hw_out_level_db = clamped;
428 echoes
429 .push(Action::TrackAutomationLevel("hw:out".to_string(), clamped));
430 }
431 }
432 ModulatorTarget::HwOutBalance { min, max } => {
433 let next = map_f32(value, *min, *max).clamp(-1.0, 1.0);
434 if (self.hw_out_balance - next).abs() > f32::EPSILON {
435 self.hw_out_balance = next;
436 echoes.push(Action::TrackAutomationBalance("hw:out".to_string(), next));
437 }
438 }
439 ModulatorTarget::ClapParameter {
440 track_name,
441 instance_id,
442 param_id,
443 min,
444 max,
445 } => {
446 let param_value = map_f64(value, *min, *max);
447 clap_params
448 .insert((track_name.clone(), *instance_id, *param_id), param_value);
449 }
450 ModulatorTarget::Vst3Parameter {
451 track_name,
452 instance_id,
453 param_id,
454 min,
455 max,
456 } => {
457 let param_value = map_f32(value, *min, *max);
458 vst3_params
459 .insert((track_name.clone(), *instance_id, *param_id), param_value);
460 }
461 #[cfg(unix)]
462 ModulatorTarget::Lv2Parameter {
463 track_name,
464 instance_id,
465 index,
466 min,
467 max,
468 } => {
469 let param_value = map_f32(value, *min, *max);
470 lv2_params.insert((track_name.clone(), *instance_id, *index), param_value);
471 }
472 ModulatorTarget::MidiCc {
473 track_name,
474 channel,
475 cc,
476 } => {
477 let cc_value = (value * 127.0).round() as u8;
478 midi_cc_events
479 .entry(track_name.clone())
480 .or_default()
481 .push(MidiEvent::new(
482 0,
483 vec![0xB0 | (*channel).min(15), (*cc).min(127), cc_value],
484 ));
485 }
486 }
487 }
488 }
489 let state = self.state_snapshot.load_full();
490 for (track_name, (level, balance)) in per_track {
491 if let Some(level) = level
492 && let Some(track) = state.tracks.get(&track_name).cloned()
493 {
494 let t = track.lock();
495 if (t.level() - level).abs() > f32::EPSILON {
496 t.set_level(level);
497 echoes.push(Action::TrackAutomationLevel(track_name.clone(), level));
498 }
499 }
500 if let Some(balance) = balance
501 && let Some(track) = state.tracks.get(&track_name).cloned()
502 {
503 let t = track.lock();
504 let next = balance.clamp(-1.0, 1.0);
505 if (t.balance() - next).abs() > f32::EPSILON {
506 t.set_balance(next);
507 echoes.push(Action::TrackAutomationBalance(track_name.clone(), next));
508 }
509 }
510 }
511
512 for (track_name, events) in midi_cc_events {
513 if let Some(track) = state.tracks.get(&track_name).cloned() {
514 track.lock().rt.pending_modulator_midi_events.extend(events);
515 }
516 }
517
518 for ((track_name, instance_id, param_id), value) in clap_params {
519 if let Some(track) = state.tracks.get(&track_name).cloned()
520 && track
521 .lock()
522 .set_clap_parameter(instance_id, param_id, value)
523 .is_ok()
524 {
525 echoes.push(Action::TrackSetClapParameter {
526 track_name,
527 instance_id,
528 param_id,
529 value,
530 });
531 }
532 }
533 for ((track_name, instance_id, param_id), value) in vst3_params {
534 if let Some(track) = state.tracks.get(&track_name).cloned()
535 && track
536 .lock()
537 .set_vst3_parameter(instance_id, param_id, value)
538 .is_ok()
539 {
540 echoes.push(Action::TrackSetVst3Parameter {
541 track_name,
542 instance_id,
543 param_id,
544 value,
545 });
546 }
547 }
548 #[cfg(unix)]
549 for ((track_name, instance_id, index), value) in lv2_params {
550 if let Some(track) = state.tracks.get(&track_name).cloned()
551 && track
552 .lock()
553 .set_lv2_control_value(instance_id, index as usize, f64::from(value))
554 .is_ok()
555 {
556 echoes.push(Action::TrackSetLv2ControlValue {
557 track_name,
558 instance_id,
559 index,
560 value,
561 });
562 }
563 }
564
565 echoes
566 }
567
568 pub(crate) fn apply_mixosc_automation(&mut self, sample: usize) {
571 if !self.playing {
572 return;
573 }
574
575 let socket = match self.mixosc_socket.as_ref() {
576 Some(socket) => socket,
577 None => match UdpSocket::bind("0.0.0.0:0") {
578 Ok(socket) => {
579 self.mixosc_socket = Some(socket);
580 self.mixosc_socket.as_ref().unwrap()
581 }
582 Err(err) => {
583 tracing::warn!(%err, "Failed to bind MixOSC output socket");
584 return;
585 }
586 },
587 };
588
589 let state = self.state_snapshot.load_full();
590 for (track_name, track) in state.tracks.iter() {
591 let track_lock = track.lock();
592 let Some(track_addr) = track_lock.mixosc_addr.as_ref() else {
593 continue;
594 };
595 let lanes: Vec<crate::message::OfflineAutomationLane> =
596 crate::engine::parse_automation_lanes(&track_lock.automation_lanes);
597 for lane in lanes {
598 if !lane.visible {
599 continue;
600 }
601 let crate::message::OfflineAutomationTarget::MixOsc {
602 addr: ref lane_addr,
603 path: ref lane_path,
604 } = lane.target
605 else {
606 continue;
607 };
608 if lane_addr != track_addr {
609 continue;
610 }
611 let Some(value) = lane.value_at(sample) else {
612 continue;
613 };
614 let key = (track_addr.clone(), lane_path.clone());
615 if let Some(last) = self.mixosc_last_values.get(&key)
616 && (last - value).abs() < f32::EPSILON
617 {
618 continue;
619 }
620 self.mixosc_last_values.insert(key, value);
621 let packet = build_set(lane_path, OscValue::Float(value));
622 if let Err(err) = socket.send_to(&packet, track_addr) {
623 tracing::debug!(
624 %err,
625 %track_name,
626 %track_addr,
627 %lane_path,
628 "Failed to send MixOSC packet"
629 );
630 }
631 }
632 }
633 }
634
635 pub(crate) fn session_end_sample(&self) -> usize {
636 self.state
637 .lock()
638 .tracks
639 .values()
640 .map(|track| {
641 let track = track.lock();
642 let audio_end = track
643 .audio
644 .clips()
645 .iter()
646 .map(|clip| clip.end)
647 .max()
648 .unwrap_or(0);
649 let midi_end = track
650 .midi
651 .clips()
652 .iter()
653 .map(|clip| clip.end)
654 .max()
655 .unwrap_or(0);
656 audio_end.max(midi_end)
657 })
658 .max()
659 .unwrap_or(0)
660 }
661
662 pub(crate) fn normalize_transport_sample(&self, sample: usize) -> usize {
663 if self.loop_enabled
664 && let Some((loop_start, loop_end)) = self.loop_range_samples
665 && loop_end > loop_start
666 && sample >= loop_end
667 {
668 let loop_len = loop_end - loop_start;
669 return loop_start + (sample - loop_start) % loop_len;
670 }
671 sample
672 }
673
674 pub(crate) fn scheduled_loop_wrap_for_next_cycle(&self) -> Option<(usize, usize, usize)> {
675 if !self.playing || !self.loop_enabled {
676 return None;
677 }
678 let (loop_start, loop_end) = self.loop_range_samples?;
679 if loop_end <= loop_start || self.transport_sample >= loop_end {
680 return None;
681 }
682 let cycle_samples = self.current_cycle_samples();
683 if cycle_samples == 0 {
684 return None;
685 }
686 let next = self.transport_sample.saturating_add(cycle_samples);
687 if next < loop_end {
688 return None;
689 }
690 let after_frames = loop_end.saturating_sub(self.transport_sample);
691 Some((
692 after_frames,
693 loop_start,
694 self.normalize_transport_sample(next),
695 ))
696 }
697
698 pub(crate) fn cycle_segments(&self, frames: usize) -> Vec<(usize, usize, usize)> {
699 if frames == 0 {
700 return vec![];
701 }
702 if !self.loop_enabled {
703 return vec![(
704 self.transport_sample,
705 self.transport_sample.saturating_add(frames),
706 0,
707 )];
708 }
709 let Some((loop_start, loop_end)) = self.loop_range_samples else {
710 return vec![(
711 self.transport_sample,
712 self.transport_sample.saturating_add(frames),
713 0,
714 )];
715 };
716 if loop_end <= loop_start {
717 return vec![(
718 self.transport_sample,
719 self.transport_sample.saturating_add(frames),
720 0,
721 )];
722 }
723 let mut segments = Vec::new();
724 let mut remaining = frames;
725 let mut out_offset = 0usize;
726 let mut current = self.transport_sample;
727 while remaining > 0 {
728 let take = loop_end.saturating_sub(current).min(remaining);
729 if take == 0 {
730 current = loop_start;
731 continue;
732 }
733 segments.push((current, current.saturating_add(take), out_offset));
734 out_offset = out_offset.saturating_add(take);
735 remaining -= take;
736 current = if remaining > 0 {
737 loop_start
738 } else {
739 current.saturating_add(take)
740 };
741 }
742 segments
743 }
744
745 pub(crate) fn recording_segments_for_cycle(&self, frames: usize) -> Vec<(usize, usize, usize)> {
746 let segments = self.cycle_segments(frames);
747 let comp = self.hw_input_latency_frames;
748 let segments: Vec<_> = if comp > 0 {
749 segments
750 .into_iter()
751 .map(|(start, end, offset)| {
752 (start.saturating_sub(comp), end.saturating_sub(comp), offset)
753 })
754 .collect()
755 } else {
756 segments
757 };
758 if !self.punch_enabled {
759 return segments;
760 }
761 let Some((punch_start, punch_end)) = self.punch_range_samples else {
762 return vec![];
763 };
764 if punch_end <= punch_start {
765 return vec![];
766 }
767 let mut clipped = Vec::new();
768 for (segment_start, segment_end, frame_offset) in segments {
769 let start = segment_start.max(punch_start);
770 let end = segment_end.min(punch_end);
771 if end <= start {
772 continue;
773 }
774 let clipped_offset = frame_offset.saturating_add(start.saturating_sub(segment_start));
775 clipped.push((start, end, clipped_offset));
776 }
777 clipped
778 }
779
780 pub async fn init(&mut self) {
781 let max_threads = num_cpus::get();
782 for id in 0..max_threads {
783 let (tx, rx) = channel::<Message>(32);
784 let tx_thread = self.tx.clone();
785 let handler = tokio::spawn(async move {
786 let wrk = Worker::new(id, rx, tx_thread, 8);
787 wrk.await.work().await;
788 });
789 let (node_job_tx, mut node_job_rx) = rtrb::RingBuffer::new(64);
790 let (mut node_result_tx, node_result_rx) = rtrb::RingBuffer::new(64);
791 let node_quit = Arc::new(AtomicBool::new(false));
792 let node_quit_thread = node_quit.clone();
793 let node_result_notify = self.node_result_notify.clone();
794 let node_thread_handle = std::thread::Builder::new()
795 .name(format!("maolan-node-worker-{id}"))
796 .spawn(move || {
797 crate::enable_flush_denormals_to_zero();
798 if let Err(e) = Worker::try_enable_realtime(8) {
799 tracing::warn!(
800 "Node worker {} realtime priority {} not enabled: {}",
801 id,
802 8,
803 e
804 );
805 }
806 while !node_quit_thread.load(std::sync::atomic::Ordering::Acquire) {
807 match node_job_rx.pop() {
808 Ok(job) => {
809 let mut result = Worker::process_node_job_result(id, job);
810 loop {
811 match node_result_tx.push(result) {
812 Ok(()) => {
813 node_result_notify.notify_one();
814 break;
815 }
816 Err(rtrb::PushError::Full(returned)) => {
817 if node_quit_thread
818 .load(std::sync::atomic::Ordering::Acquire)
819 {
820 break;
821 }
822 result = returned;
823 std::thread::yield_now();
824 }
825 }
826 }
827 }
828 Err(rtrb::PopError::Empty) => {
829 std::thread::park();
830 }
831 }
832 }
833 })
834 .expect("failed to spawn node worker thread");
835 let node_thread = node_thread_handle.thread().clone();
836 std::mem::forget(node_thread_handle);
837 self.workers.push(WorkerData::with_node_mailbox(
838 tx.clone(),
839 handler,
840 node_job_tx,
841 node_result_rx,
842 node_thread,
843 node_quit,
844 ));
845 }
846 }
847
848 pub(crate) async fn notify_clients(&mut self, action: Result<Action, String>) {
849 self.clients.retain(|client| !client.is_closed());
850 for client in self.clients.iter() {
851 if client
852 .send(Message::Response(action.clone()))
853 .await
854 .is_err()
855 {}
856 }
857 if let Some(reply_to) = self.osc_reply_target {
858 match &action {
859 Err(reason) => {
860 self.send_osc_reply(reply_to, &build_error_packet(reason));
861 }
862 Ok(Action::TrackList(names)) => {
863 let args: Vec<OscArg> = names
864 .iter()
865 .map(|name| OscArg::String(name.clone()))
866 .collect();
867 self.send_osc_reply(
868 reply_to,
869 &build_osc_packet("/response/tracks", &"s".repeat(names.len()), &args),
870 );
871 }
872 Ok(Action::TransportState {
873 sample,
874 tempo_bpm,
875 playing,
876 paused: _,
877 tsig_num,
878 tsig_denom,
879 }) => {
880 self.send_osc_reply(
881 reply_to,
882 &build_osc_packet(
883 "/response/transport",
884 "idffii",
885 &[
886 OscArg::Int(*sample as i32),
887 OscArg::Int(if *playing { 1 } else { 0 }),
888 OscArg::Float(*tempo_bpm as f32),
889 OscArg::Float(0.0), OscArg::Int(*tsig_num as i32),
891 OscArg::Int(*tsig_denom as i32),
892 ],
893 ),
894 );
895 }
896 Ok(Action::MeterSnapshot {
897 hw_out_db,
898 track_meters,
899 ..
900 }) => {
901 let mut args: Vec<OscArg> = Vec::new();
902 args.push(OscArg::Int(hw_out_db.len() as i32));
903 for db in hw_out_db.iter() {
904 args.push(OscArg::Float(*db));
905 }
906 args.push(OscArg::Int(track_meters.len() as i32));
907 for (name, channels) in track_meters.iter() {
908 args.push(OscArg::String(name.clone()));
909 args.push(OscArg::Int(channels.len() as i32));
910 for db in channels.iter() {
911 args.push(OscArg::Float(*db));
912 }
913 }
914 let types = args
915 .iter()
916 .map(|a| match a {
917 OscArg::String(_) => 's',
918 OscArg::Int(_) => 'i',
919 OscArg::Float(_) => 'f',
920 })
921 .collect::<String>();
922 self.send_osc_reply(
923 reply_to,
924 &build_osc_packet("/response/meters", &types, &args),
925 );
926 }
927 Ok(Action::TrackPluginGraph {
928 track_name,
929 plugins,
930 connections: _,
931 connectable_connections: _,
932 }) => {
933 let mut args: Vec<OscArg> = vec![OscArg::String(track_name.clone())];
934 args.push(OscArg::Int(plugins.len() as i32));
935 for plugin in plugins.iter() {
936 args.push(OscArg::Int(plugin.instance_id as i32));
937 args.push(OscArg::String(plugin.format.clone()));
938 args.push(OscArg::String(plugin.uri.clone()));
939 args.push(OscArg::String(plugin.name.clone()));
940 args.push(OscArg::Int(plugin.bypassed as i32));
941 }
942 let types = args
943 .iter()
944 .map(|a| match a {
945 OscArg::String(_) => 's',
946 OscArg::Int(_) => 'i',
947 OscArg::Float(_) => 'f',
948 })
949 .collect::<String>();
950 self.send_osc_reply(
951 reply_to,
952 &build_osc_packet("/response/plugins", &types, &args),
953 );
954 }
955 Ok(Action::ClapPlugins(plugins)) => {
956 let args: Vec<OscArg> = plugins
957 .iter()
958 .map(|p| OscArg::String(format!("{}|{}", p.path, p.name)))
959 .collect();
960 let types = "s".repeat(args.len());
961 self.send_osc_reply(
962 reply_to,
963 &build_osc_packet("/response/clap_plugins", &types, &args),
964 );
965 }
966 Ok(Action::Vst3Plugins(plugins)) => {
967 let args: Vec<OscArg> = plugins
968 .iter()
969 .map(|p| OscArg::String(format!("{}|{}", p.id, p.name)))
970 .collect();
971 let types = "s".repeat(args.len());
972 self.send_osc_reply(
973 reply_to,
974 &build_osc_packet("/response/vst3_plugins", &types, &args),
975 );
976 }
977 #[cfg(unix)]
978 Ok(Action::Lv2Plugins(plugins)) => {
979 let args: Vec<OscArg> = plugins
980 .iter()
981 .map(|p| OscArg::String(format!("{}|{}", p.uri, p.name)))
982 .collect();
983 let types = "s".repeat(args.len());
984 self.send_osc_reply(
985 reply_to,
986 &build_osc_packet("/response/lv2_plugins", &types, &args),
987 );
988 }
989 Ok(Action::ClapPluginsUnavailable { error })
990 | Ok(Action::Vst3PluginsUnavailable { error }) => {
991 self.send_osc_reply(reply_to, &build_error_packet(error));
992 }
993 #[cfg(unix)]
994 Ok(Action::Lv2PluginsUnavailable { error }) => {
995 self.send_osc_reply(reply_to, &build_error_packet(error));
996 }
997 Ok(Action::TrackClapParameters {
998 track_name,
999 instance_id,
1000 parameters,
1001 }) => {
1002 let json = serde_json::json!(
1003 parameters
1004 .iter()
1005 .map(|p| serde_json::json!({
1006 "id": p.id,
1007 "name": p.name,
1008 "module": p.module,
1009 "min_value": p.min_value,
1010 "max_value": p.max_value,
1011 "default_value": p.default_value,
1012 }))
1013 .collect::<Vec<_>>()
1014 )
1015 .to_string();
1016 self.send_osc_reply(
1017 reply_to,
1018 &build_osc_packet(
1019 "/response/plugin_parameters",
1020 "siss",
1021 &[
1022 OscArg::String(track_name.clone()),
1023 OscArg::Int(*instance_id as i32),
1024 OscArg::String("clap".to_string()),
1025 OscArg::String(json),
1026 ],
1027 ),
1028 );
1029 }
1030 Ok(Action::TrackVst3Parameters {
1031 track_name,
1032 instance_id,
1033 parameters,
1034 }) => {
1035 let json = serde_json::to_string(parameters).unwrap_or_default();
1036 self.send_osc_reply(
1037 reply_to,
1038 &build_osc_packet(
1039 "/response/plugin_parameters",
1040 "siss",
1041 &[
1042 OscArg::String(track_name.clone()),
1043 OscArg::Int(*instance_id as i32),
1044 OscArg::String("vst3".to_string()),
1045 OscArg::String(json),
1046 ],
1047 ),
1048 );
1049 }
1050 #[cfg(unix)]
1051 Ok(Action::TrackLv2PluginControls {
1052 track_name,
1053 instance_id,
1054 controls,
1055 instance_access_handle: _,
1056 }) => {
1057 let json = serde_json::json!(
1058 controls
1059 .iter()
1060 .map(|c| serde_json::json!({
1061 "index": c.index,
1062 "name": c.name,
1063 "min": c.min,
1064 "max": c.max,
1065 "value": c.value,
1066 }))
1067 .collect::<Vec<_>>()
1068 )
1069 .to_string();
1070 self.send_osc_reply(
1071 reply_to,
1072 &build_osc_packet(
1073 "/response/plugin_parameters",
1074 "siss",
1075 &[
1076 OscArg::String(track_name.clone()),
1077 OscArg::Int(*instance_id as i32),
1078 OscArg::String("lv2".to_string()),
1079 OscArg::String(json),
1080 ],
1081 ),
1082 );
1083 }
1084 Ok(Action::TrackClapNoteNames {
1085 track_name,
1086 note_names,
1087 }) => {
1088 let json = serde_json::to_string(note_names).unwrap_or_default();
1089 self.send_osc_reply(
1090 reply_to,
1091 &build_osc_packet(
1092 "/response/clap_note_names",
1093 "ss",
1094 &[OscArg::String(track_name.clone()), OscArg::String(json)],
1095 ),
1096 );
1097 }
1098 #[cfg(unix)]
1099 Ok(Action::TrackLv2Midnam {
1100 track_name,
1101 note_names,
1102 }) => {
1103 let json = serde_json::to_string(note_names).unwrap_or_default();
1104 self.send_osc_reply(
1105 reply_to,
1106 &build_osc_packet(
1107 "/response/lv2_midnam",
1108 "ss",
1109 &[OscArg::String(track_name.clone()), OscArg::String(json)],
1110 ),
1111 );
1112 }
1113 _ => {}
1114 }
1115 }
1116 }
1117
1118 pub(crate) fn send_osc_reply(&mut self, reply_to: SocketAddr, packet: &[u8]) {
1119 if self.osc_reply_socket.is_none() {
1120 self.osc_reply_socket = UdpSocket::bind("0.0.0.0:0").ok();
1121 }
1122 if let Some(socket) = self.osc_reply_socket.as_ref() {
1123 let _ = socket.send_to(packet, reply_to);
1124 }
1125 }
1126
1127 pub(crate) async fn dispatch_request(&mut self, a: Action) {
1128 match a {
1129 Action::TrackOfflineBounceCancel { track_name } => {
1130 if let Some(job) = self.offline_bounce_jobs.get(&track_name) {
1131 job.cancel.store(true, Ordering::Relaxed);
1132 }
1133 }
1134 Action::TrackOfflineBounceCancelAll => {
1135 for job in self.offline_bounce_jobs.values() {
1136 job.cancel.store(true, Ordering::Relaxed);
1137 }
1138 }
1139 _ if !self.offline_bounce_jobs.is_empty() => {
1140 self.pending_requests.push_back(a);
1141 }
1142 Action::OpenAudioDevice { .. }
1143 | Action::OpenMidiInputDevice(_)
1144 | Action::OpenMidiOutputDevice(_)
1145 | Action::RequestMeterSnapshot
1146 | Action::RequestTrackList
1147 | Action::RequestTransportState
1148 | Action::Quit
1149 | Action::Log { .. }
1150 | Action::Play
1151 | Action::Pause
1152 | Action::Stop
1153 | Action::TransportPosition(_)
1154 | Action::JumpToEnd
1155 | Action::SetLoopEnabled(_)
1156 | Action::SetLoopRange(_)
1157 | Action::SetPunchEnabled(_)
1158 | Action::SetPunchRange(_)
1159 | Action::SetMetronomeEnabled(_)
1160 | Action::SetTempo(_)
1161 | Action::SetTimeSignature { .. }
1162 | Action::SetTempoMap { .. }
1163 | Action::SetOscEnabled(_)
1164 | Action::SetClipPlaybackEnabled(_)
1165 | Action::SetRecordEnabled(_)
1166 | Action::SetStepRecording(_)
1167 | Action::StepRecordMidiNote { .. }
1168 | Action::SetClipIdentity { .. }
1169 | Action::SetClipGainDb { .. }
1170 | Action::SetSessionPath(_)
1171 | Action::ClearHistory
1172 | Action::BeginSessionRestore
1173 | Action::PianoKey { .. }
1174 | Action::ModifyMidiNotes { .. }
1175 | Action::ModifyMidiControllers { .. }
1176 | Action::DeleteMidiControllers { .. }
1177 | Action::InsertMidiControllers { .. }
1178 | Action::DeleteMidiNotes { .. }
1179 | Action::InsertMidiNotes { .. }
1180 | Action::SetMidiSysExEvents { .. }
1181 | Action::Session(_) => {
1182 self.handle_request(a).await;
1183 }
1184 #[cfg(unix)]
1185 Action::ListLv2Plugins => {
1186 self.handle_request(a).await;
1187 }
1188 Action::ListVst3Plugins => {
1189 self.handle_request(a).await;
1190 }
1191 Action::ListClapPlugins => {
1192 self.handle_request(a).await;
1193 }
1194 Action::ListClapPluginsWithCapabilities => {
1195 self.handle_request(a).await;
1196 }
1197 _ => {
1198 self.pending_requests.push_back(a);
1199 if self.can_schedule_hw_cycle() {
1200 self.request_hw_cycle().await;
1201 } else {
1202 while let Some(next) = self.pending_requests.pop_front() {
1203 self.handle_request(next).await;
1204 }
1205 }
1206 }
1207 };
1208 self.publish_clap_state_dirty().await;
1209 }
1210
1211 pub(crate) fn spawn_plugin_host_stderr_reader(
1212 &self,
1213 stderr: std::process::ChildStderr,
1214 source: String,
1215 ) {
1216 let tx = self.tx.clone();
1217 std::thread::spawn(move || {
1218 use std::io::{BufRead, BufReader};
1219 let reader = BufReader::new(stderr);
1220 for line in reader.lines() {
1221 if let Ok(line) = line
1222 && !line.is_empty()
1223 {
1224 let _ = tx.blocking_send(Message::Request(Action::Log {
1225 source: source.clone(),
1226 message: line,
1227 }));
1228 }
1229 }
1230 });
1231 }
1232
1233 pub(crate) fn set_osc_enabled_with<F>(
1234 &mut self,
1235 enabled: bool,
1236 start_server: F,
1237 ) -> Result<(), String>
1238 where
1239 F: FnOnce(Sender<Message>) -> Result<OscServer, String>,
1240 {
1241 if enabled {
1242 if self.osc_server.is_none() {
1243 self.osc_server = Some(start_server(self.tx.clone())?);
1244 }
1245 } else if let Some(mut server) = self.osc_server.take() {
1246 server.stop();
1247 }
1248 Ok(())
1249 }
1250
1251 pub(crate) async fn request_hw_cycle(&mut self) {
1252 if self.awaiting_hwfinished {
1253 tracing::debug!(
1254 playing = self.playing,
1255 transport_running = self.transport_running,
1256 transport_sample = self.transport_sample,
1257 session_transport_sample = self.session_transport_sample,
1258 cycle_samples = self.current_cycle_samples(),
1259 "request_hw_cycle skipped because HWFinished is still pending"
1260 );
1261 return;
1262 }
1263 self.mix_audio_preview_into_hw_outputs();
1264 tracing::debug!(
1265 playing = self.playing,
1266 transport_running = self.transport_running,
1267 transport_sample = self.transport_sample,
1268 session_transport_sample = self.session_transport_sample,
1269 cycle_samples = self.current_cycle_samples(),
1270 "request_hw_cycle sending TracksFinished"
1271 );
1272 self.apply_hw_out_gain_and_meter().await;
1273 self.publish_meter_snapshot_if_due();
1274 if let Some((after_frames, loop_start, cycle_end_sample)) =
1275 self.scheduled_loop_wrap_for_next_cycle()
1276 {
1277 self.notified_loop_wrap_sample = Some(cycle_end_sample);
1278 self.notify_clients(Ok(Action::TransportPositionAt {
1279 sample: loop_start,
1280 after_frames,
1281 }))
1282 .await;
1283 } else {
1284 self.notified_loop_wrap_sample = None;
1285 }
1286 if let Some(worker) = &self.hw_worker {
1287 if !self.pending_hw_midi_out_events_by_device.is_empty() {
1288 let out_events = std::mem::take(&mut self.pending_hw_midi_out_events_by_device);
1289 if let Err(e) = worker.tx.send(Message::HWMidiOutEvents(out_events)).await {
1290 error!("Error sending HWMidiOutEvents {e}");
1291 }
1292 }
1293 match worker.tx.send(Message::TracksFinished).await {
1294 Ok(_) => {
1295 self.awaiting_hwfinished = true;
1296 }
1297 Err(e) => {
1298 error!("Error sending TracksFinished {e}");
1299 }
1300 }
1301 }
1302 }
1303
1304 fn mix_audio_preview_into_hw_outputs(&mut self) {
1305 let cycle_samples = self.current_cycle_samples();
1306 if cycle_samples == 0 {
1307 return;
1308 }
1309 let plan = self.executor.plan().clone();
1310 let Some(preview) = self.audio_preview.as_mut() else {
1311 return;
1312 };
1313 let channels = preview.channels.max(1);
1314 let total_frames = preview.samples.len() / channels;
1315 if preview.cursor >= total_frames {
1316 self.audio_preview = None;
1317 return;
1318 }
1319
1320 for &(buffer, channel) in &plan.hw_out_map {
1321 let dst = unsafe { &mut *plan.buffer_ptr(buffer) };
1325 let frames = cycle_samples.min(dst.len());
1326 dst[..frames].fill(0.0);
1327 let source_channel = channel.min(channels - 1);
1328 for (frame, out) in dst.iter_mut().take(frames).enumerate() {
1329 let source_frame = preview.cursor + frame;
1330 if source_frame >= total_frames {
1331 break;
1332 }
1333 let sample_index = source_frame * channels + source_channel;
1334 *out = preview.samples.get(sample_index).copied().unwrap_or(0.0);
1335 }
1336 }
1337
1338 preview.cursor = preview.cursor.saturating_add(cycle_samples);
1339 if preview.cursor >= total_frames {
1340 self.audio_preview = None;
1341 }
1342 }
1343
1344 pub(crate) fn should_publish_hw_out_meters(&mut self) -> bool {
1345 let now = Instant::now();
1346 match self.last_hw_out_meter_publish {
1347 Some(last) if now.duration_since(last) < Self::METER_PUBLISH_INTERVAL => false,
1348 _ => {
1349 self.last_hw_out_meter_publish = Some(now);
1350 true
1351 }
1352 }
1353 }
1354
1355 pub(crate) fn should_publish_track_meters(&mut self) -> bool {
1356 let now = Instant::now();
1357 match self.last_track_meter_publish {
1358 Some(last) if now.duration_since(last) < Self::METER_PUBLISH_INTERVAL => false,
1359 _ => {
1360 self.last_track_meter_publish = Some(now);
1361 true
1362 }
1363 }
1364 }
1365
1366 pub(crate) fn should_publish_hw_out_linear(&mut self, peaks_linear: &[f32]) -> bool {
1367 #[cfg(unix)]
1368 {
1369 self.hw_out_meter_publish_phase = !self.hw_out_meter_publish_phase;
1370 if !self.hw_out_meter_publish_phase {
1371 return false;
1372 }
1373 let changed = if self.last_hw_out_meter_linear.len() != peaks_linear.len() {
1374 true
1375 } else {
1376 self.last_hw_out_meter_linear
1377 .iter()
1378 .zip(peaks_linear.iter())
1379 .any(|(prev, next)| (prev - next).abs() >= Self::HW_OUT_METER_LINEAR_EPSILON)
1380 };
1381 if !changed {
1382 return false;
1383 }
1384 self.last_hw_out_meter_linear.clear();
1385 self.last_hw_out_meter_linear
1386 .extend_from_slice(peaks_linear);
1387 true
1388 }
1389 #[cfg(not(unix))]
1390 {
1391 let _ = peaks_linear;
1392 false
1393 }
1394 }
1395
1396 pub(crate) async fn maybe_notify_hw_out_meter(&mut self, _meter_db: Vec<f32>) {
1397 {}
1398 }
1399
1400 pub(crate) async fn apply_hw_out_gain_and_meter(&mut self) {
1401 let gain = if self.hw_out_muted {
1402 0.0
1403 } else {
1404 10.0_f32.powf(self.hw_out_level_db / 20.0)
1405 };
1406
1407 if let Some(worker) = &self.hw_worker {
1410 let _ = worker
1411 .tx
1412 .send(Message::HWSetOutputGainBalance {
1413 gain,
1414 balance: self.hw_out_balance,
1415 })
1416 .await;
1417 } else {
1418 #[cfg(unix)]
1419 {
1420 if let Some(jack) = self.jack_runtime.as_ref() {
1421 jack.set_output_gain_linear(gain);
1422 jack.set_output_balance(self.hw_out_balance);
1423 } else {
1424 return;
1425 }
1426 }
1427 #[cfg(not(unix))]
1428 {
1429 return;
1430 }
1431 }
1432
1433 if self.meter_decay_after_stop.is_some() {
1434 return;
1435 }
1436
1437 let should_notify_interval = self.should_publish_hw_out_meters();
1438 if !should_notify_interval {
1439 return;
1440 }
1441
1442 let plan = self.executor.plan().clone();
1443 let peaks_linear =
1444 crate::hw::common::output_meter_linear_from_plan(&plan, gain, self.hw_out_balance);
1445 if self.hw_out_peak_hold_linear.len() != peaks_linear.len() {
1446 self.hw_out_peak_hold_linear.resize(peaks_linear.len(), 0.0);
1447 }
1448 let mut held_peaks = Vec::with_capacity(peaks_linear.len());
1449 for (idx, peak_now) in peaks_linear.iter().copied().enumerate() {
1450 let held = self.hw_out_peak_hold_linear[idx] * 0.92;
1451 let next = peak_now.max(held);
1452 self.hw_out_peak_hold_linear[idx] = next;
1453 held_peaks.push(next);
1454 }
1455 let should_notify = self.should_publish_hw_out_linear(&held_peaks);
1456 let meter_db: Vec<f32> = held_peaks
1457 .into_iter()
1458 .map(Self::meter_linear_to_db)
1459 .collect();
1460 self.latest_hw_out_meter_db = Arc::new(meter_db.clone());
1461 if should_notify {
1462 self.maybe_notify_hw_out_meter(meter_db).await;
1463 }
1464 }
1465
1466 pub(crate) fn preload_track_clips_spawn(&self) {
1467 let tracks: Vec<_> = self
1468 .state_snapshot
1469 .load_full()
1470 .tracks
1471 .values()
1472 .cloned()
1473 .collect();
1474 for track in tracks {
1475 tokio::task::spawn_blocking(move || {
1476 track.lock().preload_clips();
1477 });
1478 }
1479 }
1480
1481 pub(crate) fn preload_track_clips(
1490 &self,
1491 ) -> impl std::future::Future<Output = ()> + Send + 'static {
1492 let tracks: Vec<_> = self
1493 .state_snapshot
1494 .load_full()
1495 .tracks
1496 .values()
1497 .cloned()
1498 .collect();
1499 Self::preload_track_handles(tracks)
1500 }
1501
1502 async fn preload_track_handles(tracks: Vec<crate::state::TrackHandle>) {
1503 if tracks.is_empty() {
1504 return;
1505 }
1506 let mut handles = Vec::with_capacity(tracks.len());
1507 for track in tracks {
1508 handles.push(tokio::task::spawn_blocking(move || {
1509 track.lock().preload_clips();
1510 }));
1511 }
1512 for handle in handles {
1513 if let Err(e) = handle.await {
1514 tracing::warn!("Clip preload task panicked: {e}");
1515 }
1516 }
1517 }
1518
1519 pub(crate) fn task_track_name(task: &ProcessTask) -> String {
1520 match task {
1521 ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => {
1522 t.lock().name.clone()
1523 }
1524 ProcessTask::Plugin { track, .. } => track.lock().name.clone(),
1525 }
1526 }
1527
1528 pub(crate) fn prepare_task_track(&self, task: &ProcessTask) {
1553 let track = match task {
1554 ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => t,
1555 ProcessTask::Plugin { track, .. } => track,
1556 };
1557 self.transport_sample_snapshot
1562 .mirror(self.transport_sample, self.session_transport_sample);
1563 if track.transport_sample_snapshot().is_none() {
1564 track.attach_transport_sample_snapshot(self.transport_sample_snapshot.clone());
1568 }
1569 if track.last_prepare_generation() == self.prepare_generation {
1570 return;
1575 }
1576 let mut t = track.lock();
1577 self.transport_sample_snapshot
1578 .set_use_session(self.session_clip_playback_enabled && self.playing);
1579 t.set_loop_config(self.loop_enabled, self.loop_range_samples);
1580 t.set_transport_timing(self.tempo_bpm, self.tsig_num, self.tsig_denom);
1581 t.set_clip_playback_enabled(self.clip_playback_enabled && self.playing);
1582 t.set_session_clip_playback_enabled(self.session_clip_playback_enabled && self.playing);
1583 t.set_record_tap_enabled(self.playing && self.record_enabled);
1584 t.audio.set_processing(true);
1585 t.mark_prepare_pushed(self.prepare_generation);
1586 }
1587
1588 pub(crate) fn bump_prepare_generation(&mut self) {
1589 self.prepare_generation += 1;
1590 }
1591
1592 pub(crate) async fn dispatch_node_jobs(&mut self, jobs: Vec<crate::executor::NodeJob>) -> bool {
1596 self.pending_node_jobs.extend(jobs);
1597 let mut cycle_complete = false;
1598 while !self.pending_node_jobs.is_empty() {
1599 let Some(worker_index) = self.take_ready_worker_index() else {
1600 break;
1601 };
1602 let Some(job) = self.pending_node_jobs.pop_front() else {
1603 break;
1604 };
1605 if let Some(crate::render_plan::Op::Task { task, .. }) =
1606 job.plan.nodes.get(job.node as usize)
1607 {
1608 self.prepare_task_track(task);
1609 }
1610 let worker = &mut self.workers[worker_index];
1611 if let Some(node_job_tx) = worker.node_job_tx.as_mut() {
1612 match node_job_tx.push(job) {
1613 Ok(()) => {
1614 if let Some(thread) = &worker.node_thread {
1615 thread.unpark();
1616 }
1617 }
1618 Err(rtrb::PushError::Full(job)) => {
1619 self.pending_node_jobs.push_front(job);
1620 self.push_ready_worker(worker_index);
1621 break;
1622 }
1623 }
1624 } else {
1625 let node = job.node;
1626 error!("Worker {worker_index} has no node-job mailbox");
1627 let outcome = self.executor.abandon_node(node, Instant::now());
1628 self.log_silenced_nodes(&outcome.silenced);
1629 cycle_complete |= outcome.cycle_complete;
1630 self.pending_node_jobs.extend(outcome.jobs);
1631 }
1632 }
1633 cycle_complete
1634 }
1635
1636 fn ensure_metronome_wiring(&mut self) {
1644 let Some(track) = self
1645 .state_snapshot
1646 .load_full()
1647 .tracks
1648 .get(Self::METRONOME_TRACK)
1649 .cloned()
1650 else {
1651 return;
1652 };
1653 let frames = self.current_cycle_samples();
1654 let (_, changed) = track.lock().ensure_metronome_source(frames);
1655 if changed {
1656 self.plan_builder.mark_dirty();
1657 }
1658 }
1659
1660 pub(crate) async fn start_plan_cycle(&mut self) -> bool {
1661 if !self.playing || !self.executor.cycle_complete() || !self.offline_bounce_jobs.is_empty()
1665 {
1666 return false;
1667 }
1668 self.refresh_realtime_infection();
1669 self.ensure_metronome_wiring();
1670 let jobs = self.executor.start_cycle(Instant::now());
1671 if self.dispatch_node_jobs(jobs).await {
1672 self.on_all_tracks_finished().await;
1673 return true;
1674 }
1675 false
1676 }
1677
1678 pub(crate) async fn on_node_done(
1682 &mut self,
1683 worker_id: usize,
1684 epoch: u64,
1685 node: u32,
1686 output_linear: Vec<f32>,
1687 parameter_updates: Vec<Action>,
1688 latency_changed: bool,
1689 ) {
1690 self.push_ready_worker(worker_id);
1691 let mut complete = self.dispatch_node_jobs(Vec::new()).await;
1692 if epoch != self.executor.epoch() {
1693 tracing::debug!(
1694 "dropping stale NodeDone (epoch {} vs {}) for node {}",
1695 epoch,
1696 self.executor.epoch(),
1697 node
1698 );
1699 return;
1700 }
1701 if latency_changed {
1702 self.publish_state_snapshot();
1703 self.plan_builder.mark_dirty();
1704 }
1705 let plan = self.executor.plan().clone();
1706 if let Some(crate::render_plan::Op::Task { task, .. }) = plan.nodes.get(node as usize) {
1707 let track_name = Self::task_track_name(task);
1708 self.track_meter_linear_by_track
1709 .insert(track_name, output_linear);
1710 }
1711 for action in parameter_updates {
1712 self.notify_clients(Ok(action)).await;
1713 }
1714 let now = Instant::now();
1715 let (jobs, done) = self.executor.on_node_done(epoch, node, now);
1716 complete |= done;
1717 complete |= self.dispatch_node_jobs(jobs).await;
1718 let outcome = self
1719 .executor
1720 .force_timeouts(now, Self::TRACK_PROCESS_TIMEOUT);
1721 self.log_silenced_nodes(&outcome.silenced);
1722 complete |= self.dispatch_node_jobs(outcome.jobs).await;
1723 complete |= outcome.cycle_complete;
1724 if complete {
1725 self.on_all_tracks_finished().await;
1726 }
1727 }
1728
1729 pub(crate) async fn poll_stopped_plugin_parameter_echoes(&mut self) {
1730 if self.playing || self.transport_running {
1731 return;
1732 }
1733
1734 let state = self.state_snapshot.load_full();
1735 let mut updates = Vec::new();
1736 for track in state.tracks.values() {
1737 updates.extend(track.lock().drain_plugin_parameter_echoes());
1738 }
1739 drop(state);
1740
1741 for action in updates {
1742 self.notify_clients(Ok(action)).await;
1743 }
1744 }
1745
1746 pub(crate) async fn poll_node_worker_results(&mut self) {
1747 let mut results = Vec::new();
1748 for worker in &mut self.workers {
1749 if let Some(rx) = worker.node_result_rx.as_mut() {
1750 while let Ok(result) = rx.pop() {
1751 results.push(result);
1752 }
1753 }
1754 }
1755 for result in results {
1756 self.on_node_done(
1757 result.worker_id,
1758 result.epoch,
1759 result.node,
1760 result.output_linear,
1761 result.parameter_updates,
1762 result.latency_changed,
1763 )
1764 .await;
1765 }
1766 }
1767
1768 pub(crate) async fn poll_jack_hw_finished(&mut self) {
1769 #[cfg(unix)]
1770 {
1771 let finished = self
1772 .jack_runtime
1773 .as_ref()
1774 .map(|jack| jack.take_hw_finished_count())
1775 .unwrap_or(0);
1776 if finished > 0 {
1777 self.handle_hw_finished().await;
1778 }
1779 }
1780 }
1781
1782 #[cfg(unix)]
1783 pub(crate) async fn handle_hw_finished(&mut self) {
1784 if !self.awaiting_hwfinished {
1785 return;
1786 }
1787 tracing::debug!(
1788 playing = self.playing,
1789 transport_running = self.transport_running,
1790 transport_sample = self.transport_sample,
1791 session_transport_sample = self.session_transport_sample,
1792 cycle_samples = self.current_cycle_samples(),
1793 "HWFinished handling"
1794 );
1795 self.handling_hwfinished = true;
1796 self.awaiting_hwfinished = false;
1797 #[cfg(unix)]
1798 {
1799 if let Some(jack) = self.jack_runtime.as_mut() {
1800 if !self.pending_hw_midi_out_events.is_empty() {
1801 let out_events = std::mem::take(&mut self.pending_hw_midi_out_events);
1802 jack.write_events(&out_events);
1803 }
1804 let mut in_events = vec![];
1805 jack.read_events_into(&mut in_events);
1806 if !in_events.is_empty() {
1807 self.pending_hw_midi_events.extend(in_events);
1808 }
1809 let dropped = jack.take_midi_events_dropped();
1810 if dropped > 0 {
1811 tracing::warn!(
1812 "JACK MIDI ring full; {dropped} events dropped since last cycle"
1813 );
1814 }
1815 }
1816 }
1817 #[cfg(unix)]
1818 if self.jack_runtime.is_some() {
1819 self.sync_from_jack_transport().await;
1820 }
1821 while let Some(a) = self.pending_requests.pop_front() {
1822 self.handle_request(a).await;
1823 }
1824 self.apply_mute_solo_policy();
1825 self.append_recorded_cycle();
1826 self.flush_completed_recordings().await;
1827 let hw_in_routes = self.midi_hw_in_routes.clone();
1828 let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
1829 let mut reconfigured_tracks = Vec::new();
1830 let state = self.state_snapshot.load_full();
1831 for (track_name, track) in state.tracks.iter() {
1832 let mut track_lock = track.lock();
1833 if self.jack_runtime_is_some() {
1834 if !self.pending_hw_midi_events.is_empty() {
1835 track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
1836 }
1837 } else {
1838 for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
1839 if let Some(events) = pending_hw_in_by_device.get(&route.device) {
1840 track_lock.push_hw_midi_events_to_port(route.to_port, events);
1841 }
1842 }
1843 }
1844 if track_lock.setup() {
1845 reconfigured_tracks.push(track_name.clone());
1846 }
1847 }
1848 self.publish_track_meters();
1849 self.publish_session_runtime_reports().await;
1850 self.publish_clap_state_dirty().await;
1851 for track_name in reconfigured_tracks {
1852 let track = state.tracks.get(&track_name).cloned();
1853 if let Some(track) = track {
1854 let (plugins, connections, connectable_connections) = {
1855 let track_lock = track.lock();
1856 (
1857 track_lock.plugin_graph_plugins(false),
1858 track_lock.plugin_graph_connections(),
1859 track_lock.connectable_connections(),
1860 )
1861 };
1862 self.notify_clients(Ok(Action::TrackPluginGraph {
1863 track_name: track_name.clone(),
1864 plugins,
1865 connections,
1866 connectable_connections,
1867 }))
1868 .await;
1869 }
1870 }
1871 self.pending_hw_midi_events.clear();
1872 self.pending_hw_midi_events_by_device.clear();
1873 let cycle_samples = self.current_cycle_samples();
1874 if self.transport_running {
1875 if self.transport_panic_flush_pending {
1876 self.transport_panic_flush_pending = false;
1877 } else if self.transport_restart_pending {
1878 self.transport_restart_pending = false;
1879 } else {
1880 let before = self.transport_sample;
1881 let next = self.transport_sample.saturating_add(cycle_samples);
1882 let normalized = self.normalize_transport_sample(next);
1883 let wrapped = normalized != next;
1884 self.transport_sample = normalized;
1885 tracing::debug!(
1888 before,
1889 delta = cycle_samples,
1890 next,
1891 normalized,
1892 wrapped,
1893 "transport advanced after HWFinished"
1894 );
1895 self.publish_transport_snapshot();
1896 if wrapped {
1897 if self.notified_loop_wrap_sample == Some(self.transport_sample) {
1898 self.notified_loop_wrap_sample = None;
1899 } else {
1900 self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
1901 .await;
1902 }
1903 }
1904 }
1905 } else {
1906 tracing::debug!(
1907 playing = self.playing,
1908 cycle_samples,
1909 "transport not advanced because transport_running is false"
1910 );
1911 }
1912 if self.session_clip_playback_enabled && self.playing {
1913 let before = self.session_transport_sample;
1914 self.session_transport_sample =
1915 self.session_transport_sample.saturating_add(cycle_samples);
1916 tracing::debug!(
1917 before,
1918 delta = cycle_samples,
1919 after = self.session_transport_sample,
1920 "session transport advanced after HWFinished"
1921 );
1922 }
1923 {
1924 let echoes = self.apply_modulators(self.active_transport_sample());
1925 for action in echoes {
1926 self.notify_clients(Ok(action)).await;
1927 }
1928 }
1929 self.apply_mixosc_automation(self.active_transport_sample());
1930 let cycle_started = self.start_plan_cycle().await;
1931 if self.hw_worker.is_some()
1934 && !cycle_started
1935 && (self.playing || self.audio_preview.is_some())
1936 && self.executor.cycle_complete()
1937 {
1938 self.request_hw_cycle().await;
1939 }
1940 tracing::debug!(
1941 cycle_started,
1942 hw_worker = self.hw_worker.is_some(),
1943 awaiting_hwfinished = self.awaiting_hwfinished,
1944 executor_complete = self.executor.cycle_complete(),
1945 "HWFinished rearm decision"
1946 );
1947 #[cfg(unix)]
1948 {
1949 if self.jack_runtime.is_some() {
1950 self.awaiting_hwfinished = true;
1951 }
1952 }
1953 self.handling_hwfinished = false;
1954 }
1955
1956 pub(crate) async fn on_executor_tick(&mut self) {
1959 if self.executor.cycle_complete() {
1960 return;
1961 }
1962 let outcome = self
1963 .executor
1964 .force_timeouts(Instant::now(), Self::TRACK_PROCESS_TIMEOUT);
1965 self.log_silenced_nodes(&outcome.silenced);
1966 let mut complete = self.dispatch_node_jobs(outcome.jobs).await;
1967 complete |= outcome.cycle_complete;
1968 if complete {
1969 self.on_all_tracks_finished().await;
1970 }
1971 }
1972
1973 pub(crate) fn log_silenced_nodes(&self, silenced: &[u32]) {
1974 for &node in silenced {
1975 let plan = self.executor.plan();
1976 let name = match plan.nodes.get(node as usize) {
1977 Some(crate::render_plan::Op::Task { task, .. }) => Self::task_track_name(task),
1978 _ => format!("node {node}"),
1979 };
1980 tracing::warn!(
1981 "Node {} ('{}') exceeded process timeout ({} ms); forced silent completion for cycle",
1982 node,
1983 name,
1984 Self::TRACK_PROCESS_TIMEOUT.as_millis()
1985 );
1986 }
1987 }
1988
1989 pub(crate) async fn send_bounce_job(
1992 &mut self,
1993 worker_index: usize,
1994 job: crate::message::OfflineBounceWork,
1995 ) {
1996 let track_name = job.track_name.clone();
1997 self.bounce_worker_tracks
1998 .insert(worker_index, track_name.clone());
1999 let worker = &self.workers[worker_index];
2000 if let Err(e) = worker.tx.send(Message::ProcessOfflineBounce(job)).await {
2001 self.bounce_worker_tracks.remove(&worker_index);
2002 self.offline_bounce_jobs.remove(&track_name);
2003 self.push_ready_worker(worker_index);
2004 self.notify_clients(Err(format!("Failed to schedule offline bounce: {e}")))
2005 .await;
2006 }
2007 }
2008
2009 async fn drain_pending_requests_if_idle(&mut self) {
2012 if self.offline_bounce_jobs.is_empty() {
2013 while let Some(next) = self.pending_requests.pop_front() {
2014 self.handle_request(next).await;
2015 }
2016 }
2017 }
2018
2019 pub(crate) async fn on_all_tracks_finished(&mut self) {
2020 let pending = std::mem::take(&mut self.pending_bounce_starts);
2023 for (worker_index, job) in pending {
2024 self.send_bounce_job(worker_index, job).await;
2025 }
2026 if self.transport_restart_pending {
2027 let state = self.state_snapshot.load_full();
2028 for track in state.tracks.values() {
2029 track.lock().take_hw_midi_out_events();
2030 }
2031 } else if self.hw_worker.is_some() {
2032 self.active_hw_notes_cycle_start = self.active_hw_notes_by_track.clone();
2033 let mut out_events = self.collect_hw_midi_output_events_by_device();
2034 if self.loop_enabled
2035 && let Some((_, loop_end)) = self.loop_range_samples
2036 {
2037 let cycle_end = self
2038 .transport_sample
2039 .saturating_add(self.current_cycle_samples());
2040 if self.transport_sample < loop_end && cycle_end >= loop_end {
2041 let wrap_frame = loop_end
2042 .saturating_sub(self.transport_sample)
2043 .min(self.current_cycle_samples())
2044 as u32;
2045 out_events.extend(self.note_off_events_for_active_snapshot(
2046 &self.active_hw_notes_cycle_start,
2047 wrap_frame,
2048 ));
2049 out_events.sort_by(|a, b| {
2050 a.event
2051 .frame
2052 .cmp(&b.event.frame)
2053 .then_with(|| a.device.cmp(&b.device))
2054 });
2055 }
2056 }
2057 self.pending_hw_midi_out_events_by_device.extend(out_events);
2058 } else {
2059 self.pending_hw_midi_out_events = self.collect_hw_midi_output_events();
2060 }
2061 self.request_hw_cycle().await;
2062 }
2063
2064 pub(crate) fn take_ready_worker_index(&mut self) -> Option<usize> {
2065 while !self.ready_workers.is_empty() {
2066 let worker_index = self.ready_workers.remove(0);
2067 if worker_index < self.workers.len() {
2068 return Some(worker_index);
2069 }
2070 }
2071 None
2072 }
2073
2074 pub(crate) fn push_ready_worker(&mut self, worker_index: usize) {
2075 self.ready_workers.push(worker_index);
2076 }
2077
2078 pub(crate) fn publish_track_meters(&mut self) {
2079 if !self.should_publish_track_meters() {
2080 return;
2081 }
2082 if self.meter_decay_after_stop.is_some() {
2083 self.update_meter_decay_after_stop();
2084 return;
2085 }
2086 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2087 .state_snapshot
2088 .load_full()
2089 .tracks
2090 .iter()
2091 .map(|(name, track)| (name.clone(), track.clone()))
2092 .collect();
2093 let mut snapshot = Vec::with_capacity(tracks.len());
2094 for (name, track) in &tracks {
2095 let linear = self
2096 .track_meter_linear_by_track
2097 .get(name)
2098 .cloned()
2099 .unwrap_or_else(|| track.lock().output_meter_linear());
2100 let output_db = linear
2101 .iter()
2102 .copied()
2103 .map(Self::meter_linear_to_db)
2104 .collect::<Vec<_>>();
2105 snapshot.push((name.clone(), output_db));
2106 }
2107 self.latest_track_meter_snapshot = Arc::new(snapshot);
2108 }
2109
2110 fn record_session_completed_clip_pass(
2111 &mut self,
2112 track_name: String,
2113 scene_index: usize,
2114 clip_id: String,
2115 pass_index: usize,
2116 start_sample: usize,
2117 length_samples: usize,
2118 ) {
2119 let key = (
2120 track_name.clone(),
2121 scene_index,
2122 clip_id.clone(),
2123 pass_index,
2124 start_sample,
2125 );
2126 if self.session_reported_clip_passes.insert(key) {
2127 self.session_completed_clip_passes
2128 .push(crate::meter::SessionCompletedClipPass {
2129 track_name,
2130 scene_index,
2131 clip_id,
2132 pass_index,
2133 start_sample,
2134 length_samples,
2135 });
2136 }
2137 }
2138
2139 fn record_completed_session_scene_span(
2140 &mut self,
2141 tracks: &HashMap<String, crate::state::TrackHandle>,
2142 scene_index: usize,
2143 previous_scene: Option<usize>,
2144 scene_start_sample: usize,
2145 elapsed_samples: usize,
2146 ) {
2147 for (track_name, track) in tracks {
2148 let track = track.lock();
2149 let slot = track.rt.session_slots.get(&scene_index);
2150 let prev_slot = previous_scene.and_then(|scene| track.rt.session_slots.get(&scene));
2151 let playing_clip_id = track
2152 .rt
2153 .playing_session_clips
2154 .last()
2155 .map(|clip| clip.clip_id.as_str());
2156 let clip_id = match (slot, prev_slot) {
2157 (Some(slot), _) if slot.play_enabled => Some(slot.clip_id.as_str()),
2158 (Some(slot), _) if slot.stop_enabled => None,
2159 (_, Some(prev_slot)) if prev_slot.play_enabled => Some(prev_slot.clip_id.as_str()),
2160 (_, Some(prev_slot)) if prev_slot.stop_enabled => None,
2161 _ => playing_clip_id,
2162 }
2163 .filter(|clip_id| !clip_id.is_empty());
2164 let Some(clip_id) = clip_id else { continue };
2165 let clip_length = track
2166 .session_clip_length(clip_id, crate::kind::Kind::Audio)
2167 .or_else(|| track.session_clip_length(clip_id, crate::kind::Kind::MIDI))
2168 .unwrap_or(0);
2169 if clip_length == 0 {
2170 continue;
2171 }
2172 let completed_passes = elapsed_samples / clip_length;
2173 for pass_index in 0..completed_passes {
2174 self.record_session_completed_clip_pass(
2175 track_name.clone(),
2176 scene_index,
2177 clip_id.to_string(),
2178 pass_index,
2179 scene_start_sample.saturating_add(pass_index * clip_length),
2180 clip_length,
2181 );
2182 }
2183 }
2184 }
2185
2186 pub(crate) async fn publish_session_runtime_reports(&mut self) {
2187 let mut current = HashMap::<(String, usize), (SessionSlotState, usize, usize)>::new();
2188 {
2189 let state = self.state_snapshot.load_full();
2190 if let Some((queued_scene, queued_launch_at)) = self.session_scene_queue {
2191 let still_pending = state.tracks.values().any(|track| {
2199 let track = track.lock();
2200 track.rt.pending_session_launches.iter().any(|launch| {
2201 launch.scene_index == queued_scene
2202 && launch.launch_at_sample == queued_launch_at
2203 }) || track
2204 .rt
2205 .playing_session_clips
2206 .iter()
2207 .any(|clip| clip.stop_at_sample == Some(queued_launch_at))
2208 });
2209 if !still_pending && self.session_transport_sample >= queued_launch_at {
2210 if let Some(current_scene) = self.session_current_scene {
2211 let scene_start = self.session_current_scene_start_sample;
2212 let elapsed = queued_launch_at.saturating_sub(scene_start);
2213 self.record_completed_session_scene_span(
2214 &state.tracks,
2215 current_scene,
2216 self.session_current_scene_previous_scene,
2217 scene_start,
2218 elapsed,
2219 );
2220 }
2221 self.session_scene_queue = None;
2222 self.session_current_scene_previous_scene = self.session_current_scene;
2223 self.session_current_scene = Some(queued_scene);
2224 self.session_current_scene_start_sample = queued_launch_at;
2225 self.session_current_scene_length_samples =
2226 self.session_scene_queue_length_samples;
2227 self.session_scene_queue_length_samples = 0;
2228 }
2229 }
2230 if let Some(current_scene) = self.session_current_scene
2231 && self.session_current_scene_length_samples > 0
2232 {
2233 self.record_completed_session_scene_span(
2234 &state.tracks,
2235 current_scene,
2236 self.session_current_scene_previous_scene,
2237 self.session_current_scene_start_sample,
2238 self.session_transport_sample
2239 .saturating_sub(self.session_current_scene_start_sample),
2240 );
2241 }
2242 for (track_name, track) in &state.tracks {
2243 let track = track.lock();
2244 for launch in &track.rt.pending_session_launches {
2245 current.insert(
2246 (track_name.clone(), launch.scene_index),
2247 (SessionSlotState::Queued, 0, 0),
2248 );
2249 }
2250 for clip in &track.rt.playing_session_clips {
2251 if self.session_current_scene_length_samples == 0
2252 && let Some(clip_length) =
2253 track.session_clip_length(&clip.clip_id, clip.kind)
2254 {
2255 let scene_report = self
2256 .session_current_scene
2257 .map(|_| self.session_current_scene_length_samples)
2258 .filter(|length| *length > 0)
2259 .map(|length| {
2260 (
2261 self.session_current_scene.unwrap_or(clip.scene_index),
2262 self.session_current_scene_start_sample,
2263 self.session_transport_sample
2264 .saturating_sub(self.session_current_scene_start_sample),
2265 length,
2266 )
2267 });
2268 let launch_sample = self
2269 .session_transport_sample
2270 .saturating_sub(clip.elapsed_samples);
2271 let (
2272 report_scene_index,
2273 report_start_sample,
2274 report_elapsed,
2275 report_length,
2276 ) = scene_report.unwrap_or((
2277 clip.scene_index,
2278 launch_sample,
2279 clip.elapsed_samples,
2280 clip_length,
2281 ));
2282 if report_length == 0 {
2283 continue;
2284 }
2285 let completed_passes = report_elapsed / report_length;
2286 for pass_index in 0..completed_passes {
2287 self.record_session_completed_clip_pass(
2288 track_name.clone(),
2289 report_scene_index,
2290 clip.clip_id.clone(),
2291 pass_index,
2292 report_start_sample.saturating_add(pass_index * report_length),
2293 report_length,
2294 );
2295 }
2296 }
2297 current.insert(
2298 (track_name.clone(), clip.scene_index),
2299 (
2300 SessionSlotState::Playing,
2301 clip.play_position_samples,
2302 clip.elapsed_samples,
2303 ),
2304 );
2305 }
2306 }
2307 }
2308
2309 if self
2310 .last_session_report_publish
2311 .is_some_and(|t| t.elapsed() < Self::SESSION_RUNTIME_REPORT_INTERVAL)
2312 {
2313 return;
2314 }
2315
2316 let snapshot = self.session_runtime_snapshot_producer.write_buffer();
2317 snapshot.session_sample = self.session_transport_sample;
2318 snapshot.slots.clear();
2319 snapshot.slots.extend(current.iter().map(
2320 |((track_name, scene_index), (state, play_position_samples, elapsed_samples))| {
2321 crate::meter::SessionRuntimeSlotSnapshot {
2322 track_name: track_name.clone(),
2323 scene_index: *scene_index,
2324 state: *state,
2325 play_position_samples: *play_position_samples,
2326 elapsed_samples: *elapsed_samples,
2327 }
2328 },
2329 ));
2330 snapshot.completed_clip_passes = self.session_completed_clip_passes.clone();
2331 snapshot.current_scene = self.session_current_scene;
2332 self.session_runtime_snapshot_producer.publish();
2333 self.last_session_report_publish = Some(Instant::now());
2334 }
2335
2336 pub(crate) async fn publish_clap_state_dirty(&mut self) {
2337 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2338 .state_snapshot
2339 .load_full()
2340 .tracks
2341 .iter()
2342 .map(|(name, track)| (name.clone(), track.clone()))
2343 .collect();
2344 for (track_name, track) in &tracks {
2345 let dirty = track.lock().take_dirty_clap_instances();
2346 for instance_id in dirty {
2347 self.notify_clients(Ok(Action::TrackClapStateDirty {
2348 track_name: track_name.clone(),
2349 instance_id,
2350 }))
2351 .await;
2352 }
2353 }
2354 }
2355
2356 pub(crate) fn reset_meters_after_stop(&mut self) {
2357 self.last_hw_out_meter_publish = None;
2358 self.last_track_meter_publish = None;
2359 self.last_meter_snapshot_publish = None;
2360 #[cfg(unix)]
2361 {
2362 self.last_hw_out_meter_linear.clear();
2363 }
2364
2365 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2366 .state_snapshot
2367 .load_full()
2368 .tracks
2369 .iter()
2370 .map(|(name, track)| (name.clone(), track.clone()))
2371 .collect();
2372 let mut track_linear = Vec::with_capacity(tracks.len());
2373 for (name, track) in tracks {
2374 let linear = self
2375 .track_meter_linear_by_track
2376 .get(&name)
2377 .cloned()
2378 .unwrap_or_else(|| track.lock().output_meter_linear());
2379 track_linear.push((name, linear));
2380 }
2381 let hw_out_linear = if self.hw_out_peak_hold_linear.is_empty() {
2382 self.latest_hw_out_meter_db
2383 .iter()
2384 .copied()
2385 .map(Self::meter_db_to_linear)
2386 .collect()
2387 } else {
2388 self.hw_out_peak_hold_linear.clone()
2389 };
2390 self.meter_decay_after_stop = Some(MeterDecay {
2391 started_at: Instant::now(),
2392 hw_out_linear,
2393 track_linear,
2394 });
2395 self.update_meter_decay_after_stop();
2396 self.publish_meter_snapshot();
2397 }
2398
2399 pub(crate) fn update_meter_decay_after_stop(&mut self) {
2400 let Some(decay) = self.meter_decay_after_stop.as_ref() else {
2401 return;
2402 };
2403 let elapsed = decay.started_at.elapsed();
2404 if elapsed >= Self::METER_DECAY_AFTER_STOP {
2405 self.finish_meter_decay_after_stop();
2406 return;
2407 }
2408
2409 let remaining = 1.0 - (elapsed.as_secs_f32() / Self::METER_DECAY_AFTER_STOP.as_secs_f32());
2410 let hw_out_linear = decay
2411 .hw_out_linear
2412 .iter()
2413 .copied()
2414 .map(|value| value * remaining)
2415 .collect::<Vec<_>>();
2416 self.latest_hw_out_meter_db = Arc::new(
2417 hw_out_linear
2418 .iter()
2419 .copied()
2420 .map(Self::meter_linear_to_db)
2421 .collect(),
2422 );
2423 self.hw_out_peak_hold_linear = hw_out_linear;
2424
2425 let mut track_linear_by_track = HashMap::with_capacity(decay.track_linear.len());
2426 let mut snapshot = Vec::with_capacity(decay.track_linear.len());
2427 for (name, start_linear) in &decay.track_linear {
2428 let linear = start_linear
2429 .iter()
2430 .copied()
2431 .map(|value| value * remaining)
2432 .collect::<Vec<_>>();
2433 let output_db = linear
2434 .iter()
2435 .copied()
2436 .map(Self::meter_linear_to_db)
2437 .collect::<Vec<_>>();
2438 track_linear_by_track.insert(name.clone(), linear);
2439 snapshot.push((name.clone(), output_db));
2440 }
2441 self.track_meter_linear_by_track = track_linear_by_track;
2442 self.latest_track_meter_snapshot = Arc::new(snapshot);
2443 }
2444
2445 pub(crate) fn finish_meter_decay_after_stop(&mut self) {
2446 self.meter_decay_after_stop = None;
2447 self.hw_out_peak_hold_linear.fill(0.0);
2448 let hw_channels = self.latest_hw_out_meter_db.len();
2449 self.latest_hw_out_meter_db = Arc::new(vec![-90.0; hw_channels]);
2450
2451 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2452 .state_snapshot
2453 .load_full()
2454 .tracks
2455 .iter()
2456 .map(|(name, track)| (name.clone(), track.clone()))
2457 .collect();
2458 self.track_meter_linear_by_track.clear();
2459 let mut snapshot = Vec::with_capacity(tracks.len());
2460 for (name, track) in tracks {
2461 let mut t = track.lock();
2462 t.clear_output_meters();
2463 let width = t.output_meter_linear().len();
2464 let zero_linear = vec![0.0; width];
2465 self.track_meter_linear_by_track
2466 .insert(name.clone(), zero_linear);
2467 snapshot.push((name, vec![-90.0; width]));
2468 }
2469 self.latest_track_meter_snapshot = Arc::new(snapshot);
2470 self.publish_meter_snapshot();
2471 }
2472
2473 pub(crate) fn publish_meter_snapshot_if_due(&mut self) {
2474 let now = Instant::now();
2475 if self
2476 .last_meter_snapshot_publish
2477 .is_some_and(|last| now.duration_since(last) < Self::METER_PUBLISH_INTERVAL)
2478 {
2479 return;
2480 }
2481 self.last_meter_snapshot_publish = Some(now);
2482 self.update_meter_decay_after_stop();
2483 self.publish_meter_snapshot();
2484 }
2485
2486 pub(crate) fn publish_meter_snapshot(&mut self) {
2487 let snapshot = self.meter_snapshot_producer.write_buffer();
2488 snapshot.hw_out_db.clear();
2489 snapshot
2490 .hw_out_db
2491 .extend(self.latest_hw_out_meter_db.iter().copied());
2492 snapshot.track_meters.clear();
2493 snapshot
2494 .track_meters
2495 .extend(self.latest_track_meter_snapshot.iter().cloned());
2496 self.meter_snapshot_producer.publish();
2497 }
2498
2499 pub(crate) fn publish_transport_snapshot(&mut self) {
2500 let snapshot = self.transport_snapshot_producer.write_buffer();
2501 snapshot.sample = self.transport_sample;
2502 snapshot.tempo_bpm = self.tempo_bpm;
2503 snapshot.playing = self.playing;
2504 snapshot.transport_running = self.transport_running;
2505 snapshot.tsig_num = self.tsig_num;
2506 snapshot.tsig_denom = self.tsig_denom;
2507 self.transport_snapshot_producer.publish();
2508 }
2509
2510 pub(crate) async fn handle_request(&mut self, a: Action) {
2511 match a {
2512 Action::Log { source, message } => {
2513 self.notify_clients(Ok(Action::Log { source, message }))
2514 .await;
2515 }
2516 Action::Undo => {
2517 let actions = match self.history.undo() {
2518 Some(actions) => actions,
2519 None => {
2520 self.notify_clients(Ok(Action::Undo)).await;
2521 self.notify_clients(Ok(Action::HistoryState {
2522 dirty: self.history.is_dirty(),
2523 }))
2524 .await;
2525 return;
2526 }
2527 };
2528
2529 let was_suspended = self.history_suspended;
2530 self.history_suspended = true;
2531 for action in actions {
2532 self.handle_request_inner(action, false).await;
2533 }
2534 self.history_suspended = was_suspended;
2535 self.notify_clients(Ok(Action::Undo)).await;
2536 self.notify_clients(Ok(Action::HistoryState {
2537 dirty: self.history.is_dirty(),
2538 }))
2539 .await;
2540 }
2541 Action::Redo => {
2542 let actions = match self.history.redo() {
2543 Some(actions) => actions,
2544 None => {
2545 self.notify_clients(Ok(Action::Redo)).await;
2546 self.notify_clients(Ok(Action::HistoryState {
2547 dirty: self.history.is_dirty(),
2548 }))
2549 .await;
2550 return;
2551 }
2552 };
2553
2554 let was_suspended = self.history_suspended;
2555 self.history_suspended = true;
2556 for action in actions {
2557 self.handle_request_inner(action, false).await;
2558 }
2559 self.history_suspended = was_suspended;
2560 self.notify_clients(Ok(Action::Redo)).await;
2561 self.notify_clients(Ok(Action::HistoryState {
2562 dirty: self.history.is_dirty(),
2563 }))
2564 .await;
2565 }
2566 Action::ApplyGroupedActions(actions) => {
2567 self.handle_request_inner(Action::BeginHistoryGroup, true)
2568 .await;
2569 for action in actions {
2570 self.handle_request_inner(action, true).await;
2571 }
2572 self.handle_request_inner(Action::EndHistoryGroup, true)
2573 .await;
2574 }
2575 Action::Session(_) => {
2576 self.handle_request_inner(a, false).await;
2577 }
2578 other => {
2579 self.handle_request_inner(other, true).await;
2580 }
2581 }
2582 self.publish_state_snapshot();
2583 }
2584
2585 pub(crate) async fn handle_quit(&mut self, a: Action) {
2586 self.flush_recordings().await;
2587 if let Some(mut worker) = self.hw_worker.take() {
2595 let panic_events = self.panic_events_for_all_hw_midi_outputs();
2598 if !panic_events.is_empty() {
2599 let _ = worker.tx.send(Message::HWMidiOutEvents(panic_events)).await;
2600 }
2601 if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2604 error!("Error sending quit message to HW worker: {e}");
2605 }
2606 if let Some(handle) = worker.handle.take() {
2607 handle
2608 .await
2609 .unwrap_or_else(|e| error!("Error waiting for HW worker to quit: {e}"));
2610 }
2611 }
2612 if let Some(hw) = self.hw_driver.as_mut() {
2618 hw.close_fds();
2619 }
2620 if let Some(midi_hub) = self.midi_hub.as_mut() {
2621 midi_hub.close_all();
2622 }
2623 self.hw_driver = None;
2624 self.hw_driver_info = None;
2625 self.hw_input_ports.clear();
2626 self.hw_output_ports.clear();
2627 self.notify_clients(Ok(Action::Quit)).await;
2628 self.ready_workers.clear();
2629 while !self.workers.is_empty() {
2630 let mut worker = self.workers.remove(0);
2631 if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2632 error!("Error sending quit message to worker: {e}");
2633 }
2634 if let Some(handle) = worker.handle.take() {
2635 handle
2636 .await
2637 .unwrap_or_else(|e| error!("Error waiting for worker to quit: {e}"));
2638 }
2639 }
2640 #[cfg(unix)]
2641 {
2642 self.jack_runtime = None;
2643 }
2644 self.osc_server = None;
2645 }
2646
2647 #[inline]
2648 pub(crate) fn box_bool<'a>(
2649 fut: impl std::future::Future<Output = bool> + Send + 'a,
2650 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
2651 Box::pin(fut)
2652 }
2653
2654 pub(crate) async fn handle_request_inner(
2655 &mut self,
2656 mut action_to_process: Action,
2657 record_history: bool,
2658 ) {
2659 let a = action_to_process.clone();
2660 let suppress_timing_history = self.playing
2661 && matches!(
2662 &action_to_process,
2663 Action::SetTempo(_) | Action::SetTimeSignature { .. } | Action::SetTempoMap { .. }
2664 );
2665 let mut inverse_actions = self.prepare_inverse_actions(
2666 &action_to_process,
2667 record_history,
2668 suppress_timing_history,
2669 );
2670
2671 match action_to_process {
2672 Action::Play => {
2673 if Self::box_bool(self.handle_play(a.clone())).await {
2674 return;
2675 }
2676 }
2677 Action::Pause => {
2678 if Self::box_bool(self.handle_pause(a.clone())).await {
2679 return;
2680 }
2681 }
2682 Action::Stop => {
2683 if Self::box_bool(self.handle_stop(a.clone())).await {
2684 return;
2685 }
2686 }
2687 Action::SessionPlay => {
2688 if Self::box_bool(self.handle_session_play(a.clone())).await {
2689 return;
2690 }
2691 }
2692 Action::JumpToEnd => {
2693 self.transport_sample = self.normalize_transport_sample(self.session_end_sample());
2694 self.bump_prepare_generation();
2695 self.publish_transport_snapshot();
2696 self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
2697 .await;
2698 }
2699 Action::Panic => {
2700 if Self::box_bool(self.handle_panic(a.clone())).await {
2701 return;
2702 }
2703 }
2704 Action::Session(ref session_action) => {
2705 self.handle_session_action(session_action.clone()).await;
2706 }
2707 Action::SessionRuntimeReport { .. } => {}
2708 Action::SessionMidiLearnTriggered { .. } => {}
2709 Action::SetClipPlaybackEnabled(enabled) => {
2710 self.clip_playback_enabled = enabled;
2711 self.bump_prepare_generation();
2712 for track in self.state_snapshot.load_full().tracks.values() {
2713 track.lock().set_clip_playback_enabled(enabled);
2714 }
2715 }
2716 Action::SetSessionClipPlaybackEnabled(enabled) => {
2717 self.session_clip_playback_enabled = enabled;
2718 self.bump_prepare_generation();
2719 for track in self.state_snapshot.load_full().tracks.values() {
2720 track.lock().set_session_clip_playback_enabled(enabled);
2721 }
2722 }
2723 Action::TransportPosition(..) => {
2724 if Self::box_bool(self.handle_transport_position(a.clone())).await {
2725 return;
2726 }
2727 }
2728 Action::SetLoopEnabled(enabled) => {
2729 self.loop_enabled = enabled && self.loop_range_samples.is_some();
2730 self.bump_prepare_generation();
2731 self.notified_loop_wrap_sample = None;
2732 }
2733 Action::SetLoopRange(..) => {
2734 if Self::box_bool(self.handle_set_loop_range(a.clone())).await {
2735 return;
2736 }
2737 }
2738 Action::SetPunchEnabled(enabled) => {
2739 self.punch_enabled = enabled && self.punch_range_samples.is_some();
2740 }
2741 Action::SetPunchRange(range) => {
2742 self.punch_range_samples = range.and_then(|(start, end)| {
2743 if end > start {
2744 Some((start, end))
2745 } else {
2746 None
2747 }
2748 });
2749 self.punch_enabled = self.punch_range_samples.is_some();
2750 }
2751 Action::SetMetronomeEnabled(enabled) => {
2752 self.metronome_enabled = enabled;
2753 if enabled {
2754 self.ensure_metronome_track().await;
2755 }
2756 if let Some(track) = self
2757 .state_snapshot
2758 .load_full()
2759 .tracks
2760 .get(Self::METRONOME_TRACK)
2761 .cloned()
2762 {
2763 track.lock().set_metronome_enabled(enabled);
2764 }
2765 }
2766 Action::SetTempo(bpm) => {
2767 self.tempo_bpm = bpm.max(1.0);
2768 self.bump_prepare_generation();
2769 self.publish_transport_snapshot();
2770 }
2771 Action::SetTimeSignature {
2772 numerator,
2773 denominator,
2774 } => {
2775 self.tsig_num = numerator.max(1);
2776 self.tsig_denom = denominator.max(1);
2777 self.bump_prepare_generation();
2778 self.publish_transport_snapshot();
2779 }
2780 Action::SetTempoMap {
2781 ref tempo_points,
2782 ref time_signature_points,
2783 } => {
2784 self.tempo_points = tempo_points.clone();
2785 self.time_signature_points = time_signature_points.clone();
2786 self.update_global_tempo_from_map();
2787 self.publish_transport_snapshot();
2788 }
2789 Action::SetOscEnabled(enabled) => {
2790 if let Err(err) = self.set_osc_enabled_with(enabled, OscServer::start) {
2791 self.notify_clients(Err(err)).await;
2792 }
2793 }
2794 Action::SetRecordEnabled(..) => {
2795 if Self::box_bool(self.handle_set_record_enabled(a.clone())).await {
2796 return;
2797 }
2798 }
2799 Action::SetModulators(ref modulators) => {
2800 self.modulators = modulators.clone();
2801 let echoes = self.apply_modulators(self.active_transport_sample());
2802 for action in echoes {
2803 self.notify_clients(Ok(action)).await;
2804 }
2805 }
2806 Action::SetTrackAutomationLanes {
2807 ref track_name,
2808 ref lanes,
2809 mode,
2810 } => {
2811 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
2812 let mut track = track.lock();
2813 track.automation_lanes = lanes.clone();
2814 track.set_automation_mode(mode);
2815 }
2816 }
2817 Action::TrackAutomationToggleLane { .. } => {
2818 if Self::box_bool(self.handle_track_automation_toggle_lane(a.clone())).await {
2819 return;
2820 }
2821 }
2822 Action::TrackAutomationInsertPoint { .. } => {
2823 if Self::box_bool(self.handle_track_automation_insert_point(a.clone())).await {
2824 return;
2825 }
2826 }
2827 Action::TrackAutomationDeletePoint { .. } => {
2828 if Self::box_bool(self.handle_track_automation_delete_point(a.clone())).await {
2829 return;
2830 }
2831 }
2832 Action::TrackAutomationSetMode {
2833 ref track_name,
2834 mode,
2835 } => {
2836 if let Some(track) = self
2837 .state_snapshot
2838 .load_full()
2839 .tracks
2840 .get(track_name)
2841 .cloned()
2842 {
2843 track.lock().set_automation_mode(mode);
2844 }
2845 }
2846 Action::RequestTrackList => {
2847 let names: Vec<String> = self
2848 .state_snapshot
2849 .load_full()
2850 .tracks
2851 .keys()
2852 .cloned()
2853 .collect();
2854 self.notify_clients(Ok(Action::TrackList(names))).await;
2855 }
2856 Action::TrackList(_) => {}
2857 Action::RequestTransportState => {
2858 self.notify_clients(Ok(Action::TransportState {
2859 sample: self.transport_sample,
2860 tempo_bpm: self.tempo_bpm,
2861 playing: self.playing,
2862 paused: !self.transport_running && self.playing,
2863 tsig_num: self.tsig_num,
2864 tsig_denom: self.tsig_denom,
2865 }))
2866 .await;
2867 }
2868 Action::TransportState { .. } => {}
2869 Action::SetStepRecording(enabled) => {
2870 self.step_recording_enabled = enabled;
2871 }
2872 Action::BeginHistoryGroup if self.history_group.is_none() => {
2873 self.history_group = Some(UndoEntry {
2874 forward_actions: vec![],
2875 inverse_actions: vec![],
2876 });
2877 }
2878 Action::EndHistoryGroup => {
2879 if let Some(mut group) = self.history_group.take()
2880 && !group.forward_actions.is_empty()
2881 && !group.inverse_actions.is_empty()
2882 {
2883 let mut add_tracks = Vec::new();
2884 let mut connections = Vec::new();
2885 let mut rest = Vec::new();
2886 for action in group.inverse_actions {
2887 if matches!(action, Action::AddTrack { .. }) {
2888 add_tracks.push(action);
2889 } else if matches!(action, Action::Connect { .. }) {
2890 connections.push(action);
2891 } else {
2892 rest.push(action);
2893 }
2894 }
2895 group.inverse_actions = add_tracks;
2896 group.inverse_actions.extend(rest);
2897 group.inverse_actions.extend(connections);
2898 self.history.record(group);
2899 }
2900 }
2901 Action::SetSessionPath(ref path) => {
2902 self.session_dir = Some(Path::new(path).to_path_buf());
2903 self.ensure_session_subdirs();
2904 #[cfg(unix)]
2905 let _lv2_dir = self.session_plugins_dir();
2906 for track in self.state_snapshot.load_full().tracks.values() {
2907 track.lock().set_session_base_dir(self.session_dir.clone());
2908 }
2909 }
2910 Action::MarkHistorySavePoint => {
2911 self.history.mark_save_point();
2912 self.notify_clients(Ok(Action::HistoryState {
2913 dirty: self.history.is_dirty(),
2914 }))
2915 .await;
2916 }
2917 Action::ClearHistory => {
2918 self.history.clear();
2919 self.history.mark_save_point();
2920 }
2921 Action::BeginSessionRestore => {
2922 self.history_suspended = true;
2923 self.history.clear();
2924 }
2925 Action::EndSessionRestore => {
2926 self.history.clear();
2927 self.history_suspended = false;
2928 self.preload_track_clips_spawn();
2929 }
2930 Action::Quit => {
2931 self.handle_quit(a.clone()).await;
2932 return;
2933 }
2934 Action::AddTrack { .. } => {
2935 self.handle_add_track(a.clone()).await;
2936 }
2937 Action::TrackAddAudioInput(..) => {
2938 if Self::box_bool(self.handle_track_add_audio_input(a.clone())).await {
2939 return;
2940 }
2941 }
2942 Action::TrackAddAudioOutput(..) => {
2943 if Self::box_bool(self.handle_track_add_audio_output(a.clone())).await {
2944 return;
2945 }
2946 }
2947 Action::TrackRemoveAudioInput(..) => {
2948 if Self::box_bool(self.handle_track_remove_audio_input(a.clone())).await {
2949 return;
2950 }
2951 }
2952 Action::TrackRemoveAudioOutput(..) => {
2953 if Self::box_bool(self.handle_track_remove_audio_output(a.clone())).await {
2954 return;
2955 }
2956 }
2957 Action::RenameTrack { .. } => {
2958 if Self::box_bool(self.handle_rename_track(a.clone())).await {
2959 return;
2960 }
2961 }
2962 Action::RemoveTrack(ref name) => {
2963 self.handle_remove_track(name.clone(), record_history).await;
2964 inverse_actions = None;
2965 }
2966 Action::TrackLevel(ref name, level) => {
2967 if name == "hw:out" {
2968 self.hw_out_level_db = level;
2969 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2970 track.lock().set_level(level);
2971 }
2972 }
2973 Action::TrackBalance(ref name, balance) => {
2974 if name == "hw:out" {
2975 self.hw_out_balance = balance.clamp(-1.0, 1.0);
2976 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2977 track.lock().set_balance(balance);
2978 }
2979 }
2980 Action::TrackAutomationLevel(ref name, level) => {
2981 tracing::debug!(%name, level, "engine received TrackAutomationLevel");
2982 if name == "hw:out" {
2983 self.hw_out_level_db = level;
2984 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2985 track.lock().set_level(level);
2986 }
2987 }
2988 Action::TrackAutomationBalance(ref name, balance) => {
2989 if name == "hw:out" {
2990 self.hw_out_balance = balance.clamp(-1.0, 1.0);
2991 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2992 track.lock().set_balance(balance);
2993 }
2994 }
2995 Action::TrackMidiCc { .. } => {
2996 if Self::box_bool(self.handle_track_midi_cc(a.clone())).await {
2997 return;
2998 }
2999 }
3000 Action::RequestMeterSnapshot => {
3001 self.update_meter_decay_after_stop();
3002 self.notify_clients(Ok(Action::MeterSnapshot {
3003 hw_out_db: self.latest_hw_out_meter_db.clone(),
3004 track_meters: self.latest_track_meter_snapshot.clone(),
3005 }))
3006 .await;
3007 return;
3008 }
3009 Action::TrackMeters { .. } => {}
3010 Action::MeterSnapshot { .. } => {}
3011 Action::TrackToggleArm(..) => {
3012 if Self::box_bool(self.handle_track_toggle_arm(a.clone())).await {
3013 return;
3014 }
3015 }
3016 Action::TrackToggleMute(ref name) => {
3017 if name == "hw:out" {
3018 self.hw_out_muted = !self.hw_out_muted;
3019 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3020 track.lock().mute();
3021 }
3022 }
3023 Action::TrackTogglePhase(ref name) => {
3024 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3025 track.lock().invert_phase();
3026 }
3027 }
3028 Action::TrackToggleSolo(ref name) => {
3029 if name == "hw:out" {
3030 return;
3031 }
3032 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3033 track.lock().solo();
3034 }
3035 }
3036 Action::TrackToggleMaster(ref name) => {
3037 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3038 track.lock().toggle_master();
3039 }
3040 }
3041 Action::TrackToggleInputMonitor {
3042 ref track_name,
3043 lane,
3044 } => {
3045 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3046 track.lock().toggle_input_monitor(lane);
3047 }
3048 }
3049 Action::TrackToggleDiskMonitor {
3050 ref track_name,
3051 lane,
3052 } => {
3053 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3054 track.lock().toggle_disk_monitor(lane);
3055 }
3056 }
3057 Action::TrackToggleMidiInputMonitor {
3058 ref track_name,
3059 lane,
3060 } => {
3061 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3062 track.lock().toggle_midi_input_monitor(lane);
3063 }
3064 }
3065 Action::TrackToggleMidiDiskMonitor {
3066 ref track_name,
3067 lane,
3068 } => {
3069 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3070 track.lock().toggle_midi_disk_monitor(lane);
3071 }
3072 }
3073 Action::TrackSetColor {
3074 ref track_name,
3075 color,
3076 } => {
3077 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3078 track.lock().color = color;
3079 }
3080 }
3081 Action::TrackArmMidiLearn {
3082 ref track_name,
3083 target,
3084 } => {
3085 if let Err(e) = self.track_handle_or_err(track_name) {
3086 self.notify_clients(Err(e)).await;
3087 return;
3088 }
3089 self.pending_midi_learn = Some((track_name.clone(), target, None));
3090 }
3091 Action::GlobalArmMidiLearn { target } => {
3092 self.pending_global_midi_learn = Some(target);
3093 }
3094 Action::SessionArmMidiLearn { ref target } => {
3095 self.pending_session_midi_learn = Some(target.clone());
3096 }
3097 Action::TrackSetMidiLearnBinding { .. } => {
3098 if Self::box_bool(self.handle_track_set_midi_learn_binding(a.clone())).await {
3099 return;
3100 }
3101 }
3102 Action::SetGlobalMidiLearnBinding { .. } => {
3103 if Self::box_bool(self.handle_set_global_midi_learn_binding(a.clone())).await {
3104 return;
3105 }
3106 }
3107 Action::SetSessionMidiLearnBinding { .. } => {
3108 if Self::box_bool(self.handle_set_session_midi_learn_binding(a.clone())).await {
3109 return;
3110 }
3111 }
3112 Action::TrackSetFolder { .. } => {
3113 if Self::box_bool(self.handle_track_set_folder(a.clone())).await {
3114 return;
3115 }
3116 }
3117 Action::TrackSetParent {
3118 ref track_name,
3119 ref parent_name,
3120 } => {
3121 self.handle_track_set_parent(track_name.as_str(), parent_name.as_deref())
3122 .await;
3123 }
3124 Action::TrackToggleFolder { .. } => {
3125 if Self::box_bool(self.handle_track_toggle_folder(a.clone())).await {
3126 return;
3127 }
3128 }
3129 Action::TrackSetMidiLaneChannel { .. } => {
3130 if Self::box_bool(self.handle_track_set_midi_lane_channel(a.clone())).await {
3131 return;
3132 }
3133 }
3134 Action::TrackSetMpeZone { .. } => {
3135 if Self::box_bool(self.handle_track_set_mpe_zone(a.clone())).await {
3136 return;
3137 }
3138 }
3139 Action::TrackSetMpePitchBendSensitivity { .. } => {
3140 if Self::box_bool(self.handle_track_set_mpe_pitch_bend_sensitivity(a.clone())).await
3141 {
3142 return;
3143 }
3144 }
3145 Action::TrackSetFrozen { .. } => {
3146 if Self::box_bool(self.handle_track_set_frozen(a.clone())).await {
3147 return;
3148 }
3149 }
3150 Action::TrackSetSessionSlot { .. } => {
3151 if Self::box_bool(self.handle_track_set_session_slot(a.clone())).await {
3152 return;
3153 }
3154 }
3155 Action::TrackSetSessionSlotPlayEnabled { .. } => {
3156 if self
3157 .handle_track_set_session_slot_play_enabled(a.clone())
3158 .await
3159 {
3160 return;
3161 }
3162 }
3163 Action::TrackSetSessionSlotStopEnabled { .. } => {
3164 if self
3165 .handle_track_set_session_slot_stop_enabled(a.clone())
3166 .await
3167 {
3168 return;
3169 }
3170 }
3171 Action::TrackOfflineBounce { .. } => {
3172 self.handle_track_offline_bounce(action_to_process).await;
3173 return;
3174 }
3175 Action::TrackOfflineBounceCancel { .. } => {}
3176 Action::TrackOfflineBounceCancelAll => {}
3177 Action::TrackOfflineBounceCanceled { .. } => {}
3178 Action::TrackOfflineBounceProgress { .. } => {}
3179 Action::PianoKey {
3180 ref track_name,
3181 note,
3182 velocity,
3183 on,
3184 } => {
3185 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3186 let status = if on { 0x90 } else { 0x80 };
3187 let event = MidiEvent::new(0, vec![status, note.min(127), velocity.min(127)]);
3188 track.lock().push_hw_midi_events(&[event]);
3189 }
3190 }
3191 Action::ModifyMidiNotes { .. }
3192 | Action::ModifyMidiControllers { .. }
3193 | Action::DeleteMidiControllers { .. }
3194 | Action::InsertMidiControllers { .. }
3195 | Action::DeleteMidiNotes { .. }
3196 | Action::InsertMidiNotes { .. } => {
3197 if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3198 self.notify_clients(Err(e)).await;
3199 return;
3200 }
3201 }
3202 Action::SetMidiSysExEvents { .. } => {
3203 if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3204 self.notify_clients(Err(e)).await;
3205 return;
3206 }
3207 }
3208 Action::TrackClearDefaultPassthrough { .. } => {
3209 if Self::box_bool(self.handle_track_clear_default_passthrough(a.clone())).await {
3210 return;
3211 }
3212 }
3213 Action::TrackClearPlugins { .. } => {
3214 if Self::box_bool(self.handle_track_clear_plugins(a.clone())).await {
3215 return;
3216 }
3217 }
3218 #[cfg(unix)]
3219 Action::TrackSetLv2PluginState { .. } => {
3220 if Self::box_bool(self.handle_track_set_lv2_plugin_state(a.clone())).await {
3221 return;
3222 }
3223 }
3224 #[cfg(unix)]
3225 Action::ClipSetLv2PluginState { ref track_name, .. } => {
3226 self.notify_clients(Err(format!(
3227 "Track '{}': clip LV2 plugin state changes are not supported",
3228 track_name
3229 )))
3230 .await;
3231 }
3232 Action::TrackGetClapNoteNames { .. } => {
3233 if Self::box_bool(self.handle_track_get_clap_note_names(a.clone())).await {
3234 return;
3235 }
3236 }
3237 #[cfg(unix)]
3238 Action::TrackGetLv2Midnam { .. } => {
3239 if Self::box_bool(self.handle_track_get_lv2_midnam(a.clone())).await {
3240 return;
3241 }
3242 }
3243 Action::TrackGetPluginGraph { .. } => {
3244 if Self::box_bool(self.handle_track_get_plugin_graph(a.clone())).await {
3245 return;
3246 }
3247 }
3248 Action::TrackPluginGraph { .. } => {}
3249 Action::TrackConnectPluginAudio { .. } => {
3250 if Self::box_bool(self.handle_track_connect_plugin_audio(a.clone())).await {
3251 return;
3252 }
3253 }
3254 Action::TrackConnectPluginMidi { .. } => {
3255 if Self::box_bool(self.handle_track_connect_plugin_midi(a.clone())).await {
3256 return;
3257 }
3258 }
3259 Action::TrackDisconnectPluginAudio { .. } => {
3260 if Self::box_bool(self.handle_track_disconnect_plugin_audio(a.clone())).await {
3261 return;
3262 }
3263 }
3264 Action::TrackDisconnectPluginMidi { .. } => {
3265 if Self::box_bool(self.handle_track_disconnect_plugin_midi(a.clone())).await {
3266 return;
3267 }
3268 }
3269 Action::TrackConnectAudio { .. } => {
3270 if Self::box_bool(self.handle_track_connect_audio(a.clone())).await {
3271 return;
3272 }
3273 }
3274 Action::TrackDisconnectAudio { .. } => {
3275 if Self::box_bool(self.handle_track_disconnect_audio(a.clone())).await {
3276 return;
3277 }
3278 }
3279 Action::TrackConnectMidi { .. } => {
3280 if Self::box_bool(self.handle_track_connect_midi(a.clone())).await {
3281 return;
3282 }
3283 }
3284 Action::TrackDisconnectMidi { .. } => {
3285 if Self::box_bool(self.handle_track_disconnect_midi(a.clone())).await {
3286 return;
3287 }
3288 }
3289 #[cfg(unix)]
3290 Action::ListLv2Plugins => {
3291 if Self::box_bool(self.handle_list_lv2_plugins(a.clone())).await {
3292 return;
3293 }
3294 }
3295 #[cfg(unix)]
3296 Action::Lv2Plugins(_) => {}
3297 #[cfg(unix)]
3298 Action::Lv2PluginsUnavailable { .. } => {}
3299 Action::ListVst3Plugins => {
3300 if Self::box_bool(self.handle_list_vst3_plugins(a.clone())).await {
3301 return;
3302 }
3303 }
3304 Action::Vst3Plugins(_) => {}
3305 Action::Vst3PluginsUnavailable { .. } => {}
3306 Action::ListClapPlugins => {
3307 if Self::box_bool(self.handle_list_clap_plugins(a.clone())).await {
3308 return;
3309 }
3310 }
3311 Action::ListClapPluginsWithCapabilities => {
3312 if self
3313 .handle_list_clap_plugins_with_capabilities(a.clone())
3314 .await
3315 {
3316 return;
3317 }
3318 }
3319 Action::ClapPlugins(_) => {}
3320 Action::ClapPluginsUnavailable { .. } => {}
3321 Action::TrackLoadClapPlugin {
3322 ref track_name,
3323 ref plugin_id,
3324 instance_id,
3325 } => {
3326 if self
3327 .handle_track_load_clap_plugin(
3328 track_name.as_str(),
3329 plugin_id.as_str(),
3330 instance_id,
3331 )
3332 .await
3333 {
3334 return;
3335 }
3336 }
3337 Action::TrackUnloadClapPlugin {
3338 ref track_name,
3339 ref plugin_id,
3340 } => {
3341 if self
3342 .handle_track_unload_clap_plugin(track_name.as_str(), plugin_id.as_str())
3343 .await
3344 {
3345 return;
3346 }
3347 }
3348 Action::TrackUnloadClapPluginInstance {
3349 ref track_name,
3350 instance_id,
3351 } => {
3352 if self
3353 .handle_track_unload_clap_plugin_instance(track_name.as_str(), instance_id)
3354 .await
3355 {
3356 return;
3357 }
3358 }
3359 Action::TrackShowClapGui { .. } => {
3360 if Self::box_bool(self.handle_track_show_clap_gui(a.clone())).await {
3361 return;
3362 }
3363 }
3364 Action::ClipShowClapGui { .. } => {
3365 if Self::box_bool(self.handle_clip_show_clap_gui(a.clone())).await {
3366 return;
3367 }
3368 }
3369 Action::TrackLoadVst3Plugin {
3370 ref track_name,
3371 ref plugin_id,
3372 instance_id,
3373 } => {
3374 if self
3375 .handle_track_load_vst3_plugin(
3376 track_name.as_str(),
3377 plugin_id.as_str(),
3378 instance_id,
3379 )
3380 .await
3381 {
3382 return;
3383 }
3384 }
3385 Action::TrackUnloadVst3Plugin {
3386 ref track_name,
3387 ref plugin_id,
3388 } => {
3389 if self
3390 .handle_track_unload_vst3_plugin(track_name.as_str(), plugin_id.as_str())
3391 .await
3392 {
3393 return;
3394 }
3395 }
3396 Action::TrackUnloadVst3PluginInstance {
3397 ref track_name,
3398 instance_id,
3399 } => {
3400 if self
3401 .handle_track_unload_vst3_plugin_instance(track_name.as_str(), instance_id)
3402 .await
3403 {
3404 return;
3405 }
3406 }
3407 Action::TrackShowVst3Gui { .. } => {
3408 if Self::box_bool(self.handle_track_show_vst3_gui(a.clone())).await {
3409 return;
3410 }
3411 }
3412 Action::ClipShowVst3Gui { .. } => {
3413 if Self::box_bool(self.handle_clip_show_vst3_gui(a.clone())).await {
3414 return;
3415 }
3416 }
3417 #[cfg(unix)]
3418 Action::TrackLoadLv2Plugin {
3419 ref track_name,
3420 ref plugin_uri,
3421 instance_id,
3422 } => {
3423 if self
3424 .handle_track_load_lv2_plugin(
3425 track_name.as_str(),
3426 plugin_uri.as_str(),
3427 instance_id,
3428 )
3429 .await
3430 {
3431 return;
3432 }
3433 }
3434 #[cfg(unix)]
3435 Action::TrackUnloadLv2Plugin {
3436 ref track_name,
3437 ref plugin_uri,
3438 } => {
3439 if self
3440 .handle_track_unload_lv2_plugin(track_name.as_str(), plugin_uri.as_str())
3441 .await
3442 {
3443 return;
3444 }
3445 }
3446 #[cfg(unix)]
3447 Action::TrackUnloadLv2PluginInstance {
3448 ref track_name,
3449 instance_id,
3450 } => {
3451 if self
3452 .handle_track_unload_lv2_plugin_instance(track_name.as_str(), instance_id)
3453 .await
3454 {
3455 return;
3456 }
3457 }
3458 #[cfg(unix)]
3459 Action::TrackShowLv2Gui { .. } => {
3460 if Self::box_bool(self.handle_track_show_lv2_gui(a.clone())).await {
3461 return;
3462 }
3463 }
3464 #[cfg(unix)]
3465 Action::ClipShowLv2Gui { .. } => {
3466 if Self::box_bool(self.handle_clip_show_lv2_gui(a.clone())).await {
3467 return;
3468 }
3469 }
3470 Action::TrackSetPluginResourceDir { .. } => {
3471 if Self::box_bool(self.handle_track_set_plugin_resource_dir(a.clone())).await {
3472 return;
3473 }
3474 }
3475 Action::TrackClapCollectResources { .. } => {
3476 if Self::box_bool(self.handle_track_clap_collect_resources(a.clone())).await {
3477 return;
3478 }
3479 }
3480 Action::ClipSetPluginResourceDir { .. } => {
3481 if Self::box_bool(self.handle_clip_set_plugin_resource_dir(a.clone())).await {
3482 return;
3483 }
3484 }
3485 Action::ClipClapCollectResources { .. } => {
3486 if Self::box_bool(self.handle_clip_clap_collect_resources(a.clone())).await {
3487 return;
3488 }
3489 }
3490 Action::TrackSetClapParameter { .. } => {
3491 if Self::box_bool(self.handle_track_set_clap_parameter(a.clone())).await {
3492 return;
3493 }
3494 }
3495 Action::ClipSetClapParameter { .. } => {
3496 if Self::box_bool(self.handle_clip_set_clap_parameter(a.clone())).await {
3497 return;
3498 }
3499 }
3500 Action::ClipGetClapParameters { .. } => {
3501 if Self::box_bool(self.handle_clip_get_clap_parameters(a.clone())).await {
3502 return;
3503 }
3504 }
3505 Action::ClipClapParameters { .. } => {}
3506 Action::TrackSetClapParameterAt { .. } => {
3507 if Self::box_bool(self.handle_track_set_clap_parameter_at(a.clone())).await {
3508 return;
3509 }
3510 }
3511 Action::TrackBeginClapParameterEdit { .. } => {
3512 if Self::box_bool(self.handle_track_begin_clap_parameter_edit(a.clone())).await {
3513 return;
3514 }
3515 }
3516 Action::TrackEndClapParameterEdit { .. } => {
3517 if Self::box_bool(self.handle_track_end_clap_parameter_edit(a.clone())).await {
3518 return;
3519 }
3520 }
3521 Action::TrackGetClapParameters { .. } => {
3522 if Self::box_bool(self.handle_track_get_clap_parameters(a.clone())).await {
3523 return;
3524 }
3525 }
3526 Action::TrackClapParameters { .. } => {}
3527 Action::TrackClapSnapshotState { .. } => {
3528 if Self::box_bool(self.handle_track_clap_snapshot_state(a.clone())).await {
3529 return;
3530 }
3531 }
3532 Action::ClipClapSnapshotState { .. } => {
3533 if Self::box_bool(self.handle_clip_clap_snapshot_state(a.clone())).await {
3534 return;
3535 }
3536 }
3537 Action::TrackClapStateSnapshot { .. } => {}
3538 Action::ClipClapStateSnapshot { .. } => {}
3539 Action::TrackClapStateDirty { .. } => {}
3540 Action::ClipClapStateDirty { .. } => {}
3541 Action::TrackClapRestoreState { .. } => {
3542 if Self::box_bool(self.handle_track_clap_restore_state(a.clone())).await {
3543 return;
3544 }
3545 }
3546 Action::ClipClapRestoreState { .. } => {
3547 if Self::box_bool(self.handle_clip_clap_restore_state(a.clone())).await {
3548 return;
3549 }
3550 }
3551 Action::TrackSnapshotAllClapStates { .. } => {
3552 if Self::box_bool(self.handle_track_snapshot_all_clap_states(a.clone())).await {
3553 return;
3554 }
3555 }
3556 Action::TrackSnapshotAllClapStatesDone { .. } => {}
3557 Action::TrackGetVst3Graph { .. } => {
3558 if Self::box_bool(self.handle_track_get_vst3_graph(a.clone())).await {
3559 return;
3560 }
3561 }
3562 Action::TrackVst3Graph { .. } => {}
3563 Action::TrackSetVst3Parameter { .. } => {
3564 if Self::box_bool(self.handle_track_set_vst3_parameter(a.clone())).await {
3565 return;
3566 }
3567 }
3568 Action::ClipSetVst3Parameter { .. } => {
3569 if Self::box_bool(self.handle_clip_set_vst3_parameter(a.clone())).await {
3570 return;
3571 }
3572 }
3573 Action::TrackSetPluginBypassed { .. } => {
3574 if Self::box_bool(self.handle_track_set_plugin_bypassed(a.clone())).await {
3575 return;
3576 }
3577 }
3578 Action::TrackGetVst3Parameters { .. } => {
3579 if Self::box_bool(self.handle_track_get_vst3_parameters(a.clone())).await {
3580 return;
3581 }
3582 }
3583 Action::TrackVst3Parameters { .. } => {}
3584 Action::ClipGetVst3Parameters { .. } => {
3585 if Self::box_bool(self.handle_clip_get_vst3_parameters(a.clone())).await {
3586 return;
3587 }
3588 }
3589 Action::ClipVst3Parameters { .. } => {}
3590 #[cfg(unix)]
3591 Action::TrackSetLv2ControlValue { .. } => {
3592 if Self::box_bool(self.handle_track_set_lv2_control_value(a.clone())).await {
3593 return;
3594 }
3595 }
3596 #[cfg(unix)]
3597 Action::ClipSetLv2ControlValue { .. } => {
3598 if Self::box_bool(self.handle_clip_set_lv2_control_value(a.clone())).await {
3599 return;
3600 }
3601 }
3602 #[cfg(unix)]
3603 Action::TrackGetLv2PluginControls { .. } => {
3604 if Self::box_bool(self.handle_track_get_lv2_plugin_controls(a.clone())).await {
3605 return;
3606 }
3607 }
3608 #[cfg(unix)]
3609 Action::ClipGetLv2PluginControls { .. } => {
3610 if Self::box_bool(self.handle_clip_get_lv2_plugin_controls(a.clone())).await {
3611 return;
3612 }
3613 }
3614 #[cfg(unix)]
3615 Action::TrackLv2SnapshotState { .. } => {
3616 if Self::box_bool(self.handle_track_lv2_snapshot_state(a.clone())).await {
3617 return;
3618 }
3619 }
3620 #[cfg(unix)]
3621 Action::ClipLv2SnapshotState { .. } => {
3622 if Self::box_bool(self.handle_clip_lv2_snapshot_state(a.clone())).await {
3623 return;
3624 }
3625 }
3626 Action::TrackVst3SnapshotState { .. } => {
3627 if Self::box_bool(self.handle_track_vst3_snapshot_state(a.clone())).await {
3628 return;
3629 }
3630 }
3631 Action::ClipVst3SnapshotState { .. } => {
3632 if Self::box_bool(self.handle_clip_vst3_snapshot_state(a.clone())).await {
3633 return;
3634 }
3635 }
3636 Action::TrackVst3StateSnapshot { .. } => {}
3637 Action::ClipVst3StateSnapshot { .. } => {}
3638 Action::TrackVst3RestoreState { .. } => {
3639 if Self::box_bool(self.handle_track_vst3_restore_state(a.clone())).await {
3640 return;
3641 }
3642 }
3643 Action::TrackConnectVst3Audio { .. } => {
3644 if Self::box_bool(self.handle_track_connect_vst3_audio(a.clone())).await {
3645 return;
3646 }
3647 }
3648 Action::TrackDisconnectVst3Audio { .. } => {
3649 if Self::box_bool(self.handle_track_disconnect_vst3_audio(a.clone())).await {
3650 return;
3651 }
3652 }
3653 Action::ClipMove { .. } => {
3654 self.handle_clip_move(a.clone()).await;
3655 }
3656 Action::AddClip { .. } => {
3657 if Self::box_bool(self.handle_add_clip(a.clone())).await {
3658 return;
3659 }
3660 }
3661 Action::AddGroupedClip { .. } => {
3662 if Self::box_bool(self.handle_add_grouped_clip(a.clone())).await {
3663 return;
3664 }
3665 }
3666 Action::RemoveClip {
3667 ref track_name,
3668 kind,
3669 ref clip_indices,
3670 } => {
3671 self.remove_clips_from_track(track_name, kind, clip_indices);
3672 }
3673 Action::MoveClipToUnused {
3674 ref track_name,
3675 kind,
3676 ref clip_indices,
3677 } => {
3678 self.move_clips_to_unused(track_name, kind, clip_indices);
3679 }
3680 Action::DeleteUnusedClips { ref clip_ids } => {
3681 self.delete_unused_clips(clip_ids);
3682 }
3683 Action::SetUnusedClips {
3684 ref audio,
3685 ref midi,
3686 } => {
3687 self.set_unused_clips(audio.clone(), midi.clone());
3688 }
3689 Action::RenameClip {
3690 ref track_name,
3691 kind,
3692 clip_index,
3693 ref new_name,
3694 } => {
3695 self.rename_clip_references(track_name, kind, clip_index, new_name);
3696 }
3697 Action::SetClipIdentity {
3698 ref track_name,
3699 kind,
3700 clip_index,
3701 ref new_id,
3702 ref new_name,
3703 } => {
3704 self.set_clip_identity(track_name, kind, clip_index, new_id, new_name);
3705 }
3706 Action::SetClipSourceName {
3707 ref track_name,
3708 kind,
3709 clip_index,
3710 ref name,
3711 } => {
3712 self.set_clip_source_name(track_name, clip_index, kind, name.clone());
3713 }
3714 Action::SetClipFade { .. } => {
3715 if Self::box_bool(self.handle_set_clip_fade(a.clone())).await {
3716 return;
3717 }
3718 }
3719 Action::SetClipBounds {
3720 ref track_name,
3721 clip_index,
3722 kind,
3723 start,
3724 length,
3725 offset,
3726 } => {
3727 self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3728 }
3729 Action::SyncClipBounds {
3730 ref track_name,
3731 clip_index,
3732 kind,
3733 start,
3734 length,
3735 offset,
3736 } => {
3737 self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3738 }
3739 Action::SetClipMuted {
3740 ref track_name,
3741 clip_index,
3742 kind,
3743 muted,
3744 } => {
3745 self.set_clip_muted(track_name, clip_index, kind, muted);
3746 }
3747 Action::SetClipReversed {
3748 ref track_name,
3749 clip_index,
3750 kind,
3751 reversed,
3752 } => {
3753 self.set_clip_reversed(track_name, clip_index, kind, reversed);
3754 }
3755 Action::SetClipGainDb {
3756 ref track_name,
3757 clip_index,
3758 kind,
3759 gain_db,
3760 } => {
3761 self.set_clip_gain_db(track_name, clip_index, kind, gain_db);
3762 }
3763 Action::SetClipPluginGraphJson {
3764 ref track_name,
3765 clip_index,
3766 ref plugin_graph_json,
3767 } => {
3768 self.set_clip_plugin_graph_json(track_name, clip_index, plugin_graph_json.clone());
3769 }
3770 Action::SetClipPitchCorrection { .. } => {
3771 if Self::box_bool(self.handle_set_clip_pitch_correction(a.clone())).await {
3772 return;
3773 }
3774 }
3775 Action::Connect {
3776 ref from_track,
3777 from_port,
3778 ref to_track,
3779 to_port,
3780 kind,
3781 } => {
3782 self.handle_connect(
3783 from_track.as_str(),
3784 from_port,
3785 to_track.as_str(),
3786 to_port,
3787 kind,
3788 )
3789 .await;
3790 }
3791 Action::Disconnect { .. } => {
3792 self.handle_disconnect(a.clone()).await;
3793 }
3794 Action::OpenAudioDevice { .. } => {
3795 let (done, updated) = self.handle_open_audio_device(a.clone()).await;
3796 if done {
3797 return;
3798 }
3799 if let Some(action) = updated {
3800 action_to_process = action;
3801 }
3802 }
3803 Action::JackAddAudioInputPort => {
3804 if Self::box_bool(self.handle_jack_add_audio_input_port(a.clone())).await {
3805 return;
3806 }
3807 }
3808 Action::JackRemoveAudioInputPort(_removed_port) => {
3809 if self
3810 .handle_jack_remove_audio_input_port(_removed_port, a.clone())
3811 .await
3812 {
3813 return;
3814 }
3815 }
3816 Action::JackAddAudioOutputPort => {
3817 if Self::box_bool(self.handle_jack_add_audio_output_port(a.clone())).await {
3818 return;
3819 }
3820 }
3821 Action::JackRemoveAudioOutputPort(_removed_port) => {
3822 if self
3823 .handle_jack_remove_audio_output_port(_removed_port, a.clone())
3824 .await
3825 {
3826 return;
3827 }
3828 }
3829 Action::JackGetGraph => {
3830 #[cfg(unix)]
3831 {
3832 match self
3833 .jack_runtime
3834 .as_ref()
3835 .ok_or(
3836 "JACK runtime is not active; open the JACK backend first".to_string(),
3837 )
3838 .and_then(|jack| jack.graph_info())
3839 {
3840 Ok(graph) => self.notify_clients(Ok(Action::JackGraph(graph))).await,
3841 Err(e) => self.notify_clients(Err(e)).await,
3842 }
3843 }
3844 #[cfg(not(unix))]
3845 {
3846 self.notify_clients(Err(
3847 "JACK backend is not available on this platform build".to_string(),
3848 ))
3849 .await;
3850 }
3851 return;
3852 }
3853 Action::JackConnect {
3854 ref source,
3855 ref destination,
3856 } => {
3857 #[cfg(unix)]
3858 {
3859 match self
3860 .jack_runtime
3861 .as_ref()
3862 .ok_or(
3863 "JACK runtime is not active; open the JACK backend first".to_string(),
3864 )
3865 .and_then(|jack| jack.connect_ports_by_name(source, destination))
3866 .and_then(|_| {
3867 self.jack_runtime
3868 .as_ref()
3869 .expect("JACK runtime was checked")
3870 .graph_info()
3871 }) {
3872 Ok(graph) => {
3873 self.notify_clients(Ok(a.clone())).await;
3874 self.notify_clients(Ok(Action::JackGraph(graph))).await;
3875 }
3876 Err(e) => self.notify_clients(Err(e)).await,
3877 }
3878 }
3879 #[cfg(not(unix))]
3880 {
3881 let _ = (source, destination);
3882 self.notify_clients(Err(
3883 "JACK backend is not available on this platform build".to_string(),
3884 ))
3885 .await;
3886 }
3887 return;
3888 }
3889 Action::JackDisconnect {
3890 ref source,
3891 ref destination,
3892 } => {
3893 #[cfg(unix)]
3894 {
3895 match self
3896 .jack_runtime
3897 .as_ref()
3898 .ok_or(
3899 "JACK runtime is not active; open the JACK backend first".to_string(),
3900 )
3901 .and_then(|jack| jack.disconnect_ports_by_name(source, destination))
3902 .and_then(|_| {
3903 self.jack_runtime
3904 .as_ref()
3905 .expect("JACK runtime was checked")
3906 .graph_info()
3907 }) {
3908 Ok(graph) => {
3909 self.notify_clients(Ok(a.clone())).await;
3910 self.notify_clients(Ok(Action::JackGraph(graph))).await;
3911 }
3912 Err(e) => self.notify_clients(Err(e)).await,
3913 }
3914 }
3915 #[cfg(not(unix))]
3916 {
3917 let _ = (source, destination);
3918 self.notify_clients(Err(
3919 "JACK backend is not available on this platform build".to_string(),
3920 ))
3921 .await;
3922 }
3923 return;
3924 }
3925 Action::OpenMidiInputDevice(ref device) => {
3926 if let Some(worker) = &self.hw_worker {
3927 if let Err(e) = worker
3928 .tx
3929 .send(Message::HWOpenMidiInputDevice(device.clone()))
3930 .await
3931 {
3932 self.notify_clients(Err(format!("Failed to send MIDI input open: {e}")))
3933 .await;
3934 }
3935 return;
3936 }
3937 let Some(midi_hub) = self.midi_hub.as_mut() else {
3938 self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3939 .await;
3940 return;
3941 };
3942 if let Err(e) = midi_hub.open_input(device) {
3943 self.notify_clients(Err(e)).await;
3944 return;
3945 }
3946 }
3947 Action::OpenMidiOutputDevice(ref device) => {
3948 if let Some(worker) = &self.hw_worker {
3949 if let Err(e) = worker
3950 .tx
3951 .send(Message::HWOpenMidiOutputDevice(device.clone()))
3952 .await
3953 {
3954 self.notify_clients(Err(format!("Failed to send MIDI output open: {e}")))
3955 .await;
3956 }
3957 return;
3958 }
3959 let Some(midi_hub) = self.midi_hub.as_mut() else {
3960 self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3961 .await;
3962 return;
3963 };
3964 if let Err(e) = midi_hub.open_output(device) {
3965 self.notify_clients(Err(e)).await;
3966 return;
3967 }
3968 }
3969 Action::RequestSessionDiagnostics => {
3970 self.handle_request_session_diagnostics().await;
3971 }
3972 Action::RequestMidiLearnMappingsReport => {
3973 self.handle_request_midi_learn_mappings_report().await;
3974 }
3975 Action::ClearAllMidiLearnBindings => {
3976 if Self::box_bool(self.handle_clear_all_midi_learn_bindings(a.clone())).await {
3977 return;
3978 }
3979 }
3980 #[cfg(unix)]
3981 Action::TrackLv2PluginControls { .. } => {}
3982 #[cfg(unix)]
3983 Action::ClipLv2PluginControls { .. } => {}
3984 #[cfg(unix)]
3985 Action::TrackLv2StateSnapshot { .. } => {}
3986 #[cfg(unix)]
3987 Action::ClipLv2StateSnapshot { .. } => {}
3988 #[cfg(unix)]
3989 Action::TrackLv2Midnam { .. } => {}
3990 Action::TrackClapNoteNames { .. } => {}
3991 Action::SessionDiagnosticsReport { .. } => {}
3992 Action::MidiLearnMappingsReport { .. } => {}
3993 Action::HWInfo { .. } => {}
3994 Action::HistoryState { .. } => {}
3995 Action::Undo => {}
3996 Action::Redo => {}
3997 Action::ApplyGroupedActions(_) => {}
3998 _ => {}
3999 }
4000
4001 if let Some(inverse) = inverse_actions {
4002 if let Some(group) = self.history_group.as_mut() {
4003 group.forward_actions.push(action_to_process.clone());
4004 group.inverse_actions.splice(0..0, inverse);
4005 } else {
4006 self.history.record(UndoEntry {
4007 forward_actions: vec![action_to_process.clone()],
4008 inverse_actions: inverse,
4009 });
4010 }
4011 }
4012
4013 self.notify_clients(Ok(action_to_process)).await;
4014 }
4015 pub async fn work(&mut self) {
4016 let mut timeout_tick = tokio::time::interval(Duration::from_millis(10));
4019 timeout_tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
4020 loop {
4021 let message = tokio::select! {
4022 message = self.rx.recv() => {
4023 let Some(message) = message else {
4024 break;
4025 };
4026 tracing::debug!(?message, "engine work loop received message");
4027 Some(message)
4028 }
4029 _ = self.node_result_notify.notified() => {
4030 tracing::trace!("engine work loop woken by node result");
4031 None
4032 }
4033 _ = timeout_tick.tick() => {
4034 tracing::trace!("engine work loop timeout tick");
4035 None
4036 }
4037 };
4038 self.poll_node_worker_results().await;
4039 self.poll_jack_hw_finished().await;
4040 self.poll_stopped_plugin_parameter_echoes().await;
4041 if !self.playing && !self.transport_running {
4042 self.publish_clap_state_dirty().await;
4043 }
4044 self.on_executor_tick().await;
4045 let Some(message) = message else {
4046 continue;
4047 };
4048 match message {
4049 Message::Ready(id) => {
4050 if let Some(track_name) = self.bounce_worker_tracks.remove(&id) {
4054 self.offline_bounce_jobs.remove(&track_name);
4055 }
4056 self.push_ready_worker(id);
4057 if self.dispatch_node_jobs(Vec::new()).await {
4058 self.on_all_tracks_finished().await;
4059 }
4060 self.drain_pending_requests_if_idle().await;
4061 }
4062 Message::NodeDone {
4063 worker_id,
4064 epoch,
4065 node,
4066 output_linear,
4067 parameter_updates,
4068 latency_changed,
4069 } => {
4070 self.on_node_done(
4071 worker_id,
4072 epoch,
4073 node,
4074 output_linear,
4075 parameter_updates,
4076 latency_changed,
4077 )
4078 .await;
4079 }
4080 Message::Channel(s) => {
4081 self.clients.push(s);
4082 }
4083 Message::Response(result) => {
4084 self.notify_clients(result).await;
4085 }
4086
4087 Message::Request(a) => {
4088 self.dispatch_request(a).await;
4089 self.plan_builder.mark_dirty();
4092 }
4093 Message::OscRequest { action, reply_to } => {
4094 tracing::debug!(%reply_to, ?action, "engine received OscRequest");
4095 self.osc_reply_target = Some(reply_to);
4096 self.dispatch_request(action).await;
4097 self.osc_reply_target = None;
4098 self.plan_builder.mark_dirty();
4099 }
4100 Message::OfflineBounceFinished { result } => {
4101 if let Ok(Action::TrackOfflineBounce { track_name, .. })
4102 | Ok(Action::TrackOfflineBounceCanceled { track_name, .. }) = &result
4103 {
4104 self.offline_bounce_jobs.remove(track_name);
4105 }
4106 self.notify_clients(result).await;
4107 self.drain_pending_requests_if_idle().await;
4108 }
4109 Message::HWFinished => {
4110 if !self.awaiting_hwfinished {
4111 tracing::debug!(
4112 playing = self.playing,
4113 transport_running = self.transport_running,
4114 transport_sample = self.transport_sample,
4115 session_transport_sample = self.session_transport_sample,
4116 cycle_samples = self.current_cycle_samples(),
4117 "HWFinished ignored because engine was not awaiting it"
4118 );
4119 continue;
4120 }
4121 tracing::debug!(
4122 playing = self.playing,
4123 transport_running = self.transport_running,
4124 transport_sample = self.transport_sample,
4125 session_transport_sample = self.session_transport_sample,
4126 cycle_samples = self.current_cycle_samples(),
4127 "HWFinished handling"
4128 );
4129 self.handling_hwfinished = true;
4130 self.awaiting_hwfinished = false;
4131 #[cfg(unix)]
4132 {
4133 if let Some(jack) = self.jack_runtime.as_mut() {
4134 if !self.pending_hw_midi_out_events.is_empty() {
4135 let out_events =
4136 std::mem::take(&mut self.pending_hw_midi_out_events);
4137 jack.write_events(&out_events);
4138 }
4139 let mut in_events = vec![];
4140 jack.read_events_into(&mut in_events);
4141 if !in_events.is_empty() {
4142 self.pending_hw_midi_events.extend(in_events);
4143 }
4144 let dropped = jack.take_midi_events_dropped();
4145 if dropped > 0 {
4146 tracing::warn!(
4147 "JACK MIDI ring full; {dropped} events dropped since last cycle"
4148 );
4149 }
4150 }
4151 }
4152 #[cfg(unix)]
4153 if self.jack_runtime.is_some() {
4154 self.sync_from_jack_transport().await;
4155 }
4156 while let Some(a) = self.pending_requests.pop_front() {
4157 self.handle_request(a).await;
4158 }
4159 self.apply_mute_solo_policy();
4160 self.append_recorded_cycle();
4161 self.flush_completed_recordings().await;
4162 let hw_in_routes = self.midi_hw_in_routes.clone();
4163 let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
4164 let mut reconfigured_tracks = Vec::new();
4165 let state = self.state_snapshot.load_full();
4166 for (track_name, track) in state.tracks.iter() {
4167 let mut track_lock = track.lock();
4168 if self.jack_runtime_is_some() {
4169 if !self.pending_hw_midi_events.is_empty() {
4170 track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
4171 }
4172 } else {
4173 for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
4174 if let Some(events) = pending_hw_in_by_device.get(&route.device) {
4175 track_lock.push_hw_midi_events_to_port(route.to_port, events);
4176 }
4177 }
4178 }
4179 if track_lock.setup() {
4180 reconfigured_tracks.push(track_name.clone());
4181 }
4182 }
4183 self.publish_track_meters();
4184 self.publish_session_runtime_reports().await;
4185 self.publish_clap_state_dirty().await;
4186 for track_name in reconfigured_tracks {
4187 let track = state.tracks.get(&track_name).cloned();
4188 if let Some(track) = track {
4189 let (plugins, connections, connectable_connections) = {
4190 let track_lock = track.lock();
4191 (
4192 track_lock.plugin_graph_plugins(false),
4193 track_lock.plugin_graph_connections(),
4194 track_lock.connectable_connections(),
4195 )
4196 };
4197 self.notify_clients(Ok(Action::TrackPluginGraph {
4198 track_name: track_name.clone(),
4199 plugins,
4200 connections,
4201 connectable_connections,
4202 }))
4203 .await;
4204 }
4205 }
4206 self.pending_hw_midi_events.clear();
4207 self.pending_hw_midi_events_by_device.clear();
4208 let cycle_samples = self.current_cycle_samples();
4209 if self.transport_running {
4210 if self.transport_panic_flush_pending {
4211 self.transport_panic_flush_pending = false;
4212 } else if self.transport_restart_pending {
4213 self.transport_restart_pending = false;
4214 } else {
4215 let before = self.transport_sample;
4216 let next = self.transport_sample.saturating_add(cycle_samples);
4217 let normalized = self.normalize_transport_sample(next);
4218 let wrapped = normalized != next;
4219 self.transport_sample = normalized;
4220 tracing::debug!(
4224 before,
4225 delta = cycle_samples,
4226 next,
4227 normalized,
4228 wrapped,
4229 "transport advanced after HWFinished"
4230 );
4231 self.publish_transport_snapshot();
4232 if wrapped {
4233 if self.notified_loop_wrap_sample == Some(self.transport_sample) {
4234 self.notified_loop_wrap_sample = None;
4235 } else {
4236 self.notify_clients(Ok(Action::TransportPosition(
4237 self.transport_sample,
4238 )))
4239 .await;
4240 }
4241 }
4242 }
4243 } else {
4244 tracing::debug!(
4245 playing = self.playing,
4246 cycle_samples,
4247 "transport not advanced because transport_running is false"
4248 );
4249 }
4250 if self.session_clip_playback_enabled && self.playing {
4251 let before = self.session_transport_sample;
4252 self.session_transport_sample =
4253 self.session_transport_sample.saturating_add(cycle_samples);
4254 tracing::debug!(
4255 before,
4256 delta = cycle_samples,
4257 after = self.session_transport_sample,
4258 "session transport advanced after HWFinished"
4259 );
4260 }
4261 {
4262 let echoes = self.apply_modulators(self.active_transport_sample());
4263 for action in echoes {
4264 self.notify_clients(Ok(action)).await;
4265 }
4266 }
4267 self.apply_mixosc_automation(self.active_transport_sample());
4268 let cycle_started = self.start_plan_cycle().await;
4269 if self.hw_worker.is_some()
4272 && !cycle_started
4273 && (self.playing || self.audio_preview.is_some())
4274 && self.executor.cycle_complete()
4275 {
4276 self.request_hw_cycle().await;
4277 }
4278 tracing::debug!(
4279 cycle_started,
4280 hw_worker = self.hw_worker.is_some(),
4281 awaiting_hwfinished = self.awaiting_hwfinished,
4282 executor_complete = self.executor.cycle_complete(),
4283 "HWFinished rearm decision"
4284 );
4285 #[cfg(unix)]
4286 {
4287 if self.jack_runtime.is_some() {
4288 self.awaiting_hwfinished = true;
4289 }
4290 }
4291 self.handling_hwfinished = false;
4292 }
4293 Message::HWMidiEvents(events) => {
4294 for hw_event in events {
4295 let thru_targets: Vec<String> = self
4296 .midi_hw_thru_routes
4297 .iter()
4298 .filter(|route| route.from_device == hw_event.device)
4299 .map(|route| route.to_device.clone())
4300 .collect();
4301 for device in thru_targets {
4302 self.pending_hw_midi_out_events_by_device.push(HwMidiEvent {
4303 device,
4304 event: hw_event.event.clone(),
4305 });
4306 }
4307 if hw_event.event.data.len() >= 3 {
4308 let status = hw_event.event.data[0];
4309 if status & 0xF0 == 0xB0 {
4310 let channel = status & 0x0F;
4311 let cc = hw_event.event.data[1];
4312 let value = hw_event.event.data[2];
4313 self.handle_incoming_hw_cc(&hw_event.device, channel, cc, value)
4314 .await;
4315 }
4316 if self.step_recording_enabled && status & 0xF0 == 0x90 {
4317 let channel = status & 0x0F;
4318 let pitch = hw_event.event.data[1];
4319 let velocity = hw_event.event.data[2];
4320 if velocity > 0 {
4321 self.notify_clients(Ok(Action::StepRecordMidiNote {
4322 device: hw_event.device.clone(),
4323 channel,
4324 pitch,
4325 velocity,
4326 }))
4327 .await;
4328 }
4329 }
4330 }
4331 self.pending_hw_midi_events_by_device
4332 .entry(hw_event.device)
4333 .or_default()
4334 .push(hw_event.event);
4335 }
4336 }
4337 Message::StartAudioPreview {
4338 samples,
4339 channels,
4340 start_sample,
4341 } => {
4342 self.audio_preview = Some(AudioPreviewPlayback {
4343 samples,
4344 channels: channels.max(1),
4345 cursor: start_sample,
4346 });
4347 self.meter_decay_after_stop = None;
4348 self.set_hw_playing(true).await;
4349 if !self.awaiting_hwfinished && self.executor.cycle_complete() {
4350 self.request_hw_cycle().await;
4351 }
4352 }
4353 Message::StopAudioPreview => {
4354 self.audio_preview = None;
4355 }
4356 _ => {}
4357 }
4358 }
4359 }
4360
4361 pub(crate) fn collect_hw_midi_output_events(&self) -> Vec<MidiEvent> {
4362 let mut events = vec![];
4363 for track in self.state_snapshot.load_full().tracks.values() {
4364 events.extend(
4365 track
4366 .lock()
4367 .take_hw_midi_out_events()
4368 .into_iter()
4369 .map(|evt| evt.event),
4370 );
4371 }
4372 events.sort_by_key(|a| a.frame);
4373 events
4374 }
4375
4376 pub(crate) fn collect_hw_midi_output_events_by_device(&mut self) -> Vec<HwMidiEvent> {
4377 let mut events = Vec::<HwMidiEvent>::new();
4378 let routes = self.midi_hw_out_routes.clone();
4379 let mut events_by_track = HashMap::<String, Vec<crate::track::HwMidiOutEvent>>::new();
4380 {
4381 let state = self.state_snapshot.load_full();
4382 for route in &routes {
4383 if events_by_track.contains_key(&route.from_track) {
4384 continue;
4385 }
4386 let Some(track) = state.tracks.get(&route.from_track) else {
4387 continue;
4388 };
4389 events_by_track.insert(
4390 route.from_track.clone(),
4391 track.lock().take_hw_midi_out_events(),
4392 );
4393 }
4394 }
4395
4396 for route in routes {
4397 let Some(track_events) = events_by_track.get(&route.from_track) else {
4398 continue;
4399 };
4400 for hw_event in track_events
4401 .iter()
4402 .filter(|evt| evt.port == route.from_port)
4403 {
4404 self.update_active_hw_notes_for_track(
4405 &route.from_track,
4406 &route.device,
4407 &hw_event.event.data,
4408 );
4409 events.push(HwMidiEvent {
4410 device: route.device.clone(),
4411 event: hw_event.event.clone(),
4412 });
4413 }
4414 }
4415 events.sort_by(|a, b| {
4416 a.event
4417 .frame
4418 .cmp(&b.event.frame)
4419 .then_with(|| a.device.cmp(&b.device))
4420 });
4421 events
4422 }
4423}