Skip to main content

maolan_engine/engine/
runtime.rs

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