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    TrackSetFolder {
461        track_name: String,
462        is_folder: bool,
463    },
464    TrackSetParent {
465        track_name: String,
466        parent_name: Option<String>,
467    },
468    TrackToggleFolder {
469        track_name: String,
470    },
471    TrackSetMidiLaneChannel {
472        track_name: String,
473        lane: usize,
474        channel: Option<u8>,
475    },
476    TrackSetFrozen {
477        track_name: String,
478        frozen: bool,
479    },
480    TrackOfflineBounce {
481        track_name: String,
482        output_path: String,
483        start_sample: usize,
484        length_samples: usize,
485        automation_lanes: Vec<OfflineAutomationLane>,
486        apply_fader: bool,
487    },
488    TrackOfflineBounceCancel {
489        track_name: String,
490    },
491    TrackOfflineBounceCancelAll,
492    TrackOfflineBounceCanceled {
493        track_name: String,
494    },
495    TrackOfflineBounceProgress {
496        track_name: String,
497        progress: f32,
498        operation: Option<String>,
499    },
500    PianoKey {
501        track_name: String,
502        note: u8,
503        velocity: u8,
504        on: bool,
505    },
506    ModifyMidiNotes {
507        track_name: String,
508        clip_index: usize,
509        note_indices: Vec<usize>,
510        new_notes: Vec<MidiNoteData>,
511        old_notes: Vec<MidiNoteData>,
512    },
513    ModifyMidiControllers {
514        track_name: String,
515        clip_index: usize,
516        controller_indices: Vec<usize>,
517        new_controllers: Vec<MidiControllerData>,
518        old_controllers: Vec<MidiControllerData>,
519    },
520    DeleteMidiControllers {
521        track_name: String,
522        clip_index: usize,
523        controller_indices: Vec<usize>,
524        deleted_controllers: Vec<(usize, MidiControllerData)>,
525    },
526    InsertMidiControllers {
527        track_name: String,
528        clip_index: usize,
529        controllers: Vec<(usize, MidiControllerData)>,
530    },
531    DeleteMidiNotes {
532        track_name: String,
533        clip_index: usize,
534        note_indices: Vec<usize>,
535        deleted_notes: Vec<(usize, MidiNoteData)>,
536    },
537    InsertMidiNotes {
538        track_name: String,
539        clip_index: usize,
540        notes: Vec<(usize, MidiNoteData)>,
541    },
542    SetMidiSysExEvents {
543        track_name: String,
544        clip_index: usize,
545        new_sysex_events: Vec<MidiRawEventData>,
546        old_sysex_events: Vec<MidiRawEventData>,
547    },
548    TrackClearDefaultPassthrough {
549        track_name: String,
550    },
551    #[cfg(all(unix, not(target_os = "macos")))]
552    TrackSetLv2PluginState {
553        track_name: String,
554        instance_id: usize,
555        state: Vec<u8>,
556    },
557    #[cfg(all(unix, not(target_os = "macos")))]
558    ClipSetLv2PluginState {
559        track_name: String,
560        clip_idx: usize,
561        instance_id: usize,
562        state: Vec<u8>,
563    },
564    #[cfg(all(unix, not(target_os = "macos")))]
565    TrackGetLv2PluginControls {
566        track_name: String,
567        instance_id: usize,
568    },
569    #[cfg(all(unix, not(target_os = "macos")))]
570    ClipGetLv2PluginControls {
571        track_name: String,
572        clip_idx: usize,
573        instance_id: usize,
574    },
575    #[cfg(all(unix, not(target_os = "macos")))]
576    TrackLv2PluginControls {
577        track_name: String,
578        instance_id: usize,
579        controls: Vec<Lv2ControlPortInfo>,
580        instance_access_handle: Option<usize>,
581    },
582    #[cfg(all(unix, not(target_os = "macos")))]
583    ClipLv2PluginControls {
584        track_name: String,
585        clip_idx: usize,
586        instance_id: usize,
587        controls: Vec<Lv2ControlPortInfo>,
588        instance_access_handle: Option<usize>,
589    },
590    #[cfg(all(unix, not(target_os = "macos")))]
591    TrackGetLv2Midnam {
592        track_name: String,
593    },
594    #[cfg(all(unix, not(target_os = "macos")))]
595    TrackLv2Midnam {
596        track_name: String,
597        note_names: std::collections::HashMap<u8, String>,
598    },
599    TrackGetClapNoteNames {
600        track_name: String,
601    },
602    TrackClapNoteNames {
603        track_name: String,
604        note_names: std::collections::HashMap<u8, String>,
605    },
606    #[cfg(all(unix, not(target_os = "macos")))]
607    TrackSetLv2ControlValue {
608        track_name: String,
609        instance_id: usize,
610        index: u32,
611        value: f32,
612    },
613    #[cfg(all(unix, not(target_os = "macos")))]
614    ClipSetLv2ControlValue {
615        track_name: String,
616        clip_idx: usize,
617        instance_id: usize,
618        index: u32,
619        value: f32,
620    },
621    #[cfg(all(unix, not(target_os = "macos")))]
622    ClipLv2StateSnapshot {
623        track_name: String,
624        clip_idx: usize,
625        instance_id: usize,
626        state: Vec<u8>,
627    },
628    TrackGetPluginGraph {
629        track_name: String,
630    },
631    TrackPluginGraph {
632        track_name: String,
633        plugins: Vec<PluginGraphPlugin>,
634        connections: Vec<PluginGraphConnection>,
635    },
636    TrackConnectPluginAudio {
637        track_name: String,
638        from_node: PluginGraphNode,
639        from_port: usize,
640        to_node: PluginGraphNode,
641        to_port: usize,
642    },
643    TrackConnectPluginMidi {
644        track_name: String,
645        from_node: PluginGraphNode,
646        from_port: usize,
647        to_node: PluginGraphNode,
648        to_port: usize,
649    },
650    TrackDisconnectPluginAudio {
651        track_name: String,
652        from_node: PluginGraphNode,
653        from_port: usize,
654        to_node: PluginGraphNode,
655        to_port: usize,
656    },
657    TrackDisconnectPluginMidi {
658        track_name: String,
659        from_node: PluginGraphNode,
660        from_port: usize,
661        to_node: PluginGraphNode,
662        to_port: usize,
663    },
664    #[cfg(all(unix, not(target_os = "macos")))]
665    ListLv2Plugins,
666    #[cfg(all(unix, not(target_os = "macos")))]
667    Lv2Plugins(Vec<Lv2PluginInfo>),
668    ListVst3Plugins,
669    Vst3Plugins(Vec<Vst3PluginInfo>),
670    ListClapPlugins,
671    ListClapPluginsWithCapabilities,
672    ClapPlugins(Vec<ClapPluginInfo>),
673    TrackSetClapParameter {
674        track_name: String,
675        instance_id: usize,
676        param_id: u32,
677        value: f64,
678    },
679    ClipSetClapParameter {
680        track_name: String,
681        clip_idx: usize,
682        instance_id: usize,
683        param_id: u32,
684        value: f64,
685    },
686    TrackSetClapParameterAt {
687        track_name: String,
688        instance_id: usize,
689        param_id: u32,
690        value: f64,
691        frame: u32,
692    },
693    TrackBeginClapParameterEdit {
694        track_name: String,
695        instance_id: usize,
696        param_id: u32,
697        frame: u32,
698    },
699    TrackEndClapParameterEdit {
700        track_name: String,
701        instance_id: usize,
702        param_id: u32,
703        frame: u32,
704    },
705    TrackGetClapParameters {
706        track_name: String,
707        instance_id: usize,
708    },
709    TrackClapParameters {
710        track_name: String,
711        instance_id: usize,
712        parameters: Vec<ClapParameterInfo>,
713    },
714    TrackClapSnapshotState {
715        track_name: String,
716        instance_id: usize,
717    },
718    ClipClapSnapshotState {
719        track_name: String,
720        clip_idx: usize,
721        instance_id: usize,
722    },
723    TrackClapStateSnapshot {
724        track_name: String,
725        instance_id: usize,
726        plugin_path: String,
727        state: crate::clap::ClapPluginState,
728    },
729    ClipClapStateSnapshot {
730        track_name: String,
731        clip_idx: usize,
732        instance_id: usize,
733        plugin_path: String,
734        state: crate::clap::ClapPluginState,
735    },
736    TrackClapRestoreState {
737        track_name: String,
738        instance_id: usize,
739        state: crate::clap::ClapPluginState,
740    },
741    ClipClapRestoreState {
742        track_name: String,
743        clip_idx: usize,
744        instance_id: usize,
745        state: crate::clap::ClapPluginState,
746    },
747    TrackSnapshotAllClapStates {
748        track_name: String,
749    },
750    TrackSnapshotAllClapStatesDone {
751        track_name: String,
752    },
753    TrackLoadClapPlugin {
754        track_name: String,
755        plugin_path: String,
756        instance_id: Option<usize>,
757    },
758    TrackUnloadClapPlugin {
759        track_name: String,
760        plugin_path: String,
761    },
762    TrackUnloadClapPluginInstance {
763        track_name: String,
764        instance_id: usize,
765    },
766    TrackShowClapGui {
767        track_name: String,
768        instance_id: usize,
769    },
770    TrackLoadVst3Plugin {
771        track_name: String,
772        plugin_path: String,
773        instance_id: Option<usize>,
774    },
775    TrackUnloadVst3Plugin {
776        track_name: String,
777        plugin_path: String,
778    },
779    TrackUnloadVst3PluginInstance {
780        track_name: String,
781        instance_id: usize,
782    },
783    TrackShowVst3Gui {
784        track_name: String,
785        instance_id: usize,
786    },
787    #[cfg(all(unix, not(target_os = "macos")))]
788    TrackLoadLv2Plugin {
789        track_name: String,
790        plugin_uri: String,
791        instance_id: Option<usize>,
792    },
793    #[cfg(all(unix, not(target_os = "macos")))]
794    TrackUnloadLv2Plugin {
795        track_name: String,
796        plugin_uri: String,
797    },
798    #[cfg(all(unix, not(target_os = "macos")))]
799    TrackUnloadLv2PluginInstance {
800        track_name: String,
801        instance_id: usize,
802    },
803    TrackShowLv2Gui {
804        track_name: String,
805        instance_id: usize,
806    },
807    TrackGetVst3Graph {
808        track_name: String,
809    },
810    TrackVst3Graph {
811        track_name: String,
812        plugins: Vec<Vst3GraphPlugin>,
813        connections: Vec<Vst3GraphConnection>,
814    },
815    TrackSetVst3Parameter {
816        track_name: String,
817        instance_id: usize,
818        param_id: u32,
819        value: f32,
820    },
821    TrackSetPluginBypassed {
822        track_name: String,
823        instance_id: usize,
824        format: String,
825        bypassed: bool,
826    },
827    TrackGetVst3Parameters {
828        track_name: String,
829        instance_id: usize,
830    },
831    TrackVst3Parameters {
832        track_name: String,
833        instance_id: usize,
834        parameters: Vec<crate::vst3::port::ParameterInfo>,
835    },
836    TrackVst3SnapshotState {
837        track_name: String,
838        instance_id: usize,
839    },
840    ClipVst3SnapshotState {
841        track_name: String,
842        clip_idx: usize,
843        instance_id: usize,
844    },
845    TrackVst3StateSnapshot {
846        track_name: String,
847        instance_id: usize,
848        state: crate::vst3::state::Vst3PluginState,
849    },
850    ClipVst3StateSnapshot {
851        track_name: String,
852        clip_idx: usize,
853        instance_id: usize,
854        state: crate::vst3::state::Vst3PluginState,
855    },
856    TrackVst3RestoreState {
857        track_name: String,
858        instance_id: usize,
859        state: crate::vst3::state::Vst3PluginState,
860    },
861    TrackConnectVst3Audio {
862        track_name: String,
863        from_node: Vst3GraphNode,
864        from_port: usize,
865        to_node: Vst3GraphNode,
866        to_port: usize,
867    },
868    TrackDisconnectVst3Audio {
869        track_name: String,
870        from_node: Vst3GraphNode,
871        from_port: usize,
872        to_node: Vst3GraphNode,
873        to_port: usize,
874    },
875    ClipMove {
876        kind: Kind,
877        from: ClipMoveFrom,
878        to: ClipMoveTo,
879        copy: bool,
880    },
881    Connect {
882        from_track: String,
883        from_port: usize,
884        to_track: String,
885        to_port: usize,
886        kind: Kind,
887    },
888    Disconnect {
889        from_track: String,
890        from_port: usize,
891        to_track: String,
892        to_port: usize,
893        kind: Kind,
894    },
895    OpenAudioDevice {
896        device: String,
897        input_device: Option<String>,
898        sample_rate_hz: i32,
899        bits: i32,
900        exclusive: bool,
901        period_frames: usize,
902        realtime_frames: usize,
903        low_watermark_frames: usize,
904        nperiods: usize,
905        sync_mode: bool,
906        hybrid_enabled: bool,
907    },
908    JackAddAudioInputPort,
909    JackRemoveAudioInputPort(usize),
910    JackAddAudioOutputPort,
911    JackRemoveAudioOutputPort(usize),
912    OpenMidiInputDevice(String),
913    OpenMidiOutputDevice(String),
914    RequestSessionDiagnostics,
915    RequestMidiLearnMappingsReport,
916    ClearAllMidiLearnBindings,
917    SessionDiagnosticsReport {
918        track_count: usize,
919        frozen_track_count: usize,
920        audio_clip_count: usize,
921        midi_clip_count: usize,
922        #[cfg(all(unix, not(target_os = "macos")))]
923        lv2_instance_count: usize,
924        vst3_instance_count: usize,
925        clap_instance_count: usize,
926        pending_requests: usize,
927        workers_total: usize,
928        workers_ready: usize,
929        pending_hw_midi_events: usize,
930        playing: bool,
931        transport_sample: usize,
932        tempo_bpm: f64,
933        sample_rate_hz: usize,
934        cycle_samples: usize,
935    },
936    MidiLearnMappingsReport {
937        lines: Vec<String>,
938    },
939    HWInfo {
940        channels: usize,
941        rate: usize,
942        input: bool,
943    },
944    MarkHistorySavePoint,
945    HistoryState {
946        dirty: bool,
947    },
948    Undo,
949    Redo,
950    Panic,
951}
952
953#[derive(Clone, Debug)]
954pub enum Message {
955    Ready(usize),
956    Finished {
957        worker_id: usize,
958        track_name: String,
959        output_linear: Vec<f32>,
960        process_epoch: usize,
961        parameter_updates: Vec<Action>,
962    },
963    TracksFinished,
964
965    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
966    ProcessOfflineBounce(OfflineBounceWork),
967    Channel(Sender<Self>),
968
969    Request(Action),
970    Response(Result<Action, String>),
971    HWMidiEvents(Vec<HwMidiEvent>),
972    HWMidiOutEvents(Vec<HwMidiEvent>),
973    ClearHWMidiOutEvents,
974    HWFinished,
975    OfflineBounceFinished {
976        result: Result<Action, String>,
977    },
978}
979
980#[cfg(test)]
981mod tests {
982    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
983    use serde_json::json;
984
985    #[test]
986    fn audio_clip_data_serde_round_trips_nested_groups() {
987        let clip = AudioClipData {
988            name: "group.wav".to_string(),
989            start: 12,
990            length: 96,
991            offset: 3,
992            input_channel: 1,
993            muted: true,
994            peaks_file: Some("peaks/group.json".to_string()),
995            fade_enabled: false,
996            fade_in_samples: 10,
997            fade_out_samples: 20,
998            preview_name: Some("preview.wav".to_string()),
999            source_name: Some("source.wav".to_string()),
1000            source_offset: Some(4),
1001            source_length: Some(88),
1002            pitch_correction_points: vec![PitchCorrectionPointData {
1003                start_sample: 7,
1004                length_samples: 11,
1005                detected_midi_pitch: 60.1,
1006                target_midi_pitch: 61.2,
1007                clarity: 0.8,
1008            }],
1009            pitch_correction_frame_likeness: Some(0.5),
1010            pitch_correction_inertia_ms: Some(123),
1011            pitch_correction_formant_compensation: Some(false),
1012            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1013            grouped_clips: vec![AudioClipData {
1014                name: "child.wav".to_string(),
1015                start: 0,
1016                length: 48,
1017                ..AudioClipData::default()
1018            }],
1019        };
1020
1021        let value = serde_json::to_value(&clip).expect("serialize");
1022        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1023
1024        assert_eq!(restored.name, clip.name);
1025        assert_eq!(restored.preview_name, clip.preview_name);
1026        assert_eq!(restored.source_name, clip.source_name);
1027        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1028        assert_eq!(restored.grouped_clips.len(), 1);
1029        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1030        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1031    }
1032
1033    #[test]
1034    fn midi_clip_data_serde_round_trips_nested_groups() {
1035        let clip = MidiClipData {
1036            name: "group.mid".to_string(),
1037            start: 5,
1038            length: 64,
1039            offset: 2,
1040            input_channel: 3,
1041            muted: true,
1042            grouped_clips: vec![MidiClipData {
1043                name: "child.mid".to_string(),
1044                start: 0,
1045                length: 32,
1046                ..MidiClipData::default()
1047            }],
1048        };
1049
1050        let value = serde_json::to_value(&clip).expect("serialize");
1051        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1052
1053        assert_eq!(restored.name, clip.name);
1054        assert_eq!(restored.grouped_clips.len(), 1);
1055        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1056    }
1057
1058    #[test]
1059    fn pitch_correction_point_data_serde_round_trips() {
1060        let point = PitchCorrectionPointData {
1061            start_sample: 10,
1062            length_samples: 20,
1063            detected_midi_pitch: 57.5,
1064            target_midi_pitch: 58.0,
1065            clarity: 0.9,
1066        };
1067
1068        let value = serde_json::to_value(&point).expect("serialize");
1069        let restored: PitchCorrectionPointData =
1070            serde_json::from_value(value).expect("deserialize");
1071
1072        assert_eq!(restored.start_sample, 10);
1073        assert_eq!(restored.length_samples, 20);
1074        assert_eq!(restored.detected_midi_pitch, 57.5);
1075        assert_eq!(restored.target_midi_pitch, 58.0);
1076        assert_eq!(restored.clarity, 0.9);
1077    }
1078
1079    #[test]
1080    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1081        let restored: AudioClipData = serde_json::from_value(json!({
1082            "name": "clip.wav",
1083            "start": 1,
1084            "length": 2,
1085            "offset": 3,
1086            "input_channel": 0,
1087            "muted": false,
1088            "fade_enabled": true,
1089            "fade_in_samples": 240,
1090            "fade_out_samples": 240,
1091            "pitch_correction_points": [],
1092            "grouped_clips": []
1093        }))
1094        .expect("deserialize");
1095
1096        assert_eq!(restored.name, "clip.wav");
1097        assert!(restored.peaks_file.is_none());
1098        assert!(restored.preview_name.is_none());
1099        assert!(restored.source_name.is_none());
1100        assert!(restored.source_offset.is_none());
1101        assert!(restored.source_length.is_none());
1102        assert!(restored.pitch_correction_points.is_empty());
1103        assert!(restored.plugin_graph_json.is_none());
1104    }
1105}