1#[derive(Clone)]
2struct NesPulseChannel {
3 duty_cycle: f32,
4 frequency: f32,
5 volume: f32,
6 phase: f32,
7 enabled: bool,
8}
9
10#[derive(Clone)]
11struct NesTriangleChannel {
12 frequency: f32,
13 volume: f32,
14 phase: f32,
15 linear_counter: u8,
16 enabled: bool,
17}
18
19struct NesNoiseChannel {
20 mode: NoiseMode,
21 frequency: f32,
22 volume: f32,
23 shift_register: u16,
24 tick_counter: f32,
25 enabled: bool,
26}
27
28#[derive(Clone)]
29struct NesDpcmChannel {
30 sample_rate: f32,
31 delta: f32,
32 sample_buffer: Vec<i8>,
33 position: usize,
34 current_output: f32,
35 tick_counter: f32,
36 enabled: bool,
37}
38
39#[derive(Debug, Clone, Copy)]
40enum NoiseMode {
41 Short,
42 Long,
43}
44
45struct NesSweepUnit {
51 enabled: bool,
52 reload: bool,
53 divider_period: u8,
54 divider_counter: u8,
55 negate: bool,
56 shift: u8,
57 target_period: u16,
58}
59
60impl NesSweepUnit {
61 fn new() -> Self {
62 Self {
63 enabled: false,
64 reload: false,
65 divider_period: 0,
66 divider_counter: 0,
67 negate: false,
68 shift: 0,
69 target_period: 0,
70 }
71 }
72
73 fn write_register(&mut self, value: u8) {
74 self.enabled = (value & 0x80) != 0;
75 self.divider_period = (value >> 4) & 0x07;
76 self.negate = (value & 0x08) != 0;
77 self.shift = value & 0x07;
78 self.reload = true;
79 }
80
81 fn set_target_period(&mut self, period: u16) {
82 self.target_period = period & 0x07FF;
83 }
84
85 fn clock(&mut self) -> Option<u16> {
88 if self.divider_period == 0 && !self.reload {
89 return Some(self.target_period);
90 }
91 if self.reload {
92 self.divider_counter = self.divider_period;
93 self.reload = false;
94 }
95 if self.divider_counter > 0 {
96 self.divider_counter -= 1;
97 }
98 if self.divider_counter == 0 && self.divider_period > 0 {
99 let delta = self.target_period >> self.shift as u32;
100 let new = if self.negate {
101 self.target_period.saturating_sub(delta + 1)
102 } else {
103 self.target_period + delta
104 };
105 if !(8..=0x07FF).contains(&new) {
106 return None;
107 }
108 self.target_period = new;
109 self.divider_counter = self.divider_period;
110 }
111 Some(self.target_period)
112 }
113}
114
115pub struct NesChip {
120 pulse1: NesPulseChannel,
121 pulse2: NesPulseChannel,
122 triangle: NesTriangleChannel,
123 noise: NesNoiseChannel,
124 dpcm: NesDpcmChannel,
125 sweep1: NesSweepUnit,
126 sweep2: NesSweepUnit,
127 sweep_phase: f32,
128}
129
130impl Default for NesChip {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl NesChip {
137 pub fn new() -> Self {
139 Self {
140 pulse1: NesPulseChannel {
141 duty_cycle: 0.25,
142 frequency: 440.0,
143 volume: 0.5,
144 phase: 0.0,
145 enabled: true,
146 },
147 pulse2: NesPulseChannel {
148 duty_cycle: 0.125,
149 frequency: 660.0,
150 volume: 0.3,
151 phase: 0.0,
152 enabled: true,
153 },
154 triangle: NesTriangleChannel {
155 frequency: 220.0,
156 volume: 0.4,
157 phase: 0.0,
158 linear_counter: 0,
159 enabled: true,
160 },
161 noise: NesNoiseChannel {
162 mode: NoiseMode::Short,
163 frequency: 1000.0,
164 volume: 0.2,
165 shift_register: 1,
166 tick_counter: 0.0,
167 enabled: true,
168 },
169 dpcm: NesDpcmChannel {
170 sample_rate: 22050.0,
171 delta: 0.01,
172 sample_buffer: Vec::new(),
173 position: 0,
174 current_output: 0.0,
175 tick_counter: 0.0,
176 enabled: false,
177 },
178 sweep1: NesSweepUnit::new(),
179 sweep2: NesSweepUnit::new(),
180 sweep_phase: 0.0,
181 }
182 }
183
184 pub fn write_registers(&mut self, regs: &[u8]) {
186 if regs.len() < 22 {
187 return;
188 }
189
190 let duty_table: [f32; 4] = [0.125, 0.25, 0.5, 0.75];
191
192 self.pulse1.duty_cycle = duty_table[((regs[0] >> 6) & 0x03) as usize];
194 self.pulse1.volume = (regs[0] & 0x0F) as f32 / 15.0;
195 let p1_period = (regs[2] as u16) | (((regs[3] as u16) & 0x07) << 8);
196 self.pulse1.frequency = if p1_period > 0 {
197 1_789_773.0 / (16.0 * (p1_period + 1) as f32)
198 } else {
199 0.0
200 };
201 self.sweep1.write_register(regs[1]);
202 self.sweep1.set_target_period(p1_period);
203
204 self.pulse2.duty_cycle = duty_table[((regs[4] >> 6) & 0x03) as usize];
206 self.pulse2.volume = (regs[4] & 0x0F) as f32 / 15.0;
207 let p2_period = (regs[6] as u16) | (((regs[7] as u16) & 0x07) << 8);
208 self.pulse2.frequency = if p2_period > 0 {
209 1_789_773.0 / (16.0 * (p2_period + 1) as f32)
210 } else {
211 0.0
212 };
213 self.sweep2.write_register(regs[5]);
214 self.sweep2.set_target_period(p2_period);
215
216 self.triangle.volume = if (regs[8] & 0x80) != 0 { 0.4 } else { 0.0 };
218 self.triangle.linear_counter = regs[8] & 0x7F;
219 let tri_period = (regs[10] as u16) | (((regs[11] as u16) & 0x07) << 8);
220 self.triangle.frequency = if tri_period > 0 {
221 1_789_773.0 / (32.0 * (tri_period + 1) as f32)
222 } else {
223 0.0
224 };
225
226 self.noise.mode = if (regs[12] & 0x80) != 0 {
228 NoiseMode::Short
229 } else {
230 NoiseMode::Long
231 };
232 self.noise.volume = (regs[12] & 0x0F) as f32 / 15.0;
233 let noise_period_idx = regs[14] & 0x0F;
234 let noise_periods: [u16; 16] = [
235 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
236 ];
237 self.noise.frequency = 1_789_773.0 / noise_periods[noise_period_idx as usize] as f32;
238
239 let enable = regs[21];
241 self.pulse1.enabled = (enable & 0x01) != 0;
242 self.pulse2.enabled = (enable & 0x02) != 0;
243 self.triangle.enabled = (enable & 0x04) != 0;
244 self.noise.enabled = (enable & 0x08) != 0;
245 self.dpcm.enabled = (enable & 0x10) != 0;
246 }
247
248 pub fn generate_sample(&mut self, sample_rate: f32) -> f32 {
250 self.sweep_phase += 120.0 / sample_rate;
252 while self.sweep_phase >= 1.0 {
253 self.sweep_phase -= 1.0;
254
255 let p1 = self.sweep1.clock();
256 if let Some(period) = p1 {
257 self.pulse1.frequency = if period > 0 {
258 1_789_773.0 / (16.0 * (period + 1) as f32)
259 } else {
260 0.0
261 };
262 } else {
263 self.pulse1.enabled = false;
264 }
265
266 let p2 = self.sweep2.clock();
267 if let Some(period) = p2 {
268 self.pulse2.frequency = if period > 0 {
269 1_789_773.0 / (16.0 * (period + 1) as f32)
270 } else {
271 0.0
272 };
273 } else {
274 self.pulse2.enabled = false;
275 }
276 }
277
278 let p1 = if self.pulse1.frequency > 0.0 && self.pulse1.enabled {
280 self.pulse1.phase += self.pulse1.frequency / sample_rate;
281 if self.pulse1.phase >= 1.0 {
282 self.pulse1.phase -= 1.0;
283 }
284 (if self.pulse1.phase < self.pulse1.duty_cycle {
285 1.0
286 } else {
287 -1.0
288 }) * self.pulse1.volume
289 } else {
290 0.0
291 };
292
293 let p2 = if self.pulse2.frequency > 0.0 && self.pulse2.enabled {
295 self.pulse2.phase += self.pulse2.frequency / sample_rate;
296 if self.pulse2.phase >= 1.0 {
297 self.pulse2.phase -= 1.0;
298 }
299 (if self.pulse2.phase < self.pulse2.duty_cycle {
300 1.0
301 } else {
302 -1.0
303 }) * self.pulse2.volume
304 } else {
305 0.0
306 };
307
308 let tri = if self.triangle.frequency > 0.0 && self.triangle.enabled {
310 self.triangle.phase += self.triangle.frequency / sample_rate;
311 if self.triangle.phase >= 1.0 {
312 self.triangle.phase -= 1.0;
313 }
314 (if self.triangle.phase < 0.5 {
315 self.triangle.phase * 4.0 - 1.0
316 } else {
317 3.0 - self.triangle.phase * 4.0
318 }) * self.triangle.volume
319 } else {
320 0.0
321 };
322
323 let ns = if self.noise.enabled {
325 self.generate_noise(sample_rate)
326 } else {
327 0.0
328 };
329
330 let dpcm = if self.dpcm.enabled {
332 self.generate_dpcm(sample_rate)
333 } else {
334 0.0
335 };
336
337 let pulse_mix = (p1 + p2) * 0.5;
338 let tnd_mix = (tri * 3.0 + ns * 2.0 + dpcm) / 6.0;
339 (pulse_mix * 0.5 + tnd_mix * 0.5) * 0.5
340 }
341
342 pub fn reset(&mut self) {
344 self.pulse1.phase = 0.0;
345 self.pulse2.phase = 0.0;
346 self.triangle.phase = 0.0;
347 self.noise.shift_register = 1;
348 self.noise.tick_counter = 0.0;
349 self.dpcm.position = 0;
350 self.dpcm.current_output = 0.0;
351 self.dpcm.tick_counter = 0.0;
352 self.sweep1 = NesSweepUnit::new();
353 self.sweep2 = NesSweepUnit::new();
354 self.sweep_phase = 0.0;
355 }
356
357 fn generate_noise(&mut self, sample_rate: f32) -> f32 {
358 let ticks_per_sample = sample_rate / self.noise.frequency;
359 self.noise.tick_counter += 1.0;
360 if self.noise.tick_counter >= ticks_per_sample {
361 self.noise.tick_counter = 0.0;
362 let feedback = match self.noise.mode {
363 NoiseMode::Short => {
364 (self.noise.shift_register & 0x0001)
365 ^ ((self.noise.shift_register >> 6) & 0x0001)
366 }
367 NoiseMode::Long => {
368 (self.noise.shift_register & 0x0001)
369 ^ ((self.noise.shift_register >> 1) & 0x0001)
370 }
371 };
372 self.noise.shift_register >>= 1;
373 self.noise.shift_register |= feedback << 14;
374 }
375 let sample = if (self.noise.shift_register & 0x0001) == 0 {
376 1.0
377 } else {
378 -1.0
379 };
380 sample * self.noise.volume
381 }
382
383 fn generate_dpcm(&mut self, sample_rate: f32) -> f32 {
384 if self.dpcm.sample_buffer.is_empty()
385 || self.dpcm.position >= self.dpcm.sample_buffer.len() * 8
386 {
387 return self.dpcm.current_output;
388 }
389 let ticks_per_sample = sample_rate / self.dpcm.sample_rate;
390 self.dpcm.tick_counter += 1.0;
391 if self.dpcm.tick_counter >= ticks_per_sample {
392 self.dpcm.tick_counter = 0.0;
393 let byte_idx = self.dpcm.position / 8;
394 let bit_idx = self.dpcm.position % 8;
395 if byte_idx < self.dpcm.sample_buffer.len() {
396 let bit = (self.dpcm.sample_buffer[byte_idx] >> bit_idx) & 1;
397 if bit != 0 {
398 self.dpcm.current_output =
399 (self.dpcm.current_output + self.dpcm.delta).min(1.0);
400 } else {
401 self.dpcm.current_output =
402 (self.dpcm.current_output - self.dpcm.delta).max(-1.0);
403 }
404 self.dpcm.position += 1;
405 }
406 }
407 self.dpcm.current_output
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_nes_chip_silent_when_all_disabled() {
417 let mut chip = NesChip::new();
418 let mut regs = [0u8; 22];
419 regs[21] = 0x00; chip.write_registers(®s);
421 let s = chip.generate_sample(44100.0);
422 assert!(s.abs() < 0.001, "all disabled should be silent, got {}", s);
423 }
424
425 #[test]
426 fn test_nes_chip_produces_audio() {
427 let mut chip = NesChip::new();
428 let mut regs = [0u8; 22];
429 regs[0] = 0x8F; regs[2] = 0x00; regs[3] = 0x01; regs[21] = 0x01; chip.write_registers(®s);
435 let mut max_abs = 0.0f32;
436 for _ in 0..1024 {
437 let s = chip.generate_sample(44100.0);
438 max_abs = max_abs.max(s.abs());
439 }
440 assert!(max_abs > 0.1, "should produce audio, max_abs={}", max_abs);
441 }
442
443 #[test]
444 fn test_nes_chip_reset() {
445 let mut chip = NesChip::new();
446 let mut regs = [0u8; 22];
447 regs[21] = 0x01; chip.write_registers(®s);
449 for _ in 0..100 {
450 chip.generate_sample(44100.0);
451 }
452 chip.reset();
453 assert_eq!(chip.pulse1.phase, 0.0);
454 assert_eq!(chip.noise.shift_register, 1);
455 }
456
457 #[test]
458 fn test_sweep_unit_decreases_period() {
459 let mut unit = NesSweepUnit::new();
460 unit.write_register(0xCF); unit.set_target_period(0x100);
462 let initial = unit.target_period;
463 let mut last = initial;
465 for _ in 0..200 {
466 if let Some(p) = unit.clock() {
467 last = p;
468 }
469 }
470 assert!(
471 last < initial,
472 "sweep should decrease period (negate), initial={}, last={}",
473 initial,
474 last
475 );
476 }
477
478 #[test]
479 fn test_sweep_mutes_on_underflow() {
480 let mut unit = NesSweepUnit::new();
481 unit.write_register(0xCF); unit.set_target_period(0x10);
483 let mut muted = false;
484 for _ in 0..200 {
485 if unit.clock().is_none() {
486 muted = true;
487 break;
488 }
489 }
490 assert!(muted, "should mute when period underflows below 8");
491 }
492
493 #[test]
494 fn test_sweep_disabled_when_divider_zero() {
495 let mut unit = NesSweepUnit::new();
496 unit.write_register(0x08); unit.set_target_period(0x100);
498 let initial = unit.target_period;
499 for _ in 0..100 {
500 unit.clock();
501 }
502 assert_eq!(
503 unit.target_period, initial,
504 "disabled sweep should not change period"
505 );
506 }
507}