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    Eighth,
381    Sixteenth,
382    ThirtySecond,
383    SixtyFourth,
384}
385
386#[derive(Clone, Debug)]
387pub enum SessionAction {
388    LaunchClip {
389        track_name: String,
390        scene_index: usize,
391        clip_id: String,
392        launch_quantization: LaunchQuantization,
393        loop_enabled: bool,
394        loop_start_samples: usize,
395        loop_end_samples: usize,
396    },
397    StopClip {
398        track_name: String,
399        scene_index: usize,
400        launch_quantization: LaunchQuantization,
401    },
402    LaunchScene {
403        scene_index: usize,
404        launch_quantization: LaunchQuantization,
405    },
406    StopScene {
407        scene_index: usize,
408        launch_quantization: LaunchQuantization,
409    },
410    StopAllClips,
411}
412
413#[derive(Clone, Debug)]
414pub enum Action {
415    Quit,
416    Log {
417        source: String,
418        message: String,
419    },
420    Play,
421    Pause,
422    Stop,
423    SessionPlay,
424    TransportPosition(usize),
425    TransportPositionAt {
426        sample: usize,
427        after_frames: usize,
428    },
429    JumpToEnd,
430    SetLoopEnabled(bool),
431    SetLoopRange(Option<(usize, usize)>),
432    SetPunchEnabled(bool),
433    SetPunchRange(Option<(usize, usize)>),
434    SetMetronomeEnabled(bool),
435    SetTempo(f64),
436    SetTimeSignature {
437        numerator: u16,
438        denominator: u16,
439    },
440    SetOscEnabled(bool),
441    SetClipPlaybackEnabled(bool),
442    SetSessionClipPlaybackEnabled(bool),
443    SetRecordEnabled(bool),
444    SetModulators(Vec<Modulator>),
445    SetSessionPath(String),
446    BeginHistoryGroup,
447    EndHistoryGroup,
448    ApplyGroupedActions(Vec<Action>),
449    ClearHistory,
450    BeginSessionRestore,
451    EndSessionRestore,
452    AddTrack {
453        name: String,
454        audio_ins: usize,
455        midi_ins: usize,
456        audio_outs: usize,
457        midi_outs: usize,
458        folder: bool,
459    },
460    TrackAddAudioInput(String),
461    TrackAddAudioOutput(String),
462    TrackRemoveAudioInput(String),
463    TrackRemoveAudioOutput(String),
464    AddClip {
465        clip_id: String,
466        name: String,
467        track_name: String,
468        start: usize,
469        length: usize,
470        offset: usize,
471        input_channel: usize,
472        muted: bool,
473        peaks_file: Option<String>,
474        kind: Kind,
475        fade_enabled: bool,
476        fade_in_samples: usize,
477        fade_out_samples: usize,
478        source_name: Option<String>,
479        source_offset: Option<usize>,
480        source_length: Option<usize>,
481        preview_name: Option<String>,
482        pitch_correction_points: Vec<PitchCorrectionPointData>,
483        pitch_correction_frame_likeness: Option<f32>,
484        pitch_correction_inertia_ms: Option<u16>,
485        pitch_correction_formant_compensation: Option<bool>,
486        plugin_graph_json: Option<serde_json::Value>,
487    },
488    AddGroupedClip {
489        track_name: String,
490        kind: Kind,
491        audio_clip: Option<AudioClipData>,
492        midi_clip: Option<MidiClipData>,
493    },
494    RemoveClip {
495        track_name: String,
496        kind: Kind,
497        clip_indices: Vec<usize>,
498    },
499    SetClipFade {
500        track_name: String,
501        clip_index: usize,
502        kind: Kind,
503        fade_enabled: bool,
504        fade_in_samples: usize,
505        fade_out_samples: usize,
506    },
507    SetClipBounds {
508        track_name: String,
509        clip_index: usize,
510        kind: Kind,
511        start: usize,
512        length: usize,
513        offset: usize,
514    },
515    SyncClipBounds {
516        track_name: String,
517        clip_index: usize,
518        kind: Kind,
519        start: usize,
520        length: usize,
521        offset: usize,
522    },
523    SetClipMuted {
524        track_name: String,
525        clip_index: usize,
526        kind: Kind,
527        muted: bool,
528    },
529    SetClipPluginGraphJson {
530        track_name: String,
531        clip_index: usize,
532        plugin_graph_json: Option<serde_json::Value>,
533    },
534    SetClipPitchCorrection {
535        track_name: String,
536        clip_index: usize,
537        preview_name: Option<String>,
538        source_name: Option<String>,
539        source_offset: Option<usize>,
540        source_length: Option<usize>,
541        pitch_correction_points: Vec<PitchCorrectionPointData>,
542        pitch_correction_frame_likeness: Option<f32>,
543        pitch_correction_inertia_ms: Option<u16>,
544        pitch_correction_formant_compensation: Option<bool>,
545    },
546    RenameClip {
547        track_name: String,
548        kind: Kind,
549        clip_index: usize,
550        new_name: String,
551    },
552    SetClipSourceName {
553        track_name: String,
554        kind: Kind,
555        clip_index: usize,
556        name: String,
557    },
558    RenameTrack {
559        old_name: String,
560        new_name: String,
561    },
562    RemoveTrack(String),
563    TrackLevel(String, f32),
564    TrackBalance(String, f32),
565    TrackAutomationLevel(String, f32),
566    TrackAutomationBalance(String, f32),
567    TrackMidiCc {
568        track_name: String,
569        channel: u8,
570        cc: u8,
571        value: u8,
572    },
573    TrackMeters {
574        track_name: String,
575        output_db: Vec<f32>,
576    },
577    RequestMeterSnapshot,
578    MeterSnapshot {
579        hw_out_db: Arc<Vec<f32>>,
580        track_meters: Arc<Vec<(String, Vec<f32>)>>,
581    },
582    TrackToggleArm(String),
583    TrackToggleMute(String),
584    TrackTogglePhase(String),
585    TrackToggleSolo(String),
586    TrackToggleMaster(String),
587    TrackToggleInputMonitor {
588        track_name: String,
589        lane: usize,
590    },
591    TrackToggleDiskMonitor {
592        track_name: String,
593        lane: usize,
594    },
595    TrackToggleMidiInputMonitor {
596        track_name: String,
597        lane: usize,
598    },
599    TrackToggleMidiDiskMonitor {
600        track_name: String,
601        lane: usize,
602    },
603    TrackSetColor {
604        track_name: String,
605        color: Option<TrackColor>,
606    },
607    TrackArmMidiLearn {
608        track_name: String,
609        target: TrackMidiLearnTarget,
610    },
611    GlobalArmMidiLearn {
612        target: GlobalMidiLearnTarget,
613    },
614    SessionArmMidiLearn {
615        target: SessionMidiLearnTarget,
616    },
617    TrackSetMidiLearnBinding {
618        track_name: String,
619        target: TrackMidiLearnTarget,
620        binding: Option<MidiLearnBinding>,
621    },
622    SetGlobalMidiLearnBinding {
623        target: GlobalMidiLearnTarget,
624        binding: Option<MidiLearnBinding>,
625    },
626    SetSessionMidiLearnBinding {
627        target: SessionMidiLearnTarget,
628        binding: Option<MidiLearnBinding>,
629    },
630    SessionMidiLearnTriggered {
631        target: SessionMidiLearnTarget,
632    },
633    TrackSetFolder {
634        track_name: String,
635        is_folder: bool,
636    },
637    TrackSetParent {
638        track_name: String,
639        parent_name: Option<String>,
640    },
641    TrackToggleFolder {
642        track_name: String,
643    },
644    TrackSetMidiLaneChannel {
645        track_name: String,
646        lane: usize,
647        channel: Option<u8>,
648    },
649    TrackSetFrozen {
650        track_name: String,
651        frozen: bool,
652    },
653    TrackSetSessionSlot {
654        track_name: String,
655        scene_index: usize,
656        clip_id: Option<String>,
657    },
658    TrackSetSessionSlotPlayEnabled {
659        track_name: String,
660        scene_index: usize,
661        enabled: bool,
662    },
663    TrackOfflineBounce {
664        track_name: String,
665        output_path: String,
666        start_sample: usize,
667        length_samples: usize,
668        automation_lanes: Vec<OfflineAutomationLane>,
669        apply_fader: bool,
670    },
671    TrackOfflineBounceCancel {
672        track_name: String,
673    },
674    TrackOfflineBounceCancelAll,
675    TrackOfflineBounceCanceled {
676        track_name: String,
677    },
678    TrackOfflineBounceProgress {
679        track_name: String,
680        progress: f32,
681        operation: Option<String>,
682    },
683    PianoKey {
684        track_name: String,
685        note: u8,
686        velocity: u8,
687        on: bool,
688    },
689    ModifyMidiNotes {
690        track_name: String,
691        clip_index: usize,
692        note_indices: Vec<usize>,
693        new_notes: Vec<MidiNoteData>,
694        old_notes: Vec<MidiNoteData>,
695    },
696    ModifyMidiControllers {
697        track_name: String,
698        clip_index: usize,
699        controller_indices: Vec<usize>,
700        new_controllers: Vec<MidiControllerData>,
701        old_controllers: Vec<MidiControllerData>,
702    },
703    DeleteMidiControllers {
704        track_name: String,
705        clip_index: usize,
706        controller_indices: Vec<usize>,
707        deleted_controllers: Vec<(usize, MidiControllerData)>,
708    },
709    InsertMidiControllers {
710        track_name: String,
711        clip_index: usize,
712        controllers: Vec<(usize, MidiControllerData)>,
713    },
714    DeleteMidiNotes {
715        track_name: String,
716        clip_index: usize,
717        note_indices: Vec<usize>,
718        deleted_notes: Vec<(usize, MidiNoteData)>,
719    },
720    InsertMidiNotes {
721        track_name: String,
722        clip_index: usize,
723        notes: Vec<(usize, MidiNoteData)>,
724    },
725    SetStepRecording(bool),
726    StepRecordMidiNote {
727        device: String,
728        channel: u8,
729        pitch: u8,
730        velocity: u8,
731    },
732    SetMidiSysExEvents {
733        track_name: String,
734        clip_index: usize,
735        new_sysex_events: Vec<MidiRawEventData>,
736        old_sysex_events: Vec<MidiRawEventData>,
737    },
738    TrackClearDefaultPassthrough {
739        track_name: String,
740    },
741    TrackClearPlugins {
742        track_name: String,
743    },
744    #[cfg(all(unix, not(target_os = "macos")))]
745    TrackSetLv2PluginState {
746        track_name: String,
747        instance_id: usize,
748        state: Vec<u8>,
749    },
750    #[cfg(all(unix, not(target_os = "macos")))]
751    ClipSetLv2PluginState {
752        track_name: String,
753        clip_idx: usize,
754        instance_id: usize,
755        state: Vec<u8>,
756    },
757    #[cfg(all(unix, not(target_os = "macos")))]
758    TrackGetLv2PluginControls {
759        track_name: String,
760        instance_id: usize,
761    },
762    #[cfg(all(unix, not(target_os = "macos")))]
763    ClipGetLv2PluginControls {
764        track_name: String,
765        clip_idx: usize,
766        instance_id: usize,
767    },
768    #[cfg(all(unix, not(target_os = "macos")))]
769    TrackLv2PluginControls {
770        track_name: String,
771        instance_id: usize,
772        controls: Vec<Lv2ControlPortInfo>,
773        instance_access_handle: Option<usize>,
774    },
775    #[cfg(all(unix, not(target_os = "macos")))]
776    ClipLv2PluginControls {
777        track_name: String,
778        clip_idx: usize,
779        instance_id: usize,
780        controls: Vec<Lv2ControlPortInfo>,
781        instance_access_handle: Option<usize>,
782    },
783    #[cfg(all(unix, not(target_os = "macos")))]
784    TrackGetLv2Midnam {
785        track_name: String,
786    },
787    #[cfg(all(unix, not(target_os = "macos")))]
788    TrackLv2Midnam {
789        track_name: String,
790        note_names: std::collections::HashMap<u8, String>,
791    },
792    TrackGetClapNoteNames {
793        track_name: String,
794    },
795    TrackClapNoteNames {
796        track_name: String,
797        note_names: std::collections::HashMap<u8, String>,
798    },
799    #[cfg(all(unix, not(target_os = "macos")))]
800    TrackSetLv2ControlValue {
801        track_name: String,
802        instance_id: usize,
803        index: u32,
804        value: f32,
805    },
806    #[cfg(all(unix, not(target_os = "macos")))]
807    ClipSetLv2ControlValue {
808        track_name: String,
809        clip_idx: usize,
810        instance_id: usize,
811        index: u32,
812        value: f32,
813    },
814    #[cfg(all(unix, not(target_os = "macos")))]
815    ClipLv2StateSnapshot {
816        track_name: String,
817        clip_idx: usize,
818        instance_id: usize,
819        state: Vec<u8>,
820    },
821    TrackGetPluginGraph {
822        track_name: String,
823        include_state: bool,
824    },
825    TrackPluginGraph {
826        track_name: String,
827        plugins: Vec<PluginGraphPlugin>,
828        connections: Vec<PluginGraphConnection>,
829        connectable_connections: Vec<ConnectableConnection>,
830    },
831    TrackConnectPluginAudio {
832        track_name: String,
833        from_node: PluginGraphNode,
834        from_port: usize,
835        to_node: PluginGraphNode,
836        to_port: usize,
837    },
838    TrackConnectPluginMidi {
839        track_name: String,
840        from_node: PluginGraphNode,
841        from_port: usize,
842        to_node: PluginGraphNode,
843        to_port: usize,
844    },
845    TrackDisconnectPluginAudio {
846        track_name: String,
847        from_node: PluginGraphNode,
848        from_port: usize,
849        to_node: PluginGraphNode,
850        to_port: usize,
851    },
852    TrackDisconnectPluginMidi {
853        track_name: String,
854        from_node: PluginGraphNode,
855        from_port: usize,
856        to_node: PluginGraphNode,
857        to_port: usize,
858    },
859    TrackConnectAudio {
860        track_name: String,
861        from: ConnectableRef,
862        from_port: usize,
863        to: ConnectableRef,
864        to_port: usize,
865    },
866    TrackDisconnectAudio {
867        track_name: String,
868        from: ConnectableRef,
869        from_port: usize,
870        to: ConnectableRef,
871        to_port: usize,
872    },
873    TrackConnectMidi {
874        track_name: String,
875        from: ConnectableRef,
876        from_port: usize,
877        to: ConnectableRef,
878        to_port: usize,
879    },
880    TrackDisconnectMidi {
881        track_name: String,
882        from: ConnectableRef,
883        from_port: usize,
884        to: ConnectableRef,
885        to_port: usize,
886    },
887    #[cfg(all(unix, not(target_os = "macos")))]
888    ListLv2Plugins,
889    #[cfg(all(unix, not(target_os = "macos")))]
890    Lv2Plugins(Vec<Lv2PluginInfo>),
891    #[cfg(all(unix, not(target_os = "macos")))]
892    Lv2PluginsUnavailable {
893        error: String,
894    },
895    ListVst3Plugins,
896    Vst3Plugins(Vec<Vst3PluginInfo>),
897    Vst3PluginsUnavailable {
898        error: String,
899    },
900    ListClapPlugins,
901    ListClapPluginsWithCapabilities,
902    ClapPlugins(Vec<ClapPluginInfo>),
903    ClapPluginsUnavailable {
904        error: String,
905    },
906    TrackSetClapParameter {
907        track_name: String,
908        instance_id: usize,
909        param_id: u32,
910        value: f64,
911    },
912    ClipSetClapParameter {
913        track_name: String,
914        clip_idx: usize,
915        instance_id: usize,
916        param_id: u32,
917        value: f64,
918    },
919    TrackSetClapParameterAt {
920        track_name: String,
921        instance_id: usize,
922        param_id: u32,
923        value: f64,
924        frame: u32,
925    },
926    TrackBeginClapParameterEdit {
927        track_name: String,
928        instance_id: usize,
929        param_id: u32,
930        frame: u32,
931    },
932    TrackEndClapParameterEdit {
933        track_name: String,
934        instance_id: usize,
935        param_id: u32,
936        frame: u32,
937    },
938    TrackGetClapParameters {
939        track_name: String,
940        instance_id: usize,
941    },
942    TrackClapParameters {
943        track_name: String,
944        instance_id: usize,
945        parameters: Vec<ClapParameterInfo>,
946    },
947    TrackClapSnapshotState {
948        track_name: String,
949        instance_id: usize,
950    },
951    ClipClapSnapshotState {
952        track_name: String,
953        clip_idx: usize,
954        instance_id: usize,
955    },
956    TrackClapStateSnapshot {
957        track_name: String,
958        instance_id: usize,
959        plugin_path: String,
960        state: crate::clap::ClapPluginState,
961    },
962    ClipClapStateSnapshot {
963        track_name: String,
964        clip_idx: usize,
965        instance_id: usize,
966        plugin_path: String,
967        state: crate::clap::ClapPluginState,
968    },
969    TrackClapStateDirty {
970        track_name: String,
971        instance_id: usize,
972    },
973    ClipClapStateDirty {
974        track_name: String,
975        clip_idx: usize,
976        instance_id: usize,
977    },
978    TrackClapRestoreState {
979        track_name: String,
980        instance_id: usize,
981        state: crate::clap::ClapPluginState,
982    },
983    ClipClapRestoreState {
984        track_name: String,
985        clip_idx: usize,
986        instance_id: usize,
987        state: crate::clap::ClapPluginState,
988    },
989    TrackSnapshotAllClapStates {
990        track_name: String,
991    },
992    TrackSnapshotAllClapStatesDone {
993        track_name: String,
994    },
995    TrackLoadClapPlugin {
996        track_name: String,
997        plugin_path: String,
998        instance_id: Option<usize>,
999    },
1000    TrackUnloadClapPlugin {
1001        track_name: String,
1002        plugin_path: String,
1003    },
1004    TrackUnloadClapPluginInstance {
1005        track_name: String,
1006        instance_id: usize,
1007    },
1008    TrackShowClapGui {
1009        track_name: String,
1010        instance_id: usize,
1011    },
1012    TrackLoadVst3Plugin {
1013        track_name: String,
1014        plugin_path: String,
1015        instance_id: Option<usize>,
1016    },
1017    TrackUnloadVst3Plugin {
1018        track_name: String,
1019        plugin_path: String,
1020    },
1021    TrackUnloadVst3PluginInstance {
1022        track_name: String,
1023        instance_id: usize,
1024    },
1025    TrackShowVst3Gui {
1026        track_name: String,
1027        instance_id: usize,
1028    },
1029    #[cfg(all(unix, not(target_os = "macos")))]
1030    TrackLoadLv2Plugin {
1031        track_name: String,
1032        plugin_uri: String,
1033        instance_id: Option<usize>,
1034    },
1035    #[cfg(all(unix, not(target_os = "macos")))]
1036    TrackUnloadLv2Plugin {
1037        track_name: String,
1038        plugin_uri: String,
1039    },
1040    #[cfg(all(unix, not(target_os = "macos")))]
1041    TrackUnloadLv2PluginInstance {
1042        track_name: String,
1043        instance_id: usize,
1044    },
1045    TrackShowLv2Gui {
1046        track_name: String,
1047        instance_id: usize,
1048    },
1049    TrackSetPluginResourceDir {
1050        track_name: String,
1051        instance_id: usize,
1052        format: String,
1053        directory: String,
1054    },
1055    TrackClapFileReferences {
1056        track_name: String,
1057        instance_id: usize,
1058        refs: Vec<(u32, String)>,
1059    },
1060    TrackUpdateClapFileReference {
1061        track_name: String,
1062        instance_id: usize,
1063        index: u32,
1064        path: String,
1065    },
1066    ClipSetPluginResourceDir {
1067        track_name: String,
1068        clip_idx: usize,
1069        instance_id: usize,
1070        format: String,
1071        directory: String,
1072    },
1073    ClipClapFileReferences {
1074        track_name: String,
1075        clip_idx: usize,
1076        instance_id: usize,
1077        refs: Vec<(u32, String)>,
1078    },
1079    ClipUpdateClapFileReference {
1080        track_name: String,
1081        clip_idx: usize,
1082        instance_id: usize,
1083        index: u32,
1084        path: String,
1085    },
1086    TrackGetVst3Graph {
1087        track_name: String,
1088    },
1089    TrackVst3Graph {
1090        track_name: String,
1091        plugins: Vec<Vst3GraphPlugin>,
1092        connections: Vec<Vst3GraphConnection>,
1093    },
1094    TrackSetVst3Parameter {
1095        track_name: String,
1096        instance_id: usize,
1097        param_id: u32,
1098        value: f32,
1099    },
1100    TrackSetPluginBypassed {
1101        track_name: String,
1102        instance_id: usize,
1103        format: String,
1104        bypassed: bool,
1105    },
1106    TrackGetVst3Parameters {
1107        track_name: String,
1108        instance_id: usize,
1109    },
1110    TrackVst3Parameters {
1111        track_name: String,
1112        instance_id: usize,
1113        parameters: Vec<crate::vst3::port::ParameterInfo>,
1114    },
1115    TrackVst3SnapshotState {
1116        track_name: String,
1117        instance_id: usize,
1118    },
1119    ClipVst3SnapshotState {
1120        track_name: String,
1121        clip_idx: usize,
1122        instance_id: usize,
1123    },
1124    TrackVst3StateSnapshot {
1125        track_name: String,
1126        instance_id: usize,
1127        state: crate::vst3::state::Vst3PluginState,
1128    },
1129    ClipVst3StateSnapshot {
1130        track_name: String,
1131        clip_idx: usize,
1132        instance_id: usize,
1133        state: crate::vst3::state::Vst3PluginState,
1134    },
1135    TrackVst3RestoreState {
1136        track_name: String,
1137        instance_id: usize,
1138        state: crate::vst3::state::Vst3PluginState,
1139    },
1140    TrackConnectVst3Audio {
1141        track_name: String,
1142        from_node: Vst3GraphNode,
1143        from_port: usize,
1144        to_node: Vst3GraphNode,
1145        to_port: usize,
1146    },
1147    TrackDisconnectVst3Audio {
1148        track_name: String,
1149        from_node: Vst3GraphNode,
1150        from_port: usize,
1151        to_node: Vst3GraphNode,
1152        to_port: usize,
1153    },
1154    ClipMove {
1155        kind: Kind,
1156        from: ClipMoveFrom,
1157        to: ClipMoveTo,
1158        copy: bool,
1159    },
1160    Connect {
1161        from_track: String,
1162        from_port: usize,
1163        to_track: String,
1164        to_port: usize,
1165        kind: Kind,
1166    },
1167    Disconnect {
1168        from_track: String,
1169        from_port: usize,
1170        to_track: String,
1171        to_port: usize,
1172        kind: Kind,
1173    },
1174    OpenAudioDevice {
1175        device: String,
1176        input_device: Option<String>,
1177        sample_rate_hz: i32,
1178        bits: i32,
1179        exclusive: bool,
1180        period_frames: usize,
1181        nperiods: usize,
1182        sync_mode: bool,
1183        actual_period_frames: usize,
1184        input_channels: usize,
1185        output_channels: usize,
1186        bytes_per_frame: usize,
1187    },
1188    JackAddAudioInputPort,
1189    JackRemoveAudioInputPort(usize),
1190    JackAddAudioOutputPort,
1191    JackRemoveAudioOutputPort(usize),
1192    OpenMidiInputDevice(String),
1193    OpenMidiOutputDevice(String),
1194    RequestSessionDiagnostics,
1195    RequestMidiLearnMappingsReport,
1196    ClearAllMidiLearnBindings,
1197    SessionDiagnosticsReport {
1198        track_count: usize,
1199        frozen_track_count: usize,
1200        audio_clip_count: usize,
1201        midi_clip_count: usize,
1202        #[cfg(all(unix, not(target_os = "macos")))]
1203        lv2_instance_count: usize,
1204        vst3_instance_count: usize,
1205        clap_instance_count: usize,
1206        pending_requests: usize,
1207        workers_total: usize,
1208        workers_ready: usize,
1209        pending_hw_midi_events: usize,
1210        playing: bool,
1211        transport_running: bool,
1212        transport_sample: usize,
1213        tempo_bpm: f64,
1214        sample_rate_hz: usize,
1215        cycle_samples: usize,
1216    },
1217    MidiLearnMappingsReport {
1218        lines: Vec<String>,
1219    },
1220    HWInfo {
1221        channels: usize,
1222        rate: usize,
1223        input: bool,
1224    },
1225    MarkHistorySavePoint,
1226    HistoryState {
1227        dirty: bool,
1228    },
1229    Undo,
1230    Redo,
1231    Session(SessionAction),
1232    SessionRuntimeReport {
1233        track_name: String,
1234        scene_index: usize,
1235        state: SessionSlotState,
1236        play_position_samples: usize,
1237        elapsed_samples: usize,
1238    },
1239    Panic,
1240}
1241
1242#[derive(Clone, Debug)]
1243pub enum Message {
1244    Ready(usize),
1245    Finished {
1246        worker_id: usize,
1247        task: ProcessTask,
1248        output_linear: Vec<f32>,
1249        process_epoch: usize,
1250        parameter_updates: Vec<Action>,
1251    },
1252    TracksFinished,
1253
1254    ProcessTask(ProcessTask),
1255    ProcessOfflineBounce(OfflineBounceWork),
1256    Channel(Sender<Self>),
1257
1258    Request(Action),
1259    Response(Result<Action, String>),
1260    HWMidiEvents(Vec<HwMidiEvent>),
1261    HWMidiOutEvents(Vec<HwMidiEvent>),
1262    ClearHWMidiOutEvents,
1263    HWFinished,
1264    OfflineBounceFinished {
1265        result: Result<Action, String>,
1266    },
1267}
1268
1269#[cfg(test)]
1270mod tests {
1271    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1272    use serde_json::json;
1273
1274    #[test]
1275    fn audio_clip_data_serde_round_trips_nested_groups() {
1276        let clip = AudioClipData {
1277            id: String::new(),
1278            name: "group.wav".to_string(),
1279            start: 12,
1280            length: 96,
1281            offset: 3,
1282            input_channel: 1,
1283            muted: true,
1284            peaks_file: Some("peaks/group.json".to_string()),
1285            fade_enabled: false,
1286            fade_in_samples: 10,
1287            fade_out_samples: 20,
1288            preview_name: Some("preview.wav".to_string()),
1289            source_name: Some("source.wav".to_string()),
1290            source_offset: Some(4),
1291            source_length: Some(88),
1292            pitch_correction_points: vec![PitchCorrectionPointData {
1293                start_sample: 7,
1294                length_samples: 11,
1295                detected_midi_pitch: 60.1,
1296                target_midi_pitch: 61.2,
1297                clarity: 0.8,
1298            }],
1299            pitch_correction_frame_likeness: Some(0.5),
1300            pitch_correction_inertia_ms: Some(123),
1301            pitch_correction_formant_compensation: Some(false),
1302            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1303            grouped_clips: vec![AudioClipData {
1304                name: "child.wav".to_string(),
1305                start: 0,
1306                length: 48,
1307                ..AudioClipData::default()
1308            }],
1309        };
1310
1311        let value = serde_json::to_value(&clip).expect("serialize");
1312        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1313
1314        assert_eq!(restored.name, clip.name);
1315        assert_eq!(restored.preview_name, clip.preview_name);
1316        assert_eq!(restored.source_name, clip.source_name);
1317        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1318        assert_eq!(restored.grouped_clips.len(), 1);
1319        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1320        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1321    }
1322
1323    #[test]
1324    fn midi_clip_data_serde_round_trips_nested_groups() {
1325        let clip = MidiClipData {
1326            id: String::new(),
1327            name: "group.mid".to_string(),
1328            start: 5,
1329            length: 64,
1330            offset: 2,
1331            input_channel: 3,
1332            muted: true,
1333            grouped_clips: vec![MidiClipData {
1334                name: "child.mid".to_string(),
1335                start: 0,
1336                length: 32,
1337                ..MidiClipData::default()
1338            }],
1339        };
1340
1341        let value = serde_json::to_value(&clip).expect("serialize");
1342        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1343
1344        assert_eq!(restored.name, clip.name);
1345        assert_eq!(restored.grouped_clips.len(), 1);
1346        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1347    }
1348
1349    #[test]
1350    fn pitch_correction_point_data_serde_round_trips() {
1351        let point = PitchCorrectionPointData {
1352            start_sample: 10,
1353            length_samples: 20,
1354            detected_midi_pitch: 57.5,
1355            target_midi_pitch: 58.0,
1356            clarity: 0.9,
1357        };
1358
1359        let value = serde_json::to_value(&point).expect("serialize");
1360        let restored: PitchCorrectionPointData =
1361            serde_json::from_value(value).expect("deserialize");
1362
1363        assert_eq!(restored.start_sample, 10);
1364        assert_eq!(restored.length_samples, 20);
1365        assert_eq!(restored.detected_midi_pitch, 57.5);
1366        assert_eq!(restored.target_midi_pitch, 58.0);
1367        assert_eq!(restored.clarity, 0.9);
1368    }
1369
1370    #[test]
1371    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1372        let restored: AudioClipData = serde_json::from_value(json!({
1373            "name": "clip.wav",
1374            "start": 1,
1375            "length": 2,
1376            "offset": 3,
1377            "input_channel": 0,
1378            "muted": false,
1379            "fade_enabled": true,
1380            "fade_in_samples": 240,
1381            "fade_out_samples": 240,
1382            "pitch_correction_points": [],
1383            "grouped_clips": []
1384        }))
1385        .expect("deserialize");
1386
1387        assert_eq!(restored.name, "clip.wav");
1388        assert!(restored.peaks_file.is_none());
1389        assert!(restored.preview_name.is_none());
1390        assert!(restored.source_name.is_none());
1391        assert!(restored.source_offset.is_none());
1392        assert!(restored.source_length.is_none());
1393        assert!(restored.pitch_correction_points.is_empty());
1394        assert!(restored.plugin_graph_json.is_none());
1395    }
1396}