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, 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    Play,
282    Pause,
283    Stop,
284    TransportPosition(usize),
285    TransportPositionAt {
286        sample: usize,
287        after_frames: usize,
288    },
289    JumpToEnd,
290    SetLoopEnabled(bool),
291    SetLoopRange(Option<(usize, usize)>),
292    SetPunchEnabled(bool),
293    SetPunchRange(Option<(usize, usize)>),
294    SetMetronomeEnabled(bool),
295    SetTempo(f64),
296    SetTimeSignature {
297        numerator: u16,
298        denominator: u16,
299    },
300    SetOscEnabled(bool),
301    SetClipPlaybackEnabled(bool),
302    SetRecordEnabled(bool),
303    SetSessionPath(String),
304    BeginHistoryGroup,
305    EndHistoryGroup,
306    ApplyGroupedActions(Vec<Action>),
307    ClearHistory,
308    BeginSessionRestore,
309    EndSessionRestore,
310    AddTrack {
311        name: String,
312        audio_ins: usize,
313        midi_ins: usize,
314        audio_outs: usize,
315        midi_outs: usize,
316    },
317    TrackAddAudioInput(String),
318    TrackAddAudioOutput(String),
319    TrackRemoveAudioInput(String),
320    TrackRemoveAudioOutput(String),
321    AddClip {
322        name: String,
323        track_name: String,
324        start: usize,
325        length: usize,
326        offset: usize,
327        input_channel: usize,
328        muted: bool,
329        peaks_file: Option<String>,
330        kind: Kind,
331        fade_enabled: bool,
332        fade_in_samples: usize,
333        fade_out_samples: usize,
334        source_name: Option<String>,
335        source_offset: Option<usize>,
336        source_length: Option<usize>,
337        preview_name: Option<String>,
338        pitch_correction_points: Vec<PitchCorrectionPointData>,
339        pitch_correction_frame_likeness: Option<f32>,
340        pitch_correction_inertia_ms: Option<u16>,
341        pitch_correction_formant_compensation: Option<bool>,
342        plugin_graph_json: Option<serde_json::Value>,
343    },
344    AddGroupedClip {
345        track_name: String,
346        kind: Kind,
347        audio_clip: Option<AudioClipData>,
348        midi_clip: Option<MidiClipData>,
349    },
350    RemoveClip {
351        track_name: String,
352        kind: Kind,
353        clip_indices: Vec<usize>,
354    },
355    SetClipFade {
356        track_name: String,
357        clip_index: usize,
358        kind: Kind,
359        fade_enabled: bool,
360        fade_in_samples: usize,
361        fade_out_samples: usize,
362    },
363    SetClipBounds {
364        track_name: String,
365        clip_index: usize,
366        kind: Kind,
367        start: usize,
368        length: usize,
369        offset: usize,
370    },
371    SyncClipBounds {
372        track_name: String,
373        clip_index: usize,
374        kind: Kind,
375        start: usize,
376        length: usize,
377        offset: usize,
378    },
379    SetClipMuted {
380        track_name: String,
381        clip_index: usize,
382        kind: Kind,
383        muted: bool,
384    },
385    SetClipPluginGraphJson {
386        track_name: String,
387        clip_index: usize,
388        plugin_graph_json: Option<serde_json::Value>,
389    },
390    SetClipPitchCorrection {
391        track_name: String,
392        clip_index: usize,
393        preview_name: Option<String>,
394        source_name: Option<String>,
395        source_offset: Option<usize>,
396        source_length: Option<usize>,
397        pitch_correction_points: Vec<PitchCorrectionPointData>,
398        pitch_correction_frame_likeness: Option<f32>,
399        pitch_correction_inertia_ms: Option<u16>,
400        pitch_correction_formant_compensation: Option<bool>,
401    },
402    RenameClip {
403        track_name: String,
404        kind: Kind,
405        clip_index: usize,
406        new_name: String,
407    },
408    SetClipSourceName {
409        track_name: String,
410        kind: Kind,
411        clip_index: usize,
412        name: String,
413    },
414    RenameTrack {
415        old_name: String,
416        new_name: String,
417    },
418    RemoveTrack(String),
419    TrackLevel(String, f32),
420    TrackBalance(String, f32),
421    TrackAutomationLevel(String, f32),
422    TrackAutomationBalance(String, f32),
423    TrackAutomationMute(String, bool),
424    TrackMeters {
425        track_name: String,
426        output_db: Vec<f32>,
427    },
428    RequestMeterSnapshot,
429    MeterSnapshot {
430        hw_out_db: Arc<Vec<f32>>,
431        track_meters: Arc<Vec<(String, Vec<f32>)>>,
432    },
433    TrackToggleArm(String),
434    TrackToggleMute(String),
435    TrackTogglePhase(String),
436    TrackToggleSolo(String),
437    TrackToggleMaster(String),
438    TrackToggleInputMonitor(String),
439    TrackToggleDiskMonitor(String),
440    TrackSetColor {
441        track_name: String,
442        color: Option<TrackColor>,
443    },
444    TrackArmMidiLearn {
445        track_name: String,
446        target: TrackMidiLearnTarget,
447    },
448    GlobalArmMidiLearn {
449        target: GlobalMidiLearnTarget,
450    },
451    TrackSetMidiLearnBinding {
452        track_name: String,
453        target: TrackMidiLearnTarget,
454        binding: Option<MidiLearnBinding>,
455    },
456    SetGlobalMidiLearnBinding {
457        target: GlobalMidiLearnTarget,
458        binding: Option<MidiLearnBinding>,
459    },
460    TrackSetVcaMaster {
461        track_name: String,
462        master_track: Option<String>,
463    },
464    TrackSetFolder {
465        track_name: String,
466        is_folder: bool,
467    },
468    TrackSetParent {
469        track_name: String,
470        parent_name: Option<String>,
471    },
472    TrackToggleFolder {
473        track_name: String,
474    },
475    TrackSetMidiLaneChannel {
476        track_name: String,
477        lane: usize,
478        channel: Option<u8>,
479    },
480    TrackSetFrozen {
481        track_name: String,
482        frozen: bool,
483    },
484    TrackOfflineBounce {
485        track_name: String,
486        output_path: String,
487        start_sample: usize,
488        length_samples: usize,
489        automation_lanes: Vec<OfflineAutomationLane>,
490        apply_fader: bool,
491    },
492    TrackOfflineBounceCancel {
493        track_name: String,
494    },
495    TrackOfflineBounceCancelAll,
496    TrackOfflineBounceCanceled {
497        track_name: String,
498    },
499    TrackOfflineBounceProgress {
500        track_name: String,
501        progress: f32,
502        operation: Option<String>,
503    },
504    PianoKey {
505        track_name: String,
506        note: u8,
507        velocity: u8,
508        on: bool,
509    },
510    ModifyMidiNotes {
511        track_name: String,
512        clip_index: usize,
513        note_indices: Vec<usize>,
514        new_notes: Vec<MidiNoteData>,
515        old_notes: Vec<MidiNoteData>,
516    },
517    ModifyMidiControllers {
518        track_name: String,
519        clip_index: usize,
520        controller_indices: Vec<usize>,
521        new_controllers: Vec<MidiControllerData>,
522        old_controllers: Vec<MidiControllerData>,
523    },
524    DeleteMidiControllers {
525        track_name: String,
526        clip_index: usize,
527        controller_indices: Vec<usize>,
528        deleted_controllers: Vec<(usize, MidiControllerData)>,
529    },
530    InsertMidiControllers {
531        track_name: String,
532        clip_index: usize,
533        controllers: Vec<(usize, MidiControllerData)>,
534    },
535    DeleteMidiNotes {
536        track_name: String,
537        clip_index: usize,
538        note_indices: Vec<usize>,
539        deleted_notes: Vec<(usize, MidiNoteData)>,
540    },
541    InsertMidiNotes {
542        track_name: String,
543        clip_index: usize,
544        notes: Vec<(usize, MidiNoteData)>,
545    },
546    SetMidiSysExEvents {
547        track_name: String,
548        clip_index: usize,
549        new_sysex_events: Vec<MidiRawEventData>,
550        old_sysex_events: Vec<MidiRawEventData>,
551    },
552    TrackClearDefaultPassthrough {
553        track_name: String,
554    },
555    #[cfg(all(unix, not(target_os = "macos")))]
556    TrackSetLv2PluginState {
557        track_name: String,
558        instance_id: usize,
559        state: Vec<u8>,
560    },
561    #[cfg(all(unix, not(target_os = "macos")))]
562    ClipSetLv2PluginState {
563        track_name: String,
564        clip_idx: usize,
565        instance_id: usize,
566        state: Vec<u8>,
567    },
568    #[cfg(all(unix, not(target_os = "macos")))]
569    TrackGetLv2PluginControls {
570        track_name: String,
571        instance_id: usize,
572    },
573    #[cfg(all(unix, not(target_os = "macos")))]
574    ClipGetLv2PluginControls {
575        track_name: String,
576        clip_idx: usize,
577        instance_id: usize,
578    },
579    #[cfg(all(unix, not(target_os = "macos")))]
580    TrackLv2PluginControls {
581        track_name: String,
582        instance_id: usize,
583        controls: Vec<Lv2ControlPortInfo>,
584        instance_access_handle: Option<usize>,
585    },
586    #[cfg(all(unix, not(target_os = "macos")))]
587    ClipLv2PluginControls {
588        track_name: String,
589        clip_idx: usize,
590        instance_id: usize,
591        controls: Vec<Lv2ControlPortInfo>,
592        instance_access_handle: Option<usize>,
593    },
594    #[cfg(all(unix, not(target_os = "macos")))]
595    TrackGetLv2Midnam {
596        track_name: String,
597    },
598    #[cfg(all(unix, not(target_os = "macos")))]
599    TrackLv2Midnam {
600        track_name: String,
601        note_names: std::collections::HashMap<u8, String>,
602    },
603    TrackGetClapNoteNames {
604        track_name: String,
605    },
606    TrackClapNoteNames {
607        track_name: String,
608        note_names: std::collections::HashMap<u8, String>,
609    },
610    #[cfg(all(unix, not(target_os = "macos")))]
611    TrackSetLv2ControlValue {
612        track_name: String,
613        instance_id: usize,
614        index: u32,
615        value: f32,
616    },
617    #[cfg(all(unix, not(target_os = "macos")))]
618    ClipSetLv2ControlValue {
619        track_name: String,
620        clip_idx: usize,
621        instance_id: usize,
622        index: u32,
623        value: f32,
624    },
625    #[cfg(all(unix, not(target_os = "macos")))]
626    ClipLv2StateSnapshot {
627        track_name: String,
628        clip_idx: usize,
629        instance_id: usize,
630        state: Vec<u8>,
631    },
632    TrackGetPluginGraph {
633        track_name: String,
634    },
635    TrackPluginGraph {
636        track_name: String,
637        plugins: Vec<PluginGraphPlugin>,
638        connections: Vec<PluginGraphConnection>,
639    },
640    TrackConnectPluginAudio {
641        track_name: String,
642        from_node: PluginGraphNode,
643        from_port: usize,
644        to_node: PluginGraphNode,
645        to_port: usize,
646    },
647    TrackConnectPluginMidi {
648        track_name: String,
649        from_node: PluginGraphNode,
650        from_port: usize,
651        to_node: PluginGraphNode,
652        to_port: usize,
653    },
654    TrackDisconnectPluginAudio {
655        track_name: String,
656        from_node: PluginGraphNode,
657        from_port: usize,
658        to_node: PluginGraphNode,
659        to_port: usize,
660    },
661    TrackDisconnectPluginMidi {
662        track_name: String,
663        from_node: PluginGraphNode,
664        from_port: usize,
665        to_node: PluginGraphNode,
666        to_port: usize,
667    },
668    #[cfg(all(unix, not(target_os = "macos")))]
669    ListLv2Plugins,
670    #[cfg(all(unix, not(target_os = "macos")))]
671    Lv2Plugins(Vec<Lv2PluginInfo>),
672    ListVst3Plugins,
673    Vst3Plugins(Vec<Vst3PluginInfo>),
674    ListClapPlugins,
675    ListClapPluginsWithCapabilities,
676    ClapPlugins(Vec<ClapPluginInfo>),
677    TrackSetClapParameter {
678        track_name: String,
679        instance_id: usize,
680        param_id: u32,
681        value: f64,
682    },
683    ClipSetClapParameter {
684        track_name: String,
685        clip_idx: usize,
686        instance_id: usize,
687        param_id: u32,
688        value: f64,
689    },
690    TrackSetClapParameterAt {
691        track_name: String,
692        instance_id: usize,
693        param_id: u32,
694        value: f64,
695        frame: u32,
696    },
697    TrackBeginClapParameterEdit {
698        track_name: String,
699        instance_id: usize,
700        param_id: u32,
701        frame: u32,
702    },
703    TrackEndClapParameterEdit {
704        track_name: String,
705        instance_id: usize,
706        param_id: u32,
707        frame: u32,
708    },
709    TrackGetClapParameters {
710        track_name: String,
711        instance_id: usize,
712    },
713    TrackClapParameters {
714        track_name: String,
715        instance_id: usize,
716        parameters: Vec<ClapParameterInfo>,
717    },
718    TrackClapSnapshotState {
719        track_name: String,
720        instance_id: usize,
721    },
722    ClipClapSnapshotState {
723        track_name: String,
724        clip_idx: usize,
725        instance_id: usize,
726    },
727    TrackClapStateSnapshot {
728        track_name: String,
729        instance_id: usize,
730        plugin_path: String,
731        state: crate::clap::ClapPluginState,
732    },
733    ClipClapStateSnapshot {
734        track_name: String,
735        clip_idx: usize,
736        instance_id: usize,
737        plugin_path: String,
738        state: crate::clap::ClapPluginState,
739    },
740    TrackClapRestoreState {
741        track_name: String,
742        instance_id: usize,
743        state: crate::clap::ClapPluginState,
744    },
745    ClipClapRestoreState {
746        track_name: String,
747        clip_idx: usize,
748        instance_id: usize,
749        state: crate::clap::ClapPluginState,
750    },
751    TrackSnapshotAllClapStates {
752        track_name: String,
753    },
754    TrackSnapshotAllClapStatesDone {
755        track_name: String,
756    },
757    TrackLoadClapPlugin {
758        track_name: String,
759        plugin_path: String,
760        instance_id: Option<usize>,
761    },
762    TrackUnloadClapPlugin {
763        track_name: String,
764        plugin_path: String,
765    },
766    TrackUnloadClapPluginInstance {
767        track_name: String,
768        instance_id: usize,
769    },
770    TrackShowClapGui {
771        track_name: String,
772        instance_id: usize,
773    },
774    TrackLoadVst3Plugin {
775        track_name: String,
776        plugin_path: String,
777        instance_id: Option<usize>,
778    },
779    TrackUnloadVst3Plugin {
780        track_name: String,
781        plugin_path: String,
782    },
783    TrackUnloadVst3PluginInstance {
784        track_name: String,
785        instance_id: usize,
786    },
787    TrackShowVst3Gui {
788        track_name: String,
789        instance_id: usize,
790    },
791    #[cfg(all(unix, not(target_os = "macos")))]
792    TrackLoadLv2Plugin {
793        track_name: String,
794        plugin_uri: String,
795        instance_id: Option<usize>,
796    },
797    #[cfg(all(unix, not(target_os = "macos")))]
798    TrackUnloadLv2Plugin {
799        track_name: String,
800        plugin_uri: String,
801    },
802    #[cfg(all(unix, not(target_os = "macos")))]
803    TrackUnloadLv2PluginInstance {
804        track_name: String,
805        instance_id: usize,
806    },
807    TrackShowLv2Gui {
808        track_name: String,
809        instance_id: usize,
810    },
811    TrackGetVst3Graph {
812        track_name: String,
813    },
814    TrackVst3Graph {
815        track_name: String,
816        plugins: Vec<Vst3GraphPlugin>,
817        connections: Vec<Vst3GraphConnection>,
818    },
819    TrackSetVst3Parameter {
820        track_name: String,
821        instance_id: usize,
822        param_id: u32,
823        value: f32,
824    },
825    TrackSetPluginBypassed {
826        track_name: String,
827        instance_id: usize,
828        format: String,
829        bypassed: bool,
830    },
831    TrackGetVst3Parameters {
832        track_name: String,
833        instance_id: usize,
834    },
835    TrackVst3Parameters {
836        track_name: String,
837        instance_id: usize,
838        parameters: Vec<crate::vst3::port::ParameterInfo>,
839    },
840    TrackVst3SnapshotState {
841        track_name: String,
842        instance_id: usize,
843    },
844    ClipVst3SnapshotState {
845        track_name: String,
846        clip_idx: usize,
847        instance_id: usize,
848    },
849    TrackVst3StateSnapshot {
850        track_name: String,
851        instance_id: usize,
852        state: crate::vst3::state::Vst3PluginState,
853    },
854    ClipVst3StateSnapshot {
855        track_name: String,
856        clip_idx: usize,
857        instance_id: usize,
858        state: crate::vst3::state::Vst3PluginState,
859    },
860    TrackVst3RestoreState {
861        track_name: String,
862        instance_id: usize,
863        state: crate::vst3::state::Vst3PluginState,
864    },
865    TrackConnectVst3Audio {
866        track_name: String,
867        from_node: Vst3GraphNode,
868        from_port: usize,
869        to_node: Vst3GraphNode,
870        to_port: usize,
871    },
872    TrackDisconnectVst3Audio {
873        track_name: String,
874        from_node: Vst3GraphNode,
875        from_port: usize,
876        to_node: Vst3GraphNode,
877        to_port: usize,
878    },
879    ClipMove {
880        kind: Kind,
881        from: ClipMoveFrom,
882        to: ClipMoveTo,
883        copy: bool,
884    },
885    Connect {
886        from_track: String,
887        from_port: usize,
888        to_track: String,
889        to_port: usize,
890        kind: Kind,
891    },
892    Disconnect {
893        from_track: String,
894        from_port: usize,
895        to_track: String,
896        to_port: usize,
897        kind: Kind,
898    },
899    OpenAudioDevice {
900        device: String,
901        input_device: Option<String>,
902        sample_rate_hz: i32,
903        bits: i32,
904        exclusive: bool,
905        period_frames: usize,
906        realtime_frames: usize,
907        low_watermark_frames: usize,
908        nperiods: usize,
909        sync_mode: bool,
910        hybrid_enabled: bool,
911        actual_period_frames: usize,
912        input_channels: usize,
913        output_channels: usize,
914        bytes_per_frame: usize,
915    },
916    JackAddAudioInputPort,
917    JackRemoveAudioInputPort(usize),
918    JackAddAudioOutputPort,
919    JackRemoveAudioOutputPort(usize),
920    OpenMidiInputDevice(String),
921    OpenMidiOutputDevice(String),
922    RequestSessionDiagnostics,
923    RequestMidiLearnMappingsReport,
924    ClearAllMidiLearnBindings,
925    SessionDiagnosticsReport {
926        track_count: usize,
927        frozen_track_count: usize,
928        audio_clip_count: usize,
929        midi_clip_count: usize,
930        #[cfg(all(unix, not(target_os = "macos")))]
931        lv2_instance_count: usize,
932        vst3_instance_count: usize,
933        clap_instance_count: usize,
934        pending_requests: usize,
935        workers_total: usize,
936        workers_ready: usize,
937        pending_hw_midi_events: usize,
938        playing: bool,
939        transport_sample: usize,
940        tempo_bpm: f64,
941        sample_rate_hz: usize,
942        cycle_samples: usize,
943    },
944    MidiLearnMappingsReport {
945        lines: Vec<String>,
946    },
947    HWInfo {
948        channels: usize,
949        rate: usize,
950        input: bool,
951    },
952    MarkHistorySavePoint,
953    HistoryState {
954        dirty: bool,
955    },
956    Undo,
957    Redo,
958    Panic,
959}
960
961#[derive(Clone, Debug)]
962pub enum Message {
963    Ready(usize),
964    Finished {
965        worker_id: usize,
966        track_name: String,
967        output_linear: Vec<f32>,
968        process_epoch: usize,
969        parameter_updates: Vec<Action>,
970    },
971    TracksFinished,
972
973    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
974    ProcessOfflineBounce(OfflineBounceWork),
975    Channel(Sender<Self>),
976
977    Request(Action),
978    Response(Result<Action, String>),
979    HWMidiEvents(Vec<HwMidiEvent>),
980    HWMidiOutEvents(Vec<HwMidiEvent>),
981    ClearHWMidiOutEvents,
982    HWFinished,
983    OfflineBounceFinished {
984        result: Result<Action, String>,
985    },
986}
987
988#[cfg(test)]
989mod tests {
990    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
991    use serde_json::json;
992
993    #[test]
994    fn audio_clip_data_serde_round_trips_nested_groups() {
995        let clip = AudioClipData {
996            name: "group.wav".to_string(),
997            start: 12,
998            length: 96,
999            offset: 3,
1000            input_channel: 1,
1001            muted: true,
1002            peaks_file: Some("peaks/group.json".to_string()),
1003            fade_enabled: false,
1004            fade_in_samples: 10,
1005            fade_out_samples: 20,
1006            preview_name: Some("preview.wav".to_string()),
1007            source_name: Some("source.wav".to_string()),
1008            source_offset: Some(4),
1009            source_length: Some(88),
1010            pitch_correction_points: vec![PitchCorrectionPointData {
1011                start_sample: 7,
1012                length_samples: 11,
1013                detected_midi_pitch: 60.1,
1014                target_midi_pitch: 61.2,
1015                clarity: 0.8,
1016            }],
1017            pitch_correction_frame_likeness: Some(0.5),
1018            pitch_correction_inertia_ms: Some(123),
1019            pitch_correction_formant_compensation: Some(false),
1020            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1021            grouped_clips: vec![AudioClipData {
1022                name: "child.wav".to_string(),
1023                start: 0,
1024                length: 48,
1025                ..AudioClipData::default()
1026            }],
1027        };
1028
1029        let value = serde_json::to_value(&clip).expect("serialize");
1030        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1031
1032        assert_eq!(restored.name, clip.name);
1033        assert_eq!(restored.preview_name, clip.preview_name);
1034        assert_eq!(restored.source_name, clip.source_name);
1035        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1036        assert_eq!(restored.grouped_clips.len(), 1);
1037        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1038        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1039    }
1040
1041    #[test]
1042    fn midi_clip_data_serde_round_trips_nested_groups() {
1043        let clip = MidiClipData {
1044            name: "group.mid".to_string(),
1045            start: 5,
1046            length: 64,
1047            offset: 2,
1048            input_channel: 3,
1049            muted: true,
1050            grouped_clips: vec![MidiClipData {
1051                name: "child.mid".to_string(),
1052                start: 0,
1053                length: 32,
1054                ..MidiClipData::default()
1055            }],
1056        };
1057
1058        let value = serde_json::to_value(&clip).expect("serialize");
1059        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1060
1061        assert_eq!(restored.name, clip.name);
1062        assert_eq!(restored.grouped_clips.len(), 1);
1063        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1064    }
1065
1066    #[test]
1067    fn pitch_correction_point_data_serde_round_trips() {
1068        let point = PitchCorrectionPointData {
1069            start_sample: 10,
1070            length_samples: 20,
1071            detected_midi_pitch: 57.5,
1072            target_midi_pitch: 58.0,
1073            clarity: 0.9,
1074        };
1075
1076        let value = serde_json::to_value(&point).expect("serialize");
1077        let restored: PitchCorrectionPointData =
1078            serde_json::from_value(value).expect("deserialize");
1079
1080        assert_eq!(restored.start_sample, 10);
1081        assert_eq!(restored.length_samples, 20);
1082        assert_eq!(restored.detected_midi_pitch, 57.5);
1083        assert_eq!(restored.target_midi_pitch, 58.0);
1084        assert_eq!(restored.clarity, 0.9);
1085    }
1086
1087    #[test]
1088    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1089        let restored: AudioClipData = serde_json::from_value(json!({
1090            "name": "clip.wav",
1091            "start": 1,
1092            "length": 2,
1093            "offset": 3,
1094            "input_channel": 0,
1095            "muted": false,
1096            "fade_enabled": true,
1097            "fade_in_samples": 240,
1098            "fade_out_samples": 240,
1099            "pitch_correction_points": [],
1100            "grouped_clips": []
1101        }))
1102        .expect("deserialize");
1103
1104        assert_eq!(restored.name, "clip.wav");
1105        assert!(restored.peaks_file.is_none());
1106        assert!(restored.preview_name.is_none());
1107        assert!(restored.source_name.is_none());
1108        assert!(restored.source_offset.is_none());
1109        assert!(restored.source_length.is_none());
1110        assert!(restored.pitch_correction_points.is_empty());
1111        assert!(restored.plugin_graph_json.is_none());
1112    }
1113}