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    /// Queue a scene to launch when the longest currently playing clip
479    /// finishes its current pass. When the current scene has no clips, it
480    /// behaves as a clip whose length is `launch_quantization`.
481    /// Queueing a different scene replaces the queue. Playing clips stop
482    /// when the queued scene launches.
483    QueueScene {
484        scene_index: usize,
485        launch_quantization: LaunchQuantization,
486    },
487    StopAllClips,
488}
489
490#[derive(Clone, Debug)]
491pub enum Action {
492    Quit,
493    Log {
494        source: String,
495        message: String,
496    },
497    Play,
498    Pause,
499    Stop,
500    SessionPlay,
501    TransportPosition(usize),
502    TransportPositionAt {
503        sample: usize,
504        after_frames: usize,
505    },
506    JumpToEnd,
507    SetLoopEnabled(bool),
508    SetLoopRange(Option<(usize, usize)>),
509    SetPunchEnabled(bool),
510    SetPunchRange(Option<(usize, usize)>),
511    SetMetronomeEnabled(bool),
512    SetTempo(f64),
513    SetTimeSignature {
514        numerator: u16,
515        denominator: u16,
516    },
517    SetTempoMap {
518        tempo_points: Vec<TempoPoint>,
519        time_signature_points: Vec<TimeSignaturePoint>,
520    },
521    SetOscEnabled(bool),
522    SetClipPlaybackEnabled(bool),
523    SetSessionClipPlaybackEnabled(bool),
524    SetRecordEnabled(bool),
525    SetModulators(Vec<Modulator>),
526    SetTrackAutomationLanes {
527        track_name: String,
528        lanes: serde_json::Value,
529        mode: TrackAutomationMode,
530    },
531    TrackAutomationToggleLane {
532        track_name: String,
533        target: OfflineAutomationTarget,
534    },
535    TrackAutomationInsertPoint {
536        track_name: String,
537        target: OfflineAutomationTarget,
538        sample: usize,
539        value: f32,
540    },
541    TrackAutomationDeletePoint {
542        track_name: String,
543        target: OfflineAutomationTarget,
544        sample: usize,
545    },
546    TrackAutomationSetMode {
547        track_name: String,
548        mode: TrackAutomationMode,
549    },
550    SetSessionPath(String),
551    BeginHistoryGroup,
552    EndHistoryGroup,
553    ApplyGroupedActions(Vec<Action>),
554    ClearHistory,
555    BeginSessionRestore,
556    EndSessionRestore,
557    AddTrack {
558        name: String,
559        audio_ins: usize,
560        midi_ins: usize,
561        audio_outs: usize,
562        midi_outs: usize,
563        folder: bool,
564    },
565    TrackAddAudioInput(String),
566    TrackAddAudioOutput(String),
567    TrackRemoveAudioInput(String),
568    TrackRemoveAudioOutput(String),
569    AddClip {
570        clip_id: String,
571        name: String,
572        track_name: String,
573        start: usize,
574        length: usize,
575        offset: usize,
576        input_channel: usize,
577        muted: bool,
578        peaks_file: Option<String>,
579        kind: Kind,
580        fade_enabled: bool,
581        fade_in_samples: usize,
582        fade_out_samples: usize,
583        source_name: Option<String>,
584        source_offset: Option<usize>,
585        source_length: Option<usize>,
586        preview_name: Option<String>,
587        pitch_correction_points: Vec<PitchCorrectionPointData>,
588        pitch_correction_frame_likeness: Option<f32>,
589        pitch_correction_inertia_ms: Option<u16>,
590        pitch_correction_formant_compensation: Option<bool>,
591        plugin_graph_json: Option<serde_json::Value>,
592    },
593    AddGroupedClip {
594        track_name: String,
595        kind: Kind,
596        audio_clip: Option<AudioClipData>,
597        midi_clip: Option<MidiClipData>,
598    },
599    RemoveClip {
600        track_name: String,
601        kind: Kind,
602        clip_indices: Vec<usize>,
603    },
604    MoveClipToUnused {
605        track_name: String,
606        kind: Kind,
607        clip_indices: Vec<usize>,
608    },
609    DeleteUnusedClips {
610        clip_ids: Vec<String>,
611    },
612    SetUnusedClips {
613        audio: Vec<AudioClipData>,
614        midi: Vec<MidiClipData>,
615    },
616    SetClipFade {
617        track_name: String,
618        clip_index: usize,
619        kind: Kind,
620        fade_enabled: bool,
621        fade_in_samples: usize,
622        fade_out_samples: usize,
623    },
624    SetClipBounds {
625        track_name: String,
626        clip_index: usize,
627        kind: Kind,
628        start: usize,
629        length: usize,
630        offset: usize,
631    },
632    SyncClipBounds {
633        track_name: String,
634        clip_index: usize,
635        kind: Kind,
636        start: usize,
637        length: usize,
638        offset: usize,
639    },
640    SetClipMuted {
641        track_name: String,
642        clip_index: usize,
643        kind: Kind,
644        muted: bool,
645    },
646    SetClipPluginGraphJson {
647        track_name: String,
648        clip_index: usize,
649        plugin_graph_json: Option<serde_json::Value>,
650    },
651    SetClipPitchCorrection {
652        track_name: String,
653        clip_index: usize,
654        preview_name: Option<String>,
655        source_name: Option<String>,
656        source_offset: Option<usize>,
657        source_length: Option<usize>,
658        pitch_correction_points: Vec<PitchCorrectionPointData>,
659        pitch_correction_frame_likeness: Option<f32>,
660        pitch_correction_inertia_ms: Option<u16>,
661        pitch_correction_formant_compensation: Option<bool>,
662    },
663    RenameClip {
664        track_name: String,
665        kind: Kind,
666        clip_index: usize,
667        new_name: String,
668    },
669    SetClipIdentity {
670        track_name: String,
671        kind: Kind,
672        clip_index: usize,
673        new_id: String,
674        new_name: String,
675    },
676    SetClipSourceName {
677        track_name: String,
678        kind: Kind,
679        clip_index: usize,
680        name: String,
681    },
682    RenameTrack {
683        old_name: String,
684        new_name: String,
685    },
686    RemoveTrack(String),
687    TrackLevel(String, f32),
688    TrackBalance(String, f32),
689    TrackAutomationLevel(String, f32),
690    TrackAutomationBalance(String, f32),
691    TrackMidiCc {
692        track_name: String,
693        channel: u8,
694        cc: u8,
695        value: u8,
696    },
697    TrackMeters {
698        track_name: String,
699        output_db: Vec<f32>,
700    },
701    RequestMeterSnapshot,
702    MeterSnapshot {
703        hw_out_db: Arc<Vec<f32>>,
704        track_meters: Arc<Vec<(String, Vec<f32>)>>,
705    },
706    RequestTrackList,
707    TrackList(Vec<String>),
708    RequestTransportState,
709    TransportState {
710        sample: usize,
711        tempo_bpm: f64,
712        playing: bool,
713        paused: bool,
714        tsig_num: u16,
715        tsig_denom: u16,
716    },
717    TrackToggleArm(String),
718    TrackToggleMute(String),
719    TrackTogglePhase(String),
720    TrackToggleSolo(String),
721    TrackToggleMaster(String),
722    TrackToggleInputMonitor {
723        track_name: String,
724        lane: usize,
725    },
726    TrackToggleDiskMonitor {
727        track_name: String,
728        lane: usize,
729    },
730    TrackToggleMidiInputMonitor {
731        track_name: String,
732        lane: usize,
733    },
734    TrackToggleMidiDiskMonitor {
735        track_name: String,
736        lane: usize,
737    },
738    TrackSetColor {
739        track_name: String,
740        color: Option<TrackColor>,
741    },
742    TrackArmMidiLearn {
743        track_name: String,
744        target: TrackMidiLearnTarget,
745    },
746    GlobalArmMidiLearn {
747        target: GlobalMidiLearnTarget,
748    },
749    SessionArmMidiLearn {
750        target: SessionMidiLearnTarget,
751    },
752    TrackSetMidiLearnBinding {
753        track_name: String,
754        target: TrackMidiLearnTarget,
755        binding: Option<MidiLearnBinding>,
756    },
757    SetGlobalMidiLearnBinding {
758        target: GlobalMidiLearnTarget,
759        binding: Option<MidiLearnBinding>,
760    },
761    SetSessionMidiLearnBinding {
762        target: SessionMidiLearnTarget,
763        binding: Option<MidiLearnBinding>,
764    },
765    SessionMidiLearnTriggered {
766        target: SessionMidiLearnTarget,
767    },
768    TrackSetFolder {
769        track_name: String,
770        is_folder: bool,
771    },
772    TrackSetParent {
773        track_name: String,
774        parent_name: Option<String>,
775    },
776    TrackToggleFolder {
777        track_name: String,
778    },
779    TrackSetMidiLaneChannel {
780        track_name: String,
781        lane: usize,
782        channel: Option<u8>,
783    },
784    TrackSetFrozen {
785        track_name: String,
786        frozen: bool,
787    },
788    TrackSetSessionSlot {
789        track_name: String,
790        scene_index: usize,
791        clip_id: Option<String>,
792    },
793    TrackSetSessionSlotPlayEnabled {
794        track_name: String,
795        scene_index: usize,
796        enabled: bool,
797    },
798    TrackSetSessionSlotStopEnabled {
799        track_name: String,
800        scene_index: usize,
801        enabled: bool,
802    },
803    TrackOfflineBounce {
804        track_name: String,
805        output_path: String,
806        start_sample: usize,
807        length_samples: usize,
808        automation_lanes: Vec<OfflineAutomationLane>,
809        apply_fader: bool,
810    },
811    TrackOfflineBounceCancel {
812        track_name: String,
813    },
814    TrackOfflineBounceCancelAll,
815    TrackOfflineBounceCanceled {
816        track_name: String,
817    },
818    TrackOfflineBounceProgress {
819        track_name: String,
820        progress: f32,
821        operation: Option<String>,
822    },
823    PianoKey {
824        track_name: String,
825        note: u8,
826        velocity: u8,
827        on: bool,
828    },
829    ModifyMidiNotes {
830        track_name: String,
831        clip_index: usize,
832        note_indices: Vec<usize>,
833        new_notes: Vec<MidiNoteData>,
834        old_notes: Vec<MidiNoteData>,
835    },
836    ModifyMidiControllers {
837        track_name: String,
838        clip_index: usize,
839        controller_indices: Vec<usize>,
840        new_controllers: Vec<MidiControllerData>,
841        old_controllers: Vec<MidiControllerData>,
842    },
843    DeleteMidiControllers {
844        track_name: String,
845        clip_index: usize,
846        controller_indices: Vec<usize>,
847        deleted_controllers: Vec<(usize, MidiControllerData)>,
848    },
849    InsertMidiControllers {
850        track_name: String,
851        clip_index: usize,
852        controllers: Vec<(usize, MidiControllerData)>,
853    },
854    DeleteMidiNotes {
855        track_name: String,
856        clip_index: usize,
857        note_indices: Vec<usize>,
858        deleted_notes: Vec<(usize, MidiNoteData)>,
859    },
860    InsertMidiNotes {
861        track_name: String,
862        clip_index: usize,
863        notes: Vec<(usize, MidiNoteData)>,
864    },
865    SetStepRecording(bool),
866    StepRecordMidiNote {
867        device: String,
868        channel: u8,
869        pitch: u8,
870        velocity: u8,
871    },
872    SetMidiSysExEvents {
873        track_name: String,
874        clip_index: usize,
875        new_sysex_events: Vec<MidiRawEventData>,
876        old_sysex_events: Vec<MidiRawEventData>,
877    },
878    TrackClearDefaultPassthrough {
879        track_name: String,
880    },
881    TrackClearPlugins {
882        track_name: String,
883    },
884    #[cfg(all(unix, not(target_os = "macos")))]
885    TrackSetLv2PluginState {
886        track_name: String,
887        instance_id: usize,
888        state: Vec<u8>,
889    },
890    #[cfg(all(unix, not(target_os = "macos")))]
891    ClipSetLv2PluginState {
892        track_name: String,
893        clip_idx: usize,
894        instance_id: usize,
895        state: Vec<u8>,
896    },
897    #[cfg(all(unix, not(target_os = "macos")))]
898    TrackLv2SnapshotState {
899        track_name: String,
900        instance_id: usize,
901    },
902    #[cfg(all(unix, not(target_os = "macos")))]
903    ClipLv2SnapshotState {
904        track_name: String,
905        clip_idx: usize,
906        instance_id: usize,
907    },
908    #[cfg(all(unix, not(target_os = "macos")))]
909    TrackLv2StateSnapshot {
910        track_name: String,
911        instance_id: usize,
912        state: Vec<u8>,
913    },
914    #[cfg(all(unix, not(target_os = "macos")))]
915    TrackGetLv2PluginControls {
916        track_name: String,
917        instance_id: usize,
918    },
919    #[cfg(all(unix, not(target_os = "macos")))]
920    ClipGetLv2PluginControls {
921        track_name: String,
922        clip_idx: usize,
923        instance_id: usize,
924    },
925    #[cfg(all(unix, not(target_os = "macos")))]
926    TrackLv2PluginControls {
927        track_name: String,
928        instance_id: usize,
929        controls: Vec<Lv2ControlPortInfo>,
930        instance_access_handle: Option<usize>,
931    },
932    #[cfg(all(unix, not(target_os = "macos")))]
933    ClipLv2PluginControls {
934        track_name: String,
935        clip_idx: usize,
936        instance_id: usize,
937        controls: Vec<Lv2ControlPortInfo>,
938        instance_access_handle: Option<usize>,
939    },
940    #[cfg(all(unix, not(target_os = "macos")))]
941    TrackGetLv2Midnam {
942        track_name: String,
943    },
944    #[cfg(all(unix, not(target_os = "macos")))]
945    TrackLv2Midnam {
946        track_name: String,
947        note_names: std::collections::HashMap<u8, String>,
948    },
949    TrackGetClapNoteNames {
950        track_name: String,
951    },
952    TrackClapNoteNames {
953        track_name: String,
954        note_names: std::collections::HashMap<u8, String>,
955    },
956    #[cfg(all(unix, not(target_os = "macos")))]
957    TrackSetLv2ControlValue {
958        track_name: String,
959        instance_id: usize,
960        index: u32,
961        value: f32,
962    },
963    #[cfg(all(unix, not(target_os = "macos")))]
964    ClipSetLv2ControlValue {
965        track_name: String,
966        clip_idx: usize,
967        instance_id: usize,
968        index: u32,
969        value: f32,
970    },
971    #[cfg(all(unix, not(target_os = "macos")))]
972    ClipLv2StateSnapshot {
973        track_name: String,
974        clip_idx: usize,
975        instance_id: usize,
976        state: Vec<u8>,
977    },
978    TrackGetPluginGraph {
979        track_name: String,
980        include_state: bool,
981    },
982    TrackPluginGraph {
983        track_name: String,
984        plugins: Vec<PluginGraphPlugin>,
985        connections: Vec<PluginGraphConnection>,
986        connectable_connections: Vec<ConnectableConnection>,
987    },
988    TrackConnectPluginAudio {
989        track_name: String,
990        from_node: PluginGraphNode,
991        from_port: usize,
992        to_node: PluginGraphNode,
993        to_port: usize,
994    },
995    TrackConnectPluginMidi {
996        track_name: String,
997        from_node: PluginGraphNode,
998        from_port: usize,
999        to_node: PluginGraphNode,
1000        to_port: usize,
1001    },
1002    TrackDisconnectPluginAudio {
1003        track_name: String,
1004        from_node: PluginGraphNode,
1005        from_port: usize,
1006        to_node: PluginGraphNode,
1007        to_port: usize,
1008    },
1009    TrackDisconnectPluginMidi {
1010        track_name: String,
1011        from_node: PluginGraphNode,
1012        from_port: usize,
1013        to_node: PluginGraphNode,
1014        to_port: usize,
1015    },
1016    TrackConnectAudio {
1017        track_name: String,
1018        from: ConnectableRef,
1019        from_port: usize,
1020        to: ConnectableRef,
1021        to_port: usize,
1022    },
1023    TrackDisconnectAudio {
1024        track_name: String,
1025        from: ConnectableRef,
1026        from_port: usize,
1027        to: ConnectableRef,
1028        to_port: usize,
1029    },
1030    TrackConnectMidi {
1031        track_name: String,
1032        from: ConnectableRef,
1033        from_port: usize,
1034        to: ConnectableRef,
1035        to_port: usize,
1036    },
1037    TrackDisconnectMidi {
1038        track_name: String,
1039        from: ConnectableRef,
1040        from_port: usize,
1041        to: ConnectableRef,
1042        to_port: usize,
1043    },
1044    #[cfg(all(unix, not(target_os = "macos")))]
1045    ListLv2Plugins,
1046    #[cfg(all(unix, not(target_os = "macos")))]
1047    Lv2Plugins(Vec<Lv2PluginInfo>),
1048    #[cfg(all(unix, not(target_os = "macos")))]
1049    Lv2PluginsUnavailable {
1050        error: String,
1051    },
1052    ListVst3Plugins,
1053    Vst3Plugins(Vec<Vst3PluginInfo>),
1054    Vst3PluginsUnavailable {
1055        error: String,
1056    },
1057    ListClapPlugins,
1058    ListClapPluginsWithCapabilities,
1059    ClapPlugins(Vec<ClapPluginInfo>),
1060    ClapPluginsUnavailable {
1061        error: String,
1062    },
1063    TrackSetClapParameter {
1064        track_name: String,
1065        instance_id: usize,
1066        param_id: u32,
1067        value: f64,
1068    },
1069    ClipSetClapParameter {
1070        track_name: String,
1071        clip_idx: usize,
1072        instance_id: usize,
1073        param_id: u32,
1074        value: f64,
1075    },
1076    TrackSetClapParameterAt {
1077        track_name: String,
1078        instance_id: usize,
1079        param_id: u32,
1080        value: f64,
1081        frame: u32,
1082    },
1083    TrackBeginClapParameterEdit {
1084        track_name: String,
1085        instance_id: usize,
1086        param_id: u32,
1087        frame: u32,
1088    },
1089    TrackEndClapParameterEdit {
1090        track_name: String,
1091        instance_id: usize,
1092        param_id: u32,
1093        frame: u32,
1094    },
1095    TrackGetClapParameters {
1096        track_name: String,
1097        instance_id: usize,
1098    },
1099    TrackClapParameters {
1100        track_name: String,
1101        instance_id: usize,
1102        parameters: Vec<ClapParameterInfo>,
1103    },
1104    TrackClapSnapshotState {
1105        track_name: String,
1106        instance_id: usize,
1107    },
1108    ClipClapSnapshotState {
1109        track_name: String,
1110        clip_idx: usize,
1111        instance_id: usize,
1112    },
1113    TrackClapStateSnapshot {
1114        track_name: String,
1115        instance_id: usize,
1116        plugin_id: String,
1117        state: crate::clap::ClapPluginState,
1118    },
1119    ClipClapStateSnapshot {
1120        track_name: String,
1121        clip_idx: usize,
1122        instance_id: usize,
1123        plugin_id: String,
1124        state: crate::clap::ClapPluginState,
1125    },
1126    TrackClapStateDirty {
1127        track_name: String,
1128        instance_id: usize,
1129    },
1130    ClipClapStateDirty {
1131        track_name: String,
1132        clip_idx: usize,
1133        instance_id: usize,
1134    },
1135    TrackClapRestoreState {
1136        track_name: String,
1137        instance_id: usize,
1138        state: crate::clap::ClapPluginState,
1139    },
1140    ClipClapRestoreState {
1141        track_name: String,
1142        clip_idx: usize,
1143        instance_id: usize,
1144        state: crate::clap::ClapPluginState,
1145    },
1146    TrackSnapshotAllClapStates {
1147        track_name: String,
1148    },
1149    TrackSnapshotAllClapStatesDone {
1150        track_name: String,
1151    },
1152    TrackLoadClapPlugin {
1153        track_name: String,
1154        plugin_id: String,
1155        instance_id: Option<usize>,
1156    },
1157    TrackUnloadClapPlugin {
1158        track_name: String,
1159        plugin_id: String,
1160    },
1161    TrackUnloadClapPluginInstance {
1162        track_name: String,
1163        instance_id: usize,
1164    },
1165    TrackShowClapGui {
1166        track_name: String,
1167        instance_id: usize,
1168    },
1169    TrackLoadVst3Plugin {
1170        track_name: String,
1171        plugin_id: String,
1172        instance_id: Option<usize>,
1173    },
1174    TrackUnloadVst3Plugin {
1175        track_name: String,
1176        plugin_id: String,
1177    },
1178    TrackUnloadVst3PluginInstance {
1179        track_name: String,
1180        instance_id: usize,
1181    },
1182    TrackShowVst3Gui {
1183        track_name: String,
1184        instance_id: usize,
1185    },
1186    #[cfg(all(unix, not(target_os = "macos")))]
1187    TrackLoadLv2Plugin {
1188        track_name: String,
1189        plugin_uri: String,
1190        instance_id: Option<usize>,
1191    },
1192    #[cfg(all(unix, not(target_os = "macos")))]
1193    TrackUnloadLv2Plugin {
1194        track_name: String,
1195        plugin_uri: String,
1196    },
1197    #[cfg(all(unix, not(target_os = "macos")))]
1198    TrackUnloadLv2PluginInstance {
1199        track_name: String,
1200        instance_id: usize,
1201    },
1202    TrackShowLv2Gui {
1203        track_name: String,
1204        instance_id: usize,
1205    },
1206    TrackSetPluginResourceDir {
1207        track_name: String,
1208        instance_id: usize,
1209        format: String,
1210        directory: String,
1211    },
1212    TrackClapFileReferences {
1213        track_name: String,
1214        instance_id: usize,
1215        refs: Vec<(u32, String)>,
1216    },
1217    TrackUpdateClapFileReference {
1218        track_name: String,
1219        instance_id: usize,
1220        index: u32,
1221        path: String,
1222    },
1223    ClipSetPluginResourceDir {
1224        track_name: String,
1225        clip_idx: usize,
1226        instance_id: usize,
1227        format: String,
1228        directory: String,
1229    },
1230    ClipClapFileReferences {
1231        track_name: String,
1232        clip_idx: usize,
1233        instance_id: usize,
1234        refs: Vec<(u32, String)>,
1235    },
1236    ClipUpdateClapFileReference {
1237        track_name: String,
1238        clip_idx: usize,
1239        instance_id: usize,
1240        index: u32,
1241        path: String,
1242    },
1243    TrackGetVst3Graph {
1244        track_name: String,
1245    },
1246    TrackVst3Graph {
1247        track_name: String,
1248        plugins: Vec<Vst3GraphPlugin>,
1249        connections: Vec<Vst3GraphConnection>,
1250    },
1251    TrackSetVst3Parameter {
1252        track_name: String,
1253        instance_id: usize,
1254        param_id: u32,
1255        value: f32,
1256    },
1257    TrackSetPluginBypassed {
1258        track_name: String,
1259        instance_id: usize,
1260        format: String,
1261        bypassed: bool,
1262    },
1263    TrackGetVst3Parameters {
1264        track_name: String,
1265        instance_id: usize,
1266    },
1267    TrackVst3Parameters {
1268        track_name: String,
1269        instance_id: usize,
1270        parameters: Vec<crate::vst3::port::ParameterInfo>,
1271    },
1272    TrackVst3SnapshotState {
1273        track_name: String,
1274        instance_id: usize,
1275    },
1276    ClipVst3SnapshotState {
1277        track_name: String,
1278        clip_idx: usize,
1279        instance_id: usize,
1280    },
1281    TrackVst3StateSnapshot {
1282        track_name: String,
1283        instance_id: usize,
1284        state: crate::vst3::state::Vst3PluginState,
1285    },
1286    ClipVst3StateSnapshot {
1287        track_name: String,
1288        clip_idx: usize,
1289        instance_id: usize,
1290        state: crate::vst3::state::Vst3PluginState,
1291    },
1292    TrackVst3RestoreState {
1293        track_name: String,
1294        instance_id: usize,
1295        state: crate::vst3::state::Vst3PluginState,
1296    },
1297    ClipVst3RestoreState {
1298        track_name: String,
1299        clip_idx: usize,
1300        instance_id: usize,
1301        state: crate::vst3::state::Vst3PluginState,
1302    },
1303    TrackConnectVst3Audio {
1304        track_name: String,
1305        from_node: Vst3GraphNode,
1306        from_port: usize,
1307        to_node: Vst3GraphNode,
1308        to_port: usize,
1309    },
1310    TrackDisconnectVst3Audio {
1311        track_name: String,
1312        from_node: Vst3GraphNode,
1313        from_port: usize,
1314        to_node: Vst3GraphNode,
1315        to_port: usize,
1316    },
1317    ClipMove {
1318        kind: Kind,
1319        from: ClipMoveFrom,
1320        to: ClipMoveTo,
1321        copy: bool,
1322    },
1323    Connect {
1324        from_track: String,
1325        from_port: usize,
1326        to_track: String,
1327        to_port: usize,
1328        kind: Kind,
1329    },
1330    Disconnect {
1331        from_track: String,
1332        from_port: usize,
1333        to_track: String,
1334        to_port: usize,
1335        kind: Kind,
1336    },
1337    OpenAudioDevice {
1338        device: String,
1339        input_device: Option<String>,
1340        sample_rate_hz: i32,
1341        bits: i32,
1342        exclusive: bool,
1343        period_frames: usize,
1344        nperiods: usize,
1345        sync_mode: bool,
1346        actual_period_frames: usize,
1347        input_channels: usize,
1348        output_channels: usize,
1349        bytes_per_frame: usize,
1350    },
1351    JackAddAudioInputPort,
1352    JackRemoveAudioInputPort(usize),
1353    JackAddAudioOutputPort,
1354    JackRemoveAudioOutputPort(usize),
1355    JackGetGraph,
1356    JackGraph(JackGraphInfo),
1357    JackConnect {
1358        source: String,
1359        destination: String,
1360    },
1361    JackDisconnect {
1362        source: String,
1363        destination: String,
1364    },
1365    OpenMidiInputDevice(String),
1366    OpenMidiOutputDevice(String),
1367    RequestSessionDiagnostics,
1368    RequestMidiLearnMappingsReport,
1369    ClearAllMidiLearnBindings,
1370    SessionDiagnosticsReport {
1371        track_count: usize,
1372        frozen_track_count: usize,
1373        audio_clip_count: usize,
1374        midi_clip_count: usize,
1375        #[cfg(all(unix, not(target_os = "macos")))]
1376        lv2_instance_count: usize,
1377        vst3_instance_count: usize,
1378        clap_instance_count: usize,
1379        pending_requests: usize,
1380        workers_total: usize,
1381        workers_ready: usize,
1382        pending_hw_midi_events: usize,
1383        playing: bool,
1384        transport_running: bool,
1385        transport_sample: usize,
1386        tempo_bpm: f64,
1387        sample_rate_hz: usize,
1388        cycle_samples: usize,
1389    },
1390    MidiLearnMappingsReport {
1391        lines: Vec<String>,
1392    },
1393    HWInfo {
1394        channels: usize,
1395        rate: usize,
1396        input: bool,
1397    },
1398    MarkHistorySavePoint,
1399    HistoryState {
1400        dirty: bool,
1401    },
1402    Undo,
1403    Redo,
1404    Session(SessionAction),
1405    SessionRuntimeReport {
1406        track_name: String,
1407        scene_index: usize,
1408        state: SessionSlotState,
1409        play_position_samples: usize,
1410        elapsed_samples: usize,
1411    },
1412    Panic,
1413}
1414
1415#[derive(Clone, Debug)]
1416pub enum Message {
1417    Ready(usize),
1418    TracksFinished,
1419
1420    /// Dispatch a render-plan node to a worker (Phase 2, see `LOCKLESS.md`).
1421    NodeJob(crate::executor::NodeJob),
1422    /// A worker completed a plan node. Stale `epoch`s are dropped.
1423    NodeDone {
1424        worker_id: usize,
1425        epoch: u64,
1426        node: u32,
1427        output_linear: Vec<f32>,
1428        parameter_updates: Vec<Action>,
1429    },
1430    ProcessOfflineBounce(OfflineBounceWork),
1431    Channel(Sender<Self>),
1432
1433    Request(Action),
1434    OscRequest {
1435        action: Action,
1436        reply_to: SocketAddr,
1437    },
1438    Response(Result<Action, String>),
1439    HWMidiEvents(Vec<HwMidiEvent>),
1440    HWMidiOutEvents(Vec<HwMidiEvent>),
1441    ClearHWMidiOutEvents,
1442    HWSetPlaying(bool),
1443    HWSetOutputGainBalance {
1444        gain: f32,
1445        balance: f32,
1446    },
1447    HWOpenMidiInputDevice(String),
1448    HWOpenMidiOutputDevice(String),
1449    HWCloseMidiDevices,
1450    HWFinished,
1451    OfflineBounceFinished {
1452        result: Result<Action, String>,
1453    },
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1459    use serde_json::json;
1460
1461    #[test]
1462    fn audio_clip_data_serde_round_trips_nested_groups() {
1463        let clip = AudioClipData {
1464            id: String::new(),
1465            name: "group.wav".to_string(),
1466            start: 12,
1467            length: 96,
1468            offset: 3,
1469            input_channel: 1,
1470            muted: true,
1471            peaks_file: Some("peaks/group.json".to_string()),
1472            fade_enabled: false,
1473            fade_in_samples: 10,
1474            fade_out_samples: 20,
1475            preview_name: Some("preview.wav".to_string()),
1476            source_name: Some("source.wav".to_string()),
1477            source_offset: Some(4),
1478            source_length: Some(88),
1479            pitch_correction_points: vec![PitchCorrectionPointData {
1480                start_sample: 7,
1481                length_samples: 11,
1482                detected_midi_pitch: 60.1,
1483                target_midi_pitch: 61.2,
1484                clarity: 0.8,
1485            }],
1486            pitch_correction_frame_likeness: Some(0.5),
1487            pitch_correction_inertia_ms: Some(123),
1488            pitch_correction_formant_compensation: Some(false),
1489            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1490            grouped_clips: vec![AudioClipData {
1491                name: "child.wav".to_string(),
1492                start: 0,
1493                length: 48,
1494                ..AudioClipData::default()
1495            }],
1496        };
1497
1498        let value = serde_json::to_value(&clip).expect("serialize");
1499        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1500
1501        assert_eq!(restored.name, clip.name);
1502        assert_eq!(restored.preview_name, clip.preview_name);
1503        assert_eq!(restored.source_name, clip.source_name);
1504        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1505        assert_eq!(restored.grouped_clips.len(), 1);
1506        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1507        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1508    }
1509
1510    #[test]
1511    fn midi_clip_data_serde_round_trips_nested_groups() {
1512        let clip = MidiClipData {
1513            id: String::new(),
1514            name: "group.mid".to_string(),
1515            start: 5,
1516            length: 64,
1517            offset: 2,
1518            input_channel: 3,
1519            muted: true,
1520            grouped_clips: vec![MidiClipData {
1521                name: "child.mid".to_string(),
1522                start: 0,
1523                length: 32,
1524                ..MidiClipData::default()
1525            }],
1526        };
1527
1528        let value = serde_json::to_value(&clip).expect("serialize");
1529        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1530
1531        assert_eq!(restored.name, clip.name);
1532        assert_eq!(restored.grouped_clips.len(), 1);
1533        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1534    }
1535
1536    #[test]
1537    fn pitch_correction_point_data_serde_round_trips() {
1538        let point = PitchCorrectionPointData {
1539            start_sample: 10,
1540            length_samples: 20,
1541            detected_midi_pitch: 57.5,
1542            target_midi_pitch: 58.0,
1543            clarity: 0.9,
1544        };
1545
1546        let value = serde_json::to_value(&point).expect("serialize");
1547        let restored: PitchCorrectionPointData =
1548            serde_json::from_value(value).expect("deserialize");
1549
1550        assert_eq!(restored.start_sample, 10);
1551        assert_eq!(restored.length_samples, 20);
1552        assert_eq!(restored.detected_midi_pitch, 57.5);
1553        assert_eq!(restored.target_midi_pitch, 58.0);
1554        assert_eq!(restored.clarity, 0.9);
1555    }
1556
1557    #[test]
1558    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1559        let restored: AudioClipData = serde_json::from_value(json!({
1560            "name": "clip.wav",
1561            "start": 1,
1562            "length": 2,
1563            "offset": 3,
1564            "input_channel": 0,
1565            "muted": false,
1566            "fade_enabled": true,
1567            "fade_in_samples": 240,
1568            "fade_out_samples": 240,
1569            "pitch_correction_points": [],
1570            "grouped_clips": []
1571        }))
1572        .expect("deserialize");
1573
1574        assert_eq!(restored.name, "clip.wav");
1575        assert!(restored.peaks_file.is_none());
1576        assert!(restored.preview_name.is_none());
1577        assert!(restored.source_name.is_none());
1578        assert!(restored.source_offset.is_none());
1579        assert!(restored.source_length.is_none());
1580        assert!(restored.pitch_correction_points.is_empty());
1581        assert!(restored.plugin_graph_json.is_none());
1582    }
1583}