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::SetSessionPath(_)
1166            | Action::ClearHistory
1167            | Action::BeginSessionRestore
1168            | Action::PianoKey { .. }
1169            | Action::ModifyMidiNotes { .. }
1170            | Action::ModifyMidiControllers { .. }
1171            | Action::DeleteMidiControllers { .. }
1172            | Action::InsertMidiControllers { .. }
1173            | Action::DeleteMidiNotes { .. }
1174            | Action::InsertMidiNotes { .. }
1175            | Action::SetMidiSysExEvents { .. }
1176            | Action::Session(_) => {
1177                self.handle_request(a).await;
1178            }
1179            #[cfg(unix)]
1180            Action::ListLv2Plugins => {
1181                self.handle_request(a).await;
1182            }
1183            Action::ListVst3Plugins => {
1184                self.handle_request(a).await;
1185            }
1186            Action::ListClapPlugins => {
1187                self.handle_request(a).await;
1188            }
1189            Action::ListClapPluginsWithCapabilities => {
1190                self.handle_request(a).await;
1191            }
1192            _ => {
1193                self.pending_requests.push_back(a);
1194                if self.can_schedule_hw_cycle() {
1195                    self.request_hw_cycle().await;
1196                } else {
1197                    while let Some(next) = self.pending_requests.pop_front() {
1198                        self.handle_request(next).await;
1199                    }
1200                }
1201            }
1202        };
1203        self.publish_clap_state_dirty().await;
1204    }
1205
1206    pub(crate) fn spawn_plugin_host_stderr_reader(
1207        &self,
1208        stderr: std::process::ChildStderr,
1209        source: String,
1210    ) {
1211        let tx = self.tx.clone();
1212        std::thread::spawn(move || {
1213            use std::io::{BufRead, BufReader};
1214            let reader = BufReader::new(stderr);
1215            for line in reader.lines() {
1216                if let Ok(line) = line
1217                    && !line.is_empty()
1218                {
1219                    let _ = tx.blocking_send(Message::Request(Action::Log {
1220                        source: source.clone(),
1221                        message: line,
1222                    }));
1223                }
1224            }
1225        });
1226    }
1227
1228    pub(crate) fn set_osc_enabled_with<F>(
1229        &mut self,
1230        enabled: bool,
1231        start_server: F,
1232    ) -> Result<(), String>
1233    where
1234        F: FnOnce(Sender<Message>) -> Result<OscServer, String>,
1235    {
1236        if enabled {
1237            if self.osc_server.is_none() {
1238                self.osc_server = Some(start_server(self.tx.clone())?);
1239            }
1240        } else if let Some(mut server) = self.osc_server.take() {
1241            server.stop();
1242        }
1243        Ok(())
1244    }
1245
1246    pub(crate) async fn request_hw_cycle(&mut self) {
1247        if self.awaiting_hwfinished {
1248            tracing::debug!(
1249                playing = self.playing,
1250                transport_running = self.transport_running,
1251                transport_sample = self.transport_sample,
1252                session_transport_sample = self.session_transport_sample,
1253                cycle_samples = self.current_cycle_samples(),
1254                "request_hw_cycle skipped because HWFinished is still pending"
1255            );
1256            return;
1257        }
1258        self.mix_audio_preview_into_hw_outputs();
1259        tracing::debug!(
1260            playing = self.playing,
1261            transport_running = self.transport_running,
1262            transport_sample = self.transport_sample,
1263            session_transport_sample = self.session_transport_sample,
1264            cycle_samples = self.current_cycle_samples(),
1265            "request_hw_cycle sending TracksFinished"
1266        );
1267        self.apply_hw_out_gain_and_meter().await;
1268        self.publish_meter_snapshot_if_due();
1269        if let Some((after_frames, loop_start, cycle_end_sample)) =
1270            self.scheduled_loop_wrap_for_next_cycle()
1271        {
1272            self.notified_loop_wrap_sample = Some(cycle_end_sample);
1273            self.notify_clients(Ok(Action::TransportPositionAt {
1274                sample: loop_start,
1275                after_frames,
1276            }))
1277            .await;
1278        } else {
1279            self.notified_loop_wrap_sample = None;
1280        }
1281        if let Some(worker) = &self.hw_worker {
1282            if !self.pending_hw_midi_out_events_by_device.is_empty() {
1283                let out_events = std::mem::take(&mut self.pending_hw_midi_out_events_by_device);
1284                if let Err(e) = worker.tx.send(Message::HWMidiOutEvents(out_events)).await {
1285                    error!("Error sending HWMidiOutEvents {e}");
1286                }
1287            }
1288            match worker.tx.send(Message::TracksFinished).await {
1289                Ok(_) => {
1290                    self.awaiting_hwfinished = true;
1291                }
1292                Err(e) => {
1293                    error!("Error sending TracksFinished {e}");
1294                }
1295            }
1296        }
1297    }
1298
1299    fn mix_audio_preview_into_hw_outputs(&mut self) {
1300        let cycle_samples = self.current_cycle_samples();
1301        if cycle_samples == 0 {
1302            return;
1303        }
1304        let plan = self.executor.plan().clone();
1305        let Some(preview) = self.audio_preview.as_mut() else {
1306            return;
1307        };
1308        let channels = preview.channels.max(1);
1309        let total_frames = preview.samples.len() / channels;
1310        if preview.cursor >= total_frames {
1311            self.audio_preview = None;
1312            return;
1313        }
1314
1315        for &(buffer, channel) in &plan.hw_out_map {
1316            // Safety: request_hw_cycle runs after all render-plan producers
1317            // completed for this hardware cycle and before the hardware
1318            // backend reads the output arena.
1319            let dst = unsafe { &mut *plan.buffer_ptr(buffer) };
1320            let frames = cycle_samples.min(dst.len());
1321            dst[..frames].fill(0.0);
1322            let source_channel = channel.min(channels - 1);
1323            for (frame, out) in dst.iter_mut().take(frames).enumerate() {
1324                let source_frame = preview.cursor + frame;
1325                if source_frame >= total_frames {
1326                    break;
1327                }
1328                let sample_index = source_frame * channels + source_channel;
1329                *out = preview.samples.get(sample_index).copied().unwrap_or(0.0);
1330            }
1331        }
1332
1333        preview.cursor = preview.cursor.saturating_add(cycle_samples);
1334        if preview.cursor >= total_frames {
1335            self.audio_preview = None;
1336        }
1337    }
1338
1339    pub(crate) fn should_publish_hw_out_meters(&mut self) -> bool {
1340        let now = Instant::now();
1341        match self.last_hw_out_meter_publish {
1342            Some(last) if now.duration_since(last) < Self::METER_PUBLISH_INTERVAL => false,
1343            _ => {
1344                self.last_hw_out_meter_publish = Some(now);
1345                true
1346            }
1347        }
1348    }
1349
1350    pub(crate) fn should_publish_track_meters(&mut self) -> bool {
1351        let now = Instant::now();
1352        match self.last_track_meter_publish {
1353            Some(last) if now.duration_since(last) < Self::METER_PUBLISH_INTERVAL => false,
1354            _ => {
1355                self.last_track_meter_publish = Some(now);
1356                true
1357            }
1358        }
1359    }
1360
1361    pub(crate) fn should_publish_hw_out_linear(&mut self, peaks_linear: &[f32]) -> bool {
1362        #[cfg(unix)]
1363        {
1364            self.hw_out_meter_publish_phase = !self.hw_out_meter_publish_phase;
1365            if !self.hw_out_meter_publish_phase {
1366                return false;
1367            }
1368            let changed = if self.last_hw_out_meter_linear.len() != peaks_linear.len() {
1369                true
1370            } else {
1371                self.last_hw_out_meter_linear
1372                    .iter()
1373                    .zip(peaks_linear.iter())
1374                    .any(|(prev, next)| (prev - next).abs() >= Self::HW_OUT_METER_LINEAR_EPSILON)
1375            };
1376            if !changed {
1377                return false;
1378            }
1379            self.last_hw_out_meter_linear.clear();
1380            self.last_hw_out_meter_linear
1381                .extend_from_slice(peaks_linear);
1382            true
1383        }
1384        #[cfg(not(unix))]
1385        {
1386            let _ = peaks_linear;
1387            false
1388        }
1389    }
1390
1391    pub(crate) async fn maybe_notify_hw_out_meter(&mut self, _meter_db: Vec<f32>) {
1392        {}
1393    }
1394
1395    pub(crate) async fn apply_hw_out_gain_and_meter(&mut self) {
1396        let gain = if self.hw_out_muted {
1397            0.0
1398        } else {
1399            10.0_f32.powf(self.hw_out_level_db / 20.0)
1400        };
1401
1402        // Send master gain/balance to the driver. If there is no active audio
1403        // backend there is nothing further to meter.
1404        if let Some(worker) = &self.hw_worker {
1405            let _ = worker
1406                .tx
1407                .send(Message::HWSetOutputGainBalance {
1408                    gain,
1409                    balance: self.hw_out_balance,
1410                })
1411                .await;
1412        } else {
1413            #[cfg(unix)]
1414            {
1415                if let Some(jack) = self.jack_runtime.as_ref() {
1416                    jack.set_output_gain_linear(gain);
1417                    jack.set_output_balance(self.hw_out_balance);
1418                } else {
1419                    return;
1420                }
1421            }
1422            #[cfg(not(unix))]
1423            {
1424                return;
1425            }
1426        }
1427
1428        if self.meter_decay_after_stop.is_some() {
1429            return;
1430        }
1431
1432        // Loudness must be fed every cycle so integrated LUFS is accurate.
1433        self.feed_hw_out_loudness_meter();
1434
1435        let should_notify_interval = self.should_publish_hw_out_meters();
1436        if !should_notify_interval {
1437            return;
1438        }
1439
1440        let plan = self.executor.plan().clone();
1441        let peaks_linear =
1442            crate::hw::common::output_meter_linear_from_plan(&plan, gain, self.hw_out_balance);
1443        if self.hw_out_peak_hold_linear.len() != peaks_linear.len() {
1444            self.hw_out_peak_hold_linear.resize(peaks_linear.len(), 0.0);
1445        }
1446        let mut held_peaks = Vec::with_capacity(peaks_linear.len());
1447        for (idx, peak_now) in peaks_linear.iter().copied().enumerate() {
1448            let held = self.hw_out_peak_hold_linear[idx] * 0.92;
1449            let next = peak_now.max(held);
1450            self.hw_out_peak_hold_linear[idx] = next;
1451            held_peaks.push(next);
1452        }
1453        let should_notify = self.should_publish_hw_out_linear(&held_peaks);
1454        let meter_db: Vec<f32> = held_peaks
1455            .into_iter()
1456            .map(Self::meter_linear_to_db)
1457            .collect();
1458        self.latest_hw_out_meter_db = Arc::new(meter_db.clone());
1459        if should_notify {
1460            self.maybe_notify_hw_out_meter(meter_db).await;
1461        }
1462    }
1463
1464    fn feed_hw_out_loudness_meter(&mut self) {
1465        let Some(info) = self.hw_driver_info else {
1466            return;
1467        };
1468        let plan = self.executor.plan();
1469        let channels = plan.hw_out_map.len();
1470        if channels == 0 {
1471            return;
1472        }
1473        let sample_rate = info.sample_rate as u32;
1474
1475        let needs_recreate = self
1476            .hw_out_loudness_meter
1477            .as_ref()
1478            .is_none_or(|m| m.channels() != channels || m.sample_rate() != sample_rate);
1479        if needs_recreate {
1480            self.hw_out_loudness_meter =
1481                crate::loudness::LoudnessMeter::new(channels, sample_rate).ok();
1482        }
1483
1484        if let Some(meter) = self.hw_out_loudness_meter.as_mut() {
1485            let interleaved = crate::hw::common::interleaved_hw_out_samples(plan);
1486            meter.feed_interleaved(&interleaved);
1487        }
1488    }
1489
1490    fn update_hw_out_lufs_readout(&mut self) {
1491        self.latest_hw_out_lufs = self
1492            .hw_out_loudness_meter
1493            .as_ref()
1494            .map(|meter| meter.values());
1495    }
1496
1497    pub(crate) fn preload_track_clips_spawn(&self) {
1498        let tracks: Vec<_> = self
1499            .state_snapshot
1500            .load_full()
1501            .tracks
1502            .values()
1503            .cloned()
1504            .collect();
1505        for track in tracks {
1506            tokio::task::spawn_blocking(move || {
1507                track.lock().preload_clips();
1508            });
1509        }
1510    }
1511
1512    /// Preload all track clips and wait for completion. Used on transport
1513    /// play paths: the audio cycle must not start until clip caches are
1514    /// populated, otherwise the preload tasks' `&mut Track` access races the
1515    /// RT worker and cold-cache reads do disk I/O on the audio path.
1516    ///
1517    /// Returns an owned future rather than borrowing `self`: `Engine` is not
1518    /// `Sync` (it owns JackRuntime/MidiHub directly since Phase 6), so an
1519    /// `async fn(&self)` future would be non-`Send`.
1520    pub(crate) fn preload_track_clips(
1521        &self,
1522    ) -> impl std::future::Future<Output = ()> + Send + 'static {
1523        let tracks: Vec<_> = self
1524            .state_snapshot
1525            .load_full()
1526            .tracks
1527            .values()
1528            .cloned()
1529            .collect();
1530        Self::preload_track_handles(tracks)
1531    }
1532
1533    async fn preload_track_handles(tracks: Vec<crate::state::TrackHandle>) {
1534        if tracks.is_empty() {
1535            return;
1536        }
1537        let mut handles = Vec::with_capacity(tracks.len());
1538        for track in tracks {
1539            handles.push(tokio::task::spawn_blocking(move || {
1540                track.lock().preload_clips();
1541            }));
1542        }
1543        for handle in handles {
1544            if let Err(e) = handle.await {
1545                tracing::warn!("Clip preload task panicked: {e}");
1546            }
1547        }
1548    }
1549
1550    pub(crate) fn task_track_name(task: &ProcessTask) -> String {
1551        match task {
1552            ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => {
1553                t.lock().name.clone()
1554            }
1555            ProcessTask::Plugin { track, .. } => track.lock().name.clone(),
1556        }
1557    }
1558
1559    pub(crate) fn prepare_task_track(&self, task: &ProcessTask) {
1560        let track = match task {
1561            ProcessTask::Track(t) | ProcessTask::FolderInput(t) | ProcessTask::FolderOutput(t) => t,
1562            ProcessTask::Plugin { track, .. } => track,
1563        };
1564        let mut t = track.lock();
1565        let transport_sample = if self.session_clip_playback_enabled && self.playing {
1566            self.session_transport_sample
1567        } else {
1568            self.transport_sample
1569        };
1570        t.set_transport_sample(transport_sample);
1571        t.set_loop_config(self.loop_enabled, self.loop_range_samples);
1572        t.set_transport_timing(self.tempo_bpm, self.tsig_num, self.tsig_denom);
1573        t.set_clip_playback_enabled(self.clip_playback_enabled && self.playing);
1574        t.set_session_clip_playback_enabled(self.session_clip_playback_enabled && self.playing);
1575        t.set_record_tap_enabled(self.playing && self.record_enabled);
1576        t.audio.set_processing(true);
1577    }
1578
1579    /// Dispatch queued node jobs to ready workers, buffering the rest until
1580    /// a worker reports `Ready`. Returns true if the cycle completed while
1581    /// dispatching (only possible via abandoned nodes).
1582    pub(crate) async fn dispatch_node_jobs(&mut self, jobs: Vec<crate::executor::NodeJob>) -> bool {
1583        self.pending_node_jobs.extend(jobs);
1584        let mut cycle_complete = false;
1585        while !self.pending_node_jobs.is_empty() {
1586            let Some(worker_index) = self.take_ready_worker_index() else {
1587                break;
1588            };
1589            let Some(job) = self.pending_node_jobs.pop_front() else {
1590                break;
1591            };
1592            if let Some(crate::render_plan::Op::Task { task, .. }) =
1593                job.plan.nodes.get(job.node as usize)
1594            {
1595                self.prepare_task_track(task);
1596            }
1597            let worker = &mut self.workers[worker_index];
1598            if let Some(node_job_tx) = worker.node_job_tx.as_mut() {
1599                match node_job_tx.push(job) {
1600                    Ok(()) => {
1601                        if let Some(thread) = &worker.node_thread {
1602                            thread.unpark();
1603                        }
1604                    }
1605                    Err(rtrb::PushError::Full(job)) => {
1606                        self.pending_node_jobs.push_front(job);
1607                        self.push_ready_worker(worker_index);
1608                        break;
1609                    }
1610                }
1611            } else {
1612                let node = job.node;
1613                error!("Worker {worker_index} has no node-job mailbox");
1614                let outcome = self.executor.abandon_node(node, Instant::now());
1615                self.log_silenced_nodes(&outcome.silenced);
1616                cycle_complete |= outcome.cycle_complete;
1617                self.pending_node_jobs.extend(outcome.jobs);
1618            }
1619        }
1620        cycle_complete
1621    }
1622
1623    /// Start the next audio cycle: pull any published plan, copy hardware
1624    /// inputs, and dispatch the seed jobs. Returns true when the cycle
1625    /// completed instantly (empty plan) and `on_all_tracks_finished` ran.
1626    /// Ensure the metronome track's source port and output wiring exist
1627    /// before tasks are dispatched. Runs on the dispatcher at cycle top so
1628    /// `AudioIO.connections` is never mutated from a worker thread; marks
1629    /// the plan dirty when the wiring changed.
1630    fn ensure_metronome_wiring(&mut self) {
1631        let Some(track) = self
1632            .state_snapshot
1633            .load_full()
1634            .tracks
1635            .get(Self::METRONOME_TRACK)
1636            .cloned()
1637        else {
1638            return;
1639        };
1640        let frames = self.current_cycle_samples();
1641        let (_, changed) = track.lock().ensure_metronome_source(frames);
1642        if changed {
1643            self.plan_builder.mark_dirty();
1644        }
1645    }
1646
1647    pub(crate) async fn start_plan_cycle(&mut self) -> bool {
1648        // While a bounce job exists, plan cycles are suspended: the bounce
1649        // worker renders through live track bodies and must be their only
1650        // mutator (LOCKLESS.md Phase 5, 5b-iii).
1651        if !self.playing || !self.executor.cycle_complete() || !self.offline_bounce_jobs.is_empty()
1652        {
1653            return false;
1654        }
1655        self.refresh_realtime_infection();
1656        self.ensure_metronome_wiring();
1657        let jobs = self.executor.start_cycle(Instant::now());
1658        if self.dispatch_node_jobs(jobs).await {
1659            self.on_all_tracks_finished().await;
1660            return true;
1661        }
1662        false
1663    }
1664
1665    /// Handle a worker completion: cascade the executor, publish meters and
1666    /// parameter echoes, force timed-out nodes, and finish the cycle when
1667    /// all nodes are done.
1668    pub(crate) async fn on_node_done(
1669        &mut self,
1670        worker_id: usize,
1671        epoch: u64,
1672        node: u32,
1673        output_linear: Vec<f32>,
1674        parameter_updates: Vec<Action>,
1675        latency_changed: bool,
1676    ) {
1677        self.push_ready_worker(worker_id);
1678        let mut complete = self.dispatch_node_jobs(Vec::new()).await;
1679        if epoch != self.executor.epoch() {
1680            tracing::debug!(
1681                "dropping stale NodeDone (epoch {} vs {}) for node {}",
1682                epoch,
1683                self.executor.epoch(),
1684                node
1685            );
1686            return;
1687        }
1688        if latency_changed {
1689            self.publish_state_snapshot();
1690            self.plan_builder.mark_dirty();
1691        }
1692        let plan = self.executor.plan().clone();
1693        if let Some(crate::render_plan::Op::Task { task, .. }) = plan.nodes.get(node as usize) {
1694            let track_name = Self::task_track_name(task);
1695            self.track_meter_linear_by_track
1696                .insert(track_name, output_linear);
1697        }
1698        for action in parameter_updates {
1699            self.notify_clients(Ok(action)).await;
1700        }
1701        let now = Instant::now();
1702        let (jobs, done) = self.executor.on_node_done(epoch, node, now);
1703        complete |= done;
1704        complete |= self.dispatch_node_jobs(jobs).await;
1705        let outcome = self
1706            .executor
1707            .force_timeouts(now, Self::TRACK_PROCESS_TIMEOUT);
1708        self.log_silenced_nodes(&outcome.silenced);
1709        complete |= self.dispatch_node_jobs(outcome.jobs).await;
1710        complete |= outcome.cycle_complete;
1711        if complete {
1712            self.on_all_tracks_finished().await;
1713        }
1714    }
1715
1716    pub(crate) async fn poll_stopped_plugin_parameter_echoes(&mut self) {
1717        if self.playing || self.transport_running {
1718            return;
1719        }
1720
1721        let state = self.state_snapshot.load_full();
1722        let mut updates = Vec::new();
1723        for track in state.tracks.values() {
1724            updates.extend(track.lock().drain_plugin_parameter_echoes());
1725        }
1726        drop(state);
1727
1728        for action in updates {
1729            self.notify_clients(Ok(action)).await;
1730        }
1731    }
1732
1733    pub(crate) async fn poll_node_worker_results(&mut self) {
1734        let mut results = Vec::new();
1735        for worker in &mut self.workers {
1736            if let Some(rx) = worker.node_result_rx.as_mut() {
1737                while let Ok(result) = rx.pop() {
1738                    results.push(result);
1739                }
1740            }
1741        }
1742        for result in results {
1743            self.on_node_done(
1744                result.worker_id,
1745                result.epoch,
1746                result.node,
1747                result.output_linear,
1748                result.parameter_updates,
1749                result.latency_changed,
1750            )
1751            .await;
1752        }
1753    }
1754
1755    pub(crate) async fn poll_jack_hw_finished(&mut self) {
1756        #[cfg(unix)]
1757        {
1758            let finished = self
1759                .jack_runtime
1760                .as_ref()
1761                .map(|jack| jack.take_hw_finished_count())
1762                .unwrap_or(0);
1763            if finished > 0 {
1764                self.handle_hw_finished().await;
1765            }
1766        }
1767    }
1768
1769    #[cfg(unix)]
1770    pub(crate) async fn handle_hw_finished(&mut self) {
1771        if !self.awaiting_hwfinished {
1772            return;
1773        }
1774        tracing::debug!(
1775            playing = self.playing,
1776            transport_running = self.transport_running,
1777            transport_sample = self.transport_sample,
1778            session_transport_sample = self.session_transport_sample,
1779            cycle_samples = self.current_cycle_samples(),
1780            "HWFinished handling"
1781        );
1782        self.handling_hwfinished = true;
1783        self.awaiting_hwfinished = false;
1784        #[cfg(unix)]
1785        {
1786            if let Some(jack) = self.jack_runtime.as_mut() {
1787                if !self.pending_hw_midi_out_events.is_empty() {
1788                    let out_events = std::mem::take(&mut self.pending_hw_midi_out_events);
1789                    jack.write_events(&out_events);
1790                }
1791                let mut in_events = vec![];
1792                jack.read_events_into(&mut in_events);
1793                if !in_events.is_empty() {
1794                    self.pending_hw_midi_events.extend(in_events);
1795                }
1796                let dropped = jack.take_midi_events_dropped();
1797                if dropped > 0 {
1798                    tracing::warn!(
1799                        "JACK MIDI ring full; {dropped} events dropped since last cycle"
1800                    );
1801                }
1802            }
1803        }
1804        #[cfg(unix)]
1805        if self.jack_runtime.is_some() {
1806            self.sync_from_jack_transport().await;
1807        }
1808        while let Some(a) = self.pending_requests.pop_front() {
1809            self.handle_request(a).await;
1810        }
1811        self.apply_mute_solo_policy();
1812        self.append_recorded_cycle();
1813        self.flush_completed_recordings().await;
1814        let hw_in_routes = self.midi_hw_in_routes.clone();
1815        let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
1816        let mut reconfigured_tracks = Vec::new();
1817        let state = self.state_snapshot.load_full();
1818        for (track_name, track) in state.tracks.iter() {
1819            let mut track_lock = track.lock();
1820            if self.jack_runtime_is_some() {
1821                if !self.pending_hw_midi_events.is_empty() {
1822                    track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
1823                }
1824            } else {
1825                for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
1826                    if let Some(events) = pending_hw_in_by_device.get(&route.device) {
1827                        track_lock.push_hw_midi_events_to_port(route.to_port, events);
1828                    }
1829                }
1830            }
1831            if track_lock.setup() {
1832                reconfigured_tracks.push(track_name.clone());
1833            }
1834        }
1835        self.publish_track_meters();
1836        self.publish_session_runtime_reports().await;
1837        self.publish_clap_state_dirty().await;
1838        for track_name in reconfigured_tracks {
1839            let track = state.tracks.get(&track_name).cloned();
1840            if let Some(track) = track {
1841                let (plugins, connections, connectable_connections) = {
1842                    let track_lock = track.lock();
1843                    (
1844                        track_lock.plugin_graph_plugins(false),
1845                        track_lock.plugin_graph_connections(),
1846                        track_lock.connectable_connections(),
1847                    )
1848                };
1849                self.notify_clients(Ok(Action::TrackPluginGraph {
1850                    track_name: track_name.clone(),
1851                    plugins,
1852                    connections,
1853                    connectable_connections,
1854                }))
1855                .await;
1856            }
1857        }
1858        self.pending_hw_midi_events.clear();
1859        self.pending_hw_midi_events_by_device.clear();
1860        let cycle_samples = self.current_cycle_samples();
1861        if self.transport_running {
1862            if self.transport_panic_flush_pending {
1863                self.transport_panic_flush_pending = false;
1864            } else if self.transport_restart_pending {
1865                self.transport_restart_pending = false;
1866            } else {
1867                let before = self.transport_sample;
1868                let next = self.transport_sample.saturating_add(cycle_samples);
1869                let normalized = self.normalize_transport_sample(next);
1870                let wrapped = normalized != next;
1871                self.transport_sample = normalized;
1872                tracing::debug!(
1873                    before,
1874                    delta = cycle_samples,
1875                    next,
1876                    normalized,
1877                    wrapped,
1878                    "transport advanced after HWFinished"
1879                );
1880                self.publish_transport_snapshot();
1881                if wrapped {
1882                    if self.notified_loop_wrap_sample == Some(self.transport_sample) {
1883                        self.notified_loop_wrap_sample = None;
1884                    } else {
1885                        self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
1886                            .await;
1887                    }
1888                }
1889            }
1890        } else {
1891            tracing::debug!(
1892                playing = self.playing,
1893                cycle_samples,
1894                "transport not advanced because transport_running is false"
1895            );
1896        }
1897        if self.session_clip_playback_enabled && self.playing {
1898            let before = self.session_transport_sample;
1899            self.session_transport_sample =
1900                self.session_transport_sample.saturating_add(cycle_samples);
1901            tracing::debug!(
1902                before,
1903                delta = cycle_samples,
1904                after = self.session_transport_sample,
1905                "session transport advanced after HWFinished"
1906            );
1907        }
1908        {
1909            let echoes = self.apply_modulators(self.active_transport_sample());
1910            for action in echoes {
1911                self.notify_clients(Ok(action)).await;
1912            }
1913        }
1914        self.apply_mixosc_automation(self.active_transport_sample());
1915        let cycle_started = self.start_plan_cycle().await;
1916        // If a plan cycle is still running, its completion will request the
1917        // hardware cycle. Requesting here would replay stale arena buffers.
1918        if self.hw_worker.is_some()
1919            && !cycle_started
1920            && (self.playing || self.audio_preview.is_some())
1921            && self.executor.cycle_complete()
1922        {
1923            self.request_hw_cycle().await;
1924        }
1925        tracing::debug!(
1926            cycle_started,
1927            hw_worker = self.hw_worker.is_some(),
1928            awaiting_hwfinished = self.awaiting_hwfinished,
1929            executor_complete = self.executor.cycle_complete(),
1930            "HWFinished rearm decision"
1931        );
1932        #[cfg(unix)]
1933        {
1934            if self.jack_runtime.is_some() {
1935                self.awaiting_hwfinished = true;
1936            }
1937        }
1938        self.handling_hwfinished = false;
1939    }
1940
1941    /// Periodic tick (called from the engine loop's interval): force
1942    /// timed-out nodes even when no worker message arrives.
1943    pub(crate) async fn on_executor_tick(&mut self) {
1944        if self.executor.cycle_complete() {
1945            return;
1946        }
1947        let outcome = self
1948            .executor
1949            .force_timeouts(Instant::now(), Self::TRACK_PROCESS_TIMEOUT);
1950        self.log_silenced_nodes(&outcome.silenced);
1951        let mut complete = self.dispatch_node_jobs(outcome.jobs).await;
1952        complete |= outcome.cycle_complete;
1953        if complete {
1954            self.on_all_tracks_finished().await;
1955        }
1956    }
1957
1958    pub(crate) fn log_silenced_nodes(&self, silenced: &[u32]) {
1959        for &node in silenced {
1960            let plan = self.executor.plan();
1961            let name = match plan.nodes.get(node as usize) {
1962                Some(crate::render_plan::Op::Task { task, .. }) => Self::task_track_name(task),
1963                _ => format!("node {node}"),
1964            };
1965            tracing::warn!(
1966                "Node {} ('{}') exceeded process timeout ({} ms); forced silent completion for cycle",
1967                node,
1968                name,
1969                Self::TRACK_PROCESS_TIMEOUT.as_millis()
1970            );
1971        }
1972    }
1973
1974    /// Hand a bounce job to its reserved worker, recording the worker→track
1975    /// mapping so the terminal `Ready(id)` cleans the job up on any path.
1976    pub(crate) async fn send_bounce_job(
1977        &mut self,
1978        worker_index: usize,
1979        job: crate::message::OfflineBounceWork,
1980    ) {
1981        let track_name = job.track_name.clone();
1982        self.bounce_worker_tracks
1983            .insert(worker_index, track_name.clone());
1984        let worker = &self.workers[worker_index];
1985        if let Err(e) = worker.tx.send(Message::ProcessOfflineBounce(job)).await {
1986            self.bounce_worker_tracks.remove(&worker_index);
1987            self.offline_bounce_jobs.remove(&track_name);
1988            self.push_ready_worker(worker_index);
1989            self.notify_clients(Err(format!("Failed to schedule offline bounce: {e}")))
1990                .await;
1991        }
1992    }
1993
1994    /// Requests queued while a bounce was running are replayed once the last
1995    /// bounce job is gone (and plan cycles resume).
1996    async fn drain_pending_requests_if_idle(&mut self) {
1997        if self.offline_bounce_jobs.is_empty() {
1998            while let Some(next) = self.pending_requests.pop_front() {
1999                self.handle_request(next).await;
2000            }
2001        }
2002    }
2003
2004    pub(crate) async fn on_all_tracks_finished(&mut self) {
2005        // Hand deferred bounce jobs to their workers now that no plan cycle
2006        // is in flight (see handle_track_offline_bounce).
2007        let pending = std::mem::take(&mut self.pending_bounce_starts);
2008        for (worker_index, job) in pending {
2009            self.send_bounce_job(worker_index, job).await;
2010        }
2011        if self.transport_restart_pending {
2012            let state = self.state_snapshot.load_full();
2013            for track in state.tracks.values() {
2014                track.lock().take_hw_midi_out_events();
2015            }
2016        } else if self.hw_worker.is_some() {
2017            self.active_hw_notes_cycle_start = self.active_hw_notes_by_track.clone();
2018            let mut out_events = self.collect_hw_midi_output_events_by_device();
2019            if self.loop_enabled
2020                && let Some((_, loop_end)) = self.loop_range_samples
2021            {
2022                let cycle_end = self
2023                    .transport_sample
2024                    .saturating_add(self.current_cycle_samples());
2025                if self.transport_sample < loop_end && cycle_end >= loop_end {
2026                    let wrap_frame = loop_end
2027                        .saturating_sub(self.transport_sample)
2028                        .min(self.current_cycle_samples())
2029                        as u32;
2030                    out_events.extend(self.note_off_events_for_active_snapshot(
2031                        &self.active_hw_notes_cycle_start,
2032                        wrap_frame,
2033                    ));
2034                    out_events.sort_by(|a, b| {
2035                        a.event
2036                            .frame
2037                            .cmp(&b.event.frame)
2038                            .then_with(|| a.device.cmp(&b.device))
2039                    });
2040                }
2041            }
2042            self.pending_hw_midi_out_events_by_device.extend(out_events);
2043        } else {
2044            self.pending_hw_midi_out_events = self.collect_hw_midi_output_events();
2045        }
2046        self.request_hw_cycle().await;
2047    }
2048
2049    pub(crate) fn take_ready_worker_index(&mut self) -> Option<usize> {
2050        while !self.ready_workers.is_empty() {
2051            let worker_index = self.ready_workers.remove(0);
2052            if worker_index < self.workers.len() {
2053                return Some(worker_index);
2054            }
2055        }
2056        None
2057    }
2058
2059    pub(crate) fn push_ready_worker(&mut self, worker_index: usize) {
2060        self.ready_workers.push(worker_index);
2061    }
2062
2063    pub(crate) fn publish_track_meters(&mut self) {
2064        if !self.should_publish_track_meters() {
2065            return;
2066        }
2067        if self.meter_decay_after_stop.is_some() {
2068            self.update_meter_decay_after_stop();
2069            return;
2070        }
2071        let tracks: Vec<(String, crate::state::TrackHandle)> = self
2072            .state_snapshot
2073            .load_full()
2074            .tracks
2075            .iter()
2076            .map(|(name, track)| (name.clone(), track.clone()))
2077            .collect();
2078        let mut snapshot = Vec::with_capacity(tracks.len());
2079        for (name, track) in &tracks {
2080            let linear = self
2081                .track_meter_linear_by_track
2082                .get(name)
2083                .cloned()
2084                .unwrap_or_else(|| track.lock().output_meter_linear());
2085            let output_db = linear
2086                .iter()
2087                .copied()
2088                .map(Self::meter_linear_to_db)
2089                .collect::<Vec<_>>();
2090            snapshot.push((name.clone(), output_db));
2091        }
2092        self.latest_track_meter_snapshot = Arc::new(snapshot);
2093    }
2094
2095    fn record_session_completed_clip_pass(
2096        &mut self,
2097        track_name: String,
2098        scene_index: usize,
2099        clip_id: String,
2100        pass_index: usize,
2101        start_sample: usize,
2102        length_samples: usize,
2103    ) {
2104        let key = (
2105            track_name.clone(),
2106            scene_index,
2107            clip_id.clone(),
2108            pass_index,
2109            start_sample,
2110        );
2111        if self.session_reported_clip_passes.insert(key) {
2112            self.session_completed_clip_passes
2113                .push(crate::meter::SessionCompletedClipPass {
2114                    track_name,
2115                    scene_index,
2116                    clip_id,
2117                    pass_index,
2118                    start_sample,
2119                    length_samples,
2120                });
2121        }
2122    }
2123
2124    fn record_completed_session_scene_span(
2125        &mut self,
2126        tracks: &HashMap<String, crate::state::TrackHandle>,
2127        scene_index: usize,
2128        previous_scene: Option<usize>,
2129        scene_start_sample: usize,
2130        elapsed_samples: usize,
2131    ) {
2132        for (track_name, track) in tracks {
2133            let track = track.lock();
2134            let slot = track.rt.session_slots.get(&scene_index);
2135            let prev_slot = previous_scene.and_then(|scene| track.rt.session_slots.get(&scene));
2136            let playing_clip_id = track
2137                .rt
2138                .playing_session_clips
2139                .last()
2140                .map(|clip| clip.clip_id.as_str());
2141            let clip_id = match (slot, prev_slot) {
2142                (Some(slot), _) if slot.play_enabled => Some(slot.clip_id.as_str()),
2143                (Some(slot), _) if slot.stop_enabled => None,
2144                (_, Some(prev_slot)) if prev_slot.play_enabled => Some(prev_slot.clip_id.as_str()),
2145                (_, Some(prev_slot)) if prev_slot.stop_enabled => None,
2146                _ => playing_clip_id,
2147            }
2148            .filter(|clip_id| !clip_id.is_empty());
2149            let Some(clip_id) = clip_id else { continue };
2150            let clip_length = track
2151                .session_clip_length(clip_id, crate::kind::Kind::Audio)
2152                .or_else(|| track.session_clip_length(clip_id, crate::kind::Kind::MIDI))
2153                .unwrap_or(0);
2154            if clip_length == 0 {
2155                continue;
2156            }
2157            let completed_passes = elapsed_samples / clip_length;
2158            for pass_index in 0..completed_passes {
2159                self.record_session_completed_clip_pass(
2160                    track_name.clone(),
2161                    scene_index,
2162                    clip_id.to_string(),
2163                    pass_index,
2164                    scene_start_sample.saturating_add(pass_index * clip_length),
2165                    clip_length,
2166                );
2167            }
2168        }
2169    }
2170
2171    pub(crate) async fn publish_session_runtime_reports(&mut self) {
2172        let mut current = HashMap::<(String, usize), (SessionSlotState, usize, usize)>::new();
2173        {
2174            let state = self.state_snapshot.load_full();
2175            if let Some((queued_scene, queued_launch_at)) = self.session_scene_queue {
2176                // Drop the queue marker once it has fired: no matching
2177                // pending launch or scheduled stop remains and the session
2178                // transport has passed the launch time. The transport check
2179                // matters when the queued scene scheduled nothing (all
2180                // tracks continue): the marker must still hold until the
2181                // launch time arrives. The fired scene becomes the current
2182                // one.
2183                let still_pending = state.tracks.values().any(|track| {
2184                    let track = track.lock();
2185                    track.rt.pending_session_launches.iter().any(|launch| {
2186                        launch.scene_index == queued_scene
2187                            && launch.launch_at_sample == queued_launch_at
2188                    }) || track
2189                        .rt
2190                        .playing_session_clips
2191                        .iter()
2192                        .any(|clip| clip.stop_at_sample == Some(queued_launch_at))
2193                });
2194                if !still_pending && self.session_transport_sample >= queued_launch_at {
2195                    if let Some(current_scene) = self.session_current_scene {
2196                        let scene_start = self.session_current_scene_start_sample;
2197                        let elapsed = queued_launch_at.saturating_sub(scene_start);
2198                        self.record_completed_session_scene_span(
2199                            &state.tracks,
2200                            current_scene,
2201                            self.session_current_scene_previous_scene,
2202                            scene_start,
2203                            elapsed,
2204                        );
2205                    }
2206                    self.session_scene_queue = None;
2207                    self.session_current_scene_previous_scene = self.session_current_scene;
2208                    self.session_current_scene = Some(queued_scene);
2209                    self.session_current_scene_start_sample = queued_launch_at;
2210                    self.session_current_scene_length_samples =
2211                        self.session_scene_queue_length_samples;
2212                    self.session_scene_queue_length_samples = 0;
2213                }
2214            }
2215            if let Some(current_scene) = self.session_current_scene
2216                && self.session_current_scene_length_samples > 0
2217            {
2218                self.record_completed_session_scene_span(
2219                    &state.tracks,
2220                    current_scene,
2221                    self.session_current_scene_previous_scene,
2222                    self.session_current_scene_start_sample,
2223                    self.session_transport_sample
2224                        .saturating_sub(self.session_current_scene_start_sample),
2225                );
2226            }
2227            for (track_name, track) in &state.tracks {
2228                let track = track.lock();
2229                for launch in &track.rt.pending_session_launches {
2230                    current.insert(
2231                        (track_name.clone(), launch.scene_index),
2232                        (SessionSlotState::Queued, 0, 0),
2233                    );
2234                }
2235                for clip in &track.rt.playing_session_clips {
2236                    if self.session_current_scene_length_samples == 0
2237                        && let Some(clip_length) =
2238                            track.session_clip_length(&clip.clip_id, clip.kind)
2239                    {
2240                        let scene_report = self
2241                            .session_current_scene
2242                            .map(|_| self.session_current_scene_length_samples)
2243                            .filter(|length| *length > 0)
2244                            .map(|length| {
2245                                (
2246                                    self.session_current_scene.unwrap_or(clip.scene_index),
2247                                    self.session_current_scene_start_sample,
2248                                    self.session_transport_sample
2249                                        .saturating_sub(self.session_current_scene_start_sample),
2250                                    length,
2251                                )
2252                            });
2253                        let launch_sample = self
2254                            .session_transport_sample
2255                            .saturating_sub(clip.elapsed_samples);
2256                        let (
2257                            report_scene_index,
2258                            report_start_sample,
2259                            report_elapsed,
2260                            report_length,
2261                        ) = scene_report.unwrap_or((
2262                            clip.scene_index,
2263                            launch_sample,
2264                            clip.elapsed_samples,
2265                            clip_length,
2266                        ));
2267                        if report_length == 0 {
2268                            continue;
2269                        }
2270                        let completed_passes = report_elapsed / report_length;
2271                        for pass_index in 0..completed_passes {
2272                            self.record_session_completed_clip_pass(
2273                                track_name.clone(),
2274                                report_scene_index,
2275                                clip.clip_id.clone(),
2276                                pass_index,
2277                                report_start_sample.saturating_add(pass_index * report_length),
2278                                report_length,
2279                            );
2280                        }
2281                    }
2282                    current.insert(
2283                        (track_name.clone(), clip.scene_index),
2284                        (
2285                            SessionSlotState::Playing,
2286                            clip.play_position_samples,
2287                            clip.elapsed_samples,
2288                        ),
2289                    );
2290                }
2291            }
2292        }
2293
2294        if self
2295            .last_session_report_publish
2296            .is_some_and(|t| t.elapsed() < Self::SESSION_RUNTIME_REPORT_INTERVAL)
2297        {
2298            return;
2299        }
2300
2301        let snapshot = self.session_runtime_snapshot_producer.write_buffer();
2302        snapshot.session_sample = self.session_transport_sample;
2303        snapshot.slots.clear();
2304        snapshot.slots.extend(current.iter().map(
2305            |((track_name, scene_index), (state, play_position_samples, elapsed_samples))| {
2306                crate::meter::SessionRuntimeSlotSnapshot {
2307                    track_name: track_name.clone(),
2308                    scene_index: *scene_index,
2309                    state: *state,
2310                    play_position_samples: *play_position_samples,
2311                    elapsed_samples: *elapsed_samples,
2312                }
2313            },
2314        ));
2315        snapshot.completed_clip_passes = self.session_completed_clip_passes.clone();
2316        snapshot.current_scene = self.session_current_scene;
2317        self.session_runtime_snapshot_producer.publish();
2318        self.last_session_report_publish = Some(Instant::now());
2319    }
2320
2321    pub(crate) async fn publish_clap_state_dirty(&mut self) {
2322        let tracks: Vec<(String, crate::state::TrackHandle)> = self
2323            .state_snapshot
2324            .load_full()
2325            .tracks
2326            .iter()
2327            .map(|(name, track)| (name.clone(), track.clone()))
2328            .collect();
2329        for (track_name, track) in &tracks {
2330            let dirty = track.lock().take_dirty_clap_instances();
2331            for instance_id in dirty {
2332                self.notify_clients(Ok(Action::TrackClapStateDirty {
2333                    track_name: track_name.clone(),
2334                    instance_id,
2335                }))
2336                .await;
2337            }
2338        }
2339    }
2340
2341    pub(crate) fn reset_meters_after_stop(&mut self) {
2342        self.last_hw_out_meter_publish = None;
2343        self.last_track_meter_publish = None;
2344        self.last_meter_snapshot_publish = None;
2345        #[cfg(unix)]
2346        {
2347            self.last_hw_out_meter_linear.clear();
2348        }
2349
2350        let tracks: Vec<(String, crate::state::TrackHandle)> = self
2351            .state_snapshot
2352            .load_full()
2353            .tracks
2354            .iter()
2355            .map(|(name, track)| (name.clone(), track.clone()))
2356            .collect();
2357        let mut track_linear = Vec::with_capacity(tracks.len());
2358        for (name, track) in tracks {
2359            let linear = self
2360                .track_meter_linear_by_track
2361                .get(&name)
2362                .cloned()
2363                .unwrap_or_else(|| track.lock().output_meter_linear());
2364            track_linear.push((name, linear));
2365        }
2366        let hw_out_linear = if self.hw_out_peak_hold_linear.is_empty() {
2367            self.latest_hw_out_meter_db
2368                .iter()
2369                .copied()
2370                .map(Self::meter_db_to_linear)
2371                .collect()
2372        } else {
2373            self.hw_out_peak_hold_linear.clone()
2374        };
2375        self.meter_decay_after_stop = Some(MeterDecay {
2376            started_at: Instant::now(),
2377            hw_out_linear,
2378            track_linear,
2379        });
2380        self.update_meter_decay_after_stop();
2381        self.publish_meter_snapshot();
2382    }
2383
2384    pub(crate) fn update_meter_decay_after_stop(&mut self) {
2385        let Some(decay) = self.meter_decay_after_stop.as_ref() else {
2386            return;
2387        };
2388        let elapsed = decay.started_at.elapsed();
2389        if elapsed >= Self::METER_DECAY_AFTER_STOP {
2390            self.finish_meter_decay_after_stop();
2391            return;
2392        }
2393
2394        let remaining = 1.0 - (elapsed.as_secs_f32() / Self::METER_DECAY_AFTER_STOP.as_secs_f32());
2395        let hw_out_linear = decay
2396            .hw_out_linear
2397            .iter()
2398            .copied()
2399            .map(|value| value * remaining)
2400            .collect::<Vec<_>>();
2401        self.latest_hw_out_meter_db = Arc::new(
2402            hw_out_linear
2403                .iter()
2404                .copied()
2405                .map(Self::meter_linear_to_db)
2406                .collect(),
2407        );
2408        self.hw_out_peak_hold_linear = hw_out_linear;
2409
2410        let mut track_linear_by_track = HashMap::with_capacity(decay.track_linear.len());
2411        let mut snapshot = Vec::with_capacity(decay.track_linear.len());
2412        for (name, start_linear) in &decay.track_linear {
2413            let linear = start_linear
2414                .iter()
2415                .copied()
2416                .map(|value| value * remaining)
2417                .collect::<Vec<_>>();
2418            let output_db = linear
2419                .iter()
2420                .copied()
2421                .map(Self::meter_linear_to_db)
2422                .collect::<Vec<_>>();
2423            track_linear_by_track.insert(name.clone(), linear);
2424            snapshot.push((name.clone(), output_db));
2425        }
2426        self.track_meter_linear_by_track = track_linear_by_track;
2427        self.latest_track_meter_snapshot = Arc::new(snapshot);
2428    }
2429
2430    pub(crate) fn finish_meter_decay_after_stop(&mut self) {
2431        self.meter_decay_after_stop = None;
2432        self.hw_out_peak_hold_linear.fill(0.0);
2433        let hw_channels = self.latest_hw_out_meter_db.len();
2434        self.latest_hw_out_meter_db = Arc::new(vec![-90.0; hw_channels]);
2435
2436        let tracks: Vec<(String, crate::state::TrackHandle)> = self
2437            .state_snapshot
2438            .load_full()
2439            .tracks
2440            .iter()
2441            .map(|(name, track)| (name.clone(), track.clone()))
2442            .collect();
2443        self.track_meter_linear_by_track.clear();
2444        let mut snapshot = Vec::with_capacity(tracks.len());
2445        for (name, track) in tracks {
2446            let mut t = track.lock();
2447            t.clear_output_meters();
2448            let width = t.output_meter_linear().len();
2449            let zero_linear = vec![0.0; width];
2450            self.track_meter_linear_by_track
2451                .insert(name.clone(), zero_linear);
2452            snapshot.push((name, vec![-90.0; width]));
2453        }
2454        self.latest_track_meter_snapshot = Arc::new(snapshot);
2455        self.publish_meter_snapshot();
2456    }
2457
2458    pub(crate) fn publish_meter_snapshot_if_due(&mut self) {
2459        let now = Instant::now();
2460        if self
2461            .last_meter_snapshot_publish
2462            .is_some_and(|last| now.duration_since(last) < Self::METER_PUBLISH_INTERVAL)
2463        {
2464            return;
2465        }
2466        self.last_meter_snapshot_publish = Some(now);
2467        self.update_meter_decay_after_stop();
2468        self.publish_meter_snapshot();
2469    }
2470
2471    pub(crate) fn publish_meter_snapshot(&mut self) {
2472        // Query LUFS at the same cadence as the VU meter snapshot rather than
2473        // every audio cycle, which keeps the CPU cost low.
2474        self.update_hw_out_lufs_readout();
2475
2476        let snapshot = self.meter_snapshot_producer.write_buffer();
2477        snapshot.hw_out_db.clear();
2478        snapshot
2479            .hw_out_db
2480            .extend(self.latest_hw_out_meter_db.iter().copied());
2481        snapshot.hw_out_lufs = self.latest_hw_out_lufs;
2482        snapshot.track_meters.clear();
2483        snapshot
2484            .track_meters
2485            .extend(self.latest_track_meter_snapshot.iter().cloned());
2486        self.meter_snapshot_producer.publish();
2487    }
2488
2489    pub(crate) fn publish_transport_snapshot(&mut self) {
2490        let snapshot = self.transport_snapshot_producer.write_buffer();
2491        snapshot.sample = self.transport_sample;
2492        snapshot.tempo_bpm = self.tempo_bpm;
2493        snapshot.playing = self.playing;
2494        snapshot.transport_running = self.transport_running;
2495        snapshot.tsig_num = self.tsig_num;
2496        snapshot.tsig_denom = self.tsig_denom;
2497        self.transport_snapshot_producer.publish();
2498    }
2499
2500    pub(crate) async fn handle_request(&mut self, a: Action) {
2501        match a {
2502            Action::Log { source, message } => {
2503                self.notify_clients(Ok(Action::Log { source, message }))
2504                    .await;
2505            }
2506            Action::Undo => {
2507                let actions = match self.history.undo() {
2508                    Some(actions) => actions,
2509                    None => {
2510                        self.notify_clients(Ok(Action::Undo)).await;
2511                        self.notify_clients(Ok(Action::HistoryState {
2512                            dirty: self.history.is_dirty(),
2513                        }))
2514                        .await;
2515                        return;
2516                    }
2517                };
2518
2519                let was_suspended = self.history_suspended;
2520                self.history_suspended = true;
2521                for action in actions {
2522                    self.handle_request_inner(action, false).await;
2523                }
2524                self.history_suspended = was_suspended;
2525                self.notify_clients(Ok(Action::Undo)).await;
2526                self.notify_clients(Ok(Action::HistoryState {
2527                    dirty: self.history.is_dirty(),
2528                }))
2529                .await;
2530            }
2531            Action::Redo => {
2532                let actions = match self.history.redo() {
2533                    Some(actions) => actions,
2534                    None => {
2535                        self.notify_clients(Ok(Action::Redo)).await;
2536                        self.notify_clients(Ok(Action::HistoryState {
2537                            dirty: self.history.is_dirty(),
2538                        }))
2539                        .await;
2540                        return;
2541                    }
2542                };
2543
2544                let was_suspended = self.history_suspended;
2545                self.history_suspended = true;
2546                for action in actions {
2547                    self.handle_request_inner(action, false).await;
2548                }
2549                self.history_suspended = was_suspended;
2550                self.notify_clients(Ok(Action::Redo)).await;
2551                self.notify_clients(Ok(Action::HistoryState {
2552                    dirty: self.history.is_dirty(),
2553                }))
2554                .await;
2555            }
2556            Action::ApplyGroupedActions(actions) => {
2557                self.handle_request_inner(Action::BeginHistoryGroup, true)
2558                    .await;
2559                for action in actions {
2560                    self.handle_request_inner(action, true).await;
2561                }
2562                self.handle_request_inner(Action::EndHistoryGroup, true)
2563                    .await;
2564            }
2565            Action::Session(_) => {
2566                self.handle_request_inner(a, false).await;
2567            }
2568            other => {
2569                self.handle_request_inner(other, true).await;
2570            }
2571        }
2572        self.publish_state_snapshot();
2573    }
2574
2575    pub(crate) async fn handle_quit(&mut self, a: Action) {
2576        self.flush_recordings().await;
2577        // Stop the HW worker before notifying the GUI so the
2578        // OSS audio channels are halted and closed from the
2579        // worker's own thread. The GUI calls exit(0) upon
2580        // receiving the Quit response, which skips Rust
2581        // destructors. Without this, the kernel's dsp_close
2582        // drains pending audio buffers for up to CHN_TIMEOUT
2583        // (5s) during process teardown.
2584        if let Some(mut worker) = self.hw_worker.take() {
2585            // Send MIDI panic (All Sound Off) for any active
2586            // notes before stopping the worker.
2587            let panic_events = self.panic_events_for_all_hw_midi_outputs();
2588            if !panic_events.is_empty() {
2589                let _ = worker.tx.send(Message::HWMidiOutEvents(panic_events)).await;
2590            }
2591            // Send Quit to the worker so it stops its audio
2592            // cycle loop and releases the driver.
2593            if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2594                error!("Error sending quit message to HW worker: {e}");
2595            }
2596            if let Some(handle) = worker.handle.take() {
2597                handle
2598                    .await
2599                    .unwrap_or_else(|e| error!("Error waiting for HW worker to quit: {e}"));
2600            }
2601        }
2602        // Explicitly close audio and MIDI fds before sending
2603        // the Quit response. The GUI calls exit(0) upon
2604        // receiving it, which skips destructors — any
2605        // still-open device fd would trigger the kernel's
2606        // 5-second drain during process teardown.
2607        if let Some(hw) = self.hw_driver.as_mut() {
2608            hw.close_fds();
2609        }
2610        if let Some(midi_hub) = self.midi_hub.as_mut() {
2611            midi_hub.close_all();
2612        }
2613        self.hw_driver = None;
2614        self.hw_driver_info = None;
2615        self.hw_input_ports.clear();
2616        self.hw_output_ports.clear();
2617        self.notify_clients(Ok(Action::Quit)).await;
2618        self.ready_workers.clear();
2619        while !self.workers.is_empty() {
2620            let mut worker = self.workers.remove(0);
2621            if let Err(e) = worker.tx.send(Message::Request(a.clone())).await {
2622                error!("Error sending quit message to worker: {e}");
2623            }
2624            if let Some(handle) = worker.handle.take() {
2625                handle
2626                    .await
2627                    .unwrap_or_else(|e| error!("Error waiting for worker to quit: {e}"));
2628            }
2629        }
2630        #[cfg(unix)]
2631        {
2632            self.jack_runtime = None;
2633        }
2634        self.osc_server = None;
2635    }
2636
2637    #[inline]
2638    pub(crate) fn box_bool<'a>(
2639        fut: impl std::future::Future<Output = bool> + Send + 'a,
2640    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + 'a>> {
2641        Box::pin(fut)
2642    }
2643
2644    pub(crate) async fn handle_request_inner(
2645        &mut self,
2646        mut action_to_process: Action,
2647        record_history: bool,
2648    ) {
2649        let a = action_to_process.clone();
2650        let suppress_timing_history = self.playing
2651            && matches!(
2652                &action_to_process,
2653                Action::SetTempo(_) | Action::SetTimeSignature { .. } | Action::SetTempoMap { .. }
2654            );
2655        let mut inverse_actions = self.prepare_inverse_actions(
2656            &action_to_process,
2657            record_history,
2658            suppress_timing_history,
2659        );
2660
2661        match action_to_process {
2662            Action::Play => {
2663                if Self::box_bool(self.handle_play(a.clone())).await {
2664                    return;
2665                }
2666            }
2667            Action::Pause => {
2668                if Self::box_bool(self.handle_pause(a.clone())).await {
2669                    return;
2670                }
2671            }
2672            Action::Stop => {
2673                if Self::box_bool(self.handle_stop(a.clone())).await {
2674                    return;
2675                }
2676            }
2677            Action::SessionPlay => {
2678                if Self::box_bool(self.handle_session_play(a.clone())).await {
2679                    return;
2680                }
2681            }
2682            Action::JumpToEnd => {
2683                self.transport_sample = self.normalize_transport_sample(self.session_end_sample());
2684                self.publish_transport_snapshot();
2685                self.notify_clients(Ok(Action::TransportPosition(self.transport_sample)))
2686                    .await;
2687            }
2688            Action::Panic => {
2689                if Self::box_bool(self.handle_panic(a.clone())).await {
2690                    return;
2691                }
2692            }
2693            Action::Session(ref session_action) => {
2694                self.handle_session_action(session_action.clone()).await;
2695            }
2696            Action::SessionRuntimeReport { .. } => {}
2697            Action::SessionMidiLearnTriggered { .. } => {}
2698            Action::SetClipPlaybackEnabled(enabled) => {
2699                self.clip_playback_enabled = enabled;
2700                for track in self.state_snapshot.load_full().tracks.values() {
2701                    track.lock().set_clip_playback_enabled(enabled);
2702                }
2703            }
2704            Action::SetSessionClipPlaybackEnabled(enabled) => {
2705                self.session_clip_playback_enabled = enabled;
2706                for track in self.state_snapshot.load_full().tracks.values() {
2707                    track.lock().set_session_clip_playback_enabled(enabled);
2708                }
2709            }
2710            Action::TransportPosition(..) => {
2711                if Self::box_bool(self.handle_transport_position(a.clone())).await {
2712                    return;
2713                }
2714            }
2715            Action::SetLoopEnabled(enabled) => {
2716                self.loop_enabled = enabled && self.loop_range_samples.is_some();
2717                self.notified_loop_wrap_sample = None;
2718            }
2719            Action::SetLoopRange(..) => {
2720                if Self::box_bool(self.handle_set_loop_range(a.clone())).await {
2721                    return;
2722                }
2723            }
2724            Action::SetPunchEnabled(enabled) => {
2725                self.punch_enabled = enabled && self.punch_range_samples.is_some();
2726            }
2727            Action::SetPunchRange(range) => {
2728                self.punch_range_samples = range.and_then(|(start, end)| {
2729                    if end > start {
2730                        Some((start, end))
2731                    } else {
2732                        None
2733                    }
2734                });
2735                self.punch_enabled = self.punch_range_samples.is_some();
2736            }
2737            Action::SetMetronomeEnabled(enabled) => {
2738                self.metronome_enabled = enabled;
2739                if enabled {
2740                    self.ensure_metronome_track().await;
2741                }
2742                if let Some(track) = self
2743                    .state_snapshot
2744                    .load_full()
2745                    .tracks
2746                    .get(Self::METRONOME_TRACK)
2747                    .cloned()
2748                {
2749                    track.lock().set_metronome_enabled(enabled);
2750                }
2751            }
2752            Action::SetTempo(bpm) => {
2753                self.tempo_bpm = bpm.max(1.0);
2754                self.publish_transport_snapshot();
2755            }
2756            Action::SetTimeSignature {
2757                numerator,
2758                denominator,
2759            } => {
2760                self.tsig_num = numerator.max(1);
2761                self.tsig_denom = denominator.max(1);
2762                self.publish_transport_snapshot();
2763            }
2764            Action::SetTempoMap {
2765                ref tempo_points,
2766                ref time_signature_points,
2767            } => {
2768                self.tempo_points = tempo_points.clone();
2769                self.time_signature_points = time_signature_points.clone();
2770                self.update_global_tempo_from_map();
2771                self.publish_transport_snapshot();
2772            }
2773            Action::SetOscEnabled(enabled) => {
2774                if let Err(err) = self.set_osc_enabled_with(enabled, OscServer::start) {
2775                    self.notify_clients(Err(err)).await;
2776                }
2777            }
2778            Action::SetRecordEnabled(..) => {
2779                if Self::box_bool(self.handle_set_record_enabled(a.clone())).await {
2780                    return;
2781                }
2782            }
2783            Action::SetModulators(ref modulators) => {
2784                self.modulators = modulators.clone();
2785                let echoes = self.apply_modulators(self.active_transport_sample());
2786                for action in echoes {
2787                    self.notify_clients(Ok(action)).await;
2788                }
2789            }
2790            Action::SetTrackAutomationLanes {
2791                ref track_name,
2792                ref lanes,
2793                mode,
2794            } => {
2795                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
2796                    let mut track = track.lock();
2797                    track.automation_lanes = lanes.clone();
2798                    track.set_automation_mode(mode);
2799                }
2800            }
2801            Action::TrackAutomationToggleLane { .. } => {
2802                if Self::box_bool(self.handle_track_automation_toggle_lane(a.clone())).await {
2803                    return;
2804                }
2805            }
2806            Action::TrackAutomationInsertPoint { .. } => {
2807                if Self::box_bool(self.handle_track_automation_insert_point(a.clone())).await {
2808                    return;
2809                }
2810            }
2811            Action::TrackAutomationDeletePoint { .. } => {
2812                if Self::box_bool(self.handle_track_automation_delete_point(a.clone())).await {
2813                    return;
2814                }
2815            }
2816            Action::TrackAutomationSetMode {
2817                ref track_name,
2818                mode,
2819            } => {
2820                if let Some(track) = self
2821                    .state_snapshot
2822                    .load_full()
2823                    .tracks
2824                    .get(track_name)
2825                    .cloned()
2826                {
2827                    track.lock().set_automation_mode(mode);
2828                }
2829            }
2830            Action::RequestTrackList => {
2831                let names: Vec<String> = self
2832                    .state_snapshot
2833                    .load_full()
2834                    .tracks
2835                    .keys()
2836                    .cloned()
2837                    .collect();
2838                self.notify_clients(Ok(Action::TrackList(names))).await;
2839            }
2840            Action::TrackList(_) => {}
2841            Action::RequestTransportState => {
2842                self.notify_clients(Ok(Action::TransportState {
2843                    sample: self.transport_sample,
2844                    tempo_bpm: self.tempo_bpm,
2845                    playing: self.playing,
2846                    paused: !self.transport_running && self.playing,
2847                    tsig_num: self.tsig_num,
2848                    tsig_denom: self.tsig_denom,
2849                }))
2850                .await;
2851            }
2852            Action::TransportState { .. } => {}
2853            Action::SetStepRecording(enabled) => {
2854                self.step_recording_enabled = enabled;
2855            }
2856            Action::BeginHistoryGroup if self.history_group.is_none() => {
2857                self.history_group = Some(UndoEntry {
2858                    forward_actions: vec![],
2859                    inverse_actions: vec![],
2860                });
2861            }
2862            Action::EndHistoryGroup => {
2863                if let Some(mut group) = self.history_group.take()
2864                    && !group.forward_actions.is_empty()
2865                    && !group.inverse_actions.is_empty()
2866                {
2867                    let mut add_tracks = Vec::new();
2868                    let mut connections = Vec::new();
2869                    let mut rest = Vec::new();
2870                    for action in group.inverse_actions {
2871                        if matches!(action, Action::AddTrack { .. }) {
2872                            add_tracks.push(action);
2873                        } else if matches!(action, Action::Connect { .. }) {
2874                            connections.push(action);
2875                        } else {
2876                            rest.push(action);
2877                        }
2878                    }
2879                    group.inverse_actions = add_tracks;
2880                    group.inverse_actions.extend(rest);
2881                    group.inverse_actions.extend(connections);
2882                    self.history.record(group);
2883                }
2884            }
2885            Action::SetSessionPath(ref path) => {
2886                self.session_dir = Some(Path::new(path).to_path_buf());
2887                self.ensure_session_subdirs();
2888                #[cfg(unix)]
2889                let _lv2_dir = self.session_plugins_dir();
2890                for track in self.state_snapshot.load_full().tracks.values() {
2891                    track.lock().set_session_base_dir(self.session_dir.clone());
2892                }
2893            }
2894            Action::MarkHistorySavePoint => {
2895                self.history.mark_save_point();
2896                self.notify_clients(Ok(Action::HistoryState {
2897                    dirty: self.history.is_dirty(),
2898                }))
2899                .await;
2900            }
2901            Action::ClearHistory => {
2902                self.history.clear();
2903                self.history.mark_save_point();
2904            }
2905            Action::BeginSessionRestore => {
2906                self.history_suspended = true;
2907                self.history.clear();
2908            }
2909            Action::EndSessionRestore => {
2910                self.history.clear();
2911                self.history_suspended = false;
2912                self.preload_track_clips_spawn();
2913            }
2914            Action::Quit => {
2915                self.handle_quit(a.clone()).await;
2916                return;
2917            }
2918            Action::AddTrack { .. } => {
2919                self.handle_add_track(a.clone()).await;
2920            }
2921            Action::TrackAddAudioInput(..) => {
2922                if Self::box_bool(self.handle_track_add_audio_input(a.clone())).await {
2923                    return;
2924                }
2925            }
2926            Action::TrackAddAudioOutput(..) => {
2927                if Self::box_bool(self.handle_track_add_audio_output(a.clone())).await {
2928                    return;
2929                }
2930            }
2931            Action::TrackRemoveAudioInput(..) => {
2932                if Self::box_bool(self.handle_track_remove_audio_input(a.clone())).await {
2933                    return;
2934                }
2935            }
2936            Action::TrackRemoveAudioOutput(..) => {
2937                if Self::box_bool(self.handle_track_remove_audio_output(a.clone())).await {
2938                    return;
2939                }
2940            }
2941            Action::RenameTrack { .. } => {
2942                if Self::box_bool(self.handle_rename_track(a.clone())).await {
2943                    return;
2944                }
2945            }
2946            Action::RemoveTrack(ref name) => {
2947                self.handle_remove_track(name.clone(), record_history).await;
2948                inverse_actions = None;
2949            }
2950            Action::TrackLevel(ref name, level) => {
2951                if name == "hw:out" {
2952                    self.hw_out_level_db = level;
2953                } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2954                    track.lock().set_level(level);
2955                }
2956            }
2957            Action::TrackBalance(ref name, balance) => {
2958                if name == "hw:out" {
2959                    self.hw_out_balance = balance.clamp(-1.0, 1.0);
2960                } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2961                    track.lock().set_balance(balance);
2962                }
2963            }
2964            Action::TrackAutomationLevel(ref name, level) => {
2965                tracing::debug!(%name, level, "engine received TrackAutomationLevel");
2966                if name == "hw:out" {
2967                    self.hw_out_level_db = level;
2968                } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2969                    track.lock().set_level(level);
2970                }
2971            }
2972            Action::TrackAutomationBalance(ref name, balance) => {
2973                if name == "hw:out" {
2974                    self.hw_out_balance = balance.clamp(-1.0, 1.0);
2975                } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
2976                    track.lock().set_balance(balance);
2977                }
2978            }
2979            Action::TrackMidiCc { .. } => {
2980                if Self::box_bool(self.handle_track_midi_cc(a.clone())).await {
2981                    return;
2982                }
2983            }
2984            Action::RequestMeterSnapshot => {
2985                self.update_meter_decay_after_stop();
2986                self.notify_clients(Ok(Action::MeterSnapshot {
2987                    hw_out_db: self.latest_hw_out_meter_db.clone(),
2988                    hw_out_lufs: self.latest_hw_out_lufs,
2989                    track_meters: self.latest_track_meter_snapshot.clone(),
2990                }))
2991                .await;
2992                return;
2993            }
2994            Action::TrackMeters { .. } => {}
2995            Action::MeterSnapshot { .. } => {}
2996            Action::TrackToggleArm(..) => {
2997                if Self::box_bool(self.handle_track_toggle_arm(a.clone())).await {
2998                    return;
2999                }
3000            }
3001            Action::TrackToggleMute(ref name) => {
3002                if name == "hw:out" {
3003                    self.hw_out_muted = !self.hw_out_muted;
3004                } else if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3005                    track.lock().mute();
3006                }
3007            }
3008            Action::TrackTogglePhase(ref name) => {
3009                if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3010                    track.lock().invert_phase();
3011                }
3012            }
3013            Action::TrackToggleSolo(ref name) => {
3014                if name == "hw:out" {
3015                    return;
3016                }
3017                if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3018                    track.lock().solo();
3019                }
3020            }
3021            Action::TrackToggleMaster(ref name) => {
3022                if let Some(track) = self.state_snapshot.load_full().tracks.get(name) {
3023                    track.lock().toggle_master();
3024                }
3025            }
3026            Action::TrackToggleInputMonitor {
3027                ref track_name,
3028                lane,
3029            } => {
3030                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3031                    track.lock().toggle_input_monitor(lane);
3032                }
3033            }
3034            Action::TrackToggleDiskMonitor {
3035                ref track_name,
3036                lane,
3037            } => {
3038                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3039                    track.lock().toggle_disk_monitor(lane);
3040                }
3041            }
3042            Action::TrackToggleMidiInputMonitor {
3043                ref track_name,
3044                lane,
3045            } => {
3046                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3047                    track.lock().toggle_midi_input_monitor(lane);
3048                }
3049            }
3050            Action::TrackToggleMidiDiskMonitor {
3051                ref track_name,
3052                lane,
3053            } => {
3054                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3055                    track.lock().toggle_midi_disk_monitor(lane);
3056                }
3057            }
3058            Action::TrackSetColor {
3059                ref track_name,
3060                color,
3061            } => {
3062                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3063                    track.lock().color = color;
3064                }
3065            }
3066            Action::TrackArmMidiLearn {
3067                ref track_name,
3068                target,
3069            } => {
3070                if let Err(e) = self.track_handle_or_err(track_name) {
3071                    self.notify_clients(Err(e)).await;
3072                    return;
3073                }
3074                self.pending_midi_learn = Some((track_name.clone(), target, None));
3075            }
3076            Action::GlobalArmMidiLearn { target } => {
3077                self.pending_global_midi_learn = Some(target);
3078            }
3079            Action::SessionArmMidiLearn { ref target } => {
3080                self.pending_session_midi_learn = Some(target.clone());
3081            }
3082            Action::TrackSetMidiLearnBinding { .. } => {
3083                if Self::box_bool(self.handle_track_set_midi_learn_binding(a.clone())).await {
3084                    return;
3085                }
3086            }
3087            Action::SetGlobalMidiLearnBinding { .. } => {
3088                if Self::box_bool(self.handle_set_global_midi_learn_binding(a.clone())).await {
3089                    return;
3090                }
3091            }
3092            Action::SetSessionMidiLearnBinding { .. } => {
3093                if Self::box_bool(self.handle_set_session_midi_learn_binding(a.clone())).await {
3094                    return;
3095                }
3096            }
3097            Action::TrackSetFolder { .. } => {
3098                if Self::box_bool(self.handle_track_set_folder(a.clone())).await {
3099                    return;
3100                }
3101            }
3102            Action::TrackSetParent {
3103                ref track_name,
3104                ref parent_name,
3105            } => {
3106                self.handle_track_set_parent(track_name.as_str(), parent_name.as_deref())
3107                    .await;
3108            }
3109            Action::TrackToggleFolder { .. } => {
3110                if Self::box_bool(self.handle_track_toggle_folder(a.clone())).await {
3111                    return;
3112                }
3113            }
3114            Action::TrackSetMidiLaneChannel { .. } => {
3115                if Self::box_bool(self.handle_track_set_midi_lane_channel(a.clone())).await {
3116                    return;
3117                }
3118            }
3119            Action::TrackSetMpeZone { .. } => {
3120                if Self::box_bool(self.handle_track_set_mpe_zone(a.clone())).await {
3121                    return;
3122                }
3123            }
3124            Action::TrackSetMpePitchBendSensitivity { .. } => {
3125                if Self::box_bool(self.handle_track_set_mpe_pitch_bend_sensitivity(a.clone())).await
3126                {
3127                    return;
3128                }
3129            }
3130            Action::TrackSetFrozen { .. } => {
3131                if Self::box_bool(self.handle_track_set_frozen(a.clone())).await {
3132                    return;
3133                }
3134            }
3135            Action::TrackSetSessionSlot { .. } => {
3136                if Self::box_bool(self.handle_track_set_session_slot(a.clone())).await {
3137                    return;
3138                }
3139            }
3140            Action::TrackSetSessionSlotPlayEnabled { .. } => {
3141                if self
3142                    .handle_track_set_session_slot_play_enabled(a.clone())
3143                    .await
3144                {
3145                    return;
3146                }
3147            }
3148            Action::TrackSetSessionSlotStopEnabled { .. } => {
3149                if self
3150                    .handle_track_set_session_slot_stop_enabled(a.clone())
3151                    .await
3152                {
3153                    return;
3154                }
3155            }
3156            Action::TrackOfflineBounce { .. } => {
3157                self.handle_track_offline_bounce(action_to_process).await;
3158                return;
3159            }
3160            Action::TrackOfflineBounceCancel { .. } => {}
3161            Action::TrackOfflineBounceCancelAll => {}
3162            Action::TrackOfflineBounceCanceled { .. } => {}
3163            Action::TrackOfflineBounceProgress { .. } => {}
3164            Action::PianoKey {
3165                ref track_name,
3166                note,
3167                velocity,
3168                on,
3169            } => {
3170                if let Some(track) = self.state_snapshot.load_full().tracks.get(track_name) {
3171                    let status = if on { 0x90 } else { 0x80 };
3172                    let event = MidiEvent::new(0, vec![status, note.min(127), velocity.min(127)]);
3173                    track.lock().push_hw_midi_events(&[event]);
3174                }
3175            }
3176            Action::ModifyMidiNotes { .. }
3177            | Action::ModifyMidiControllers { .. }
3178            | Action::DeleteMidiControllers { .. }
3179            | Action::InsertMidiControllers { .. }
3180            | Action::DeleteMidiNotes { .. }
3181            | Action::InsertMidiNotes { .. } => {
3182                if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3183                    self.notify_clients(Err(e)).await;
3184                    return;
3185                }
3186            }
3187            Action::SetMidiSysExEvents { .. } => {
3188                if let Err(e) = self.apply_midi_edit_action(&action_to_process) {
3189                    self.notify_clients(Err(e)).await;
3190                    return;
3191                }
3192            }
3193            Action::TrackClearDefaultPassthrough { .. } => {
3194                if Self::box_bool(self.handle_track_clear_default_passthrough(a.clone())).await {
3195                    return;
3196                }
3197            }
3198            Action::TrackClearPlugins { .. } => {
3199                if Self::box_bool(self.handle_track_clear_plugins(a.clone())).await {
3200                    return;
3201                }
3202            }
3203            #[cfg(unix)]
3204            Action::TrackSetLv2PluginState { .. } => {
3205                if Self::box_bool(self.handle_track_set_lv2_plugin_state(a.clone())).await {
3206                    return;
3207                }
3208            }
3209            #[cfg(unix)]
3210            Action::ClipSetLv2PluginState { ref track_name, .. } => {
3211                self.notify_clients(Err(format!(
3212                    "Track '{}': clip LV2 plugin state changes are not supported",
3213                    track_name
3214                )))
3215                .await;
3216            }
3217            Action::TrackGetClapNoteNames { .. } => {
3218                if Self::box_bool(self.handle_track_get_clap_note_names(a.clone())).await {
3219                    return;
3220                }
3221            }
3222            #[cfg(unix)]
3223            Action::TrackGetLv2Midnam { .. } => {
3224                if Self::box_bool(self.handle_track_get_lv2_midnam(a.clone())).await {
3225                    return;
3226                }
3227            }
3228            Action::TrackGetPluginGraph { .. } => {
3229                if Self::box_bool(self.handle_track_get_plugin_graph(a.clone())).await {
3230                    return;
3231                }
3232            }
3233            Action::TrackPluginGraph { .. } => {}
3234            Action::TrackConnectPluginAudio { .. } => {
3235                if Self::box_bool(self.handle_track_connect_plugin_audio(a.clone())).await {
3236                    return;
3237                }
3238            }
3239            Action::TrackConnectPluginMidi { .. } => {
3240                if Self::box_bool(self.handle_track_connect_plugin_midi(a.clone())).await {
3241                    return;
3242                }
3243            }
3244            Action::TrackDisconnectPluginAudio { .. } => {
3245                if Self::box_bool(self.handle_track_disconnect_plugin_audio(a.clone())).await {
3246                    return;
3247                }
3248            }
3249            Action::TrackDisconnectPluginMidi { .. } => {
3250                if Self::box_bool(self.handle_track_disconnect_plugin_midi(a.clone())).await {
3251                    return;
3252                }
3253            }
3254            Action::TrackConnectAudio { .. } => {
3255                if Self::box_bool(self.handle_track_connect_audio(a.clone())).await {
3256                    return;
3257                }
3258            }
3259            Action::TrackDisconnectAudio { .. } => {
3260                if Self::box_bool(self.handle_track_disconnect_audio(a.clone())).await {
3261                    return;
3262                }
3263            }
3264            Action::TrackConnectMidi { .. } => {
3265                if Self::box_bool(self.handle_track_connect_midi(a.clone())).await {
3266                    return;
3267                }
3268            }
3269            Action::TrackDisconnectMidi { .. } => {
3270                if Self::box_bool(self.handle_track_disconnect_midi(a.clone())).await {
3271                    return;
3272                }
3273            }
3274            #[cfg(unix)]
3275            Action::ListLv2Plugins => {
3276                if Self::box_bool(self.handle_list_lv2_plugins(a.clone())).await {
3277                    return;
3278                }
3279            }
3280            #[cfg(unix)]
3281            Action::Lv2Plugins(_) => {}
3282            #[cfg(unix)]
3283            Action::Lv2PluginsUnavailable { .. } => {}
3284            Action::ListVst3Plugins => {
3285                if Self::box_bool(self.handle_list_vst3_plugins(a.clone())).await {
3286                    return;
3287                }
3288            }
3289            Action::Vst3Plugins(_) => {}
3290            Action::Vst3PluginsUnavailable { .. } => {}
3291            Action::ListClapPlugins => {
3292                if Self::box_bool(self.handle_list_clap_plugins(a.clone())).await {
3293                    return;
3294                }
3295            }
3296            Action::ListClapPluginsWithCapabilities => {
3297                if self
3298                    .handle_list_clap_plugins_with_capabilities(a.clone())
3299                    .await
3300                {
3301                    return;
3302                }
3303            }
3304            Action::ClapPlugins(_) => {}
3305            Action::ClapPluginsUnavailable { .. } => {}
3306            Action::TrackLoadClapPlugin {
3307                ref track_name,
3308                ref plugin_id,
3309                instance_id,
3310            } => {
3311                if self
3312                    .handle_track_load_clap_plugin(
3313                        track_name.as_str(),
3314                        plugin_id.as_str(),
3315                        instance_id,
3316                    )
3317                    .await
3318                {
3319                    return;
3320                }
3321            }
3322            Action::TrackUnloadClapPlugin {
3323                ref track_name,
3324                ref plugin_id,
3325            } => {
3326                if self
3327                    .handle_track_unload_clap_plugin(track_name.as_str(), plugin_id.as_str())
3328                    .await
3329                {
3330                    return;
3331                }
3332            }
3333            Action::TrackUnloadClapPluginInstance {
3334                ref track_name,
3335                instance_id,
3336            } => {
3337                if self
3338                    .handle_track_unload_clap_plugin_instance(track_name.as_str(), instance_id)
3339                    .await
3340                {
3341                    return;
3342                }
3343            }
3344            Action::TrackShowClapGui { .. } => {
3345                if Self::box_bool(self.handle_track_show_clap_gui(a.clone())).await {
3346                    return;
3347                }
3348            }
3349            Action::ClipShowClapGui { .. } => {
3350                if Self::box_bool(self.handle_clip_show_clap_gui(a.clone())).await {
3351                    return;
3352                }
3353            }
3354            Action::TrackLoadVst3Plugin {
3355                ref track_name,
3356                ref plugin_id,
3357                instance_id,
3358            } => {
3359                if self
3360                    .handle_track_load_vst3_plugin(
3361                        track_name.as_str(),
3362                        plugin_id.as_str(),
3363                        instance_id,
3364                    )
3365                    .await
3366                {
3367                    return;
3368                }
3369            }
3370            Action::TrackUnloadVst3Plugin {
3371                ref track_name,
3372                ref plugin_id,
3373            } => {
3374                if self
3375                    .handle_track_unload_vst3_plugin(track_name.as_str(), plugin_id.as_str())
3376                    .await
3377                {
3378                    return;
3379                }
3380            }
3381            Action::TrackUnloadVst3PluginInstance {
3382                ref track_name,
3383                instance_id,
3384            } => {
3385                if self
3386                    .handle_track_unload_vst3_plugin_instance(track_name.as_str(), instance_id)
3387                    .await
3388                {
3389                    return;
3390                }
3391            }
3392            Action::TrackShowVst3Gui { .. } => {
3393                if Self::box_bool(self.handle_track_show_vst3_gui(a.clone())).await {
3394                    return;
3395                }
3396            }
3397            Action::ClipShowVst3Gui { .. } => {
3398                if Self::box_bool(self.handle_clip_show_vst3_gui(a.clone())).await {
3399                    return;
3400                }
3401            }
3402            #[cfg(unix)]
3403            Action::TrackLoadLv2Plugin {
3404                ref track_name,
3405                ref plugin_uri,
3406                instance_id,
3407            } => {
3408                if self
3409                    .handle_track_load_lv2_plugin(
3410                        track_name.as_str(),
3411                        plugin_uri.as_str(),
3412                        instance_id,
3413                    )
3414                    .await
3415                {
3416                    return;
3417                }
3418            }
3419            #[cfg(unix)]
3420            Action::TrackUnloadLv2Plugin {
3421                ref track_name,
3422                ref plugin_uri,
3423            } => {
3424                if self
3425                    .handle_track_unload_lv2_plugin(track_name.as_str(), plugin_uri.as_str())
3426                    .await
3427                {
3428                    return;
3429                }
3430            }
3431            #[cfg(unix)]
3432            Action::TrackUnloadLv2PluginInstance {
3433                ref track_name,
3434                instance_id,
3435            } => {
3436                if self
3437                    .handle_track_unload_lv2_plugin_instance(track_name.as_str(), instance_id)
3438                    .await
3439                {
3440                    return;
3441                }
3442            }
3443            #[cfg(unix)]
3444            Action::TrackShowLv2Gui { .. } => {
3445                if Self::box_bool(self.handle_track_show_lv2_gui(a.clone())).await {
3446                    return;
3447                }
3448            }
3449            #[cfg(unix)]
3450            Action::ClipShowLv2Gui { .. } => {
3451                if Self::box_bool(self.handle_clip_show_lv2_gui(a.clone())).await {
3452                    return;
3453                }
3454            }
3455            Action::TrackSetPluginResourceDir { .. } => {
3456                if Self::box_bool(self.handle_track_set_plugin_resource_dir(a.clone())).await {
3457                    return;
3458                }
3459            }
3460            Action::TrackClapCollectResources { .. } => {
3461                if Self::box_bool(self.handle_track_clap_collect_resources(a.clone())).await {
3462                    return;
3463                }
3464            }
3465            Action::ClipSetPluginResourceDir { .. } => {
3466                if Self::box_bool(self.handle_clip_set_plugin_resource_dir(a.clone())).await {
3467                    return;
3468                }
3469            }
3470            Action::ClipClapCollectResources { .. } => {
3471                if Self::box_bool(self.handle_clip_clap_collect_resources(a.clone())).await {
3472                    return;
3473                }
3474            }
3475            Action::TrackSetClapParameter { .. } => {
3476                if Self::box_bool(self.handle_track_set_clap_parameter(a.clone())).await {
3477                    return;
3478                }
3479            }
3480            Action::ClipSetClapParameter { .. } => {
3481                if Self::box_bool(self.handle_clip_set_clap_parameter(a.clone())).await {
3482                    return;
3483                }
3484            }
3485            Action::ClipGetClapParameters { .. } => {
3486                if Self::box_bool(self.handle_clip_get_clap_parameters(a.clone())).await {
3487                    return;
3488                }
3489            }
3490            Action::ClipClapParameters { .. } => {}
3491            Action::TrackSetClapParameterAt { .. } => {
3492                if Self::box_bool(self.handle_track_set_clap_parameter_at(a.clone())).await {
3493                    return;
3494                }
3495            }
3496            Action::TrackBeginClapParameterEdit { .. } => {
3497                if Self::box_bool(self.handle_track_begin_clap_parameter_edit(a.clone())).await {
3498                    return;
3499                }
3500            }
3501            Action::TrackEndClapParameterEdit { .. } => {
3502                if Self::box_bool(self.handle_track_end_clap_parameter_edit(a.clone())).await {
3503                    return;
3504                }
3505            }
3506            Action::TrackGetClapParameters { .. } => {
3507                if Self::box_bool(self.handle_track_get_clap_parameters(a.clone())).await {
3508                    return;
3509                }
3510            }
3511            Action::TrackClapParameters { .. } => {}
3512            Action::TrackClapSnapshotState { .. } => {
3513                if Self::box_bool(self.handle_track_clap_snapshot_state(a.clone())).await {
3514                    return;
3515                }
3516            }
3517            Action::ClipClapSnapshotState { .. } => {
3518                if Self::box_bool(self.handle_clip_clap_snapshot_state(a.clone())).await {
3519                    return;
3520                }
3521            }
3522            Action::TrackClapStateSnapshot { .. } => {}
3523            Action::ClipClapStateSnapshot { .. } => {}
3524            Action::TrackClapStateDirty { .. } => {}
3525            Action::ClipClapStateDirty { .. } => {}
3526            Action::TrackClapRestoreState { .. } => {
3527                if Self::box_bool(self.handle_track_clap_restore_state(a.clone())).await {
3528                    return;
3529                }
3530            }
3531            Action::ClipClapRestoreState { .. } => {
3532                if Self::box_bool(self.handle_clip_clap_restore_state(a.clone())).await {
3533                    return;
3534                }
3535            }
3536            Action::TrackSnapshotAllClapStates { .. } => {
3537                if Self::box_bool(self.handle_track_snapshot_all_clap_states(a.clone())).await {
3538                    return;
3539                }
3540            }
3541            Action::TrackSnapshotAllClapStatesDone { .. } => {}
3542            Action::TrackGetVst3Graph { .. } => {
3543                if Self::box_bool(self.handle_track_get_vst3_graph(a.clone())).await {
3544                    return;
3545                }
3546            }
3547            Action::TrackVst3Graph { .. } => {}
3548            Action::TrackSetVst3Parameter { .. } => {
3549                if Self::box_bool(self.handle_track_set_vst3_parameter(a.clone())).await {
3550                    return;
3551                }
3552            }
3553            Action::ClipSetVst3Parameter { .. } => {
3554                if Self::box_bool(self.handle_clip_set_vst3_parameter(a.clone())).await {
3555                    return;
3556                }
3557            }
3558            Action::TrackSetPluginBypassed { .. } => {
3559                if Self::box_bool(self.handle_track_set_plugin_bypassed(a.clone())).await {
3560                    return;
3561                }
3562            }
3563            Action::TrackGetVst3Parameters { .. } => {
3564                if Self::box_bool(self.handle_track_get_vst3_parameters(a.clone())).await {
3565                    return;
3566                }
3567            }
3568            Action::TrackVst3Parameters { .. } => {}
3569            Action::ClipGetVst3Parameters { .. } => {
3570                if Self::box_bool(self.handle_clip_get_vst3_parameters(a.clone())).await {
3571                    return;
3572                }
3573            }
3574            Action::ClipVst3Parameters { .. } => {}
3575            #[cfg(unix)]
3576            Action::TrackSetLv2ControlValue { .. } => {
3577                if Self::box_bool(self.handle_track_set_lv2_control_value(a.clone())).await {
3578                    return;
3579                }
3580            }
3581            #[cfg(unix)]
3582            Action::ClipSetLv2ControlValue { .. } => {
3583                if Self::box_bool(self.handle_clip_set_lv2_control_value(a.clone())).await {
3584                    return;
3585                }
3586            }
3587            #[cfg(unix)]
3588            Action::TrackGetLv2PluginControls { .. } => {
3589                if Self::box_bool(self.handle_track_get_lv2_plugin_controls(a.clone())).await {
3590                    return;
3591                }
3592            }
3593            #[cfg(unix)]
3594            Action::ClipGetLv2PluginControls { .. } => {
3595                if Self::box_bool(self.handle_clip_get_lv2_plugin_controls(a.clone())).await {
3596                    return;
3597                }
3598            }
3599            #[cfg(unix)]
3600            Action::TrackLv2SnapshotState { .. } => {
3601                if Self::box_bool(self.handle_track_lv2_snapshot_state(a.clone())).await {
3602                    return;
3603                }
3604            }
3605            #[cfg(unix)]
3606            Action::ClipLv2SnapshotState { .. } => {
3607                if Self::box_bool(self.handle_clip_lv2_snapshot_state(a.clone())).await {
3608                    return;
3609                }
3610            }
3611            Action::TrackVst3SnapshotState { .. } => {
3612                if Self::box_bool(self.handle_track_vst3_snapshot_state(a.clone())).await {
3613                    return;
3614                }
3615            }
3616            Action::ClipVst3SnapshotState { .. } => {
3617                if Self::box_bool(self.handle_clip_vst3_snapshot_state(a.clone())).await {
3618                    return;
3619                }
3620            }
3621            Action::TrackVst3StateSnapshot { .. } => {}
3622            Action::ClipVst3StateSnapshot { .. } => {}
3623            Action::TrackVst3RestoreState { .. } => {
3624                if Self::box_bool(self.handle_track_vst3_restore_state(a.clone())).await {
3625                    return;
3626                }
3627            }
3628            Action::TrackConnectVst3Audio { .. } => {
3629                if Self::box_bool(self.handle_track_connect_vst3_audio(a.clone())).await {
3630                    return;
3631                }
3632            }
3633            Action::TrackDisconnectVst3Audio { .. } => {
3634                if Self::box_bool(self.handle_track_disconnect_vst3_audio(a.clone())).await {
3635                    return;
3636                }
3637            }
3638            Action::ClipMove { .. } => {
3639                self.handle_clip_move(a.clone()).await;
3640            }
3641            Action::AddClip { .. } => {
3642                if Self::box_bool(self.handle_add_clip(a.clone())).await {
3643                    return;
3644                }
3645            }
3646            Action::AddGroupedClip { .. } => {
3647                if Self::box_bool(self.handle_add_grouped_clip(a.clone())).await {
3648                    return;
3649                }
3650            }
3651            Action::RemoveClip {
3652                ref track_name,
3653                kind,
3654                ref clip_indices,
3655            } => {
3656                self.remove_clips_from_track(track_name, kind, clip_indices);
3657            }
3658            Action::MoveClipToUnused {
3659                ref track_name,
3660                kind,
3661                ref clip_indices,
3662            } => {
3663                self.move_clips_to_unused(track_name, kind, clip_indices);
3664            }
3665            Action::DeleteUnusedClips { ref clip_ids } => {
3666                self.delete_unused_clips(clip_ids);
3667            }
3668            Action::SetUnusedClips {
3669                ref audio,
3670                ref midi,
3671            } => {
3672                self.set_unused_clips(audio.clone(), midi.clone());
3673            }
3674            Action::RenameClip {
3675                ref track_name,
3676                kind,
3677                clip_index,
3678                ref new_name,
3679            } => {
3680                self.rename_clip_references(track_name, kind, clip_index, new_name);
3681            }
3682            Action::SetClipIdentity {
3683                ref track_name,
3684                kind,
3685                clip_index,
3686                ref new_id,
3687                ref new_name,
3688            } => {
3689                self.set_clip_identity(track_name, kind, clip_index, new_id, new_name);
3690            }
3691            Action::SetClipSourceName {
3692                ref track_name,
3693                kind,
3694                clip_index,
3695                ref name,
3696            } => {
3697                self.set_clip_source_name(track_name, clip_index, kind, name.clone());
3698            }
3699            Action::SetClipFade { .. } => {
3700                if Self::box_bool(self.handle_set_clip_fade(a.clone())).await {
3701                    return;
3702                }
3703            }
3704            Action::SetClipBounds {
3705                ref track_name,
3706                clip_index,
3707                kind,
3708                start,
3709                length,
3710                offset,
3711            } => {
3712                self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3713            }
3714            Action::SyncClipBounds {
3715                ref track_name,
3716                clip_index,
3717                kind,
3718                start,
3719                length,
3720                offset,
3721            } => {
3722                self.set_clip_bounds(track_name, clip_index, kind, start, length, offset);
3723            }
3724            Action::SetClipMuted {
3725                ref track_name,
3726                clip_index,
3727                kind,
3728                muted,
3729            } => {
3730                self.set_clip_muted(track_name, clip_index, kind, muted);
3731            }
3732            Action::SetClipPluginGraphJson {
3733                ref track_name,
3734                clip_index,
3735                ref plugin_graph_json,
3736            } => {
3737                self.set_clip_plugin_graph_json(track_name, clip_index, plugin_graph_json.clone());
3738            }
3739            Action::SetClipPitchCorrection { .. } => {
3740                if Self::box_bool(self.handle_set_clip_pitch_correction(a.clone())).await {
3741                    return;
3742                }
3743            }
3744            Action::Connect {
3745                ref from_track,
3746                from_port,
3747                ref to_track,
3748                to_port,
3749                kind,
3750            } => {
3751                self.handle_connect(
3752                    from_track.as_str(),
3753                    from_port,
3754                    to_track.as_str(),
3755                    to_port,
3756                    kind,
3757                )
3758                .await;
3759            }
3760            Action::Disconnect { .. } => {
3761                self.handle_disconnect(a.clone()).await;
3762            }
3763            Action::OpenAudioDevice { .. } => {
3764                let (done, updated) = self.handle_open_audio_device(a.clone()).await;
3765                if done {
3766                    return;
3767                }
3768                if let Some(action) = updated {
3769                    action_to_process = action;
3770                }
3771            }
3772            Action::JackAddAudioInputPort => {
3773                if Self::box_bool(self.handle_jack_add_audio_input_port(a.clone())).await {
3774                    return;
3775                }
3776            }
3777            Action::JackRemoveAudioInputPort(_removed_port) => {
3778                if self
3779                    .handle_jack_remove_audio_input_port(_removed_port, a.clone())
3780                    .await
3781                {
3782                    return;
3783                }
3784            }
3785            Action::JackAddAudioOutputPort => {
3786                if Self::box_bool(self.handle_jack_add_audio_output_port(a.clone())).await {
3787                    return;
3788                }
3789            }
3790            Action::JackRemoveAudioOutputPort(_removed_port) => {
3791                if self
3792                    .handle_jack_remove_audio_output_port(_removed_port, a.clone())
3793                    .await
3794                {
3795                    return;
3796                }
3797            }
3798            Action::JackGetGraph => {
3799                #[cfg(unix)]
3800                {
3801                    match self
3802                        .jack_runtime
3803                        .as_ref()
3804                        .ok_or(
3805                            "JACK runtime is not active; open the JACK backend first".to_string(),
3806                        )
3807                        .and_then(|jack| jack.graph_info())
3808                    {
3809                        Ok(graph) => self.notify_clients(Ok(Action::JackGraph(graph))).await,
3810                        Err(e) => self.notify_clients(Err(e)).await,
3811                    }
3812                }
3813                #[cfg(not(unix))]
3814                {
3815                    self.notify_clients(Err(
3816                        "JACK backend is not available on this platform build".to_string(),
3817                    ))
3818                    .await;
3819                }
3820                return;
3821            }
3822            Action::JackConnect {
3823                ref source,
3824                ref destination,
3825            } => {
3826                #[cfg(unix)]
3827                {
3828                    match self
3829                        .jack_runtime
3830                        .as_ref()
3831                        .ok_or(
3832                            "JACK runtime is not active; open the JACK backend first".to_string(),
3833                        )
3834                        .and_then(|jack| jack.connect_ports_by_name(source, destination))
3835                        .and_then(|_| {
3836                            self.jack_runtime
3837                                .as_ref()
3838                                .expect("JACK runtime was checked")
3839                                .graph_info()
3840                        }) {
3841                        Ok(graph) => {
3842                            self.notify_clients(Ok(a.clone())).await;
3843                            self.notify_clients(Ok(Action::JackGraph(graph))).await;
3844                        }
3845                        Err(e) => self.notify_clients(Err(e)).await,
3846                    }
3847                }
3848                #[cfg(not(unix))]
3849                {
3850                    let _ = (source, destination);
3851                    self.notify_clients(Err(
3852                        "JACK backend is not available on this platform build".to_string(),
3853                    ))
3854                    .await;
3855                }
3856                return;
3857            }
3858            Action::JackDisconnect {
3859                ref source,
3860                ref destination,
3861            } => {
3862                #[cfg(unix)]
3863                {
3864                    match self
3865                        .jack_runtime
3866                        .as_ref()
3867                        .ok_or(
3868                            "JACK runtime is not active; open the JACK backend first".to_string(),
3869                        )
3870                        .and_then(|jack| jack.disconnect_ports_by_name(source, destination))
3871                        .and_then(|_| {
3872                            self.jack_runtime
3873                                .as_ref()
3874                                .expect("JACK runtime was checked")
3875                                .graph_info()
3876                        }) {
3877                        Ok(graph) => {
3878                            self.notify_clients(Ok(a.clone())).await;
3879                            self.notify_clients(Ok(Action::JackGraph(graph))).await;
3880                        }
3881                        Err(e) => self.notify_clients(Err(e)).await,
3882                    }
3883                }
3884                #[cfg(not(unix))]
3885                {
3886                    let _ = (source, destination);
3887                    self.notify_clients(Err(
3888                        "JACK backend is not available on this platform build".to_string(),
3889                    ))
3890                    .await;
3891                }
3892                return;
3893            }
3894            Action::OpenMidiInputDevice(ref device) => {
3895                if let Some(worker) = &self.hw_worker {
3896                    if let Err(e) = worker
3897                        .tx
3898                        .send(Message::HWOpenMidiInputDevice(device.clone()))
3899                        .await
3900                    {
3901                        self.notify_clients(Err(format!("Failed to send MIDI input open: {e}")))
3902                            .await;
3903                    }
3904                    return;
3905                }
3906                let Some(midi_hub) = self.midi_hub.as_mut() else {
3907                    self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3908                        .await;
3909                    return;
3910                };
3911                if let Err(e) = midi_hub.open_input(device) {
3912                    self.notify_clients(Err(e)).await;
3913                    return;
3914                }
3915            }
3916            Action::OpenMidiOutputDevice(ref device) => {
3917                if let Some(worker) = &self.hw_worker {
3918                    if let Err(e) = worker
3919                        .tx
3920                        .send(Message::HWOpenMidiOutputDevice(device.clone()))
3921                        .await
3922                    {
3923                        self.notify_clients(Err(format!("Failed to send MIDI output open: {e}")))
3924                            .await;
3925                    }
3926                    return;
3927                }
3928                let Some(midi_hub) = self.midi_hub.as_mut() else {
3929                    self.notify_clients(Err("Hardware MIDI hub is not available".to_string()))
3930                        .await;
3931                    return;
3932                };
3933                if let Err(e) = midi_hub.open_output(device) {
3934                    self.notify_clients(Err(e)).await;
3935                    return;
3936                }
3937            }
3938            Action::RequestSessionDiagnostics => {
3939                self.handle_request_session_diagnostics().await;
3940            }
3941            Action::RequestMidiLearnMappingsReport => {
3942                self.handle_request_midi_learn_mappings_report().await;
3943            }
3944            Action::ClearAllMidiLearnBindings => {
3945                if Self::box_bool(self.handle_clear_all_midi_learn_bindings(a.clone())).await {
3946                    return;
3947                }
3948            }
3949            #[cfg(unix)]
3950            Action::TrackLv2PluginControls { .. } => {}
3951            #[cfg(unix)]
3952            Action::ClipLv2PluginControls { .. } => {}
3953            #[cfg(unix)]
3954            Action::TrackLv2StateSnapshot { .. } => {}
3955            #[cfg(unix)]
3956            Action::ClipLv2StateSnapshot { .. } => {}
3957            #[cfg(unix)]
3958            Action::TrackLv2Midnam { .. } => {}
3959            Action::TrackClapNoteNames { .. } => {}
3960            Action::SessionDiagnosticsReport { .. } => {}
3961            Action::MidiLearnMappingsReport { .. } => {}
3962            Action::HWInfo { .. } => {}
3963            Action::HistoryState { .. } => {}
3964            Action::Undo => {}
3965            Action::Redo => {}
3966            Action::ApplyGroupedActions(_) => {}
3967            _ => {}
3968        }
3969
3970        if let Some(inverse) = inverse_actions {
3971            if let Some(group) = self.history_group.as_mut() {
3972                group.forward_actions.push(action_to_process.clone());
3973                group.inverse_actions.splice(0..0, inverse);
3974            } else {
3975                self.history.record(UndoEntry {
3976                    forward_actions: vec![action_to_process.clone()],
3977                    inverse_actions: inverse,
3978                });
3979            }
3980        }
3981
3982        self.notify_clients(Ok(action_to_process)).await;
3983    }
3984    pub async fn work(&mut self) {
3985        // Wake immediately when a fixed-thread node worker pushes a result;
3986        // a slower periodic tick still force-completes timed-out nodes.
3987        let mut timeout_tick = tokio::time::interval(Duration::from_millis(10));
3988        timeout_tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
3989        loop {
3990            let message = tokio::select! {
3991                message = self.rx.recv() => {
3992                    let Some(message) = message else {
3993                        break;
3994                    };
3995                    tracing::debug!(?message, "engine work loop received message");
3996                    Some(message)
3997                }
3998                _ = self.node_result_notify.notified() => {
3999                    tracing::trace!("engine work loop woken by node result");
4000                    None
4001                }
4002                _ = timeout_tick.tick() => {
4003                    tracing::trace!("engine work loop timeout tick");
4004                    None
4005                }
4006            };
4007            self.poll_node_worker_results().await;
4008            self.poll_jack_hw_finished().await;
4009            self.poll_stopped_plugin_parameter_echoes().await;
4010            if !self.playing && !self.transport_running {
4011                self.publish_clap_state_dirty().await;
4012            }
4013            self.on_executor_tick().await;
4014            let Some(message) = message else {
4015                continue;
4016            };
4017            match message {
4018                Message::Ready(id) => {
4019                    // A bounce worker's terminal Ready cleans up its job even
4020                    // when the OfflineBounceFinished payload was an Err
4021                    // without a track name.
4022                    if let Some(track_name) = self.bounce_worker_tracks.remove(&id) {
4023                        self.offline_bounce_jobs.remove(&track_name);
4024                    }
4025                    self.push_ready_worker(id);
4026                    if self.dispatch_node_jobs(Vec::new()).await {
4027                        self.on_all_tracks_finished().await;
4028                    }
4029                    self.drain_pending_requests_if_idle().await;
4030                }
4031                Message::NodeDone {
4032                    worker_id,
4033                    epoch,
4034                    node,
4035                    output_linear,
4036                    parameter_updates,
4037                    latency_changed,
4038                } => {
4039                    self.on_node_done(
4040                        worker_id,
4041                        epoch,
4042                        node,
4043                        output_linear,
4044                        parameter_updates,
4045                        latency_changed,
4046                    )
4047                    .await;
4048                }
4049                Message::Channel(s) => {
4050                    self.clients.push(s);
4051                }
4052                Message::Response(result) => {
4053                    self.notify_clients(result).await;
4054                }
4055
4056                Message::Request(a) => {
4057                    self.dispatch_request(a).await;
4058                    // Any request may have changed the topology; the builder
4059                    // coalesces bursts into a single plan rebuild.
4060                    self.plan_builder.mark_dirty();
4061                }
4062                Message::OscRequest { action, reply_to } => {
4063                    tracing::debug!(%reply_to, ?action, "engine received OscRequest");
4064                    self.osc_reply_target = Some(reply_to);
4065                    self.dispatch_request(action).await;
4066                    self.osc_reply_target = None;
4067                    self.plan_builder.mark_dirty();
4068                }
4069                Message::OfflineBounceFinished { result } => {
4070                    if let Ok(Action::TrackOfflineBounce { track_name, .. })
4071                    | Ok(Action::TrackOfflineBounceCanceled { track_name, .. }) = &result
4072                    {
4073                        self.offline_bounce_jobs.remove(track_name);
4074                    }
4075                    self.notify_clients(result).await;
4076                    self.drain_pending_requests_if_idle().await;
4077                }
4078                Message::HWFinished => {
4079                    if !self.awaiting_hwfinished {
4080                        tracing::debug!(
4081                            playing = self.playing,
4082                            transport_running = self.transport_running,
4083                            transport_sample = self.transport_sample,
4084                            session_transport_sample = self.session_transport_sample,
4085                            cycle_samples = self.current_cycle_samples(),
4086                            "HWFinished ignored because engine was not awaiting it"
4087                        );
4088                        continue;
4089                    }
4090                    tracing::debug!(
4091                        playing = self.playing,
4092                        transport_running = self.transport_running,
4093                        transport_sample = self.transport_sample,
4094                        session_transport_sample = self.session_transport_sample,
4095                        cycle_samples = self.current_cycle_samples(),
4096                        "HWFinished handling"
4097                    );
4098                    self.handling_hwfinished = true;
4099                    self.awaiting_hwfinished = false;
4100                    #[cfg(unix)]
4101                    {
4102                        if let Some(jack) = self.jack_runtime.as_mut() {
4103                            if !self.pending_hw_midi_out_events.is_empty() {
4104                                let out_events =
4105                                    std::mem::take(&mut self.pending_hw_midi_out_events);
4106                                jack.write_events(&out_events);
4107                            }
4108                            let mut in_events = vec![];
4109                            jack.read_events_into(&mut in_events);
4110                            if !in_events.is_empty() {
4111                                self.pending_hw_midi_events.extend(in_events);
4112                            }
4113                            let dropped = jack.take_midi_events_dropped();
4114                            if dropped > 0 {
4115                                tracing::warn!(
4116                                    "JACK MIDI ring full; {dropped} events dropped since last cycle"
4117                                );
4118                            }
4119                        }
4120                    }
4121                    #[cfg(unix)]
4122                    if self.jack_runtime.is_some() {
4123                        self.sync_from_jack_transport().await;
4124                    }
4125                    while let Some(a) = self.pending_requests.pop_front() {
4126                        self.handle_request(a).await;
4127                    }
4128                    self.apply_mute_solo_policy();
4129                    self.append_recorded_cycle();
4130                    self.flush_completed_recordings().await;
4131                    let hw_in_routes = self.midi_hw_in_routes.clone();
4132                    let pending_hw_in_by_device = self.pending_hw_midi_events_by_device.clone();
4133                    let mut reconfigured_tracks = Vec::new();
4134                    let state = self.state_snapshot.load_full();
4135                    for (track_name, track) in state.tracks.iter() {
4136                        let mut track_lock = track.lock();
4137                        if self.jack_runtime_is_some() {
4138                            if !self.pending_hw_midi_events.is_empty() {
4139                                track_lock.push_hw_midi_events(&self.pending_hw_midi_events);
4140                            }
4141                        } else {
4142                            for route in hw_in_routes.iter().filter(|r| &r.to_track == track_name) {
4143                                if let Some(events) = pending_hw_in_by_device.get(&route.device) {
4144                                    track_lock.push_hw_midi_events_to_port(route.to_port, events);
4145                                }
4146                            }
4147                        }
4148                        if track_lock.setup() {
4149                            reconfigured_tracks.push(track_name.clone());
4150                        }
4151                    }
4152                    self.publish_track_meters();
4153                    self.publish_session_runtime_reports().await;
4154                    self.publish_clap_state_dirty().await;
4155                    for track_name in reconfigured_tracks {
4156                        let track = state.tracks.get(&track_name).cloned();
4157                        if let Some(track) = track {
4158                            let (plugins, connections, connectable_connections) = {
4159                                let track_lock = track.lock();
4160                                (
4161                                    track_lock.plugin_graph_plugins(false),
4162                                    track_lock.plugin_graph_connections(),
4163                                    track_lock.connectable_connections(),
4164                                )
4165                            };
4166                            self.notify_clients(Ok(Action::TrackPluginGraph {
4167                                track_name: track_name.clone(),
4168                                plugins,
4169                                connections,
4170                                connectable_connections,
4171                            }))
4172                            .await;
4173                        }
4174                    }
4175                    self.pending_hw_midi_events.clear();
4176                    self.pending_hw_midi_events_by_device.clear();
4177                    let cycle_samples = self.current_cycle_samples();
4178                    if self.transport_running {
4179                        if self.transport_panic_flush_pending {
4180                            self.transport_panic_flush_pending = false;
4181                        } else if self.transport_restart_pending {
4182                            self.transport_restart_pending = false;
4183                        } else {
4184                            let before = self.transport_sample;
4185                            let next = self.transport_sample.saturating_add(cycle_samples);
4186                            let normalized = self.normalize_transport_sample(next);
4187                            let wrapped = normalized != next;
4188                            self.transport_sample = normalized;
4189                            tracing::debug!(
4190                                before,
4191                                delta = cycle_samples,
4192                                next,
4193                                normalized,
4194                                wrapped,
4195                                "transport advanced after HWFinished"
4196                            );
4197                            self.publish_transport_snapshot();
4198                            if wrapped {
4199                                if self.notified_loop_wrap_sample == Some(self.transport_sample) {
4200                                    self.notified_loop_wrap_sample = None;
4201                                } else {
4202                                    self.notify_clients(Ok(Action::TransportPosition(
4203                                        self.transport_sample,
4204                                    )))
4205                                    .await;
4206                                }
4207                            }
4208                        }
4209                    } else {
4210                        tracing::debug!(
4211                            playing = self.playing,
4212                            cycle_samples,
4213                            "transport not advanced because transport_running is false"
4214                        );
4215                    }
4216                    if self.session_clip_playback_enabled && self.playing {
4217                        let before = self.session_transport_sample;
4218                        self.session_transport_sample =
4219                            self.session_transport_sample.saturating_add(cycle_samples);
4220                        tracing::debug!(
4221                            before,
4222                            delta = cycle_samples,
4223                            after = self.session_transport_sample,
4224                            "session transport advanced after HWFinished"
4225                        );
4226                    }
4227                    {
4228                        let echoes = self.apply_modulators(self.active_transport_sample());
4229                        for action in echoes {
4230                            self.notify_clients(Ok(action)).await;
4231                        }
4232                    }
4233                    self.apply_mixosc_automation(self.active_transport_sample());
4234                    let cycle_started = self.start_plan_cycle().await;
4235                    // If a plan cycle is still running, its completion will request the
4236                    // hardware cycle. Requesting here would replay stale arena buffers.
4237                    if self.hw_worker.is_some()
4238                        && !cycle_started
4239                        && (self.playing || self.audio_preview.is_some())
4240                        && self.executor.cycle_complete()
4241                    {
4242                        self.request_hw_cycle().await;
4243                    }
4244                    tracing::debug!(
4245                        cycle_started,
4246                        hw_worker = self.hw_worker.is_some(),
4247                        awaiting_hwfinished = self.awaiting_hwfinished,
4248                        executor_complete = self.executor.cycle_complete(),
4249                        "HWFinished rearm decision"
4250                    );
4251                    #[cfg(unix)]
4252                    {
4253                        if self.jack_runtime.is_some() {
4254                            self.awaiting_hwfinished = true;
4255                        }
4256                    }
4257                    self.handling_hwfinished = false;
4258                }
4259                Message::HWMidiEvents(events) => {
4260                    for hw_event in events {
4261                        let thru_targets: Vec<String> = self
4262                            .midi_hw_thru_routes
4263                            .iter()
4264                            .filter(|route| route.from_device == hw_event.device)
4265                            .map(|route| route.to_device.clone())
4266                            .collect();
4267                        for device in thru_targets {
4268                            self.pending_hw_midi_out_events_by_device.push(HwMidiEvent {
4269                                device,
4270                                event: hw_event.event.clone(),
4271                            });
4272                        }
4273                        if hw_event.event.data.len() >= 3 {
4274                            let status = hw_event.event.data[0];
4275                            if status & 0xF0 == 0xB0 {
4276                                let channel = status & 0x0F;
4277                                let cc = hw_event.event.data[1];
4278                                let value = hw_event.event.data[2];
4279                                self.handle_incoming_hw_cc(&hw_event.device, channel, cc, value)
4280                                    .await;
4281                            }
4282                            if self.step_recording_enabled && status & 0xF0 == 0x90 {
4283                                let channel = status & 0x0F;
4284                                let pitch = hw_event.event.data[1];
4285                                let velocity = hw_event.event.data[2];
4286                                if velocity > 0 {
4287                                    self.notify_clients(Ok(Action::StepRecordMidiNote {
4288                                        device: hw_event.device.clone(),
4289                                        channel,
4290                                        pitch,
4291                                        velocity,
4292                                    }))
4293                                    .await;
4294                                }
4295                            }
4296                        }
4297                        self.pending_hw_midi_events_by_device
4298                            .entry(hw_event.device)
4299                            .or_default()
4300                            .push(hw_event.event);
4301                    }
4302                }
4303                Message::StartAudioPreview {
4304                    samples,
4305                    channels,
4306                    start_sample,
4307                } => {
4308                    self.audio_preview = Some(AudioPreviewPlayback {
4309                        samples,
4310                        channels: channels.max(1),
4311                        cursor: start_sample,
4312                    });
4313                    self.meter_decay_after_stop = None;
4314                    self.set_hw_playing(true).await;
4315                    if !self.awaiting_hwfinished && self.executor.cycle_complete() {
4316                        self.request_hw_cycle().await;
4317                    }
4318                }
4319                Message::StopAudioPreview => {
4320                    self.audio_preview = None;
4321                }
4322                _ => {}
4323            }
4324        }
4325    }
4326
4327    pub(crate) fn collect_hw_midi_output_events(&self) -> Vec<MidiEvent> {
4328        let mut events = vec![];
4329        for track in self.state_snapshot.load_full().tracks.values() {
4330            events.extend(
4331                track
4332                    .lock()
4333                    .take_hw_midi_out_events()
4334                    .into_iter()
4335                    .map(|evt| evt.event),
4336            );
4337        }
4338        events.sort_by_key(|a| a.frame);
4339        events
4340    }
4341
4342    pub(crate) fn collect_hw_midi_output_events_by_device(&mut self) -> Vec<HwMidiEvent> {
4343        let mut events = Vec::<HwMidiEvent>::new();
4344        let routes = self.midi_hw_out_routes.clone();
4345        let mut events_by_track = HashMap::<String, Vec<crate::track::HwMidiOutEvent>>::new();
4346        {
4347            let state = self.state_snapshot.load_full();
4348            for route in &routes {
4349                if events_by_track.contains_key(&route.from_track) {
4350                    continue;
4351                }
4352                let Some(track) = state.tracks.get(&route.from_track) else {
4353                    continue;
4354                };
4355                events_by_track.insert(
4356                    route.from_track.clone(),
4357                    track.lock().take_hw_midi_out_events(),
4358                );
4359            }
4360        }
4361
4362        for route in routes {
4363            let Some(track_events) = events_by_track.get(&route.from_track) else {
4364                continue;
4365            };
4366            for hw_event in track_events
4367                .iter()
4368                .filter(|evt| evt.port == route.from_port)
4369            {
4370                self.update_active_hw_notes_for_track(
4371                    &route.from_track,
4372                    &route.device,
4373                    &hw_event.event.data,
4374                );
4375                events.push(HwMidiEvent {
4376                    device: route.device.clone(),
4377                    event: hw_event.event.clone(),
4378                });
4379            }
4380        }
4381        events.sort_by(|a, b| {
4382            a.event
4383                .frame
4384                .cmp(&b.event.frame)
4385                .then_with(|| a.device.cmp(&b.device))
4386        });
4387        events
4388    }
4389}