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