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