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