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