Skip to main content

maolan_engine/
message.rs

1use crate::clap::{ClapParameterInfo, ClapPluginInfo};
2pub use crate::connectable::{ConnectableConnection, ConnectableRef};
3#[cfg(all(unix, not(target_os = "macos")))]
4use crate::lv2::Lv2PluginInfo;
5use crate::midi::io::MidiEvent;
6use crate::vst3::Vst3PluginInfo;
7use crate::{kind::Kind, modulator::Modulator, mutex::UnsafeMutex, track::Track};
8use std::sync::{Arc, atomic::AtomicBool};
9use tokio::sync::mpsc::Sender;
10
11#[derive(Clone, Debug, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
12pub struct TrackColor {
13    pub r: f32,
14    pub g: f32,
15    pub b: f32,
16    pub a: f32,
17}
18
19#[derive(Clone, Debug)]
20pub struct MidiNoteData {
21    pub start_sample: usize,
22    pub length_samples: usize,
23    pub pitch: u8,
24    pub velocity: u8,
25    pub channel: u8,
26}
27
28#[derive(Clone, Debug)]
29pub struct MidiControllerData {
30    pub sample: usize,
31    pub controller: u8,
32    pub value: u8,
33    pub channel: u8,
34}
35
36#[derive(Debug, Clone)]
37pub struct MidiRawEventData {
38    pub sample: usize,
39    pub data: Vec<u8>,
40}
41
42#[derive(Clone, Debug)]
43pub struct HwMidiEvent {
44    pub device: String,
45    pub event: MidiEvent,
46}
47
48#[derive(Clone, Debug)]
49pub struct OfflineAutomationPoint {
50    pub sample: usize,
51    pub value: f32,
52}
53
54#[derive(Clone, Debug)]
55pub enum OfflineAutomationTarget {
56    Volume,
57    Balance,
58    MidiCc {
59        channel: u8,
60        cc: u8,
61    },
62    #[cfg(all(unix, not(target_os = "macos")))]
63    Lv2Parameter {
64        instance_id: usize,
65        index: u32,
66        min: f32,
67        max: f32,
68    },
69    Vst3Parameter {
70        instance_id: usize,
71        param_id: u32,
72    },
73    ClapParameter {
74        instance_id: usize,
75        param_id: u32,
76        min: f64,
77        max: f64,
78    },
79}
80
81#[derive(Clone, Debug)]
82pub struct OfflineAutomationLane {
83    pub target: OfflineAutomationTarget,
84    pub points: Vec<OfflineAutomationPoint>,
85}
86
87#[derive(Clone, Debug)]
88pub struct OfflineBounceWork {
89    pub state: Arc<UnsafeMutex<crate::state::State>>,
90    pub track_name: String,
91    pub output_path: String,
92    pub start_sample: usize,
93    pub length_samples: usize,
94    pub tempo_bpm: f64,
95    pub tsig_num: u16,
96    pub tsig_denom: u16,
97    pub automation_lanes: Vec<OfflineAutomationLane>,
98    pub cancel: Arc<AtomicBool>,
99    pub apply_fader: bool,
100}
101
102#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
103pub struct PitchCorrectionPointData {
104    pub start_sample: usize,
105    pub length_samples: usize,
106    pub detected_midi_pitch: f32,
107    pub target_midi_pitch: f32,
108    pub clarity: f32,
109}
110
111#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
112pub struct AudioClipData {
113    #[serde(default)]
114    pub id: String,
115    pub name: String,
116    pub start: usize,
117    pub length: usize,
118    pub offset: usize,
119    pub input_channel: usize,
120    pub muted: bool,
121    pub peaks_file: Option<String>,
122    pub fade_enabled: bool,
123    pub fade_in_samples: usize,
124    pub fade_out_samples: usize,
125    pub preview_name: Option<String>,
126    pub source_name: Option<String>,
127    pub source_offset: Option<usize>,
128    pub source_length: Option<usize>,
129    pub pitch_correction_points: Vec<PitchCorrectionPointData>,
130    pub pitch_correction_frame_likeness: Option<f32>,
131    pub pitch_correction_inertia_ms: Option<u16>,
132    pub pitch_correction_formant_compensation: Option<bool>,
133    pub plugin_graph_json: Option<serde_json::Value>,
134    pub grouped_clips: Vec<AudioClipData>,
135}
136
137#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
138pub struct MidiClipData {
139    #[serde(default)]
140    pub id: String,
141    pub name: String,
142    pub start: usize,
143    pub length: usize,
144    pub offset: usize,
145    pub input_channel: usize,
146    pub muted: bool,
147    pub grouped_clips: Vec<MidiClipData>,
148}
149
150/// Generates a new unique clip identifier.
151pub fn generate_clip_id() -> String {
152    uuid::Uuid::new_v4().to_string()
153}
154
155#[derive(Clone, Debug)]
156pub struct ClipMoveFrom {
157    pub track_name: String,
158    pub clip_index: usize,
159}
160
161#[derive(Clone, Debug)]
162pub struct ClipMoveTo {
163    pub track_name: String,
164    pub sample_offset: usize,
165    pub input_channel: usize,
166}
167
168#[derive(Clone, Debug, PartialEq, Eq, Hash)]
169pub enum PluginGraphNode {
170    TrackInput,
171    TrackOutput,
172    ClapPluginInstance(usize),
173    Vst3PluginInstance(usize),
174    #[cfg(all(unix, not(target_os = "macos")))]
175    Lv2PluginInstance(usize),
176}
177
178#[derive(Clone, Debug, PartialEq)]
179pub struct PluginGraphPlugin {
180    pub node: PluginGraphNode,
181    pub instance_id: usize,
182    pub format: String,
183    pub uri: String,
184    pub plugin_id: String,
185    pub name: String,
186    pub main_audio_inputs: usize,
187    pub main_audio_outputs: usize,
188    pub audio_inputs: usize,
189    pub audio_outputs: usize,
190    pub midi_inputs: usize,
191    pub midi_outputs: usize,
192    pub state: Option<serde_json::Value>,
193    pub bypassed: bool,
194}
195
196#[cfg(unix)]
197#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
198pub struct Lv2StatePortValue {
199    pub index: u32,
200    pub value: f32,
201}
202
203#[cfg(unix)]
204#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
205pub struct Lv2StateProperty {
206    pub key_uri: String,
207    pub type_uri: String,
208    pub flags: u32,
209    pub value: Vec<u8>,
210}
211
212#[cfg(unix)]
213#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
214pub struct Lv2PluginState {
215    pub port_values: Vec<Lv2StatePortValue>,
216    pub properties: Vec<Lv2StateProperty>,
217}
218
219#[cfg(all(unix, not(target_os = "macos")))]
220#[derive(Clone, Debug, PartialEq)]
221pub struct Lv2ControlPortInfo {
222    pub index: u32,
223    pub name: String,
224    pub min: f32,
225    pub max: f32,
226    pub value: f32,
227}
228
229#[derive(Clone, Debug, PartialEq, Eq)]
230pub struct PluginGraphConnection {
231    pub from_node: PluginGraphNode,
232    pub from_port: usize,
233    pub to_node: PluginGraphNode,
234    pub to_port: usize,
235    pub kind: Kind,
236}
237
238pub type PluginGraphSnapshot = (Vec<PluginGraphPlugin>, Vec<PluginGraphConnection>);
239
240#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
241pub enum PluginKind {
242    Clap,
243    Vst3,
244    #[cfg(all(unix, not(target_os = "macos")))]
245    Lv2,
246}
247
248#[derive(Clone, Debug)]
249pub enum ProcessTask {
250    Track(Arc<UnsafeMutex<Box<Track>>>),
251    FolderInput(Arc<UnsafeMutex<Box<Track>>>),
252    FolderOutput(Arc<UnsafeMutex<Box<Track>>>),
253    Plugin {
254        track: Arc<UnsafeMutex<Box<Track>>>,
255        kind: PluginKind,
256        index: usize,
257    },
258}
259
260impl PartialEq for ProcessTask {
261    fn eq(&self, other: &Self) -> bool {
262        use ProcessTask::*;
263        match (self, other) {
264            (Track(a), Track(b))
265            | (FolderInput(a), FolderInput(b))
266            | (FolderOutput(a), FolderOutput(b)) => Arc::ptr_eq(a, b),
267            (
268                Plugin {
269                    track: a,
270                    kind: ka,
271                    index: ia,
272                },
273                Plugin {
274                    track: b,
275                    kind: kb,
276                    index: ib,
277                },
278            ) => Arc::ptr_eq(a, b) && ka == kb && ia == ib,
279            _ => false,
280        }
281    }
282}
283
284impl Eq for ProcessTask {}
285
286impl std::hash::Hash for ProcessTask {
287    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
288        use ProcessTask::*;
289        match self {
290            Track(t) | FolderInput(t) | FolderOutput(t) => {
291                Arc::as_ptr(t).hash(state);
292            }
293            Plugin { track, kind, index } => {
294                Arc::as_ptr(track).hash(state);
295                kind.hash(state);
296                index.hash(state);
297            }
298        }
299    }
300}
301
302#[derive(Clone, Debug, PartialEq, Eq, Hash)]
303pub enum Vst3GraphNode {
304    TrackInput,
305    TrackOutput,
306    PluginInstance(usize),
307}
308
309#[derive(Clone, Debug)]
310pub struct Vst3GraphPlugin {
311    pub instance_id: usize,
312    pub name: String,
313    pub path: String,
314    pub audio_inputs: usize,
315    pub audio_outputs: usize,
316    pub parameters: Vec<crate::vst3::port::ParameterInfo>,
317}
318
319#[derive(Clone, Debug, PartialEq, Eq)]
320pub struct Vst3GraphConnection {
321    pub from_node: Vst3GraphNode,
322    pub from_port: usize,
323    pub to_node: Vst3GraphNode,
324    pub to_port: usize,
325    pub kind: Kind,
326}
327
328#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
329pub struct MidiLearnBinding {
330    pub device: Option<String>,
331    pub channel: u8,
332    pub cc: u8,
333}
334
335#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
336pub enum TrackMidiLearnTarget {
337    Volume,
338    Balance,
339    Mute,
340    Solo,
341    Arm,
342    InputMonitor,
343    DiskMonitor,
344}
345
346#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
347pub enum GlobalMidiLearnTarget {
348    PlayPause,
349    Stop,
350    RecordToggle,
351}
352
353#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
354pub enum SessionMidiLearnTarget {
355    Slot {
356        track_name: String,
357        scene_index: usize,
358    },
359    Scene(usize),
360    StopTrack(String),
361    StopAll,
362}
363
364#[derive(Clone, Copy, Debug, PartialEq, Eq)]
365pub enum SessionSlotState {
366    Stopped,
367    Queued,
368    Playing,
369    Stopping,
370}
371
372#[derive(Clone, Copy, Debug, PartialEq, Eq)]
373pub enum LaunchQuantization {
374    None,
375    Beat,
376    Bar,
377    TwoBars,
378    FourBars,
379    EightBars,
380}
381
382#[derive(Clone, Debug)]
383pub enum SessionAction {
384    LaunchClip {
385        track_name: String,
386        scene_index: usize,
387        clip_id: String,
388        launch_quantization: LaunchQuantization,
389        loop_enabled: bool,
390        loop_start_samples: usize,
391        loop_end_samples: usize,
392    },
393    StopClip {
394        track_name: String,
395        scene_index: usize,
396        launch_quantization: LaunchQuantization,
397    },
398    LaunchScene {
399        scene_index: usize,
400        launch_quantization: LaunchQuantization,
401    },
402    StopScene {
403        scene_index: usize,
404        launch_quantization: LaunchQuantization,
405    },
406    StopAllClips,
407}
408
409#[derive(Clone, Debug)]
410pub enum Action {
411    Quit,
412    Log {
413        source: String,
414        message: String,
415    },
416    Play,
417    Pause,
418    Stop,
419    TransportPosition(usize),
420    TransportPositionAt {
421        sample: usize,
422        after_frames: usize,
423    },
424    JumpToEnd,
425    SetLoopEnabled(bool),
426    SetLoopRange(Option<(usize, usize)>),
427    SetPunchEnabled(bool),
428    SetPunchRange(Option<(usize, usize)>),
429    SetMetronomeEnabled(bool),
430    SetTempo(f64),
431    SetTimeSignature {
432        numerator: u16,
433        denominator: u16,
434    },
435    SetOscEnabled(bool),
436    SetClipPlaybackEnabled(bool),
437    SetRecordEnabled(bool),
438    SetModulators(Vec<Modulator>),
439    SetSessionPath(String),
440    BeginHistoryGroup,
441    EndHistoryGroup,
442    ApplyGroupedActions(Vec<Action>),
443    ClearHistory,
444    BeginSessionRestore,
445    EndSessionRestore,
446    AddTrack {
447        name: String,
448        audio_ins: usize,
449        midi_ins: usize,
450        audio_outs: usize,
451        midi_outs: usize,
452        folder: bool,
453    },
454    TrackAddAudioInput(String),
455    TrackAddAudioOutput(String),
456    TrackRemoveAudioInput(String),
457    TrackRemoveAudioOutput(String),
458    AddClip {
459        clip_id: String,
460        name: String,
461        track_name: String,
462        start: usize,
463        length: usize,
464        offset: usize,
465        input_channel: usize,
466        muted: bool,
467        peaks_file: Option<String>,
468        kind: Kind,
469        fade_enabled: bool,
470        fade_in_samples: usize,
471        fade_out_samples: usize,
472        source_name: Option<String>,
473        source_offset: Option<usize>,
474        source_length: Option<usize>,
475        preview_name: Option<String>,
476        pitch_correction_points: Vec<PitchCorrectionPointData>,
477        pitch_correction_frame_likeness: Option<f32>,
478        pitch_correction_inertia_ms: Option<u16>,
479        pitch_correction_formant_compensation: Option<bool>,
480        plugin_graph_json: Option<serde_json::Value>,
481    },
482    AddGroupedClip {
483        track_name: String,
484        kind: Kind,
485        audio_clip: Option<AudioClipData>,
486        midi_clip: Option<MidiClipData>,
487    },
488    RemoveClip {
489        track_name: String,
490        kind: Kind,
491        clip_indices: Vec<usize>,
492    },
493    SetClipFade {
494        track_name: String,
495        clip_index: usize,
496        kind: Kind,
497        fade_enabled: bool,
498        fade_in_samples: usize,
499        fade_out_samples: usize,
500    },
501    SetClipBounds {
502        track_name: String,
503        clip_index: usize,
504        kind: Kind,
505        start: usize,
506        length: usize,
507        offset: usize,
508    },
509    SyncClipBounds {
510        track_name: String,
511        clip_index: usize,
512        kind: Kind,
513        start: usize,
514        length: usize,
515        offset: usize,
516    },
517    SetClipMuted {
518        track_name: String,
519        clip_index: usize,
520        kind: Kind,
521        muted: bool,
522    },
523    SetClipPluginGraphJson {
524        track_name: String,
525        clip_index: usize,
526        plugin_graph_json: Option<serde_json::Value>,
527    },
528    SetClipPitchCorrection {
529        track_name: String,
530        clip_index: usize,
531        preview_name: Option<String>,
532        source_name: Option<String>,
533        source_offset: Option<usize>,
534        source_length: Option<usize>,
535        pitch_correction_points: Vec<PitchCorrectionPointData>,
536        pitch_correction_frame_likeness: Option<f32>,
537        pitch_correction_inertia_ms: Option<u16>,
538        pitch_correction_formant_compensation: Option<bool>,
539    },
540    RenameClip {
541        track_name: String,
542        kind: Kind,
543        clip_index: usize,
544        new_name: String,
545    },
546    SetClipSourceName {
547        track_name: String,
548        kind: Kind,
549        clip_index: usize,
550        name: String,
551    },
552    RenameTrack {
553        old_name: String,
554        new_name: String,
555    },
556    RemoveTrack(String),
557    TrackLevel(String, f32),
558    TrackBalance(String, f32),
559    TrackAutomationLevel(String, f32),
560    TrackAutomationBalance(String, f32),
561    TrackMidiCc {
562        track_name: String,
563        channel: u8,
564        cc: u8,
565        value: u8,
566    },
567    TrackMeters {
568        track_name: String,
569        output_db: Vec<f32>,
570    },
571    RequestMeterSnapshot,
572    MeterSnapshot {
573        hw_out_db: Arc<Vec<f32>>,
574        track_meters: Arc<Vec<(String, Vec<f32>)>>,
575    },
576    TrackToggleArm(String),
577    TrackToggleMute(String),
578    TrackTogglePhase(String),
579    TrackToggleSolo(String),
580    TrackToggleMaster(String),
581    TrackToggleInputMonitor {
582        track_name: String,
583        lane: usize,
584    },
585    TrackToggleDiskMonitor {
586        track_name: String,
587        lane: usize,
588    },
589    TrackToggleMidiInputMonitor {
590        track_name: String,
591        lane: usize,
592    },
593    TrackToggleMidiDiskMonitor {
594        track_name: String,
595        lane: usize,
596    },
597    TrackSetColor {
598        track_name: String,
599        color: Option<TrackColor>,
600    },
601    TrackArmMidiLearn {
602        track_name: String,
603        target: TrackMidiLearnTarget,
604    },
605    GlobalArmMidiLearn {
606        target: GlobalMidiLearnTarget,
607    },
608    SessionArmMidiLearn {
609        target: SessionMidiLearnTarget,
610    },
611    TrackSetMidiLearnBinding {
612        track_name: String,
613        target: TrackMidiLearnTarget,
614        binding: Option<MidiLearnBinding>,
615    },
616    SetGlobalMidiLearnBinding {
617        target: GlobalMidiLearnTarget,
618        binding: Option<MidiLearnBinding>,
619    },
620    SetSessionMidiLearnBinding {
621        target: SessionMidiLearnTarget,
622        binding: Option<MidiLearnBinding>,
623    },
624    SessionMidiLearnTriggered {
625        target: SessionMidiLearnTarget,
626    },
627    TrackSetFolder {
628        track_name: String,
629        is_folder: bool,
630    },
631    TrackSetParent {
632        track_name: String,
633        parent_name: Option<String>,
634    },
635    TrackToggleFolder {
636        track_name: String,
637    },
638    TrackSetMidiLaneChannel {
639        track_name: String,
640        lane: usize,
641        channel: Option<u8>,
642    },
643    TrackSetFrozen {
644        track_name: String,
645        frozen: bool,
646    },
647    TrackSetSessionSlot {
648        track_name: String,
649        scene_index: usize,
650        clip_id: Option<String>,
651    },
652    TrackOfflineBounce {
653        track_name: String,
654        output_path: String,
655        start_sample: usize,
656        length_samples: usize,
657        automation_lanes: Vec<OfflineAutomationLane>,
658        apply_fader: bool,
659    },
660    TrackOfflineBounceCancel {
661        track_name: String,
662    },
663    TrackOfflineBounceCancelAll,
664    TrackOfflineBounceCanceled {
665        track_name: String,
666    },
667    TrackOfflineBounceProgress {
668        track_name: String,
669        progress: f32,
670        operation: Option<String>,
671    },
672    PianoKey {
673        track_name: String,
674        note: u8,
675        velocity: u8,
676        on: bool,
677    },
678    ModifyMidiNotes {
679        track_name: String,
680        clip_index: usize,
681        note_indices: Vec<usize>,
682        new_notes: Vec<MidiNoteData>,
683        old_notes: Vec<MidiNoteData>,
684    },
685    ModifyMidiControllers {
686        track_name: String,
687        clip_index: usize,
688        controller_indices: Vec<usize>,
689        new_controllers: Vec<MidiControllerData>,
690        old_controllers: Vec<MidiControllerData>,
691    },
692    DeleteMidiControllers {
693        track_name: String,
694        clip_index: usize,
695        controller_indices: Vec<usize>,
696        deleted_controllers: Vec<(usize, MidiControllerData)>,
697    },
698    InsertMidiControllers {
699        track_name: String,
700        clip_index: usize,
701        controllers: Vec<(usize, MidiControllerData)>,
702    },
703    DeleteMidiNotes {
704        track_name: String,
705        clip_index: usize,
706        note_indices: Vec<usize>,
707        deleted_notes: Vec<(usize, MidiNoteData)>,
708    },
709    InsertMidiNotes {
710        track_name: String,
711        clip_index: usize,
712        notes: Vec<(usize, MidiNoteData)>,
713    },
714    SetStepRecording(bool),
715    StepRecordMidiNote {
716        device: String,
717        channel: u8,
718        pitch: u8,
719        velocity: u8,
720    },
721    SetMidiSysExEvents {
722        track_name: String,
723        clip_index: usize,
724        new_sysex_events: Vec<MidiRawEventData>,
725        old_sysex_events: Vec<MidiRawEventData>,
726    },
727    TrackClearDefaultPassthrough {
728        track_name: String,
729    },
730    TrackClearPlugins {
731        track_name: String,
732    },
733    #[cfg(all(unix, not(target_os = "macos")))]
734    TrackSetLv2PluginState {
735        track_name: String,
736        instance_id: usize,
737        state: Vec<u8>,
738    },
739    #[cfg(all(unix, not(target_os = "macos")))]
740    ClipSetLv2PluginState {
741        track_name: String,
742        clip_idx: usize,
743        instance_id: usize,
744        state: Vec<u8>,
745    },
746    #[cfg(all(unix, not(target_os = "macos")))]
747    TrackGetLv2PluginControls {
748        track_name: String,
749        instance_id: usize,
750    },
751    #[cfg(all(unix, not(target_os = "macos")))]
752    ClipGetLv2PluginControls {
753        track_name: String,
754        clip_idx: usize,
755        instance_id: usize,
756    },
757    #[cfg(all(unix, not(target_os = "macos")))]
758    TrackLv2PluginControls {
759        track_name: String,
760        instance_id: usize,
761        controls: Vec<Lv2ControlPortInfo>,
762        instance_access_handle: Option<usize>,
763    },
764    #[cfg(all(unix, not(target_os = "macos")))]
765    ClipLv2PluginControls {
766        track_name: String,
767        clip_idx: usize,
768        instance_id: usize,
769        controls: Vec<Lv2ControlPortInfo>,
770        instance_access_handle: Option<usize>,
771    },
772    #[cfg(all(unix, not(target_os = "macos")))]
773    TrackGetLv2Midnam {
774        track_name: String,
775    },
776    #[cfg(all(unix, not(target_os = "macos")))]
777    TrackLv2Midnam {
778        track_name: String,
779        note_names: std::collections::HashMap<u8, String>,
780    },
781    TrackGetClapNoteNames {
782        track_name: String,
783    },
784    TrackClapNoteNames {
785        track_name: String,
786        note_names: std::collections::HashMap<u8, String>,
787    },
788    #[cfg(all(unix, not(target_os = "macos")))]
789    TrackSetLv2ControlValue {
790        track_name: String,
791        instance_id: usize,
792        index: u32,
793        value: f32,
794    },
795    #[cfg(all(unix, not(target_os = "macos")))]
796    ClipSetLv2ControlValue {
797        track_name: String,
798        clip_idx: usize,
799        instance_id: usize,
800        index: u32,
801        value: f32,
802    },
803    #[cfg(all(unix, not(target_os = "macos")))]
804    ClipLv2StateSnapshot {
805        track_name: String,
806        clip_idx: usize,
807        instance_id: usize,
808        state: Vec<u8>,
809    },
810    TrackGetPluginGraph {
811        track_name: String,
812    },
813    TrackPluginGraph {
814        track_name: String,
815        plugins: Vec<PluginGraphPlugin>,
816        connections: Vec<PluginGraphConnection>,
817        connectable_connections: Vec<ConnectableConnection>,
818    },
819    TrackConnectPluginAudio {
820        track_name: String,
821        from_node: PluginGraphNode,
822        from_port: usize,
823        to_node: PluginGraphNode,
824        to_port: usize,
825    },
826    TrackConnectPluginMidi {
827        track_name: String,
828        from_node: PluginGraphNode,
829        from_port: usize,
830        to_node: PluginGraphNode,
831        to_port: usize,
832    },
833    TrackDisconnectPluginAudio {
834        track_name: String,
835        from_node: PluginGraphNode,
836        from_port: usize,
837        to_node: PluginGraphNode,
838        to_port: usize,
839    },
840    TrackDisconnectPluginMidi {
841        track_name: String,
842        from_node: PluginGraphNode,
843        from_port: usize,
844        to_node: PluginGraphNode,
845        to_port: usize,
846    },
847    TrackConnectAudio {
848        track_name: String,
849        from: ConnectableRef,
850        from_port: usize,
851        to: ConnectableRef,
852        to_port: usize,
853    },
854    TrackDisconnectAudio {
855        track_name: String,
856        from: ConnectableRef,
857        from_port: usize,
858        to: ConnectableRef,
859        to_port: usize,
860    },
861    TrackConnectMidi {
862        track_name: String,
863        from: ConnectableRef,
864        from_port: usize,
865        to: ConnectableRef,
866        to_port: usize,
867    },
868    TrackDisconnectMidi {
869        track_name: String,
870        from: ConnectableRef,
871        from_port: usize,
872        to: ConnectableRef,
873        to_port: usize,
874    },
875    #[cfg(all(unix, not(target_os = "macos")))]
876    ListLv2Plugins,
877    #[cfg(all(unix, not(target_os = "macos")))]
878    Lv2Plugins(Vec<Lv2PluginInfo>),
879    #[cfg(all(unix, not(target_os = "macos")))]
880    Lv2PluginsUnavailable {
881        error: String,
882    },
883    ListVst3Plugins,
884    Vst3Plugins(Vec<Vst3PluginInfo>),
885    Vst3PluginsUnavailable {
886        error: String,
887    },
888    ListClapPlugins,
889    ListClapPluginsWithCapabilities,
890    ClapPlugins(Vec<ClapPluginInfo>),
891    ClapPluginsUnavailable {
892        error: String,
893    },
894    TrackSetClapParameter {
895        track_name: String,
896        instance_id: usize,
897        param_id: u32,
898        value: f64,
899    },
900    ClipSetClapParameter {
901        track_name: String,
902        clip_idx: usize,
903        instance_id: usize,
904        param_id: u32,
905        value: f64,
906    },
907    TrackSetClapParameterAt {
908        track_name: String,
909        instance_id: usize,
910        param_id: u32,
911        value: f64,
912        frame: u32,
913    },
914    TrackBeginClapParameterEdit {
915        track_name: String,
916        instance_id: usize,
917        param_id: u32,
918        frame: u32,
919    },
920    TrackEndClapParameterEdit {
921        track_name: String,
922        instance_id: usize,
923        param_id: u32,
924        frame: u32,
925    },
926    TrackGetClapParameters {
927        track_name: String,
928        instance_id: usize,
929    },
930    TrackClapParameters {
931        track_name: String,
932        instance_id: usize,
933        parameters: Vec<ClapParameterInfo>,
934    },
935    TrackClapSnapshotState {
936        track_name: String,
937        instance_id: usize,
938    },
939    ClipClapSnapshotState {
940        track_name: String,
941        clip_idx: usize,
942        instance_id: usize,
943    },
944    TrackClapStateSnapshot {
945        track_name: String,
946        instance_id: usize,
947        plugin_path: String,
948        state: crate::clap::ClapPluginState,
949    },
950    ClipClapStateSnapshot {
951        track_name: String,
952        clip_idx: usize,
953        instance_id: usize,
954        plugin_path: String,
955        state: crate::clap::ClapPluginState,
956    },
957    TrackClapStateDirty {
958        track_name: String,
959        instance_id: usize,
960    },
961    ClipClapStateDirty {
962        track_name: String,
963        clip_idx: usize,
964        instance_id: usize,
965    },
966    TrackClapRestoreState {
967        track_name: String,
968        instance_id: usize,
969        state: crate::clap::ClapPluginState,
970    },
971    ClipClapRestoreState {
972        track_name: String,
973        clip_idx: usize,
974        instance_id: usize,
975        state: crate::clap::ClapPluginState,
976    },
977    TrackSnapshotAllClapStates {
978        track_name: String,
979    },
980    TrackSnapshotAllClapStatesDone {
981        track_name: String,
982    },
983    TrackLoadClapPlugin {
984        track_name: String,
985        plugin_path: String,
986        instance_id: Option<usize>,
987    },
988    TrackUnloadClapPlugin {
989        track_name: String,
990        plugin_path: String,
991    },
992    TrackUnloadClapPluginInstance {
993        track_name: String,
994        instance_id: usize,
995    },
996    TrackShowClapGui {
997        track_name: String,
998        instance_id: usize,
999    },
1000    TrackLoadVst3Plugin {
1001        track_name: String,
1002        plugin_path: String,
1003        instance_id: Option<usize>,
1004    },
1005    TrackUnloadVst3Plugin {
1006        track_name: String,
1007        plugin_path: String,
1008    },
1009    TrackUnloadVst3PluginInstance {
1010        track_name: String,
1011        instance_id: usize,
1012    },
1013    TrackShowVst3Gui {
1014        track_name: String,
1015        instance_id: usize,
1016    },
1017    #[cfg(all(unix, not(target_os = "macos")))]
1018    TrackLoadLv2Plugin {
1019        track_name: String,
1020        plugin_uri: String,
1021        instance_id: Option<usize>,
1022    },
1023    #[cfg(all(unix, not(target_os = "macos")))]
1024    TrackUnloadLv2Plugin {
1025        track_name: String,
1026        plugin_uri: String,
1027    },
1028    #[cfg(all(unix, not(target_os = "macos")))]
1029    TrackUnloadLv2PluginInstance {
1030        track_name: String,
1031        instance_id: usize,
1032    },
1033    TrackShowLv2Gui {
1034        track_name: String,
1035        instance_id: usize,
1036    },
1037    TrackSetPluginResourceDir {
1038        track_name: String,
1039        instance_id: usize,
1040        format: String,
1041        directory: String,
1042    },
1043    TrackClapFileReferences {
1044        track_name: String,
1045        instance_id: usize,
1046        refs: Vec<(u32, String)>,
1047    },
1048    TrackUpdateClapFileReference {
1049        track_name: String,
1050        instance_id: usize,
1051        index: u32,
1052        path: String,
1053    },
1054    ClipSetPluginResourceDir {
1055        track_name: String,
1056        clip_idx: usize,
1057        instance_id: usize,
1058        format: String,
1059        directory: String,
1060    },
1061    ClipClapFileReferences {
1062        track_name: String,
1063        clip_idx: usize,
1064        instance_id: usize,
1065        refs: Vec<(u32, String)>,
1066    },
1067    ClipUpdateClapFileReference {
1068        track_name: String,
1069        clip_idx: usize,
1070        instance_id: usize,
1071        index: u32,
1072        path: String,
1073    },
1074    TrackGetVst3Graph {
1075        track_name: String,
1076    },
1077    TrackVst3Graph {
1078        track_name: String,
1079        plugins: Vec<Vst3GraphPlugin>,
1080        connections: Vec<Vst3GraphConnection>,
1081    },
1082    TrackSetVst3Parameter {
1083        track_name: String,
1084        instance_id: usize,
1085        param_id: u32,
1086        value: f32,
1087    },
1088    TrackSetPluginBypassed {
1089        track_name: String,
1090        instance_id: usize,
1091        format: String,
1092        bypassed: bool,
1093    },
1094    TrackGetVst3Parameters {
1095        track_name: String,
1096        instance_id: usize,
1097    },
1098    TrackVst3Parameters {
1099        track_name: String,
1100        instance_id: usize,
1101        parameters: Vec<crate::vst3::port::ParameterInfo>,
1102    },
1103    TrackVst3SnapshotState {
1104        track_name: String,
1105        instance_id: usize,
1106    },
1107    ClipVst3SnapshotState {
1108        track_name: String,
1109        clip_idx: usize,
1110        instance_id: usize,
1111    },
1112    TrackVst3StateSnapshot {
1113        track_name: String,
1114        instance_id: usize,
1115        state: crate::vst3::state::Vst3PluginState,
1116    },
1117    ClipVst3StateSnapshot {
1118        track_name: String,
1119        clip_idx: usize,
1120        instance_id: usize,
1121        state: crate::vst3::state::Vst3PluginState,
1122    },
1123    TrackVst3RestoreState {
1124        track_name: String,
1125        instance_id: usize,
1126        state: crate::vst3::state::Vst3PluginState,
1127    },
1128    TrackConnectVst3Audio {
1129        track_name: String,
1130        from_node: Vst3GraphNode,
1131        from_port: usize,
1132        to_node: Vst3GraphNode,
1133        to_port: usize,
1134    },
1135    TrackDisconnectVst3Audio {
1136        track_name: String,
1137        from_node: Vst3GraphNode,
1138        from_port: usize,
1139        to_node: Vst3GraphNode,
1140        to_port: usize,
1141    },
1142    ClipMove {
1143        kind: Kind,
1144        from: ClipMoveFrom,
1145        to: ClipMoveTo,
1146        copy: bool,
1147    },
1148    Connect {
1149        from_track: String,
1150        from_port: usize,
1151        to_track: String,
1152        to_port: usize,
1153        kind: Kind,
1154    },
1155    Disconnect {
1156        from_track: String,
1157        from_port: usize,
1158        to_track: String,
1159        to_port: usize,
1160        kind: Kind,
1161    },
1162    OpenAudioDevice {
1163        device: String,
1164        input_device: Option<String>,
1165        sample_rate_hz: i32,
1166        bits: i32,
1167        exclusive: bool,
1168        period_frames: usize,
1169        nperiods: usize,
1170        sync_mode: bool,
1171        actual_period_frames: usize,
1172        input_channels: usize,
1173        output_channels: usize,
1174        bytes_per_frame: usize,
1175    },
1176    JackAddAudioInputPort,
1177    JackRemoveAudioInputPort(usize),
1178    JackAddAudioOutputPort,
1179    JackRemoveAudioOutputPort(usize),
1180    OpenMidiInputDevice(String),
1181    OpenMidiOutputDevice(String),
1182    RequestSessionDiagnostics,
1183    RequestMidiLearnMappingsReport,
1184    ClearAllMidiLearnBindings,
1185    SessionDiagnosticsReport {
1186        track_count: usize,
1187        frozen_track_count: usize,
1188        audio_clip_count: usize,
1189        midi_clip_count: usize,
1190        #[cfg(all(unix, not(target_os = "macos")))]
1191        lv2_instance_count: usize,
1192        vst3_instance_count: usize,
1193        clap_instance_count: usize,
1194        pending_requests: usize,
1195        workers_total: usize,
1196        workers_ready: usize,
1197        pending_hw_midi_events: usize,
1198        playing: bool,
1199        transport_sample: usize,
1200        tempo_bpm: f64,
1201        sample_rate_hz: usize,
1202        cycle_samples: usize,
1203    },
1204    MidiLearnMappingsReport {
1205        lines: Vec<String>,
1206    },
1207    HWInfo {
1208        channels: usize,
1209        rate: usize,
1210        input: bool,
1211    },
1212    MarkHistorySavePoint,
1213    HistoryState {
1214        dirty: bool,
1215    },
1216    Undo,
1217    Redo,
1218    Session(SessionAction),
1219    SessionRuntimeReport {
1220        track_name: String,
1221        scene_index: usize,
1222        state: SessionSlotState,
1223        play_position_samples: usize,
1224    },
1225    Panic,
1226}
1227
1228#[derive(Clone, Debug)]
1229pub enum Message {
1230    Ready(usize),
1231    Finished {
1232        worker_id: usize,
1233        task: ProcessTask,
1234        output_linear: Vec<f32>,
1235        process_epoch: usize,
1236        parameter_updates: Vec<Action>,
1237    },
1238    TracksFinished,
1239
1240    ProcessTask(ProcessTask),
1241    ProcessOfflineBounce(OfflineBounceWork),
1242    Channel(Sender<Self>),
1243
1244    Request(Action),
1245    Response(Result<Action, String>),
1246    HWMidiEvents(Vec<HwMidiEvent>),
1247    HWMidiOutEvents(Vec<HwMidiEvent>),
1248    ClearHWMidiOutEvents,
1249    HWFinished,
1250    OfflineBounceFinished {
1251        result: Result<Action, String>,
1252    },
1253}
1254
1255#[cfg(test)]
1256mod tests {
1257    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1258    use serde_json::json;
1259
1260    #[test]
1261    fn audio_clip_data_serde_round_trips_nested_groups() {
1262        let clip = AudioClipData {
1263            id: String::new(),
1264            name: "group.wav".to_string(),
1265            start: 12,
1266            length: 96,
1267            offset: 3,
1268            input_channel: 1,
1269            muted: true,
1270            peaks_file: Some("peaks/group.json".to_string()),
1271            fade_enabled: false,
1272            fade_in_samples: 10,
1273            fade_out_samples: 20,
1274            preview_name: Some("preview.wav".to_string()),
1275            source_name: Some("source.wav".to_string()),
1276            source_offset: Some(4),
1277            source_length: Some(88),
1278            pitch_correction_points: vec![PitchCorrectionPointData {
1279                start_sample: 7,
1280                length_samples: 11,
1281                detected_midi_pitch: 60.1,
1282                target_midi_pitch: 61.2,
1283                clarity: 0.8,
1284            }],
1285            pitch_correction_frame_likeness: Some(0.5),
1286            pitch_correction_inertia_ms: Some(123),
1287            pitch_correction_formant_compensation: Some(false),
1288            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1289            grouped_clips: vec![AudioClipData {
1290                name: "child.wav".to_string(),
1291                start: 0,
1292                length: 48,
1293                ..AudioClipData::default()
1294            }],
1295        };
1296
1297        let value = serde_json::to_value(&clip).expect("serialize");
1298        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1299
1300        assert_eq!(restored.name, clip.name);
1301        assert_eq!(restored.preview_name, clip.preview_name);
1302        assert_eq!(restored.source_name, clip.source_name);
1303        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1304        assert_eq!(restored.grouped_clips.len(), 1);
1305        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1306        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1307    }
1308
1309    #[test]
1310    fn midi_clip_data_serde_round_trips_nested_groups() {
1311        let clip = MidiClipData {
1312            id: String::new(),
1313            name: "group.mid".to_string(),
1314            start: 5,
1315            length: 64,
1316            offset: 2,
1317            input_channel: 3,
1318            muted: true,
1319            grouped_clips: vec![MidiClipData {
1320                name: "child.mid".to_string(),
1321                start: 0,
1322                length: 32,
1323                ..MidiClipData::default()
1324            }],
1325        };
1326
1327        let value = serde_json::to_value(&clip).expect("serialize");
1328        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1329
1330        assert_eq!(restored.name, clip.name);
1331        assert_eq!(restored.grouped_clips.len(), 1);
1332        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1333    }
1334
1335    #[test]
1336    fn pitch_correction_point_data_serde_round_trips() {
1337        let point = PitchCorrectionPointData {
1338            start_sample: 10,
1339            length_samples: 20,
1340            detected_midi_pitch: 57.5,
1341            target_midi_pitch: 58.0,
1342            clarity: 0.9,
1343        };
1344
1345        let value = serde_json::to_value(&point).expect("serialize");
1346        let restored: PitchCorrectionPointData =
1347            serde_json::from_value(value).expect("deserialize");
1348
1349        assert_eq!(restored.start_sample, 10);
1350        assert_eq!(restored.length_samples, 20);
1351        assert_eq!(restored.detected_midi_pitch, 57.5);
1352        assert_eq!(restored.target_midi_pitch, 58.0);
1353        assert_eq!(restored.clarity, 0.9);
1354    }
1355
1356    #[test]
1357    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1358        let restored: AudioClipData = serde_json::from_value(json!({
1359            "name": "clip.wav",
1360            "start": 1,
1361            "length": 2,
1362            "offset": 3,
1363            "input_channel": 0,
1364            "muted": false,
1365            "fade_enabled": true,
1366            "fade_in_samples": 240,
1367            "fade_out_samples": 240,
1368            "pitch_correction_points": [],
1369            "grouped_clips": []
1370        }))
1371        .expect("deserialize");
1372
1373        assert_eq!(restored.name, "clip.wav");
1374        assert!(restored.peaks_file.is_none());
1375        assert!(restored.preview_name.is_none());
1376        assert!(restored.source_name.is_none());
1377        assert!(restored.source_offset.is_none());
1378        assert!(restored.source_length.is_none());
1379        assert!(restored.pitch_correction_points.is_empty());
1380        assert!(restored.plugin_graph_json.is_none());
1381    }
1382}