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