use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use crate::engine::VuLevels;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TrackId(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackKind {
Instrument,
Audio,
SendA,
SendB,
Master,
}
#[derive(Debug)]
pub struct TrackConfig {
pub muted: AtomicBool,
pub soloed: AtomicBool,
pub armed: AtomicBool,
pub midi_active: AtomicBool,
pub volume: AtomicU32,
}
impl TrackConfig {
pub const MIN_VOLUME: f32 = 0.0;
pub const UNITY_VOLUME: f32 = 1.0;
pub const MAX_VOLUME: f32 = 2.0;
pub const DEFAULT_VOLUME: f32 = 0.75;
pub fn new() -> Self {
Self {
muted: AtomicBool::new(false),
soloed: AtomicBool::new(false),
armed: AtomicBool::new(false),
midi_active: AtomicBool::new(false),
volume: AtomicU32::new(Self::DEFAULT_VOLUME.to_bits()),
}
}
pub fn get_volume(&self) -> f32 {
f32::from_bits(self.volume.load(Ordering::Relaxed))
}
pub fn set_volume(&self, v: f32) {
if v.is_nan() {
return;
}
let clamped = v.clamp(Self::MIN_VOLUME, Self::MAX_VOLUME);
self.volume.store(clamped.to_bits(), Ordering::Relaxed);
}
pub fn is_muted(&self) -> bool {
self.muted.load(Ordering::Relaxed)
}
pub fn is_soloed(&self) -> bool {
self.soloed.load(Ordering::Relaxed)
}
pub fn is_armed(&self) -> bool {
self.armed.load(Ordering::Relaxed)
}
pub fn is_midi_active(&self) -> bool {
self.midi_active.load(Ordering::Relaxed)
}
}
impl Default for TrackConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct TrackHandle {
pub id: usize,
pub kind: TrackKind,
pub config: TrackConfig,
pub vu: VuLevels,
}
impl TrackHandle {
pub fn new(id: usize, kind: TrackKind) -> Self {
Self {
id,
kind,
config: TrackConfig::new(),
vu: VuLevels::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn track_config_defaults() {
let cfg = TrackConfig::new();
assert!(!cfg.is_muted());
assert!(!cfg.is_soloed());
assert!(!cfg.is_armed());
assert!((cfg.get_volume() - TrackConfig::DEFAULT_VOLUME).abs() < 0.001);
}
#[test]
fn track_config_volume_round_trip() {
let cfg = TrackConfig::new();
cfg.set_volume(0.42);
assert!((cfg.get_volume() - 0.42).abs() < 0.001);
}
#[test]
fn track_config_volume_spans_the_fader() {
let cfg = TrackConfig::new();
for v in [0.0f32, 0.25, TrackConfig::DEFAULT_VOLUME, TrackConfig::UNITY_VOLUME, 1.5, 2.0] {
cfg.set_volume(v);
assert_eq!(cfg.get_volume().to_bits(), v.to_bits(), "fader lost {v}");
}
}
#[test]
fn track_config_volume_is_clamped() {
let cfg = TrackConfig::new();
cfg.set_volume(-1.0);
assert_eq!(cfg.get_volume(), TrackConfig::MIN_VOLUME);
cfg.set_volume(50.0);
assert_eq!(cfg.get_volume(), TrackConfig::MAX_VOLUME);
cfg.set_volume(f32::INFINITY);
assert_eq!(cfg.get_volume(), TrackConfig::MAX_VOLUME);
cfg.set_volume(f32::NEG_INFINITY);
assert_eq!(cfg.get_volume(), TrackConfig::MIN_VOLUME);
}
#[test]
fn track_config_volume_ignores_nan() {
let cfg = TrackConfig::new();
cfg.set_volume(1.25);
cfg.set_volume(f32::NAN);
assert_eq!(cfg.get_volume(), 1.25);
}
#[test]
fn track_config_atomics() {
let cfg = TrackConfig::new();
cfg.muted.store(true, Ordering::Relaxed);
assert!(cfg.is_muted());
cfg.soloed.store(true, Ordering::Relaxed);
assert!(cfg.is_soloed());
cfg.armed.store(true, Ordering::Relaxed);
assert!(cfg.is_armed());
}
#[test]
fn track_handle_new() {
let h = TrackHandle::new(0, TrackKind::Instrument);
assert_eq!(h.id, 0);
assert_eq!(h.kind, TrackKind::Instrument);
assert!(!h.config.is_muted());
}
#[test]
fn track_kind_variants() {
assert_ne!(TrackKind::Instrument, TrackKind::Audio);
assert_ne!(TrackKind::SendA, TrackKind::SendB);
assert_ne!(TrackKind::Master, TrackKind::Audio);
}
}