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