Skip to main content

maolan_engine/
message.rs

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