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