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