use std::path::PathBuf; // ─── Defaults ─────────────────────────────────────────────────────────────── pub const PITCH_DEFAULT: f32 = 0.0; pub const TEMPO_DEFAULT: f32 = 1.0; pub const CUTOFF_DEFAULT: f32 = 0.0; pub const REVERB_DEFAULT: f32 = 0.0; pub const FADER_DEFAULT: f32 = 0.8; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DeckId { A = 0, Z = 1, S = 2, X = 3, } impl DeckId { pub fn from_index(i: usize) -> Option { match i { 0 => Some(Self::A), 1 => Some(Self::Z), 2 => Some(Self::S), 3 => Some(Self::X), _ => None, } } pub fn label(self) -> &'static str { match self { Self::A => "A", Self::Z => "Z", Self::S => "S", Self::X => "X", } } /// Lowercase node name suffix used in PipeWire node names pub fn node_suffix(self) -> &'static str { match self { Self::A => "a", Self::Z => "z", Self::S => "s", Self::X => "x", } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum PlayState { Stopped, Playing, Paused, Cued, } impl PlayState { pub fn icon(&self) -> &'static str { match self { Self::Playing => "▶", Self::Paused => "⏸", Self::Stopped => "⏹", Self::Cued => "⏮", } } } #[derive(Debug, Clone)] pub struct Deck { pub id: DeckId, // Track pub track: Option, pub track_name: Option, pub artist: Option, pub album: Option, // Transport pub play_state: PlayState, pub position: u64, pub duration: u64, pub sample_rate: u32, pub cue_point: Option, // Mix / effect controls (mirror of the realtime DSP state in audio.rs) pub fader: f32, // 0.0 .. 1.5 pub pitch: f32, // semitones, -12 .. +12 pub tempo: f32, // 0.5 .. 2.0 pub cutoff: f32, // < 0 lowpass Hz, > 0 highpass Hz, 0 flat pub reverb: f32, // 0.0 .. 1.0 // VU (updated by audio engine at ~60 Hz) pub vu: (f32, f32), // Per-bucket band peaks for the waveform: [bass, mid, high], one entry per // ~0.1 s of audio (PEAK_BUCKET frames). Sent in WaveformChunks during decode. pub waveform_bands: Vec<[f32; 3]>, /// BPM estimate from the decode-time analyzer; `None` until detected. pub bpm: Option, } impl Deck { pub fn new(id: DeckId) -> Self { Self { id, track: None, track_name: None, artist: None, album: None, play_state: PlayState::Stopped, position: 0, duration: 0, sample_rate: 44100, cue_point: None, fader: FADER_DEFAULT, pitch: PITCH_DEFAULT, tempo: TEMPO_DEFAULT, cutoff: CUTOFF_DEFAULT, reverb: REVERB_DEFAULT, vu: (0.0, 0.0), waveform_bands: Vec::new(), bpm: None, } } pub fn is_loaded(&self) -> bool { self.track.is_some() } pub fn peaks_empty(&self) -> bool { self.waveform_bands.is_empty() } pub fn progress(&self) -> f32 { if self.duration == 0 { 0.0 } else { (self.position as f32 / self.duration as f32).clamp(0.0, 1.0) } } pub fn elapsed(&self) -> (u64, u64) { let s = self.position / self.sample_rate as u64; (s / 60, s % 60) } pub fn remaining(&self) -> (u64, u64) { let s = self.duration.saturating_sub(self.position) / self.sample_rate as u64; (s / 60, s % 60) } }