Files
djdeck/src/deck.rs
T

152 lines
3.9 KiB
Rust

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<Self> {
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<PathBuf>,
pub track_name: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
// Transport
pub play_state: PlayState,
pub position: u64,
pub duration: u64,
pub sample_rate: u32,
pub cue_point: Option<u64>,
// 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<f32>,
}
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)
}
}