Skip to main content

ym2149_common/
player.rs

1//! Unified chiptune player trait.
2//!
3//! Defines the common interface for all YM2149-based music players.
4//!
5//! # Trait Hierarchy
6//!
7//! - [`ChiptunePlayerBase`] - Object-safe base trait for dynamic dispatch
8//! - [`ChiptunePlayer`] - Full trait with associated `Metadata` type
9//!
10//! Use `ChiptunePlayerBase` when you need trait objects (`Box<dyn ChiptunePlayerBase>`).
11//! Use `ChiptunePlayer` when you need access to the specific metadata type.
12
13use crate::PlaybackMetadata;
14
15/// Playback state for chiptune players.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum PlaybackState {
18    /// Player is stopped (at beginning or end).
19    #[default]
20    Stopped,
21    /// Player is actively playing.
22    Playing,
23    /// Player is paused (can resume).
24    Paused,
25}
26
27/// Object-safe base trait for chiptune players.
28///
29/// This trait provides all playback functionality without the associated
30/// `Metadata` type, making it usable as a trait object (`Box<dyn ChiptunePlayerBase>`).
31///
32/// All types implementing [`ChiptunePlayer`] automatically implement this trait.
33///
34/// # Example
35///
36/// ```ignore
37/// use ym2149_common::{ChiptunePlayerBase, PlaybackState};
38///
39/// fn play_any(player: &mut dyn ChiptunePlayerBase) {
40///     player.play();
41///     while player.state() == PlaybackState::Playing {
42///         let mut buffer = vec![0.0; 1024];
43///         player.generate_samples_into(&mut buffer);
44///         // ... send buffer to audio device
45///     }
46/// }
47/// ```
48pub trait ChiptunePlayerBase: Send {
49    /// Start or resume playback.
50    fn play(&mut self);
51
52    /// Pause playback (keeps position).
53    fn pause(&mut self);
54
55    /// Stop playback and reset to beginning.
56    fn stop(&mut self);
57
58    /// Get current playback state.
59    fn state(&self) -> PlaybackState;
60
61    /// Check if currently playing.
62    fn is_playing(&self) -> bool {
63        self.state() == PlaybackState::Playing
64    }
65
66    /// Generate samples into an existing buffer.
67    ///
68    /// Fills the entire buffer with audio samples. If playback is stopped
69    /// or paused, the buffer is filled with silence (zeros).
70    fn generate_samples_into(&mut self, buffer: &mut [f32]);
71
72    /// Generate samples into a new buffer.
73    fn generate_samples(&mut self, count: usize) -> Vec<f32> {
74        let mut buffer = vec![0.0; count];
75        self.generate_samples_into(&mut buffer);
76        buffer
77    }
78
79    /// Get the output sample rate in Hz.
80    ///
81    /// Typical value is 44100 Hz.
82    fn sample_rate(&self) -> u32 {
83        44100
84    }
85
86    /// Mute or unmute a specific channel (0-2).
87    ///
88    /// Default implementation does nothing. Override if the player
89    /// supports channel muting.
90    fn set_channel_mute(&mut self, _channel: usize, _mute: bool) {}
91
92    /// Check if a channel is muted.
93    ///
94    /// Default returns false. Override if the player supports channel muting.
95    fn is_channel_muted(&self, _channel: usize) -> bool {
96        false
97    }
98
99    /// Get playback position as a percentage (0.0 to 1.0).
100    ///
101    /// Default returns 0.0. Override if position tracking is available.
102    fn playback_position(&self) -> f32 {
103        0.0
104    }
105
106    /// Seek to a position (0.0 to 1.0).
107    ///
108    /// Returns `true` if seeking is supported and successful.
109    /// Default returns `false` (seeking not supported).
110    fn seek(&mut self, _position: f32) -> bool {
111        false
112    }
113
114    /// Get the total duration in seconds.
115    ///
116    /// Returns 0.0 if duration is unknown.
117    fn duration_seconds(&self) -> f32 {
118        0.0
119    }
120
121    /// Get elapsed time in seconds based on playback position.
122    ///
123    /// Uses `playback_position()` and `duration_seconds()` for calculation.
124    fn elapsed_seconds(&self) -> f32 {
125        self.playback_position() * self.duration_seconds()
126    }
127
128    /// Get the number of subsongs in this file.
129    ///
130    /// Default returns 1. Override for formats with multiple subsongs.
131    fn subsong_count(&self) -> usize {
132        1
133    }
134
135    /// Get the current subsong index (1-based).
136    ///
137    /// Default returns 1. Override for formats with multiple subsongs.
138    fn current_subsong(&self) -> usize {
139        1
140    }
141
142    /// Switch to a different subsong by 1-based index.
143    ///
144    /// Returns `true` if successful. Default returns `false`.
145    fn set_subsong(&mut self, _index: usize) -> bool {
146        false
147    }
148
149    /// Check if this player supports multiple subsongs.
150    fn has_subsongs(&self) -> bool {
151        self.subsong_count() > 1
152    }
153
154    /// Get the number of PSG chips used by this player.
155    ///
156    /// Most players use a single chip (returns 1). Arkos Tracker songs
157    /// can use multiple PSGs for 6+ channel music.
158    fn psg_count(&self) -> usize {
159        1
160    }
161
162    /// Get the total number of audio channels.
163    ///
164    /// Each PSG chip has 3 channels (A, B, C), so this returns `psg_count() * 3`.
165    fn channel_count(&self) -> usize {
166        self.psg_count() * 3
167    }
168}
169
170/// Unified player interface for chiptune formats.
171///
172/// This trait extends [`ChiptunePlayerBase`] with metadata access.
173/// It provides a common API for playing YM, AKS, AY and other
174/// chiptune formats.
175///
176/// # Metadata
177///
178/// Each player provides metadata through [`PlaybackMetadata`]. The trait
179/// uses an associated type to allow format-specific metadata structs while
180/// still providing a common interface.
181///
182/// # Object Safety
183///
184/// This trait is **not** object-safe due to the associated `Metadata` type.
185/// Use [`ChiptunePlayerBase`] when you need trait objects.
186///
187/// # Example
188///
189/// ```ignore
190/// use ym2149_common::{ChiptunePlayer, PlaybackState};
191///
192/// fn play_song(player: &mut impl ChiptunePlayer) {
193///     println!("Playing: {}", player.metadata().title());
194///     player.play();
195///
196///     let mut buffer = vec![0.0; 1024];
197///     while player.state() == PlaybackState::Playing {
198///         player.generate_samples_into(&mut buffer);
199///         // ... send buffer to audio device
200///     }
201/// }
202/// ```
203pub trait ChiptunePlayer: ChiptunePlayerBase {
204    /// The metadata type for this player.
205    type Metadata: PlaybackMetadata;
206
207    /// Get song metadata.
208    fn metadata(&self) -> &Self::Metadata;
209}