UNPKG

wasmboy

Version:

Gameboy / Gameboy Color Emulator written for Web Assembly using AssemblyScript. Shell/Debugger in Preact

290 lines (266 loc) 10.4 kB
// Functions involved in R/W of sound registers // Information of bits on every register can be found at: https://gist.github.com/drhelius/3652407 // Passing channel number to make things simpler than passing around memory addresses, to avoid bugs in choosing the wrong address import { Sound } from './sound'; import { SoundAccumulator } from './accumulator'; import { Channel1 } from './channel1'; import { Channel2 } from './channel2'; import { Channel3 } from './channel3'; import { Channel4 } from './channel4'; import { eightBitLoadFromGBMemory, eightBitStoreIntoGBMemory, eightBitStoreIntoGBMemoryWithTraps } from '../memory/index'; import { checkBitOnByte, setBitOnByte, resetBitOnByte, log } from '../helpers/index'; // Function to check and handle writes to sound registers // Inlined because closure compiler inlines // NOTE: For write traps, return false = don't write to memory, // return true = allow the write to memory export function SoundRegisterWriteTraps(offset: i32, value: i32): boolean { if (offset !== Sound.memoryLocationNR52 && !Sound.NR52IsSoundEnabled) { // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Power_Control // When sound is turned off / enabled // Block all writes to any sound register EXCEPT NR52! // This is under the assumption that the check for // offset >= 0xFF10 && offset <= 0xFF26 // is done in writeTraps.ts (which it is) // NOTE: Except on DMG, length can still be written (whatever that means) return false; } switch (offset) { // Handle NRx0 on Channels case Channel1.memoryLocationNRx0: Channel1.updateNRx0(value); return true; case Channel3.memoryLocationNRx0: Channel3.updateNRx0(value); return true; // Handle NRx1 (Length Counter) on Channels case Channel1.memoryLocationNRx1: Channel1.updateNRx1(value); return true; case Channel2.memoryLocationNRx1: Channel2.updateNRx1(value); return true; case Channel3.memoryLocationNRx1: Channel3.updateNRx1(value); return true; case Channel4.memoryLocationNRx1: Channel4.updateNRx1(value); return true; // Handle NRx2 (Envelope / Volume) on Channels case Channel1.memoryLocationNRx2: Channel1.updateNRx2(value); return true; case Channel2.memoryLocationNRx2: Channel2.updateNRx2(value); return true; case Channel3.memoryLocationNRx2: // Check if channel 3's volume code was written too // This is handcy to know for accumulation of samples Channel3.volumeCodeChanged = true; Channel3.updateNRx2(value); return true; case Channel4.memoryLocationNRx2: Channel4.updateNRx2(value); return true; // Handle NRx3 (Frequency / Noise Properties) on Channels case Channel1.memoryLocationNRx3: Channel1.updateNRx3(value); return true; case Channel2.memoryLocationNRx3: Channel2.updateNRx3(value); return true; case Channel3.memoryLocationNRx3: Channel3.updateNRx3(value); return true; case Channel4.memoryLocationNRx3: Channel4.updateNRx3(value); return true; // Check our NRx4 registers to trap our trigger bits case Channel1.memoryLocationNRx4: Channel1.updateNRx4(value); return true; case Channel2.memoryLocationNRx4: Channel2.updateNRx4(value); return true; case Channel3.memoryLocationNRx4: Channel3.updateNRx4(value); return true; case Channel4.memoryLocationNRx4: Channel4.updateNRx4(value); return true; // Tell the sound accumulator if volumes changes case Sound.memoryLocationNR50: Sound.updateNR50(value); SoundAccumulator.mixerVolumeChanged = true; return true; // Tell the sound accumulator if volumes changes case Sound.memoryLocationNR51: Sound.updateNR51(value); SoundAccumulator.mixerEnabledChanged = true; return true; case Sound.memoryLocationNR52: // Reset all registers except NR52 // See if we were enabled, then update the register. let wasNR52Enabled = Sound.NR52IsSoundEnabled; // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Power_Control // When powered on, the frame sequencer is reset so that the next step will be 0, // the square duty units are reset to the first step of the waveform, // and the wave channel's sample buffer is reset to 0. if (!wasNR52Enabled && checkBitOnByte(7, value)) { Sound.frameSequencer = 0x07; Channel1.waveFormPositionOnDuty = 0x00; Channel2.waveFormPositionOnDuty = 0x00; // TODO: Wave Channel Sample Buffer? // I don't think we clear wave RAM here... } // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Power_Control // When powered off, all registers (NR10-NR51) are instantly written with zero // and any writes to those registers are ignored while power remains off if (wasNR52Enabled && !checkBitOnByte(7, value)) { for (let i = 0xff10; i < 0xff26; ++i) { eightBitStoreIntoGBMemoryWithTraps(i, 0x00); } } // Need to update our new value here, that way writes go through :p Sound.updateNR52(value); return true; } // We did not handle the write, Allow the write return true; } // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Registers // Inlined because closure compiler inlines export function SoundRegisterReadTraps(offset: i32): i32 { // Registers must be OR'd with values when being read // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Registers switch (offset) { // Handle NRx0 on Channels case Channel1.memoryLocationNRx0: { let register = eightBitLoadFromGBMemory(Channel1.memoryLocationNRx0); return register | 0x80; } case Channel2.memoryLocationNRx0: { let register = eightBitLoadFromGBMemory(Channel2.memoryLocationNRx0); return register | 0xff; } case Channel3.memoryLocationNRx0: { let register = eightBitLoadFromGBMemory(Channel3.memoryLocationNRx0); return register | 0x7f; } case Channel4.memoryLocationNRx0: { let register = eightBitLoadFromGBMemory(Channel4.memoryLocationNRx0); return register | 0xff; } case Sound.memoryLocationNR50: { let register = eightBitLoadFromGBMemory(Sound.memoryLocationNR50); return register | 0x00; } // Handle NRx1 on Channels case Channel1.memoryLocationNRx1: { let register = eightBitLoadFromGBMemory(Channel1.memoryLocationNRx1); return register | 0x3f; } case Channel2.memoryLocationNRx1: { let register = eightBitLoadFromGBMemory(Channel2.memoryLocationNRx1); return register | 0x3f; } case Channel3.memoryLocationNRx1: { let register = eightBitLoadFromGBMemory(Channel3.memoryLocationNRx1); return register | 0xff; } case Channel4.memoryLocationNRx1: { let register = eightBitLoadFromGBMemory(Channel4.memoryLocationNRx1); return register | 0xff; } case Sound.memoryLocationNR51: { let register = eightBitLoadFromGBMemory(Sound.memoryLocationNR51); return register | 0x00; } // Handle NRx2 on Channels case Channel1.memoryLocationNRx2: { let register = eightBitLoadFromGBMemory(Channel1.memoryLocationNRx2); return register | 0x00; } case Channel2.memoryLocationNRx2: { let register = eightBitLoadFromGBMemory(Channel2.memoryLocationNRx2); return register | 0x00; } case Channel3.memoryLocationNRx2: { let register = eightBitLoadFromGBMemory(Channel3.memoryLocationNRx2); return register | 0x9f; } case Channel4.memoryLocationNRx2: { let register = eightBitLoadFromGBMemory(Channel4.memoryLocationNRx2); return register | 0x00; } case Sound.memoryLocationNR52: { // This will fix bugs in orcale of ages :) // Start our registerNR52 let registerNR52 = 0x00; // Set the first bit to the sound paower status if (Sound.NR52IsSoundEnabled) { registerNR52 = setBitOnByte(7, registerNR52); } else { registerNR52 = resetBitOnByte(7, registerNR52); } // Set our lower 4 bits to our channel length statuses if (Channel1.isEnabled) { registerNR52 = setBitOnByte(0, registerNR52); } else { registerNR52 = resetBitOnByte(0, registerNR52); } if (Channel2.isEnabled) { registerNR52 = setBitOnByte(1, registerNR52); } else { registerNR52 = resetBitOnByte(1, registerNR52); } if (Channel3.isEnabled) { registerNR52 = setBitOnByte(2, registerNR52); } else { registerNR52 = resetBitOnByte(2, registerNR52); } if (Channel4.isEnabled) { registerNR52 = setBitOnByte(3, registerNR52); } else { registerNR52 = resetBitOnByte(3, registerNR52); } // Or from the table registerNR52 |= 0x70; return registerNR52; } // Handle NRx3 on Channels case Channel1.memoryLocationNRx3: { let register = eightBitLoadFromGBMemory(Channel1.memoryLocationNRx3); return register | 0xff; } case Channel2.memoryLocationNRx3: { let register = eightBitLoadFromGBMemory(Channel2.memoryLocationNRx3); return register | 0xff; } case Channel3.memoryLocationNRx3: { let register = eightBitLoadFromGBMemory(Channel3.memoryLocationNRx3); return register | 0xff; } case Channel4.memoryLocationNRx3: { let register = eightBitLoadFromGBMemory(Channel4.memoryLocationNRx3); return register | 0x00; } // Handle NRx4 on Channels case Channel1.memoryLocationNRx4: { let register = eightBitLoadFromGBMemory(Channel1.memoryLocationNRx4); return register | 0xbf; } case Channel2.memoryLocationNRx4: { let register = eightBitLoadFromGBMemory(Channel2.memoryLocationNRx4); return register | 0xbf; } case Channel3.memoryLocationNRx4: { let register = eightBitLoadFromGBMemory(Channel3.memoryLocationNRx4); return register | 0xbf; } case Channel4.memoryLocationNRx4: { let register = eightBitLoadFromGBMemory(Channel4.memoryLocationNRx4); return register | 0xbf; } } return -1; }