diff --git a/higan/data/higan.plist b/higan/data/higan.plist index 44ee215b..6d59f131 100644 --- a/higan/data/higan.plist +++ b/higan/data/higan.plist @@ -2,11 +2,17 @@ + CFBundleIdentifier + org.byuu.higan CFBundleDisplayName higan CFBundleExecutable higan CFBundleIconFile higan.icns + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 5e949d23..f023a238 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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/"; diff --git a/higan/fc/cartridge/board/konami-vrc2.cpp b/higan/fc/cartridge/board/konami-vrc2.cpp index 01011890..d033d2b5 100644 --- a/higan/fc/cartridge/board/konami-vrc2.cpp +++ b/higan/fc/cartridge/board/konami-vrc2.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/konami-vrc3.cpp b/higan/fc/cartridge/board/konami-vrc3.cpp index baa00525..5a6cd5da 100644 --- a/higan/fc/cartridge/board/konami-vrc3.cpp +++ b/higan/fc/cartridge/board/konami-vrc3.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/konami-vrc4.cpp b/higan/fc/cartridge/board/konami-vrc4.cpp index ca7b0c14..13041ee6 100644 --- a/higan/fc/cartridge/board/konami-vrc4.cpp +++ b/higan/fc/cartridge/board/konami-vrc4.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/nes-bnrom.cpp b/higan/fc/cartridge/board/nes-bnrom.cpp index 48a2e657..c39c60c5 100644 --- a/higan/fc/cartridge/board/nes-bnrom.cpp +++ b/higan/fc/cartridge/board/nes-bnrom.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/nes-cnrom.cpp b/higan/fc/cartridge/board/nes-cnrom.cpp index 3f1769ba..d0028f5d 100644 --- a/higan/fc/cartridge/board/nes-cnrom.cpp +++ b/higan/fc/cartridge/board/nes-cnrom.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/nes-gxrom.cpp b/higan/fc/cartridge/board/nes-gxrom.cpp index 4603f4b1..d5d58aac 100644 --- a/higan/fc/cartridge/board/nes-gxrom.cpp +++ b/higan/fc/cartridge/board/nes-gxrom.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/nes-nrom.cpp b/higan/fc/cartridge/board/nes-nrom.cpp index 462cf775..2969a5b9 100644 --- a/higan/fc/cartridge/board/nes-nrom.cpp +++ b/higan/fc/cartridge/board/nes-nrom.cpp @@ -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 { diff --git a/higan/fc/cartridge/board/nes-uxrom.cpp b/higan/fc/cartridge/board/nes-uxrom.cpp index f5b55b29..305672f9 100644 --- a/higan/fc/cartridge/board/nes-uxrom.cpp +++ b/higan/fc/cartridge/board/nes-uxrom.cpp @@ -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 { diff --git a/higan/gb/apu/apu.cpp b/higan/gb/apu/apu.cpp index 21353885..2c8df0e6 100644 --- a/higan/gb/apu/apu.cpp +++ b/higan/gb/apu/apu.cpp @@ -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(); diff --git a/higan/gb/apu/apu.hpp b/higan/gb/apu/apu.hpp index 51d92e03..f30a6d15 100644 --- a/higan/gb/apu/apu.hpp +++ b/higan/gb/apu/apu.hpp @@ -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; diff --git a/higan/gb/apu/master/master.cpp b/higan/gb/apu/master/master.cpp index e47d4132..9157e86f 100644 --- a/higan/gb/apu/master/master.cpp +++ b/higan/gb/apu/master/master.cpp @@ -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); } diff --git a/higan/gb/apu/master/master.hpp b/higan/gb/apu/master/master.hpp index 0cdfe8db..0c861610 100644 --- a/higan/gb/apu/master/master.hpp +++ b/higan/gb/apu/master/master.hpp @@ -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; }; diff --git a/higan/gb/apu/noise/noise.cpp b/higan/gb/apu/noise/noise.cpp index 1796c92b..3042cbd4 100644 --- a/higan/gb/apu/noise/noise.cpp +++ b/higan/gb/apu/noise/noise.cpp @@ -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); diff --git a/higan/gb/apu/noise/noise.hpp b/higan/gb/apu/noise/noise.hpp index 095cedf7..c3d8519c 100644 --- a/higan/gb/apu/noise/noise.hpp +++ b/higan/gb/apu/noise/noise.hpp @@ -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; diff --git a/higan/gb/apu/serialization.cpp b/higan/gb/apu/serialization.cpp index 58becd79..cfb2327d 100644 --- a/higan/gb/apu/serialization.cpp +++ b/higan/gb/apu/serialization.cpp @@ -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); diff --git a/higan/gb/apu/square1/square1.cpp b/higan/gb/apu/square1/square1.cpp index 70f7709f..adaacdc7 100644 --- a/higan/gb/apu/square1/square1.cpp +++ b/higan/gb/apu/square1/square1.cpp @@ -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); } diff --git a/higan/gb/apu/square1/square1.hpp b/higan/gb/apu/square1/square1.hpp index 4bd8b344..24aab93c 100644 --- a/higan/gb/apu/square1/square1.hpp +++ b/higan/gb/apu/square1/square1.hpp @@ -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; }; diff --git a/higan/gb/apu/square2/square2.cpp b/higan/gb/apu/square2/square2.cpp index af3ecccc..f07d5203 100644 --- a/higan/gb/apu/square2/square2.cpp +++ b/higan/gb/apu/square2/square2.cpp @@ -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); } diff --git a/higan/gb/apu/square2/square2.hpp b/higan/gb/apu/square2/square2.hpp index db9025c6..48577d9b 100644 --- a/higan/gb/apu/square2/square2.hpp +++ b/higan/gb/apu/square2/square2.hpp @@ -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; }; diff --git a/higan/gb/apu/wave/wave.cpp b/higan/gb/apu/wave/wave.cpp index cd04178f..b0032748 100644 --- a/higan/gb/apu/wave/wave.cpp +++ b/higan/gb/apu/wave/wave.cpp @@ -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); } diff --git a/higan/gb/apu/wave/wave.hpp b/higan/gb/apu/wave/wave.hpp index 9a39ef7d..732c6cc6 100644 --- a/higan/gb/apu/wave/wave.hpp +++ b/higan/gb/apu/wave/wave.hpp @@ -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; }; diff --git a/higan/gb/ppu/mmio.cpp b/higan/gb/ppu/mmio.cpp index d7d13e2d..e6af4bf9 100644 --- a/higan/gb/ppu/mmio.cpp +++ b/higan/gb/ppu/mmio.cpp @@ -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; diff --git a/higan/gb/ppu/ppu.cpp b/higan/gb/ppu/ppu.cpp index d0bbf130..80a67406 100644 --- a/higan/gb/ppu/ppu.cpp +++ b/higan/gb/ppu/ppu.cpp @@ -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 diff --git a/higan/gb/ppu/ppu.hpp b/higan/gb/ppu/ppu.hpp index 33e035af..7ffa8326 100644 --- a/higan/gb/ppu/ppu.hpp +++ b/higan/gb/ppu/ppu.hpp @@ -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 void> scanline; + function void> run; + struct Status { uint lx;