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        realtime_frames: usize,
892        low_watermark_frames: usize,
893        nperiods: usize,
894        sync_mode: bool,
895    },
896    JackAddAudioInputPort,
897    JackRemoveAudioInputPort(usize),
898    JackAddAudioOutputPort,
899    JackRemoveAudioOutputPort(usize),
900    OpenMidiInputDevice(String),
901    OpenMidiOutputDevice(String),
902    RequestSessionDiagnostics,
903    RequestMidiLearnMappingsReport,
904    ClearAllMidiLearnBindings,
905    SessionDiagnosticsReport {
906        track_count: usize,
907        frozen_track_count: usize,
908        audio_clip_count: usize,
909        midi_clip_count: usize,
910        #[cfg(all(unix, not(target_os = "macos")))]
911        lv2_instance_count: usize,
912        vst3_instance_count: usize,
913        clap_instance_count: usize,
914        pending_requests: usize,
915        workers_total: usize,
916        workers_ready: usize,
917        pending_hw_midi_events: usize,
918        playing: bool,
919        transport_sample: usize,
920        tempo_bpm: f64,
921        sample_rate_hz: usize,
922        cycle_samples: usize,
923    },
924    MidiLearnMappingsReport {
925        lines: Vec<String>,
926    },
927    HWInfo {
928        channels: usize,
929        rate: usize,
930        input: bool,
931    },
932    MarkHistorySavePoint,
933    HistoryState {
934        dirty: bool,
935    },
936    Undo,
937    Redo,
938    Panic,
939}
940
941#[derive(Clone, Debug)]
942pub enum Message {
943    Ready(usize),
944    Finished {
945        worker_id: usize,
946        track_name: String,
947        output_linear: Vec<f32>,
948        process_epoch: usize,
949        parameter_updates: Vec<Action>,
950    },
951    TracksFinished,
952
953    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
954    ProcessOfflineBounce(OfflineBounceWork),
955    Channel(Sender<Self>),
956
957    Request(Action),
958    Response(Result<Action, String>),
959    HWMidiEvents(Vec<HwMidiEvent>),
960    HWMidiOutEvents(Vec<HwMidiEvent>),
961    ClearHWMidiOutEvents,
962    HWFinished,
963    OfflineBounceFinished {
964        result: Result<Action, String>,
965    },
966}
967
968#[cfg(test)]
969mod tests {
970    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
971    use serde_json::json;
972
973    #[test]
974    fn audio_clip_data_serde_round_trips_nested_groups() {
975        let clip = AudioClipData {
976            name: "group.wav".to_string(),
977            start: 12,
978            length: 96,
979            offset: 3,
980            input_channel: 1,
981            muted: true,
982            peaks_file: Some("peaks/group.json".to_string()),
983            fade_enabled: false,
984            fade_in_samples: 10,
985            fade_out_samples: 20,
986            preview_name: Some("preview.wav".to_string()),
987            source_name: Some("source.wav".to_string()),
988            source_offset: Some(4),
989            source_length: Some(88),
990            pitch_correction_points: vec![PitchCorrectionPointData {
991                start_sample: 7,
992                length_samples: 11,
993                detected_midi_pitch: 60.1,
994                target_midi_pitch: 61.2,
995                clarity: 0.8,
996            }],
997            pitch_correction_frame_likeness: Some(0.5),
998            pitch_correction_inertia_ms: Some(123),
999            pitch_correction_formant_compensation: Some(false),
1000            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1001            grouped_clips: vec![AudioClipData {
1002                name: "child.wav".to_string(),
1003                start: 0,
1004                length: 48,
1005                ..AudioClipData::default()
1006            }],
1007        };
1008
1009        let value = serde_json::to_value(&clip).expect("serialize");
1010        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1011
1012        assert_eq!(restored.name, clip.name);
1013        assert_eq!(restored.preview_name, clip.preview_name);
1014        assert_eq!(restored.source_name, clip.source_name);
1015        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1016        assert_eq!(restored.grouped_clips.len(), 1);
1017        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1018        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1019    }
1020
1021    #[test]
1022    fn midi_clip_data_serde_round_trips_nested_groups() {
1023        let clip = MidiClipData {
1024            name: "group.mid".to_string(),
1025            start: 5,
1026            length: 64,
1027            offset: 2,
1028            input_channel: 3,
1029            muted: true,
1030            grouped_clips: vec![MidiClipData {
1031                name: "child.mid".to_string(),
1032                start: 0,
1033                length: 32,
1034                ..MidiClipData::default()
1035            }],
1036        };
1037
1038        let value = serde_json::to_value(&clip).expect("serialize");
1039        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1040
1041        assert_eq!(restored.name, clip.name);
1042        assert_eq!(restored.grouped_clips.len(), 1);
1043        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1044    }
1045
1046    #[test]
1047    fn pitch_correction_point_data_serde_round_trips() {
1048        let point = PitchCorrectionPointData {
1049            start_sample: 10,
1050            length_samples: 20,
1051            detected_midi_pitch: 57.5,
1052            target_midi_pitch: 58.0,
1053            clarity: 0.9,
1054        };
1055
1056        let value = serde_json::to_value(&point).expect("serialize");
1057        let restored: PitchCorrectionPointData =
1058            serde_json::from_value(value).expect("deserialize");
1059
1060        assert_eq!(restored.start_sample, 10);
1061        assert_eq!(restored.length_samples, 20);
1062        assert_eq!(restored.detected_midi_pitch, 57.5);
1063        assert_eq!(restored.target_midi_pitch, 58.0);
1064        assert_eq!(restored.clarity, 0.9);
1065    }
1066
1067    #[test]
1068    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1069        let restored: AudioClipData = serde_json::from_value(json!({
1070            "name": "clip.wav",
1071            "start": 1,
1072            "length": 2,
1073            "offset": 3,
1074            "input_channel": 0,
1075            "muted": false,
1076            "fade_enabled": true,
1077            "fade_in_samples": 240,
1078            "fade_out_samples": 240,
1079            "pitch_correction_points": [],
1080            "grouped_clips": []
1081        }))
1082        .expect("deserialize");
1083
1084        assert_eq!(restored.name, "clip.wav");
1085        assert!(restored.peaks_file.is_none());
1086        assert!(restored.preview_name.is_none());
1087        assert!(restored.source_name.is_none());
1088        assert!(restored.source_offset.is_none());
1089        assert!(restored.source_length.is_none());
1090        assert!(restored.pitch_correction_points.is_empty());
1091        assert!(restored.plugin_graph_json.is_none());
1092    }
1093}