internal void HandleMemoryChange(MMR register, byte value, bool updatedEnabledFlag = true) { if (!_state.Enabled) { // When powered off, the internal length can be changed (DMG only) switch (register) { case MMR.NR11: _channel1.ChangeLength(value); break; case MMR.NR21: _channel2.ChangeLength(value); break; case MMR.NR31: _channel3.ChangeLength(value); break; case MMR.NR41: _channel4.ChangeLength(value); break; } // Other than turning on, all other writes are ignored if (register != MMR.NR52) { return; } } // We store previous channel status bool channel1Enabled = _channel1.Enabled; bool channel2Enabled = _channel2.Enabled; bool channel3Enabled = _channel3.Enabled; bool channel4Enabled = _channel4.Enabled; bool prevEnabled = _state.Enabled; switch (register) { case MMR.NR10: case MMR.NR11: case MMR.NR12: case MMR.NR13: case MMR.NR14: _channel1.HandleMemoryChange(register, value); break; case MMR.NR21: case MMR.NR22: case MMR.NR23: case MMR.NR24: _channel2.HandleMemoryChange(register, value); break; case MMR.NR30: case MMR.NR31: case MMR.NR32: case MMR.NR33: case MMR.NR34: _channel3.HandleMemoryChange(register, value); break; case MMR.NR41: case MMR.NR42: case MMR.NR43: case MMR.NR44: _channel4.HandleMemoryChange(register, value); break; case MMR.NR50: // NOTE(Cristian): No Vin support _memory.LowLevelWrite((ushort)register, value); break; case MMR.NR51: // TODO(Cristian): Implement this logic _state.OutputChannel1Left = ((value & 0x01) != 0); _state.OutputChannel2Left = ((value & 0x02) != 0); _state.OutputChannel3Left = ((value & 0x04) != 0); _state.OutputChannel4Left = ((value & 0x08) != 0); _state.OutputChannel1Right = ((value & 0x10) != 0); _state.OutputChannel2Right = ((value & 0x20) != 0); _state.OutputChannel3Right = ((value & 0x40) != 0); _state.OutputChannel4Right = ((value & 0x80) != 0); _memory.LowLevelWrite((ushort)register, value); break; case MMR.NR52: bool apuEnabled = (Utils.UtilFuncs.TestBit(value, 7) != 0); if (!apuEnabled) { // Powering down the APU should power down all the registers //for (ushort r = (ushort)MMR.NR10; r < (ushort)MMR.NR52; ++r) //{ // HandleMemoryChange((MMR)r, 0, false); //} _channel1.PowerOff(); _channel1.SetEnabled(false); _channel2.PowerOff(); _channel2.SetEnabled(false); _channel3.PowerOff(); _channel3.SetEnabled(false); _channel4.PowerOff(); _channel4.SetEnabled(false); _memory.LowLevelWrite((ushort)MMR.NR50, 0); _memory.LowLevelWrite((ushort)MMR.NR51, 0); } else if (!_state.Enabled) { _frameSequencer.Reset(); } // We update at the end because otherwise the recursive calls would // be rejected by the guard _state.Enabled = apuEnabled; break; } // NOTE(Cristian): This is an "optimization" for when NR52 is disabled, // All the registers are set to 0. A normal recursive call // would write the NR52 memory several times unnecessarily if (!updatedEnabledFlag) { return; } // We compare to see if we have to change the NR52 byte if ((channel1Enabled != _channel1.Enabled) || (channel2Enabled != _channel2.Enabled) || (channel3Enabled != _channel3.Enabled) || (channel4Enabled != _channel4.Enabled) || (prevEnabled != _state.Enabled)) { byte nr52 = 0x70; if (_state.Enabled) { nr52 = (byte)((_channel1.Enabled ? 0x1 : 0) | // bit 0 (_channel2.Enabled ? 0x2 : 0) | // bit 1 (_channel3.Enabled ? 0x4 : 0) | // bit 2 (_channel4.Enabled ? 0x8 : 0) | // bit 3 0xF0); // bit 4-7 are 1 } // We know bit 7 is 1 because otherwise the whole register is 0x70 _memory.LowLevelWrite((ushort)MMR.NR52, nr52); } }