ym2149_common/
backend.rs

1//! Backend trait abstraction for YM2149 chip implementations
2//!
3//! This module defines the core interface that all YM2149 backends must implement,
4//! whether they are cycle-accurate hardware emulations or experimental synthesizers.
5
6/// Common interface for YM2149 chip backends
7///
8/// This trait allows different implementations to be used interchangeably:
9/// - Hardware-accurate emulation (cycle-exact, bit-perfect)
10/// - Experimental software synthesizers (musical, non-accurate)
11/// - Future implementations (FPGA cores, etc.)
12///
13/// # Example
14///
15/// ```ignore
16/// use ym2149_common::Ym2149Backend;
17///
18/// fn play_note<B: Ym2149Backend>(chip: &mut B) {
19///     chip.write_register(0x00, 0xF0); // Channel A period low
20///     chip.write_register(0x01, 0x01); // Channel A period high
21///     chip.write_register(0x08, 0x0F); // Channel A volume
22///     chip.write_register(0x07, 0x3E); // Mixer: enable tone A
23///
24///     chip.clock();
25///     let sample = chip.get_sample();
26/// }
27/// ```
28pub trait Ym2149Backend: Send {
29    /// Create a new backend instance with default clocks
30    ///
31    /// Default clocks:
32    /// - Master clock: 2,000,000 Hz (Atari ST frequency)
33    /// - Sample rate: 44,100 Hz
34    fn new() -> Self
35    where
36        Self: Sized;
37
38    /// Create a backend with custom master clock and sample rate
39    ///
40    /// # Arguments
41    ///
42    /// * `master_clock` - YM2149 master clock frequency in Hz
43    /// * `sample_rate` - Audio output sample rate in Hz
44    fn with_clocks(master_clock: u32, sample_rate: u32) -> Self
45    where
46        Self: Sized;
47
48    /// Reset the backend to initial state
49    ///
50    /// Clears all registers, resets generators, and stops all audio output.
51    fn reset(&mut self);
52
53    /// Write to a YM2149 register
54    ///
55    /// # Arguments
56    ///
57    /// * `addr` - Register address (0x00-0x0F)
58    /// * `value` - Register value (0x00-0xFF)
59    ///
60    /// Registers outside the valid range are ignored.
61    fn write_register(&mut self, addr: u8, value: u8);
62
63    /// Read from a YM2149 register
64    ///
65    /// # Arguments
66    ///
67    /// * `addr` - Register address (0x00-0x0F)
68    ///
69    /// # Returns
70    ///
71    /// Current register value, or 0x00 for invalid addresses
72    fn read_register(&self, addr: u8) -> u8;
73
74    /// Load all 16 YM2149 registers at once
75    ///
76    /// More efficient than 16 individual `write_register` calls.
77    ///
78    /// # Arguments
79    ///
80    /// * `regs` - Array of 16 register values (R0-R15)
81    fn load_registers(&mut self, regs: &[u8; 16]);
82
83    /// Dump all 16 YM2149 registers
84    ///
85    /// # Returns
86    ///
87    /// Current state of all registers (R0-R15)
88    fn dump_registers(&self) -> [u8; 16];
89
90    /// Advance the chip by one clock cycle
91    ///
92    /// Updates all internal generators (tone, noise, envelope) and produces
93    /// a new audio sample. Call this at the backend's sample rate.
94    fn clock(&mut self);
95
96    /// Get the last generated audio sample
97    ///
98    /// # Returns
99    ///
100    /// Normalized audio sample in range [-1.0, 1.0]
101    fn get_sample(&self) -> f32;
102
103    /// Generate multiple audio samples
104    ///
105    /// # Arguments
106    ///
107    /// * `count` - Number of samples to generate
108    ///
109    /// # Returns
110    ///
111    /// Vector of normalized audio samples in range [-1.0, 1.0]
112    fn generate_samples(&mut self, count: usize) -> Vec<f32> {
113        let mut samples = vec![0.0; count];
114        self.generate_samples_into(&mut samples);
115        samples
116    }
117
118    /// Generate multiple audio samples into a caller-provided buffer
119    ///
120    /// This avoids per-call allocations; prefer this in hot paths.
121    ///
122    /// # Arguments
123    ///
124    /// * `buffer` - Output slice to fill with normalized audio samples in range [-1.0, 1.0]
125    fn generate_samples_into(&mut self, buffer: &mut [f32]) {
126        for sample in buffer.iter_mut() {
127            self.clock();
128            *sample = self.get_sample();
129        }
130    }
131
132    /// Generate samples with synchronized per-sample channel outputs
133    ///
134    /// This method generates mono samples and captures per-channel outputs at the same time,
135    /// ensuring that the channel data is perfectly synchronized with the audio.
136    /// This is essential for accurate visualization (oscilloscope, spectrum analyzer).
137    ///
138    /// # Arguments
139    ///
140    /// * `buffer` - Output slice for mono samples in range [-1.0, 1.0]
141    /// * `channel_outputs` - Output slice for per-sample channel outputs [A, B, C]
142    ///
143    /// # Panics
144    ///
145    /// Panics if buffer and channel_outputs have different lengths.
146    fn generate_samples_with_channels(
147        &mut self,
148        buffer: &mut [f32],
149        channel_outputs: &mut [[f32; 3]],
150    ) {
151        debug_assert_eq!(buffer.len(), channel_outputs.len());
152        for (sample, channels) in buffer.iter_mut().zip(channel_outputs.iter_mut()) {
153            self.clock();
154            *sample = self.get_sample();
155            let (a, b, c) = self.get_channel_outputs();
156            *channels = [a, b, c];
157        }
158    }
159
160    /// Get individual channel outputs
161    ///
162    /// # Returns
163    ///
164    /// Tuple of (channel_a, channel_b, channel_c) samples in range [-1.0, 1.0]
165    fn get_channel_outputs(&self) -> (f32, f32, f32);
166
167    /// Mute or unmute a channel
168    ///
169    /// # Arguments
170    ///
171    /// * `channel` - Channel index (0=A, 1=B, 2=C)
172    /// * `mute` - true to mute, false to unmute
173    fn set_channel_mute(&mut self, channel: usize, mute: bool);
174
175    /// Check if a channel is muted
176    ///
177    /// # Arguments
178    ///
179    /// * `channel` - Channel index (0=A, 1=B, 2=C)
180    ///
181    /// # Returns
182    ///
183    /// true if channel is muted, false otherwise
184    fn is_channel_muted(&self, channel: usize) -> bool;
185
186    /// Enable or disable post-processing color filter
187    ///
188    /// # Arguments
189    ///
190    /// * `enabled` - true to enable filter, false to disable
191    fn set_color_filter(&mut self, enabled: bool);
192
193    /// Trigger envelope restart (used by YM6 Sync Buzzer effect)
194    ///
195    /// This is a hardware-specific feature. Default implementation is a no-op.
196    /// Only Ym2149 provides full implementation.
197    fn trigger_envelope(&mut self) {
198        // Default: no-op for backends that don't support this
199    }
200
201    /// Override drum sample for a channel (used by YM6 DigiDrum effect)
202    ///
203    /// This is a hardware-specific feature. Default implementation is a no-op.
204    /// Only Ym2149 provides full implementation.
205    ///
206    /// # Arguments
207    ///
208    /// * `channel` - Channel index (0=A, 1=B, 2=C)
209    /// * `sample` - Optional sample value to inject, None to disable override
210    fn set_drum_sample_override(&mut self, _channel: usize, _sample: Option<f32>) {
211        // Default: no-op for backends that don't support this
212    }
213
214    /// Set mixer tone/noise overrides (used by YM6 DigiDrum effect)
215    ///
216    /// This is a hardware-specific feature. Default implementation is a no-op.
217    /// Only Ym2149 provides full implementation.
218    ///
219    /// # Arguments
220    ///
221    /// * `force_tone` - Per-channel flags to force tone enable
222    /// * `force_noise_mute` - Per-channel flags to force noise mute
223    fn set_mixer_overrides(&mut self, _force_tone: [bool; 3], _force_noise_mute: [bool; 3]) {
224        // Default: no-op for backends that don't support this
225    }
226}