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