1use super::*;
2#[cfg(target_os = "linux")]
3use crate::hw::alsa::MidiHub;
4#[cfg(target_os = "freebsd")]
5use crate::hw::oss::MidiHub;
6#[cfg(target_os = "openbsd")]
7use crate::hw::sndio::{HwDriver, HwOptions, MidiHub};
8#[cfg(target_os = "windows")]
9use crate::hw::wasapi::MidiHub;
10#[cfg(target_os = "openbsd")]
11use crate::workers::sndio_worker::HwWorker;
12use crate::{
13 history::{History, UndoEntry},
14 message::{Action, HwMidiEvent, Message, ProcessTask, SessionSlotState},
15 midi::io::MidiEvent,
16 osc::{OscArg, OscServer, build_error_packet, build_osc_packet},
17 state::State,
18 workers::worker::Worker,
19};
20use mixosc::parameters::{OscValue, build_set};
21use std::{
22 collections::{HashMap, VecDeque},
23 net::{SocketAddr, UdpSocket},
24 path::{Path, PathBuf},
25 sync::{Arc, atomic::Ordering},
26 time::{Duration, Instant},
27};
28use tokio::sync::Notify;
29use tokio::sync::mpsc::{Receiver, Sender, channel};
30use tracing::error;
31
32impl Engine {
33 pub fn state(&self) -> Arc<State> {
34 self.state.clone()
35 }
36
37 pub(crate) fn timing_at_sample(&self, sample: usize) -> (f64, u16, u16) {
38 let bpm = self
39 .tempo_points
40 .iter()
41 .filter(|p| p.sample <= sample)
42 .max_by_key(|p| p.sample)
43 .map(|p| p.bpm)
44 .unwrap_or(self.tempo_bpm)
45 .max(1.0);
46 let (num, den) = self
47 .time_signature_points
48 .iter()
49 .filter(|p| p.sample <= sample)
50 .max_by_key(|p| p.sample)
51 .map(|p| (p.numerator.max(1), p.denominator.max(1)))
52 .unwrap_or((self.tsig_num.max(1), self.tsig_denom.max(1)));
53 (bpm, num, den)
54 }
55
56 pub(crate) fn update_global_tempo_from_map(&mut self) {
57 let (bpm, num, den) = self.timing_at_sample(0);
58 self.tempo_bpm = bpm;
59 self.tsig_num = num;
60 self.tsig_denom = den;
61 }
62
63 pub(crate) fn meter_linear_to_db(peak: f32) -> f32 {
64 if peak <= 1.0e-6 {
65 -90.0
66 } else {
67 (20.0 * peak.log10()).clamp(-90.0, 20.0)
68 }
69 }
70
71 pub(crate) fn meter_db_to_linear(db: f32) -> f32 {
72 if db <= -90.0 {
73 0.0
74 } else {
75 10.0_f32.powf(db / 20.0)
76 }
77 }
78
79 pub(crate) const METER_PUBLISH_INTERVAL: Duration = Duration::from_millis(50);
80 pub(crate) const METER_DECAY_AFTER_STOP: Duration = Duration::from_secs(1);
81 pub(crate) const SESSION_RUNTIME_REPORT_INTERVAL: Duration = Duration::from_millis(50);
82 pub(crate) const TRACK_PROCESS_TIMEOUT: Duration = Duration::from_millis(250);
83 #[cfg(unix)]
84 pub(crate) const HW_OUT_METER_LINEAR_EPSILON: f32 = 0.0025;
85
86 #[cfg(unix)]
87 pub(crate) fn session_plugins_dir(&self) -> Option<PathBuf> {
88 self.session_dir.as_ref().map(|d| d.join("plugins"))
89 }
90
91 pub(crate) fn session_audio_dir(&self) -> Option<PathBuf> {
92 self.session_dir.as_ref().map(|d| d.join("audio"))
93 }
94
95 pub(crate) fn session_midi_dir(&self) -> Option<PathBuf> {
96 self.session_dir.as_ref().map(|d| d.join("midi"))
97 }
98
99 pub(crate) fn session_peaks_dir(&self) -> Option<PathBuf> {
100 self.session_dir.as_ref().map(|d| d.join("peaks"))
101 }
102
103 pub(crate) fn ensure_session_subdirs(&self) {
104 if let Some(root) = &self.session_dir {
105 let _ = std::fs::create_dir_all(root.join("plugins"));
106 let _ = std::fs::create_dir_all(root.join("audio"));
107 let _ = std::fs::create_dir_all(root.join("midi"));
108 let _ = std::fs::create_dir_all(root.join("peaks"));
109 }
110 }
111
112 pub fn new(rx: Receiver<Message>, tx: Sender<Message>) -> Self {
113 let (meter_snapshot_producer, _) =
114 crate::triple_buffer::triple_buffer(crate::meter::MeterSnapshot::default());
115 let (transport_snapshot_producer, _) =
116 crate::triple_buffer::triple_buffer(crate::meter::TransportSnapshot::default());
117 let (session_runtime_snapshot_producer, _) =
118 crate::triple_buffer::triple_buffer(crate::meter::SessionRuntimeSnapshot::default());
119 Self::new_with_snapshots(
120 rx,
121 tx,
122 meter_snapshot_producer,
123 transport_snapshot_producer,
124 session_runtime_snapshot_producer,
125 )
126 }
127
128 pub fn new_with_snapshots(
129 rx: Receiver<Message>,
130 tx: Sender<Message>,
131 meter_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
132 crate::meter::MeterSnapshot,
133 >,
134 transport_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
135 crate::meter::TransportSnapshot,
136 >,
137 session_runtime_snapshot_producer: crate::triple_buffer::TripleBufferProducer<
138 crate::meter::SessionRuntimeSnapshot,
139 >,
140 ) -> Self {
141 let state = Arc::new(State::default());
142 let initial_state_snapshot = state.lock().snapshot();
143 let state_snapshot = Arc::new(crate::state::StateSlot::from_pointee(
144 initial_state_snapshot.clone(),
145 ));
146 let collector = basedrop::Collector::new();
149 let hw_ports = Arc::new(arc_swap::ArcSwap::from_pointee(
150 crate::plan_builder::HwPorts {
151 buffer_size: 1024,
152 ..Default::default()
153 },
154 ));
155 let initial_plan =
156 { crate::render_plan::RenderPlan::compile(&initial_state_snapshot, &[], &[], 1024) };
157 let plan_slot = Arc::new(crate::render_plan::PlanSlot::from_pointee(
158 basedrop::Owned::new(&collector.handle(), initial_plan),
159 ));
160 let plan_builder = crate::plan_builder::PlanBuilder::spawn(
161 state_snapshot.clone(),
162 hw_ports.clone(),
163 plan_slot.clone(),
164 collector,
165 );
166 let executor = crate::executor::CycleExecutor::new(plan_slot.clone());
167 Self {
168 rx,
169 tx,
170 clients: vec![],
171 state,
172 state_snapshot,
173 workers: vec![],
174 hw_driver: None,
175 hw_driver_info: None,
176 hw_input_ports: Vec::new(),
177 hw_output_ports: Vec::new(),
178 #[cfg(unix)]
179 jack_runtime: None,
180 midi_hub: Some(MidiHub::default()),
181 hw_worker: None,
182 osc_server: None,
183 osc_reply_socket: None,
184 osc_reply_target: None,
185 mixosc_socket: None,
186 pending_hw_midi_events: vec![],
187 pending_hw_midi_events_by_device: HashMap::new(),
188 pending_hw_midi_out_events: vec![],
189 pending_hw_midi_out_events_by_device: vec![],
190 active_hw_notes_by_track: HashMap::new(),
191 active_hw_notes_cycle_start: HashMap::new(),
192 midi_hw_in_routes: vec![],
193 midi_hw_out_routes: vec![],
194 midi_hw_thru_routes: vec![],
195 ready_workers: vec![],
196 pending_requests: VecDeque::new(),
197 awaiting_hwfinished: false,
198 handling_hwfinished: false,
199 transport_panic_flush_pending: false,
200 transport_restart_pending: false,
201 notified_loop_wrap_sample: None,
202 transport_sample: 0,
203 hw_input_latency_frames: 0,
204 hw_output_latency_frames: 0,
205 loop_enabled: false,
206 loop_range_samples: None,
207 metronome_enabled: false,
208 tempo_bpm: 120.0,
209 tsig_num: 4,
210 tsig_denom: 4,
211 tempo_points: vec![crate::message::TempoPoint {
212 sample: 0,
213 bpm: 120.0,
214 }],
215 time_signature_points: vec![crate::message::TimeSignaturePoint {
216 sample: 0,
217 numerator: 4,
218 denominator: 4,
219 }],
220 punch_enabled: false,
221 punch_range_samples: None,
222 audio_recordings: std::collections::HashMap::new(),
223 midi_recordings: std::collections::HashMap::new(),
224 completed_audio_recordings: Vec::new(),
225 completed_midi_recordings: Vec::new(),
226 playing: false,
227 transport_running: false,
228 clip_playback_enabled: true,
229 session_clip_playback_enabled: false,
230 session_transport_sample: 0,
231 session_scene_queue: None,
232 session_scene_queue_length_samples: 0,
233 session_current_scene: None,
234 session_current_scene_previous_scene: None,
235 session_current_scene_start_sample: 0,
236 session_current_scene_length_samples: 0,
237 session_completed_clip_passes: Vec::new(),
238 session_reported_clip_passes: std::collections::HashSet::new(),
239 record_enabled: false,
240 step_recording_enabled: false,
241 session_dir: None,
242 hw_out_level_db: 0.0,
243 hw_out_balance: 0.0,
244 hw_out_muted: false,
245 last_hw_out_meter_publish: None,
246 #[cfg(unix)]
247 last_hw_out_meter_linear: vec![],
248 hw_out_peak_hold_linear: vec![],
249 #[cfg(unix)]
250 hw_out_meter_publish_phase: false,
251 last_track_meter_publish: None,
252 last_meter_snapshot_publish: None,
253 last_session_report_publish: None,
254 track_meter_linear_by_track: HashMap::new(),
255 meter_decay_after_stop: None,
256 meter_snapshot_producer,
257 transport_snapshot_producer,
258 session_runtime_snapshot_producer,
259 executor,
260 plan_builder,
261 plan_slot,
262 hw_ports,
263 pending_node_jobs: VecDeque::new(),
264 latest_hw_out_meter_db: Arc::new(Vec::new()),
265 latest_track_meter_snapshot: Arc::new(Vec::new()),
266 hw_out_loudness_meter: None,
267 latest_hw_out_lufs: None,
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 self.feed_hw_out_loudness_meter();
1435
1436 let should_notify_interval = self.should_publish_hw_out_meters();
1437 if !should_notify_interval {
1438 return;
1439 }
1440
1441 let plan = self.executor.plan().clone();
1442 let peaks_linear =
1443 crate::hw::common::output_meter_linear_from_plan(&plan, gain, self.hw_out_balance);
1444 if self.hw_out_peak_hold_linear.len() != peaks_linear.len() {
1445 self.hw_out_peak_hold_linear.resize(peaks_linear.len(), 0.0);
1446 }
1447 let mut held_peaks = Vec::with_capacity(peaks_linear.len());
1448 for (idx, peak_now) in peaks_linear.iter().copied().enumerate() {
1449 let held = self.hw_out_peak_hold_linear[idx] * 0.92;
1450 let next = peak_now.max(held);
1451 self.hw_out_peak_hold_linear[idx] = next;
1452 held_peaks.push(next);
1453 }
1454 let should_notify = self.should_publish_hw_out_linear(&held_peaks);
1455 let meter_db: Vec<f32> = held_peaks
1456 .into_iter()
1457 .map(Self::meter_linear_to_db)
1458 .collect();
1459 self.latest_hw_out_meter_db = Arc::new(meter_db.clone());
1460 if should_notify {
1461 self.maybe_notify_hw_out_meter(meter_db).await;
1462 }
1463 }
1464
1465 fn feed_hw_out_loudness_meter(&mut self) {
1466 let Some(info) = self.hw_driver_info else {
1467 return;
1468 };
1469 let plan = self.executor.plan();
1470 let channels = plan.hw_out_map.len();
1471 if channels == 0 {
1472 return;
1473 }
1474 let sample_rate = info.sample_rate as u32;
1475
1476 let needs_recreate = self
1477 .hw_out_loudness_meter
1478 .as_ref()
1479 .is_none_or(|m| m.channels() != channels || m.sample_rate() != sample_rate);
1480 if needs_recreate {
1481 self.hw_out_loudness_meter =
1482 crate::loudness::LoudnessMeter::new(channels, sample_rate).ok();
1483 }
1484
1485 if let Some(meter) = self.hw_out_loudness_meter.as_mut() {
1486 let interleaved = crate::hw::common::interleaved_hw_out_samples(plan);
1487 meter.feed_interleaved(&interleaved);
1488 }
1489 }
1490
1491 fn update_hw_out_lufs_readout(&mut self) {
1492 self.latest_hw_out_lufs = self
1493 .hw_out_loudness_meter
1494 .as_ref()
1495 .map(|meter| meter.values());
1496 }
1497
1498 pub(crate) fn preload_track_clips_spawn(&self) {
1499 let tracks: Vec<_> = self
1500 .state_snapshot
1501 .load_full()
1502 .tracks
1503 .values()
1504 .cloned()
1505 .collect();
1506 for track in tracks {
1507 tokio::task::spawn_blocking(move || {
1508 track.lock().preload_clips();
1509 });
1510 }
1511 }
1512
1513 pub(crate) fn preload_track_clips(
1522 &self,
1523 ) -> impl std::future::Future<Output = ()> + Send + 'static {
1524 let tracks: Vec<_> = self
1525 .state_snapshot
1526 .load_full()
1527 .tracks
1528 .values()
1529 .cloned()
1530 .collect();
1531 Self::preload_track_handles(tracks)
1532 }
1533
1534 async fn preload_track_handles(tracks: Vec<crate::state::TrackHandle>) {
1535 if tracks.is_empty() {
1536 return;
1537 }
1538 let mut handles = Vec::with_capacity(tracks.len());
1539 for track in tracks {
1540 handles.push(tokio::task::spawn_blocking(move || {
1541 track.lock().preload_clips();
1542 }));
1543 }
1544 for handle in handles {
1545 if let Err(e) = handle.await {
1546 tracing::warn!("Clip preload task panicked: {e}");
1547 }
1548 }
1549 }
1550
1551 pub(crate) fn task_track_name(task: &ProcessTask) -> String {
1552 match task {
1553 ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => {
1554 t.lock().name.clone()
1555 }
1556 ProcessTask::Plugin { track, .. } => track.lock().name.clone(),
1557 }
1558 }
1559
1560 pub(crate) fn prepare_task_track(&self, task: &ProcessTask) {
1561 let track = match task {
1562 ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => t,
1563 ProcessTask::Plugin { track, .. } => track,
1564 };
1565 let mut t = track.lock();
1566 let transport_sample = if self.session_clip_playback_enabled && self.playing {
1567 self.session_transport_sample
1568 } else {
1569 self.transport_sample
1570 };
1571 t.set_transport_sample(transport_sample);
1572 t.set_loop_config(self.loop_enabled, self.loop_range_samples);
1573 t.set_transport_timing(self.tempo_bpm, self.tsig_num, self.tsig_denom);
1574 t.set_clip_playback_enabled(self.clip_playback_enabled && self.playing);
1575 t.set_session_clip_playback_enabled(self.session_clip_playback_enabled && self.playing);
1576 t.set_record_tap_enabled(self.playing && self.record_enabled);
1577 t.audio.set_processing(true);
1578 }
1579
1580 pub(crate) async fn dispatch_node_jobs(&mut self, jobs: Vec<crate::executor::NodeJob>) -> bool {
1584 self.pending_node_jobs.extend(jobs);
1585 let mut cycle_complete = false;
1586 while !self.pending_node_jobs.is_empty() {
1587 let Some(worker_index) = self.take_ready_worker_index() else {
1588 break;
1589 };
1590 let Some(job) = self.pending_node_jobs.pop_front() else {
1591 break;
1592 };
1593 if let Some(crate::render_plan::Op::Task { task, .. }) =
1594 job.plan.nodes.get(job.node as usize)
1595 {
1596 self.prepare_task_track(task);
1597 }
1598 let worker = &mut self.workers[worker_index];
1599 if let Some(node_job_tx) = worker.node_job_tx.as_mut() {
1600 match node_job_tx.push(job) {
1601 Ok(()) => {
1602 if let Some(thread) = &worker.node_thread {
1603 thread.unpark();
1604 }
1605 }
1606 Err(rtrb::PushError::Full(job)) => {
1607 self.pending_node_jobs.push_front(job);
1608 self.push_ready_worker(worker_index);
1609 break;
1610 }
1611 }
1612 } else {
1613 let node = job.node;
1614 error!("Worker {worker_index} has no node-job mailbox");
1615 let outcome = self.executor.abandon_node(node, Instant::now());
1616 self.log_silenced_nodes(&outcome.silenced);
1617 cycle_complete |= outcome.cycle_complete;
1618 self.pending_node_jobs.extend(outcome.jobs);
1619 }
1620 }
1621 cycle_complete
1622 }
1623
1624 fn ensure_metronome_wiring(&mut self) {
1632 let Some(track) = self
1633 .state_snapshot
1634 .load_full()
1635 .tracks
1636 .get(Self::METRONOME_TRACK)
1637 .cloned()
1638 else {
1639 return;
1640 };
1641 let frames = self.current_cycle_samples();
1642 let (_, changed) = track.lock().ensure_metronome_source(frames);
1643 if changed {
1644 self.plan_builder.mark_dirty();
1645 }
1646 }
1647
1648 pub(crate) async fn start_plan_cycle(&mut self) -> bool {
1649 if !self.playing || !self.executor.cycle_complete() || !self.offline_bounce_jobs.is_empty()
1653 {
1654 return false;
1655 }
1656 self.refresh_realtime_infection();
1657 self.ensure_metronome_wiring();
1658 let jobs = self.executor.start_cycle(Instant::now());
1659 if self.dispatch_node_jobs(jobs).await {
1660 self.on_all_tracks_finished().await;
1661 return true;
1662 }
1663 false
1664 }
1665
1666 pub(crate) async fn on_node_done(
1670 &mut self,
1671 worker_id: usize,
1672 epoch: u64,
1673 node: u32,
1674 output_linear: Vec<f32>,
1675 parameter_updates: Vec<Action>,
1676 latency_changed: bool,
1677 ) {
1678 self.push_ready_worker(worker_id);
1679 let mut complete = self.dispatch_node_jobs(Vec::new()).await;
1680 if epoch != self.executor.epoch() {
1681 tracing::debug!(
1682 "dropping stale NodeDone (epoch {} vs {}) for node {}",
1683 epoch,
1684 self.executor.epoch(),
1685 node
1686 );
1687 return;
1688 }
1689 if latency_changed {
1690 self.publish_state_snapshot();
1691 self.plan_builder.mark_dirty();
1692 }
1693 let plan = self.executor.plan().clone();
1694 if let Some(crate::render_plan::Op::Task { task, .. }) = plan.nodes.get(node as usize) {
1695 let track_name = Self::task_track_name(task);
1696 self.track_meter_linear_by_track
1697 .insert(track_name, output_linear);
1698 }
1699 for action in parameter_updates {
1700 self.notify_clients(Ok(action)).await;
1701 }
1702 let now = Instant::now();
1703 let (jobs, done) = self.executor.on_node_done(epoch, node, now);
1704 complete |= done;
1705 complete |= self.dispatch_node_jobs(jobs).await;
1706 let outcome = self
1707 .executor
1708 .force_timeouts(now, Self::TRACK_PROCESS_TIMEOUT);
1709 self.log_silenced_nodes(&outcome.silenced);
1710 complete |= self.dispatch_node_jobs(outcome.jobs).await;
1711 complete |= outcome.cycle_complete;
1712 if complete {
1713 self.on_all_tracks_finished().await;
1714 }
1715 }
1716
1717 pub(crate) async fn poll_stopped_plugin_parameter_echoes(&mut self) {
1718 if self.playing || self.transport_running {
1719 return;
1720 }
1721
1722 let state = self.state_snapshot.load_full();
1723 let mut updates = Vec::new();
1724 for track in state.tracks.values() {
1725 updates.extend(track.lock().drain_plugin_parameter_echoes());
1726 }
1727 drop(state);
1728
1729 for action in updates {
1730 self.notify_clients(Ok(action)).await;
1731 }
1732 }
1733
1734 pub(crate) async fn poll_node_worker_results(&mut self) {
1735 let mut results = Vec::new();
1736 for worker in &mut self.workers {
1737 if let Some(rx) = worker.node_result_rx.as_mut() {
1738 while let Ok(result) = rx.pop() {
1739 results.push(result);
1740 }
1741 }
1742 }
1743 for result in results {
1744 self.on_node_done(
1745 result.worker_id,
1746 result.epoch,
1747 result.node,
1748 result.output_linear,
1749 result.parameter_updates,
1750 result.latency_changed,
1751 )
1752 .await;
1753 }
1754 }
1755
1756 pub(crate) async fn poll_jack_hw_finished(&mut self) {
1757 #[cfg(unix)]
1758 {
1759 let finished = self
1760 .jack_runtime
1761 .as_ref()
1762 .map(|jack| jack.take_hw_finished_count())
1763 .unwrap_or(0);
1764 if finished > 0 {
1765 self.handle_hw_finished().await;
1766 }
1767 }
1768 }
1769
1770 #[cfg(unix)]
1771 pub(crate) async fn handle_hw_finished(&mut self) {
1772 if !self.awaiting_hwfinished {
1773 return;
1774 }
1775 tracing::debug!(
1776 playing = self.playing,
1777 transport_running = self.transport_running,
1778 transport_sample = self.transport_sample,
1779 session_transport_sample = self.session_transport_sample,
1780 cycle_samples = self.current_cycle_samples(),
1781 "HWFinished handling"
1782 );
1783 self.handling_hwfinished = true;
1784 self.awaiting_hwfinished = false;
1785 #[cfg(unix)]
1786 {
1787 if let Some(jack) = self.jack_runtime.as_mut() {
1788 if !self.pending_hw_midi_out_events.is_empty() {
1789 let out_events = std::mem::take(&mut self.pending_hw_midi_out_events);
1790 jack.write_events(&out_events);
1791 }
1792 let mut in_events = vec![];
1793 jack.read_events_into(&mut in_events);
1794 if !in_events.is_empty() {
1795 self.pending_hw_midi_events.extend(in_events);
1796 }
1797 let dropped = jack.take_midi_events_dropped();
1798 if dropped > 0 {
1799 tracing::warn!(
1800 "JACK MIDI ring full; {dropped} events dropped since last cycle"
1801 );
1802 }
1803 }
1804 }
1805 #[cfg(unix)]
1806 if self.jack_runtime.is_some() {
1807 self.sync_from_jack_transport().await;
1808 }
1809 while let Some(a) = self.pending_requests.pop_front() {
1810 self.handle_request(a).await;
1811 }
1812 self.apply_mute_solo_policy();
1813 self.append_recorded_cycle();
1814 self.flush_completed_recordings().await;
1815 let hw_in_routes = self.midi_hw_in_routes.clone();
1816 let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
1817 let mut reconfigured_tracks = Vec::new();
1818 let state = self.state_snapshot.load_full();
1819 for (track_name, track) in state.tracks.iter() {
1820 let mut track_lock = track.lock();
1821 if self.jack_runtime_is_some() {
1822 if !self.pending_hw_midi_events.is_empty() {
1823 track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
1824 }
1825 } else {
1826 for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
1827 if let Some(events) = pending_hw_in_by_device.get(&route.device) {
1828 track_lock.push_hw_midi_events_to_port(route.to_port, events);
1829 }
1830 }
1831 }
1832 if track_lock.setup() {
1833 reconfigured_tracks.push(track_name.clone());
1834 }
1835 }
1836 self.publish_track_meters();
1837 self.publish_session_runtime_reports().await;
1838 self.publish_clap_state_dirty().await;
1839 for track_name in reconfigured_tracks {
1840 let track = state.tracks.get(&track_name).cloned();
1841 if let Some(track) = track {
1842 let (plugins, connections, connectable_connections) = {
1843 let track_lock = track.lock();
1844 (
1845 track_lock.plugin_graph_plugins(false),
1846 track_lock.plugin_graph_connections(),
1847 track_lock.connectable_connections(),
1848 )
1849 };
1850 self.notify_clients(Ok(Action::TrackPluginGraph {
1851 track_name: track_name.clone(),
1852 plugins,
1853 connections,
1854 connectable_connections,
1855 }))
1856 .await;
1857 }
1858 }
1859 self.pending_hw_midi_events.clear();
1860 self.pending_hw_midi_events_by_device.clear();
1861 let cycle_samples = self.current_cycle_samples();
1862 if self.transport_running {
1863 if self.transport_panic_flush_pending {
1864 self.transport_panic_flush_pending = false;
1865 } else if self.transport_restart_pending {
1866 self.transport_restart_pending = false;
1867 } else {
1868 let before = self.transport_sample;
1869 let next = self.transport_sample.saturating_add(cycle_samples);
1870 let normalized = self.normalize_transport_sample(next);
1871 let wrapped = normalized != next;
1872 self.transport_sample = normalized;
1873 tracing::debug!(
1874 before,
1875 delta = cycle_samples,
1876 next,
1877 normalized,
1878 wrapped,
1879 "transport advanced after HWFinished"
1880 );
1881 self.publish_transport_snapshot();
1882 if wrapped {
1883 if self.notified_loop_wrap_sample == Some(self.transport_sample) {
1884 self.notified_loop_wrap_sample = None;
1885 } else {
1886 self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
1887 .await;
1888 }
1889 }
1890 }
1891 } else {
1892 tracing::debug!(
1893 playing = self.playing,
1894 cycle_samples,
1895 "transport not advanced because transport_running is false"
1896 );
1897 }
1898 if self.session_clip_playback_enabled && self.playing {
1899 let before = self.session_transport_sample;
1900 self.session_transport_sample =
1901 self.session_transport_sample.saturating_add(cycle_samples);
1902 tracing::debug!(
1903 before,
1904 delta = cycle_samples,
1905 after = self.session_transport_sample,
1906 "session transport advanced after HWFinished"
1907 );
1908 }
1909 {
1910 let echoes = self.apply_modulators(self.active_transport_sample());
1911 for action in echoes {
1912 self.notify_clients(Ok(action)).await;
1913 }
1914 }
1915 self.apply_mixosc_automation(self.active_transport_sample());
1916 let cycle_started = self.start_plan_cycle().await;
1917 if self.hw_worker.is_some()
1920 && !cycle_started
1921 && (self.playing || self.audio_preview.is_some())
1922 && self.executor.cycle_complete()
1923 {
1924 self.request_hw_cycle().await;
1925 }
1926 tracing::debug!(
1927 cycle_started,
1928 hw_worker = self.hw_worker.is_some(),
1929 awaiting_hwfinished = self.awaiting_hwfinished,
1930 executor_complete = self.executor.cycle_complete(),
1931 "HWFinished rearm decision"
1932 );
1933 #[cfg(unix)]
1934 {
1935 if self.jack_runtime.is_some() {
1936 self.awaiting_hwfinished = true;
1937 }
1938 }
1939 self.handling_hwfinished = false;
1940 }
1941
1942 pub(crate) async fn on_executor_tick(&mut self) {
1945 if self.executor.cycle_complete() {
1946 return;
1947 }
1948 let outcome = self
1949 .executor
1950 .force_timeouts(Instant::now(), Self::TRACK_PROCESS_TIMEOUT);
1951 self.log_silenced_nodes(&outcome.silenced);
1952 let mut complete = self.dispatch_node_jobs(outcome.jobs).await;
1953 complete |= outcome.cycle_complete;
1954 if complete {
1955 self.on_all_tracks_finished().await;
1956 }
1957 }
1958
1959 pub(crate) fn log_silenced_nodes(&self, silenced: &[u32]) {
1960 for &node in silenced {
1961 let plan = self.executor.plan();
1962 let name = match plan.nodes.get(node as usize) {
1963 Some(crate::render_plan::Op::Task { task, .. }) => Self::task_track_name(task),
1964 _ => format!("node {node}"),
1965 };
1966 tracing::warn!(
1967 "Node {} ('{}') exceeded process timeout ({} ms); forced silent completion for cycle",
1968 node,
1969 name,
1970 Self::TRACK_PROCESS_TIMEOUT.as_millis()
1971 );
1972 }
1973 }
1974
1975 pub(crate) async fn send_bounce_job(
1978 &mut self,
1979 worker_index: usize,
1980 job: crate::message::OfflineBounceWork,
1981 ) {
1982 let track_name = job.track_name.clone();
1983 self.bounce_worker_tracks
1984 .insert(worker_index, track_name.clone());
1985 let worker = &self.workers[worker_index];
1986 if let Err(e) = worker.tx.send(Message::ProcessOfflineBounce(job)).await {
1987 self.bounce_worker_tracks.remove(&worker_index);
1988 self.offline_bounce_jobs.remove(&track_name);
1989 self.push_ready_worker(worker_index);
1990 self.notify_clients(Err(format!("Failed to schedule offline bounce: {e}")))
1991 .await;
1992 }
1993 }
1994
1995 async fn drain_pending_requests_if_idle(&mut self) {
1998 if self.offline_bounce_jobs.is_empty() {
1999 while let Some(next) = self.pending_requests.pop_front() {
2000 self.handle_request(next).await;
2001 }
2002 }
2003 }
2004
2005 pub(crate) async fn on_all_tracks_finished(&mut self) {
2006 let pending = std::mem::take(&mut self.pending_bounce_starts);
2009 for (worker_index, job) in pending {
2010 self.send_bounce_job(worker_index, job).await;
2011 }
2012 if self.transport_restart_pending {
2013 let state = self.state_snapshot.load_full();
2014 for track in state.tracks.values() {
2015 track.lock().take_hw_midi_out_events();
2016 }
2017 } else if self.hw_worker.is_some() {
2018 self.active_hw_notes_cycle_start = self.active_hw_notes_by_track.clone();
2019 let mut out_events = self.collect_hw_midi_output_events_by_device();
2020 if self.loop_enabled
2021 && let Some((_, loop_end)) = self.loop_range_samples
2022 {
2023 let cycle_end = self
2024 .transport_sample
2025 .saturating_add(self.current_cycle_samples());
2026 if self.transport_sample < loop_end && cycle_end >= loop_end {
2027 let wrap_frame = loop_end
2028 .saturating_sub(self.transport_sample)
2029 .min(self.current_cycle_samples())
2030 as u32;
2031 out_events.extend(self.note_off_events_for_active_snapshot(
2032 &self.active_hw_notes_cycle_start,
2033 wrap_frame,
2034 ));
2035 out_events.sort_by(|a, b| {
2036 a.event
2037 .frame
2038 .cmp(&b.event.frame)
2039 .then_with(|| a.device.cmp(&b.device))
2040 });
2041 }
2042 }
2043 self.pending_hw_midi_out_events_by_device.extend(out_events);
2044 } else {
2045 self.pending_hw_midi_out_events = self.collect_hw_midi_output_events();
2046 }
2047 self.request_hw_cycle().await;
2048 }
2049
2050 pub(crate) fn take_ready_worker_index(&mut self) -> Option<usize> {
2051 while !self.ready_workers.is_empty() {
2052 let worker_index = self.ready_workers.remove(0);
2053 if worker_index < self.workers.len() {
2054 return Some(worker_index);
2055 }
2056 }
2057 None
2058 }
2059
2060 pub(crate) fn push_ready_worker(&mut self, worker_index: usize) {
2061 self.ready_workers.push(worker_index);
2062 }
2063
2064 pub(crate) fn publish_track_meters(&mut self) {
2065 if !self.should_publish_track_meters() {
2066 return;
2067 }
2068 if self.meter_decay_after_stop.is_some() {
2069 self.update_meter_decay_after_stop();
2070 return;
2071 }
2072 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2073 .state_snapshot
2074 .load_full()
2075 .tracks
2076 .iter()
2077 .map(|(name, track)| (name.clone(), track.clone()))
2078 .collect();
2079 let mut snapshot = Vec::with_capacity(tracks.len());
2080 for (name, track) in &tracks {
2081 let linear = self
2082 .track_meter_linear_by_track
2083 .get(name)
2084 .cloned()
2085 .unwrap_or_else(|| track.lock().output_meter_linear());
2086 let output_db = linear
2087 .iter()
2088 .copied()
2089 .map(Self::meter_linear_to_db)
2090 .collect::<Vec<_>>();
2091 snapshot.push((name.clone(), output_db));
2092 }
2093 self.latest_track_meter_snapshot = Arc::new(snapshot);
2094 }
2095
2096 fn record_session_completed_clip_pass(
2097 &mut self,
2098 track_name: String,
2099 scene_index: usize,
2100 clip_id: String,
2101 pass_index: usize,
2102 start_sample: usize,
2103 length_samples: usize,
2104 ) {
2105 let key = (
2106 track_name.clone(),
2107 scene_index,
2108 clip_id.clone(),
2109 pass_index,
2110 start_sample,
2111 );
2112 if self.session_reported_clip_passes.insert(key) {
2113 self.session_completed_clip_passes
2114 .push(crate::meter::SessionCompletedClipPass {
2115 track_name,
2116 scene_index,
2117 clip_id,
2118 pass_index,
2119 start_sample,
2120 length_samples,
2121 });
2122 }
2123 }
2124
2125 fn record_completed_session_scene_span(
2126 &mut self,
2127 tracks: &HashMap<String, crate::state::TrackHandle>,
2128 scene_index: usize,
2129 previous_scene: Option<usize>,
2130 scene_start_sample: usize,
2131 elapsed_samples: usize,
2132 ) {
2133 for (track_name, track) in tracks {
2134 let track = track.lock();
2135 let slot = track.rt.session_slots.get(&scene_index);
2136 let prev_slot = previous_scene.and_then(|scene| track.rt.session_slots.get(&scene));
2137 let playing_clip_id = track
2138 .rt
2139 .playing_session_clips
2140 .last()
2141 .map(|clip| clip.clip_id.as_str());
2142 let clip_id = match (slot, prev_slot) {
2143 (Some(slot), _) if slot.play_enabled => Some(slot.clip_id.as_str()),
2144 (Some(slot), _) if slot.stop_enabled => None,
2145 (_, Some(prev_slot)) if prev_slot.play_enabled => Some(prev_slot.clip_id.as_str()),
2146 (_, Some(prev_slot)) if prev_slot.stop_enabled => None,
2147 _ => playing_clip_id,
2148 }
2149 .filter(|clip_id| !clip_id.is_empty());
2150 let Some(clip_id) = clip_id else { continue };
2151 let clip_length = track
2152 .session_clip_length(clip_id, crate::kind::Kind::Audio)
2153 .or_else(|| track.session_clip_length(clip_id, crate::kind::Kind::MIDI))
2154 .unwrap_or(0);
2155 if clip_length == 0 {
2156 continue;
2157 }
2158 let completed_passes = elapsed_samples / clip_length;
2159 for pass_index in 0..completed_passes {
2160 self.record_session_completed_clip_pass(
2161 track_name.clone(),
2162 scene_index,
2163 clip_id.to_string(),
2164 pass_index,
2165 scene_start_sample.saturating_add(pass_index * clip_length),
2166 clip_length,
2167 );
2168 }
2169 }
2170 }
2171
2172 pub(crate) async fn publish_session_runtime_reports(&mut self) {
2173 let mut current = HashMap::<(String, usize), (SessionSlotState, usize, usize)>::new();
2174 {
2175 let state = self.state_snapshot.load_full();
2176 if let Some((queued_scene, queued_launch_at)) = self.session_scene_queue {
2177 let still_pending = state.tracks.values().any(|track| {
2185 let track = track.lock();
2186 track.rt.pending_session_launches.iter().any(|launch| {
2187 launch.scene_index == queued_scene
2188 && launch.launch_at_sample == queued_launch_at
2189 }) || track
2190 .rt
2191 .playing_session_clips
2192 .iter()
2193 .any(|clip| clip.stop_at_sample == Some(queued_launch_at))
2194 });
2195 if !still_pending && self.session_transport_sample >= queued_launch_at {
2196 if let Some(current_scene) = self.session_current_scene {
2197 let scene_start = self.session_current_scene_start_sample;
2198 let elapsed = queued_launch_at.saturating_sub(scene_start);
2199 self.record_completed_session_scene_span(
2200 &state.tracks,
2201 current_scene,
2202 self.session_current_scene_previous_scene,
2203 scene_start,
2204 elapsed,
2205 );
2206 }
2207 self.session_scene_queue = None;
2208 self.session_current_scene_previous_scene = self.session_current_scene;
2209 self.session_current_scene = Some(queued_scene);
2210 self.session_current_scene_start_sample = queued_launch_at;
2211 self.session_current_scene_length_samples =
2212 self.session_scene_queue_length_samples;
2213 self.session_scene_queue_length_samples = 0;
2214 }
2215 }
2216 if let Some(current_scene) = self.session_current_scene
2217 && self.session_current_scene_length_samples > 0
2218 {
2219 self.record_completed_session_scene_span(
2220 &state.tracks,
2221 current_scene,
2222 self.session_current_scene_previous_scene,
2223 self.session_current_scene_start_sample,
2224 self.session_transport_sample
2225 .saturating_sub(self.session_current_scene_start_sample),
2226 );
2227 }
2228 for (track_name, track) in &state.tracks {
2229 let track = track.lock();
2230 for launch in &track.rt.pending_session_launches {
2231 current.insert(
2232 (track_name.clone(), launch.scene_index),
2233 (SessionSlotState::Queued, 0, 0),
2234 );
2235 }
2236 for clip in &track.rt.playing_session_clips {
2237 if self.session_current_scene_length_samples == 0
2238 && let Some(clip_length) =
2239 track.session_clip_length(&clip.clip_id, clip.kind)
2240 {
2241 let scene_report = self
2242 .session_current_scene
2243 .map(|_| self.session_current_scene_length_samples)
2244 .filter(|length| *length > 0)
2245 .map(|length| {
2246 (
2247 self.session_current_scene.unwrap_or(clip.scene_index),
2248 self.session_current_scene_start_sample,
2249 self.session_transport_sample
2250 .saturating_sub(self.session_current_scene_start_sample),
2251 length,
2252 )
2253 });
2254 let launch_sample = self
2255 .session_transport_sample
2256 .saturating_sub(clip.elapsed_samples);
2257 let (
2258 report_scene_index,
2259 report_start_sample,
2260 report_elapsed,
2261 report_length,
2262 ) = scene_report.unwrap_or((
2263 clip.scene_index,
2264 launch_sample,
2265 clip.elapsed_samples,
2266 clip_length,
2267 ));
2268 if report_length == 0 {
2269 continue;
2270 }
2271 let completed_passes = report_elapsed / report_length;
2272 for pass_index in 0..completed_passes {
2273 self.record_session_completed_clip_pass(
2274 track_name.clone(),
2275 report_scene_index,
2276 clip.clip_id.clone(),
2277 pass_index,
2278 report_start_sample.saturating_add(pass_index * report_length),
2279 report_length,
2280 );
2281 }
2282 }
2283 current.insert(
2284 (track_name.clone(), clip.scene_index),
2285 (
2286 SessionSlotState::Playing,
2287 clip.play_position_samples,
2288 clip.elapsed_samples,
2289 ),
2290 );
2291 }
2292 }
2293 }
2294
2295 if self
2296 .last_session_report_publish
2297 .is_some_and(|t| t.elapsed() < Self::SESSION_RUNTIME_REPORT_INTERVAL)
2298 {
2299 return;
2300 }
2301
2302 let snapshot = self.session_runtime_snapshot_producer.write_buffer();
2303 snapshot.session_sample = self.session_transport_sample;
2304 snapshot.slots.clear();
2305 snapshot.slots.extend(current.iter().map(
2306 |((track_name, scene_index), (state, play_position_samples, elapsed_samples))| {
2307 crate::meter::SessionRuntimeSlotSnapshot {
2308 track_name: track_name.clone(),
2309 scene_index: *scene_index,
2310 state: *state,
2311 play_position_samples: *play_position_samples,
2312 elapsed_samples: *elapsed_samples,
2313 }
2314 },
2315 ));
2316 snapshot.completed_clip_passes = self.session_completed_clip_passes.clone();
2317 snapshot.current_scene = self.session_current_scene;
2318 self.session_runtime_snapshot_producer.publish();
2319 self.last_session_report_publish = Some(Instant::now());
2320 }
2321
2322 pub(crate) async fn publish_clap_state_dirty(&mut self) {
2323 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2324 .state_snapshot
2325 .load_full()
2326 .tracks
2327 .iter()
2328 .map(|(name, track)| (name.clone(), track.clone()))
2329 .collect();
2330 for (track_name, track) in &tracks {
2331 let dirty = track.lock().take_dirty_clap_instances();
2332 for instance_id in dirty {
2333 self.notify_clients(Ok(Action::TrackClapStateDirty {
2334 track_name: track_name.clone(),
2335 instance_id,
2336 }))
2337 .await;
2338 }
2339 }
2340 }
2341
2342 pub(crate) fn reset_meters_after_stop(&mut self) {
2343 self.last_hw_out_meter_publish = None;
2344 self.last_track_meter_publish = None;
2345 self.last_meter_snapshot_publish = None;
2346 #[cfg(unix)]
2347 {
2348 self.last_hw_out_meter_linear.clear();
2349 }
2350
2351 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2352 .state_snapshot
2353 .load_full()
2354 .tracks
2355 .iter()
2356 .map(|(name, track)| (name.clone(), track.clone()))
2357 .collect();
2358 let mut track_linear = Vec::with_capacity(tracks.len());
2359 for (name, track) in tracks {
2360 let linear = self
2361 .track_meter_linear_by_track
2362 .get(&name)
2363 .cloned()
2364 .unwrap_or_else(|| track.lock().output_meter_linear());
2365 track_linear.push((name, linear));
2366 }
2367 let hw_out_linear = if self.hw_out_peak_hold_linear.is_empty() {
2368 self.latest_hw_out_meter_db
2369 .iter()
2370 .copied()
2371 .map(Self::meter_db_to_linear)
2372 .collect()
2373 } else {
2374 self.hw_out_peak_hold_linear.clone()
2375 };
2376 self.meter_decay_after_stop = Some(MeterDecay {
2377 started_at: Instant::now(),
2378 hw_out_linear,
2379 track_linear,
2380 });
2381 self.update_meter_decay_after_stop();
2382 self.publish_meter_snapshot();
2383 }
2384
2385 pub(crate) fn update_meter_decay_after_stop(&mut self) {
2386 let Some(decay) = self.meter_decay_after_stop.as_ref() else {
2387 return;
2388 };
2389 let elapsed = decay.started_at.elapsed();
2390 if elapsed >= Self::METER_DECAY_AFTER_STOP {
2391 self.finish_meter_decay_after_stop();
2392 return;
2393 }
2394
2395 let remaining = 1.0 - (elapsed.as_secs_f32() / Self::METER_DECAY_AFTER_STOP.as_secs_f32());
2396 let hw_out_linear = decay
2397 .hw_out_linear
2398 .iter()
2399 .copied()
2400 .map(|value| value * remaining)
2401 .collect::<Vec<_>>();
2402 self.latest_hw_out_meter_db = Arc::new(
2403 hw_out_linear
2404 .iter()
2405 .copied()
2406 .map(Self::meter_linear_to_db)
2407 .collect(),
2408 );
2409 self.hw_out_peak_hold_linear = hw_out_linear;
2410
2411 let mut track_linear_by_track = HashMap::with_capacity(decay.track_linear.len());
2412 let mut snapshot = Vec::with_capacity(decay.track_linear.len());
2413 for (name, start_linear) in &decay.track_linear {
2414 let linear = start_linear
2415 .iter()
2416 .copied()
2417 .map(|value| value * remaining)
2418 .collect::<Vec<_>>();
2419 let output_db = linear
2420 .iter()
2421 .copied()
2422 .map(Self::meter_linear_to_db)
2423 .collect::<Vec<_>>();
2424 track_linear_by_track.insert(name.clone(), linear);
2425 snapshot.push((name.clone(), output_db));
2426 }
2427 self.track_meter_linear_by_track = track_linear_by_track;
2428 self.latest_track_meter_snapshot = Arc::new(snapshot);
2429 }
2430
2431 pub(crate) fn finish_meter_decay_after_stop(&mut self) {
2432 self.meter_decay_after_stop = None;
2433 self.hw_out_peak_hold_linear.fill(0.0);
2434 let hw_channels = self.latest_hw_out_meter_db.len();
2435 self.latest_hw_out_meter_db = Arc::new(vec![-90.0; hw_channels]);
2436
2437 let tracks: Vec<(String, crate::state::TrackHandle)> = self
2438 .state_snapshot
2439 .load_full()
2440 .tracks
2441 .iter()
2442 .map(|(name, track)| (name.clone(), track.clone()))
2443 .collect();
2444 self.track_meter_linear_by_track.clear();
2445 let mut snapshot = Vec::with_capacity(tracks.len());
2446 for (name, track) in tracks {
2447 let mut t = track.lock();
2448 t.clear_output_meters();
2449 let width = t.output_meter_linear().len();
2450 let zero_linear = vec![0.0; width];
2451 self.track_meter_linear_by_track
2452 .insert(name.clone(), zero_linear);
2453 snapshot.push((name, vec![-90.0; width]));
2454 }
2455 self.latest_track_meter_snapshot = Arc::new(snapshot);
2456 self.publish_meter_snapshot();
2457 }
2458
2459 pub(crate) fn publish_meter_snapshot_if_due(&mut self) {
2460 let now = Instant::now();
2461 if self
2462 .last_meter_snapshot_publish
2463 .is_some_and(|last| now.duration_since(last) < Self::METER_PUBLISH_INTERVAL)
2464 {
2465 return;
2466 }
2467 self.last_meter_snapshot_publish = Some(now);
2468 self.update_meter_decay_after_stop();
2469 self.publish_meter_snapshot();
2470 }
2471
2472 pub(crate) fn publish_meter_snapshot(&mut self) {
2473 self.update_hw_out_lufs_readout();
2476
2477 let snapshot = self.meter_snapshot_producer.write_buffer();
2478 snapshot.hw_out_db.clear();
2479 snapshot
2480 .hw_out_db
2481 .extend(self.latest_hw_out_meter_db.iter().copied());
2482 snapshot.hw_out_lufs = self.latest_hw_out_lufs;
2483 snapshot.track_meters.clear();
2484 snapshot
2485 .track_meters
2486 .extend(self.latest_track_meter_snapshot.iter().cloned());
2487 self.meter_snapshot_producer.publish();
2488 }
2489
2490 pub(crate) fn publish_transport_snapshot(&mut self) {
2491 let snapshot = self.transport_snapshot_producer.write_buffer();
2492 snapshot.sample = self.transport_sample;
2493 snapshot.tempo_bpm = self.tempo_bpm;
2494 snapshot.playing = self.playing;
2495 snapshot.transport_running = self.transport_running;
2496 snapshot.tsig_num = self.tsig_num;
2497 snapshot.tsig_denom = self.tsig_denom;
2498 self.transport_snapshot_producer.publish();
2499 }
2500
2501 pub(crate) async fn handle_request(&mut self, a: Action) {
2502 match a {
2503 Action::Log { source, message } => {
2504 self.notify_clients(Ok(Action::Log { source, message }))
2505 .await;
2506 }
2507 Action::Undo => {
2508 let actions = match self.history.undo() {
2509 Some(actions) => actions,
2510 None => {
2511 self.notify_clients(Ok(Action::Undo)).await;
2512 self.notify_clients(Ok(Action::HistoryState {
2513 dirty: self.history.is_dirty(),
2514 }))
2515 .await;
2516 return;
2517 }
2518 };
2519
2520 let was_suspended = self.history_suspended;
2521 self.history_suspended = true;
2522 for action in actions {
2523 self.handle_request_inner(action, false).await;
2524 }
2525 self.history_suspended = was_suspended;
2526 self.notify_clients(Ok(Action::Undo)).await;
2527 self.notify_clients(Ok(Action::HistoryState {
2528 dirty: self.history.is_dirty(),
2529 }))
2530 .await;
2531 }
2532 Action::Redo => {
2533 let actions = match self.history.redo() {
2534 Some(actions) => actions,
2535 None => {
2536 self.notify_clients(Ok(Action::Redo)).await;
2537 self.notify_clients(Ok(Action::HistoryState {
2538 dirty: self.history.is_dirty(),
2539 }))
2540 .await;
2541 return;
2542 }
2543 };
2544
2545 let was_suspended = self.history_suspended;
2546 self.history_suspended = true;
2547 for action in actions {
2548 self.handle_request_inner(action, false).await;
2549 }
2550 self.history_suspended = was_suspended;
2551 self.notify_clients(Ok(Action::Redo)).await;
2552 self.notify_clients(Ok(Action::HistoryState {
2553 dirty: self.history.is_dirty(),
2554 }))
2555 .await;
2556 }
2557 Action::ApplyGroupedActions(actions) => {
2558 self.handle_request_inner(Action::BeginHistoryGroup, true)
2559 .await;
2560 for action in actions {
2561 self.handle_request_inner(action, true).await;
2562 }
2563 self.handle_request_inner(Action::EndHistoryGroup, true)
2564 .await;
2565 }
2566 Action::Session(_) => {
2567 self.handle_request_inner(a, false).await;
2568 }
2569 other => {
2570 self.handle_request_inner(other, true).await;
2571 }
2572 }
2573 self.publish_state_snapshot();
2574 }
2575
2576 pub(crate) async fn handle_quit(&mut self, a: Action) {
2577 self.flush_recordings().await;
2578 if let Some(mut worker) = self.hw_worker.take() {
2586 let panic_events = self.panic_events_for_all_hw_midi_outputs();
2589 if !panic_events.is_empty() {
2590 let _ = worker.tx.send(Message::HWMidiOutEvents(panic_events)).await;
2591 }
2592 if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2595 error!("Error sending quit message to HW worker: {e}");
2596 }
2597 if let Some(handle) = worker.handle.take() {
2598 handle
2599 .await
2600 .unwrap_or_else(|e| error!("Error waiting for HW worker to quit: {e}"));
2601 }
2602 }
2603 if let Some(hw) = self.hw_driver.as_mut() {
2609 hw.close_fds();
2610 }
2611 if let Some(midi_hub) = self.midi_hub.as_mut() {
2612 midi_hub.close_all();
2613 }
2614 self.hw_driver = None;
2615 self.hw_driver_info = None;
2616 self.hw_input_ports.clear();
2617 self.hw_output_ports.clear();
2618 self.notify_clients(Ok(Action::Quit)).await;
2619 self.ready_workers.clear();
2620 while !self.workers.is_empty() {
2621 let mut worker = self.workers.remove(0);
2622 if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2623 error!("Error sending quit message to worker: {e}");
2624 }
2625 if let Some(handle) = worker.handle.take() {
2626 handle
2627 .await
2628 .unwrap_or_else(|e| error!("Error waiting for worker to quit: {e}"));
2629 }
2630 }
2631 #[cfg(unix)]
2632 {
2633 self.jack_runtime = None;
2634 }
2635 self.osc_server = None;
2636 }
2637
2638 #[inline]
2639 pub(crate) fn box_bool<'a>(
2640 fut: impl std::future::Future<Output = bool> + Send + 'a,
2641 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
2642 Box::pin(fut)
2643 }
2644
2645 pub(crate) async fn handle_request_inner(
2646 &mut self,
2647 mut action_to_process: Action,
2648 record_history: bool,
2649 ) {
2650 let a = action_to_process.clone();
2651 let suppress_timing_history = self.playing
2652 && matches!(
2653 &action_to_process,
2654 Action::SetTempo(_) | Action::SetTimeSignature { .. } | Action::SetTempoMap { .. }
2655 );
2656 let mut inverse_actions = self.prepare_inverse_actions(
2657 &action_to_process,
2658 record_history,
2659 suppress_timing_history,
2660 );
2661
2662 match action_to_process {
2663 Action::Play => {
2664 if Self::box_bool(self.handle_play(a.clone())).await {
2665 return;
2666 }
2667 }
2668 Action::Pause => {
2669 if Self::box_bool(self.handle_pause(a.clone())).await {
2670 return;
2671 }
2672 }
2673 Action::Stop => {
2674 if Self::box_bool(self.handle_stop(a.clone())).await {
2675 return;
2676 }
2677 }
2678 Action::SessionPlay => {
2679 if Self::box_bool(self.handle_session_play(a.clone())).await {
2680 return;
2681 }
2682 }
2683 Action::JumpToEnd => {
2684 self.transport_sample = self.normalize_transport_sample(self.session_end_sample());
2685 self.publish_transport_snapshot();
2686 self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
2687 .await;
2688 }
2689 Action::Panic => {
2690 if Self::box_bool(self.handle_panic(a.clone())).await {
2691 return;
2692 }
2693 }
2694 Action::Session(ref session_action) => {
2695 self.handle_session_action(session_action.clone()).await;
2696 }
2697 Action::SessionRuntimeReport { .. } => {}
2698 Action::SessionMidiLearnTriggered { .. } => {}
2699 Action::SetClipPlaybackEnabled(enabled) => {
2700 self.clip_playback_enabled = enabled;
2701 for track in self.state_snapshot.load_full().tracks.values() {
2702 track.lock().set_clip_playback_enabled(enabled);
2703 }
2704 }
2705 Action::SetSessionClipPlaybackEnabled(enabled) => {
2706 self.session_clip_playback_enabled = enabled;
2707 for track in self.state_snapshot.load_full().tracks.values() {
2708 track.lock().set_session_clip_playback_enabled(enabled);
2709 }
2710 }
2711 Action::TransportPosition(..) => {
2712 if Self::box_bool(self.handle_transport_position(a.clone())).await {
2713 return;
2714 }
2715 }
2716 Action::SetLoopEnabled(enabled) => {
2717 self.loop_enabled = enabled && self.loop_range_samples.is_some();
2718 self.notified_loop_wrap_sample = None;
2719 }
2720 Action::SetLoopRange(..) => {
2721 if Self::box_bool(self.handle_set_loop_range(a.clone())).await {
2722 return;
2723 }
2724 }
2725 Action::SetPunchEnabled(enabled) => {
2726 self.punch_enabled = enabled && self.punch_range_samples.is_some();
2727 }
2728 Action::SetPunchRange(range) => {
2729 self.punch_range_samples = range.and_then(|(start, end)| {
2730 if end > start {
2731 Some((start, end))
2732 } else {
2733 None
2734 }
2735 });
2736 self.punch_enabled = self.punch_range_samples.is_some();
2737 }
2738 Action::SetMetronomeEnabled(enabled) => {
2739 self.metronome_enabled = enabled;
2740 if enabled {
2741 self.ensure_metronome_track().await;
2742 }
2743 if let Some(track) = self
2744 .state_snapshot
2745 .load_full()
2746 .tracks
2747 .get(Self::METRONOME_TRACK)
2748 .cloned()
2749 {
2750 track.lock().set_metronome_enabled(enabled);
2751 }
2752 }
2753 Action::SetTempo(bpm) => {
2754 self.tempo_bpm = bpm.max(1.0);
2755 self.publish_transport_snapshot();
2756 }
2757 Action::SetTimeSignature {
2758 numerator,
2759 denominator,
2760 } => {
2761 self.tsig_num = numerator.max(1);
2762 self.tsig_denom = denominator.max(1);
2763 self.publish_transport_snapshot();
2764 }
2765 Action::SetTempoMap {
2766 ref tempo_points,
2767 ref time_signature_points,
2768 } => {
2769 self.tempo_points = tempo_points.clone();
2770 self.time_signature_points = time_signature_points.clone();
2771 self.update_global_tempo_from_map();
2772 self.publish_transport_snapshot();
2773 }
2774 Action::SetOscEnabled(enabled) => {
2775 if let Err(err) = self.set_osc_enabled_with(enabled, OscServer::start) {
2776 self.notify_clients(Err(err)).await;
2777 }
2778 }
2779 Action::SetRecordEnabled(..) => {
2780 if Self::box_bool(self.handle_set_record_enabled(a.clone())).await {
2781 return;
2782 }
2783 }
2784 Action::SetModulators(ref modulators) => {
2785 self.modulators = modulators.clone();
2786 let echoes = self.apply_modulators(self.active_transport_sample());
2787 for action in echoes {
2788 self.notify_clients(Ok(action)).await;
2789 }
2790 }
2791 Action::SetTrackAutomationLanes {
2792 ref track_name,
2793 ref lanes,
2794 mode,
2795 } => {
2796 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
2797 let mut track = track.lock();
2798 track.automation_lanes = lanes.clone();
2799 track.set_automation_mode(mode);
2800 }
2801 }
2802 Action::TrackAutomationToggleLane { .. } => {
2803 if Self::box_bool(self.handle_track_automation_toggle_lane(a.clone())).await {
2804 return;
2805 }
2806 }
2807 Action::TrackAutomationInsertPoint { .. } => {
2808 if Self::box_bool(self.handle_track_automation_insert_point(a.clone())).await {
2809 return;
2810 }
2811 }
2812 Action::TrackAutomationDeletePoint { .. } => {
2813 if Self::box_bool(self.handle_track_automation_delete_point(a.clone())).await {
2814 return;
2815 }
2816 }
2817 Action::TrackAutomationSetMode {
2818 ref track_name,
2819 mode,
2820 } => {
2821 if let Some(track) = self
2822 .state_snapshot
2823 .load_full()
2824 .tracks
2825 .get(track_name)
2826 .cloned()
2827 {
2828 track.lock().set_automation_mode(mode);
2829 }
2830 }
2831 Action::RequestTrackList => {
2832 let names: Vec<String> = self
2833 .state_snapshot
2834 .load_full()
2835 .tracks
2836 .keys()
2837 .cloned()
2838 .collect();
2839 self.notify_clients(Ok(Action::TrackList(names))).await;
2840 }
2841 Action::TrackList(_) => {}
2842 Action::RequestTransportState => {
2843 self.notify_clients(Ok(Action::TransportState {
2844 sample: self.transport_sample,
2845 tempo_bpm: self.tempo_bpm,
2846 playing: self.playing,
2847 paused: !self.transport_running && self.playing,
2848 tsig_num: self.tsig_num,
2849 tsig_denom: self.tsig_denom,
2850 }))
2851 .await;
2852 }
2853 Action::TransportState { .. } => {}
2854 Action::SetStepRecording(enabled) => {
2855 self.step_recording_enabled = enabled;
2856 }
2857 Action::BeginHistoryGroup if self.history_group.is_none() => {
2858 self.history_group = Some(UndoEntry {
2859 forward_actions: vec![],
2860 inverse_actions: vec![],
2861 });
2862 }
2863 Action::EndHistoryGroup => {
2864 if let Some(mut group) = self.history_group.take()
2865 && !group.forward_actions.is_empty()
2866 && !group.inverse_actions.is_empty()
2867 {
2868 let mut add_tracks = Vec::new();
2869 let mut connections = Vec::new();
2870 let mut rest = Vec::new();
2871 for action in group.inverse_actions {
2872 if matches!(action, Action::AddTrack { .. }) {
2873 add_tracks.push(action);
2874 } else if matches!(action, Action::Connect { .. }) {
2875 connections.push(action);
2876 } else {
2877 rest.push(action);
2878 }
2879 }
2880 group.inverse_actions = add_tracks;
2881 group.inverse_actions.extend(rest);
2882 group.inverse_actions.extend(connections);
2883 self.history.record(group);
2884 }
2885 }
2886 Action::SetSessionPath(ref path) => {
2887 self.session_dir = Some(Path::new(path).to_path_buf());
2888 self.ensure_session_subdirs();
2889 #[cfg(unix)]
2890 let _lv2_dir = self.session_plugins_dir();
2891 for track in self.state_snapshot.load_full().tracks.values() {
2892 track.lock().set_session_base_dir(self.session_dir.clone());
2893 }
2894 }
2895 Action::MarkHistorySavePoint => {
2896 self.history.mark_save_point();
2897 self.notify_clients(Ok(Action::HistoryState {
2898 dirty: self.history.is_dirty(),
2899 }))
2900 .await;
2901 }
2902 Action::ClearHistory => {
2903 self.history.clear();
2904 self.history.mark_save_point();
2905 }
2906 Action::BeginSessionRestore => {
2907 self.history_suspended = true;
2908 self.history.clear();
2909 }
2910 Action::EndSessionRestore => {
2911 self.history.clear();
2912 self.history_suspended = false;
2913 self.preload_track_clips_spawn();
2914 }
2915 Action::Quit => {
2916 self.handle_quit(a.clone()).await;
2917 return;
2918 }
2919 Action::AddTrack { .. } => {
2920 self.handle_add_track(a.clone()).await;
2921 }
2922 Action::TrackAddAudioInput(..) => {
2923 if Self::box_bool(self.handle_track_add_audio_input(a.clone())).await {
2924 return;
2925 }
2926 }
2927 Action::TrackAddAudioOutput(..) => {
2928 if Self::box_bool(self.handle_track_add_audio_output(a.clone())).await {
2929 return;
2930 }
2931 }
2932 Action::TrackRemoveAudioInput(..) => {
2933 if Self::box_bool(self.handle_track_remove_audio_input(a.clone())).await {
2934 return;
2935 }
2936 }
2937 Action::TrackRemoveAudioOutput(..) => {
2938 if Self::box_bool(self.handle_track_remove_audio_output(a.clone())).await {
2939 return;
2940 }
2941 }
2942 Action::RenameTrack { .. } => {
2943 if Self::box_bool(self.handle_rename_track(a.clone())).await {
2944 return;
2945 }
2946 }
2947 Action::RemoveTrack(ref name) => {
2948 self.handle_remove_track(name.clone(), record_history).await;
2949 inverse_actions = None;
2950 }
2951 Action::TrackLevel(ref name, level) => {
2952 if name == "hw:out" {
2953 self.hw_out_level_db = level;
2954 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2955 track.lock().set_level(level);
2956 }
2957 }
2958 Action::TrackBalance(ref name, balance) => {
2959 if name == "hw:out" {
2960 self.hw_out_balance = balance.clamp(-1.0, 1.0);
2961 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2962 track.lock().set_balance(balance);
2963 }
2964 }
2965 Action::TrackAutomationLevel(ref name, level) => {
2966 tracing::debug!(%name, level, "engine received TrackAutomationLevel");
2967 if name == "hw:out" {
2968 self.hw_out_level_db = level;
2969 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2970 track.lock().set_level(level);
2971 }
2972 }
2973 Action::TrackAutomationBalance(ref name, balance) => {
2974 if name == "hw:out" {
2975 self.hw_out_balance = balance.clamp(-1.0, 1.0);
2976 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2977 track.lock().set_balance(balance);
2978 }
2979 }
2980 Action::TrackMidiCc { .. } => {
2981 if Self::box_bool(self.handle_track_midi_cc(a.clone())).await {
2982 return;
2983 }
2984 }
2985 Action::RequestMeterSnapshot => {
2986 self.update_meter_decay_after_stop();
2987 self.notify_clients(Ok(Action::MeterSnapshot {
2988 hw_out_db: self.latest_hw_out_meter_db.clone(),
2989 hw_out_lufs: self.latest_hw_out_lufs,
2990 track_meters: self.latest_track_meter_snapshot.clone(),
2991 }))
2992 .await;
2993 return;
2994 }
2995 Action::TrackMeters { .. } => {}
2996 Action::MeterSnapshot { .. } => {}
2997 Action::TrackToggleArm(..) => {
2998 if Self::box_bool(self.handle_track_toggle_arm(a.clone())).await {
2999 return;
3000 }
3001 }
3002 Action::TrackToggleMute(ref name) => {
3003 if name == "hw:out" {
3004 self.hw_out_muted = !self.hw_out_muted;
3005 } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3006 track.lock().mute();
3007 }
3008 }
3009 Action::TrackTogglePhase(ref name) => {
3010 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3011 track.lock().invert_phase();
3012 }
3013 }
3014 Action::TrackToggleSolo(ref name) => {
3015 if name == "hw:out" {
3016 return;
3017 }
3018 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3019 track.lock().solo();
3020 }
3021 }
3022 Action::TrackToggleMaster(ref name) => {
3023 if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3024 track.lock().toggle_master();
3025 }
3026 }
3027 Action::TrackToggleInputMonitor {
3028 ref track_name,
3029 lane,
3030 } => {
3031 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3032 track.lock().toggle_input_monitor(lane);
3033 }
3034 }
3035 Action::TrackToggleDiskMonitor {
3036 ref track_name,
3037 lane,
3038 } => {
3039 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3040 track.lock().toggle_disk_monitor(lane);
3041 }
3042 }
3043 Action::TrackToggleMidiInputMonitor {
3044 ref track_name,
3045 lane,
3046 } => {
3047 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3048 track.lock().toggle_midi_input_monitor(lane);
3049 }
3050 }
3051 Action::TrackToggleMidiDiskMonitor {
3052 ref track_name,
3053 lane,
3054 } => {
3055 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3056 track.lock().toggle_midi_disk_monitor(lane);
3057 }
3058 }
3059 Action::TrackSetColor {
3060 ref track_name,
3061 color,
3062 } => {
3063 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3064 track.lock().color = color;
3065 }
3066 }
3067 Action::TrackArmMidiLearn {
3068 ref track_name,
3069 target,
3070 } => {
3071 if let Err(e) = self.track_handle_or_err(track_name) {
3072 self.notify_clients(Err(e)).await;
3073 return;
3074 }
3075 self.pending_midi_learn = Some((track_name.clone(), target, None));
3076 }
3077 Action::GlobalArmMidiLearn { target } => {
3078 self.pending_global_midi_learn = Some(target);
3079 }
3080 Action::SessionArmMidiLearn { ref target } => {
3081 self.pending_session_midi_learn = Some(target.clone());
3082 }
3083 Action::TrackSetMidiLearnBinding { .. } => {
3084 if Self::box_bool(self.handle_track_set_midi_learn_binding(a.clone())).await {
3085 return;
3086 }
3087 }
3088 Action::SetGlobalMidiLearnBinding { .. } => {
3089 if Self::box_bool(self.handle_set_global_midi_learn_binding(a.clone())).await {
3090 return;
3091 }
3092 }
3093 Action::SetSessionMidiLearnBinding { .. } => {
3094 if Self::box_bool(self.handle_set_session_midi_learn_binding(a.clone())).await {
3095 return;
3096 }
3097 }
3098 Action::TrackSetFolder { .. } => {
3099 if Self::box_bool(self.handle_track_set_folder(a.clone())).await {
3100 return;
3101 }
3102 }
3103 Action::TrackSetParent {
3104 ref track_name,
3105 ref parent_name,
3106 } => {
3107 self.handle_track_set_parent(track_name.as_str(), parent_name.as_deref())
3108 .await;
3109 }
3110 Action::TrackToggleFolder { .. } => {
3111 if Self::box_bool(self.handle_track_toggle_folder(a.clone())).await {
3112 return;
3113 }
3114 }
3115 Action::TrackSetMidiLaneChannel { .. } => {
3116 if Self::box_bool(self.handle_track_set_midi_lane_channel(a.clone())).await {
3117 return;
3118 }
3119 }
3120 Action::TrackSetMpeZone { .. } => {
3121 if Self::box_bool(self.handle_track_set_mpe_zone(a.clone())).await {
3122 return;
3123 }
3124 }
3125 Action::TrackSetMpePitchBendSensitivity { .. } => {
3126 if Self::box_bool(self.handle_track_set_mpe_pitch_bend_sensitivity(a.clone())).await
3127 {
3128 return;
3129 }
3130 }
3131 Action::TrackSetFrozen { .. } => {
3132 if Self::box_bool(self.handle_track_set_frozen(a.clone())).await {
3133 return;
3134 }
3135 }
3136 Action::TrackSetSessionSlot { .. } => {
3137 if Self::box_bool(self.handle_track_set_session_slot(a.clone())).await {
3138 return;
3139 }
3140 }
3141 Action::TrackSetSessionSlotPlayEnabled { .. } => {
3142 if self
3143 .handle_track_set_session_slot_play_enabled(a.clone())
3144 .await
3145 {
3146 return;
3147 }
3148 }
3149 Action::TrackSetSessionSlotStopEnabled { .. } => {
3150 if self
3151 .handle_track_set_session_slot_stop_enabled(a.clone())
3152 .await
3153 {
3154 return;
3155 }
3156 }
3157 Action::TrackOfflineBounce { .. } => {
3158 self.handle_track_offline_bounce(action_to_process).await;
3159 return;
3160 }
3161 Action::TrackOfflineBounceCancel { .. } => {}
3162 Action::TrackOfflineBounceCancelAll => {}
3163 Action::TrackOfflineBounceCanceled { .. } => {}
3164 Action::TrackOfflineBounceProgress { .. } => {}
3165 Action::PianoKey {
3166 ref track_name,
3167 note,
3168 velocity,
3169 on,
3170 } => {
3171 if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3172 let status = if on { 0x90 } else { 0x80 };
3173 let event = MidiEvent::new(0, vec![status, note.min(127), velocity.min(127)]);
3174 track.lock().push_hw_midi_events(&[event]);
3175 }
3176 }
3177 Action::ModifyMidiNotes { .. }
3178 | Action::ModifyMidiControllers { .. }
3179 | Action::DeleteMidiControllers { .. }
3180 | Action::InsertMidiControllers { .. }
3181 | Action::DeleteMidiNotes { .. }
3182 | Action::InsertMidiNotes { .. } => {
3183 if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3184 self.notify_clients(Err(e)).await;
3185 return;
3186 }
3187 }
3188 Action::SetMidiSysExEvents { .. } => {
3189 if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3190 self.notify_clients(Err(e)).await;
3191 return;
3192 }
3193 }
3194 Action::TrackClearDefaultPassthrough { .. } => {
3195 if Self::box_bool(self.handle_track_clear_default_passthrough(a.clone())).await {
3196 return;
3197 }
3198 }
3199 Action::TrackClearPlugins { .. } => {
3200 if Self::box_bool(self.handle_track_clear_plugins(a.clone())).await {
3201 return;
3202 }
3203 }
3204 #[cfg(unix)]
3205 Action::TrackSetLv2PluginState { .. } => {
3206 if Self::box_bool(self.handle_track_set_lv2_plugin_state(a.clone())).await {
3207 return;
3208 }
3209 }
3210 #[cfg(unix)]
3211 Action::ClipSetLv2PluginState { ref track_name, .. } => {
3212 self.notify_clients(Err(format!(
3213 "Track '{}': clip LV2 plugin state changes are not supported",
3214 track_name
3215 )))
3216 .await;
3217 }
3218 Action::TrackGetClapNoteNames { .. } => {
3219 if Self::box_bool(self.handle_track_get_clap_note_names(a.clone())).await {
3220 return;
3221 }
3222 }
3223 #[cfg(unix)]
3224 Action::TrackGetLv2Midnam { .. } => {
3225 if Self::box_bool(self.handle_track_get_lv2_midnam(a.clone())).await {
3226 return;
3227 }
3228 }
3229 Action::TrackGetPluginGraph { .. } => {
3230 if Self::box_bool(self.handle_track_get_plugin_graph(a.clone())).await {
3231 return;
3232 }
3233 }
3234 Action::TrackPluginGraph { .. } => {}
3235 Action::TrackConnectPluginAudio { .. } => {
3236 if Self::box_bool(self.handle_track_connect_plugin_audio(a.clone())).await {
3237 return;
3238 }
3239 }
3240 Action::TrackConnectPluginMidi { .. } => {
3241 if Self::box_bool(self.handle_track_connect_plugin_midi(a.clone())).await {
3242 return;
3243 }
3244 }
3245 Action::TrackDisconnectPluginAudio { .. } => {
3246 if Self::box_bool(self.handle_track_disconnect_plugin_audio(a.clone())).await {
3247 return;
3248 }
3249 }
3250 Action::TrackDisconnectPluginMidi { .. } => {
3251 if Self::box_bool(self.handle_track_disconnect_plugin_midi(a.clone())).await {
3252 return;
3253 }
3254 }
3255 Action::TrackConnectAudio { .. } => {
3256 if Self::box_bool(self.handle_track_connect_audio(a.clone())).await {
3257 return;
3258 }
3259 }
3260 Action::TrackDisconnectAudio { .. } => {
3261 if Self::box_bool(self.handle_track_disconnect_audio(a.clone())).await {
3262 return;
3263 }
3264 }
3265 Action::TrackConnectMidi { .. } => {
3266 if Self::box_bool(self.handle_track_connect_midi(a.clone())).await {
3267 return;
3268 }
3269 }
3270 Action::TrackDisconnectMidi { .. } => {
3271 if Self::box_bool(self.handle_track_disconnect_midi(a.clone())).await {
3272 return;
3273 }
3274 }
3275 #[cfg(unix)]
3276 Action::ListLv2Plugins => {
3277 if Self::box_bool(self.handle_list_lv2_plugins(a.clone())).await {
3278 return;
3279 }
3280 }
3281 #[cfg(unix)]
3282 Action::Lv2Plugins(_) => {}
3283 #[cfg(unix)]
3284 Action::Lv2PluginsUnavailable { .. } => {}
3285 Action::ListVst3Plugins => {
3286 if Self::box_bool(self.handle_list_vst3_plugins(a.clone())).await {
3287 return;
3288 }
3289 }
3290 Action::Vst3Plugins(_) => {}
3291 Action::Vst3PluginsUnavailable { .. } => {}
3292 Action::ListClapPlugins => {
3293 if Self::box_bool(self.handle_list_clap_plugins(a.clone())).await {
3294 return;
3295 }
3296 }
3297 Action::ListClapPluginsWithCapabilities => {
3298 if self
3299 .handle_list_clap_plugins_with_capabilities(a.clone())
3300 .await
3301 {
3302 return;
3303 }
3304 }
3305 Action::ClapPlugins(_) => {}
3306 Action::ClapPluginsUnavailable { .. } => {}
3307 Action::TrackLoadClapPlugin {
3308 ref track_name,
3309 ref plugin_id,
3310 instance_id,
3311 } => {
3312 if self
3313 .handle_track_load_clap_plugin(
3314 track_name.as_str(),
3315 plugin_id.as_str(),
3316 instance_id,
3317 )
3318 .await
3319 {
3320 return;
3321 }
3322 }
3323 Action::TrackUnloadClapPlugin {
3324 ref track_name,
3325 ref plugin_id,
3326 } => {
3327 if self
3328 .handle_track_unload_clap_plugin(track_name.as_str(), plugin_id.as_str())
3329 .await
3330 {
3331 return;
3332 }
3333 }
3334 Action::TrackUnloadClapPluginInstance {
3335 ref track_name,
3336 instance_id,
3337 } => {
3338 if self
3339 .handle_track_unload_clap_plugin_instance(track_name.as_str(), instance_id)
3340 .await
3341 {
3342 return;
3343 }
3344 }
3345 Action::TrackShowClapGui { .. } => {
3346 if Self::box_bool(self.handle_track_show_clap_gui(a.clone())).await {
3347 return;
3348 }
3349 }
3350 Action::ClipShowClapGui { .. } => {
3351 if Self::box_bool(self.handle_clip_show_clap_gui(a.clone())).await {
3352 return;
3353 }
3354 }
3355 Action::TrackLoadVst3Plugin {
3356 ref track_name,
3357 ref plugin_id,
3358 instance_id,
3359 } => {
3360 if self
3361 .handle_track_load_vst3_plugin(
3362 track_name.as_str(),
3363 plugin_id.as_str(),
3364 instance_id,
3365 )
3366 .await
3367 {
3368 return;
3369 }
3370 }
3371 Action::TrackUnloadVst3Plugin {
3372 ref track_name,
3373 ref plugin_id,
3374 } => {
3375 if self
3376 .handle_track_unload_vst3_plugin(track_name.as_str(), plugin_id.as_str())
3377 .await
3378 {
3379 return;
3380 }
3381 }
3382 Action::TrackUnloadVst3PluginInstance {
3383 ref track_name,
3384 instance_id,
3385 } => {
3386 if self
3387 .handle_track_unload_vst3_plugin_instance(track_name.as_str(), instance_id)
3388 .await
3389 {
3390 return;
3391 }
3392 }
3393 Action::TrackShowVst3Gui { .. } => {
3394 if Self::box_bool(self.handle_track_show_vst3_gui(a.clone())).await {
3395 return;
3396 }
3397 }
3398 Action::ClipShowVst3Gui { .. } => {
3399 if Self::box_bool(self.handle_clip_show_vst3_gui(a.clone())).await {
3400 return;
3401 }
3402 }
3403 #[cfg(unix)]
3404 Action::TrackLoadLv2Plugin {
3405 ref track_name,
3406 ref plugin_uri,
3407 instance_id,
3408 } => {
3409 if self
3410 .handle_track_load_lv2_plugin(
3411 track_name.as_str(),
3412 plugin_uri.as_str(),
3413 instance_id,
3414 )
3415 .await
3416 {
3417 return;
3418 }
3419 }
3420 #[cfg(unix)]
3421 Action::TrackUnloadLv2Plugin {
3422 ref track_name,
3423 ref plugin_uri,
3424 } => {
3425 if self
3426 .handle_track_unload_lv2_plugin(track_name.as_str(), plugin_uri.as_str())
3427 .await
3428 {
3429 return;
3430 }
3431 }
3432 #[cfg(unix)]
3433 Action::TrackUnloadLv2PluginInstance {
3434 ref track_name,
3435 instance_id,
3436 } => {
3437 if self
3438 .handle_track_unload_lv2_plugin_instance(track_name.as_str(), instance_id)
3439 .await
3440 {
3441 return;
3442 }
3443 }
3444 #[cfg(unix)]
3445 Action::TrackShowLv2Gui { .. } => {
3446 if Self::box_bool(self.handle_track_show_lv2_gui(a.clone())).await {
3447 return;
3448 }
3449 }
3450 #[cfg(unix)]
3451 Action::ClipShowLv2Gui { .. } => {
3452 if Self::box_bool(self.handle_clip_show_lv2_gui(a.clone())).await {
3453 return;
3454 }
3455 }
3456 Action::TrackSetPluginResourceDir { .. } => {
3457 if Self::box_bool(self.handle_track_set_plugin_resource_dir(a.clone())).await {
3458 return;
3459 }
3460 }
3461 Action::TrackClapCollectResources { .. } => {
3462 if Self::box_bool(self.handle_track_clap_collect_resources(a.clone())).await {
3463 return;
3464 }
3465 }
3466 Action::ClipSetPluginResourceDir { .. } => {
3467 if Self::box_bool(self.handle_clip_set_plugin_resource_dir(a.clone())).await {
3468 return;
3469 }
3470 }
3471 Action::ClipClapCollectResources { .. } => {
3472 if Self::box_bool(self.handle_clip_clap_collect_resources(a.clone())).await {
3473 return;
3474 }
3475 }
3476 Action::TrackSetClapParameter { .. } => {
3477 if Self::box_bool(self.handle_track_set_clap_parameter(a.clone())).await {
3478 return;
3479 }
3480 }
3481 Action::ClipSetClapParameter { .. } => {
3482 if Self::box_bool(self.handle_clip_set_clap_parameter(a.clone())).await {
3483 return;
3484 }
3485 }
3486 Action::ClipGetClapParameters { .. } => {
3487 if Self::box_bool(self.handle_clip_get_clap_parameters(a.clone())).await {
3488 return;
3489 }
3490 }
3491 Action::ClipClapParameters { .. } => {}
3492 Action::TrackSetClapParameterAt { .. } => {
3493 if Self::box_bool(self.handle_track_set_clap_parameter_at(a.clone())).await {
3494 return;
3495 }
3496 }
3497 Action::TrackBeginClapParameterEdit { .. } => {
3498 if Self::box_bool(self.handle_track_begin_clap_parameter_edit(a.clone())).await {
3499 return;
3500 }
3501 }
3502 Action::TrackEndClapParameterEdit { .. } => {
3503 if Self::box_bool(self.handle_track_end_clap_parameter_edit(a.clone())).await {
3504 return;
3505 }
3506 }
3507 Action::TrackGetClapParameters { .. } => {
3508 if Self::box_bool(self.handle_track_get_clap_parameters(a.clone())).await {
3509 return;
3510 }
3511 }
3512 Action::TrackClapParameters { .. } => {}
3513 Action::TrackClapSnapshotState { .. } => {
3514 if Self::box_bool(self.handle_track_clap_snapshot_state(a.clone())).await {
3515 return;
3516 }
3517 }
3518 Action::ClipClapSnapshotState { .. } => {
3519 if Self::box_bool(self.handle_clip_clap_snapshot_state(a.clone())).await {
3520 return;
3521 }
3522 }
3523 Action::TrackClapStateSnapshot { .. } => {}
3524 Action::ClipClapStateSnapshot { .. } => {}
3525 Action::TrackClapStateDirty { .. } => {}
3526 Action::ClipClapStateDirty { .. } => {}
3527 Action::TrackClapRestoreState { .. } => {
3528 if Self::box_bool(self.handle_track_clap_restore_state(a.clone())).await {
3529 return;
3530 }
3531 }
3532 Action::ClipClapRestoreState { .. } => {
3533 if Self::box_bool(self.handle_clip_clap_restore_state(a.clone())).await {
3534 return;
3535 }
3536 }
3537 Action::TrackSnapshotAllClapStates { .. } => {
3538 if Self::box_bool(self.handle_track_snapshot_all_clap_states(a.clone())).await {
3539 return;
3540 }
3541 }
3542 Action::TrackSnapshotAllClapStatesDone { .. } => {}
3543 Action::TrackGetVst3Graph { .. } => {
3544 if Self::box_bool(self.handle_track_get_vst3_graph(a.clone())).await {
3545 return;
3546 }
3547 }
3548 Action::TrackVst3Graph { .. } => {}
3549 Action::TrackSetVst3Parameter { .. } => {
3550 if Self::box_bool(self.handle_track_set_vst3_parameter(a.clone())).await {
3551 return;
3552 }
3553 }
3554 Action::ClipSetVst3Parameter { .. } => {
3555 if Self::box_bool(self.handle_clip_set_vst3_parameter(a.clone())).await {
3556 return;
3557 }
3558 }
3559 Action::TrackSetPluginBypassed { .. } => {
3560 if Self::box_bool(self.handle_track_set_plugin_bypassed(a.clone())).await {
3561 return;
3562 }
3563 }
3564 Action::TrackGetVst3Parameters { .. } => {
3565 if Self::box_bool(self.handle_track_get_vst3_parameters(a.clone())).await {
3566 return;
3567 }
3568 }
3569 Action::TrackVst3Parameters { .. } => {}
3570 Action::ClipGetVst3Parameters { .. } => {
3571 if Self::box_bool(self.handle_clip_get_vst3_parameters(a.clone())).await {
3572 return;
3573 }
3574 }
3575 Action::ClipVst3Parameters { .. } => {}
3576 #[cfg(unix)]
3577 Action::TrackSetLv2ControlValue { .. } => {
3578 if Self::box_bool(self.handle_track_set_lv2_control_value(a.clone())).await {
3579 return;
3580 }
3581 }
3582 #[cfg(unix)]
3583 Action::ClipSetLv2ControlValue { .. } => {
3584 if Self::box_bool(self.handle_clip_set_lv2_control_value(a.clone())).await {
3585 return;
3586 }
3587 }
3588 #[cfg(unix)]
3589 Action::TrackGetLv2PluginControls { .. } => {
3590 if Self::box_bool(self.handle_track_get_lv2_plugin_controls(a.clone())).await {
3591 return;
3592 }
3593 }
3594 #[cfg(unix)]
3595 Action::ClipGetLv2PluginControls { .. } => {
3596 if Self::box_bool(self.handle_clip_get_lv2_plugin_controls(a.clone())).await {
3597 return;
3598 }
3599 }
3600 #[cfg(unix)]
3601 Action::TrackLv2SnapshotState { .. } => {
3602 if Self::box_bool(self.handle_track_lv2_snapshot_state(a.clone())).await {
3603 return;
3604 }
3605 }
3606 #[cfg(unix)]
3607 Action::ClipLv2SnapshotState { .. } => {
3608 if Self::box_bool(self.handle_clip_lv2_snapshot_state(a.clone())).await {
3609 return;
3610 }
3611 }
3612 Action::TrackVst3SnapshotState { .. } => {
3613 if Self::box_bool(self.handle_track_vst3_snapshot_state(a.clone())).await {
3614 return;
3615 }
3616 }
3617 Action::ClipVst3SnapshotState { .. } => {
3618 if Self::box_bool(self.handle_clip_vst3_snapshot_state(a.clone())).await {
3619 return;
3620 }
3621 }
3622 Action::TrackVst3StateSnapshot { .. } => {}
3623 Action::ClipVst3StateSnapshot { .. } => {}
3624 Action::TrackVst3RestoreState { .. } => {
3625 if Self::box_bool(self.handle_track_vst3_restore_state(a.clone())).await {
3626 return;
3627 }
3628 }
3629 Action::TrackConnectVst3Audio { .. } => {
3630 if Self::box_bool(self.handle_track_connect_vst3_audio(a.clone())).await {
3631 return;
3632 }
3633 }
3634 Action::TrackDisconnectVst3Audio { .. } => {
3635 if Self::box_bool(self.handle_track_disconnect_vst3_audio(a.clone())).await {
3636 return;
3637 }
3638 }
3639 Action::ClipMove { .. } => {
3640 self.handle_clip_move(a.clone()).await;
3641 }
3642 Action::AddClip { .. } => {
3643 if Self::box_bool(self.handle_add_clip(a.clone())).await {
3644 return;
3645 }
3646 }
3647 Action::AddGroupedClip { .. } => {
3648 if Self::box_bool(self.handle_add_grouped_clip(a.clone())).await {
3649 return;
3650 }
3651 }
3652 Action::RemoveClip {
3653 ref track_name,
3654 kind,
3655 ref clip_indices,
3656 } => {
3657 self.remove_clips_from_track(track_name, kind, clip_indices);
3658 }
3659 Action::MoveClipToUnused {
3660 ref track_name,
3661 kind,
3662 ref clip_indices,
3663 } => {
3664 self.move_clips_to_unused(track_name, kind, clip_indices);
3665 }
3666 Action::DeleteUnusedClips { ref clip_ids } => {
3667 self.delete_unused_clips(clip_ids);
3668 }
3669 Action::SetUnusedClips {
3670 ref audio,
3671 ref midi,
3672 } => {
3673 self.set_unused_clips(audio.clone(), midi.clone());
3674 }
3675 Action::RenameClip {
3676 ref track_name,
3677 kind,
3678 clip_index,
3679 ref new_name,
3680 } => {
3681 self.rename_clip_references(track_name, kind, clip_index, new_name);
3682 }
3683 Action::SetClipIdentity {
3684 ref track_name,
3685 kind,
3686 clip_index,
3687 ref new_id,
3688 ref new_name,
3689 } => {
3690 self.set_clip_identity(track_name, kind, clip_index, new_id, new_name);
3691 }
3692 Action::SetClipSourceName {
3693 ref track_name,
3694 kind,
3695 clip_index,
3696 ref name,
3697 } => {
3698 self.set_clip_source_name(track_name, clip_index, kind, name.clone());
3699 }
3700 Action::SetClipFade { .. } => {
3701 if Self::box_bool(self.handle_set_clip_fade(a.clone())).await {
3702 return;
3703 }
3704 }
3705 Action::SetClipBounds {
3706 ref track_name,
3707 clip_index,
3708 kind,
3709 start,
3710 length,
3711 offset,
3712 } => {
3713 self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3714 }
3715 Action::SyncClipBounds {
3716 ref track_name,
3717 clip_index,
3718 kind,
3719 start,
3720 length,
3721 offset,
3722 } => {
3723 self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3724 }
3725 Action::SetClipMuted {
3726 ref track_name,
3727 clip_index,
3728 kind,
3729 muted,
3730 } => {
3731 self.set_clip_muted(track_name, clip_index, kind, muted);
3732 }
3733 Action::SetClipReversed {
3734 ref track_name,
3735 clip_index,
3736 kind,
3737 reversed,
3738 } => {
3739 self.set_clip_reversed(track_name, clip_index, kind, reversed);
3740 }
3741 Action::SetClipGainDb {
3742 ref track_name,
3743 clip_index,
3744 kind,
3745 gain_db,
3746 } => {
3747 self.set_clip_gain_db(track_name, clip_index, kind, gain_db);
3748 }
3749 Action::SetClipPluginGraphJson {
3750 ref track_name,
3751 clip_index,
3752 ref plugin_graph_json,
3753 } => {
3754 self.set_clip_plugin_graph_json(track_name, clip_index, plugin_graph_json.clone());
3755 }
3756 Action::SetClipPitchCorrection { .. } => {
3757 if Self::box_bool(self.handle_set_clip_pitch_correction(a.clone())).await {
3758 return;
3759 }
3760 }
3761 Action::Connect {
3762 ref from_track,
3763 from_port,
3764 ref to_track,
3765 to_port,
3766 kind,
3767 } => {
3768 self.handle_connect(
3769 from_track.as_str(),
3770 from_port,
3771 to_track.as_str(),
3772 to_port,
3773 kind,
3774 )
3775 .await;
3776 }
3777 Action::Disconnect { .. } => {
3778 self.handle_disconnect(a.clone()).await;
3779 }
3780 Action::OpenAudioDevice { .. } => {
3781 let (done, updated) = self.handle_open_audio_device(a.clone()).await;
3782 if done {
3783 return;
3784 }
3785 if let Some(action) = updated {
3786 action_to_process = action;
3787 }
3788 }
3789 Action::JackAddAudioInputPort => {
3790 if Self::box_bool(self.handle_jack_add_audio_input_port(a.clone())).await {
3791 return;
3792 }
3793 }
3794 Action::JackRemoveAudioInputPort(_removed_port) => {
3795 if self
3796 .handle_jack_remove_audio_input_port(_removed_port, a.clone())
3797 .await
3798 {
3799 return;
3800 }
3801 }
3802 Action::JackAddAudioOutputPort => {
3803 if Self::box_bool(self.handle_jack_add_audio_output_port(a.clone())).await {
3804 return;
3805 }
3806 }
3807 Action::JackRemoveAudioOutputPort(_removed_port) => {
3808 if self
3809 .handle_jack_remove_audio_output_port(_removed_port, a.clone())
3810 .await
3811 {
3812 return;
3813 }
3814 }
3815 Action::JackGetGraph => {
3816 #[cfg(unix)]
3817 {
3818 match self
3819 .jack_runtime
3820 .as_ref()
3821 .ok_or(
3822 "JACK runtime is not active; open the JACK backend first".to_string(),
3823 )
3824 .and_then(|jack| jack.graph_info())
3825 {
3826 Ok(graph) => self.notify_clients(Ok(Action::JackGraph(graph))).await,
3827 Err(e) => self.notify_clients(Err(e)).await,
3828 }
3829 }
3830 #[cfg(not(unix))]
3831 {
3832 self.notify_clients(Err(
3833 "JACK backend is not available on this platform build".to_string(),
3834 ))
3835 .await;
3836 }
3837 return;
3838 }
3839 Action::JackConnect {
3840 ref source,
3841 ref destination,
3842 } => {
3843 #[cfg(unix)]
3844 {
3845 match self
3846 .jack_runtime
3847 .as_ref()
3848 .ok_or(
3849 "JACK runtime is not active; open the JACK backend first".to_string(),
3850 )
3851 .and_then(|jack| jack.connect_ports_by_name(source, destination))
3852 .and_then(|_| {
3853 self.jack_runtime
3854 .as_ref()
3855 .expect("JACK runtime was checked")
3856 .graph_info()
3857 }) {
3858 Ok(graph) => {
3859 self.notify_clients(Ok(a.clone())).await;
3860 self.notify_clients(Ok(Action::JackGraph(graph))).await;
3861 }
3862 Err(e) => self.notify_clients(Err(e)).await,
3863 }
3864 }
3865 #[cfg(not(unix))]
3866 {
3867 let _ = (source, destination);
3868 self.notify_clients(Err(
3869 "JACK backend is not available on this platform build".to_string(),
3870 ))
3871 .await;
3872 }
3873 return;
3874 }
3875 Action::JackDisconnect {
3876 ref source,
3877 ref destination,
3878 } => {
3879 #[cfg(unix)]
3880 {
3881 match self
3882 .jack_runtime
3883 .as_ref()
3884 .ok_or(
3885 "JACK runtime is not active; open the JACK backend first".to_string(),
3886 )
3887 .and_then(|jack| jack.disconnect_ports_by_name(source, destination))
3888 .and_then(|_| {
3889 self.jack_runtime
3890 .as_ref()
3891 .expect("JACK runtime was checked")
3892 .graph_info()
3893 }) {
3894 Ok(graph) => {
3895 self.notify_clients(Ok(a.clone())).await;
3896 self.notify_clients(Ok(Action::JackGraph(graph))).await;
3897 }
3898 Err(e) => self.notify_clients(Err(e)).await,
3899 }
3900 }
3901 #[cfg(not(unix))]
3902 {
3903 let _ = (source, destination);
3904 self.notify_clients(Err(
3905 "JACK backend is not available on this platform build".to_string(),
3906 ))
3907 .await;
3908 }
3909 return;
3910 }
3911 Action::OpenMidiInputDevice(ref device) => {
3912 if let Some(worker) = &self.hw_worker {
3913 if let Err(e) = worker
3914 .tx
3915 .send(Message::HWOpenMidiInputDevice(device.clone()))
3916 .await
3917 {
3918 self.notify_clients(Err(format!("Failed to send MIDI input open: {e}")))
3919 .await;
3920 }
3921 return;
3922 }
3923 let Some(midi_hub) = self.midi_hub.as_mut() else {
3924 self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3925 .await;
3926 return;
3927 };
3928 if let Err(e) = midi_hub.open_input(device) {
3929 self.notify_clients(Err(e)).await;
3930 return;
3931 }
3932 }
3933 Action::OpenMidiOutputDevice(ref device) => {
3934 if let Some(worker) = &self.hw_worker {
3935 if let Err(e) = worker
3936 .tx
3937 .send(Message::HWOpenMidiOutputDevice(device.clone()))
3938 .await
3939 {
3940 self.notify_clients(Err(format!("Failed to send MIDI output open: {e}")))
3941 .await;
3942 }
3943 return;
3944 }
3945 let Some(midi_hub) = self.midi_hub.as_mut() else {
3946 self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3947 .await;
3948 return;
3949 };
3950 if let Err(e) = midi_hub.open_output(device) {
3951 self.notify_clients(Err(e)).await;
3952 return;
3953 }
3954 }
3955 Action::RequestSessionDiagnostics => {
3956 self.handle_request_session_diagnostics().await;
3957 }
3958 Action::RequestMidiLearnMappingsReport => {
3959 self.handle_request_midi_learn_mappings_report().await;
3960 }
3961 Action::ClearAllMidiLearnBindings => {
3962 if Self::box_bool(self.handle_clear_all_midi_learn_bindings(a.clone())).await {
3963 return;
3964 }
3965 }
3966 #[cfg(unix)]
3967 Action::TrackLv2PluginControls { .. } => {}
3968 #[cfg(unix)]
3969 Action::ClipLv2PluginControls { .. } => {}
3970 #[cfg(unix)]
3971 Action::TrackLv2StateSnapshot { .. } => {}
3972 #[cfg(unix)]
3973 Action::ClipLv2StateSnapshot { .. } => {}
3974 #[cfg(unix)]
3975 Action::TrackLv2Midnam { .. } => {}
3976 Action::TrackClapNoteNames { .. } => {}
3977 Action::SessionDiagnosticsReport { .. } => {}
3978 Action::MidiLearnMappingsReport { .. } => {}
3979 Action::HWInfo { .. } => {}
3980 Action::HistoryState { .. } => {}
3981 Action::Undo => {}
3982 Action::Redo => {}
3983 Action::ApplyGroupedActions(_) => {}
3984 _ => {}
3985 }
3986
3987 if let Some(inverse) = inverse_actions {
3988 if let Some(group) = self.history_group.as_mut() {
3989 group.forward_actions.push(action_to_process.clone());
3990 group.inverse_actions.splice(0..0, inverse);
3991 } else {
3992 self.history.record(UndoEntry {
3993 forward_actions: vec![action_to_process.clone()],
3994 inverse_actions: inverse,
3995 });
3996 }
3997 }
3998
3999 self.notify_clients(Ok(action_to_process)).await;
4000 }
4001 pub async fn work(&mut self) {
4002 let mut timeout_tick = tokio::time::interval(Duration::from_millis(10));
4005 timeout_tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
4006 loop {
4007 let message = tokio::select! {
4008 message = self.rx.recv() => {
4009 let Some(message) = message else {
4010 break;
4011 };
4012 tracing::debug!(?message, "engine work loop received message");
4013 Some(message)
4014 }
4015 _ = self.node_result_notify.notified() => {
4016 tracing::trace!("engine work loop woken by node result");
4017 None
4018 }
4019 _ = timeout_tick.tick() => {
4020 tracing::trace!("engine work loop timeout tick");
4021 None
4022 }
4023 };
4024 self.poll_node_worker_results().await;
4025 self.poll_jack_hw_finished().await;
4026 self.poll_stopped_plugin_parameter_echoes().await;
4027 if !self.playing && !self.transport_running {
4028 self.publish_clap_state_dirty().await;
4029 }
4030 self.on_executor_tick().await;
4031 let Some(message) = message else {
4032 continue;
4033 };
4034 match message {
4035 Message::Ready(id) => {
4036 if let Some(track_name) = self.bounce_worker_tracks.remove(&id) {
4040 self.offline_bounce_jobs.remove(&track_name);
4041 }
4042 self.push_ready_worker(id);
4043 if self.dispatch_node_jobs(Vec::new()).await {
4044 self.on_all_tracks_finished().await;
4045 }
4046 self.drain_pending_requests_if_idle().await;
4047 }
4048 Message::NodeDone {
4049 worker_id,
4050 epoch,
4051 node,
4052 output_linear,
4053 parameter_updates,
4054 latency_changed,
4055 } => {
4056 self.on_node_done(
4057 worker_id,
4058 epoch,
4059 node,
4060 output_linear,
4061 parameter_updates,
4062 latency_changed,
4063 )
4064 .await;
4065 }
4066 Message::Channel(s) => {
4067 self.clients.push(s);
4068 }
4069 Message::Response(result) => {
4070 self.notify_clients(result).await;
4071 }
4072
4073 Message::Request(a) => {
4074 self.dispatch_request(a).await;
4075 self.plan_builder.mark_dirty();
4078 }
4079 Message::OscRequest { action, reply_to } => {
4080 tracing::debug!(%reply_to, ?action, "engine received OscRequest");
4081 self.osc_reply_target = Some(reply_to);
4082 self.dispatch_request(action).await;
4083 self.osc_reply_target = None;
4084 self.plan_builder.mark_dirty();
4085 }
4086 Message::OfflineBounceFinished { result } => {
4087 if let Ok(Action::TrackOfflineBounce { track_name, .. })
4088 | Ok(Action::TrackOfflineBounceCanceled { track_name, .. }) = &result
4089 {
4090 self.offline_bounce_jobs.remove(track_name);
4091 }
4092 self.notify_clients(result).await;
4093 self.drain_pending_requests_if_idle().await;
4094 }
4095 Message::HWFinished => {
4096 if !self.awaiting_hwfinished {
4097 tracing::debug!(
4098 playing = self.playing,
4099 transport_running = self.transport_running,
4100 transport_sample = self.transport_sample,
4101 session_transport_sample = self.session_transport_sample,
4102 cycle_samples = self.current_cycle_samples(),
4103 "HWFinished ignored because engine was not awaiting it"
4104 );
4105 continue;
4106 }
4107 tracing::debug!(
4108 playing = self.playing,
4109 transport_running = self.transport_running,
4110 transport_sample = self.transport_sample,
4111 session_transport_sample = self.session_transport_sample,
4112 cycle_samples = self.current_cycle_samples(),
4113 "HWFinished handling"
4114 );
4115 self.handling_hwfinished = true;
4116 self.awaiting_hwfinished = false;
4117 #[cfg(unix)]
4118 {
4119 if let Some(jack) = self.jack_runtime.as_mut() {
4120 if !self.pending_hw_midi_out_events.is_empty() {
4121 let out_events =
4122 std::mem::take(&mut self.pending_hw_midi_out_events);
4123 jack.write_events(&out_events);
4124 }
4125 let mut in_events = vec![];
4126 jack.read_events_into(&mut in_events);
4127 if !in_events.is_empty() {
4128 self.pending_hw_midi_events.extend(in_events);
4129 }
4130 let dropped = jack.take_midi_events_dropped();
4131 if dropped > 0 {
4132 tracing::warn!(
4133 "JACK MIDI ring full; {dropped} events dropped since last cycle"
4134 );
4135 }
4136 }
4137 }
4138 #[cfg(unix)]
4139 if self.jack_runtime.is_some() {
4140 self.sync_from_jack_transport().await;
4141 }
4142 while let Some(a) = self.pending_requests.pop_front() {
4143 self.handle_request(a).await;
4144 }
4145 self.apply_mute_solo_policy();
4146 self.append_recorded_cycle();
4147 self.flush_completed_recordings().await;
4148 let hw_in_routes = self.midi_hw_in_routes.clone();
4149 let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
4150 let mut reconfigured_tracks = Vec::new();
4151 let state = self.state_snapshot.load_full();
4152 for (track_name, track) in state.tracks.iter() {
4153 let mut track_lock = track.lock();
4154 if self.jack_runtime_is_some() {
4155 if !self.pending_hw_midi_events.is_empty() {
4156 track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
4157 }
4158 } else {
4159 for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
4160 if let Some(events) = pending_hw_in_by_device.get(&route.device) {
4161 track_lock.push_hw_midi_events_to_port(route.to_port, events);
4162 }
4163 }
4164 }
4165 if track_lock.setup() {
4166 reconfigured_tracks.push(track_name.clone());
4167 }
4168 }
4169 self.publish_track_meters();
4170 self.publish_session_runtime_reports().await;
4171 self.publish_clap_state_dirty().await;
4172 for track_name in reconfigured_tracks {
4173 let track = state.tracks.get(&track_name).cloned();
4174 if let Some(track) = track {
4175 let (plugins, connections, connectable_connections) = {
4176 let track_lock = track.lock();
4177 (
4178 track_lock.plugin_graph_plugins(false),
4179 track_lock.plugin_graph_connections(),
4180 track_lock.connectable_connections(),
4181 )
4182 };
4183 self.notify_clients(Ok(Action::TrackPluginGraph {
4184 track_name: track_name.clone(),
4185 plugins,
4186 connections,
4187 connectable_connections,
4188 }))
4189 .await;
4190 }
4191 }
4192 self.pending_hw_midi_events.clear();
4193 self.pending_hw_midi_events_by_device.clear();
4194 let cycle_samples = self.current_cycle_samples();
4195 if self.transport_running {
4196 if self.transport_panic_flush_pending {
4197 self.transport_panic_flush_pending = false;
4198 } else if self.transport_restart_pending {
4199 self.transport_restart_pending = false;
4200 } else {
4201 let before = self.transport_sample;
4202 let next = self.transport_sample.saturating_add(cycle_samples);
4203 let normalized = self.normalize_transport_sample(next);
4204 let wrapped = normalized != next;
4205 self.transport_sample = normalized;
4206 tracing::debug!(
4207 before,
4208 delta = cycle_samples,
4209 next,
4210 normalized,
4211 wrapped,
4212 "transport advanced after HWFinished"
4213 );
4214 self.publish_transport_snapshot();
4215 if wrapped {
4216 if self.notified_loop_wrap_sample == Some(self.transport_sample) {
4217 self.notified_loop_wrap_sample = None;
4218 } else {
4219 self.notify_clients(Ok(Action::TransportPosition(
4220 self.transport_sample,
4221 )))
4222 .await;
4223 }
4224 }
4225 }
4226 } else {
4227 tracing::debug!(
4228 playing = self.playing,
4229 cycle_samples,
4230 "transport not advanced because transport_running is false"
4231 );
4232 }
4233 if self.session_clip_playback_enabled && self.playing {
4234 let before = self.session_transport_sample;
4235 self.session_transport_sample =
4236 self.session_transport_sample.saturating_add(cycle_samples);
4237 tracing::debug!(
4238 before,
4239 delta = cycle_samples,
4240 after = self.session_transport_sample,
4241 "session transport advanced after HWFinished"
4242 );
4243 }
4244 {
4245 let echoes = self.apply_modulators(self.active_transport_sample());
4246 for action in echoes {
4247 self.notify_clients(Ok(action)).await;
4248 }
4249 }
4250 self.apply_mixosc_automation(self.active_transport_sample());
4251 let cycle_started = self.start_plan_cycle().await;
4252 if self.hw_worker.is_some()
4255 && !cycle_started
4256 && (self.playing || self.audio_preview.is_some())
4257 && self.executor.cycle_complete()
4258 {
4259 self.request_hw_cycle().await;
4260 }
4261 tracing::debug!(
4262 cycle_started,
4263 hw_worker = self.hw_worker.is_some(),
4264 awaiting_hwfinished = self.awaiting_hwfinished,
4265 executor_complete = self.executor.cycle_complete(),
4266 "HWFinished rearm decision"
4267 );
4268 #[cfg(unix)]
4269 {
4270 if self.jack_runtime.is_some() {
4271 self.awaiting_hwfinished = true;
4272 }
4273 }
4274 self.handling_hwfinished = false;
4275 }
4276 Message::HWMidiEvents(events) => {
4277 for hw_event in events {
4278 let thru_targets: Vec<String> = self
4279 .midi_hw_thru_routes
4280 .iter()
4281 .filter(|route| route.from_device == hw_event.device)
4282 .map(|route| route.to_device.clone())
4283 .collect();
4284 for device in thru_targets {
4285 self.pending_hw_midi_out_events_by_device.push(HwMidiEvent {
4286 device,
4287 event: hw_event.event.clone(),
4288 });
4289 }
4290 if hw_event.event.data.len() >= 3 {
4291 let status = hw_event.event.data[0];
4292 if status & 0xF0 == 0xB0 {
4293 let channel = status & 0x0F;
4294 let cc = hw_event.event.data[1];
4295 let value = hw_event.event.data[2];
4296 self.handle_incoming_hw_cc(&hw_event.device, channel, cc, value)
4297 .await;
4298 }
4299 if self.step_recording_enabled && status & 0xF0 == 0x90 {
4300 let channel = status & 0x0F;
4301 let pitch = hw_event.event.data[1];
4302 let velocity = hw_event.event.data[2];
4303 if velocity > 0 {
4304 self.notify_clients(Ok(Action::StepRecordMidiNote {
4305 device: hw_event.device.clone(),
4306 channel,
4307 pitch,
4308 velocity,
4309 }))
4310 .await;
4311 }
4312 }
4313 }
4314 self.pending_hw_midi_events_by_device
4315 .entry(hw_event.device)
4316 .or_default()
4317 .push(hw_event.event);
4318 }
4319 }
4320 Message::StartAudioPreview {
4321 samples,
4322 channels,
4323 start_sample,
4324 } => {
4325 self.audio_preview = Some(AudioPreviewPlayback {
4326 samples,
4327 channels: channels.max(1),
4328 cursor: start_sample,
4329 });
4330 self.meter_decay_after_stop = None;
4331 self.set_hw_playing(true).await;
4332 if !self.awaiting_hwfinished && self.executor.cycle_complete() {
4333 self.request_hw_cycle().await;
4334 }
4335 }
4336 Message::StopAudioPreview => {
4337 self.audio_preview = None;
4338 }
4339 _ => {}
4340 }
4341 }
4342 }
4343
4344 pub(crate) fn collect_hw_midi_output_events(&self) -> Vec<MidiEvent> {
4345 let mut events = vec![];
4346 for track in self.state_snapshot.load_full().tracks.values() {
4347 events.extend(
4348 track
4349 .lock()
4350 .take_hw_midi_out_events()
4351 .into_iter()
4352 .map(|evt| evt.event),
4353 );
4354 }
4355 events.sort_by_key(|a| a.frame);
4356 events
4357 }
4358
4359 pub(crate) fn collect_hw_midi_output_events_by_device(&mut self) -> Vec<HwMidiEvent> {
4360 let mut events = Vec::<HwMidiEvent>::new();
4361 let routes = self.midi_hw_out_routes.clone();
4362 let mut events_by_track = HashMap::<String, Vec<crate::track::HwMidiOutEvent>>::new();
4363 {
4364 let state = self.state_snapshot.load_full();
4365 for route in &routes {
4366 if events_by_track.contains_key(&route.from_track) {
4367 continue;
4368 }
4369 let Some(track) = state.tracks.get(&route.from_track) else {
4370 continue;
4371 };
4372 events_by_track.insert(
4373 route.from_track.clone(),
4374 track.lock().take_hw_midi_out_events(),
4375 );
4376 }
4377 }
4378
4379 for route in routes {
4380 let Some(track_events) = events_by_track.get(&route.from_track) else {
4381 continue;
4382 };
4383 for hw_event in track_events
4384 .iter()
4385 .filter(|evt| evt.port == route.from_port)
4386 {
4387 self.update_active_hw_notes_for_track(
4388 &route.from_track,
4389 &route.device,
4390 &hw_event.event.data,
4391 );
4392 events.push(HwMidiEvent {
4393 device: route.device.clone(),
4394 event: hw_event.event.clone(),
4395 });
4396 }
4397 }
4398 events.sort_by(|a, b| {
4399 a.event
4400 .frame
4401 .cmp(&b.event.frame)
4402 .then_with(|| a.device.cmp(&b.device))
4403 });
4404 events
4405 }
4406}