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 /// Get the number of subsongs in this file.
107 ///
108 /// Default returns 1. Override for formats with multiple subsongs.
109 fn subsong_count(&self) -> usize {
110 1
111 }
112
113 /// Get the current subsong index (1-based).
114 ///
115 /// Default returns 1. Override for formats with multiple subsongs.
116 fn current_subsong(&self) -> usize {
117 1
118 }
119
120 /// Switch to a different subsong by 1-based index.
121 ///
122 /// Returns `true` if successful. Default returns `false`.
123 fn set_subsong(&mut self, _index: usize) -> bool {
124 false
125 }
126
127 /// Check if this player supports multiple subsongs.
128 fn has_subsongs(&self) -> bool {
129 self.subsong_count() > 1
130 }
131
132 /// Get the number of PSG chips used by this player.
133 ///
134 /// Most players use a single chip (returns 1). Arkos Tracker songs
135 /// can use multiple PSGs for 6+ channel music.
136 fn psg_count(&self) -> usize {
137 1
138 }
139
140 /// Get the total number of audio channels.
141 ///
142 /// Each PSG chip has 3 channels (A, B, C), so this returns `psg_count() * 3`.
143 fn channel_count(&self) -> usize {
144 self.psg_count() * 3
145 }
146}
147
148/// Unified player interface for chiptune formats.
149///
150/// This trait extends [`ChiptunePlayerBase`] with metadata access.
151/// It provides a common API for playing YM, AKS, AY and other
152/// chiptune formats.
153///
154/// # Metadata
155///
156/// Each player provides metadata through [`PlaybackMetadata`]. The trait
157/// uses an associated type to allow format-specific metadata structs while
158/// still providing a common interface.
159///
160/// # Object Safety
161///
162/// This trait is **not** object-safe due to the associated `Metadata` type.
163/// Use [`ChiptunePlayerBase`] when you need trait objects.
164///
165/// # Example
166///
167/// ```ignore
168/// use ym2149_common::{ChiptunePlayer, PlaybackState};
169///
170/// fn play_song(player: &mut impl ChiptunePlayer) {
171/// println!("Playing: {}", player.metadata().title());
172/// player.play();
173///
174/// let mut buffer = vec![0.0; 1024];
175/// while player.state() == PlaybackState::Playing {
176/// player.generate_samples_into(&mut buffer);
177/// // ... send buffer to audio device
178/// }
179/// }
180/// ```
181pub trait ChiptunePlayer: ChiptunePlayerBase {
182 /// The metadata type for this player.
183 type Metadata: PlaybackMetadata;
184
185 /// Get song metadata.
186 fn metadata(&self) -> &Self::Metadata;
187}