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