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    #[cfg(all(unix, not(target_os = "macos")))]
799    Lv2PluginsUnavailable {
800        error: String,
801    },
802    ListVst3Plugins,
803    Vst3Plugins(Vec<Vst3PluginInfo>),
804    Vst3PluginsUnavailable {
805        error: String,
806    },
807    ListClapPlugins,
808    ListClapPluginsWithCapabilities,
809    ClapPlugins(Vec<ClapPluginInfo>),
810    ClapPluginsUnavailable {
811        error: String,
812    },
813    TrackSetClapParameter {
814        track_name: String,
815        instance_id: usize,
816        param_id: u32,
817        value: f64,
818    },
819    ClipSetClapParameter {
820        track_name: String,
821        clip_idx: usize,
822        instance_id: usize,
823        param_id: u32,
824        value: f64,
825    },
826    TrackSetClapParameterAt {
827        track_name: String,
828        instance_id: usize,
829        param_id: u32,
830        value: f64,
831        frame: u32,
832    },
833    TrackBeginClapParameterEdit {
834        track_name: String,
835        instance_id: usize,
836        param_id: u32,
837        frame: u32,
838    },
839    TrackEndClapParameterEdit {
840        track_name: String,
841        instance_id: usize,
842        param_id: u32,
843        frame: u32,
844    },
845    TrackGetClapParameters {
846        track_name: String,
847        instance_id: usize,
848    },
849    TrackClapParameters {
850        track_name: String,
851        instance_id: usize,
852        parameters: Vec<ClapParameterInfo>,
853    },
854    TrackClapSnapshotState {
855        track_name: String,
856        instance_id: usize,
857    },
858    ClipClapSnapshotState {
859        track_name: String,
860        clip_idx: usize,
861        instance_id: usize,
862    },
863    TrackClapStateSnapshot {
864        track_name: String,
865        instance_id: usize,
866        plugin_path: String,
867        state: crate::clap::ClapPluginState,
868    },
869    ClipClapStateSnapshot {
870        track_name: String,
871        clip_idx: usize,
872        instance_id: usize,
873        plugin_path: String,
874        state: crate::clap::ClapPluginState,
875    },
876    TrackClapStateDirty {
877        track_name: String,
878        instance_id: usize,
879    },
880    ClipClapStateDirty {
881        track_name: String,
882        clip_idx: usize,
883        instance_id: usize,
884    },
885    TrackClapRestoreState {
886        track_name: String,
887        instance_id: usize,
888        state: crate::clap::ClapPluginState,
889    },
890    ClipClapRestoreState {
891        track_name: String,
892        clip_idx: usize,
893        instance_id: usize,
894        state: crate::clap::ClapPluginState,
895    },
896    TrackSnapshotAllClapStates {
897        track_name: String,
898    },
899    TrackSnapshotAllClapStatesDone {
900        track_name: String,
901    },
902    TrackLoadClapPlugin {
903        track_name: String,
904        plugin_path: String,
905        instance_id: Option<usize>,
906    },
907    TrackUnloadClapPlugin {
908        track_name: String,
909        plugin_path: String,
910    },
911    TrackUnloadClapPluginInstance {
912        track_name: String,
913        instance_id: usize,
914    },
915    TrackShowClapGui {
916        track_name: String,
917        instance_id: usize,
918    },
919    TrackLoadVst3Plugin {
920        track_name: String,
921        plugin_path: String,
922        instance_id: Option<usize>,
923    },
924    TrackUnloadVst3Plugin {
925        track_name: String,
926        plugin_path: String,
927    },
928    TrackUnloadVst3PluginInstance {
929        track_name: String,
930        instance_id: usize,
931    },
932    TrackShowVst3Gui {
933        track_name: String,
934        instance_id: usize,
935    },
936    #[cfg(all(unix, not(target_os = "macos")))]
937    TrackLoadLv2Plugin {
938        track_name: String,
939        plugin_uri: String,
940        instance_id: Option<usize>,
941    },
942    #[cfg(all(unix, not(target_os = "macos")))]
943    TrackUnloadLv2Plugin {
944        track_name: String,
945        plugin_uri: String,
946    },
947    #[cfg(all(unix, not(target_os = "macos")))]
948    TrackUnloadLv2PluginInstance {
949        track_name: String,
950        instance_id: usize,
951    },
952    TrackShowLv2Gui {
953        track_name: String,
954        instance_id: usize,
955    },
956    TrackSetPluginResourceDir {
957        track_name: String,
958        instance_id: usize,
959        format: String,
960        directory: String,
961    },
962    TrackClapFileReferences {
963        track_name: String,
964        instance_id: usize,
965        refs: Vec<(u32, String)>,
966    },
967    TrackUpdateClapFileReference {
968        track_name: String,
969        instance_id: usize,
970        index: u32,
971        path: String,
972    },
973    ClipSetPluginResourceDir {
974        track_name: String,
975        clip_idx: usize,
976        instance_id: usize,
977        format: String,
978        directory: String,
979    },
980    ClipClapFileReferences {
981        track_name: String,
982        clip_idx: usize,
983        instance_id: usize,
984        refs: Vec<(u32, String)>,
985    },
986    ClipUpdateClapFileReference {
987        track_name: String,
988        clip_idx: usize,
989        instance_id: usize,
990        index: u32,
991        path: String,
992    },
993    TrackGetVst3Graph {
994        track_name: String,
995    },
996    TrackVst3Graph {
997        track_name: String,
998        plugins: Vec<Vst3GraphPlugin>,
999        connections: Vec<Vst3GraphConnection>,
1000    },
1001    TrackSetVst3Parameter {
1002        track_name: String,
1003        instance_id: usize,
1004        param_id: u32,
1005        value: f32,
1006    },
1007    TrackSetPluginBypassed {
1008        track_name: String,
1009        instance_id: usize,
1010        format: String,
1011        bypassed: bool,
1012    },
1013    TrackGetVst3Parameters {
1014        track_name: String,
1015        instance_id: usize,
1016    },
1017    TrackVst3Parameters {
1018        track_name: String,
1019        instance_id: usize,
1020        parameters: Vec<crate::vst3::port::ParameterInfo>,
1021    },
1022    TrackVst3SnapshotState {
1023        track_name: String,
1024        instance_id: usize,
1025    },
1026    ClipVst3SnapshotState {
1027        track_name: String,
1028        clip_idx: usize,
1029        instance_id: usize,
1030    },
1031    TrackVst3StateSnapshot {
1032        track_name: String,
1033        instance_id: usize,
1034        state: crate::vst3::state::Vst3PluginState,
1035    },
1036    ClipVst3StateSnapshot {
1037        track_name: String,
1038        clip_idx: usize,
1039        instance_id: usize,
1040        state: crate::vst3::state::Vst3PluginState,
1041    },
1042    TrackVst3RestoreState {
1043        track_name: String,
1044        instance_id: usize,
1045        state: crate::vst3::state::Vst3PluginState,
1046    },
1047    TrackConnectVst3Audio {
1048        track_name: String,
1049        from_node: Vst3GraphNode,
1050        from_port: usize,
1051        to_node: Vst3GraphNode,
1052        to_port: usize,
1053    },
1054    TrackDisconnectVst3Audio {
1055        track_name: String,
1056        from_node: Vst3GraphNode,
1057        from_port: usize,
1058        to_node: Vst3GraphNode,
1059        to_port: usize,
1060    },
1061    ClipMove {
1062        kind: Kind,
1063        from: ClipMoveFrom,
1064        to: ClipMoveTo,
1065        copy: bool,
1066    },
1067    Connect {
1068        from_track: String,
1069        from_port: usize,
1070        to_track: String,
1071        to_port: usize,
1072        kind: Kind,
1073    },
1074    Disconnect {
1075        from_track: String,
1076        from_port: usize,
1077        to_track: String,
1078        to_port: usize,
1079        kind: Kind,
1080    },
1081    OpenAudioDevice {
1082        device: String,
1083        input_device: Option<String>,
1084        sample_rate_hz: i32,
1085        bits: i32,
1086        exclusive: bool,
1087        period_frames: usize,
1088        nperiods: usize,
1089        sync_mode: bool,
1090        actual_period_frames: usize,
1091        input_channels: usize,
1092        output_channels: usize,
1093        bytes_per_frame: usize,
1094    },
1095    JackAddAudioInputPort,
1096    JackRemoveAudioInputPort(usize),
1097    JackAddAudioOutputPort,
1098    JackRemoveAudioOutputPort(usize),
1099    OpenMidiInputDevice(String),
1100    OpenMidiOutputDevice(String),
1101    RequestSessionDiagnostics,
1102    RequestMidiLearnMappingsReport,
1103    ClearAllMidiLearnBindings,
1104    SessionDiagnosticsReport {
1105        track_count: usize,
1106        frozen_track_count: usize,
1107        audio_clip_count: usize,
1108        midi_clip_count: usize,
1109        #[cfg(all(unix, not(target_os = "macos")))]
1110        lv2_instance_count: usize,
1111        vst3_instance_count: usize,
1112        clap_instance_count: usize,
1113        pending_requests: usize,
1114        workers_total: usize,
1115        workers_ready: usize,
1116        pending_hw_midi_events: usize,
1117        playing: bool,
1118        transport_sample: usize,
1119        tempo_bpm: f64,
1120        sample_rate_hz: usize,
1121        cycle_samples: usize,
1122    },
1123    MidiLearnMappingsReport {
1124        lines: Vec<String>,
1125    },
1126    HWInfo {
1127        channels: usize,
1128        rate: usize,
1129        input: bool,
1130    },
1131    MarkHistorySavePoint,
1132    HistoryState {
1133        dirty: bool,
1134    },
1135    Undo,
1136    Redo,
1137    Panic,
1138}
1139
1140#[derive(Clone, Debug)]
1141pub enum Message {
1142    Ready(usize),
1143    Finished {
1144        worker_id: usize,
1145        task: ProcessTask,
1146        output_linear: Vec<f32>,
1147        process_epoch: usize,
1148        parameter_updates: Vec<Action>,
1149    },
1150    TracksFinished,
1151
1152    ProcessTask(ProcessTask),
1153    ProcessOfflineBounce(OfflineBounceWork),
1154    Channel(Sender<Self>),
1155
1156    Request(Action),
1157    Response(Result<Action, String>),
1158    HWMidiEvents(Vec<HwMidiEvent>),
1159    HWMidiOutEvents(Vec<HwMidiEvent>),
1160    ClearHWMidiOutEvents,
1161    HWFinished,
1162    OfflineBounceFinished {
1163        result: Result<Action, String>,
1164    },
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169    use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1170    use serde_json::json;
1171
1172    #[test]
1173    fn audio_clip_data_serde_round_trips_nested_groups() {
1174        let clip = AudioClipData {
1175            name: "group.wav".to_string(),
1176            start: 12,
1177            length: 96,
1178            offset: 3,
1179            input_channel: 1,
1180            muted: true,
1181            peaks_file: Some("peaks/group.json".to_string()),
1182            fade_enabled: false,
1183            fade_in_samples: 10,
1184            fade_out_samples: 20,
1185            preview_name: Some("preview.wav".to_string()),
1186            source_name: Some("source.wav".to_string()),
1187            source_offset: Some(4),
1188            source_length: Some(88),
1189            pitch_correction_points: vec![PitchCorrectionPointData {
1190                start_sample: 7,
1191                length_samples: 11,
1192                detected_midi_pitch: 60.1,
1193                target_midi_pitch: 61.2,
1194                clarity: 0.8,
1195            }],
1196            pitch_correction_frame_likeness: Some(0.5),
1197            pitch_correction_inertia_ms: Some(123),
1198            pitch_correction_formant_compensation: Some(false),
1199            plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1200            grouped_clips: vec![AudioClipData {
1201                name: "child.wav".to_string(),
1202                start: 0,
1203                length: 48,
1204                ..AudioClipData::default()
1205            }],
1206        };
1207
1208        let value = serde_json::to_value(&clip).expect("serialize");
1209        let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1210
1211        assert_eq!(restored.name, clip.name);
1212        assert_eq!(restored.preview_name, clip.preview_name);
1213        assert_eq!(restored.source_name, clip.source_name);
1214        assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1215        assert_eq!(restored.grouped_clips.len(), 1);
1216        assert_eq!(restored.grouped_clips[0].name, "child.wav");
1217        assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1218    }
1219
1220    #[test]
1221    fn midi_clip_data_serde_round_trips_nested_groups() {
1222        let clip = MidiClipData {
1223            name: "group.mid".to_string(),
1224            start: 5,
1225            length: 64,
1226            offset: 2,
1227            input_channel: 3,
1228            muted: true,
1229            grouped_clips: vec![MidiClipData {
1230                name: "child.mid".to_string(),
1231                start: 0,
1232                length: 32,
1233                ..MidiClipData::default()
1234            }],
1235        };
1236
1237        let value = serde_json::to_value(&clip).expect("serialize");
1238        let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1239
1240        assert_eq!(restored.name, clip.name);
1241        assert_eq!(restored.grouped_clips.len(), 1);
1242        assert_eq!(restored.grouped_clips[0].name, "child.mid");
1243    }
1244
1245    #[test]
1246    fn pitch_correction_point_data_serde_round_trips() {
1247        let point = PitchCorrectionPointData {
1248            start_sample: 10,
1249            length_samples: 20,
1250            detected_midi_pitch: 57.5,
1251            target_midi_pitch: 58.0,
1252            clarity: 0.9,
1253        };
1254
1255        let value = serde_json::to_value(&point).expect("serialize");
1256        let restored: PitchCorrectionPointData =
1257            serde_json::from_value(value).expect("deserialize");
1258
1259        assert_eq!(restored.start_sample, 10);
1260        assert_eq!(restored.length_samples, 20);
1261        assert_eq!(restored.detected_midi_pitch, 57.5);
1262        assert_eq!(restored.target_midi_pitch, 58.0);
1263        assert_eq!(restored.clarity, 0.9);
1264    }
1265
1266    #[test]
1267    fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1268        let restored: AudioClipData = serde_json::from_value(json!({
1269            "name": "clip.wav",
1270            "start": 1,
1271            "length": 2,
1272            "offset": 3,
1273            "input_channel": 0,
1274            "muted": false,
1275            "fade_enabled": true,
1276            "fade_in_samples": 240,
1277            "fade_out_samples": 240,
1278            "pitch_correction_points": [],
1279            "grouped_clips": []
1280        }))
1281        .expect("deserialize");
1282
1283        assert_eq!(restored.name, "clip.wav");
1284        assert!(restored.peaks_file.is_none());
1285        assert!(restored.preview_name.is_none());
1286        assert!(restored.source_name.is_none());
1287        assert!(restored.source_offset.is_none());
1288        assert!(restored.source_length.is_none());
1289        assert!(restored.pitch_correction_points.is_empty());
1290        assert!(restored.plugin_graph_json.is_none());
1291    }
1292}