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