Skip to main content

maolan_engine/
message.rs

1use crate::clap::{ClapParameterInfo, ClapPluginInfo};
2#[cfg(all(unix, not(target_os = "macos")))]
3use crate::lv2::Lv2PluginInfo;
4use crate::midi::io::MidiEvent;
5use crate::vst3::Vst3PluginInfo;
6use crate::{kind::Kind, modulator::Modulator, mutex::UnsafeMutex, track::Track};
7use std::sync::{Arc, atomic::AtomicBool};
8use tokio::sync::mpsc::Sender;
9
10#[derive(Clone, Debug, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
11pub struct TrackColor {
12    pub r: f32,
13    pub g: f32,
14    pub b: f32,
15    pub a: f32,
16}
17
18#[derive(Clone, Debug)]
19pub struct MidiNoteData {
20    pub start_sample: usize,
21    pub length_samples: usize,
22    pub pitch: u8,
23    pub velocity: u8,
24    pub channel: u8,
25}
26
27#[derive(Clone, Debug)]
28pub struct MidiControllerData {
29    pub sample: usize,
30    pub controller: u8,
31    pub value: u8,
32    pub channel: u8,
33}
34
35#[derive(Debug, Clone)]
36pub struct MidiRawEventData {
37    pub sample: usize,
38    pub data: Vec<u8>,
39}
40
41#[derive(Clone, Debug)]
42pub struct HwMidiEvent {
43    pub device: String,
44    pub event: MidiEvent,
45}
46
47#[derive(Clone, Debug)]
48pub struct OfflineAutomationPoint {
49    pub sample: usize,
50    pub value: f32,
51}
52
53#[derive(Clone, Debug)]
54pub enum OfflineAutomationTarget {
55    Volume,
56    Balance,
57    Mute,
58    #[cfg(all(unix, not(target_os = "macos")))]
59    Lv2Parameter {
60        instance_id: usize,
61        index: u32,
62        min: f32,
63        max: f32,
64    },
65    Vst3Parameter {
66        instance_id: usize,
67        param_id: u32,
68    },
69    ClapParameter {
70        instance_id: usize,
71        param_id: u32,
72        min: f64,
73        max: f64,
74    },
75}
76
77#[derive(Clone, Debug)]
78pub struct OfflineAutomationLane {
79    pub target: OfflineAutomationTarget,
80    pub points: Vec<OfflineAutomationPoint>,
81}
82
83#[derive(Clone, Debug)]
84pub struct OfflineBounceWork {
85    pub state: Arc<UnsafeMutex<crate::state::State>>,
86    pub track_name: String,
87    pub output_path: String,
88    pub start_sample: usize,
89    pub length_samples: usize,
90    pub tempo_bpm: f64,
91    pub tsig_num: u16,
92    pub tsig_denom: u16,
93    pub automation_lanes: Vec<OfflineAutomationLane>,
94    pub cancel: Arc<AtomicBool>,
95    pub apply_fader: bool,
96}
97
98#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
99pub struct PitchCorrectionPointData {
100    pub start_sample: usize,
101    pub length_samples: usize,
102    pub detected_midi_pitch: f32,
103    pub target_midi_pitch: f32,
104    pub clarity: f32,
105}
106
107#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
108pub struct AudioClipData {
109    pub name: String,
110    pub start: usize,
111    pub length: usize,
112    pub offset: usize,
113    pub input_channel: usize,
114    pub muted: bool,
115    pub peaks_file: Option<String>,
116    pub fade_enabled: bool,
117    pub fade_in_samples: usize,
118    pub fade_out_samples: usize,
119    pub preview_name: Option<String>,
120    pub source_name: Option<String>,
121    pub source_offset: Option<usize>,
122    pub source_length: Option<usize>,
123    pub pitch_correction_points: Vec<PitchCorrectionPointData>,
124    pub pitch_correction_frame_likeness: Option<f32>,
125    pub pitch_correction_inertia_ms: Option<u16>,
126    pub pitch_correction_formant_compensation: Option<bool>,
127    pub plugin_graph_json: Option<serde_json::Value>,
128    pub grouped_clips: Vec<AudioClipData>,
129}
130
131#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
132pub struct MidiClipData {
133    pub name: String,
134    pub start: usize,
135    pub length: usize,
136    pub offset: usize,
137    pub input_channel: usize,
138    pub muted: bool,
139    pub grouped_clips: Vec<MidiClipData>,
140}
141
142#[derive(Clone, Debug)]
143pub struct ClipMoveFrom {
144    pub track_name: String,
145    pub clip_index: usize,
146}
147
148#[derive(Clone, Debug)]
149pub struct ClipMoveTo {
150    pub track_name: String,
151    pub sample_offset: usize,
152    pub input_channel: usize,
153}
154
155#[derive(Clone, Debug, PartialEq, Eq, Hash)]
156pub enum PluginGraphNode {
157    TrackInput,
158    TrackOutput,
159    ClapPluginInstance(usize),
160    Vst3PluginInstance(usize),
161    #[cfg(all(unix, not(target_os = "macos")))]
162    Lv2PluginInstance(usize),
163}
164
165#[derive(Clone, Debug, PartialEq)]
166pub struct PluginGraphPlugin {
167    pub node: PluginGraphNode,
168    pub instance_id: usize,
169    pub format: String,
170    pub uri: String,
171    pub plugin_id: String,
172    pub name: String,
173    pub main_audio_inputs: usize,
174    pub main_audio_outputs: usize,
175    pub audio_inputs: usize,
176    pub audio_outputs: usize,
177    pub midi_inputs: usize,
178    pub midi_outputs: usize,
179    pub state: Option<serde_json::Value>,
180    pub bypassed: bool,
181}
182
183#[cfg(unix)]
184#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
185pub struct Lv2StatePortValue {
186    pub index: u32,
187    pub value: f32,
188}
189
190#[cfg(unix)]
191#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
192pub struct Lv2StateProperty {
193    pub key_uri: String,
194    pub type_uri: String,
195    pub flags: u32,
196    pub value: Vec<u8>,
197}
198
199#[cfg(unix)]
200#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
201pub struct Lv2PluginState {
202    pub port_values: Vec<Lv2StatePortValue>,
203    pub properties: Vec<Lv2StateProperty>,
204}
205
206#[cfg(all(unix, not(target_os = "macos")))]
207#[derive(Clone, Debug, PartialEq)]
208pub struct Lv2ControlPortInfo {
209    pub index: u32,
210    pub name: String,
211    pub min: f32,
212    pub max: f32,
213    pub value: f32,
214}
215
216#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct PluginGraphConnection {
218    pub from_node: PluginGraphNode,
219    pub from_port: usize,
220    pub to_node: PluginGraphNode,
221    pub to_port: usize,
222    pub kind: Kind,
223}
224
225pub type PluginGraphSnapshot = (Vec<PluginGraphPlugin>, Vec<PluginGraphConnection>);
226
227#[derive(Clone, Debug, PartialEq, Eq, Hash)]
228pub enum Vst3GraphNode {
229    TrackInput,
230    TrackOutput,
231    PluginInstance(usize),
232}
233
234#[derive(Clone, Debug)]
235pub struct Vst3GraphPlugin {
236    pub instance_id: usize,
237    pub name: String,
238    pub path: String,
239    pub audio_inputs: usize,
240    pub audio_outputs: usize,
241    pub parameters: Vec<crate::vst3::port::ParameterInfo>,
242}
243
244#[derive(Clone, Debug, PartialEq, Eq)]
245pub struct Vst3GraphConnection {
246    pub from_node: Vst3GraphNode,
247    pub from_port: usize,
248    pub to_node: Vst3GraphNode,
249    pub to_port: usize,
250    pub kind: Kind,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
254pub struct MidiLearnBinding {
255    pub device: Option<String>,
256    pub channel: u8,
257    pub cc: u8,
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
261pub enum TrackMidiLearnTarget {
262    Volume,
263    Balance,
264    Mute,
265    Solo,
266    Arm,
267    InputMonitor,
268    DiskMonitor,
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
272pub enum GlobalMidiLearnTarget {
273    PlayPause,
274    Stop,
275    RecordToggle,
276}
277
278#[derive(Clone, Debug)]
279pub enum Action {
280    Quit,
281    Log {
282        source: String,
283        message: String,
284    },
285    Play,
286    Pause,
287    Stop,
288    TransportPosition(usize),
289    TransportPositionAt {
290        sample: usize,
291        after_frames: usize,
292    },
293    JumpToEnd,
294    SetLoopEnabled(bool),
295    SetLoopRange(Option<(usize, usize)>),
296    SetPunchEnabled(bool),
297    SetPunchRange(Option<(usize, usize)>),
298    SetMetronomeEnabled(bool),
299    SetTempo(f64),
300    SetTimeSignature {
301        numerator: u16,
302        denominator: u16,
303    },
304    SetOscEnabled(bool),
305    SetClipPlaybackEnabled(bool),
306    SetRecordEnabled(bool),
307    SetModulators(Vec<Modulator>),
308    SetSessionPath(String),
309    BeginHistoryGroup,
310    EndHistoryGroup,
311    ApplyGroupedActions(Vec<Action>),
312    ClearHistory,
313    BeginSessionRestore,
314    EndSessionRestore,
315    AddTrack {
316        name: String,
317        audio_ins: usize,
318        midi_ins: usize,
319        audio_outs: usize,
320        midi_outs: usize,
321    },
322    TrackAddAudioInput(String),
323    TrackAddAudioOutput(String),
324    TrackRemoveAudioInput(String),
325    TrackRemoveAudioOutput(String),
326    AddClip {
327        name: String,
328        track_name: String,
329        start: usize,
330        length: usize,
331        offset: usize,
332        input_channel: usize,
333        muted: bool,
334        peaks_file: Option<String>,
335        kind: Kind,
336        fade_enabled: bool,
337        fade_in_samples: usize,
338        fade_out_samples: usize,
339        source_name: Option<String>,
340        source_offset: Option<usize>,
341        source_length: Option<usize>,
342        preview_name: Option<String>,
343        pitch_correction_points: Vec<PitchCorrectionPointData>,
344        pitch_correction_frame_likeness: Option<f32>,
345        pitch_correction_inertia_ms: Option<u16>,
346        pitch_correction_formant_compensation: Option<bool>,
347        plugin_graph_json: Option<serde_json::Value>,
348    },
349    AddGroupedClip {
350        track_name: String,
351        kind: Kind,
352        audio_clip: Option<AudioClipData>,
353        midi_clip: Option<MidiClipData>,
354    },
355    RemoveClip {
356        track_name: String,
357        kind: Kind,
358        clip_indices: Vec<usize>,
359    },
360    SetClipFade {
361        track_name: String,
362        clip_index: usize,
363        kind: Kind,
364        fade_enabled: bool,
365        fade_in_samples: usize,
366        fade_out_samples: usize,
367    },
368    SetClipBounds {
369        track_name: String,
370        clip_index: usize,
371        kind: Kind,
372        start: usize,
373        length: usize,
374        offset: usize,
375    },
376    SyncClipBounds {
377        track_name: String,
378        clip_index: usize,
379        kind: Kind,
380        start: usize,
381        length: usize,
382        offset: usize,
383    },
384    SetClipMuted {
385        track_name: String,
386        clip_index: usize,
387        kind: Kind,
388        muted: bool,
389    },
390    SetClipPluginGraphJson {
391        track_name: String,
392        clip_index: usize,
393        plugin_graph_json: Option<serde_json::Value>,
394    },
395    SetClipPitchCorrection {
396        track_name: String,
397        clip_index: usize,
398        preview_name: Option<String>,
399        source_name: Option<String>,
400        source_offset: Option<usize>,
401        source_length: Option<usize>,
402        pitch_correction_points: Vec<PitchCorrectionPointData>,
403        pitch_correction_frame_likeness: Option<f32>,
404        pitch_correction_inertia_ms: Option<u16>,
405        pitch_correction_formant_compensation: Option<bool>,
406    },
407    RenameClip {
408        track_name: String,
409        kind: Kind,
410        clip_index: usize,
411        new_name: String,
412    },
413    SetClipSourceName {
414        track_name: String,
415        kind: Kind,
416        clip_index: usize,
417        name: String,
418    },
419    RenameTrack {
420        old_name: String,
421        new_name: String,
422    },
423    RemoveTrack(String),
424    TrackLevel(String, f32),
425    TrackBalance(String, f32),
426    TrackAutomationLevel(String, f32),
427    TrackAutomationBalance(String, f32),
428    TrackAutomationMute(String, bool),
429    TrackMeters {
430        track_name: String,
431        output_db: Vec<f32>,
432    },
433    RequestMeterSnapshot,
434    MeterSnapshot {
435        hw_out_db: Arc<Vec<f32>>,
436        track_meters: Arc<Vec<(String, Vec<f32>)>>,
437    },
438    TrackToggleArm(String),
439    TrackToggleMute(String),
440    TrackTogglePhase(String),
441    TrackToggleSolo(String),
442    TrackToggleMaster(String),
443    TrackToggleInputMonitor(String),
444    TrackToggleDiskMonitor(String),
445    TrackSetColor {
446        track_name: String,
447        color: Option<TrackColor>,
448    },
449    TrackArmMidiLearn {
450        track_name: String,
451        target: TrackMidiLearnTarget,
452    },
453    GlobalArmMidiLearn {
454        target: GlobalMidiLearnTarget,
455    },
456    TrackSetMidiLearnBinding {
457        track_name: String,
458        target: TrackMidiLearnTarget,
459        binding: Option<MidiLearnBinding>,
460    },
461    SetGlobalMidiLearnBinding {
462        target: GlobalMidiLearnTarget,
463        binding: Option<MidiLearnBinding>,
464    },
465    TrackSetVcaMaster {
466        track_name: String,
467        master_track: Option<String>,
468    },
469    TrackSetFolder {
470        track_name: String,
471        is_folder: bool,
472    },
473    TrackSetParent {
474        track_name: String,
475        parent_name: Option<String>,
476    },
477    TrackToggleFolder {
478        track_name: String,
479    },
480    TrackSetMidiLaneChannel {
481        track_name: String,
482        lane: usize,
483        channel: Option<u8>,
484    },
485    TrackSetFrozen {
486        track_name: String,
487        frozen: bool,
488    },
489    TrackOfflineBounce {
490        track_name: String,
491        output_path: String,
492        start_sample: usize,
493        length_samples: usize,
494        automation_lanes: Vec<OfflineAutomationLane>,
495        apply_fader: bool,
496    },
497    TrackOfflineBounceCancel {
498        track_name: String,
499    },
500    TrackOfflineBounceCancelAll,
501    TrackOfflineBounceCanceled {
502        track_name: String,
503    },
504    TrackOfflineBounceProgress {
505        track_name: String,
506        progress: f32,
507        operation: Option<String>,
508    },
509    PianoKey {
510        track_name: String,
511        note: u8,
512        velocity: u8,
513        on: bool,
514    },
515    ModifyMidiNotes {
516        track_name: String,
517        clip_index: usize,
518        note_indices: Vec<usize>,
519        new_notes: Vec<MidiNoteData>,
520        old_notes: Vec<MidiNoteData>,
521    },
522    ModifyMidiControllers {
523        track_name: String,
524        clip_index: usize,
525        controller_indices: Vec<usize>,
526        new_controllers: Vec<MidiControllerData>,
527        old_controllers: Vec<MidiControllerData>,
528    },
529    DeleteMidiControllers {
530        track_name: String,
531        clip_index: usize,
532        controller_indices: Vec<usize>,
533        deleted_controllers: Vec<(usize, MidiControllerData)>,
534    },
535    InsertMidiControllers {
536        track_name: String,
537        clip_index: usize,
538        controllers: Vec<(usize, MidiControllerData)>,
539    },
540    DeleteMidiNotes {
541        track_name: String,
542        clip_index: usize,
543        note_indices: Vec<usize>,
544        deleted_notes: Vec<(usize, MidiNoteData)>,
545    },
546    InsertMidiNotes {
547        track_name: String,
548        clip_index: usize,
549        notes: Vec<(usize, MidiNoteData)>,
550    },
551    SetStepRecording(bool),
552    StepRecordMidiNote {
553        device: String,
554        channel: u8,
555        pitch: u8,
556        velocity: u8,
557    },
558    SetMidiSysExEvents {
559        track_name: String,
560        clip_index: usize,
561        new_sysex_events: Vec<MidiRawEventData>,
562        old_sysex_events: Vec<MidiRawEventData>,
563    },
564    TrackClearDefaultPassthrough {
565        track_name: String,
566    },
567    TrackClearPlugins {
568        track_name: String,
569    },
570    #[cfg(all(unix, not(target_os = "macos")))]
571    TrackSetLv2PluginState {
572        track_name: String,
573        instance_id: usize,
574        state: Vec<u8>,
575    },
576    #[cfg(all(unix, not(target_os = "macos")))]
577    ClipSetLv2PluginState {
578        track_name: String,
579        clip_idx: usize,
580        instance_id: usize,
581        state: Vec<u8>,
582    },
583    #[cfg(all(unix, not(target_os = "macos")))]
584    TrackGetLv2PluginControls {
585        track_name: String,
586        instance_id: usize,
587    },
588    #[cfg(all(unix, not(target_os = "macos")))]
589    ClipGetLv2PluginControls {
590        track_name: String,
591        clip_idx: usize,
592        instance_id: usize,
593    },
594    #[cfg(all(unix, not(target_os = "macos")))]
595    TrackLv2PluginControls {
596        track_name: String,
597        instance_id: usize,
598        controls: Vec<Lv2ControlPortInfo>,
599        instance_access_handle: Option<usize>,
600    },
601    #[cfg(all(unix, not(target_os = "macos")))]
602    ClipLv2PluginControls {
603        track_name: String,
604        clip_idx: usize,
605        instance_id: usize,
606        controls: Vec<Lv2ControlPortInfo>,
607        instance_access_handle: Option<usize>,
608    },
609    #[cfg(all(unix, not(target_os = "macos")))]
610    TrackGetLv2Midnam {
611        track_name: String,
612    },
613    #[cfg(all(unix, not(target_os = "macos")))]
614    TrackLv2Midnam {
615        track_name: String,
616        note_names: std::collections::HashMap<u8, String>,
617    },
618    TrackGetClapNoteNames {
619        track_name: String,
620    },
621    TrackClapNoteNames {
622        track_name: String,
623        note_names: std::collections::HashMap<u8, String>,
624    },
625    #[cfg(all(unix, not(target_os = "macos")))]
626    TrackSetLv2ControlValue {
627        track_name: String,
628        instance_id: usize,
629        index: u32,
630        value: f32,
631    },
632    #[cfg(all(unix, not(target_os = "macos")))]
633    ClipSetLv2ControlValue {
634        track_name: String,
635        clip_idx: usize,
636        instance_id: usize,
637        index: u32,
638        value: f32,
639    },
640    #[cfg(all(unix, not(target_os = "macos")))]
641    ClipLv2StateSnapshot {
642        track_name: String,
643        clip_idx: usize,
644        instance_id: usize,
645        state: Vec<u8>,
646    },
647    TrackGetPluginGraph {
648        track_name: String,
649    },
650    TrackPluginGraph {
651        track_name: String,
652        plugins: Vec<PluginGraphPlugin>,
653        connections: Vec<PluginGraphConnection>,
654    },
655    TrackConnectPluginAudio {
656        track_name: String,
657        from_node: PluginGraphNode,
658        from_port: usize,
659        to_node: PluginGraphNode,
660        to_port: usize,
661    },
662    TrackConnectPluginMidi {
663        track_name: String,
664        from_node: PluginGraphNode,
665        from_port: usize,
666        to_node: PluginGraphNode,
667        to_port: usize,
668    },
669    TrackDisconnectPluginAudio {
670        track_name: String,
671        from_node: PluginGraphNode,
672        from_port: usize,
673        to_node: PluginGraphNode,
674        to_port: usize,
675    },
676    TrackDisconnectPluginMidi {
677        track_name: String,
678        from_node: PluginGraphNode,
679        from_port: usize,
680        to_node: PluginGraphNode,
681        to_port: usize,
682    },
683    #[cfg(all(unix, not(target_os = "macos")))]
684    ListLv2Plugins,
685    #[cfg(all(unix, not(target_os = "macos")))]
686    Lv2Plugins(Vec<Lv2PluginInfo>),
687    ListVst3Plugins,
688    Vst3Plugins(Vec<Vst3PluginInfo>),
689    ListClapPlugins,
690    ListClapPluginsWithCapabilities,
691    ClapPlugins(Vec<ClapPluginInfo>),
692    TrackSetClapParameter {
693        track_name: String,
694        instance_id: usize,
695        param_id: u32,
696        value: f64,
697    },
698    ClipSetClapParameter {
699        track_name: String,
700        clip_idx: usize,
701        instance_id: usize,
702        param_id: u32,
703        value: f64,
704    },
705    TrackSetClapParameterAt {
706        track_name: String,
707        instance_id: usize,
708        param_id: u32,
709        value: f64,
710        frame: u32,
711    },
712    TrackBeginClapParameterEdit {
713        track_name: String,
714        instance_id: usize,
715        param_id: u32,
716        frame: u32,
717    },
718    TrackEndClapParameterEdit {
719        track_name: String,
720        instance_id: usize,
721        param_id: u32,
722        frame: u32,
723    },
724    TrackGetClapParameters {
725        track_name: String,
726        instance_id: usize,
727    },
728    TrackClapParameters {
729        track_name: String,
730        instance_id: usize,
731        parameters: Vec<ClapParameterInfo>,
732    },
733    TrackClapSnapshotState {
734        track_name: String,
735        instance_id: usize,
736    },
737    ClipClapSnapshotState {
738        track_name: String,
739        clip_idx: usize,
740        instance_id: usize,
741    },
742    TrackClapStateSnapshot {
743        track_name: String,
744        instance_id: usize,
745        plugin_path: String,
746        state: crate::clap::ClapPluginState,
747    },
748    ClipClapStateSnapshot {
749        track_name: String,
750        clip_idx: usize,
751        instance_id: usize,
752        plugin_path: String,
753        state: crate::clap::ClapPluginState,
754    },
755    TrackClapStateDirty {
756        track_name: String,
757        instance_id: usize,
758    },
759    ClipClapStateDirty {
760        track_name: String,
761        clip_idx: usize,
762        instance_id: usize,
763    },
764    TrackClapRestoreState {
765        track_name: String,
766        instance_id: usize,
767        state: crate::clap::ClapPluginState,
768    },
769    ClipClapRestoreState {
770        track_name: String,
771        clip_idx: usize,
772        instance_id: usize,
773        state: crate::clap::ClapPluginState,
774    },
775    TrackSnapshotAllClapStates {
776        track_name: String,
777    },
778    TrackSnapshotAllClapStatesDone {
779        track_name: String,
780    },
781    TrackLoadClapPlugin {
782        track_name: String,
783        plugin_path: String,
784        instance_id: Option<usize>,
785    },
786    TrackUnloadClapPlugin {
787        track_name: String,
788        plugin_path: String,
789    },
790    TrackUnloadClapPluginInstance {
791        track_name: String,
792        instance_id: usize,
793    },
794    TrackShowClapGui {
795        track_name: String,
796        instance_id: usize,
797    },
798    TrackLoadVst3Plugin {
799        track_name: String,
800        plugin_path: String,
801        instance_id: Option<usize>,
802    },
803    TrackUnloadVst3Plugin {
804        track_name: String,
805        plugin_path: String,
806    },
807    TrackUnloadVst3PluginInstance {
808        track_name: String,
809        instance_id: usize,
810    },
811    TrackShowVst3Gui {
812        track_name: String,
813        instance_id: usize,
814    },
815    #[cfg(all(unix, not(target_os = "macos")))]
816    TrackLoadLv2Plugin {
817        track_name: String,
818        plugin_uri: String,
819        instance_id: Option<usize>,
820    },
821    #[cfg(all(unix, not(target_os = "macos")))]
822    TrackUnloadLv2Plugin {
823        track_name: String,
824        plugin_uri: String,
825    },
826    #[cfg(all(unix, not(target_os = "macos")))]
827    TrackUnloadLv2PluginInstance {
828        track_name: String,
829        instance_id: usize,
830    },
831    TrackShowLv2Gui {
832        track_name: String,
833        instance_id: usize,
834    },
835    TrackSetPluginResourceDir {
836        track_name: String,
837        instance_id: usize,
838        format: String,
839        directory: String,
840    },
841    TrackClapFileReferences {
842        track_name: String,
843        instance_id: usize,
844        refs: Vec<(u32, String)>,
845    },
846    TrackUpdateClapFileReference {
847        track_name: String,
848        instance_id: usize,
849        index: u32,
850        path: String,
851    },
852    ClipSetPluginResourceDir {
853        track_name: String,
854        clip_idx: usize,
855        instance_id: usize,
856        format: String,
857        directory: String,
858    },
859    ClipClapFileReferences {
860        track_name: String,
861        clip_idx: usize,
862        instance_id: usize,
863        refs: Vec<(u32, String)>,
864    },
865    ClipUpdateClapFileReference {
866        track_name: String,
867        clip_idx: usize,
868        instance_id: usize,
869        index: u32,
870        path: String,
871    },
872    TrackGetVst3Graph {
873        track_name: String,
874    },
875    TrackVst3Graph {
876        track_name: String,
877        plugins: Vec<Vst3GraphPlugin>,
878        connections: Vec<Vst3GraphConnection>,
879    },
880    TrackSetVst3Parameter {
881        track_name: String,
882        instance_id: usize,
883        param_id: u32,
884        value: f32,
885    },
886    TrackSetPluginBypassed {
887        track_name: String,
888        instance_id: usize,
889        format: String,
890        bypassed: bool,
891    },
892    TrackGetVst3Parameters {
893        track_name: String,
894        instance_id: usize,
895    },
896    TrackVst3Parameters {
897        track_name: String,
898        instance_id: usize,
899        parameters: Vec<crate::vst3::port::ParameterInfo>,
900    },
901    TrackVst3SnapshotState {
902        track_name: String,
903        instance_id: usize,
904    },
905    ClipVst3SnapshotState {
906        track_name: String,
907        clip_idx: usize,
908        instance_id: usize,
909    },
910    TrackVst3StateSnapshot {
911        track_name: String,
912        instance_id: usize,
913        state: crate::vst3::state::Vst3PluginState,
914    },
915    ClipVst3StateSnapshot {
916        track_name: String,
917        clip_idx: usize,
918        instance_id: usize,
919        state: crate::vst3::state::Vst3PluginState,
920    },
921    TrackVst3RestoreState {
922        track_name: String,
923        instance_id: usize,
924        state: crate::vst3::state::Vst3PluginState,
925    },
926    TrackConnectVst3Audio {
927        track_name: String,
928        from_node: Vst3GraphNode,
929        from_port: usize,
930        to_node: Vst3GraphNode,
931        to_port: usize,
932    },
933    TrackDisconnectVst3Audio {
934        track_name: String,
935        from_node: Vst3GraphNode,
936        from_port: usize,
937        to_node: Vst3GraphNode,
938        to_port: usize,
939    },
940    ClipMove {
941        kind: Kind,
942        from: ClipMoveFrom,
943        to: ClipMoveTo,
944        copy: bool,
945    },
946    Connect {
947        from_track: String,
948        from_port: usize,
949        to_track: String,
950        to_port: usize,
951        kind: Kind,
952    },
953    Disconnect {
954        from_track: String,
955        from_port: usize,
956        to_track: String,
957        to_port: usize,
958        kind: Kind,
959    },
960    OpenAudioDevice {
961        device: String,
962        input_device: Option<String>,
963        sample_rate_hz: i32,
964        bits: i32,
965        exclusive: bool,
966        period_frames: usize,
967        realtime_frames: usize,
968        low_watermark_frames: usize,
969        nperiods: usize,
970        sync_mode: bool,
971        hybrid_enabled: bool,
972        actual_period_frames: usize,
973        input_channels: usize,
974        output_channels: usize,
975        bytes_per_frame: usize,
976    },
977    JackAddAudioInputPort,
978    JackRemoveAudioInputPort(usize),
979    JackAddAudioOutputPort,
980    JackRemoveAudioOutputPort(usize),
981    OpenMidiInputDevice(String),
982    OpenMidiOutputDevice(String),
983    RequestSessionDiagnostics,
984    RequestMidiLearnMappingsReport,
985    ClearAllMidiLearnBindings,
986    SessionDiagnosticsReport {
987        track_count: usize,
988        frozen_track_count: usize,
989        audio_clip_count: usize,
990        midi_clip_count: usize,
991        #[cfg(all(unix, not(target_os = "macos")))]
992        lv2_instance_count: usize,
993        vst3_instance_count: usize,
994        clap_instance_count: usize,
995        pending_requests: usize,
996        workers_total: usize,
997        workers_ready: usize,
998        pending_hw_midi_events: usize,
999        playing: bool,
1000        transport_sample: usize,
1001        tempo_bpm: f64,
1002        sample_rate_hz: usize,
1003        cycle_samples: usize,
1004    },
1005    MidiLearnMappingsReport {
1006        lines: Vec<String>,
1007    },
1008    HWInfo {
1009        channels: usize,
1010        rate: usize,
1011        input: bool,
1012    },
1013    MarkHistorySavePoint,
1014    HistoryState {
1015        dirty: bool,
1016    },
1017    Undo,
1018    Redo,
1019    Panic,
1020}
1021
1022#[derive(Clone, Debug)]
1023pub enum Message {
1024    Ready(usize),
1025    Finished {
1026        worker_id: usize,
1027        track_name: String,
1028        output_linear: Vec<f32>,
1029        process_epoch: usize,
1030        parameter_updates: Vec<Action>,
1031    },
1032    TracksFinished,
1033
1034    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
1035    ProcessOfflineBounce(OfflineBounceWork),
1036    Channel(Sender<Self>),
1037
1038    Request(Action),
1039    Response(Result<Action, String>),
1040    HWMidiEvents(Vec<HwMidiEvent>),
1041    HWMidiOutEvents(Vec<HwMidiEvent>),
1042    ClearHWMidiOutEvents,
1043    HWFinished,
1044    OfflineBounceFinished {
1045        result: Result<Action, String>,
1046    },
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1052    use serde_json::json;
1053
1054    #[test]
1055    fn audio_clip_data_serde_round_trips_nested_groups() {
1056        let clip = AudioClipData {
1057            name: "group.wav".to_string(),
1058            start: 12,
1059            length: 96,
1060            offset: 3,
1061            input_channel: 1,
1062            muted: true,
1063            peaks_file: Some("peaks/group.json".to_string()),
1064            fade_enabled: false,
1065            fade_in_samples: 10,
1066            fade_out_samples: 20,
1067            preview_name: Some("preview.wav".to_string()),
1068            source_name: Some("source.wav".to_string()),
1069            source_offset: Some(4),
1070            source_length: Some(88),
1071            pitch_correction_points: vec![PitchCorrectionPointData {
1072                start_sample: 7,
1073                length_samples: 11,
1074                detected_midi_pitch: 60.1,
1075                target_midi_pitch: 61.2,
1076                clarity: 0.8,
1077            }],
1078            pitch_correction_frame_likeness: Some(0.5),
1079            pitch_correction_inertia_ms: Some(123),
1080            pitch_correction_formant_compensation: Some(false),
1081            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1082            grouped_clips: vec![AudioClipData {
1083                name: "child.wav".to_string(),
1084                start: 0,
1085                length: 48,
1086                ..AudioClipData::default()
1087            }],
1088        };
1089
1090        let value = serde_json::to_value(&clip).expect("serialize");
1091        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1092
1093        assert_eq!(restored.name, clip.name);
1094        assert_eq!(restored.preview_name, clip.preview_name);
1095        assert_eq!(restored.source_name, clip.source_name);
1096        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1097        assert_eq!(restored.grouped_clips.len(), 1);
1098        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1099        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1100    }
1101
1102    #[test]
1103    fn midi_clip_data_serde_round_trips_nested_groups() {
1104        let clip = MidiClipData {
1105            name: "group.mid".to_string(),
1106            start: 5,
1107            length: 64,
1108            offset: 2,
1109            input_channel: 3,
1110            muted: true,
1111            grouped_clips: vec![MidiClipData {
1112                name: "child.mid".to_string(),
1113                start: 0,
1114                length: 32,
1115                ..MidiClipData::default()
1116            }],
1117        };
1118
1119        let value = serde_json::to_value(&clip).expect("serialize");
1120        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1121
1122        assert_eq!(restored.name, clip.name);
1123        assert_eq!(restored.grouped_clips.len(), 1);
1124        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1125    }
1126
1127    #[test]
1128    fn pitch_correction_point_data_serde_round_trips() {
1129        let point = PitchCorrectionPointData {
1130            start_sample: 10,
1131            length_samples: 20,
1132            detected_midi_pitch: 57.5,
1133            target_midi_pitch: 58.0,
1134            clarity: 0.9,
1135        };
1136
1137        let value = serde_json::to_value(&point).expect("serialize");
1138        let restored: PitchCorrectionPointData =
1139            serde_json::from_value(value).expect("deserialize");
1140
1141        assert_eq!(restored.start_sample, 10);
1142        assert_eq!(restored.length_samples, 20);
1143        assert_eq!(restored.detected_midi_pitch, 57.5);
1144        assert_eq!(restored.target_midi_pitch, 58.0);
1145        assert_eq!(restored.clarity, 0.9);
1146    }
1147
1148    #[test]
1149    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1150        let restored: AudioClipData = serde_json::from_value(json!({
1151            "name": "clip.wav",
1152            "start": 1,
1153            "length": 2,
1154            "offset": 3,
1155            "input_channel": 0,
1156            "muted": false,
1157            "fade_enabled": true,
1158            "fade_in_samples": 240,
1159            "fade_out_samples": 240,
1160            "pitch_correction_points": [],
1161            "grouped_clips": []
1162        }))
1163        .expect("deserialize");
1164
1165        assert_eq!(restored.name, "clip.wav");
1166        assert!(restored.peaks_file.is_none());
1167        assert!(restored.preview_name.is_none());
1168        assert!(restored.source_name.is_none());
1169        assert!(restored.source_offset.is_none());
1170        assert!(restored.source_length.is_none());
1171        assert!(restored.pitch_correction_points.is_empty());
1172        assert!(restored.plugin_graph_json.is_none());
1173    }
1174}