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}