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)]
11pub struct MidiNoteData {
12    pub start_sample: usize,
13    pub length_samples: usize,
14    pub pitch: u8,
15    pub velocity: u8,
16    pub channel: u8,
17}
18
19#[derive(Clone, Debug)]
20pub struct MidiControllerData {
21    pub sample: usize,
22    pub controller: u8,
23    pub value: u8,
24    pub channel: u8,
25}
26
27#[derive(Debug, Clone)]
28pub struct MidiRawEventData {
29    pub sample: usize,
30    pub data: Vec<u8>,
31}
32
33#[derive(Clone, Debug)]
34pub struct HwMidiEvent {
35    pub device: String,
36    pub event: MidiEvent,
37}
38
39#[derive(Clone, Debug)]
40pub struct OfflineAutomationPoint {
41    pub sample: usize,
42    pub value: f32,
43}
44
45#[derive(Clone, Debug)]
46pub enum OfflineAutomationTarget {
47    Volume,
48    Balance,
49    Mute,
50    #[cfg(all(unix, not(target_os = "macos")))]
51    Lv2Parameter {
52        instance_id: usize,
53        index: u32,
54        min: f32,
55        max: f32,
56    },
57    Vst3Parameter {
58        instance_id: usize,
59        param_id: u32,
60    },
61    ClapParameter {
62        instance_id: usize,
63        param_id: u32,
64        min: f64,
65        max: f64,
66    },
67}
68
69#[derive(Clone, Debug)]
70pub struct OfflineAutomationLane {
71    pub target: OfflineAutomationTarget,
72    pub points: Vec<OfflineAutomationPoint>,
73}
74
75#[derive(Clone, Debug)]
76pub struct OfflineBounceWork {
77    pub state: Arc<UnsafeMutex<crate::state::State>>,
78    pub track_name: String,
79    pub output_path: String,
80    pub start_sample: usize,
81    pub length_samples: usize,
82    pub tempo_bpm: f64,
83    pub tsig_num: u16,
84    pub tsig_denom: u16,
85    pub automation_lanes: Vec<OfflineAutomationLane>,
86    pub cancel: Arc<AtomicBool>,
87}
88
89#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
90pub struct PitchCorrectionPointData {
91    pub start_sample: usize,
92    pub length_samples: usize,
93    pub detected_midi_pitch: f32,
94    pub target_midi_pitch: f32,
95    pub clarity: f32,
96}
97
98#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
99pub struct AudioClipData {
100    pub name: String,
101    pub start: usize,
102    pub length: usize,
103    pub offset: usize,
104    pub input_channel: usize,
105    pub muted: bool,
106    pub peaks_file: Option<String>,
107    pub fade_enabled: bool,
108    pub fade_in_samples: usize,
109    pub fade_out_samples: usize,
110    pub preview_name: Option<String>,
111    pub source_name: Option<String>,
112    pub source_offset: Option<usize>,
113    pub source_length: Option<usize>,
114    pub pitch_correction_points: Vec<PitchCorrectionPointData>,
115    pub pitch_correction_frame_likeness: Option<f32>,
116    pub pitch_correction_inertia_ms: Option<u16>,
117    pub pitch_correction_formant_compensation: Option<bool>,
118    pub plugin_graph_json: Option<serde_json::Value>,
119    pub grouped_clips: Vec<AudioClipData>,
120}
121
122#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
123pub struct MidiClipData {
124    pub name: String,
125    pub start: usize,
126    pub length: usize,
127    pub offset: usize,
128    pub input_channel: usize,
129    pub muted: bool,
130    pub grouped_clips: Vec<MidiClipData>,
131}
132
133#[derive(Clone, Debug)]
134pub struct ClipMoveFrom {
135    pub track_name: String,
136    pub clip_index: usize,
137}
138
139#[derive(Clone, Debug)]
140pub struct ClipMoveTo {
141    pub track_name: String,
142    pub sample_offset: usize,
143    pub input_channel: usize,
144}
145
146#[cfg(unix)]
147#[derive(Clone, Debug, PartialEq, Eq, Hash)]
148pub enum PluginGraphNode {
149    TrackInput,
150    TrackOutput,
151    Lv2PluginInstance(usize),
152    Vst3PluginInstance(usize),
153    ClapPluginInstance(usize),
154}
155
156#[cfg(unix)]
157#[derive(Clone, Debug, PartialEq)]
158pub struct PluginGraphPlugin {
159    pub node: PluginGraphNode,
160    pub instance_id: usize,
161    pub format: String,
162    pub uri: String,
163    pub plugin_id: String,
164    pub name: String,
165    pub main_audio_inputs: usize,
166    pub main_audio_outputs: usize,
167    pub audio_inputs: usize,
168    pub audio_outputs: usize,
169    pub midi_inputs: usize,
170    pub midi_outputs: usize,
171    pub state: Option<serde_json::Value>,
172}
173
174#[cfg(unix)]
175#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
176pub struct Lv2StatePortValue {
177    pub index: u32,
178    pub value: f32,
179}
180
181#[cfg(unix)]
182#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
183pub struct Lv2StateProperty {
184    pub key_uri: String,
185    pub type_uri: String,
186    pub flags: u32,
187    pub value: Vec<u8>,
188}
189
190#[cfg(unix)]
191#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
192pub struct Lv2PluginState {
193    pub port_values: Vec<Lv2StatePortValue>,
194    pub properties: Vec<Lv2StateProperty>,
195}
196
197#[cfg(all(unix, not(target_os = "macos")))]
198#[derive(Clone, Debug, PartialEq)]
199pub struct Lv2ControlPortInfo {
200    pub index: u32,
201    pub name: String,
202    pub min: f32,
203    pub max: f32,
204    pub value: f32,
205}
206
207#[cfg(unix)]
208#[derive(Clone, Debug, PartialEq, Eq)]
209pub struct PluginGraphConnection {
210    pub from_node: PluginGraphNode,
211    pub from_port: usize,
212    pub to_node: PluginGraphNode,
213    pub to_port: usize,
214    pub kind: Kind,
215}
216
217#[cfg(unix)]
218pub type PluginGraphSnapshot = (Vec<PluginGraphPlugin>, Vec<PluginGraphConnection>);
219
220// VST3 graph types
221#[derive(Clone, Debug, PartialEq, Eq, Hash)]
222pub enum Vst3GraphNode {
223    TrackInput,
224    TrackOutput,
225    PluginInstance(usize),
226}
227
228#[derive(Clone, Debug)]
229pub struct Vst3GraphPlugin {
230    pub instance_id: usize,
231    pub name: String,
232    pub path: String,
233    pub audio_inputs: usize,
234    pub audio_outputs: usize,
235    pub parameters: Vec<crate::vst3::port::ParameterInfo>,
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
239pub struct Vst3GraphConnection {
240    pub from_node: Vst3GraphNode,
241    pub from_port: usize,
242    pub to_node: Vst3GraphNode,
243    pub to_port: usize,
244    pub kind: Kind,
245}
246
247#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
248pub struct MidiLearnBinding {
249    pub device: Option<String>,
250    pub channel: u8,
251    pub cc: u8,
252}
253
254#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
255pub enum TrackMidiLearnTarget {
256    Volume,
257    Balance,
258    Mute,
259    Solo,
260    Arm,
261    InputMonitor,
262    DiskMonitor,
263}
264
265#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
266pub enum GlobalMidiLearnTarget {
267    PlayPause,
268    Stop,
269    RecordToggle,
270}
271
272#[derive(Clone, Debug)]
273pub enum Action {
274    Quit,
275    Play,
276    Pause,
277    Stop,
278    TransportPosition(usize),
279    JumpToEnd,
280    SetLoopEnabled(bool),
281    SetLoopRange(Option<(usize, usize)>),
282    SetPunchEnabled(bool),
283    SetPunchRange(Option<(usize, usize)>),
284    SetMetronomeEnabled(bool),
285    SetTempo(f64),
286    SetTimeSignature {
287        numerator: u16,
288        denominator: u16,
289    },
290    SetOscEnabled(bool),
291    SetClipPlaybackEnabled(bool),
292    SetRecordEnabled(bool),
293    SetSessionPath(String),
294    BeginHistoryGroup,
295    EndHistoryGroup,
296    ClearHistory,
297    BeginSessionRestore,
298    EndSessionRestore,
299    AddTrack {
300        name: String,
301        audio_ins: usize,
302        midi_ins: usize,
303        audio_outs: usize,
304        midi_outs: usize,
305    },
306    TrackAddAudioInput(String),
307    TrackAddAudioOutput(String),
308    TrackRemoveAudioInput(String),
309    TrackRemoveAudioOutput(String),
310    AddClip {
311        name: String,
312        track_name: String,
313        start: usize,
314        length: usize,
315        offset: usize,
316        input_channel: usize,
317        muted: bool,
318        peaks_file: Option<String>,
319        kind: Kind,
320        fade_enabled: bool,
321        fade_in_samples: usize,
322        fade_out_samples: usize,
323        source_name: Option<String>,
324        source_offset: Option<usize>,
325        source_length: Option<usize>,
326        preview_name: Option<String>,
327        pitch_correction_points: Vec<PitchCorrectionPointData>,
328        pitch_correction_frame_likeness: Option<f32>,
329        pitch_correction_inertia_ms: Option<u16>,
330        pitch_correction_formant_compensation: Option<bool>,
331        plugin_graph_json: Option<serde_json::Value>,
332    },
333    AddGroupedClip {
334        track_name: String,
335        kind: Kind,
336        audio_clip: Option<AudioClipData>,
337        midi_clip: Option<MidiClipData>,
338    },
339    RemoveClip {
340        track_name: String,
341        kind: Kind,
342        clip_indices: Vec<usize>,
343    },
344    SetClipFade {
345        track_name: String,
346        clip_index: usize,
347        kind: Kind,
348        fade_enabled: bool,
349        fade_in_samples: usize,
350        fade_out_samples: usize,
351    },
352    SetClipBounds {
353        track_name: String,
354        clip_index: usize,
355        kind: Kind,
356        start: usize,
357        length: usize,
358        offset: usize,
359    },
360    SetClipMuted {
361        track_name: String,
362        clip_index: usize,
363        kind: Kind,
364        muted: bool,
365    },
366    SetClipPluginGraphJson {
367        track_name: String,
368        clip_index: usize,
369        plugin_graph_json: Option<serde_json::Value>,
370    },
371    SetClipPitchCorrection {
372        track_name: String,
373        clip_index: usize,
374        preview_name: Option<String>,
375        source_name: Option<String>,
376        source_offset: Option<usize>,
377        source_length: Option<usize>,
378        pitch_correction_points: Vec<PitchCorrectionPointData>,
379        pitch_correction_frame_likeness: Option<f32>,
380        pitch_correction_inertia_ms: Option<u16>,
381        pitch_correction_formant_compensation: Option<bool>,
382    },
383    RenameClip {
384        track_name: String,
385        kind: Kind,
386        clip_index: usize,
387        new_name: String,
388    },
389    RenameTrack {
390        old_name: String,
391        new_name: String,
392    },
393    RemoveTrack(String),
394    TrackLevel(String, f32),
395    TrackBalance(String, f32),
396    TrackAutomationLevel(String, f32),
397    TrackAutomationBalance(String, f32),
398    TrackAutomationMute(String, bool),
399    TrackMeters {
400        track_name: String,
401        output_db: Vec<f32>,
402    },
403    RequestMeterSnapshot,
404    MeterSnapshot {
405        hw_out_db: Arc<Vec<f32>>,
406        track_meters: Arc<Vec<(String, Vec<f32>)>>,
407    },
408    TrackToggleArm(String),
409    TrackToggleMute(String),
410    TrackToggleSolo(String),
411    TrackToggleInputMonitor(String),
412    TrackToggleDiskMonitor(String),
413    TrackArmMidiLearn {
414        track_name: String,
415        target: TrackMidiLearnTarget,
416    },
417    GlobalArmMidiLearn {
418        target: GlobalMidiLearnTarget,
419    },
420    TrackSetMidiLearnBinding {
421        track_name: String,
422        target: TrackMidiLearnTarget,
423        binding: Option<MidiLearnBinding>,
424    },
425    SetGlobalMidiLearnBinding {
426        target: GlobalMidiLearnTarget,
427        binding: Option<MidiLearnBinding>,
428    },
429    TrackSetVcaMaster {
430        track_name: String,
431        master_track: Option<String>,
432    },
433    TrackSetMidiLaneChannel {
434        track_name: String,
435        lane: usize,
436        channel: Option<u8>,
437    },
438    TrackSetFrozen {
439        track_name: String,
440        frozen: bool,
441    },
442    TrackOfflineBounce {
443        track_name: String,
444        output_path: String,
445        start_sample: usize,
446        length_samples: usize,
447        automation_lanes: Vec<OfflineAutomationLane>,
448    },
449    TrackOfflineBounceCancel {
450        track_name: String,
451    },
452    TrackOfflineBounceCanceled {
453        track_name: String,
454    },
455    TrackOfflineBounceProgress {
456        track_name: String,
457        progress: f32,
458        operation: Option<String>,
459    },
460    PianoKey {
461        track_name: String,
462        note: u8,
463        velocity: u8,
464        on: bool,
465    },
466    ModifyMidiNotes {
467        track_name: String,
468        clip_index: usize,
469        note_indices: Vec<usize>,
470        new_notes: Vec<MidiNoteData>,
471        old_notes: Vec<MidiNoteData>,
472    },
473    ModifyMidiControllers {
474        track_name: String,
475        clip_index: usize,
476        controller_indices: Vec<usize>,
477        new_controllers: Vec<MidiControllerData>,
478        old_controllers: Vec<MidiControllerData>,
479    },
480    DeleteMidiControllers {
481        track_name: String,
482        clip_index: usize,
483        controller_indices: Vec<usize>,
484        deleted_controllers: Vec<(usize, MidiControllerData)>,
485    },
486    InsertMidiControllers {
487        track_name: String,
488        clip_index: usize,
489        controllers: Vec<(usize, MidiControllerData)>,
490    },
491    DeleteMidiNotes {
492        track_name: String,
493        clip_index: usize,
494        note_indices: Vec<usize>,
495        deleted_notes: Vec<(usize, MidiNoteData)>,
496    },
497    InsertMidiNotes {
498        track_name: String,
499        clip_index: usize,
500        notes: Vec<(usize, MidiNoteData)>,
501    },
502    SetMidiSysExEvents {
503        track_name: String,
504        clip_index: usize,
505        new_sysex_events: Vec<MidiRawEventData>,
506        old_sysex_events: Vec<MidiRawEventData>,
507    },
508    #[cfg(all(unix, not(target_os = "macos")))]
509    TrackLoadLv2Plugin {
510        track_name: String,
511        plugin_uri: String,
512    },
513    TrackClearDefaultPassthrough {
514        track_name: String,
515    },
516    #[cfg(all(unix, not(target_os = "macos")))]
517    TrackSetLv2PluginState {
518        track_name: String,
519        instance_id: usize,
520        state: Lv2PluginState,
521    },
522    #[cfg(all(unix, not(target_os = "macos")))]
523    ClipSetLv2PluginState {
524        track_name: String,
525        clip_idx: usize,
526        instance_id: usize,
527        state: Lv2PluginState,
528    },
529    #[cfg(all(unix, not(target_os = "macos")))]
530    TrackUnloadLv2PluginInstance {
531        track_name: String,
532        instance_id: usize,
533    },
534    #[cfg(all(unix, not(target_os = "macos")))]
535    TrackGetLv2PluginControls {
536        track_name: String,
537        instance_id: usize,
538    },
539    #[cfg(all(unix, not(target_os = "macos")))]
540    ClipGetLv2PluginControls {
541        track_name: String,
542        clip_idx: usize,
543        instance_id: usize,
544    },
545    #[cfg(all(unix, not(target_os = "macos")))]
546    TrackLv2PluginControls {
547        track_name: String,
548        instance_id: usize,
549        controls: Vec<Lv2ControlPortInfo>,
550        instance_access_handle: Option<usize>,
551    },
552    #[cfg(all(unix, not(target_os = "macos")))]
553    ClipLv2PluginControls {
554        track_name: String,
555        clip_idx: usize,
556        instance_id: usize,
557        controls: Vec<Lv2ControlPortInfo>,
558        instance_access_handle: Option<usize>,
559    },
560    #[cfg(all(unix, not(target_os = "macos")))]
561    TrackGetLv2Midnam {
562        track_name: String,
563    },
564    #[cfg(all(unix, not(target_os = "macos")))]
565    TrackLv2Midnam {
566        track_name: String,
567        note_names: std::collections::HashMap<u8, String>,
568    },
569    TrackGetClapNoteNames {
570        track_name: String,
571    },
572    TrackClapNoteNames {
573        track_name: String,
574        note_names: std::collections::HashMap<u8, String>,
575    },
576    #[cfg(all(unix, not(target_os = "macos")))]
577    TrackSetLv2ControlValue {
578        track_name: String,
579        instance_id: usize,
580        index: u32,
581        value: f32,
582    },
583    #[cfg(all(unix, not(target_os = "macos")))]
584    ClipSetLv2ControlValue {
585        track_name: String,
586        clip_idx: usize,
587        instance_id: usize,
588        index: u32,
589        value: f32,
590    },
591    #[cfg(all(unix, not(target_os = "macos")))]
592    ClipLv2StateSnapshot {
593        track_name: String,
594        clip_idx: usize,
595        instance_id: usize,
596        state: Lv2PluginState,
597    },
598    #[cfg(unix)]
599    TrackGetPluginGraph {
600        track_name: String,
601    },
602    #[cfg(unix)]
603    TrackPluginGraph {
604        track_name: String,
605        plugins: Vec<PluginGraphPlugin>,
606        connections: Vec<PluginGraphConnection>,
607    },
608    #[cfg(unix)]
609    TrackConnectPluginAudio {
610        track_name: String,
611        from_node: PluginGraphNode,
612        from_port: usize,
613        to_node: PluginGraphNode,
614        to_port: usize,
615    },
616    #[cfg(unix)]
617    TrackConnectPluginMidi {
618        track_name: String,
619        from_node: PluginGraphNode,
620        from_port: usize,
621        to_node: PluginGraphNode,
622        to_port: usize,
623    },
624    #[cfg(unix)]
625    TrackDisconnectPluginAudio {
626        track_name: String,
627        from_node: PluginGraphNode,
628        from_port: usize,
629        to_node: PluginGraphNode,
630        to_port: usize,
631    },
632    #[cfg(unix)]
633    TrackDisconnectPluginMidi {
634        track_name: String,
635        from_node: PluginGraphNode,
636        from_port: usize,
637        to_node: PluginGraphNode,
638        to_port: usize,
639    },
640    #[cfg(all(unix, not(target_os = "macos")))]
641    ListLv2Plugins,
642    #[cfg(all(unix, not(target_os = "macos")))]
643    Lv2Plugins(Vec<Lv2PluginInfo>),
644    ListVst3Plugins,
645    Vst3Plugins(Vec<Vst3PluginInfo>),
646    ListClapPlugins,
647    ListClapPluginsWithCapabilities,
648    ClapPlugins(Vec<ClapPluginInfo>),
649    TrackLoadClapPlugin {
650        track_name: String,
651        plugin_path: String,
652    },
653    TrackUnloadClapPlugin {
654        track_name: String,
655        plugin_path: String,
656    },
657    TrackSetClapParameter {
658        track_name: String,
659        instance_id: usize,
660        param_id: u32,
661        value: f64,
662    },
663    ClipSetClapParameter {
664        track_name: String,
665        clip_idx: usize,
666        instance_id: usize,
667        param_id: u32,
668        value: f64,
669    },
670    TrackSetClapParameterAt {
671        track_name: String,
672        instance_id: usize,
673        param_id: u32,
674        value: f64,
675        frame: u32,
676    },
677    TrackBeginClapParameterEdit {
678        track_name: String,
679        instance_id: usize,
680        param_id: u32,
681        frame: u32,
682    },
683    TrackEndClapParameterEdit {
684        track_name: String,
685        instance_id: usize,
686        param_id: u32,
687        frame: u32,
688    },
689    TrackGetClapParameters {
690        track_name: String,
691        instance_id: usize,
692    },
693    TrackClapParameters {
694        track_name: String,
695        instance_id: usize,
696        parameters: Vec<ClapParameterInfo>,
697    },
698    TrackClapSnapshotState {
699        track_name: String,
700        instance_id: usize,
701    },
702    ClipClapSnapshotState {
703        track_name: String,
704        clip_idx: usize,
705        instance_id: usize,
706    },
707    TrackGetClapProcessor {
708        track_name: String,
709        instance_id: usize,
710    },
711    ClipGetClapProcessor {
712        track_name: String,
713        clip_idx: usize,
714        instance_id: usize,
715    },
716    TrackClapProcessor {
717        track_name: String,
718        instance_id: usize,
719        processor: Arc<crate::clap::ClapProcessor>,
720    },
721    ClipClapProcessor {
722        track_name: String,
723        clip_idx: usize,
724        instance_id: usize,
725        processor: Arc<crate::clap::ClapProcessor>,
726    },
727    TrackClapStateSnapshot {
728        track_name: String,
729        instance_id: usize,
730        plugin_path: String,
731        state: crate::clap::ClapPluginState,
732    },
733    ClipClapStateSnapshot {
734        track_name: String,
735        clip_idx: usize,
736        instance_id: usize,
737        plugin_path: String,
738        state: crate::clap::ClapPluginState,
739    },
740    TrackClapRestoreState {
741        track_name: String,
742        instance_id: usize,
743        state: crate::clap::ClapPluginState,
744    },
745    ClipClapRestoreState {
746        track_name: String,
747        clip_idx: usize,
748        instance_id: usize,
749        state: crate::clap::ClapPluginState,
750    },
751    TrackSnapshotAllClapStates {
752        track_name: String,
753    },
754    TrackSnapshotAllClapStatesDone {
755        track_name: String,
756    },
757    TrackLoadVst3Plugin {
758        track_name: String,
759        plugin_path: String,
760    },
761    TrackUnloadVst3PluginInstance {
762        track_name: String,
763        instance_id: usize,
764    },
765    TrackGetVst3Graph {
766        track_name: String,
767    },
768    TrackVst3Graph {
769        track_name: String,
770        plugins: Vec<Vst3GraphPlugin>,
771        connections: Vec<Vst3GraphConnection>,
772    },
773    TrackSetVst3Parameter {
774        track_name: String,
775        instance_id: usize,
776        param_id: u32,
777        value: f32,
778    },
779    TrackGetVst3Parameters {
780        track_name: String,
781        instance_id: usize,
782    },
783    TrackVst3Parameters {
784        track_name: String,
785        instance_id: usize,
786        parameters: Vec<crate::vst3::port::ParameterInfo>,
787    },
788    TrackVst3SnapshotState {
789        track_name: String,
790        instance_id: usize,
791    },
792    ClipVst3SnapshotState {
793        track_name: String,
794        clip_idx: usize,
795        instance_id: usize,
796    },
797    TrackVst3StateSnapshot {
798        track_name: String,
799        instance_id: usize,
800        state: crate::vst3::state::Vst3PluginState,
801    },
802    ClipVst3StateSnapshot {
803        track_name: String,
804        clip_idx: usize,
805        instance_id: usize,
806        state: crate::vst3::state::Vst3PluginState,
807    },
808    TrackVst3RestoreState {
809        track_name: String,
810        instance_id: usize,
811        state: crate::vst3::state::Vst3PluginState,
812    },
813    TrackConnectVst3Audio {
814        track_name: String,
815        from_node: Vst3GraphNode,
816        from_port: usize,
817        to_node: Vst3GraphNode,
818        to_port: usize,
819    },
820    TrackDisconnectVst3Audio {
821        track_name: String,
822        from_node: Vst3GraphNode,
823        from_port: usize,
824        to_node: Vst3GraphNode,
825        to_port: usize,
826    },
827    ClipMove {
828        kind: Kind,
829        from: ClipMoveFrom,
830        to: ClipMoveTo,
831        copy: bool,
832    },
833    Connect {
834        from_track: String,
835        from_port: usize,
836        to_track: String,
837        to_port: usize,
838        kind: Kind,
839    },
840    Disconnect {
841        from_track: String,
842        from_port: usize,
843        to_track: String,
844        to_port: usize,
845        kind: Kind,
846    },
847    OpenAudioDevice {
848        device: String,
849        input_device: Option<String>,
850        sample_rate_hz: i32,
851        bits: i32,
852        exclusive: bool,
853        period_frames: usize,
854        nperiods: usize,
855        sync_mode: bool,
856    },
857    JackAddAudioInputPort,
858    JackRemoveAudioInputPort(usize),
859    JackAddAudioOutputPort,
860    JackRemoveAudioOutputPort(usize),
861    OpenMidiInputDevice(String),
862    OpenMidiOutputDevice(String),
863    RequestSessionDiagnostics,
864    RequestMidiLearnMappingsReport,
865    ClearAllMidiLearnBindings,
866    SessionDiagnosticsReport {
867        track_count: usize,
868        frozen_track_count: usize,
869        audio_clip_count: usize,
870        midi_clip_count: usize,
871        #[cfg(all(unix, not(target_os = "macos")))]
872        lv2_instance_count: usize,
873        vst3_instance_count: usize,
874        clap_instance_count: usize,
875        pending_requests: usize,
876        workers_total: usize,
877        workers_ready: usize,
878        pending_hw_midi_events: usize,
879        playing: bool,
880        transport_sample: usize,
881        tempo_bpm: f64,
882        sample_rate_hz: usize,
883        cycle_samples: usize,
884    },
885    MidiLearnMappingsReport {
886        lines: Vec<String>,
887    },
888    HWInfo {
889        channels: usize,
890        rate: usize,
891        input: bool,
892    },
893    Undo,
894    Redo,
895    Panic,
896}
897
898#[derive(Clone, Debug)]
899pub enum Message {
900    Ready(usize),
901    Finished {
902        worker_id: usize,
903        track_name: String,
904        output_linear: Vec<f32>,
905        process_epoch: usize,
906    },
907    TracksFinished,
908
909    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
910    ProcessOfflineBounce(OfflineBounceWork),
911    Channel(Sender<Self>),
912
913    Request(Action),
914    Response(Result<Action, String>),
915    HWMidiEvents(Vec<HwMidiEvent>),
916    HWMidiOutEvents(Vec<HwMidiEvent>),
917    ClearHWMidiOutEvents,
918    HWFinished,
919    OfflineBounceFinished {
920        result: Result<Action, String>,
921    },
922}
923
924#[cfg(test)]
925mod tests {
926    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
927    use serde_json::json;
928
929    #[test]
930    fn audio_clip_data_serde_round_trips_nested_groups() {
931        let clip = AudioClipData {
932            name: "group.wav".to_string(),
933            start: 12,
934            length: 96,
935            offset: 3,
936            input_channel: 1,
937            muted: true,
938            peaks_file: Some("peaks/group.json".to_string()),
939            fade_enabled: false,
940            fade_in_samples: 10,
941            fade_out_samples: 20,
942            preview_name: Some("preview.wav".to_string()),
943            source_name: Some("source.wav".to_string()),
944            source_offset: Some(4),
945            source_length: Some(88),
946            pitch_correction_points: vec![PitchCorrectionPointData {
947                start_sample: 7,
948                length_samples: 11,
949                detected_midi_pitch: 60.1,
950                target_midi_pitch: 61.2,
951                clarity: 0.8,
952            }],
953            pitch_correction_frame_likeness: Some(0.5),
954            pitch_correction_inertia_ms: Some(123),
955            pitch_correction_formant_compensation: Some(false),
956            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
957            grouped_clips: vec![AudioClipData {
958                name: "child.wav".to_string(),
959                start: 0,
960                length: 48,
961                ..AudioClipData::default()
962            }],
963        };
964
965        let value = serde_json::to_value(&clip).expect("serialize");
966        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
967
968        assert_eq!(restored.name, clip.name);
969        assert_eq!(restored.preview_name, clip.preview_name);
970        assert_eq!(restored.source_name, clip.source_name);
971        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
972        assert_eq!(restored.grouped_clips.len(), 1);
973        assert_eq!(restored.grouped_clips[0].name, "child.wav");
974        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
975    }
976
977    #[test]
978    fn midi_clip_data_serde_round_trips_nested_groups() {
979        let clip = MidiClipData {
980            name: "group.mid".to_string(),
981            start: 5,
982            length: 64,
983            offset: 2,
984            input_channel: 3,
985            muted: true,
986            grouped_clips: vec![MidiClipData {
987                name: "child.mid".to_string(),
988                start: 0,
989                length: 32,
990                ..MidiClipData::default()
991            }],
992        };
993
994        let value = serde_json::to_value(&clip).expect("serialize");
995        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
996
997        assert_eq!(restored.name, clip.name);
998        assert_eq!(restored.grouped_clips.len(), 1);
999        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1000    }
1001
1002    #[test]
1003    fn pitch_correction_point_data_serde_round_trips() {
1004        let point = PitchCorrectionPointData {
1005            start_sample: 10,
1006            length_samples: 20,
1007            detected_midi_pitch: 57.5,
1008            target_midi_pitch: 58.0,
1009            clarity: 0.9,
1010        };
1011
1012        let value = serde_json::to_value(&point).expect("serialize");
1013        let restored: PitchCorrectionPointData =
1014            serde_json::from_value(value).expect("deserialize");
1015
1016        assert_eq!(restored.start_sample, 10);
1017        assert_eq!(restored.length_samples, 20);
1018        assert_eq!(restored.detected_midi_pitch, 57.5);
1019        assert_eq!(restored.target_midi_pitch, 58.0);
1020        assert_eq!(restored.clarity, 0.9);
1021    }
1022
1023    #[test]
1024    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1025        let restored: AudioClipData = serde_json::from_value(json!({
1026            "name": "clip.wav",
1027            "start": 1,
1028            "length": 2,
1029            "offset": 3,
1030            "input_channel": 0,
1031            "muted": false,
1032            "fade_enabled": true,
1033            "fade_in_samples": 240,
1034            "fade_out_samples": 240,
1035            "pitch_correction_points": [],
1036            "grouped_clips": []
1037        }))
1038        .expect("deserialize");
1039
1040        assert_eq!(restored.name, "clip.wav");
1041        assert!(restored.peaks_file.is_none());
1042        assert!(restored.preview_name.is_none());
1043        assert!(restored.source_name.is_none());
1044        assert!(restored.source_offset.is_none());
1045        assert!(restored.source_length.is_none());
1046        assert!(restored.pitch_correction_points.is_empty());
1047        assert!(restored.plugin_graph_json.is_none());
1048    }
1049}