1use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
8
9use crate::engine::VuLevels;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct TrackId(pub usize);
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum TrackKind {
18 Instrument,
20 Audio,
22 SendA,
24 SendB,
26 Master,
28}
29
30#[derive(Debug)]
35pub struct TrackConfig {
36 pub muted: AtomicBool,
37 pub soloed: AtomicBool,
38 pub armed: AtomicBool,
39 pub midi_active: AtomicBool,
42 pub volume: AtomicU32,
48}
49
50impl TrackConfig {
51 pub const MIN_VOLUME: f32 = 0.0;
53
54 pub const UNITY_VOLUME: f32 = 1.0;
57
58 pub const MAX_VOLUME: f32 = 2.0;
67
68 pub const DEFAULT_VOLUME: f32 = 0.75;
74
75 pub fn new() -> Self {
76 Self {
77 muted: AtomicBool::new(false),
78 soloed: AtomicBool::new(false),
79 armed: AtomicBool::new(false),
80 midi_active: AtomicBool::new(false),
81 volume: AtomicU32::new(Self::DEFAULT_VOLUME.to_bits()),
82 }
83 }
84
85 pub fn get_volume(&self) -> f32 {
86 f32::from_bits(self.volume.load(Ordering::Relaxed))
87 }
88
89 pub fn set_volume(&self, v: f32) {
100 if v.is_nan() {
101 return;
102 }
103 let clamped = v.clamp(Self::MIN_VOLUME, Self::MAX_VOLUME);
104 self.volume.store(clamped.to_bits(), Ordering::Relaxed);
105 }
106
107 pub fn is_muted(&self) -> bool {
108 self.muted.load(Ordering::Relaxed)
109 }
110
111 pub fn is_soloed(&self) -> bool {
112 self.soloed.load(Ordering::Relaxed)
113 }
114
115 pub fn is_armed(&self) -> bool {
116 self.armed.load(Ordering::Relaxed)
117 }
118
119 pub fn is_midi_active(&self) -> bool {
120 self.midi_active.load(Ordering::Relaxed)
121 }
122}
123
124impl Default for TrackConfig {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130#[derive(Debug)]
133pub struct TrackHandle {
134 pub id: usize,
135 pub kind: TrackKind,
136 pub config: TrackConfig,
137 pub vu: VuLevels,
138}
139
140impl TrackHandle {
141 pub fn new(id: usize, kind: TrackKind) -> Self {
142 Self {
143 id,
144 kind,
145 config: TrackConfig::new(),
146 vu: VuLevels::new(),
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn track_config_defaults() {
157 let cfg = TrackConfig::new();
158 assert!(!cfg.is_muted());
159 assert!(!cfg.is_soloed());
160 assert!(!cfg.is_armed());
161 assert!((cfg.get_volume() - TrackConfig::DEFAULT_VOLUME).abs() < 0.001);
162 }
163
164 #[test]
165 fn track_config_volume_round_trip() {
166 let cfg = TrackConfig::new();
167 cfg.set_volume(0.42);
168 assert!((cfg.get_volume() - 0.42).abs() < 0.001);
169 }
170
171 #[test]
174 fn track_config_volume_spans_the_fader() {
175 let cfg = TrackConfig::new();
176 for v in [0.0f32, 0.25, TrackConfig::DEFAULT_VOLUME, TrackConfig::UNITY_VOLUME, 1.5, 2.0] {
177 cfg.set_volume(v);
178 assert_eq!(cfg.get_volume().to_bits(), v.to_bits(), "fader lost {v}");
179 }
180 }
181
182 #[test]
185 fn track_config_volume_is_clamped() {
186 let cfg = TrackConfig::new();
187 cfg.set_volume(-1.0);
188 assert_eq!(cfg.get_volume(), TrackConfig::MIN_VOLUME);
189 cfg.set_volume(50.0);
190 assert_eq!(cfg.get_volume(), TrackConfig::MAX_VOLUME);
191 cfg.set_volume(f32::INFINITY);
192 assert_eq!(cfg.get_volume(), TrackConfig::MAX_VOLUME);
193 cfg.set_volume(f32::NEG_INFINITY);
194 assert_eq!(cfg.get_volume(), TrackConfig::MIN_VOLUME);
195 }
196
197 #[test]
200 fn track_config_volume_ignores_nan() {
201 let cfg = TrackConfig::new();
202 cfg.set_volume(1.25);
203 cfg.set_volume(f32::NAN);
204 assert_eq!(cfg.get_volume(), 1.25);
205 }
206
207 #[test]
208 fn track_config_atomics() {
209 let cfg = TrackConfig::new();
210 cfg.muted.store(true, Ordering::Relaxed);
211 assert!(cfg.is_muted());
212 cfg.soloed.store(true, Ordering::Relaxed);
213 assert!(cfg.is_soloed());
214 cfg.armed.store(true, Ordering::Relaxed);
215 assert!(cfg.is_armed());
216 }
217
218 #[test]
219 fn track_handle_new() {
220 let h = TrackHandle::new(0, TrackKind::Instrument);
221 assert_eq!(h.id, 0);
222 assert_eq!(h.kind, TrackKind::Instrument);
223 assert!(!h.config.is_muted());
224 }
225
226 #[test]
227 fn track_kind_variants() {
228 assert_ne!(TrackKind::Instrument, TrackKind::Audio);
229 assert_ne!(TrackKind::SendA, TrackKind::SendB);
230 assert_ne!(TrackKind::Master, TrackKind::Audio);
231 }
232}