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    Lv2PluginInstance(usize),
160    Vst3PluginInstance(usize),
161    ClapPluginInstance(usize),
162}
163
164#[derive(Clone, Debug, PartialEq)]
165pub struct PluginGraphPlugin {
166    pub node: PluginGraphNode,
167    pub instance_id: usize,
168    pub format: String,
169    pub uri: String,
170    pub plugin_id: String,
171    pub name: String,
172    pub main_audio_inputs: usize,
173    pub main_audio_outputs: usize,
174    pub audio_inputs: usize,
175    pub audio_outputs: usize,
176    pub midi_inputs: usize,
177    pub midi_outputs: usize,
178    pub state: Option<serde_json::Value>,
179    pub bypassed: bool,
180}
181
182#[cfg(unix)]
183#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
184pub struct Lv2StatePortValue {
185    pub index: u32,
186    pub value: f32,
187}
188
189#[cfg(unix)]
190#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
191pub struct Lv2StateProperty {
192    pub key_uri: String,
193    pub type_uri: String,
194    pub flags: u32,
195    pub value: Vec<u8>,
196}
197
198#[cfg(unix)]
199#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
200pub struct Lv2PluginState {
201    pub port_values: Vec<Lv2StatePortValue>,
202    pub properties: Vec<Lv2StateProperty>,
203}
204
205#[cfg(all(unix, not(target_os = "macos")))]
206#[derive(Clone, Debug, PartialEq)]
207pub struct Lv2ControlPortInfo {
208    pub index: u32,
209    pub name: String,
210    pub min: f32,
211    pub max: f32,
212    pub value: f32,
213}
214
215#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct PluginGraphConnection {
217    pub from_node: PluginGraphNode,
218    pub from_port: usize,
219    pub to_node: PluginGraphNode,
220    pub to_port: usize,
221    pub kind: Kind,
222}
223
224pub type PluginGraphSnapshot = (Vec<PluginGraphPlugin>, Vec<PluginGraphConnection>);
225
226#[derive(Clone, Debug, PartialEq, Eq, Hash)]
227pub enum Vst3GraphNode {
228    TrackInput,
229    TrackOutput,
230    PluginInstance(usize),
231}
232
233#[derive(Clone, Debug)]
234pub struct Vst3GraphPlugin {
235    pub instance_id: usize,
236    pub name: String,
237    pub path: String,
238    pub audio_inputs: usize,
239    pub audio_outputs: usize,
240    pub parameters: Vec<crate::vst3::port::ParameterInfo>,
241}
242
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub struct Vst3GraphConnection {
245    pub from_node: Vst3GraphNode,
246    pub from_port: usize,
247    pub to_node: Vst3GraphNode,
248    pub to_port: usize,
249    pub kind: Kind,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
253pub struct MidiLearnBinding {
254    pub device: Option<String>,
255    pub channel: u8,
256    pub cc: u8,
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
260pub enum TrackMidiLearnTarget {
261    Volume,
262    Balance,
263    Mute,
264    Solo,
265    Arm,
266    InputMonitor,
267    DiskMonitor,
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
271pub enum GlobalMidiLearnTarget {
272    PlayPause,
273    Stop,
274    RecordToggle,
275}
276
277#[derive(Clone, Debug)]
278pub enum Action {
279    Quit,
280    Play,
281    Pause,
282    Stop,
283    TransportPosition(usize),
284    JumpToEnd,
285    SetLoopEnabled(bool),
286    SetLoopRange(Option<(usize, usize)>),
287    SetPunchEnabled(bool),
288    SetPunchRange(Option<(usize, usize)>),
289    SetMetronomeEnabled(bool),
290    SetTempo(f64),
291    SetTimeSignature {
292        numerator: u16,
293        denominator: u16,
294    },
295    SetOscEnabled(bool),
296    SetClipPlaybackEnabled(bool),
297    SetRecordEnabled(bool),
298    SetSessionPath(String),
299    BeginHistoryGroup,
300    EndHistoryGroup,
301    ApplyGroupedActions(Vec<Action>),
302    ClearHistory,
303    BeginSessionRestore,
304    EndSessionRestore,
305    AddTrack {
306        name: String,
307        audio_ins: usize,
308        midi_ins: usize,
309        audio_outs: usize,
310        midi_outs: usize,
311    },
312    TrackAddAudioInput(String),
313    TrackAddAudioOutput(String),
314    TrackRemoveAudioInput(String),
315    TrackRemoveAudioOutput(String),
316    AddClip {
317        name: String,
318        track_name: String,
319        start: usize,
320        length: usize,
321        offset: usize,
322        input_channel: usize,
323        muted: bool,
324        peaks_file: Option<String>,
325        kind: Kind,
326        fade_enabled: bool,
327        fade_in_samples: usize,
328        fade_out_samples: usize,
329        source_name: Option<String>,
330        source_offset: Option<usize>,
331        source_length: Option<usize>,
332        preview_name: Option<String>,
333        pitch_correction_points: Vec<PitchCorrectionPointData>,
334        pitch_correction_frame_likeness: Option<f32>,
335        pitch_correction_inertia_ms: Option<u16>,
336        pitch_correction_formant_compensation: Option<bool>,
337        plugin_graph_json: Option<serde_json::Value>,
338    },
339    AddGroupedClip {
340        track_name: String,
341        kind: Kind,
342        audio_clip: Option<AudioClipData>,
343        midi_clip: Option<MidiClipData>,
344    },
345    RemoveClip {
346        track_name: String,
347        kind: Kind,
348        clip_indices: Vec<usize>,
349    },
350    SetClipFade {
351        track_name: String,
352        clip_index: usize,
353        kind: Kind,
354        fade_enabled: bool,
355        fade_in_samples: usize,
356        fade_out_samples: usize,
357    },
358    SetClipBounds {
359        track_name: String,
360        clip_index: usize,
361        kind: Kind,
362        start: usize,
363        length: usize,
364        offset: usize,
365    },
366    SyncClipBounds {
367        track_name: String,
368        clip_index: usize,
369        kind: Kind,
370        start: usize,
371        length: usize,
372        offset: usize,
373    },
374    SetClipMuted {
375        track_name: String,
376        clip_index: usize,
377        kind: Kind,
378        muted: bool,
379    },
380    SetClipPluginGraphJson {
381        track_name: String,
382        clip_index: usize,
383        plugin_graph_json: Option<serde_json::Value>,
384    },
385    SetClipPitchCorrection {
386        track_name: String,
387        clip_index: usize,
388        preview_name: Option<String>,
389        source_name: Option<String>,
390        source_offset: Option<usize>,
391        source_length: Option<usize>,
392        pitch_correction_points: Vec<PitchCorrectionPointData>,
393        pitch_correction_frame_likeness: Option<f32>,
394        pitch_correction_inertia_ms: Option<u16>,
395        pitch_correction_formant_compensation: Option<bool>,
396    },
397    RenameClip {
398        track_name: String,
399        kind: Kind,
400        clip_index: usize,
401        new_name: String,
402    },
403    SetClipSourceName {
404        track_name: String,
405        kind: Kind,
406        clip_index: usize,
407        name: String,
408    },
409    RenameTrack {
410        old_name: String,
411        new_name: String,
412    },
413    RemoveTrack(String),
414    TrackLevel(String, f32),
415    TrackBalance(String, f32),
416    TrackAutomationLevel(String, f32),
417    TrackAutomationBalance(String, f32),
418    TrackAutomationMute(String, bool),
419    TrackMeters {
420        track_name: String,
421        output_db: Vec<f32>,
422    },
423    RequestMeterSnapshot,
424    MeterSnapshot {
425        hw_out_db: Arc<Vec<f32>>,
426        track_meters: Arc<Vec<(String, Vec<f32>)>>,
427    },
428    TrackToggleArm(String),
429    TrackToggleMute(String),
430    TrackTogglePhase(String),
431    TrackToggleSolo(String),
432    TrackToggleMaster(String),
433    TrackToggleInputMonitor(String),
434    TrackToggleDiskMonitor(String),
435    TrackSetColor {
436        track_name: String,
437        color: Option<TrackColor>,
438    },
439    TrackArmMidiLearn {
440        track_name: String,
441        target: TrackMidiLearnTarget,
442    },
443    GlobalArmMidiLearn {
444        target: GlobalMidiLearnTarget,
445    },
446    TrackSetMidiLearnBinding {
447        track_name: String,
448        target: TrackMidiLearnTarget,
449        binding: Option<MidiLearnBinding>,
450    },
451    SetGlobalMidiLearnBinding {
452        target: GlobalMidiLearnTarget,
453        binding: Option<MidiLearnBinding>,
454    },
455    TrackSetVcaMaster {
456        track_name: String,
457        master_track: Option<String>,
458    },
459    TrackSetMidiLaneChannel {
460        track_name: String,
461        lane: usize,
462        channel: Option<u8>,
463    },
464    TrackSetFrozen {
465        track_name: String,
466        frozen: bool,
467    },
468    TrackOfflineBounce {
469        track_name: String,
470        output_path: String,
471        start_sample: usize,
472        length_samples: usize,
473        automation_lanes: Vec<OfflineAutomationLane>,
474        apply_fader: bool,
475    },
476    TrackOfflineBounceCancel {
477        track_name: String,
478    },
479    TrackOfflineBounceCancelAll,
480    TrackOfflineBounceCanceled {
481        track_name: String,
482    },
483    TrackOfflineBounceProgress {
484        track_name: String,
485        progress: f32,
486        operation: Option<String>,
487    },
488    PianoKey {
489        track_name: String,
490        note: u8,
491        velocity: u8,
492        on: bool,
493    },
494    ModifyMidiNotes {
495        track_name: String,
496        clip_index: usize,
497        note_indices: Vec<usize>,
498        new_notes: Vec<MidiNoteData>,
499        old_notes: Vec<MidiNoteData>,
500    },
501    ModifyMidiControllers {
502        track_name: String,
503        clip_index: usize,
504        controller_indices: Vec<usize>,
505        new_controllers: Vec<MidiControllerData>,
506        old_controllers: Vec<MidiControllerData>,
507    },
508    DeleteMidiControllers {
509        track_name: String,
510        clip_index: usize,
511        controller_indices: Vec<usize>,
512        deleted_controllers: Vec<(usize, MidiControllerData)>,
513    },
514    InsertMidiControllers {
515        track_name: String,
516        clip_index: usize,
517        controllers: Vec<(usize, MidiControllerData)>,
518    },
519    DeleteMidiNotes {
520        track_name: String,
521        clip_index: usize,
522        note_indices: Vec<usize>,
523        deleted_notes: Vec<(usize, MidiNoteData)>,
524    },
525    InsertMidiNotes {
526        track_name: String,
527        clip_index: usize,
528        notes: Vec<(usize, MidiNoteData)>,
529    },
530    SetMidiSysExEvents {
531        track_name: String,
532        clip_index: usize,
533        new_sysex_events: Vec<MidiRawEventData>,
534        old_sysex_events: Vec<MidiRawEventData>,
535    },
536    #[cfg(all(unix, not(target_os = "macos")))]
537    TrackLoadLv2Plugin {
538        track_name: String,
539        plugin_uri: String,
540        instance_id: Option<usize>,
541    },
542    TrackClearDefaultPassthrough {
543        track_name: String,
544    },
545    #[cfg(all(unix, not(target_os = "macos")))]
546    TrackSetLv2PluginState {
547        track_name: String,
548        instance_id: usize,
549        state: Lv2PluginState,
550    },
551    #[cfg(all(unix, not(target_os = "macos")))]
552    ClipSetLv2PluginState {
553        track_name: String,
554        clip_idx: usize,
555        instance_id: usize,
556        state: Lv2PluginState,
557    },
558    #[cfg(all(unix, not(target_os = "macos")))]
559    TrackUnloadLv2PluginInstance {
560        track_name: String,
561        instance_id: usize,
562    },
563    #[cfg(all(unix, not(target_os = "macos")))]
564    TrackGetLv2PluginControls {
565        track_name: String,
566        instance_id: usize,
567    },
568    #[cfg(all(unix, not(target_os = "macos")))]
569    ClipGetLv2PluginControls {
570        track_name: String,
571        clip_idx: usize,
572        instance_id: usize,
573    },
574    #[cfg(all(unix, not(target_os = "macos")))]
575    TrackLv2PluginControls {
576        track_name: String,
577        instance_id: usize,
578        controls: Vec<Lv2ControlPortInfo>,
579        instance_access_handle: Option<usize>,
580    },
581    #[cfg(all(unix, not(target_os = "macos")))]
582    ClipLv2PluginControls {
583        track_name: String,
584        clip_idx: usize,
585        instance_id: usize,
586        controls: Vec<Lv2ControlPortInfo>,
587        instance_access_handle: Option<usize>,
588    },
589    #[cfg(all(unix, not(target_os = "macos")))]
590    TrackGetLv2Midnam {
591        track_name: String,
592    },
593    #[cfg(all(unix, not(target_os = "macos")))]
594    TrackLv2Midnam {
595        track_name: String,
596        note_names: std::collections::HashMap<u8, String>,
597    },
598    TrackGetClapNoteNames {
599        track_name: String,
600    },
601    TrackClapNoteNames {
602        track_name: String,
603        note_names: std::collections::HashMap<u8, String>,
604    },
605    #[cfg(all(unix, not(target_os = "macos")))]
606    TrackSetLv2ControlValue {
607        track_name: String,
608        instance_id: usize,
609        index: u32,
610        value: f32,
611    },
612    #[cfg(all(unix, not(target_os = "macos")))]
613    ClipSetLv2ControlValue {
614        track_name: String,
615        clip_idx: usize,
616        instance_id: usize,
617        index: u32,
618        value: f32,
619    },
620    #[cfg(all(unix, not(target_os = "macos")))]
621    ClipLv2StateSnapshot {
622        track_name: String,
623        clip_idx: usize,
624        instance_id: usize,
625        state: Lv2PluginState,
626    },
627    TrackGetPluginGraph {
628        track_name: String,
629    },
630    TrackPluginGraph {
631        track_name: String,
632        plugins: Vec<PluginGraphPlugin>,
633        connections: Vec<PluginGraphConnection>,
634    },
635    TrackConnectPluginAudio {
636        track_name: String,
637        from_node: PluginGraphNode,
638        from_port: usize,
639        to_node: PluginGraphNode,
640        to_port: usize,
641    },
642    TrackConnectPluginMidi {
643        track_name: String,
644        from_node: PluginGraphNode,
645        from_port: usize,
646        to_node: PluginGraphNode,
647        to_port: usize,
648    },
649    TrackDisconnectPluginAudio {
650        track_name: String,
651        from_node: PluginGraphNode,
652        from_port: usize,
653        to_node: PluginGraphNode,
654        to_port: usize,
655    },
656    TrackDisconnectPluginMidi {
657        track_name: String,
658        from_node: PluginGraphNode,
659        from_port: usize,
660        to_node: PluginGraphNode,
661        to_port: usize,
662    },
663    #[cfg(all(unix, not(target_os = "macos")))]
664    ListLv2Plugins,
665    #[cfg(all(unix, not(target_os = "macos")))]
666    Lv2Plugins(Vec<Lv2PluginInfo>),
667    ListVst3Plugins,
668    Vst3Plugins(Vec<Vst3PluginInfo>),
669    ListClapPlugins,
670    ListClapPluginsWithCapabilities,
671    ClapPlugins(Vec<ClapPluginInfo>),
672    TrackLoadClapPlugin {
673        track_name: String,
674        plugin_path: String,
675        instance_id: Option<usize>,
676    },
677    TrackUnloadClapPlugin {
678        track_name: String,
679        plugin_path: String,
680    },
681    TrackSetClapParameter {
682        track_name: String,
683        instance_id: usize,
684        param_id: u32,
685        value: f64,
686    },
687    ClipSetClapParameter {
688        track_name: String,
689        clip_idx: usize,
690        instance_id: usize,
691        param_id: u32,
692        value: f64,
693    },
694    TrackSetClapParameterAt {
695        track_name: String,
696        instance_id: usize,
697        param_id: u32,
698        value: f64,
699        frame: u32,
700    },
701    TrackBeginClapParameterEdit {
702        track_name: String,
703        instance_id: usize,
704        param_id: u32,
705        frame: u32,
706    },
707    TrackEndClapParameterEdit {
708        track_name: String,
709        instance_id: usize,
710        param_id: u32,
711        frame: u32,
712    },
713    TrackGetClapParameters {
714        track_name: String,
715        instance_id: usize,
716    },
717    TrackClapParameters {
718        track_name: String,
719        instance_id: usize,
720        parameters: Vec<ClapParameterInfo>,
721    },
722    TrackClapSnapshotState {
723        track_name: String,
724        instance_id: usize,
725    },
726    ClipClapSnapshotState {
727        track_name: String,
728        clip_idx: usize,
729        instance_id: usize,
730    },
731    TrackGetClapProcessor {
732        track_name: String,
733        instance_id: usize,
734    },
735    ClipGetClapProcessor {
736        track_name: String,
737        clip_idx: usize,
738        instance_id: usize,
739    },
740    TrackClapProcessor {
741        track_name: String,
742        instance_id: usize,
743        processor: crate::clap::SharedClapProcessor,
744    },
745    ClipClapProcessor {
746        track_name: String,
747        clip_idx: usize,
748        instance_id: usize,
749        processor: crate::clap::SharedClapProcessor,
750    },
751    TrackClapStateSnapshot {
752        track_name: String,
753        instance_id: usize,
754        plugin_path: String,
755        state: crate::clap::ClapPluginState,
756    },
757    ClipClapStateSnapshot {
758        track_name: String,
759        clip_idx: usize,
760        instance_id: usize,
761        plugin_path: String,
762        state: crate::clap::ClapPluginState,
763    },
764    TrackClapRestoreState {
765        track_name: String,
766        instance_id: usize,
767        state: crate::clap::ClapPluginState,
768    },
769    ClipClapRestoreState {
770        track_name: String,
771        clip_idx: usize,
772        instance_id: usize,
773        state: crate::clap::ClapPluginState,
774    },
775    TrackSnapshotAllClapStates {
776        track_name: String,
777    },
778    TrackSnapshotAllClapStatesDone {
779        track_name: String,
780    },
781    TrackLoadVst3Plugin {
782        track_name: String,
783        plugin_path: String,
784        instance_id: Option<usize>,
785    },
786    TrackUnloadVst3PluginInstance {
787        track_name: String,
788        instance_id: usize,
789    },
790    TrackGetVst3Graph {
791        track_name: String,
792    },
793    TrackVst3Graph {
794        track_name: String,
795        plugins: Vec<Vst3GraphPlugin>,
796        connections: Vec<Vst3GraphConnection>,
797    },
798    TrackSetVst3Parameter {
799        track_name: String,
800        instance_id: usize,
801        param_id: u32,
802        value: f32,
803    },
804    TrackSetPluginBypassed {
805        track_name: String,
806        instance_id: usize,
807        format: String,
808        bypassed: bool,
809    },
810    TrackGetVst3Parameters {
811        track_name: String,
812        instance_id: usize,
813    },
814    TrackVst3Parameters {
815        track_name: String,
816        instance_id: usize,
817        parameters: Vec<crate::vst3::port::ParameterInfo>,
818    },
819    TrackGetVst3Processor {
820        track_name: String,
821        instance_id: usize,
822    },
823    ClipGetVst3Processor {
824        track_name: String,
825        clip_idx: usize,
826        instance_id: usize,
827    },
828    TrackVst3Processor {
829        track_name: String,
830        instance_id: usize,
831        processor: Arc<crate::vst3::Vst3Processor>,
832    },
833    ClipVst3Processor {
834        track_name: String,
835        clip_idx: usize,
836        instance_id: usize,
837        processor: Arc<crate::vst3::Vst3Processor>,
838    },
839    TrackVst3SnapshotState {
840        track_name: String,
841        instance_id: usize,
842    },
843    ClipVst3SnapshotState {
844        track_name: String,
845        clip_idx: usize,
846        instance_id: usize,
847    },
848    TrackVst3StateSnapshot {
849        track_name: String,
850        instance_id: usize,
851        state: crate::vst3::state::Vst3PluginState,
852    },
853    ClipVst3StateSnapshot {
854        track_name: String,
855        clip_idx: usize,
856        instance_id: usize,
857        state: crate::vst3::state::Vst3PluginState,
858    },
859    TrackVst3RestoreState {
860        track_name: String,
861        instance_id: usize,
862        state: crate::vst3::state::Vst3PluginState,
863    },
864    TrackConnectVst3Audio {
865        track_name: String,
866        from_node: Vst3GraphNode,
867        from_port: usize,
868        to_node: Vst3GraphNode,
869        to_port: usize,
870    },
871    TrackDisconnectVst3Audio {
872        track_name: String,
873        from_node: Vst3GraphNode,
874        from_port: usize,
875        to_node: Vst3GraphNode,
876        to_port: usize,
877    },
878    ClipMove {
879        kind: Kind,
880        from: ClipMoveFrom,
881        to: ClipMoveTo,
882        copy: bool,
883    },
884    Connect {
885        from_track: String,
886        from_port: usize,
887        to_track: String,
888        to_port: usize,
889        kind: Kind,
890    },
891    Disconnect {
892        from_track: String,
893        from_port: usize,
894        to_track: String,
895        to_port: usize,
896        kind: Kind,
897    },
898    OpenAudioDevice {
899        device: String,
900        input_device: Option<String>,
901        sample_rate_hz: i32,
902        bits: i32,
903        exclusive: bool,
904        period_frames: usize,
905        nperiods: usize,
906        sync_mode: 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    },
962    TracksFinished,
963
964    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
965    ProcessOfflineBounce(OfflineBounceWork),
966    Channel(Sender<Self>),
967
968    Request(Action),
969    Response(Result<Action, String>),
970    HWMidiEvents(Vec<HwMidiEvent>),
971    HWMidiOutEvents(Vec<HwMidiEvent>),
972    ClearHWMidiOutEvents,
973    HWFinished,
974    OfflineBounceFinished {
975        result: Result<Action, String>,
976    },
977}
978
979#[cfg(test)]
980mod tests {
981    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
982    use serde_json::json;
983
984    #[test]
985    fn audio_clip_data_serde_round_trips_nested_groups() {
986        let clip = AudioClipData {
987            name: "group.wav".to_string(),
988            start: 12,
989            length: 96,
990            offset: 3,
991            input_channel: 1,
992            muted: true,
993            peaks_file: Some("peaks/group.json".to_string()),
994            fade_enabled: false,
995            fade_in_samples: 10,
996            fade_out_samples: 20,
997            preview_name: Some("preview.wav".to_string()),
998            source_name: Some("source.wav".to_string()),
999            source_offset: Some(4),
1000            source_length: Some(88),
1001            pitch_correction_points: vec![PitchCorrectionPointData {
1002                start_sample: 7,
1003                length_samples: 11,
1004                detected_midi_pitch: 60.1,
1005                target_midi_pitch: 61.2,
1006                clarity: 0.8,
1007            }],
1008            pitch_correction_frame_likeness: Some(0.5),
1009            pitch_correction_inertia_ms: Some(123),
1010            pitch_correction_formant_compensation: Some(false),
1011            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1012            grouped_clips: vec![AudioClipData {
1013                name: "child.wav".to_string(),
1014                start: 0,
1015                length: 48,
1016                ..AudioClipData::default()
1017            }],
1018        };
1019
1020        let value = serde_json::to_value(&clip).expect("serialize");
1021        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1022
1023        assert_eq!(restored.name, clip.name);
1024        assert_eq!(restored.preview_name, clip.preview_name);
1025        assert_eq!(restored.source_name, clip.source_name);
1026        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1027        assert_eq!(restored.grouped_clips.len(), 1);
1028        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1029        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1030    }
1031
1032    #[test]
1033    fn midi_clip_data_serde_round_trips_nested_groups() {
1034        let clip = MidiClipData {
1035            name: "group.mid".to_string(),
1036            start: 5,
1037            length: 64,
1038            offset: 2,
1039            input_channel: 3,
1040            muted: true,
1041            grouped_clips: vec![MidiClipData {
1042                name: "child.mid".to_string(),
1043                start: 0,
1044                length: 32,
1045                ..MidiClipData::default()
1046            }],
1047        };
1048
1049        let value = serde_json::to_value(&clip).expect("serialize");
1050        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1051
1052        assert_eq!(restored.name, clip.name);
1053        assert_eq!(restored.grouped_clips.len(), 1);
1054        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1055    }
1056
1057    #[test]
1058    fn pitch_correction_point_data_serde_round_trips() {
1059        let point = PitchCorrectionPointData {
1060            start_sample: 10,
1061            length_samples: 20,
1062            detected_midi_pitch: 57.5,
1063            target_midi_pitch: 58.0,
1064            clarity: 0.9,
1065        };
1066
1067        let value = serde_json::to_value(&point).expect("serialize");
1068        let restored: PitchCorrectionPointData =
1069            serde_json::from_value(value).expect("deserialize");
1070
1071        assert_eq!(restored.start_sample, 10);
1072        assert_eq!(restored.length_samples, 20);
1073        assert_eq!(restored.detected_midi_pitch, 57.5);
1074        assert_eq!(restored.target_midi_pitch, 58.0);
1075        assert_eq!(restored.clarity, 0.9);
1076    }
1077
1078    #[test]
1079    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1080        let restored: AudioClipData = serde_json::from_value(json!({
1081            "name": "clip.wav",
1082            "start": 1,
1083            "length": 2,
1084            "offset": 3,
1085            "input_channel": 0,
1086            "muted": false,
1087            "fade_enabled": true,
1088            "fade_in_samples": 240,
1089            "fade_out_samples": 240,
1090            "pitch_correction_points": [],
1091            "grouped_clips": []
1092        }))
1093        .expect("deserialize");
1094
1095        assert_eq!(restored.name, "clip.wav");
1096        assert!(restored.peaks_file.is_none());
1097        assert!(restored.preview_name.is_none());
1098        assert!(restored.source_name.is_none());
1099        assert!(restored.source_offset.is_none());
1100        assert!(restored.source_length.is_none());
1101        assert!(restored.pitch_correction_points.is_empty());
1102        assert!(restored.plugin_graph_json.is_none());
1103    }
1104}