Skip to main content

maolan_engine/
message.rs

1use crate::clap::{ClapParameterInfo, ClapPluginInfo};
2pub use crate::connectable::{ConnectableConnection, ConnectableRef};
3#[cfg(all(unix, not(target_os = "macos")))]
4use crate::lv2::Lv2PluginInfo;
5use crate::midi::io::MidiEvent;
6use crate::state::TrackHandle;
7use crate::vst3::Vst3PluginInfo;
8use crate::{kind::Kind, modulator::Modulator};
9use std::net::SocketAddr;
10use std::sync::{Arc, atomic::AtomicBool};
11use tokio::sync::mpsc::Sender;
12
13#[derive(Clone, Debug, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
14pub struct TrackColor {
15    pub r: f32,
16    pub g: f32,
17    pub b: f32,
18    pub a: f32,
19}
20
21#[derive(Clone, Debug)]
22pub struct MidiNoteData {
23    pub start_sample: usize,
24    pub length_samples: usize,
25    pub pitch: u8,
26    pub velocity: u8,
27    pub channel: u8,
28}
29
30#[derive(Clone, Debug)]
31pub struct MidiControllerData {
32    pub sample: usize,
33    pub controller: u8,
34    pub value: u8,
35    pub channel: u8,
36}
37
38#[derive(Debug, Clone)]
39pub struct MidiRawEventData {
40    pub sample: usize,
41    pub data: Vec<u8>,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45pub struct JackPortInfo {
46    pub name: String,
47    pub kind: Kind,
48    pub is_input: bool,
49    pub is_output: bool,
50    pub is_physical: bool,
51    pub is_maolan: bool,
52}
53
54#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
55pub struct JackConnectionInfo {
56    pub source: String,
57    pub destination: String,
58}
59
60#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
61pub struct JackGraphInfo {
62    pub ports: Vec<JackPortInfo>,
63    pub connections: Vec<JackConnectionInfo>,
64}
65
66#[derive(Clone, Debug)]
67pub struct HwMidiEvent {
68    pub device: String,
69    pub event: MidiEvent,
70}
71
72#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
73pub struct OfflineAutomationPoint {
74    pub sample: usize,
75    pub value: f32,
76}
77
78#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
79pub enum OfflineAutomationTarget {
80    Volume,
81    Balance,
82    MidiCc {
83        channel: u8,
84        cc: u8,
85    },
86    #[cfg(all(unix, not(target_os = "macos")))]
87    Lv2Parameter {
88        instance_id: usize,
89        index: u32,
90        min: f32,
91        max: f32,
92    },
93    Vst3Parameter {
94        instance_id: usize,
95        param_id: u32,
96    },
97    ClapParameter {
98        instance_id: usize,
99        param_id: u32,
100        min: f64,
101        max: f64,
102    },
103}
104
105#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
106pub struct OfflineAutomationLane {
107    pub target: OfflineAutomationTarget,
108    #[serde(default)]
109    pub visible: bool,
110    pub points: Vec<OfflineAutomationPoint>,
111}
112
113#[derive(Clone, Debug)]
114pub struct OfflineBounceWork {
115    pub state: Arc<crate::state::StateSnapshot>,
116    pub track_name: String,
117    pub output_path: String,
118    pub start_sample: usize,
119    pub length_samples: usize,
120    pub tempo_bpm: f64,
121    pub tsig_num: u16,
122    pub tsig_denom: u16,
123    pub automation_lanes: Vec<OfflineAutomationLane>,
124    pub cancel: Arc<AtomicBool>,
125    pub apply_fader: bool,
126}
127
128#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
129pub struct PitchCorrectionPointData {
130    pub start_sample: usize,
131    pub length_samples: usize,
132    pub detected_midi_pitch: f32,
133    pub target_midi_pitch: f32,
134    pub clarity: f32,
135}
136
137#[derive(Clone, Debug, PartialEq)]
138pub struct TempoPoint {
139    pub sample: usize,
140    pub bpm: f64,
141}
142
143#[derive(Clone, Debug, PartialEq)]
144pub struct TimeSignaturePoint {
145    pub sample: usize,
146    pub numerator: u16,
147    pub denominator: u16,
148}
149
150#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
151pub struct AudioClipData {
152    #[serde(default)]
153    pub id: String,
154    pub name: String,
155    pub start: usize,
156    pub length: usize,
157    pub offset: usize,
158    pub input_channel: usize,
159    pub muted: bool,
160    pub peaks_file: Option<String>,
161    pub fade_enabled: bool,
162    pub fade_in_samples: usize,
163    pub fade_out_samples: usize,
164    pub preview_name: Option<String>,
165    pub source_name: Option<String>,
166    pub source_offset: Option<usize>,
167    pub source_length: Option<usize>,
168    pub pitch_correction_points: Vec<PitchCorrectionPointData>,
169    pub pitch_correction_frame_likeness: Option<f32>,
170    pub pitch_correction_inertia_ms: Option<u16>,
171    pub pitch_correction_formant_compensation: Option<bool>,
172    pub plugin_graph_json: Option<serde_json::Value>,
173    pub grouped_clips: Vec<AudioClipData>,
174}
175
176#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
177pub struct MidiClipData {
178    #[serde(default)]
179    pub id: String,
180    pub name: String,
181    pub start: usize,
182    pub length: usize,
183    pub offset: usize,
184    pub input_channel: usize,
185    pub muted: bool,
186    pub grouped_clips: Vec<MidiClipData>,
187}
188
189/// Generates a new unique clip identifier.
190pub fn generate_clip_id() -> String {
191    uuid::Uuid::new_v4().to_string()
192}
193
194#[derive(Clone, Debug)]
195pub struct ClipMoveFrom {
196    pub track_name: String,
197    pub clip_index: usize,
198}
199
200#[derive(Clone, Debug)]
201pub struct ClipMoveTo {
202    pub track_name: String,
203    pub sample_offset: usize,
204    pub input_channel: usize,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Hash)]
208pub enum PluginGraphNode {
209    TrackInput,
210    TrackOutput,
211    ClapPluginInstance(usize),
212    Vst3PluginInstance(usize),
213    #[cfg(all(unix, not(target_os = "macos")))]
214    Lv2PluginInstance(usize),
215}
216
217#[derive(Clone, Debug, PartialEq)]
218pub struct PluginGraphPlugin {
219    pub node: PluginGraphNode,
220    pub instance_id: usize,
221    pub format: String,
222    pub uri: String,
223    pub plugin_id: String,
224    pub name: String,
225    pub main_audio_inputs: usize,
226    pub main_audio_outputs: usize,
227    pub audio_inputs: usize,
228    pub audio_outputs: usize,
229    pub midi_inputs: usize,
230    pub midi_outputs: usize,
231    pub state: Option<serde_json::Value>,
232    pub bypassed: bool,
233}
234
235#[cfg(unix)]
236#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
237pub struct Lv2StatePortValue {
238    pub index: u32,
239    pub value: f32,
240}
241
242#[cfg(unix)]
243#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
244pub struct Lv2StateProperty {
245    pub key_uri: String,
246    pub type_uri: String,
247    pub flags: u32,
248    pub value: Vec<u8>,
249}
250
251#[cfg(unix)]
252#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
253pub struct Lv2PluginState {
254    pub port_values: Vec<Lv2StatePortValue>,
255    pub properties: Vec<Lv2StateProperty>,
256}
257
258#[cfg(all(unix, not(target_os = "macos")))]
259#[derive(Clone, Debug, PartialEq)]
260pub struct Lv2ControlPortInfo {
261    pub index: u32,
262    pub name: String,
263    pub min: f32,
264    pub max: f32,
265    pub value: f32,
266}
267
268#[derive(Clone, Debug, PartialEq, Eq)]
269pub struct PluginGraphConnection {
270    pub from_node: PluginGraphNode,
271    pub from_port: usize,
272    pub to_node: PluginGraphNode,
273    pub to_port: usize,
274    pub kind: Kind,
275}
276
277pub type PluginGraphSnapshot = (Vec<PluginGraphPlugin>, Vec<PluginGraphConnection>);
278
279#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
280pub enum PluginKind {
281    Clap,
282    Vst3,
283    #[cfg(all(unix, not(target_os = "macos")))]
284    Lv2,
285}
286
287#[derive(Clone, Debug)]
288pub enum ProcessTask {
289    Track(TrackHandle),
290    FolderInput(TrackHandle),
291    FolderOutput(TrackHandle),
292    Plugin {
293        track: TrackHandle,
294        kind: PluginKind,
295        index: usize,
296    },
297}
298
299impl PartialEq for ProcessTask {
300    fn eq(&self, other: &Self) -> bool {
301        use ProcessTask::*;
302        match (self, other) {
303            (Track(a), Track(b))
304            | (FolderInput(a), FolderInput(b))
305            | (FolderOutput(a), FolderOutput(b)) => Arc::ptr_eq(a, b),
306            (
307                Plugin {
308                    track: a,
309                    kind: ka,
310                    index: ia,
311                },
312                Plugin {
313                    track: b,
314                    kind: kb,
315                    index: ib,
316                },
317            ) => Arc::ptr_eq(a, b) && ka == kb && ia == ib,
318            _ => false,
319        }
320    }
321}
322
323impl Eq for ProcessTask {}
324
325impl std::hash::Hash for ProcessTask {
326    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
327        use ProcessTask::*;
328        match self {
329            Track(t) | FolderInput(t) | FolderOutput(t) => {
330                Arc::as_ptr(t).hash(state);
331            }
332            Plugin { track, kind, index } => {
333                Arc::as_ptr(track).hash(state);
334                kind.hash(state);
335                index.hash(state);
336            }
337        }
338    }
339}
340
341#[derive(Clone, Debug, PartialEq, Eq, Hash)]
342pub enum Vst3GraphNode {
343    TrackInput,
344    TrackOutput,
345    PluginInstance(usize),
346}
347
348#[derive(Clone, Debug)]
349pub struct Vst3GraphPlugin {
350    pub instance_id: usize,
351    pub name: String,
352    pub path: String,
353    pub audio_inputs: usize,
354    pub audio_outputs: usize,
355    pub parameters: Vec<crate::vst3::port::ParameterInfo>,
356}
357
358#[derive(Clone, Debug, PartialEq, Eq)]
359pub struct Vst3GraphConnection {
360    pub from_node: Vst3GraphNode,
361    pub from_port: usize,
362    pub to_node: Vst3GraphNode,
363    pub to_port: usize,
364    pub kind: Kind,
365}
366
367#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
368pub struct MidiLearnBinding {
369    pub device: Option<String>,
370    pub channel: u8,
371    pub cc: u8,
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
375pub enum TrackMidiLearnTarget {
376    Volume,
377    Balance,
378    Mute,
379    Solo,
380    Arm,
381    InputMonitor,
382    DiskMonitor,
383}
384
385#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
386pub enum GlobalMidiLearnTarget {
387    PlayPause,
388    Stop,
389    RecordToggle,
390}
391
392#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
393pub enum SessionMidiLearnTarget {
394    Slot {
395        track_name: String,
396        scene_index: usize,
397    },
398    Scene(usize),
399    StopTrack(String),
400    StopAll,
401}
402
403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
404pub enum SessionSlotState {
405    Stopped,
406    Queued,
407    Playing,
408    Stopping,
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
412pub enum LaunchQuantization {
413    None,
414    Beat,
415    Bar,
416    TwoBars,
417    FourBars,
418    EightBars,
419    Eighth,
420    Sixteenth,
421    ThirtySecond,
422    SixtyFourth,
423}
424
425#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
426pub enum TrackAutomationMode {
427    #[default]
428    Read,
429    Touch,
430    Latch,
431    Write,
432}
433
434impl TrackAutomationMode {
435    pub fn as_u8(self) -> u8 {
436        match self {
437            Self::Read => 0,
438            Self::Touch => 1,
439            Self::Latch => 2,
440            Self::Write => 3,
441        }
442    }
443
444    pub fn from_u8(value: u8) -> Self {
445        match value {
446            1 => Self::Touch,
447            2 => Self::Latch,
448            3 => Self::Write,
449            _ => Self::Read,
450        }
451    }
452}
453
454#[derive(Clone, Debug)]
455pub enum SessionAction {
456    LaunchClip {
457        track_name: String,
458        scene_index: usize,
459        clip_id: String,
460        launch_quantization: LaunchQuantization,
461        loop_enabled: bool,
462        loop_start_samples: usize,
463        loop_end_samples: usize,
464    },
465    StopClip {
466        track_name: String,
467        scene_index: usize,
468        launch_quantization: LaunchQuantization,
469    },
470    LaunchScene {
471        scene_index: usize,
472        launch_quantization: LaunchQuantization,
473    },
474    StopScene {
475        scene_index: usize,
476        launch_quantization: LaunchQuantization,
477    },
478    StopAllClips,
479}
480
481#[derive(Clone, Debug)]
482pub enum Action {
483    Quit,
484    Log {
485        source: String,
486        message: String,
487    },
488    Play,
489    Pause,
490    Stop,
491    SessionPlay,
492    TransportPosition(usize),
493    TransportPositionAt {
494        sample: usize,
495        after_frames: usize,
496    },
497    JumpToEnd,
498    SetLoopEnabled(bool),
499    SetLoopRange(Option<(usize, usize)>),
500    SetPunchEnabled(bool),
501    SetPunchRange(Option<(usize, usize)>),
502    SetMetronomeEnabled(bool),
503    SetTempo(f64),
504    SetTimeSignature {
505        numerator: u16,
506        denominator: u16,
507    },
508    SetTempoMap {
509        tempo_points: Vec<TempoPoint>,
510        time_signature_points: Vec<TimeSignaturePoint>,
511    },
512    SetOscEnabled(bool),
513    SetClipPlaybackEnabled(bool),
514    SetSessionClipPlaybackEnabled(bool),
515    SetRecordEnabled(bool),
516    SetModulators(Vec<Modulator>),
517    SetTrackAutomationLanes {
518        track_name: String,
519        lanes: serde_json::Value,
520        mode: TrackAutomationMode,
521    },
522    TrackAutomationToggleLane {
523        track_name: String,
524        target: OfflineAutomationTarget,
525    },
526    TrackAutomationInsertPoint {
527        track_name: String,
528        target: OfflineAutomationTarget,
529        sample: usize,
530        value: f32,
531    },
532    TrackAutomationDeletePoint {
533        track_name: String,
534        target: OfflineAutomationTarget,
535        sample: usize,
536    },
537    TrackAutomationSetMode {
538        track_name: String,
539        mode: TrackAutomationMode,
540    },
541    SetSessionPath(String),
542    BeginHistoryGroup,
543    EndHistoryGroup,
544    ApplyGroupedActions(Vec<Action>),
545    ClearHistory,
546    BeginSessionRestore,
547    EndSessionRestore,
548    AddTrack {
549        name: String,
550        audio_ins: usize,
551        midi_ins: usize,
552        audio_outs: usize,
553        midi_outs: usize,
554        folder: bool,
555    },
556    TrackAddAudioInput(String),
557    TrackAddAudioOutput(String),
558    TrackRemoveAudioInput(String),
559    TrackRemoveAudioOutput(String),
560    AddClip {
561        clip_id: String,
562        name: String,
563        track_name: String,
564        start: usize,
565        length: usize,
566        offset: usize,
567        input_channel: usize,
568        muted: bool,
569        peaks_file: Option<String>,
570        kind: Kind,
571        fade_enabled: bool,
572        fade_in_samples: usize,
573        fade_out_samples: usize,
574        source_name: Option<String>,
575        source_offset: Option<usize>,
576        source_length: Option<usize>,
577        preview_name: Option<String>,
578        pitch_correction_points: Vec<PitchCorrectionPointData>,
579        pitch_correction_frame_likeness: Option<f32>,
580        pitch_correction_inertia_ms: Option<u16>,
581        pitch_correction_formant_compensation: Option<bool>,
582        plugin_graph_json: Option<serde_json::Value>,
583    },
584    AddGroupedClip {
585        track_name: String,
586        kind: Kind,
587        audio_clip: Option<AudioClipData>,
588        midi_clip: Option<MidiClipData>,
589    },
590    RemoveClip {
591        track_name: String,
592        kind: Kind,
593        clip_indices: Vec<usize>,
594    },
595    SetClipFade {
596        track_name: String,
597        clip_index: usize,
598        kind: Kind,
599        fade_enabled: bool,
600        fade_in_samples: usize,
601        fade_out_samples: usize,
602    },
603    SetClipBounds {
604        track_name: String,
605        clip_index: usize,
606        kind: Kind,
607        start: usize,
608        length: usize,
609        offset: usize,
610    },
611    SyncClipBounds {
612        track_name: String,
613        clip_index: usize,
614        kind: Kind,
615        start: usize,
616        length: usize,
617        offset: usize,
618    },
619    SetClipMuted {
620        track_name: String,
621        clip_index: usize,
622        kind: Kind,
623        muted: bool,
624    },
625    SetClipPluginGraphJson {
626        track_name: String,
627        clip_index: usize,
628        plugin_graph_json: Option<serde_json::Value>,
629    },
630    SetClipPitchCorrection {
631        track_name: String,
632        clip_index: usize,
633        preview_name: Option<String>,
634        source_name: Option<String>,
635        source_offset: Option<usize>,
636        source_length: Option<usize>,
637        pitch_correction_points: Vec<PitchCorrectionPointData>,
638        pitch_correction_frame_likeness: Option<f32>,
639        pitch_correction_inertia_ms: Option<u16>,
640        pitch_correction_formant_compensation: Option<bool>,
641    },
642    RenameClip {
643        track_name: String,
644        kind: Kind,
645        clip_index: usize,
646        new_name: String,
647    },
648    SetClipSourceName {
649        track_name: String,
650        kind: Kind,
651        clip_index: usize,
652        name: String,
653    },
654    RenameTrack {
655        old_name: String,
656        new_name: String,
657    },
658    RemoveTrack(String),
659    TrackLevel(String, f32),
660    TrackBalance(String, f32),
661    TrackAutomationLevel(String, f32),
662    TrackAutomationBalance(String, f32),
663    TrackMidiCc {
664        track_name: String,
665        channel: u8,
666        cc: u8,
667        value: u8,
668    },
669    TrackMeters {
670        track_name: String,
671        output_db: Vec<f32>,
672    },
673    RequestMeterSnapshot,
674    MeterSnapshot {
675        hw_out_db: Arc<Vec<f32>>,
676        track_meters: Arc<Vec<(String, Vec<f32>)>>,
677    },
678    RequestTrackList,
679    TrackList(Vec<String>),
680    RequestTransportState,
681    TransportState {
682        sample: usize,
683        tempo_bpm: f64,
684        playing: bool,
685        paused: bool,
686        tsig_num: u16,
687        tsig_denom: u16,
688    },
689    TrackToggleArm(String),
690    TrackToggleMute(String),
691    TrackTogglePhase(String),
692    TrackToggleSolo(String),
693    TrackToggleMaster(String),
694    TrackToggleInputMonitor {
695        track_name: String,
696        lane: usize,
697    },
698    TrackToggleDiskMonitor {
699        track_name: String,
700        lane: usize,
701    },
702    TrackToggleMidiInputMonitor {
703        track_name: String,
704        lane: usize,
705    },
706    TrackToggleMidiDiskMonitor {
707        track_name: String,
708        lane: usize,
709    },
710    TrackSetColor {
711        track_name: String,
712        color: Option<TrackColor>,
713    },
714    TrackArmMidiLearn {
715        track_name: String,
716        target: TrackMidiLearnTarget,
717    },
718    GlobalArmMidiLearn {
719        target: GlobalMidiLearnTarget,
720    },
721    SessionArmMidiLearn {
722        target: SessionMidiLearnTarget,
723    },
724    TrackSetMidiLearnBinding {
725        track_name: String,
726        target: TrackMidiLearnTarget,
727        binding: Option<MidiLearnBinding>,
728    },
729    SetGlobalMidiLearnBinding {
730        target: GlobalMidiLearnTarget,
731        binding: Option<MidiLearnBinding>,
732    },
733    SetSessionMidiLearnBinding {
734        target: SessionMidiLearnTarget,
735        binding: Option<MidiLearnBinding>,
736    },
737    SessionMidiLearnTriggered {
738        target: SessionMidiLearnTarget,
739    },
740    TrackSetFolder {
741        track_name: String,
742        is_folder: bool,
743    },
744    TrackSetParent {
745        track_name: String,
746        parent_name: Option<String>,
747    },
748    TrackToggleFolder {
749        track_name: String,
750    },
751    TrackSetMidiLaneChannel {
752        track_name: String,
753        lane: usize,
754        channel: Option<u8>,
755    },
756    TrackSetFrozen {
757        track_name: String,
758        frozen: bool,
759    },
760    TrackSetSessionSlot {
761        track_name: String,
762        scene_index: usize,
763        clip_id: Option<String>,
764    },
765    TrackSetSessionSlotPlayEnabled {
766        track_name: String,
767        scene_index: usize,
768        enabled: bool,
769    },
770    TrackOfflineBounce {
771        track_name: String,
772        output_path: String,
773        start_sample: usize,
774        length_samples: usize,
775        automation_lanes: Vec<OfflineAutomationLane>,
776        apply_fader: bool,
777    },
778    TrackOfflineBounceCancel {
779        track_name: String,
780    },
781    TrackOfflineBounceCancelAll,
782    TrackOfflineBounceCanceled {
783        track_name: String,
784    },
785    TrackOfflineBounceProgress {
786        track_name: String,
787        progress: f32,
788        operation: Option<String>,
789    },
790    PianoKey {
791        track_name: String,
792        note: u8,
793        velocity: u8,
794        on: bool,
795    },
796    ModifyMidiNotes {
797        track_name: String,
798        clip_index: usize,
799        note_indices: Vec<usize>,
800        new_notes: Vec<MidiNoteData>,
801        old_notes: Vec<MidiNoteData>,
802    },
803    ModifyMidiControllers {
804        track_name: String,
805        clip_index: usize,
806        controller_indices: Vec<usize>,
807        new_controllers: Vec<MidiControllerData>,
808        old_controllers: Vec<MidiControllerData>,
809    },
810    DeleteMidiControllers {
811        track_name: String,
812        clip_index: usize,
813        controller_indices: Vec<usize>,
814        deleted_controllers: Vec<(usize, MidiControllerData)>,
815    },
816    InsertMidiControllers {
817        track_name: String,
818        clip_index: usize,
819        controllers: Vec<(usize, MidiControllerData)>,
820    },
821    DeleteMidiNotes {
822        track_name: String,
823        clip_index: usize,
824        note_indices: Vec<usize>,
825        deleted_notes: Vec<(usize, MidiNoteData)>,
826    },
827    InsertMidiNotes {
828        track_name: String,
829        clip_index: usize,
830        notes: Vec<(usize, MidiNoteData)>,
831    },
832    SetStepRecording(bool),
833    StepRecordMidiNote {
834        device: String,
835        channel: u8,
836        pitch: u8,
837        velocity: u8,
838    },
839    SetMidiSysExEvents {
840        track_name: String,
841        clip_index: usize,
842        new_sysex_events: Vec<MidiRawEventData>,
843        old_sysex_events: Vec<MidiRawEventData>,
844    },
845    TrackClearDefaultPassthrough {
846        track_name: String,
847    },
848    TrackClearPlugins {
849        track_name: String,
850    },
851    #[cfg(all(unix, not(target_os = "macos")))]
852    TrackSetLv2PluginState {
853        track_name: String,
854        instance_id: usize,
855        state: Vec<u8>,
856    },
857    #[cfg(all(unix, not(target_os = "macos")))]
858    ClipSetLv2PluginState {
859        track_name: String,
860        clip_idx: usize,
861        instance_id: usize,
862        state: Vec<u8>,
863    },
864    #[cfg(all(unix, not(target_os = "macos")))]
865    TrackLv2SnapshotState {
866        track_name: String,
867        instance_id: usize,
868    },
869    #[cfg(all(unix, not(target_os = "macos")))]
870    ClipLv2SnapshotState {
871        track_name: String,
872        clip_idx: usize,
873        instance_id: usize,
874    },
875    #[cfg(all(unix, not(target_os = "macos")))]
876    TrackLv2StateSnapshot {
877        track_name: String,
878        instance_id: usize,
879        state: Vec<u8>,
880    },
881    #[cfg(all(unix, not(target_os = "macos")))]
882    TrackGetLv2PluginControls {
883        track_name: String,
884        instance_id: usize,
885    },
886    #[cfg(all(unix, not(target_os = "macos")))]
887    ClipGetLv2PluginControls {
888        track_name: String,
889        clip_idx: usize,
890        instance_id: usize,
891    },
892    #[cfg(all(unix, not(target_os = "macos")))]
893    TrackLv2PluginControls {
894        track_name: String,
895        instance_id: usize,
896        controls: Vec<Lv2ControlPortInfo>,
897        instance_access_handle: Option<usize>,
898    },
899    #[cfg(all(unix, not(target_os = "macos")))]
900    ClipLv2PluginControls {
901        track_name: String,
902        clip_idx: usize,
903        instance_id: usize,
904        controls: Vec<Lv2ControlPortInfo>,
905        instance_access_handle: Option<usize>,
906    },
907    #[cfg(all(unix, not(target_os = "macos")))]
908    TrackGetLv2Midnam {
909        track_name: String,
910    },
911    #[cfg(all(unix, not(target_os = "macos")))]
912    TrackLv2Midnam {
913        track_name: String,
914        note_names: std::collections::HashMap<u8, String>,
915    },
916    TrackGetClapNoteNames {
917        track_name: String,
918    },
919    TrackClapNoteNames {
920        track_name: String,
921        note_names: std::collections::HashMap<u8, String>,
922    },
923    #[cfg(all(unix, not(target_os = "macos")))]
924    TrackSetLv2ControlValue {
925        track_name: String,
926        instance_id: usize,
927        index: u32,
928        value: f32,
929    },
930    #[cfg(all(unix, not(target_os = "macos")))]
931    ClipSetLv2ControlValue {
932        track_name: String,
933        clip_idx: usize,
934        instance_id: usize,
935        index: u32,
936        value: f32,
937    },
938    #[cfg(all(unix, not(target_os = "macos")))]
939    ClipLv2StateSnapshot {
940        track_name: String,
941        clip_idx: usize,
942        instance_id: usize,
943        state: Vec<u8>,
944    },
945    TrackGetPluginGraph {
946        track_name: String,
947        include_state: bool,
948    },
949    TrackPluginGraph {
950        track_name: String,
951        plugins: Vec<PluginGraphPlugin>,
952        connections: Vec<PluginGraphConnection>,
953        connectable_connections: Vec<ConnectableConnection>,
954    },
955    TrackConnectPluginAudio {
956        track_name: String,
957        from_node: PluginGraphNode,
958        from_port: usize,
959        to_node: PluginGraphNode,
960        to_port: usize,
961    },
962    TrackConnectPluginMidi {
963        track_name: String,
964        from_node: PluginGraphNode,
965        from_port: usize,
966        to_node: PluginGraphNode,
967        to_port: usize,
968    },
969    TrackDisconnectPluginAudio {
970        track_name: String,
971        from_node: PluginGraphNode,
972        from_port: usize,
973        to_node: PluginGraphNode,
974        to_port: usize,
975    },
976    TrackDisconnectPluginMidi {
977        track_name: String,
978        from_node: PluginGraphNode,
979        from_port: usize,
980        to_node: PluginGraphNode,
981        to_port: usize,
982    },
983    TrackConnectAudio {
984        track_name: String,
985        from: ConnectableRef,
986        from_port: usize,
987        to: ConnectableRef,
988        to_port: usize,
989    },
990    TrackDisconnectAudio {
991        track_name: String,
992        from: ConnectableRef,
993        from_port: usize,
994        to: ConnectableRef,
995        to_port: usize,
996    },
997    TrackConnectMidi {
998        track_name: String,
999        from: ConnectableRef,
1000        from_port: usize,
1001        to: ConnectableRef,
1002        to_port: usize,
1003    },
1004    TrackDisconnectMidi {
1005        track_name: String,
1006        from: ConnectableRef,
1007        from_port: usize,
1008        to: ConnectableRef,
1009        to_port: usize,
1010    },
1011    #[cfg(all(unix, not(target_os = "macos")))]
1012    ListLv2Plugins,
1013    #[cfg(all(unix, not(target_os = "macos")))]
1014    Lv2Plugins(Vec<Lv2PluginInfo>),
1015    #[cfg(all(unix, not(target_os = "macos")))]
1016    Lv2PluginsUnavailable {
1017        error: String,
1018    },
1019    ListVst3Plugins,
1020    Vst3Plugins(Vec<Vst3PluginInfo>),
1021    Vst3PluginsUnavailable {
1022        error: String,
1023    },
1024    ListClapPlugins,
1025    ListClapPluginsWithCapabilities,
1026    ClapPlugins(Vec<ClapPluginInfo>),
1027    ClapPluginsUnavailable {
1028        error: String,
1029    },
1030    TrackSetClapParameter {
1031        track_name: String,
1032        instance_id: usize,
1033        param_id: u32,
1034        value: f64,
1035    },
1036    ClipSetClapParameter {
1037        track_name: String,
1038        clip_idx: usize,
1039        instance_id: usize,
1040        param_id: u32,
1041        value: f64,
1042    },
1043    TrackSetClapParameterAt {
1044        track_name: String,
1045        instance_id: usize,
1046        param_id: u32,
1047        value: f64,
1048        frame: u32,
1049    },
1050    TrackBeginClapParameterEdit {
1051        track_name: String,
1052        instance_id: usize,
1053        param_id: u32,
1054        frame: u32,
1055    },
1056    TrackEndClapParameterEdit {
1057        track_name: String,
1058        instance_id: usize,
1059        param_id: u32,
1060        frame: u32,
1061    },
1062    TrackGetClapParameters {
1063        track_name: String,
1064        instance_id: usize,
1065    },
1066    TrackClapParameters {
1067        track_name: String,
1068        instance_id: usize,
1069        parameters: Vec<ClapParameterInfo>,
1070    },
1071    TrackClapSnapshotState {
1072        track_name: String,
1073        instance_id: usize,
1074    },
1075    ClipClapSnapshotState {
1076        track_name: String,
1077        clip_idx: usize,
1078        instance_id: usize,
1079    },
1080    TrackClapStateSnapshot {
1081        track_name: String,
1082        instance_id: usize,
1083        plugin_id: String,
1084        state: crate::clap::ClapPluginState,
1085    },
1086    ClipClapStateSnapshot {
1087        track_name: String,
1088        clip_idx: usize,
1089        instance_id: usize,
1090        plugin_id: String,
1091        state: crate::clap::ClapPluginState,
1092    },
1093    TrackClapStateDirty {
1094        track_name: String,
1095        instance_id: usize,
1096    },
1097    ClipClapStateDirty {
1098        track_name: String,
1099        clip_idx: usize,
1100        instance_id: usize,
1101    },
1102    TrackClapRestoreState {
1103        track_name: String,
1104        instance_id: usize,
1105        state: crate::clap::ClapPluginState,
1106    },
1107    ClipClapRestoreState {
1108        track_name: String,
1109        clip_idx: usize,
1110        instance_id: usize,
1111        state: crate::clap::ClapPluginState,
1112    },
1113    TrackSnapshotAllClapStates {
1114        track_name: String,
1115    },
1116    TrackSnapshotAllClapStatesDone {
1117        track_name: String,
1118    },
1119    TrackLoadClapPlugin {
1120        track_name: String,
1121        plugin_id: String,
1122        instance_id: Option<usize>,
1123    },
1124    TrackUnloadClapPlugin {
1125        track_name: String,
1126        plugin_id: String,
1127    },
1128    TrackUnloadClapPluginInstance {
1129        track_name: String,
1130        instance_id: usize,
1131    },
1132    TrackShowClapGui {
1133        track_name: String,
1134        instance_id: usize,
1135    },
1136    TrackLoadVst3Plugin {
1137        track_name: String,
1138        plugin_id: String,
1139        instance_id: Option<usize>,
1140    },
1141    TrackUnloadVst3Plugin {
1142        track_name: String,
1143        plugin_id: String,
1144    },
1145    TrackUnloadVst3PluginInstance {
1146        track_name: String,
1147        instance_id: usize,
1148    },
1149    TrackShowVst3Gui {
1150        track_name: String,
1151        instance_id: usize,
1152    },
1153    #[cfg(all(unix, not(target_os = "macos")))]
1154    TrackLoadLv2Plugin {
1155        track_name: String,
1156        plugin_uri: String,
1157        instance_id: Option<usize>,
1158    },
1159    #[cfg(all(unix, not(target_os = "macos")))]
1160    TrackUnloadLv2Plugin {
1161        track_name: String,
1162        plugin_uri: String,
1163    },
1164    #[cfg(all(unix, not(target_os = "macos")))]
1165    TrackUnloadLv2PluginInstance {
1166        track_name: String,
1167        instance_id: usize,
1168    },
1169    TrackShowLv2Gui {
1170        track_name: String,
1171        instance_id: usize,
1172    },
1173    TrackSetPluginResourceDir {
1174        track_name: String,
1175        instance_id: usize,
1176        format: String,
1177        directory: String,
1178    },
1179    TrackClapFileReferences {
1180        track_name: String,
1181        instance_id: usize,
1182        refs: Vec<(u32, String)>,
1183    },
1184    TrackUpdateClapFileReference {
1185        track_name: String,
1186        instance_id: usize,
1187        index: u32,
1188        path: String,
1189    },
1190    ClipSetPluginResourceDir {
1191        track_name: String,
1192        clip_idx: usize,
1193        instance_id: usize,
1194        format: String,
1195        directory: String,
1196    },
1197    ClipClapFileReferences {
1198        track_name: String,
1199        clip_idx: usize,
1200        instance_id: usize,
1201        refs: Vec<(u32, String)>,
1202    },
1203    ClipUpdateClapFileReference {
1204        track_name: String,
1205        clip_idx: usize,
1206        instance_id: usize,
1207        index: u32,
1208        path: String,
1209    },
1210    TrackGetVst3Graph {
1211        track_name: String,
1212    },
1213    TrackVst3Graph {
1214        track_name: String,
1215        plugins: Vec<Vst3GraphPlugin>,
1216        connections: Vec<Vst3GraphConnection>,
1217    },
1218    TrackSetVst3Parameter {
1219        track_name: String,
1220        instance_id: usize,
1221        param_id: u32,
1222        value: f32,
1223    },
1224    TrackSetPluginBypassed {
1225        track_name: String,
1226        instance_id: usize,
1227        format: String,
1228        bypassed: bool,
1229    },
1230    TrackGetVst3Parameters {
1231        track_name: String,
1232        instance_id: usize,
1233    },
1234    TrackVst3Parameters {
1235        track_name: String,
1236        instance_id: usize,
1237        parameters: Vec<crate::vst3::port::ParameterInfo>,
1238    },
1239    TrackVst3SnapshotState {
1240        track_name: String,
1241        instance_id: usize,
1242    },
1243    ClipVst3SnapshotState {
1244        track_name: String,
1245        clip_idx: usize,
1246        instance_id: usize,
1247    },
1248    TrackVst3StateSnapshot {
1249        track_name: String,
1250        instance_id: usize,
1251        state: crate::vst3::state::Vst3PluginState,
1252    },
1253    ClipVst3StateSnapshot {
1254        track_name: String,
1255        clip_idx: usize,
1256        instance_id: usize,
1257        state: crate::vst3::state::Vst3PluginState,
1258    },
1259    TrackVst3RestoreState {
1260        track_name: String,
1261        instance_id: usize,
1262        state: crate::vst3::state::Vst3PluginState,
1263    },
1264    ClipVst3RestoreState {
1265        track_name: String,
1266        clip_idx: usize,
1267        instance_id: usize,
1268        state: crate::vst3::state::Vst3PluginState,
1269    },
1270    TrackConnectVst3Audio {
1271        track_name: String,
1272        from_node: Vst3GraphNode,
1273        from_port: usize,
1274        to_node: Vst3GraphNode,
1275        to_port: usize,
1276    },
1277    TrackDisconnectVst3Audio {
1278        track_name: String,
1279        from_node: Vst3GraphNode,
1280        from_port: usize,
1281        to_node: Vst3GraphNode,
1282        to_port: usize,
1283    },
1284    ClipMove {
1285        kind: Kind,
1286        from: ClipMoveFrom,
1287        to: ClipMoveTo,
1288        copy: bool,
1289    },
1290    Connect {
1291        from_track: String,
1292        from_port: usize,
1293        to_track: String,
1294        to_port: usize,
1295        kind: Kind,
1296    },
1297    Disconnect {
1298        from_track: String,
1299        from_port: usize,
1300        to_track: String,
1301        to_port: usize,
1302        kind: Kind,
1303    },
1304    OpenAudioDevice {
1305        device: String,
1306        input_device: Option<String>,
1307        sample_rate_hz: i32,
1308        bits: i32,
1309        exclusive: bool,
1310        period_frames: usize,
1311        nperiods: usize,
1312        sync_mode: bool,
1313        actual_period_frames: usize,
1314        input_channels: usize,
1315        output_channels: usize,
1316        bytes_per_frame: usize,
1317    },
1318    JackAddAudioInputPort,
1319    JackRemoveAudioInputPort(usize),
1320    JackAddAudioOutputPort,
1321    JackRemoveAudioOutputPort(usize),
1322    JackGetGraph,
1323    JackGraph(JackGraphInfo),
1324    JackConnect {
1325        source: String,
1326        destination: String,
1327    },
1328    JackDisconnect {
1329        source: String,
1330        destination: String,
1331    },
1332    OpenMidiInputDevice(String),
1333    OpenMidiOutputDevice(String),
1334    RequestSessionDiagnostics,
1335    RequestMidiLearnMappingsReport,
1336    ClearAllMidiLearnBindings,
1337    SessionDiagnosticsReport {
1338        track_count: usize,
1339        frozen_track_count: usize,
1340        audio_clip_count: usize,
1341        midi_clip_count: usize,
1342        #[cfg(all(unix, not(target_os = "macos")))]
1343        lv2_instance_count: usize,
1344        vst3_instance_count: usize,
1345        clap_instance_count: usize,
1346        pending_requests: usize,
1347        workers_total: usize,
1348        workers_ready: usize,
1349        pending_hw_midi_events: usize,
1350        playing: bool,
1351        transport_running: bool,
1352        transport_sample: usize,
1353        tempo_bpm: f64,
1354        sample_rate_hz: usize,
1355        cycle_samples: usize,
1356    },
1357    MidiLearnMappingsReport {
1358        lines: Vec<String>,
1359    },
1360    HWInfo {
1361        channels: usize,
1362        rate: usize,
1363        input: bool,
1364    },
1365    MarkHistorySavePoint,
1366    HistoryState {
1367        dirty: bool,
1368    },
1369    Undo,
1370    Redo,
1371    Session(SessionAction),
1372    SessionRuntimeReport {
1373        track_name: String,
1374        scene_index: usize,
1375        state: SessionSlotState,
1376        play_position_samples: usize,
1377        elapsed_samples: usize,
1378    },
1379    Panic,
1380}
1381
1382#[derive(Clone, Debug)]
1383pub enum Message {
1384    Ready(usize),
1385    TracksFinished,
1386
1387    /// Dispatch a render-plan node to a worker (Phase 2, see `LOCKLESS.md`).
1388    NodeJob(crate::executor::NodeJob),
1389    /// A worker completed a plan node. Stale `epoch`s are dropped.
1390    NodeDone {
1391        worker_id: usize,
1392        epoch: u64,
1393        node: u32,
1394        output_linear: Vec<f32>,
1395        parameter_updates: Vec<Action>,
1396    },
1397    ProcessOfflineBounce(OfflineBounceWork),
1398    Channel(Sender<Self>),
1399
1400    Request(Action),
1401    OscRequest {
1402        action: Action,
1403        reply_to: SocketAddr,
1404    },
1405    Response(Result<Action, String>),
1406    HWMidiEvents(Vec<HwMidiEvent>),
1407    HWMidiOutEvents(Vec<HwMidiEvent>),
1408    ClearHWMidiOutEvents,
1409    HWSetPlaying(bool),
1410    HWSetOutputGainBalance {
1411        gain: f32,
1412        balance: f32,
1413    },
1414    HWOpenMidiInputDevice(String),
1415    HWOpenMidiOutputDevice(String),
1416    HWCloseMidiDevices,
1417    HWFinished,
1418    OfflineBounceFinished {
1419        result: Result<Action, String>,
1420    },
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1426    use serde_json::json;
1427
1428    #[test]
1429    fn audio_clip_data_serde_round_trips_nested_groups() {
1430        let clip = AudioClipData {
1431            id: String::new(),
1432            name: "group.wav".to_string(),
1433            start: 12,
1434            length: 96,
1435            offset: 3,
1436            input_channel: 1,
1437            muted: true,
1438            peaks_file: Some("peaks/group.json".to_string()),
1439            fade_enabled: false,
1440            fade_in_samples: 10,
1441            fade_out_samples: 20,
1442            preview_name: Some("preview.wav".to_string()),
1443            source_name: Some("source.wav".to_string()),
1444            source_offset: Some(4),
1445            source_length: Some(88),
1446            pitch_correction_points: vec![PitchCorrectionPointData {
1447                start_sample: 7,
1448                length_samples: 11,
1449                detected_midi_pitch: 60.1,
1450                target_midi_pitch: 61.2,
1451                clarity: 0.8,
1452            }],
1453            pitch_correction_frame_likeness: Some(0.5),
1454            pitch_correction_inertia_ms: Some(123),
1455            pitch_correction_formant_compensation: Some(false),
1456            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1457            grouped_clips: vec![AudioClipData {
1458                name: "child.wav".to_string(),
1459                start: 0,
1460                length: 48,
1461                ..AudioClipData::default()
1462            }],
1463        };
1464
1465        let value = serde_json::to_value(&clip).expect("serialize");
1466        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1467
1468        assert_eq!(restored.name, clip.name);
1469        assert_eq!(restored.preview_name, clip.preview_name);
1470        assert_eq!(restored.source_name, clip.source_name);
1471        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1472        assert_eq!(restored.grouped_clips.len(), 1);
1473        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1474        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1475    }
1476
1477    #[test]
1478    fn midi_clip_data_serde_round_trips_nested_groups() {
1479        let clip = MidiClipData {
1480            id: String::new(),
1481            name: "group.mid".to_string(),
1482            start: 5,
1483            length: 64,
1484            offset: 2,
1485            input_channel: 3,
1486            muted: true,
1487            grouped_clips: vec![MidiClipData {
1488                name: "child.mid".to_string(),
1489                start: 0,
1490                length: 32,
1491                ..MidiClipData::default()
1492            }],
1493        };
1494
1495        let value = serde_json::to_value(&clip).expect("serialize");
1496        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1497
1498        assert_eq!(restored.name, clip.name);
1499        assert_eq!(restored.grouped_clips.len(), 1);
1500        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1501    }
1502
1503    #[test]
1504    fn pitch_correction_point_data_serde_round_trips() {
1505        let point = PitchCorrectionPointData {
1506            start_sample: 10,
1507            length_samples: 20,
1508            detected_midi_pitch: 57.5,
1509            target_midi_pitch: 58.0,
1510            clarity: 0.9,
1511        };
1512
1513        let value = serde_json::to_value(&point).expect("serialize");
1514        let restored: PitchCorrectionPointData =
1515            serde_json::from_value(value).expect("deserialize");
1516
1517        assert_eq!(restored.start_sample, 10);
1518        assert_eq!(restored.length_samples, 20);
1519        assert_eq!(restored.detected_midi_pitch, 57.5);
1520        assert_eq!(restored.target_midi_pitch, 58.0);
1521        assert_eq!(restored.clarity, 0.9);
1522    }
1523
1524    #[test]
1525    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1526        let restored: AudioClipData = serde_json::from_value(json!({
1527            "name": "clip.wav",
1528            "start": 1,
1529            "length": 2,
1530            "offset": 3,
1531            "input_channel": 0,
1532            "muted": false,
1533            "fade_enabled": true,
1534            "fade_in_samples": 240,
1535            "fade_out_samples": 240,
1536            "pitch_correction_points": [],
1537            "grouped_clips": []
1538        }))
1539        .expect("deserialize");
1540
1541        assert_eq!(restored.name, "clip.wav");
1542        assert!(restored.peaks_file.is_none());
1543        assert!(restored.preview_name.is_none());
1544        assert!(restored.source_name.is_none());
1545        assert!(restored.source_offset.is_none());
1546        assert!(restored.source_length.is_none());
1547        assert!(restored.pitch_correction_points.is_empty());
1548        assert!(restored.plugin_graph_json.is_none());
1549    }
1550}