Update to v096r05 release.

byuu says:

Changelog:
- GB: re-enabling the LCD resets the display to LY=0,LX=0 [1]
- GB: emulated new findings (as of today!) for a DMG quirk that triggers
  an extra OAM STAT IRQ when Vblank STAT IRQs are off
- GB: made VBK, BGPI, OBPI readable
- GB: fixed APU length operations
- GB: fixed APU sweep operations
- NES: fixed cartridge/ -> board/ manifest lookups for mirroring/pinous
- hiro/Cocoa: added endrift's plist keys

Fixed:
- Astro Rabby is fully playable, even the title screen works correctly
- Bomb Jack is fully playable
- Kirby's Dream Land 2 intro scrolling first scanline of Rick is now fixed
- GBVideoPlayer functions correctly [2]
- Shin Megami Tensei: Devichil series regression fixed

[1] doesn't pass oam_bug-2/1-lcd_sync; because it seems to want
LY=0,LX>0, and I can't step the PPU in a register write as it's not
a state machine; the effect is emulated, it just starts the frame a tiny
bit sooner. blargg's testing is brutal, you can't be even one cycle off
or the test will fail.

[2] note that you will need the GBC Display Emulation shader from
hunterk's repository, or it will look like absolute shit. The
inter-frame blending is absolutely critical here.
This commit is contained in:
Tim Allen 2016-01-12 22:08:34 +11:00
parent 72b6a8b32e
commit 82ec876302
26 changed files with 442 additions and 364 deletions

View File

@ -2,11 +2,17 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>org.byuu.higan</string>
<key>CFBundleDisplayName</key>
<string>higan</string>
<key>CFBundleExecutable</key>
<string>higan</string>
<key>CFBundleIconFile</key>
<string>higan.icns</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View File

