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