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        hw_out_lufs: Option<crate::loudness::LoudnessValues>,
705        track_meters: Arc<Vec<(String, Vec<f32>)>>,
706    },
707    RequestTrackList,
708    TrackList(Vec<String>),
709    RequestTransportState,
710    TransportState {
711        sample: usize,
712        tempo_bpm: f64,
713        playing: bool,
714        paused: bool,
715        tsig_num: u16,
716        tsig_denom: u16,
717    },
718    TrackToggleArm(String),
719    TrackToggleMute(String),
720    TrackTogglePhase(String),
721    TrackToggleSolo(String),
722    TrackToggleMaster(String),
723    TrackToggleInputMonitor {
724        track_name: String,
725        lane: usize,
726    },
727    TrackToggleDiskMonitor {
728        track_name: String,
729        lane: usize,
730    },
731    TrackToggleMidiInputMonitor {
732        track_name: String,
733        lane: usize,
734    },
735    TrackToggleMidiDiskMonitor {
736        track_name: String,
737        lane: usize,
738    },
739    TrackSetColor {
740        track_name: String,
741        color: Option<TrackColor>,
742    },
743    TrackArmMidiLearn {
744        track_name: String,
745        target: TrackMidiLearnTarget,
746    },
747    GlobalArmMidiLearn {
748        target: GlobalMidiLearnTarget,
749    },
750    SessionArmMidiLearn {
751        target: SessionMidiLearnTarget,
752    },
753    TrackSetMidiLearnBinding {
754        track_name: String,
755        target: TrackMidiLearnTarget,
756        binding: Option<MidiLearnBinding>,
757    },
758    SetGlobalMidiLearnBinding {
759        target: GlobalMidiLearnTarget,
760        binding: Option<MidiLearnBinding>,
761    },
762    SetSessionMidiLearnBinding {
763        target: SessionMidiLearnTarget,
764        binding: Option<MidiLearnBinding>,
765    },
766    SessionMidiLearnTriggered {
767        target: SessionMidiLearnTarget,
768    },
769    TrackSetFolder {
770        track_name: String,
771        is_folder: bool,
772    },
773    TrackSetParent {
774        track_name: String,
775        parent_name: Option<String>,
776    },
777    TrackToggleFolder {
778        track_name: String,
779    },
780    TrackSetMidiLaneChannel {
781        track_name: String,
782        lane: usize,
783        channel: Option<u8>,
784    },
785    TrackSetFrozen {
786        track_name: String,
787        frozen: bool,
788    },
789    TrackSetSessionSlot {
790        track_name: String,
791        scene_index: usize,
792        clip_id: Option<String>,
793    },
794    TrackSetSessionSlotPlayEnabled {
795        track_name: String,
796        scene_index: usize,
797        enabled: bool,
798    },
799    TrackSetSessionSlotStopEnabled {
800        track_name: String,
801        scene_index: usize,
802        enabled: bool,
803    },
804    TrackOfflineBounce {
805        track_name: String,
806        output_path: String,
807        start_sample: usize,
808        length_samples: usize,
809        automation_lanes: Vec<OfflineAutomationLane>,
810        apply_fader: bool,
811    },
812    TrackOfflineBounceCancel {
813        track_name: String,
814    },
815    TrackOfflineBounceCancelAll,
816    TrackOfflineBounceCanceled {
817        track_name: String,
818    },
819    TrackOfflineBounceProgress {
820        track_name: String,
821        progress: f32,
822        operation: Option<String>,
823    },
824    PianoKey {
825        track_name: String,
826        note: u8,
827        velocity: u8,
828        on: bool,
829    },
830    ModifyMidiNotes {
831        track_name: String,
832        clip_index: usize,
833        note_indices: Vec<usize>,
834        new_notes: Vec<MidiNoteData>,
835        old_notes: Vec<MidiNoteData>,
836    },
837    ModifyMidiControllers {
838        track_name: String,
839        clip_index: usize,
840        controller_indices: Vec<usize>,
841        new_controllers: Vec<MidiControllerData>,
842        old_controllers: Vec<MidiControllerData>,
843    },
844    DeleteMidiControllers {
845        track_name: String,
846        clip_index: usize,
847        controller_indices: Vec<usize>,
848        deleted_controllers: Vec<(usize, MidiControllerData)>,
849    },
850    InsertMidiControllers {
851        track_name: String,
852        clip_index: usize,
853        controllers: Vec<(usize, MidiControllerData)>,
854    },
855    DeleteMidiNotes {
856        track_name: String,
857        clip_index: usize,
858        note_indices: Vec<usize>,
859        deleted_notes: Vec<(usize, MidiNoteData)>,
860    },
861    InsertMidiNotes {
862        track_name: String,
863        clip_index: usize,
864        notes: Vec<(usize, MidiNoteData)>,
865    },
866    SetStepRecording(bool),
867    StepRecordMidiNote {
868        device: String,
869        channel: u8,
870        pitch: u8,
871        velocity: u8,
872    },
873    SetMidiSysExEvents {
874        track_name: String,
875        clip_index: usize,
876        new_sysex_events: Vec<MidiRawEventData>,
877        old_sysex_events: Vec<MidiRawEventData>,
878    },
879    TrackClearDefaultPassthrough {
880        track_name: String,
881    },
882    TrackClearPlugins {
883        track_name: String,
884    },
885    #[cfg(all(unix, not(target_os = "macos")))]
886    TrackSetLv2PluginState {
887        track_name: String,
888        instance_id: usize,
889        state: Vec<u8>,
890    },
891    #[cfg(all(unix, not(target_os = "macos")))]
892    ClipSetLv2PluginState {
893        track_name: String,
894        clip_idx: usize,
895        instance_id: usize,
896        state: Vec<u8>,
897    },
898    #[cfg(all(unix, not(target_os = "macos")))]
899    TrackLv2SnapshotState {
900        track_name: String,
901        instance_id: usize,
902    },
903    #[cfg(all(unix, not(target_os = "macos")))]
904    ClipLv2SnapshotState {
905        track_name: String,
906        clip_idx: usize,
907        instance_id: usize,
908    },
909    #[cfg(all(unix, not(target_os = "macos")))]
910    TrackLv2StateSnapshot {
911        track_name: String,
912        instance_id: usize,
913        state: Vec<u8>,
914    },
915    #[cfg(all(unix, not(target_os = "macos")))]
916    TrackGetLv2PluginControls {
917        track_name: String,
918        instance_id: usize,
919    },
920    #[cfg(all(unix, not(target_os = "macos")))]
921    ClipGetLv2PluginControls {
922        track_name: String,
923        clip_idx: usize,
924        instance_id: usize,
925    },
926    #[cfg(all(unix, not(target_os = "macos")))]
927    TrackLv2PluginControls {
928        track_name: String,
929        instance_id: usize,
930        controls: Vec<Lv2ControlPortInfo>,
931        instance_access_handle: Option<usize>,
932    },
933    #[cfg(all(unix, not(target_os = "macos")))]
934    ClipLv2PluginControls {
935        track_name: String,
936        clip_idx: usize,
937        instance_id: usize,
938        controls: Vec<Lv2ControlPortInfo>,
939        instance_access_handle: Option<usize>,
940    },
941    #[cfg(all(unix, not(target_os = "macos")))]
942    TrackGetLv2Midnam {
943        track_name: String,
944    },
945    #[cfg(all(unix, not(target_os = "macos")))]
946    TrackLv2Midnam {
947        track_name: String,
948        note_names: std::collections::HashMap<u8, String>,
949    },
950    TrackGetClapNoteNames {
951        track_name: String,
952    },
953    TrackClapNoteNames {
954        track_name: String,
955        note_names: std::collections::HashMap<u8, String>,
956    },
957    #[cfg(all(unix, not(target_os = "macos")))]
958    TrackSetLv2ControlValue {
959        track_name: String,
960        instance_id: usize,
961        index: u32,
962        value: f32,
963    },
964    #[cfg(all(unix, not(target_os = "macos")))]
965    ClipSetLv2ControlValue {
966        track_name: String,
967        clip_idx: usize,
968        instance_id: usize,
969        index: u32,
970        value: f32,
971    },
972    #[cfg(all(unix, not(target_os = "macos")))]
973    ClipLv2StateSnapshot {
974        track_name: String,
975        clip_idx: usize,
976        instance_id: usize,
977        state: Vec<u8>,
978    },
979    TrackGetPluginGraph {
980        track_name: String,
981        include_state: bool,
982    },
983    TrackPluginGraph {
984        track_name: String,
985        plugins: Vec<PluginGraphPlugin>,
986        connections: Vec<PluginGraphConnection>,
987        connectable_connections: Vec<ConnectableConnection>,
988    },
989    TrackConnectPluginAudio {
990        track_name: String,
991        from_node: PluginGraphNode,
992        from_port: usize,
993        to_node: PluginGraphNode,
994        to_port: usize,
995    },
996    TrackConnectPluginMidi {
997        track_name: String,
998        from_node: PluginGraphNode,
999        from_port: usize,
1000        to_node: PluginGraphNode,
1001        to_port: usize,
1002    },
1003    TrackDisconnectPluginAudio {
1004        track_name: String,
1005        from_node: PluginGraphNode,
1006        from_port: usize,
1007        to_node: PluginGraphNode,
1008        to_port: usize,
1009    },
1010    TrackDisconnectPluginMidi {
1011        track_name: String,
1012        from_node: PluginGraphNode,
1013        from_port: usize,
1014        to_node: PluginGraphNode,
1015        to_port: usize,
1016    },
1017    TrackConnectAudio {
1018        track_name: String,
1019        from: ConnectableRef,
1020        from_port: usize,
1021        to: ConnectableRef,
1022        to_port: usize,
1023    },
1024    TrackDisconnectAudio {
1025        track_name: String,
1026        from: ConnectableRef,
1027        from_port: usize,
1028        to: ConnectableRef,
1029        to_port: usize,
1030    },
1031    TrackConnectMidi {
1032        track_name: String,
1033        from: ConnectableRef,
1034        from_port: usize,
1035        to: ConnectableRef,
1036        to_port: usize,
1037    },
1038    TrackDisconnectMidi {
1039        track_name: String,
1040        from: ConnectableRef,
1041        from_port: usize,
1042        to: ConnectableRef,
1043        to_port: usize,
1044    },
1045    #[cfg(all(unix, not(target_os = "macos")))]
1046    ListLv2Plugins,
1047    #[cfg(all(unix, not(target_os = "macos")))]
1048    Lv2Plugins(Vec<Lv2PluginInfo>),
1049    #[cfg(all(unix, not(target_os = "macos")))]
1050    Lv2PluginsUnavailable {
1051        error: String,
1052    },
1053    ListVst3Plugins,
1054    Vst3Plugins(Vec<Vst3PluginInfo>),
1055    Vst3PluginsUnavailable {
1056        error: String,
1057    },
1058    ListClapPlugins,
1059    ListClapPluginsWithCapabilities,
1060    ClapPlugins(Vec<ClapPluginInfo>),
1061    ClapPluginsUnavailable {
1062        error: String,
1063    },
1064    TrackSetClapParameter {
1065        track_name: String,
1066        instance_id: usize,
1067        param_id: u32,
1068        value: f64,
1069    },
1070    ClipSetClapParameter {
1071        track_name: String,
1072        clip_idx: usize,
1073        instance_id: usize,
1074        param_id: u32,
1075        value: f64,
1076    },
1077    TrackSetClapParameterAt {
1078        track_name: String,
1079        instance_id: usize,
1080        param_id: u32,
1081        value: f64,
1082        frame: u32,
1083    },
1084    TrackBeginClapParameterEdit {
1085        track_name: String,
1086        instance_id: usize,
1087        param_id: u32,
1088        frame: u32,
1089    },
1090    TrackEndClapParameterEdit {
1091        track_name: String,
1092        instance_id: usize,
1093        param_id: u32,
1094        frame: u32,
1095    },
1096    TrackGetClapParameters {
1097        track_name: String,
1098        instance_id: usize,
1099    },
1100    TrackClapParameters {
1101        track_name: String,
1102        instance_id: usize,
1103        parameters: Vec<ClapParameterInfo>,
1104    },
1105    TrackClapSnapshotState {
1106        track_name: String,
1107        instance_id: usize,
1108    },
1109    ClipClapSnapshotState {
1110        track_name: String,
1111        clip_idx: usize,
1112        instance_id: usize,
1113    },
1114    TrackClapStateSnapshot {
1115        track_name: String,
1116        instance_id: usize,
1117        plugin_id: String,
1118        state: crate::clap::ClapPluginState,
1119    },
1120    ClipClapStateSnapshot {
1121        track_name: String,
1122        clip_idx: usize,
1123        instance_id: usize,
1124        plugin_id: String,
1125        state: crate::clap::ClapPluginState,
1126    },
1127    TrackClapStateDirty {
1128        track_name: String,
1129        instance_id: usize,
1130    },
1131    ClipClapStateDirty {
1132        track_name: String,
1133        clip_idx: usize,
1134        instance_id: usize,
1135    },
1136    TrackClapRestoreState {
1137        track_name: String,
1138        instance_id: usize,
1139        state: crate::clap::ClapPluginState,
1140    },
1141    ClipClapRestoreState {
1142        track_name: String,
1143        clip_idx: usize,
1144        instance_id: usize,
1145        state: crate::clap::ClapPluginState,
1146    },
1147    TrackSnapshotAllClapStates {
1148        track_name: String,
1149    },
1150    TrackSnapshotAllClapStatesDone {
1151        track_name: String,
1152    },
1153    TrackLoadClapPlugin {
1154        track_name: String,
1155        plugin_id: String,
1156        instance_id: Option<usize>,
1157    },
1158    TrackUnloadClapPlugin {
1159        track_name: String,
1160        plugin_id: String,
1161    },
1162    TrackUnloadClapPluginInstance {
1163        track_name: String,
1164        instance_id: usize,
1165    },
1166    TrackShowClapGui {
1167        track_name: String,
1168        instance_id: usize,
1169    },
1170    TrackLoadVst3Plugin {
1171        track_name: String,
1172        plugin_id: String,
1173        instance_id: Option<usize>,
1174    },
1175    TrackUnloadVst3Plugin {
1176        track_name: String,
1177        plugin_id: String,
1178    },
1179    TrackUnloadVst3PluginInstance {
1180        track_name: String,
1181        instance_id: usize,
1182    },
1183    TrackShowVst3Gui {
1184        track_name: String,
1185        instance_id: usize,
1186    },
1187    #[cfg(all(unix, not(target_os = "macos")))]
1188    TrackLoadLv2Plugin {
1189        track_name: String,
1190        plugin_uri: String,
1191        instance_id: Option<usize>,
1192    },
1193    #[cfg(all(unix, not(target_os = "macos")))]
1194    TrackUnloadLv2Plugin {
1195        track_name: String,
1196        plugin_uri: String,
1197    },
1198    #[cfg(all(unix, not(target_os = "macos")))]
1199    TrackUnloadLv2PluginInstance {
1200        track_name: String,
1201        instance_id: usize,
1202    },
1203    TrackShowLv2Gui {
1204        track_name: String,
1205        instance_id: usize,
1206    },
1207    TrackSetPluginResourceDir {
1208        track_name: String,
1209        instance_id: usize,
1210        format: String,
1211        directory: String,
1212    },
1213    TrackClapFileReferences {
1214        track_name: String,
1215        instance_id: usize,
1216        refs: Vec<(u32, String)>,
1217    },
1218    TrackUpdateClapFileReference {
1219        track_name: String,
1220        instance_id: usize,
1221        index: u32,
1222        path: String,
1223    },
1224    ClipSetPluginResourceDir {
1225        track_name: String,
1226        clip_idx: usize,
1227        instance_id: usize,
1228        format: String,
1229        directory: String,
1230    },
1231    ClipClapFileReferences {
1232        track_name: String,
1233        clip_idx: usize,
1234        instance_id: usize,
1235        refs: Vec<(u32, String)>,
1236    },
1237    ClipUpdateClapFileReference {
1238        track_name: String,
1239        clip_idx: usize,
1240        instance_id: usize,
1241        index: u32,
1242        path: String,
1243    },
1244    TrackGetVst3Graph {
1245        track_name: String,
1246    },
1247    TrackVst3Graph {
1248        track_name: String,
1249        plugins: Vec<Vst3GraphPlugin>,
1250        connections: Vec<Vst3GraphConnection>,
1251    },
1252    TrackSetVst3Parameter {
1253        track_name: String,
1254        instance_id: usize,
1255        param_id: u32,
1256        value: f32,
1257    },
1258    TrackSetPluginBypassed {
1259        track_name: String,
1260        instance_id: usize,
1261        format: String,
1262        bypassed: bool,
1263    },
1264    TrackGetVst3Parameters {
1265        track_name: String,
1266        instance_id: usize,
1267    },
1268    TrackVst3Parameters {
1269        track_name: String,
1270        instance_id: usize,
1271        parameters: Vec<crate::vst3::port::ParameterInfo>,
1272    },
1273    TrackVst3SnapshotState {
1274        track_name: String,
1275        instance_id: usize,
1276    },
1277    ClipVst3SnapshotState {
1278        track_name: String,
1279        clip_idx: usize,
1280        instance_id: usize,
1281    },
1282    TrackVst3StateSnapshot {
1283        track_name: String,
1284        instance_id: usize,
1285        state: crate::vst3::state::Vst3PluginState,
1286    },
1287    ClipVst3StateSnapshot {
1288        track_name: String,
1289        clip_idx: usize,
1290        instance_id: usize,
1291        state: crate::vst3::state::Vst3PluginState,
1292    },
1293    TrackVst3RestoreState {
1294        track_name: String,
1295        instance_id: usize,
1296        state: crate::vst3::state::Vst3PluginState,
1297    },
1298    ClipVst3RestoreState {
1299        track_name: String,
1300        clip_idx: usize,
1301        instance_id: usize,
1302        state: crate::vst3::state::Vst3PluginState,
1303    },
1304    TrackConnectVst3Audio {
1305        track_name: String,
1306        from_node: Vst3GraphNode,
1307        from_port: usize,
1308        to_node: Vst3GraphNode,
1309        to_port: usize,
1310    },
1311    TrackDisconnectVst3Audio {
1312        track_name: String,
1313        from_node: Vst3GraphNode,
1314        from_port: usize,
1315        to_node: Vst3GraphNode,
1316        to_port: usize,
1317    },
1318    ClipMove {
1319        kind: Kind,
1320        from: ClipMoveFrom,
1321        to: ClipMoveTo,
1322        copy: bool,
1323    },
1324    Connect {
1325        from_track: String,
1326        from_port: usize,
1327        to_track: String,
1328        to_port: usize,
1329        kind: Kind,
1330    },
1331    Disconnect {
1332        from_track: String,
1333        from_port: usize,
1334        to_track: String,
1335        to_port: usize,
1336        kind: Kind,
1337    },
1338    OpenAudioDevice {
1339        device: String,
1340        input_device: Option<String>,
1341        sample_rate_hz: i32,
1342        bits: i32,
1343        exclusive: bool,
1344        period_frames: usize,
1345        nperiods: usize,
1346        sync_mode: bool,
1347        actual_period_frames: usize,
1348        input_channels: usize,
1349        output_channels: usize,
1350        bytes_per_frame: usize,
1351    },
1352    JackAddAudioInputPort,
1353    JackRemoveAudioInputPort(usize),
1354    JackAddAudioOutputPort,
1355    JackRemoveAudioOutputPort(usize),
1356    JackGetGraph,
1357    JackGraph(JackGraphInfo),
1358    JackConnect {
1359        source: String,
1360        destination: String,
1361    },
1362    JackDisconnect {
1363        source: String,
1364        destination: String,
1365    },
1366    OpenMidiInputDevice(String),
1367    OpenMidiOutputDevice(String),
1368    RequestSessionDiagnostics,
1369    RequestMidiLearnMappingsReport,
1370    ClearAllMidiLearnBindings,
1371    SessionDiagnosticsReport {
1372        track_count: usize,
1373        frozen_track_count: usize,
1374        audio_clip_count: usize,
1375        midi_clip_count: usize,
1376        #[cfg(all(unix, not(target_os = "macos")))]
1377        lv2_instance_count: usize,
1378        vst3_instance_count: usize,
1379        clap_instance_count: usize,
1380        pending_requests: usize,
1381        workers_total: usize,
1382        workers_ready: usize,
1383        pending_hw_midi_events: usize,
1384        playing: bool,
1385        transport_running: bool,
1386        transport_sample: usize,
1387        tempo_bpm: f64,
1388        sample_rate_hz: usize,
1389        cycle_samples: usize,
1390    },
1391    MidiLearnMappingsReport {
1392        lines: Vec<String>,
1393    },
1394    HWInfo {
1395        channels: usize,
1396        rate: usize,
1397        input: bool,
1398    },
1399    MarkHistorySavePoint,
1400    HistoryState {
1401        dirty: bool,
1402    },
1403    Undo,
1404    Redo,
1405    Session(SessionAction),
1406    SessionRuntimeReport {
1407        track_name: String,
1408        scene_index: usize,
1409        state: SessionSlotState,
1410        play_position_samples: usize,
1411        elapsed_samples: usize,
1412    },
1413    Panic,
1414}
1415
1416#[derive(Clone, Debug)]
1417pub enum Message {
1418    Ready(usize),
1419    TracksFinished,
1420
1421    /// Dispatch a render-plan node to a worker (Phase 2, see `LOCKLESS.md`).
1422    NodeJob(crate::executor::NodeJob),
1423    /// A worker completed a plan node. Stale `epoch`s are dropped.
1424    NodeDone {
1425        worker_id: usize,
1426        epoch: u64,
1427        node: u32,
1428        output_linear: Vec<f32>,
1429        parameter_updates: Vec<Action>,
1430        latency_changed: bool,
1431    },
1432    ProcessOfflineBounce(OfflineBounceWork),
1433    Channel(Sender<Self>),
1434
1435    Request(Action),
1436    OscRequest {
1437        action: Action,
1438        reply_to: SocketAddr,
1439    },
1440    Response(Result<Action, String>),
1441    HWMidiEvents(Vec<HwMidiEvent>),
1442    HWMidiOutEvents(Vec<HwMidiEvent>),
1443    ClearHWMidiOutEvents,
1444    HWSetPlaying(bool),
1445    HWSetOutputGainBalance {
1446        gain: f32,
1447        balance: f32,
1448    },
1449    HWOpenMidiInputDevice(String),
1450    HWOpenMidiOutputDevice(String),
1451    HWCloseMidiDevices,
1452    HWFinished,
1453    OfflineBounceFinished {
1454        result: Result<Action, String>,
1455    },
1456}
1457
1458#[cfg(test)]
1459mod tests {
1460    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1461    use serde_json::json;
1462
1463    #[test]
1464    fn audio_clip_data_serde_round_trips_nested_groups() {
1465        let clip = AudioClipData {
1466            id: String::new(),
1467            name: "group.wav".to_string(),
1468            start: 12,
1469            length: 96,
1470            offset: 3,
1471            input_channel: 1,
1472            muted: true,
1473            peaks_file: Some("peaks/group.json".to_string()),
1474            fade_enabled: false,
1475            fade_in_samples: 10,
1476            fade_out_samples: 20,
1477            preview_name: Some("preview.wav".to_string()),
1478            source_name: Some("source.wav".to_string()),
1479            source_offset: Some(4),
1480            source_length: Some(88),
1481            pitch_correction_points: vec![PitchCorrectionPointData {
1482                start_sample: 7,
1483                length_samples: 11,
1484                detected_midi_pitch: 60.1,
1485                target_midi_pitch: 61.2,
1486                clarity: 0.8,
1487            }],
1488            pitch_correction_frame_likeness: Some(0.5),
1489            pitch_correction_inertia_ms: Some(123),
1490            pitch_correction_formant_compensation: Some(false),
1491            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1492            grouped_clips: vec![AudioClipData {
1493                name: "child.wav".to_string(),
1494                start: 0,
1495                length: 48,
1496                ..AudioClipData::default()
1497            }],
1498        };
1499
1500        let value = serde_json::to_value(&clip).expect("serialize");
1501        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1502
1503        assert_eq!(restored.name, clip.name);
1504        assert_eq!(restored.preview_name, clip.preview_name);
1505        assert_eq!(restored.source_name, clip.source_name);
1506        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1507        assert_eq!(restored.grouped_clips.len(), 1);
1508        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1509        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1510    }
1511
1512    #[test]
1513    fn midi_clip_data_serde_round_trips_nested_groups() {
1514        let clip = MidiClipData {
1515            id: String::new(),
1516            name: "group.mid".to_string(),
1517            start: 5,
1518            length: 64,
1519            offset: 2,
1520            input_channel: 3,
1521            muted: true,
1522            grouped_clips: vec![MidiClipData {
1523                name: "child.mid".to_string(),
1524                start: 0,
1525                length: 32,
1526                ..MidiClipData::default()
1527            }],
1528        };
1529
1530        let value = serde_json::to_value(&clip).expect("serialize");
1531        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1532
1533        assert_eq!(restored.name, clip.name);
1534        assert_eq!(restored.grouped_clips.len(), 1);
1535        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1536    }
1537
1538    #[test]
1539    fn pitch_correction_point_data_serde_round_trips() {
1540        let point = PitchCorrectionPointData {
1541            start_sample: 10,
1542            length_samples: 20,
1543            detected_midi_pitch: 57.5,
1544            target_midi_pitch: 58.0,
1545            clarity: 0.9,
1546        };
1547
1548        let value = serde_json::to_value(&point).expect("serialize");
1549        let restored: PitchCorrectionPointData =
1550            serde_json::from_value(value).expect("deserialize");
1551
1552        assert_eq!(restored.start_sample, 10);
1553        assert_eq!(restored.length_samples, 20);
1554        assert_eq!(restored.detected_midi_pitch, 57.5);
1555        assert_eq!(restored.target_midi_pitch, 58.0);
1556        assert_eq!(restored.clarity, 0.9);
1557    }
1558
1559    #[test]
1560    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1561        let restored: AudioClipData = serde_json::from_value(json!({
1562            "name": "clip.wav",
1563            "start": 1,
1564            "length": 2,
1565            "offset": 3,
1566            "input_channel": 0,
1567            "muted": false,
1568            "fade_enabled": true,
1569            "fade_in_samples": 240,
1570            "fade_out_samples": 240,
1571            "pitch_correction_points": [],
1572            "grouped_clips": []
1573        }))
1574        .expect("deserialize");
1575
1576        assert_eq!(restored.name, "clip.wav");
1577        assert!(restored.peaks_file.is_none());
1578        assert!(restored.preview_name.is_none());
1579        assert!(restored.source_name.is_none());
1580        assert!(restored.source_offset.is_none());
1581        assert!(restored.source_length.is_none());
1582        assert!(restored.pitch_correction_points.is_empty());
1583        assert!(restored.plugin_graph_json.is_none());
1584    }
1585}