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}