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