@ -6,7 +6,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "096.04";
static const string Version = "096.05";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -1,7 +1,7 @@
struct KonamiVRC2 : Board {
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
settings.pinout.a0 = 1 << document["cartridge/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["cartridge/chip/pinout/a1"].natural();
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto prg_read(uint addr) -> uint8 {

View File

@ -1,6 +1,6 @@
struct KonamiVRC3 : Board {
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto main() -> void {

View File

@ -1,7 +1,7 @@
struct KonamiVRC4 : Board {
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
settings.pinout.a0 = 1 << document["cartridge/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["cartridge/chip/pinout/a1"].natural();
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto main() -> void {

View File

@ -2,7 +2,7 @@
struct NES_BNROM : Board {
NES_BNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto prg_read(uint addr) -> uint8 {

View File

@ -2,7 +2,7 @@
struct NES_CNROM : Board {
NES_CNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto prg_read(uint addr) -> uint8 {

View File

@ -3,7 +3,7 @@
struct NES_GxROM : Board {
NES_GxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto prg_read(uint addr) -> uint8 {

View File

@ -3,7 +3,7 @@
struct NES_NROM : Board {
NES_NROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto prg_read(uint addr) -> uint8 {

View File

@ -3,7 +3,7 @@
struct NES_UxROM : Board {
NES_UxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["cartridge/mirror/mode"].text() == "vertical" ? 1 : 0;
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto prg_read(uint addr) -> uint8 {

View File

@ -20,24 +20,24 @@ auto APU::main() -> void {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
if(sequencer_base == 0) { //512hz
if(sequencer_step == 0 || sequencer_step == 2 || sequencer_step == 4 || sequencer_step == 6) { //256hz
square1.clock_length();
square2.clock_length();
wave.clock_length();
noise.clock_length();
if(stage == 0) { //512hz
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
square1.clockLength();
square2.clockLength();
wave.clockLength();
noise.clockLength();
}
if(sequencer_step == 2 || sequencer_step == 6) { //128hz
square1.clock_sweep();
if(phase == 2 || phase == 6) { //128hz
square1.clockSweep();
}
if(sequencer_step == 7) { //64hz
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
if(phase == 7) { //64hz
square1.clockEnvelope();
square2.clockEnvelope();
noise.clockEnvelope();
}
sequencer_step++;
phase++;
}
sequencer_base++;
stage++;
square1.run();
square2.run();
@ -45,9 +45,9 @@ auto APU::main() -> void {
noise.run();
master.run();
hipass(master.center, master.center_bias);
hipass(master.left, master.left_bias);
hipass(master.right, master.right_bias);
hipass(master.center, master.centerBias);
hipass(master.left, master.leftBias);
hipass(master.right, master.rightBias);
interface->audioSample(master.left, master.right);
@ -65,8 +65,8 @@ auto APU::power() -> void {
create(Main, 2 * 1024 * 1024);
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
sequencer_base = 0;
sequencer_step = 0;
stage = 0;
phase = 0;
square1.power();
square2.power();

View File

@ -15,8 +15,8 @@ struct APU : Thread, MMIO {
#include "noise/noise.hpp"
#include "master/master.hpp"
uint12 sequencer_base;
uint3 sequencer_step;
uint12 stage;
uint3 phase;
Square1 square1;
Square2 square2;

View File

@ -4,7 +4,7 @@ auto APU::Master::run() -> void {
left = 0;
right = 0;
center_bias = left_bias = right_bias = 0;
centerBias = leftBias = rightBias = 0;
return;
}
@ -16,21 +16,21 @@ auto APU::Master::run() -> void {
center = (sample * 512) - 16384;
sample = 0;
if(channel1_left_enable) sample += apu.square1.output;
if(channel2_left_enable) sample += apu.square2.output;
if(channel3_left_enable) sample += apu.wave.output;
if(channel4_left_enable) sample += apu.noise.output;
if(square1.leftEnable) sample += apu.square1.output;
if(square2.leftEnable) sample += apu.square2.output;
if( wave.leftEnable) sample += apu.wave.output;
if( noise.leftEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (left_volume + 1)) / 8;
sample = (sample * (leftVolume + 1)) / 8;
left = sample;
sample = 0;
if(channel1_right_enable) sample += apu.square1.output;
if(channel2_right_enable) sample += apu.square2.output;
if(channel3_right_enable) sample += apu.wave.output;
if(channel4_right_enable) sample += apu.noise.output;
if(square1.rightEnable) sample += apu.square1.output;
if(square2.rightEnable) sample += apu.square2.output;
if( wave.rightEnable) sample += apu.wave.output;
if( noise.rightEnable) sample += apu.noise.output;
sample = (sample * 512) - 16384;
sample = (sample * (right_volume + 1)) / 8;
sample = (sample * (rightVolume + 1)) / 8;
right = sample;
//reduce audio volume
@ -41,18 +41,18 @@ auto APU::Master::run() -> void {
auto APU::Master::read(uint16 addr) -> uint8 {
if(addr == 0xff24) { //NR50
return left_in_enable << 7 | left_volume << 4 | right_in_enable << 3 | right_volume;
return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
}
if(addr == 0xff25) { //NR51
return channel4_left_enable << 7
| channel3_left_enable << 6
| channel2_left_enable << 5
| channel1_left_enable << 4
| channel4_right_enable << 3
| channel3_right_enable << 2
| channel2_right_enable << 1
| channel1_right_enable << 0;
return noise.leftEnable << 7
| wave.leftEnable << 6
| square2.leftEnable << 5
| square1.leftEnable << 4
| noise.rightEnable << 3
| wave.rightEnable << 2
| square2.rightEnable << 1
| square1.rightEnable << 0;
}
if(addr == 0xff26) { //NR52
@ -68,79 +68,80 @@ auto APU::Master::read(uint16 addr) -> uint8 {
auto APU::Master::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
left_in_enable = data & 0x80;
left_volume = (data >> 4) & 7;
right_in_enable = data & 0x08;
right_volume = (data >> 0) & 7;
leftEnable = data & 0x80;
leftVolume = (data >> 4) & 7;
rightEnable = data & 0x08;
rightVolume = (data >> 0) & 7;
}
if(addr == 0xff25) { //NR51
channel4_left_enable = data & 0x80;
channel3_left_enable = data & 0x40;
channel2_left_enable = data & 0x20;
channel1_left_enable = data & 0x10;
channel4_right_enable = data & 0x08;
channel3_right_enable = data & 0x04;
channel2_right_enable = data & 0x02;
channel1_right_enable = data & 0x01;
noise.leftEnable = data & 0x80;
wave.leftEnable = data & 0x40;
square2.leftEnable = data & 0x20;
square1.leftEnable = data & 0x10;
noise.rightEnable = data & 0x08;
wave.rightEnable = data & 0x04;
square2.rightEnable = data & 0x02;
square1.rightEnable = data & 0x01;
}
if(addr == 0xff26) { //NR52
enable = data & 0x80;
if(!enable) {
apu.square1.power();
apu.square2.power();
apu.wave.power();
apu.noise.power();
//power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(system.cgb());
apu.square2.power(system.cgb());
apu.wave.power(system.cgb());
apu.noise.power(system.cgb());
power();
}
}
}
auto APU::Master::power() -> void {
left_in_enable = 0;
left_volume = 0;
right_in_enable = 0;
right_volume = 0;
channel4_left_enable = 0;
channel3_left_enable = 0;
channel2_left_enable = 0;
channel1_left_enable = 0;
channel4_right_enable = 0;
channel3_right_enable = 0;
channel2_right_enable = 0;
channel1_right_enable = 0;
leftEnable = 0;
leftVolume = 0;
rightEnable = 0;
rightVolume = 0;
noise.leftEnable = 0;
wave.leftEnable = 0;
square2.leftEnable = 0;
square1.leftEnable = 0;
noise.rightEnable = 0;
wave.rightEnable = 0;
square2.rightEnable = 0;
square1.rightEnable = 0;
enable = 0;
center = 0;
left = 0;
right = 0;
center_bias = 0;
left_bias = 0;
right_bias = 0;
centerBias = 0;
leftBias = 0;
rightBias = 0;
}
auto APU::Master::serialize(serializer& s) -> void {
s.integer(left_in_enable);
s.integer(left_volume);
s.integer(right_in_enable);
s.integer(right_volume);
s.integer(channel4_left_enable);
s.integer(channel3_left_enable);
s.integer(channel2_left_enable);
s.integer(channel1_left_enable);
s.integer(channel4_right_enable);
s.integer(channel3_right_enable);
s.integer(channel2_right_enable);
s.integer(channel1_right_enable);
s.integer(leftEnable);
s.integer(leftVolume);
s.integer(rightEnable);
s.integer(rightVolume);
s.integer(noise.leftEnable);
s.integer(wave.leftEnable);
s.integer(square2.leftEnable);
s.integer(square1.leftEnable);
s.integer(noise.rightEnable);
s.integer(wave.rightEnable);
s.integer(square2.rightEnable);
s.integer(square1.rightEnable);
s.integer(enable);
s.integer(center);
s.integer(left);
s.integer(right);
s.integer(center_bias);
s.integer(left_bias);
s.integer(right_bias);
s.integer(centerBias);
s.integer(leftBias);
s.integer(rightBias);
}

View File

@ -6,25 +6,23 @@ struct Master {
auto serialize(serializer&) -> void;
bool left_in_enable;
uint3 left_volume;
bool right_in_enable;
uint3 right_volume;
bool channel4_left_enable;
bool channel3_left_enable;
bool channel2_left_enable;
bool channel1_left_enable;
bool channel4_right_enable;
bool channel3_right_enable;
bool channel2_right_enable;
bool channel1_right_enable;
bool leftEnable;
uint3 leftVolume;
bool rightEnable;
uint3 rightVolume;
struct Channel {
bool leftEnable;
bool rightEnable;
} square1, square2, wave, noise;
bool enable;
int16 center;
int16 left;
int16 right;
int64 center_bias;
int64 left_bias;
int64 right_bias;
int64 centerBias;
int64 leftBias;
int64 rightBias;
};

View File

@ -1,38 +1,38 @@
auto APU::Noise::dac_enable() const -> bool {
return (envelope_volume || envelope_direction);
auto APU::Noise::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Noise::get_period() const -> uint {
auto APU::Noise::getPeriod() const -> uint {
static const uint table[] = {4, 8, 16, 24, 32, 40, 48, 56};
return table[divisor] << frequency;
}
auto APU::Noise::run() -> void {
if(period && --period == 0) {
period = get_period();
period = getPeriod();
if(frequency < 14) {
bool bit = (lfsr ^ (lfsr >> 1)) & 1;
lfsr = (lfsr >> 1) ^ (bit << (narrow_lfsr ? 6 : 14));
lfsr = (lfsr >> 1) ^ (bit << (narrow ? 6 : 14));
}
}
uint4 sample = (lfsr & 1) ? (uint4)0 : volume;
if(enable == false) sample = 0;
if(!enable) sample = 0;
output = sample;
}
auto APU::Noise::clock_length() -> void {
auto APU::Noise::clockLength() -> void {
if(counter) {
if(++length == 0) enable = false;
if(length && --length == 0) enable = false;
}
}
auto APU::Noise::clock_envelope() -> void {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
auto APU::Noise::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
@ -46,11 +46,11 @@ auto APU::Noise::read(uint16 addr) -> uint8 {
}
if(addr == 0xff21) { //NR42
return envelope_volume << 4 | envelope_direction << 3 | envelope_frequency;
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff22) { //NR43
return frequency << 4 | narrow_lfsr << 3 | divisor;
return frequency << 4 | narrow << 3 | divisor;
}
if(addr == 0xff23) { //NR44
@ -62,69 +62,79 @@ auto APU::Noise::read(uint16 addr) -> uint8 {
auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff20) { //NR41
length = data & 0x3f;
length = 64 - (data & 0x3f);
}
if(addr == 0xff21) { //NR42
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
if(!dacEnable()) enable = false;
}
if(addr == 0xff22) { //NR43
frequency = data >> 4;
narrow_lfsr = data & 0x08;
narrow = data & 0x08;
divisor = data & 0x07;
period = get_period();
period = getPeriod();
}
if(addr == 0xff23) { //NR44
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
if(initialize) {
enable = dac_enable();
enable = dacEnable();
lfsr = -1;
envelope_period = envelope_frequency;
volume = envelope_volume;
envelopePeriod = envelopeFrequency;
volume = envelopeVolume;
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
}
}
}
}
auto APU::Noise::power() -> void {
auto APU::Noise::power(bool initializeLength) -> void {
enable = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
narrow_lfsr = 0;
narrow = 0;
divisor = 0;
counter = 0;
output = 0;
length = 0;
envelope_period = 0;
envelopePeriod = 0;
volume = 0;
period = 0;
lfsr = 0;
if(initializeLength) length = 64;
}
auto APU::Noise::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(narrow_lfsr);
s.integer(narrow);
s.integer(divisor);
s.integer(counter);
s.integer(output);
s.integer(length);
s.integer(envelope_period);
s.integer(envelopePeriod);
s.integer(volume);
s.integer(period);
s.integer(lfsr);

View File

@ -1,29 +1,29 @@
struct Noise {
auto dac_enable() const -> bool;
auto get_period() const -> uint;
auto dacEnable() const -> bool;
auto getPeriod() const -> uint;
auto run() -> void;
auto clock_length() -> void;
auto clock_envelope() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint4 frequency;
bool narrow_lfsr;
bool narrow;
uint3 divisor;
bool counter;
int16 output;
uint6 length;
uint3 envelope_period;
uint length;
uint3 envelopePeriod;
uint4 volume;
uint period;
uint15 lfsr;

View File

@ -1,8 +1,8 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(sequencer_base);
s.integer(sequencer_step);
s.integer(stage);
s.integer(phase);
square1.serialize(s);
square2.serialize(s);

View File

@ -1,5 +1,5 @@
auto APU::Square1::dac_enable() const -> bool {
return (envelope_volume || envelope_direction);
auto APU::Square1::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square1::run() -> void {
@ -7,60 +7,62 @@ auto APU::Square1::run() -> void {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
uint4 sample = (dutyOutput ? volume : (uint4)0);
if(!enable) sample = 0;
output = sample;
}
auto APU::Square1::sweep(bool update) -> void {
if(sweep_enable == false) return;
if(!sweepEnable) return;
sweep_negate = sweep_direction;
uint delta = frequency_shadow >> sweep_shift;
int freq = frequency_shadow + (sweep_negate ? -delta : delta);
sweepNegate = sweepDirection;
uint delta = frequencyShadow >> sweepShift;
int freq = frequencyShadow + (sweepNegate ? -delta : delta);
if(freq > 2047) {
enable = false;
} else if(sweep_shift && update) {
frequency_shadow = freq;
} else if(sweepShift && update) {
frequencyShadow = freq;
frequency = freq & 2047;
period = 2 * (2048 - frequency);
}
}
auto APU::Square1::clock_length() -> void {
auto APU::Square1::clockLength() -> void {
if(counter) {
if(++length == 0) enable = false;
if(length && --length == 0) enable = false;
}
}
auto APU::Square1::clock_sweep() -> void {
if(enable && sweep_frequency && --sweep_period == 0) {
sweep_period = sweep_frequency;
sweep(1);
sweep(0);
auto APU::Square1::clockSweep() -> void {
if(--sweepPeriod == 0) {
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
if(sweepEnable && sweepFrequency) {
sweep(1);
sweep(0);
}
}
}
auto APU::Square1::clock_envelope() -> void {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
auto APU::Square1::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
auto APU::Square1::read(uint16 addr) -> uint8 {
if(addr == 0xff10) { //NR10
return 0x80 | sweep_frequency << 4 | sweep_direction << 3 | sweep_shift;
return 0x80 | sweepFrequency << 4 | sweepDirection << 3 | sweepShift;
}
if(addr == 0xff11) { //NR11
@ -68,7 +70,7 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
}
if(addr == 0xff12) { //NR12
return envelope_volume << 4 | envelope_direction << 3 | envelope_frequency;
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff13) { //NR13
@ -84,22 +86,22 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff10) { //NR10
if(sweep_negate && sweep_direction && !(data & 0x08)) enable = false;
sweep_frequency = (data >> 4) & 7;
sweep_direction = data & 0x08;
sweep_shift = data & 0x07;
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
sweepFrequency = (data >> 4) & 7;
sweepDirection = data & 0x08;
sweepShift = data & 0x07;
}
if(addr == 0xff11) { //NR11
duty = data >> 6;
length = data & 0x3f;
length = 64 - (data & 0x3f);
}
if(addr == 0xff12) { //NR12
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
if(!dacEnable()) enable = false;
}
if(addr == 0xff13) { //NR13
@ -107,72 +109,83 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff14) { //NR14
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
enable = dacEnable();
period = 2 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
frequency_shadow = frequency;
sweep_period = sweep_frequency;
sweep_enable = sweep_period || sweep_shift;
sweep_negate = false;
if(sweep_shift) sweep(0);
envelopePeriod = envelopeFrequency;
volume = envelopeVolume;
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
}
frequencyShadow = frequency;
sweepNegate = false;
sweepPeriod = sweepFrequency ? (uint)sweepFrequency : 8;
sweepEnable = sweepPeriod || sweepShift;
if(sweepShift) sweep(0);
}
}
}
auto APU::Square1::power() -> void {
auto APU::Square1::power(bool initializeLength) -> void {
enable = 0;
sweep_frequency = 0;
sweep_direction = 0;
sweep_shift = 0;
sweep_negate = 0;
sweepFrequency = 0;
sweepDirection = 0;
sweepShift = 0;
sweepNegate = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelope_period = 0;
sweep_period = 0;
frequency_shadow = 0;
sweep_enable = 0;
envelopePeriod = 0;
sweepPeriod = 0;
frequencyShadow = 0;
sweepEnable = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square1::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(sweep_frequency);
s.integer(sweep_direction);
s.integer(sweep_shift);
s.integer(sweep_negate);
s.integer(sweepFrequency);
s.integer(sweepDirection);
s.integer(sweepShift);
s.integer(sweepNegate);
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(sweep_period);
s.integer(frequency_shadow);
s.integer(sweep_enable);
s.integer(envelopePeriod);
s.integer(sweepPeriod);
s.integer(frequencyShadow);
s.integer(sweepEnable);
s.integer(volume);
}

View File

@ -1,38 +1,38 @@
struct Square1 {
auto dac_enable() const -> bool;
auto dacEnable() const -> bool;
auto run() -> void;
auto sweep(bool update) -> void;
auto clock_length() -> void;
auto clock_sweep() -> void;
auto clock_envelope() -> void;
auto clockLength() -> void;
auto clockSweep() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint3 sweep_frequency;
bool sweep_direction;
uint3 sweep_shift;
bool sweep_negate;
uint3 sweepFrequency;
bool sweepDirection;
uint3 sweepShift;
bool sweepNegate;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelope_period;
uint3 sweep_period;
int frequency_shadow;
bool sweep_enable;
uint3 envelopePeriod;
uint3 sweepPeriod;
int frequencyShadow;
bool sweepEnable;
uint4 volume;
};

View File

@ -1,5 +1,5 @@
auto APU::Square2::dac_enable() const -> bool {
return (envelope_volume || envelope_direction);
auto APU::Square2::dacEnable() const -> bool {
return (envelopeVolume || envelopeDirection);
}
auto APU::Square2::run() -> void {
@ -7,30 +7,30 @@ auto APU::Square2::run() -> void {
period = 2 * (2048 - frequency);
phase++;
switch(duty) {
case 0: duty_output = (phase == 6); break; //______-_
case 1: duty_output = (phase >= 6); break; //______--
case 2: duty_output = (phase >= 4); break; //____----
case 3: duty_output = (phase <= 5); break; //------__
case 0: dutyOutput = (phase == 6); break; //______-_
case 1: dutyOutput = (phase >= 6); break; //______--
case 2: dutyOutput = (phase >= 4); break; //____----
case 3: dutyOutput = (phase <= 5); break; //------__
}
}
uint4 sample = (duty_output ? volume : (uint4)0);
if(enable == false) sample = 0;
uint4 sample = (dutyOutput ? volume : (uint4)0);
if(!enable) sample = 0;
output = sample;
}
auto APU::Square2::clock_length() -> void {
auto APU::Square2::clockLength() -> void {
if(counter) {
if(++length == 0) enable = false;
if(length && --length == 0) enable = false;
}
}
auto APU::Square2::clock_envelope() -> void {
if(enable && envelope_frequency && --envelope_period == 0) {
envelope_period = envelope_frequency;
if(envelope_direction == 0 && volume > 0) volume--;
if(envelope_direction == 1 && volume < 15) volume++;
auto APU::Square2::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
}
@ -44,7 +44,7 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
}
if(addr == 0xff17) { //NR22
return envelope_volume << 4 | envelope_direction << 3 | envelope_frequency;
return envelopeVolume << 4 | envelopeDirection << 3 | envelopeFrequency;
}
if(addr == 0xff18) { //NR23
@ -61,14 +61,14 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff16) { //NR21
duty = data >> 6;
length = (data & 0x3f);
length = 64 - (data & 0x3f);
}
if(addr == 0xff17) { //NR22
envelope_volume = data >> 4;
envelope_direction = data & 0x08;
envelope_frequency = data & 0x07;
if(dac_enable() == false) enable = false;
envelopeVolume = data >> 4;
envelopeDirection = data & 0x08;
envelopeFrequency = data & 0x07;
if(!dacEnable()) enable = false;
}
if(addr == 0xff18) { //NR23
@ -76,36 +76,46 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff19) { //NR24
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable();
enable = dacEnable();
period = 2 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
envelopePeriod = envelopeFrequency;
volume = envelopeVolume;
if(!length) {
length = 64;
if((apu.phase & 1) && counter) length--;
}
}
}
}
auto APU::Square2::power() -> void {
auto APU::Square2::power(bool initializeLength) -> void {
enable = 0;
duty = 0;
length = 0;
envelope_volume = 0;
envelope_direction = 0;
envelope_frequency = 0;
envelopeVolume = 0;
envelopeDirection = 0;
envelopeFrequency = 0;
frequency = 0;
counter = 0;
output = 0;
duty_output = 0;
dutyOutput = 0;
phase = 0;
period = 0;
envelope_period = 0;
envelopePeriod = 0;
volume = 0;
if(initializeLength) length = 64;
}
auto APU::Square2::serialize(serializer& s) -> void {
@ -113,16 +123,16 @@ auto APU::Square2::serialize(serializer& s) -> void {
s.integer(duty);
s.integer(length);
s.integer(envelope_volume);
s.integer(envelope_direction);
s.integer(envelope_frequency);
s.integer(envelopeVolume);
s.integer(envelopeDirection);
s.integer(envelopeFrequency);
s.integer(frequency);
s.integer(counter);
s.integer(output);
s.integer(duty_output);
s.integer(dutyOutput);
s.integer(phase);
s.integer(period);
s.integer(envelope_period);
s.integer(envelopePeriod);
s.integer(volume);
}

View File

@ -1,29 +1,29 @@
struct Square2 {
auto dac_enable() const -> bool;
auto dacEnable() const -> bool;
auto run() -> void;
auto clock_length() -> void;
auto clock_envelope() -> void;
auto clockLength() -> void;
auto clockEnvelope() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
uint2 duty;
uint6 length;
uint4 envelope_volume;
bool envelope_direction;
uint3 envelope_frequency;
uint length;
uint4 envelopeVolume;
bool envelopeDirection;
uint3 envelopeFrequency;
uint11 frequency;
bool counter;
int16 output;
bool duty_output;
bool dutyOutput;
uint3 phase;
uint period;
uint3 envelope_period;
uint3 envelopePeriod;
uint4 volume;
};

View File

@ -1,29 +1,29 @@
auto APU::Wave::get_pattern(uint5 offset) const -> uint4 {
auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
}
auto APU::Wave::run() -> void {
if(period && --period == 0) {
period = 1 * (2048 - frequency);
pattern_sample = get_pattern(++pattern_offset);
patternSample = getPattern(++patternOffset);
}
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
uint4 sample = pattern_sample >> shift[volume];
uint4 sample = patternSample >> shift[volume];
if(enable == false) sample = 0;
output = sample;
}
auto APU::Wave::clock_length() -> void {
auto APU::Wave::clockLength() -> void {
if(counter) {
if(++length == 0) enable = false;
if(length && --length == 0) enable = false;
}
}
auto APU::Wave::read(uint16 addr) -> uint8 {
if(addr == 0xff1a) { //NR30
return dac_enable << 7 | 0x7f;
return dacEnable << 7 | 0x7f;
}
if(addr == 0xff1b) { //NR31
@ -51,12 +51,12 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff1a) { //NR30
dac_enable = data & 0x80;
if(dac_enable == false) enable = false;
dacEnable = data & 0x80;
if(!dacEnable) enable = false;
}
if(addr == 0xff1b) { //NR31
length = data;
length = 256 - data;
}
if(addr == 0xff1c) { //NR32
@ -68,14 +68,23 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff1e) { //NR34
if((apu.phase & 1) && !counter && (data & 0x40)) {
if(length && --length == 0) enable = false;
}
bool initialize = data & 0x80;
counter = data & 0x40;
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
if(initialize) {
enable = dac_enable;
enable = dacEnable;
period = 1 * (2048 - frequency);
pattern_offset = 0;
patternOffset = 0;
if(!length) {
length = 256;
if((apu.phase & 1) && counter) length--;
}
}
}
@ -84,25 +93,26 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
}
}
auto APU::Wave::power() -> void {
auto APU::Wave::power(bool initializeLength) -> void {
enable = 0;
dac_enable = 0;
dacEnable = 0;
volume = 0;
frequency = 0;
counter = 0;
output = 0;
length = 0;
period = 0;
pattern_offset = 0;
pattern_sample = 0;
patternOffset = 0;
patternSample = 0;
if(initializeLength) length = 256;
}
auto APU::Wave::serialize(serializer& s) -> void {
s.integer(enable);
s.integer(dac_enable);
s.integer(dacEnable);
s.integer(volume);
s.integer(frequency);
s.integer(counter);
@ -111,6 +121,6 @@ auto APU::Wave::serialize(serializer& s) -> void {
s.integer(output);
s.integer(length);
s.integer(period);
s.integer(pattern_offset);
s.integer(pattern_sample);
s.integer(patternOffset);
s.integer(patternSample);
}

View File

@ -1,25 +1,25 @@
struct Wave {
auto get_pattern(uint5 offset) const -> uint4;
auto getPattern(uint5 offset) const -> uint4;
auto run() -> void;
auto clock_length() -> void;
auto clockLength() -> void;
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
auto power() -> void;
auto power(bool initializeLength = true) -> void;
auto serialize(serializer&) -> void;
bool enable;
bool dac_enable;
bool dacEnable;
uint2 volume;
uint11 frequency;
bool counter;
uint8 pattern[16];
int16 output;
uint8 length;
uint length;
uint period;
uint5 pattern_offset;
uint4 pattern_sample;
uint5 patternOffset;
uint4 patternSample;
};

View File

@ -77,15 +77,27 @@ auto PPU::mmio_read(uint16 addr) -> uint8 {
return status.wx;
}
if(addr == 0xff69) { //BGPD
if(addr == 0xff4f) { //VBK
return status.vram_bank;
}
if(addr == 0xff68) { //BGPI
return status.bgpi_increment << 7 | status.bgpi;
}
if(addr == 0xff69) { //BGPD
return bgpd[status.bgpi];
}
if(addr == 0xff6a) { //OBPI
return status.obpi_increment << 7 | status.obpi;
}
if(addr == 0xff6b) { //OBPD
return obpd[status.obpi];
}
return 0xff;
return 0xff; //should never occur
}
auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
@ -94,7 +106,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
if(addr == 0xff40) { //LCDC
if(status.display_enable == false && (data & 0x80)) {
status.lx = 0; //unverified behavior; fixes Super Mario Land 2 - Tree Zone
status.ly = 0;
status.lx = 0;
//restart cothread to begin new frame
auto clock = this->clock;
create(Main, 4 * 1024 * 1024);
this->clock = clock;
}
status.display_enable = data & 0x80;

View File

@ -24,23 +24,49 @@ auto PPU::main() -> void {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
status.lx = 0;
interface->lcdScanline(); //Super Game Boy notification
if(status.display_enable && status.ly < 144) {
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
add_clocks(92);
for(auto n : range(160)) {
system.cgb() ? cgb_run() : dmg_run();
add_clocks(1);
if(status.display_enable) {
//LYC of zero triggers on LY==153
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
if(status.ly <= 143) {
scanline();
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
if(status.ly == 144) {
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
cpu.interrupt_raise(CPU::Interrupt::Vblank);
}
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
cpu.hblank();
add_clocks(204);
} else {
add_clocks(456);
}
scanline();
add_clocks(92);
if(status.ly <= 143) {
for(auto n : range(160)) {
if(status.display_enable) run();
add_clocks(1);
}
if(status.display_enable) {
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
cpu.hblank();
}
} else {
add_clocks(160);
}
add_clocks(204);
if(++status.ly == 154) {
status.ly = 0;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
}
}
@ -54,29 +80,6 @@ auto PPU::add_clocks(uint clocks) -> void {
}
}
auto PPU::scanline() -> void {
status.lx = 0;
if(++status.ly == 154) frame();
if(status.ly < 144) {
system.cgb() ? cgb_scanline() : dmg_scanline();
}
if(status.display_enable && status.interrupt_lyc == true) {
if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
if(status.display_enable && status.ly == 144) {
cpu.interrupt_raise(CPU::Interrupt::Vblank);
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
}
}
auto PPU::frame() -> void {
status.ly = 0;
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
auto PPU::hflip(uint data) const -> uint {
return ((data & 0x8080) >> 7) | ((data & 0x4040) >> 5)
| ((data & 0x2020) >> 3) | ((data & 0x1010) >> 1)
@ -87,6 +90,14 @@ auto PPU::hflip(uint data) const -> uint {
auto PPU::power() -> void {
create(Main, 4 * 1024 * 1024);
if(system.cgb()) {
scanline = {&PPU::cgb_scanline, this};
run = {&PPU::cgb_run, this};
} else {
scanline = {&PPU::dmg_scanline, this};
run = {&PPU::dmg_run, this};
}
for(uint n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM
for(uint n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM

View File

@ -2,8 +2,6 @@ struct PPU : Thread, MMIO {
static auto Main() -> void;
auto main() -> void;
auto add_clocks(uint clocks) -> void;
auto scanline() -> void;
auto frame() -> void;
auto hflip(uint data) const -> uint;
@ -39,6 +37,9 @@ struct PPU : Thread, MMIO {
uint8 bgpd[64];
uint8 obpd[64];
function<auto () -> void> scanline;
function<auto () -> void> run;
struct Status {
uint lx;