Code for the Namco 129 and Namco 163 boards (iNES mapper 19).

3use crate::bus::cartridge::mappers::{BankSizeKb, ChrType, PpuMapResult};
4use crate::bus::cartridge::{HasBasicPpuMapping, MapperImpl};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
7use std::array;

15-bit counter

10const MAX_IRQ_COUNTER: u16 = 0x7FFF;
12const AUDIO_DIVIDER: u8 = 15;

Mix instead of multiplex when 6+ channels are playing to avoid high-pitched ringing noise

15const CHANNEL_MULTIPLEX_THRESHOLD: u8 = 6;
17#[derive(Debug, Clone, Encode, Decode)]
18struct IrqCounter {
19    enabled: bool,
20    counter: u16,
21}
22
23impl IrqCounter {
24    fn new() -> Self {
25        Self { enabled: false, counter: 0 }
26    }
27
28    fn get_counter_low_bits(&self) -> u8 {
29        self.counter as u8
30    }
31
32    fn get_counter_high_bits(&self) -> u8 {
33        (u8::from(self.enabled) << 7) | ((self.counter >> 8) as u8)
34    }
35
36    fn update_counter_low_bits(&mut self, value: u8) {
37        self.counter = (self.counter & 0xFF00) | u16::from(value);
38    }
39
40    fn update_counter_high_bits(&mut self, value: u8) {
41        self.enabled = value.bit(7);
42        self.counter = (self.counter & 0x00FF) | (u16::from(value & 0x7F) << 8);
43    }
44
45    fn tick_cpu(&mut self) {
46        if self.enabled && self.counter < MAX_IRQ_COUNTER {
47            self.counter += 1;
48        }
49    }
50
51    fn interrupt_flag(&self) -> bool {
52        self.enabled && self.counter == MAX_IRQ_COUNTER
53    }
54}
55
56#[derive(Debug, Clone, Encode, Decode)]
57struct Namco163AudioChannel {
58    channel_idx: u8,
59    current_output: f64,
60}
61
62impl Namco163AudioChannel {
63    fn new(channel_idx: u8) -> Self {
64        Self { channel_idx, current_output: 0.0 }
65    }
66
67    fn clock(&mut self, internal_ram: &mut [u8; 128]) {
68        // Channel config/state stored in $40-$7F, 8 bytes per channel
69        let config_addr = 0x40 | (8 * self.channel_idx as usize);
70
71        let frequency = u32::from_le_bytes([
72            internal_ram[config_addr],
73            internal_ram[config_addr + 2],
74            internal_ram[config_addr + 4] & 0x03,
75            0,
76        ]);
77
78        let mut phase = u32::from_le_bytes([
79            internal_ram[config_addr + 1],
80            internal_ram[config_addr + 3],
81            internal_ram[config_addr + 5],
82            0,
83        ]);
84
85        let length = 256 - u32::from(internal_ram[config_addr + 4] & 0xFC);
86        let base_address: u32 = internal_ram[config_addr + 6].into();
87        let volume: i16 = (internal_ram[config_addr + 7] & 0x0F).into();
88
89        phase = (phase + frequency) & ((1 << 24) - 1);
90        while phase >= (length << 16) {
91            phase -= length << 16;
92        }
93
94        let relative_sample_idx = phase >> 16;
95        let sample_idx = (base_address + relative_sample_idx) & 0xFF;
96        let sample_byte = internal_ram[(sample_idx >> 1) as usize];
97
98        // Samples are 4-bit nibbles in little-endian: 0=low nibble, 1=high nibble
99        let sample = (sample_byte >> (4 * (sample_idx & 1))) & 0xF;
100
101        // Volume should act as if the waveform is centered at sample value 8
102        // This will produce a value in the range [-120, 105]
103        let sample = (i16::from(sample) - 8) * volume;
104
105        self.current_output = f64::from(sample) / 120.0;
106
107        // Write updated phase back to wavetable RAM
108        let [phase_low, phase_mid, phase_high, _] = phase.to_le_bytes();
109        internal_ram[config_addr + 1] = phase_low;
110        internal_ram[config_addr + 3] = phase_mid;
111        internal_ram[config_addr + 5] = phase_high;
112    }
113}
114
115#[derive(Debug, Clone, Encode, Decode)]
116struct Namco163AudioUnit {
117    enabled: bool,
118    channels: [Namco163AudioChannel; 8],
119    divider: u8,
120    current_channel: u8,
121    enabled_channel_count: u8,
122}
123
124impl Namco163AudioUnit {
125    fn new() -> Self {
126        Self {
127            enabled: false,
128            channels: array::from_fn(|i| Namco163AudioChannel::new(i as u8)),
129            divider: AUDIO_DIVIDER,
130            current_channel: 0,
131            enabled_channel_count: 0,
132        }
133    }
134
135    fn process_internal_ram_update(&mut self, address: u8, value: u8) {
136        if address == 0x7F {
137            // Bits 6-4 of $7F control which channels are enabled in addition to channel 8 volume
138            self.enabled_channel_count = ((value & 0x70) >> 4) + 1;
139
140            log::trace!("# channels enabled: {}", self.enabled_channel_count);
141        }
142    }
143
144    fn clock(&mut self, internal_ram: &mut [u8; 128]) {
145        if !self.enabled {
146            return;
147        }
148
149        self.current_channel = self.current_channel.wrapping_sub(1) & 0x07;
150        if self.current_channel < 8 - self.enabled_channel_count {
151            self.current_channel = 7;
152        }
153        self.channels[self.current_channel as usize].clock(internal_ram);
154    }
155
156    fn tick_cpu(&mut self, internal_ram: &mut [u8; 128]) {
157        self.divider -= 1;
158        if self.divider == 0 {
159            self.clock(internal_ram);
160            self.divider = AUDIO_DIVIDER;
161        }
162    }
163
164    fn sample(&self) -> f64 {
165        if self.enabled_channel_count < CHANNEL_MULTIPLEX_THRESHOLD {
166            self.channels[self.current_channel as usize].current_output
167        } else {
168            // Special case 6-8 enabled channels because an accurate implementation sounds horrible
169            // without a low-pass filter with a low cutoff frequency
170            let channel_sum = self
171                .channels
172                .iter()
173                .rev()
174                .take(self.enabled_channel_count as usize)
175                .map(|channel| channel.current_output)
176                .sum::<f64>();
177            channel_sum / f64::from(self.enabled_channel_count)
178        }
179    }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
183enum VolumeVariantDb {
184    Twelve,
185    Sixteen,
186    Eighteen,
187}
188
189impl VolumeVariantDb {
190    const fn n163_coefficient(self) -> f64 {
191        match self {
192            Self::Twelve => {
193                // APU pulse volume * 10^(12/20) / (1 + 105/120)
194                0.31716257177124485
195            }
196            Self::Sixteen => {
197                // APU pulse volume * 10^(16.5/20) / (1 + 105/120)
198                0.5324537998876507
199            }
200            Self::Eighteen => {
201                // APU pulse volume * 10^(18.75/20) / (1 + 105/120)
202                0.6898933055568182
203            }
204        }
205    }
206}
207
208#[derive(Debug, Clone, Encode, Decode)]
209pub(crate) struct Namco163 {
210    chr_type: ChrType,
211    internal_ram: [u8; 128],
212    internal_ram_addr: u8,
213    internal_ram_auto_increment: bool,
214    internal_ram_dirty_bit: bool,
215    prg_banks: [u8; 3],
216    pattern_table_chr_banks: [u8; 8],
217    nametable_chr_banks: [u8; 4],
218    vram_chr_banks_enabled: [bool; 2],
219    ram_writes_enabled: bool,
220    ram_window_writes_enabled: [bool; 4],
221    irq: IrqCounter,
222    audio: Namco163AudioUnit,
223    volume_variant: VolumeVariantDb,
224}
225
226impl Namco163 {
227    pub(crate) fn new(
228        sub_mapper_number: u8,
229        chr_type: ChrType,
230        has_battery: bool,
231        prg_ram_len: u32,
232        sav_bytes: Option<Vec<u8>>,
233    ) -> Self {
234        let volume_variant = match sub_mapper_number {
235            4 => VolumeVariantDb::Sixteen,
236            5 => VolumeVariantDb::Eighteen,
237            _ => VolumeVariantDb::Twelve,
238        };
239
240        let mut internal_ram = [0; 128];
241
242        if has_battery
243            && prg_ram_len == 0
244            && let Some(sav_bytes) = sav_bytes
245            && sav_bytes.len() == internal_ram.len()
246        {
247            internal_ram.copy_from_slice(&sav_bytes);
248        }
249
250        log::info!("Namco 163 volume variant: {volume_variant:?}");
251
252        Self {
253            chr_type,
254            internal_ram,
255            internal_ram_addr: 0,
256            internal_ram_auto_increment: false,
257            internal_ram_dirty_bit: true,
258            prg_banks: [0; 3],
259            pattern_table_chr_banks: [0; 8],
260            nametable_chr_banks: [0; 4],
261            vram_chr_banks_enabled: [false; 2],
262            ram_writes_enabled: false,
263            ram_window_writes_enabled: [false; 4],
264            irq: IrqCounter::new(),
265            audio: Namco163AudioUnit::new(),
266            volume_variant,
267        }
268    }
269}
270
271impl MapperImpl<Namco163> {
272    pub(crate) fn read_cpu_address(&mut self, address: u16, cpu_open_bus: u8) -> u8 {
273        match address {
274            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
275            0x4020..=0x47FF => cpu_open_bus,
276            0x4800..=0x4FFF => {
277                let byte = self.data.internal_ram[self.data.internal_ram_addr as usize];
278                if self.data.internal_ram_auto_increment {
279                    self.data.internal_ram_addr = (self.data.internal_ram_addr + 1) & 0x7F;
280                }
281                byte
282            }
283            0x5000..=0x57FF => self.data.irq.get_counter_low_bits(),
284            0x5800..=0x5FFF => self.data.irq.get_counter_high_bits(),
285            0x6000..=0x7FFF => {
286                if !self.cartridge.prg_ram.is_empty() {
287                    self.cartridge.get_prg_ram((address & 0x1FFF).into())
288                } else {
289                    cpu_open_bus
290                }
291            }
292            0x8000..=0xDFFF => {
293                // $8000-$9FFF to bank index 0
294                // $A000-$BFFF to bank index 1
295                // $C000-$DFFF to bank index 2
296                let bank_index = (address & 0x7FFF) / 0x2000;
297                let bank_number = self.data.prg_banks[bank_index as usize];
298                let prg_rom_addr = BankSizeKb::Eight.to_absolute_address(bank_number, address);
299                self.cartridge.get_prg_rom(prg_rom_addr)
300            }
301            0xE000..=0xFFFF => {
302                let prg_rom_addr = BankSizeKb::Eight
303                    .to_absolute_address_last_bank(self.cartridge.prg_rom.len() as u32, address);
304                self.cartridge.get_prg_rom(prg_rom_addr)
305            }
306        }
307    }
308
309    pub(crate) fn write_cpu_address(&mut self, address: u16, value: u8) {
310        match address {
311            0x0000..=0x401F => panic!("invalid CPU map address: {address:04X}"),
312            0x4020..=0x47FF => {}
313            0x4800..=0x4FFF => {
314                let ram_addr = self.data.internal_ram_addr;
315                self.data.internal_ram[ram_addr as usize] = value;
316                self.data.internal_ram_dirty_bit = true;
317
318                self.data.audio.process_internal_ram_update(ram_addr, value);
319
320                if self.data.internal_ram_auto_increment {
321                    self.data.internal_ram_addr = (ram_addr + 1) & 0x7F;
322                }
323            }
324            0x5000..=0x57FF => {
325                self.data.irq.update_counter_low_bits(value);
326            }
327            0x5800..=0x5FFF => {
328                self.data.irq.update_counter_high_bits(value);
329            }
330            0x6000..=0x7FFF => {
331                if !self.cartridge.prg_ram.is_empty() && self.data.ram_writes_enabled {
332                    let prg_ram_addr = address & 0x1FFF;
333                    let window_index = prg_ram_addr / 0x0800;
334                    if self.data.ram_window_writes_enabled[window_index as usize] {
335                        self.cartridge.set_prg_ram(prg_ram_addr.into(), value);
336                    }
337                }
338            }
339            0x8000..=0xBFFF => {
340                let bank_index = (address & 0x7FFF) / 0x0800;
341                self.data.pattern_table_chr_banks[bank_index as usize] = value;
342            }
343            0xC000..=0xDFFF => {
344                let bank_index = (address & 0x3FFF) / 0x0800;
345                self.data.nametable_chr_banks[bank_index as usize] = value;
346            }
347            0xE000..=0xE7FF => {
348                self.data.audio.enabled = !value.bit(6);
349                self.data.prg_banks[0] = value & 0x3F;
350            }
351            0xE800..=0xEFFF => {
352                self.data.vram_chr_banks_enabled[1] = !value.bit(7);
353                self.data.vram_chr_banks_enabled[0] = !value.bit(6);
354                self.data.prg_banks[1] = value & 0x3F;
355            }
356            0xF000..=0xF7FF => {
357                self.data.prg_banks[2] = value & 0x3F;
358            }
359            0xF800..=0xFFFF => {
360                // This register doubles as both PRG RAM write protection and the internal RAM address
361                self.data.ram_writes_enabled = value & 0xF0 == 0x40;
362                for bit in 0..=3 {
363                    self.data.ram_window_writes_enabled[bit as usize] = !value.bit(bit);
364                }
365
366                self.data.internal_ram_auto_increment = value.bit(7);
367                self.data.internal_ram_addr = value & 0x7F;
368            }
369        }
370    }
371
372    pub(crate) fn tick_cpu(&mut self) {
373        self.data.irq.tick_cpu();
374        self.data.audio.tick_cpu(&mut self.data.internal_ram);
375    }
376
377    pub(crate) fn interrupt_flag(&self) -> bool {
378        self.data.irq.interrupt_flag()
379    }
380
381    pub(crate) fn has_battery_backed_internal_ram(&self) -> bool {
382        self.cartridge.has_ram_battery && self.cartridge.prg_ram.is_empty()
383    }
384
385    pub(crate) fn get_and_clear_internal_ram_dirty_bit(&mut self) -> bool {
386        let dirty_bit = self.data.internal_ram_dirty_bit;
387        self.data.internal_ram_dirty_bit = false;
388        dirty_bit
389    }
390
391    pub(crate) fn get_internal_ram(&self) -> &[u8; 128] {
392        &self.data.internal_ram
393    }
394
395    pub(crate) fn sample_audio(&self, mixed_apu_sample: f64) -> f64 {
396        if !self.data.audio.enabled {
397            return mixed_apu_sample;
398        }
399
400        let n163_sample = self.data.audio.sample() * self.data.volume_variant.n163_coefficient();
401        (mixed_apu_sample + n163_sample).clamp(-1.0, 1.0)
402    }
403}
404
405impl HasBasicPpuMapping for MapperImpl<Namco163> {
406    fn map_ppu_address(&self, address: u16) -> PpuMapResult {
407        match address {
408            0x0000..=0x1FFF => {
409                let bank_index = address / 0x0400;
410                let bank_number = self.data.pattern_table_chr_banks[bank_index as usize];
411                let pattern_table_index = address / 0x1000;
412                if bank_number >= 0xE0
413                    && self.data.vram_chr_banks_enabled[pattern_table_index as usize]
414                {
415                    let vram_bank = u16::from(bank_number & 0x01);
416                    PpuMapResult::Vram((vram_bank * 0x0400) | (address & 0x03FF))
417                } else {
418                    let chr_addr = BankSizeKb::One.to_absolute_address(bank_number, address);
419                    self.data.chr_type.to_map_result(chr_addr)
420                }
421            }
422            0x2000..=0x3EFF => {
423                let relative_addr = address & 0x0FFF;
424                let bank_index = relative_addr / 0x0400;
425                let bank_number = self.data.nametable_chr_banks[bank_index as usize];
426                if bank_number >= 0xE0 {
427                    let vram_bank = u16::from(bank_number & 0x01);
428                    PpuMapResult::Vram((vram_bank * 0x0400) | (address & 0x03FF))
429                } else {
430                    let chr_addr = BankSizeKb::One.to_absolute_address(bank_number, address);
431                    self.data.chr_type.to_map_result(chr_addr)
432                }
433            }
434            0x3F00..=0xFFFF => {
435                panic!("invalid PPU map address: {address:04X}")
436            }
437        }
438    }
439}