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}