Skip to main content

phosphor_app/state/
track.rs

1//! Track state — TrackState, TrackElement, Clip, MidiNote.
2
3use phosphor_core::project::TrackKind;
4
5// ── Track Element Navigation ──
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum TrackElement {
9    Label,
10    Fx,
11    Volume,
12    Mute,
13    Solo,
14    RecordArm,
15    Clip(usize),
16}
17
18impl TrackElement {
19    pub fn move_right(self, num_clips: usize) -> Self {
20        match self {
21            Self::Label => Self::Fx,
22            Self::Fx => Self::Volume,
23            Self::Volume => Self::Mute,
24            Self::Mute => Self::Solo,
25            Self::Solo => Self::RecordArm,
26            Self::RecordArm => {
27                if num_clips > 0 { Self::Clip(0) } else { Self::RecordArm }
28            }
29            Self::Clip(i) => {
30                if i + 1 < num_clips { Self::Clip(i + 1) } else { Self::Clip(i) }
31            }
32        }
33    }
34
35    pub fn move_left(self) -> Self {
36        match self {
37            Self::Label => Self::Label,
38            Self::Fx => Self::Label,
39            Self::Volume => Self::Fx,
40            Self::Mute => Self::Volume,
41            Self::Solo => Self::Mute,
42            Self::RecordArm => Self::Solo,
43            Self::Clip(0) => Self::RecordArm,
44            Self::Clip(i) => Self::Clip(i - 1),
45        }
46    }
47}
48
49// ── Data Models ──
50
51#[derive(Debug, Clone)]
52pub struct Clip {
53    pub number: usize,
54    pub width: u16,
55    pub has_content: bool,
56    /// Start position on the timeline (ticks).
57    pub start_tick: i64,
58    /// Length in ticks.
59    pub length_ticks: i64,
60    /// Notes for piano roll display (from ClipSnapshot).
61    pub notes: Vec<phosphor_core::clip::NoteSnapshot>,
62    /// Notes hidden by shrinking the clip. Stored with start_frac and
63    /// duration_frac as absolute tick ratios (tick / original_length_when_hidden)
64    /// converted to tick offsets for stable restore.
65    /// Format: (tick_offset_from_clip_start, duration_ticks, note, velocity)
66    pub hidden_notes: Vec<(i64, i64, u8, u8)>,
67}
68
69#[derive(Debug, Clone)]
70pub struct TrackState {
71    pub name: String,
72    pub muted: bool,
73    pub soloed: bool,
74    pub armed: bool,
75    pub color_index: usize,
76    pub kind: TrackKind,
77    pub clips: Vec<Clip>,
78    /// Track-level FX chain.
79    pub fx_chain: Vec<super::FxInstance>,
80    /// Track volume (0.0..1.0).
81    pub volume: f32,
82    /// Unique ID for this track (matches the mixer's track ID).
83    pub mixer_id: Option<usize>,
84    /// Handle to the audio engine's track state. When present, mute/solo/arm/volume
85    /// writes go directly to the audio thread via atomics.
86    pub handle: Option<std::sync::Arc<phosphor_core::project::TrackHandle>>,
87    /// What type of instrument this track has.
88    pub instrument_type: Option<super::InstrumentType>,
89    /// Parameter values (mirrors the audio thread's plugin params).
90    pub synth_params: Vec<f32>,
91}
92
93impl TrackState {
94    pub fn new(name: &str, color_index: usize, armed: bool, kind: TrackKind, clips: Vec<Clip>) -> Self {
95        Self {
96            name: name.to_string(),
97            muted: false,
98            soloed: false,
99            armed,
100            color_index,
101            kind,
102            clips,
103            fx_chain: Vec::new(),
104            volume: 0.75,
105            mixer_id: None,
106            handle: None,
107            instrument_type: None,
108            synth_params: Vec::new(),
109        }
110    }
111
112    /// Sync mute/solo/arm/volume to the audio thread handle (if wired up).
113    pub fn sync_to_audio(&self) {
114        if let Some(ref h) = self.handle {
115            h.config.muted.store(self.muted, std::sync::atomic::Ordering::Relaxed);
116            h.config.soloed.store(self.soloed, std::sync::atomic::Ordering::Relaxed);
117            h.config.armed.store(self.armed, std::sync::atomic::Ordering::Relaxed);
118            h.config.set_volume(self.volume);
119        }
120    }
121
122    /// Read VU levels from the audio thread handle.
123    pub fn vu_levels(&self) -> (f32, f32) {
124        self.handle.as_ref().map(|h| h.vu.get()).unwrap_or((0.0, 0.0))
125    }
126
127    /// Whether this track is wired to the audio engine.
128    pub fn is_live(&self) -> bool {
129        self.handle.is_some()
130    }
131}