initial code scetch
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[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>,
|
||||
|
||||
// Transport
|
||||
pub play_state: PlayState,
|
||||
pub position: u64, // samples
|
||||
pub duration: u64, // samples
|
||||
pub sample_rate: u32,
|
||||
pub cue_point: Option<u64>,
|
||||
|
||||
// Per-deck mix controls
|
||||
/// 0.0 – 1.0, sent to wpctl on the deck's PipeWire stream node
|
||||
pub fader: f32,
|
||||
|
||||
// Effect chain parameters (sent to pw-cli / filter-chain node)
|
||||
/// Semitones -12.0 .. +12.0
|
||||
pub pitch: f32,
|
||||
/// Tempo multiplier 0.5 .. 2.0
|
||||
pub tempo: f32,
|
||||
|
||||
// VU (updated by audio engine)
|
||||
pub vu: (f32, f32),
|
||||
|
||||
// PipeWire object IDs (populated after graph setup)
|
||||
/// Object ID of the deck's playback stream node
|
||||
pub stream_node_id: Option<u32>,
|
||||
/// Object ID of the filter-chain node for this deck
|
||||
pub filter_node_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl Deck {
|
||||
pub fn new(id: DeckId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
track: None,
|
||||
track_name: None,
|
||||
play_state: PlayState::Stopped,
|
||||
position: 0,
|
||||
duration: 0,
|
||||
sample_rate: 44100,
|
||||
cue_point: None,
|
||||
fader: 0.8,
|
||||
pitch: 0.0,
|
||||
tempo: 1.0,
|
||||
vu: (0.0, 0.0),
|
||||
stream_node_id: None,
|
||||
filter_node_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_loaded(&self) -> bool {
|
||||
self.track.is_some()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user