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