Skip to main content

maolan_engine/engine/
runtime.rs

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        // Phase 2 render-plan machinery: an initial (empty-session) plan is
146        // built synchronously; the builder thread republishes on demand.
147        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            // Await instead of try_send: a dropped HWSetPlaying(false) would
323            // leave the device playing after transport stop.
324            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), // placeholder for future beat position
797                                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                // Read the plan's hardware-output arena buffers (the driver
1295                // drain source) instead of the legacy bridge ports.
1296                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    /// Preload all track clips and wait for completion. Used on transport
1342    /// play paths: the audio cycle must not start until clip caches are
1343    /// populated, otherwise the preload tasks' `&mut Track` access races the
1344    /// RT worker and cold-cache reads do disk I/O on the audio path.
1345    ///
1346    /// Returns an owned future rather than borrowing `self`: `Engine` is not
1347    /// `Sync` (it owns JackRuntime/MidiHub directly since Phase 6), so an
1348    /// `async fn(&self)` future would be non-`Send`.
1349    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    /// Dispatch queued node jobs to ready workers, buffering the rest until
1409    /// a worker reports `Ready`. Returns true if the cycle completed while
1410    /// dispatching (only possible via abandoned nodes).
1411    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    /// Start the next audio cycle: pull any published plan, copy hardware
1453    /// inputs, and dispatch the seed jobs. Returns true when the cycle
1454    /// completed instantly (empty plan) and `on_all_tracks_finished` ran.
1455    /// Ensure the metronome track's source port and output wiring exist
1456    /// before tasks are dispatched. Runs on the dispatcher at cycle top so
1457    /// `AudioIO.connections` is never mutated from a worker thread; marks
1458    /// the plan dirty when the wiring changed.
1459    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        // While a bounce job exists, plan cycles are suspended: the bounce
1478        // worker renders through live track bodies and must be their only
1479        // mutator (LOCKLESS.md Phase 5, 5b-iii).
1480        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    /// Handle a worker completion: cascade the executor, publish meters and
1495    /// parameter echoes, force timed-out nodes, and finish the cycle when
1496    /// all nodes are done.
1497    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    /// Periodic tick (called from the engine loop's interval): force
1704    /// timed-out nodes even when no worker message arrives.
1705    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    /// Hand a bounce job to its reserved worker, recording the worker→track
1737    /// mapping so the terminal `Ready(id)` cleans the job up on any path.
1738    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    /// Requests queued while a bounce was running are replayed once the last
1757    /// bounce job is gone (and plan cycles resume).
1758    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        // Hand deferred bounce jobs to their workers now that no plan cycle
1768        // is in flight (see handle_track_offline_bounce).
1769        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        // Stop the HW worker before notifying the GUI so the
2070        // OSS audio channels are halted and closed from the
2071        // worker's own thread. The GUI calls exit(0) upon
2072        // receiving the Quit response, which skips Rust
2073        // destructors. Without this, the kernel's dsp_close
2074        // drains pending audio buffers for up to CHN_TIMEOUT
2075        // (5s) during process teardown.
2076        if let Some(mut worker) = self.hw_worker.take() {
2077            // Send MIDI panic (All Sound Off) for any active
2078            // notes before stopping the worker.
2079            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            // Send Quit to the worker so it stops its audio
2084            // cycle loop and releases the driver.
2085            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        // Explicitly close audio and MIDI fds before sending
2095        // the Quit response. The GUI calls exit(0) upon
2096        // receiving it, which skips destructors — any
2097        // still-open device fd would trigger the kernel's
2098        // 5-second drain during process teardown.
2099        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        // Periodic tick drains fixed-thread node-worker result rings and
3401        // force-completes timed-out nodes even when no worker result arrives.
3402        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                    // A bounce worker's terminal Ready cleans up its job even
3424                    // when the OfflineBounceFinished payload was an Err
3425                    // without a track name.
3426                    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                    // Any request may have changed the topology; the builder
3455                    // coalesces bursts into a single plan rebuild.
3456                    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}