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 track_meters: Arc<Vec<(String, Vec<f32>)>>,
779 },
780 RequestTrackList,
781 TrackList(Vec<String>),
782 RequestTransportState,
783 TransportState {
784 sample: usize,
785 tempo_bpm: f64,
786 playing: bool,
787 paused: bool,
788 tsig_num: u16,
789 tsig_denom: u16,
790 },
791 TrackToggleArm(String),
792 TrackToggleMute(String),
793 TrackTogglePhase(String),
794 TrackToggleSolo(String),
795 TrackToggleMaster(String),
796 TrackToggleInputMonitor {
797 track_name: String,
798 lane: usize,
799 },
800 TrackToggleDiskMonitor {
801 track_name: String,
802 lane: usize,
803 },
804 TrackToggleMidiInputMonitor {
805 track_name: String,
806 lane: usize,
807 },
808 TrackToggleMidiDiskMonitor {
809 track_name: String,
810 lane: usize,
811 },
812 TrackSetColor {
813 track_name: String,
814 color: Option<TrackColor>,
815 },
816 TrackArmMidiLearn {
817 track_name: String,
818 target: TrackMidiLearnTarget,
819 },
820 GlobalArmMidiLearn {
821 target: GlobalMidiLearnTarget,
822 },
823 SessionArmMidiLearn {
824 target: SessionMidiLearnTarget,
825 },
826 TrackSetMidiLearnBinding {
827 track_name: String,
828 target: TrackMidiLearnTarget,
829 binding: Option<MidiLearnBinding>,
830 },
831 SetGlobalMidiLearnBinding {
832 target: GlobalMidiLearnTarget,
833 binding: Option<MidiLearnBinding>,
834 },
835 SetSessionMidiLearnBinding {
836 target: SessionMidiLearnTarget,
837 binding: Option<MidiLearnBinding>,
838 },
839 SessionMidiLearnTriggered {
840 target: SessionMidiLearnTarget,
841 },
842 TrackSetFolder {
843 track_name: String,
844 is_folder: bool,
845 },
846 TrackSetParent {
847 track_name: String,
848 parent_name: Option<String>,
849 },
850 TrackToggleFolder {
851 track_name: String,
852 },
853 TrackSetMidiLaneChannel {
854 track_name: String,
855 lane: usize,
856 channel: Option<u8>,
857 },
858 TrackSetMpeZone {
859 track_name: String,
860 manager_channel: u8,
861 member_count: u8,
862 },
863 TrackSetMpePitchBendSensitivity {
864 track_name: String,
865 channel: u8,
866 semitones: u8,
867 },
868 TrackSetFrozen {
869 track_name: String,
870 frozen: bool,
871 },
872 TrackSetSessionSlot {
873 track_name: String,
874 scene_index: usize,
875 clip_id: Option<String>,
876 },
877 TrackSetSessionSlotPlayEnabled {
878 track_name: String,
879 scene_index: usize,
880 enabled: bool,
881 },
882 TrackSetSessionSlotStopEnabled {
883 track_name: String,
884 scene_index: usize,
885 enabled: bool,
886 },
887 TrackOfflineBounce {
888 track_name: String,
889 output_path: String,
890 start_sample: usize,
891 length_samples: usize,
892 automation_lanes: Vec<OfflineAutomationLane>,
893 apply_fader: bool,
894 },
895 TrackOfflineBounceCancel {
896 track_name: String,
897 },
898 TrackOfflineBounceCancelAll,
899 TrackOfflineBounceCanceled {
900 track_name: String,
901 },
902 TrackOfflineBounceProgress {
903 track_name: String,
904 progress: f32,
905 operation: Option<String>,
906 },
907 PianoKey {
908 track_name: String,
909 note: u8,
910 velocity: u8,
911 on: bool,
912 },
913 ModifyMidiNotes {
914 track_name: String,
915 clip_index: usize,
916 note_indices: Vec<usize>,
917 new_notes: Vec<MidiNoteData>,
918 old_notes: Vec<MidiNoteData>,
919 },
920 ModifyMidiControllers {
921 track_name: String,
922 clip_index: usize,
923 controller_indices: Vec<usize>,
924 new_controllers: Vec<MidiControllerData>,
925 old_controllers: Vec<MidiControllerData>,
926 },
927 DeleteMidiControllers {
928 track_name: String,
929 clip_index: usize,
930 controller_indices: Vec<usize>,
931 deleted_controllers: Vec<(usize, MidiControllerData)>,
932 },
933 InsertMidiControllers {
934 track_name: String,
935 clip_index: usize,
936 controllers: Vec<(usize, MidiControllerData)>,
937 },
938 DeleteMidiNotes {
939 track_name: String,
940 clip_index: usize,
941 note_indices: Vec<usize>,
942 deleted_notes: Vec<(usize, MidiNoteData)>,
943 },
944 InsertMidiNotes {
945 track_name: String,
946 clip_index: usize,
947 notes: Vec<(usize, MidiNoteData)>,
948 },
949 SetStepRecording(bool),
950 StepRecordMidiNote {
951 device: String,
952 channel: u8,
953 pitch: u8,
954 velocity: u8,
955 },
956 SetMidiSysExEvents {
957 track_name: String,
958 clip_index: usize,
959 new_sysex_events: Vec<MidiRawEventData>,
960 old_sysex_events: Vec<MidiRawEventData>,
961 },
962 TrackClearDefaultPassthrough {
963 track_name: String,
964 },
965 TrackClearPlugins {
966 track_name: String,
967 },
968 #[cfg(unix)]
969 TrackSetLv2PluginState {
970 track_name: String,
971 instance_id: usize,
972 state: Vec<u8>,
973 },
974 #[cfg(unix)]
975 ClipSetLv2PluginState {
976 track_name: String,
977 clip_idx: usize,
978 instance_id: usize,
979 state: Vec<u8>,
980 },
981 #[cfg(unix)]
982 TrackLv2SnapshotState {
983 track_name: String,
984 instance_id: usize,
985 },
986 #[cfg(unix)]
987 ClipLv2SnapshotState {
988 track_name: String,
989 clip_idx: usize,
990 instance_id: usize,
991 },
992 #[cfg(unix)]
993 TrackLv2StateSnapshot {
994 track_name: String,
995 instance_id: usize,
996 state: Vec<u8>,
997 },
998 #[cfg(unix)]
999 TrackGetLv2PluginControls {
1000 track_name: String,
1001 instance_id: usize,
1002 },
1003 #[cfg(unix)]
1004 ClipGetLv2PluginControls {
1005 track_name: String,
1006 clip_idx: usize,
1007 instance_id: usize,
1008 },
1009 #[cfg(unix)]
1010 TrackLv2PluginControls {
1011 track_name: String,
1012 instance_id: usize,
1013 controls: Vec<Lv2ControlPortInfo>,
1014 instance_access_handle: Option<usize>,
1015 },
1016 #[cfg(unix)]
1017 ClipLv2PluginControls {
1018 track_name: String,
1019 clip_idx: usize,
1020 instance_id: usize,
1021 controls: Vec<Lv2ControlPortInfo>,
1022 instance_access_handle: Option<usize>,
1023 },
1024 #[cfg(unix)]
1025 TrackGetLv2Midnam {
1026 track_name: String,
1027 },
1028 #[cfg(unix)]
1029 TrackLv2Midnam {
1030 track_name: String,
1031 note_names: std::collections::HashMap<u8, String>,
1032 },
1033 TrackGetClapNoteNames {
1034 track_name: String,
1035 },
1036 TrackClapNoteNames {
1037 track_name: String,
1038 note_names: std::collections::HashMap<u8, String>,
1039 },
1040 #[cfg(unix)]
1041 TrackSetLv2ControlValue {
1042 track_name: String,
1043 instance_id: usize,
1044 index: u32,
1045 value: f32,
1046 },
1047 #[cfg(unix)]
1048 ClipSetLv2ControlValue {
1049 track_name: String,
1050 clip_idx: usize,
1051 instance_id: usize,
1052 index: u32,
1053 value: f32,
1054 },
1055 #[cfg(unix)]
1056 ClipLv2StateSnapshot {
1057 track_name: String,
1058 clip_idx: usize,
1059 instance_id: usize,
1060 state: Vec<u8>,
1061 },
1062 TrackGetPluginGraph {
1063 track_name: String,
1064 include_state: bool,
1065 },
1066 TrackPluginGraph {
1067 track_name: String,
1068 plugins: Vec<PluginGraphPlugin>,
1069 connections: Vec<PluginGraphConnection>,
1070 connectable_connections: Vec<ConnectableConnection>,
1071 },
1072 TrackConnectPluginAudio {
1073 track_name: String,
1074 from_node: PluginGraphNode,
1075 from_port: usize,
1076 to_node: PluginGraphNode,
1077 to_port: usize,
1078 },
1079 TrackConnectPluginMidi {
1080 track_name: String,
1081 from_node: PluginGraphNode,
1082 from_port: usize,
1083 to_node: PluginGraphNode,
1084 to_port: usize,
1085 },
1086 TrackDisconnectPluginAudio {
1087 track_name: String,
1088 from_node: PluginGraphNode,
1089 from_port: usize,
1090 to_node: PluginGraphNode,
1091 to_port: usize,
1092 },
1093 TrackDisconnectPluginMidi {
1094 track_name: String,
1095 from_node: PluginGraphNode,
1096 from_port: usize,
1097 to_node: PluginGraphNode,
1098 to_port: usize,
1099 },
1100 TrackConnectAudio {
1101 track_name: String,
1102 from: ConnectableRef,
1103 from_port: usize,
1104 to: ConnectableRef,
1105 to_port: usize,
1106 },
1107 TrackDisconnectAudio {
1108 track_name: String,
1109 from: ConnectableRef,
1110 from_port: usize,
1111 to: ConnectableRef,
1112 to_port: usize,
1113 },
1114 TrackConnectMidi {
1115 track_name: String,
1116 from: ConnectableRef,
1117 from_port: usize,
1118 to: ConnectableRef,
1119 to_port: usize,
1120 },
1121 TrackDisconnectMidi {
1122 track_name: String,
1123 from: ConnectableRef,
1124 from_port: usize,
1125 to: ConnectableRef,
1126 to_port: usize,
1127 },
1128 #[cfg(unix)]
1129 ListLv2Plugins,
1130 #[cfg(unix)]
1131 Lv2Plugins(Vec<Lv2PluginInfo>),
1132 #[cfg(unix)]
1133 Lv2PluginsUnavailable {
1134 error: String,
1135 },
1136 ListVst3Plugins,
1137 Vst3Plugins(Vec<Vst3PluginInfo>),
1138 Vst3PluginsUnavailable {
1139 error: String,
1140 },
1141 ListClapPlugins,
1142 ListClapPluginsWithCapabilities,
1143 ClapPlugins(Vec<ClapPluginInfo>),
1144 ClapPluginsUnavailable {
1145 error: String,
1146 },
1147 TrackSetClapParameter {
1148 track_name: String,
1149 instance_id: usize,
1150 param_id: u32,
1151 value: f64,
1152 },
1153 ClipSetClapParameter {
1154 track_name: String,
1155 clip_idx: usize,
1156 instance_id: usize,
1157 param_id: u32,
1158 value: f64,
1159 },
1160 ClipGetClapParameters {
1161 track_name: String,
1162 clip_idx: usize,
1163 instance_id: usize,
1164 },
1165 ClipClapParameters {
1166 track_name: String,
1167 clip_idx: usize,
1168 instance_id: usize,
1169 parameters: Vec<ClapParameterInfo>,
1170 },
1171 TrackSetClapParameterAt {
1172 track_name: String,
1173 instance_id: usize,
1174 param_id: u32,
1175 value: f64,
1176 frame: u32,
1177 },
1178 TrackBeginClapParameterEdit {
1179 track_name: String,
1180 instance_id: usize,
1181 param_id: u32,
1182 frame: u32,
1183 },
1184 TrackEndClapParameterEdit {
1185 track_name: String,
1186 instance_id: usize,
1187 param_id: u32,
1188 frame: u32,
1189 },
1190 TrackGetClapParameters {
1191 track_name: String,
1192 instance_id: usize,
1193 },
1194 TrackClapParameters {
1195 track_name: String,
1196 instance_id: usize,
1197 parameters: Vec<ClapParameterInfo>,
1198 },
1199 TrackClapSnapshotState {
1200 track_name: String,
1201 instance_id: usize,
1202 },
1203 ClipClapSnapshotState {
1204 track_name: String,
1205 clip_idx: usize,
1206 instance_id: usize,
1207 },
1208 TrackClapStateSnapshot {
1209 track_name: String,
1210 instance_id: usize,
1211 plugin_id: String,
1212 state: crate::clap::ClapPluginState,
1213 },
1214 ClipClapStateSnapshot {
1215 track_name: String,
1216 clip_idx: usize,
1217 instance_id: usize,
1218 plugin_id: String,
1219 state: crate::clap::ClapPluginState,
1220 },
1221 TrackClapStateDirty {
1222 track_name: String,
1223 instance_id: usize,
1224 },
1225 ClipClapStateDirty {
1226 track_name: String,
1227 clip_idx: usize,
1228 instance_id: usize,
1229 },
1230 TrackClapRestoreState {
1231 track_name: String,
1232 instance_id: usize,
1233 state: crate::clap::ClapPluginState,
1234 },
1235 ClipClapRestoreState {
1236 track_name: String,
1237 clip_idx: usize,
1238 instance_id: usize,
1239 state: crate::clap::ClapPluginState,
1240 },
1241 TrackSnapshotAllClapStates {
1242 track_name: String,
1243 },
1244 TrackSnapshotAllClapStatesDone {
1245 track_name: String,
1246 },
1247 TrackLoadClapPlugin {
1248 track_name: String,
1249 plugin_id: String,
1250 instance_id: Option<usize>,
1251 },
1252 TrackUnloadClapPlugin {
1253 track_name: String,
1254 plugin_id: String,
1255 },
1256 TrackUnloadClapPluginInstance {
1257 track_name: String,
1258 instance_id: usize,
1259 },
1260 TrackShowClapGui {
1261 track_name: String,
1262 instance_id: usize,
1263 },
1264 ClipShowClapGui {
1265 track_name: String,
1266 clip_idx: usize,
1267 instance_id: usize,
1268 },
1269 TrackLoadVst3Plugin {
1270 track_name: String,
1271 plugin_id: String,
1272 instance_id: Option<usize>,
1273 },
1274 TrackUnloadVst3Plugin {
1275 track_name: String,
1276 plugin_id: String,
1277 },
1278 TrackUnloadVst3PluginInstance {
1279 track_name: String,
1280 instance_id: usize,
1281 },
1282 TrackShowVst3Gui {
1283 track_name: String,
1284 instance_id: usize,
1285 },
1286 ClipShowVst3Gui {
1287 track_name: String,
1288 clip_idx: usize,
1289 instance_id: usize,
1290 },
1291 #[cfg(unix)]
1292 TrackLoadLv2Plugin {
1293 track_name: String,
1294 plugin_uri: String,
1295 instance_id: Option<usize>,
1296 },
1297 #[cfg(unix)]
1298 TrackUnloadLv2Plugin {
1299 track_name: String,
1300 plugin_uri: String,
1301 },
1302 #[cfg(unix)]
1303 TrackUnloadLv2PluginInstance {
1304 track_name: String,
1305 instance_id: usize,
1306 },
1307 TrackShowLv2Gui {
1308 track_name: String,
1309 instance_id: usize,
1310 },
1311 ClipShowLv2Gui {
1312 track_name: String,
1313 clip_idx: usize,
1314 instance_id: usize,
1315 },
1316 TrackSetPluginResourceDir {
1317 track_name: String,
1318 instance_id: usize,
1319 format: String,
1320 directory: String,
1321 shared: bool,
1322 },
1323 TrackClapCollectResources {
1324 track_name: String,
1325 instance_id: usize,
1326 },
1327 TrackClapResourceFiles {
1328 track_name: String,
1329 instance_id: usize,
1330 files: Vec<(u32, String)>,
1331 },
1332 ClipSetPluginResourceDir {
1333 track_name: String,
1334 clip_idx: usize,
1335 instance_id: usize,
1336 format: String,
1337 directory: String,
1338 shared: bool,
1339 },
1340 ClipClapCollectResources {
1341 track_name: String,
1342 clip_idx: usize,
1343 instance_id: usize,
1344 },
1345 ClipClapResourceFiles {
1346 track_name: String,
1347 clip_idx: usize,
1348 instance_id: usize,
1349 files: Vec<(u32, String)>,
1350 },
1351 TrackGetVst3Graph {
1352 track_name: String,
1353 },
1354 TrackVst3Graph {
1355 track_name: String,
1356 plugins: Vec<Vst3GraphPlugin>,
1357 connections: Vec<Vst3GraphConnection>,
1358 },
1359 TrackSetVst3Parameter {
1360 track_name: String,
1361 instance_id: usize,
1362 param_id: u32,
1363 value: f32,
1364 },
1365 ClipSetVst3Parameter {
1366 track_name: String,
1367 clip_idx: usize,
1368 instance_id: usize,
1369 param_id: u32,
1370 value: f32,
1371 },
1372 TrackSetPluginBypassed {
1373 track_name: String,
1374 instance_id: usize,
1375 format: String,
1376 bypassed: bool,
1377 },
1378 TrackGetVst3Parameters {
1379 track_name: String,
1380 instance_id: usize,
1381 },
1382 TrackVst3Parameters {
1383 track_name: String,
1384 instance_id: usize,
1385 parameters: Vec<crate::vst3::port::ParameterInfo>,
1386 },
1387 ClipGetVst3Parameters {
1388 track_name: String,
1389 clip_idx: usize,
1390 instance_id: usize,
1391 },
1392 ClipVst3Parameters {
1393 track_name: String,
1394 clip_idx: usize,
1395 instance_id: usize,
1396 parameters: Vec<crate::vst3::port::ParameterInfo>,
1397 },
1398 TrackVst3SnapshotState {
1399 track_name: String,
1400 instance_id: usize,
1401 },
1402 ClipVst3SnapshotState {
1403 track_name: String,
1404 clip_idx: usize,
1405 instance_id: usize,
1406 },
1407 TrackVst3StateSnapshot {
1408 track_name: String,
1409 instance_id: usize,
1410 state: crate::vst3::state::Vst3PluginState,
1411 },
1412 ClipVst3StateSnapshot {
1413 track_name: String,
1414 clip_idx: usize,
1415 instance_id: usize,
1416 state: crate::vst3::state::Vst3PluginState,
1417 },
1418 TrackVst3RestoreState {
1419 track_name: String,
1420 instance_id: usize,
1421 state: crate::vst3::state::Vst3PluginState,
1422 },
1423 ClipVst3RestoreState {
1424 track_name: String,
1425 clip_idx: usize,
1426 instance_id: usize,
1427 state: crate::vst3::state::Vst3PluginState,
1428 },
1429 TrackConnectVst3Audio {
1430 track_name: String,
1431 from_node: Vst3GraphNode,
1432 from_port: usize,
1433 to_node: Vst3GraphNode,
1434 to_port: usize,
1435 },
1436 TrackDisconnectVst3Audio {
1437 track_name: String,
1438 from_node: Vst3GraphNode,
1439 from_port: usize,
1440 to_node: Vst3GraphNode,
1441 to_port: usize,
1442 },
1443 ClipMove {
1444 kind: Kind,
1445 from: ClipMoveFrom,
1446 to: ClipMoveTo,
1447 copy: bool,
1448 },
1449 Connect {
1450 from_track: String,
1451 from_port: usize,
1452 to_track: String,
1453 to_port: usize,
1454 kind: Kind,
1455 },
1456 Disconnect {
1457 from_track: String,
1458 from_port: usize,
1459 to_track: String,
1460 to_port: usize,
1461 kind: Kind,
1462 },
1463 OpenAudioDevice {
1464 device: String,
1465 input_device: Option<String>,
1466 sample_rate_hz: i32,
1467 bits: i32,
1468 exclusive: bool,
1469 period_frames: usize,
1470 nperiods: usize,
1471 sync_mode: bool,
1472 actual_period_frames: usize,
1473 input_channels: usize,
1474 output_channels: usize,
1475 bytes_per_frame: usize,
1476 },
1477 JackAddAudioInputPort,
1478 JackRemoveAudioInputPort(usize),
1479 JackAddAudioOutputPort,
1480 JackRemoveAudioOutputPort(usize),
1481 JackGetGraph,
1482 JackGraph(JackGraphInfo),
1483 JackConnect {
1484 source: String,
1485 destination: String,
1486 },
1487 JackDisconnect {
1488 source: String,
1489 destination: String,
1490 },
1491 OpenMidiInputDevice(String),
1492 OpenMidiOutputDevice(String),
1493 RequestSessionDiagnostics,
1494 RequestMidiLearnMappingsReport,
1495 ClearAllMidiLearnBindings,
1496 SessionDiagnosticsReport {
1497 track_count: usize,
1498 frozen_track_count: usize,
1499 audio_clip_count: usize,
1500 midi_clip_count: usize,
1501 #[cfg(unix)]
1502 lv2_instance_count: usize,
1503 vst3_instance_count: usize,
1504 clap_instance_count: usize,
1505 pending_requests: usize,
1506 workers_total: usize,
1507 workers_ready: usize,
1508 pending_hw_midi_events: usize,
1509 playing: bool,
1510 transport_running: bool,
1511 transport_sample: usize,
1512 tempo_bpm: f64,
1513 sample_rate_hz: usize,
1514 cycle_samples: usize,
1515 },
1516 MidiLearnMappingsReport {
1517 lines: Vec<String>,
1518 },
1519 HWInfo {
1520 channels: usize,
1521 rate: usize,
1522 input: bool,
1523 },
1524 MarkHistorySavePoint,
1525 HistoryState {
1526 dirty: bool,
1527 },
1528 Undo,
1529 Redo,
1530 Session(SessionAction),
1531 SessionRuntimeReport {
1532 track_name: String,
1533 scene_index: usize,
1534 state: SessionSlotState,
1535 play_position_samples: usize,
1536 elapsed_samples: usize,
1537 },
1538 Panic,
1539}
1540
1541#[derive(Clone, Debug)]
1542pub enum Message {
1543 Ready(usize),
1544 TracksFinished,
1545
1546 NodeJob(crate::executor::NodeJob),
1548 NodeDone {
1550 worker_id: usize,
1551 epoch: u64,
1552 node: u32,
1553 output_linear: Vec<f32>,
1554 parameter_updates: Vec<Action>,
1555 latency_changed: bool,
1556 },
1557 ProcessOfflineBounce(OfflineBounceWork),
1558 Channel(Sender<Self>),
1559
1560 Request(Action),
1561 OscRequest {
1562 action: Action,
1563 reply_to: SocketAddr,
1564 },
1565 Response(Result<Action, String>),
1566 HWMidiEvents(Vec<HwMidiEvent>),
1567 HWMidiOutEvents(Vec<HwMidiEvent>),
1568 ClearHWMidiOutEvents,
1569 StartAudioPreview {
1570 samples: Arc<Vec<f32>>,
1571 channels: usize,
1572 start_sample: usize,
1573 },
1574 StopAudioPreview,
1575 HWSetPlaying(bool),
1576 HWSetOutputGainBalance {
1577 gain: f32,
1578 balance: f32,
1579 },
1580 HWOpenMidiInputDevice(String),
1581 HWOpenMidiOutputDevice(String),
1582 HWCloseMidiDevices,
1583 HWFinished,
1584 OfflineBounceFinished {
1585 result: Result<Action, String>,
1586 },
1587}
1588
1589#[cfg(test)]
1590mod tests {
1591 use super::{AudioClipData, MidiClipData, PitchCorrectionPointData};
1592 use serde_json::json;
1593
1594 #[test]
1595 fn audio_clip_data_serde_round_trips_nested_groups() {
1596 let clip = AudioClipData {
1597 id: String::new(),
1598 name: "group.wav".to_string(),
1599 start: 12,
1600 length: 96,
1601 offset: 3,
1602 input_channel: 1,
1603 muted: true,
1604 reversed: false,
1605 gain_db: 0.0,
1606 peaks_file: Some("peaks/group.json".to_string()),
1607 fade_enabled: false,
1608 fade_in_samples: 10,
1609 fade_out_samples: 20,
1610 preview_name: Some("preview.wav".to_string()),
1611 source_name: Some("source.wav".to_string()),
1612 source_offset: Some(4),
1613 source_length: Some(88),
1614 pitch_correction_points: vec![PitchCorrectionPointData {
1615 start_sample: 7,
1616 length_samples: 11,
1617 detected_midi_pitch: 60.1,
1618 target_midi_pitch: 61.2,
1619 clarity: 0.8,
1620 }],
1621 pitch_correction_frame_likeness: Some(0.5),
1622 pitch_correction_inertia_ms: Some(123),
1623 pitch_correction_formant_compensation: Some(false),
1624 plugin_graph_json: Some(json!({"plugins":[],"connections":[{"kind":"Audio"}]})),
1625 grouped_clips: vec![AudioClipData {
1626 name: "child.wav".to_string(),
1627 start: 0,
1628 length: 48,
1629 ..AudioClipData::default()
1630 }],
1631 };
1632
1633 let value = serde_json::to_value(&clip).expect("serialize");
1634 let restored: AudioClipData = serde_json::from_value(value).expect("deserialize");
1635
1636 assert_eq!(restored.name, clip.name);
1637 assert_eq!(restored.preview_name, clip.preview_name);
1638 assert_eq!(restored.source_name, clip.source_name);
1639 assert_eq!(restored.plugin_graph_json, clip.plugin_graph_json);
1640 assert_eq!(restored.grouped_clips.len(), 1);
1641 assert_eq!(restored.grouped_clips[0].name, "child.wav");
1642 assert_eq!(restored.pitch_correction_points[0].target_midi_pitch, 61.2);
1643 }
1644
1645 #[test]
1646 fn midi_clip_data_serde_round_trips_nested_groups() {
1647 let clip = MidiClipData {
1648 id: String::new(),
1649 name: "group.mid".to_string(),
1650 start: 5,
1651 length: 64,
1652 offset: 2,
1653 input_channel: 3,
1654 muted: true,
1655 reversed: false,
1656 grouped_clips: vec![MidiClipData {
1657 name: "child.mid".to_string(),
1658 start: 0,
1659 length: 32,
1660 ..MidiClipData::default()
1661 }],
1662 };
1663
1664 let value = serde_json::to_value(&clip).expect("serialize");
1665 let restored: MidiClipData = serde_json::from_value(value).expect("deserialize");
1666
1667 assert_eq!(restored.name, clip.name);
1668 assert_eq!(restored.grouped_clips.len(), 1);
1669 assert_eq!(restored.grouped_clips[0].name, "child.mid");
1670 }
1671
1672 #[test]
1673 fn pitch_correction_point_data_serde_round_trips() {
1674 let point = PitchCorrectionPointData {
1675 start_sample: 10,
1676 length_samples: 20,
1677 detected_midi_pitch: 57.5,
1678 target_midi_pitch: 58.0,
1679 clarity: 0.9,
1680 };
1681
1682 let value = serde_json::to_value(&point).expect("serialize");
1683 let restored: PitchCorrectionPointData =
1684 serde_json::from_value(value).expect("deserialize");
1685
1686 assert_eq!(restored.start_sample, 10);
1687 assert_eq!(restored.length_samples, 20);
1688 assert_eq!(restored.detected_midi_pitch, 57.5);
1689 assert_eq!(restored.target_midi_pitch, 58.0);
1690 assert_eq!(restored.clarity, 0.9);
1691 }
1692
1693 #[test]
1694 fn audio_clip_data_deserializes_with_omitted_optional_fields() {
1695 let restored: AudioClipData = serde_json::from_value(json!({
1696 "name": "clip.wav",
1697 "start": 1,
1698 "length": 2,
1699 "offset": 3,
1700 "input_channel": 0,
1701 "muted": false,
1702 "fade_enabled": true,
1703 "fade_in_samples": 240,
1704 "fade_out_samples": 240,
1705 "pitch_correction_points": [],
1706 "grouped_clips": []
1707 }))
1708 .expect("deserialize");
1709
1710 assert_eq!(restored.name, "clip.wav");
1711 assert!(restored.peaks_file.is_none());
1712 assert!(restored.preview_name.is_none());
1713 assert!(restored.source_name.is_none());
1714 assert!(restored.source_offset.is_none());
1715 assert!(restored.source_length.is_none());
1716 assert!(restored.pitch_correction_points.is_empty());
1717 assert!(restored.plugin_graph_json.is_none());
1718 }
1719}