Code for the Namco 129 and Namco 163 boards (iNES mapper 19).
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}