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