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<Lv2PluginState>,
172}
173
174#[cfg(unix)]
175#[derive(Clone, Debug, PartialEq)]
176pub struct Lv2StatePortValue {
177    pub index: u32,
178    pub value: f32,
179}
180
181#[cfg(unix)]
182#[derive(Clone, Debug, PartialEq, Eq)]
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)]
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    #[cfg(all(unix, not(target_os = "macos")))]
570    TrackSetLv2ControlValue {
571        track_name: String,
572        instance_id: usize,
573        index: u32,
574        value: f32,
575    },
576    #[cfg(all(unix, not(target_os = "macos")))]
577    ClipSetLv2ControlValue {
578        track_name: String,
579        clip_idx: usize,
580        instance_id: usize,
581        index: u32,
582        value: f32,
583    },
584    #[cfg(all(unix, not(target_os = "macos")))]
585    ClipLv2StateSnapshot {
586        track_name: String,
587        clip_idx: usize,
588        instance_id: usize,
589        state: Lv2PluginState,
590    },
591    #[cfg(unix)]
592    TrackGetPluginGraph {
593        track_name: String,
594    },
595    #[cfg(unix)]
596    TrackPluginGraph {
597        track_name: String,
598        plugins: Vec<PluginGraphPlugin>,
599        connections: Vec<PluginGraphConnection>,
600    },
601    #[cfg(unix)]
602    TrackConnectPluginAudio {
603        track_name: String,
604        from_node: PluginGraphNode,
605        from_port: usize,
606        to_node: PluginGraphNode,
607        to_port: usize,
608    },
609    #[cfg(unix)]
610    TrackConnectPluginMidi {
611        track_name: String,
612        from_node: PluginGraphNode,
613        from_port: usize,
614        to_node: PluginGraphNode,
615        to_port: usize,
616    },
617    #[cfg(unix)]
618    TrackDisconnectPluginAudio {
619        track_name: String,
620        from_node: PluginGraphNode,
621        from_port: usize,
622        to_node: PluginGraphNode,
623        to_port: usize,
624    },
625    #[cfg(unix)]
626    TrackDisconnectPluginMidi {
627        track_name: String,
628        from_node: PluginGraphNode,
629        from_port: usize,
630        to_node: PluginGraphNode,
631        to_port: usize,
632    },
633    #[cfg(all(unix, not(target_os = "macos")))]
634    ListLv2Plugins,
635    #[cfg(all(unix, not(target_os = "macos")))]
636    Lv2Plugins(Vec<Lv2PluginInfo>),
637    ListVst3Plugins,
638    Vst3Plugins(Vec<Vst3PluginInfo>),
639    ListClapPlugins,
640    ListClapPluginsWithCapabilities,
641    ClapPlugins(Vec<ClapPluginInfo>),
642    TrackLoadClapPlugin {
643        track_name: String,
644        plugin_path: String,
645    },
646    TrackUnloadClapPlugin {
647        track_name: String,
648        plugin_path: String,
649    },
650    TrackSetClapParameter {
651        track_name: String,
652        instance_id: usize,
653        param_id: u32,
654        value: f64,
655    },
656    TrackSetClapParameterAt {
657        track_name: String,
658        instance_id: usize,
659        param_id: u32,
660        value: f64,
661        frame: u32,
662    },
663    TrackBeginClapParameterEdit {
664        track_name: String,
665        instance_id: usize,
666        param_id: u32,
667        frame: u32,
668    },
669    TrackEndClapParameterEdit {
670        track_name: String,
671        instance_id: usize,
672        param_id: u32,
673        frame: u32,
674    },
675    TrackGetClapParameters {
676        track_name: String,
677        instance_id: usize,
678    },
679    TrackClapParameters {
680        track_name: String,
681        instance_id: usize,
682        parameters: Vec<ClapParameterInfo>,
683    },
684    TrackClapSnapshotState {
685        track_name: String,
686        instance_id: usize,
687    },
688    ClipClapSnapshotState {
689        track_name: String,
690        clip_idx: usize,
691        instance_id: usize,
692    },
693    TrackClapStateSnapshot {
694        track_name: String,
695        instance_id: usize,
696        plugin_path: String,
697        state: crate::clap::ClapPluginState,
698    },
699    ClipClapStateSnapshot {
700        track_name: String,
701        clip_idx: usize,
702        instance_id: usize,
703        plugin_path: String,
704        state: crate::clap::ClapPluginState,
705    },
706    TrackClapRestoreState {
707        track_name: String,
708        instance_id: usize,
709        state: crate::clap::ClapPluginState,
710    },
711    TrackSnapshotAllClapStates {
712        track_name: String,
713    },
714    TrackLoadVst3Plugin {
715        track_name: String,
716        plugin_path: String,
717    },
718    TrackUnloadVst3PluginInstance {
719        track_name: String,
720        instance_id: usize,
721    },
722    TrackGetVst3Graph {
723        track_name: String,
724    },
725    TrackVst3Graph {
726        track_name: String,
727        plugins: Vec<Vst3GraphPlugin>,
728        connections: Vec<Vst3GraphConnection>,
729    },
730    TrackSetVst3Parameter {
731        track_name: String,
732        instance_id: usize,
733        param_id: u32,
734        value: f32,
735    },
736    TrackGetVst3Parameters {
737        track_name: String,
738        instance_id: usize,
739    },
740    TrackVst3Parameters {
741        track_name: String,
742        instance_id: usize,
743        parameters: Vec<crate::vst3::port::ParameterInfo>,
744    },
745    TrackVst3SnapshotState {
746        track_name: String,
747        instance_id: usize,
748    },
749    ClipVst3SnapshotState {
750        track_name: String,
751        clip_idx: usize,
752        instance_id: usize,
753    },
754    TrackVst3StateSnapshot {
755        track_name: String,
756        instance_id: usize,
757        state: crate::vst3::state::Vst3PluginState,
758    },
759    ClipVst3StateSnapshot {
760        track_name: String,
761        clip_idx: usize,
762        instance_id: usize,
763        state: crate::vst3::state::Vst3PluginState,
764    },
765    TrackVst3RestoreState {
766        track_name: String,
767        instance_id: usize,
768        state: crate::vst3::state::Vst3PluginState,
769    },
770    TrackConnectVst3Audio {
771        track_name: String,
772        from_node: Vst3GraphNode,
773        from_port: usize,
774        to_node: Vst3GraphNode,
775        to_port: usize,
776    },
777    TrackDisconnectVst3Audio {
778        track_name: String,
779        from_node: Vst3GraphNode,
780        from_port: usize,
781        to_node: Vst3GraphNode,
782        to_port: usize,
783    },
784    ClipMove {
785        kind: Kind,
786        from: ClipMoveFrom,
787        to: ClipMoveTo,
788        copy: bool,
789    },
790    Connect {
791        from_track: String,
792        from_port: usize,
793        to_track: String,
794        to_port: usize,
795        kind: Kind,
796    },
797    Disconnect {
798        from_track: String,
799        from_port: usize,
800        to_track: String,
801        to_port: usize,
802        kind: Kind,
803    },
804    OpenAudioDevice {
805        device: String,
806        input_device: Option<String>,
807        sample_rate_hz: i32,
808        bits: i32,
809        exclusive: bool,
810        period_frames: usize,
811        nperiods: usize,
812        sync_mode: bool,
813    },
814    JackAddAudioInputPort,
815    JackRemoveAudioInputPort(usize),
816    JackAddAudioOutputPort,
817    JackRemoveAudioOutputPort(usize),
818    OpenMidiInputDevice(String),
819    OpenMidiOutputDevice(String),
820    RequestSessionDiagnostics,
821    RequestMidiLearnMappingsReport,
822    ClearAllMidiLearnBindings,
823    SessionDiagnosticsReport {
824        track_count: usize,
825        frozen_track_count: usize,
826        audio_clip_count: usize,
827        midi_clip_count: usize,
828        #[cfg(all(unix, not(target_os = "macos")))]
829        lv2_instance_count: usize,
830        vst3_instance_count: usize,
831        clap_instance_count: usize,
832        pending_requests: usize,
833        workers_total: usize,
834        workers_ready: usize,
835        pending_hw_midi_events: usize,
836        playing: bool,
837        transport_sample: usize,
838        tempo_bpm: f64,
839        sample_rate_hz: usize,
840        cycle_samples: usize,
841    },
842    MidiLearnMappingsReport {
843        lines: Vec<String>,
844    },
845    HWInfo {
846        channels: usize,
847        rate: usize,
848        input: bool,
849    },
850    Undo,
851    Redo,
852    Panic,
853}
854
855#[derive(Clone, Debug)]
856pub enum Message {
857    Ready(usize),
858    Finished {
859        worker_id: usize,
860        track_name: String,
861        output_linear: Vec<f32>,
862        process_epoch: usize,
863    },
864    TracksFinished,
865
866    ProcessTrack(Arc<UnsafeMutex<Box<Track>>>),
867    ProcessOfflineBounce(OfflineBounceWork),
868    Channel(Sender<Self>),
869
870    Request(Action),
871    Response(Result<Action, String>),
872    HWMidiEvents(Vec<HwMidiEvent>),
873    HWMidiOutEvents(Vec<HwMidiEvent>),
874    ClearHWMidiOutEvents,
875    HWFinished,
876    OfflineBounceFinished {
877        result: Result<Action, String>,
878    },
879}
880
881#[cfg(test)]
882mod tests {
883    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
884    use serde_json::json;
885
886    #[test]
887    fn audio_clip_data_serde_round_trips_nested_groups() {
888        let clip = AudioClipData {
889            name: "group.wav".to_string(),
890            start: 12,
891            length: 96,
892            offset: 3,
893            input_channel: 1,
894            muted: true,
895            peaks_file: Some("peaks/group.json".to_string()),
896            fade_enabled: false,
897            fade_in_samples: 10,
898            fade_out_samples: 20,
899            preview_name: Some("preview.wav".to_string()),
900            source_name: Some("source.wav".to_string()),
901            source_offset: Some(4),
902            source_length: Some(88),
903            pitch_correction_points: vec![PitchCorrectionPointData {
904                start_sample: 7,
905                length_samples: 11,
906                detected_midi_pitch: 60.1,
907                target_midi_pitch: 61.2,
908                clarity: 0.8,
909            }],
910            pitch_correction_frame_likeness: Some(0.5),
911            pitch_correction_inertia_ms: Some(123),
912            pitch_correction_formant_compensation: Some(false),
913            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
914            grouped_clips: vec![AudioClipData {
915                name: "child.wav".to_string(),
916                start: 0,
917                length: 48,
918                ..AudioClipData::default()
919            }],
920        };
921
922        let value = serde_json::to_value(&clip).expect("serialize");
923        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
924
925        assert_eq!(restored.name, clip.name);
926        assert_eq!(restored.preview_name, clip.preview_name);
927        assert_eq!(restored.source_name, clip.source_name);
928        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
929        assert_eq!(restored.grouped_clips.len(), 1);
930        assert_eq!(restored.grouped_clips[0].name, "child.wav");
931        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
932    }
933
934    #[test]
935    fn midi_clip_data_serde_round_trips_nested_groups() {
936        let clip = MidiClipData {
937            name: "group.mid".to_string(),
938            start: 5,
939            length: 64,
940            offset: 2,
941            input_channel: 3,
942            muted: true,
943            grouped_clips: vec![MidiClipData {
944                name: "child.mid".to_string(),
945                start: 0,
946                length: 32,
947                ..MidiClipData::default()
948            }],
949        };
950
951        let value = serde_json::to_value(&clip).expect("serialize");
952        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
953
954        assert_eq!(restored.name, clip.name);
955        assert_eq!(restored.grouped_clips.len(), 1);
956        assert_eq!(restored.grouped_clips[0].name, "child.mid");
957    }
958
959    #[test]
960    fn pitch_correction_point_data_serde_round_trips() {
961        let point = PitchCorrectionPointData {
962            start_sample: 10,
963            length_samples: 20,
964            detected_midi_pitch: 57.5,
965            target_midi_pitch: 58.0,
966            clarity: 0.9,
967        };
968
969        let value = serde_json::to_value(&point).expect("serialize");
970        let restored: PitchCorrectionPointData =
971            serde_json::from_value(value).expect("deserialize");
972
973        assert_eq!(restored.start_sample, 10);
974        assert_eq!(restored.length_samples, 20);
975        assert_eq!(restored.detected_midi_pitch, 57.5);
976        assert_eq!(restored.target_midi_pitch, 58.0);
977        assert_eq!(restored.clarity, 0.9);
978    }
979
980    #[test]
981    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
982        let restored: AudioClipData = serde_json::from_value(json!({
983            "name": "clip.wav",
984            "start": 1,
985            "length": 2,
986            "offset": 3,
987            "input_channel": 0,
988            "muted": false,
989            "fade_enabled": true,
990            "fade_in_samples": 240,
991            "fade_out_samples": 240,
992            "pitch_correction_points": [],
993            "grouped_clips": []
994        }))
995        .expect("deserialize");
996
997        assert_eq!(restored.name, "clip.wav");
998        assert!(restored.peaks_file.is_none());
999        assert!(restored.preview_name.is_none());
1000        assert!(restored.source_name.is_none());
1001        assert!(restored.source_offset.is_none());
1002        assert!(restored.source_length.is_none());
1003        assert!(restored.pitch_correction_points.is_empty());
1004        assert!(restored.plugin_graph_json.is_none());
1005    }
1006}