From b586471562cd84f0ec4e8e690a12740db1c2b98d Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Mon, 14 Mar 2016 22:03:32 +1100 Subject: [PATCH] Update to v097r25 release. byuu says: Changelog: - WS: added HblankTimer and VblankTimer IRQs; although they don't appear to have any effect on any games that use them :/ - WS: added sound emulation; works perfectly in some games (eg Riviera); is completely silent in most games (eg GunPey) The sound emulation only partially supports the hypervoice (headphone only) channel. I need to implement the SDMA before it'll actually do anything useful. I'm a bit confused about how exactly things work. It looks like the speaker volume shift and clamp only applies to speaker mode and not headphone mode, which is very weird. Then there's the software possibility of muting the headphones and/or the speaker. Preferably, I want to leave the emulator always in headphone mode for the extra audio channel. If there are games that force-mute the headphones, but not speakers, then I may need to force headphones back on but with the hypervoice channel disabled. I guess we'll see how things go. Rough guess is probably that the channels default to enabled after the IPLROM, and games aren't bothering to manually enable them or something. --- higan/emulator/emulator.hpp | 2 +- higan/processor/v30mz/v30mz.cpp | 4 +- higan/ws/apu/apu.cpp | 141 +++++++++++++++++++++------ higan/ws/apu/apu.hpp | 142 ++++++++++++++++++++++------ higan/ws/apu/channel.cpp | 8 ++ higan/ws/apu/channel0.cpp | 11 +++ higan/ws/apu/channel1.cpp | 15 +++ higan/ws/apu/channel2.cpp | 18 ++++ higan/ws/apu/channel3.cpp | 30 ++++++ higan/ws/apu/channel4.cpp | 11 +++ higan/ws/apu/io.cpp | 162 +++++++++++++++++++------------- higan/ws/cpu/interrupt.cpp | 21 ++--- higan/ws/ppu/io.cpp | 40 ++++++++ higan/ws/ppu/ppu.cpp | 34 ++++++- higan/ws/ppu/ppu.hpp | 18 ++++ 15 files changed, 514 insertions(+), 143 deletions(-) create mode 100644 higan/ws/apu/channel.cpp create mode 100644 higan/ws/apu/channel0.cpp create mode 100644 higan/ws/apu/channel1.cpp create mode 100644 higan/ws/apu/channel2.cpp create mode 100644 higan/ws/apu/channel3.cpp create mode 100644 higan/ws/apu/channel4.cpp diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index ff693915..260b391e 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 = "097.24"; + static const string Version = "097.25"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/processor/v30mz/v30mz.cpp b/higan/processor/v30mz/v30mz.cpp index d784e59c..11bf214a 100644 --- a/higan/processor/v30mz/v30mz.cpp +++ b/higan/processor/v30mz/v30mz.cpp @@ -22,8 +22,8 @@ auto V30MZ::debug(string text) -> void { } auto V30MZ::power() -> void { - state.halt = false; - state.poll = true; + state.halt = false; + state.poll = true; state.prefix = false; prefixes.reset(); diff --git a/higan/ws/apu/apu.cpp b/higan/ws/apu/apu.cpp index bc79c129..c4a1801e 100644 --- a/higan/ws/apu/apu.cpp +++ b/higan/ws/apu/apu.cpp @@ -4,14 +4,61 @@ namespace WonderSwan { APU apu; #include "io.cpp" +#include "channel.cpp" +#include "channel0.cpp" +#include "channel1.cpp" +#include "channel2.cpp" +#include "channel3.cpp" +#include "channel4.cpp" auto APU::Enter() -> void { while(true) scheduler.synchronize(), apu.main(); } auto APU::main() -> void { - step(128); - interface->audioSample(0, 0); + channel0.run(); + channel1.run(); + channel2.run(); + channel3.run(); + + if(s.clock.bits(0,12) == 0) { + channel2.sweep(); + } + + if(s.clock.bits(0,6) == 0) { + channel4.run(); + dacRun(); + } + + s.clock++; + step(1); +} + +auto APU::dacRun() -> void { + int left = 0; + if(channel0.r.enable) left += channel0.o.left; + if(channel1.r.enable) left += channel1.o.left; + if(channel2.r.enable) left += channel2.o.left; + if(channel3.r.enable) left += channel3.o.left; + left = (left >> r.speakerShift) << 5; + if(channel4.r.enable) left += channel4.o.left; + left = sclamp<16>(left << 3); + + int right = 0; + if(channel0.r.enable) right += channel0.o.right; + if(channel1.r.enable) right += channel1.o.right; + if(channel2.r.enable) right += channel2.o.right; + if(channel3.r.enable) right += channel3.o.right; + right = (right >> r.speakerShift) << 5; + if(channel4.r.enable) right += channel4.o.right; + right = sclamp<16>(right << 3); + + if(!r.speakerEnable) { + left = 0; + right = 0; + } + + interface->audioSample(left, right); } auto APU::step(uint clocks) -> void { @@ -22,40 +69,74 @@ auto APU::step(uint clocks) -> void { auto APU::power() -> void { create(APU::Enter, 3'072'000); + for(uint n = 0x006a; n <= 0x006b; n++) iomap[n] = this; for(uint n = 0x0080; n <= 0x0094; n++) iomap[n] = this; - channel1.r.pitch = 0; - channel1.r.volumeLeft = 0; - channel1.r.volumeRight = 0; - channel1.r.enable = 0; - - channel2.r.pitch = 0; - channel2.r.volumeLeft = 0; - channel2.r.volumeRight = 0; - channel2.r.enable = 0; - channel2.r.voice = 0; - - channel3.r.pitch = 0; - channel3.r.volumeLeft = 0; - channel3.r.volumeRight = 0; - channel3.r.sweepValue = 0; - channel3.r.sweepTime = 0; - channel3.r.enable = 0; - channel3.r.sweep = 0; - - channel4.r.pitch = 0; - channel4.r.volumeLeft = 0; - channel4.r.volumeRight = 0; - channel4.r.noiseMode = 0; - channel4.r.enable = 0; - channel4.r.noise = 0; - + s.clock = 0; r.waveBase = 0; r.speakerEnable = 0; r.speakerShift = 0; r.headphoneEnable = 0; - r.voiceEnableLeft = 0; - r.voiceEnableRight = 0; + + channel0.o.left = 0; + channel0.o.right = 0; + channel0.s.period = 0; + channel0.s.sampleOffset = 0; + channel0.r.pitch = 0; + channel0.r.volumeLeft = 0; + channel0.r.volumeRight = 0; + channel0.r.enable = 0; + + channel1.o.left = 0; + channel1.o.right = 0; + channel1.s.period = 0; + channel1.s.sampleOffset = 0; + channel1.r.pitch = 0; + channel1.r.volumeLeft = 0; + channel1.r.volumeRight = 0; + channel1.r.enable = 0; + channel1.r.voice = 0; + channel1.r.voiceEnableLeft = 0; + channel1.r.voiceEnableRight = 0; + + channel2.o.left = 0; + channel2.o.right = 0; + channel2.s.period = 0; + channel2.s.sampleOffset = 0; + channel2.s.sweepCounter = 0; + channel2.r.pitch = 0; + channel2.r.volumeLeft = 0; + channel2.r.volumeRight = 0; + channel2.r.sweepValue = 0; + channel2.r.sweepTime = 0; + channel2.r.enable = 0; + channel2.r.sweep = 0; + + channel3.o.left = 0; + channel3.o.right = 0; + channel3.s.period = 0; + channel3.s.sampleOffset = 0; + channel3.s.noiseOutput = 0; + channel3.s.noiseLFSR = 0; + channel3.r.pitch = 0; + channel3.r.volumeLeft = 0; + channel3.r.volumeRight = 0; + channel3.r.noiseMode = 0; + channel3.r.noiseReset = 0; + channel3.r.noiseUpdate = 0; + channel3.r.enable = 0; + channel3.r.noise = 0; + + channel4.o.left = 0; + channel4.o.right = 0; + channel4.s.data = 0; + channel4.r.volume = 0; + channel4.r.scale = 0; + channel4.r.speed = 0; + channel4.r.enable = 0; + channel4.r.unknown = 0; + channel4.r.leftEnable = 0; + channel4.r.rightEnable = 0; } } diff --git a/higan/ws/apu/apu.hpp b/higan/ws/apu/apu.hpp index e737e6d3..641fbe04 100644 --- a/higan/ws/apu/apu.hpp +++ b/higan/ws/apu/apu.hpp @@ -1,6 +1,7 @@ struct APU : Thread, IO { static auto Enter() -> void; auto main() -> void; + auto dacRun() -> void; auto step(uint clocks) -> void; auto power() -> void; @@ -8,41 +9,98 @@ struct APU : Thread, IO { auto portRead(uint16 addr) -> uint8; auto portWrite(uint16 addr, uint8 data) -> void; - struct Channel1 { + struct State { + uint13 clock; + } s; + + struct Registers { + //$008f SND_WAVE_BASE + uint8 waveBase; + + //$0091 SND_OUTPUT + uint1 speakerEnable; + uint2 speakerShift; + uint1 headphoneEnable; + } r; + + struct Channel { + Channel(uint id); + auto sample(uint5 index) -> uint4; + + const uint id; + + struct Output { + int16 left; + int16 right; + } o; + }; + + struct Channel0 : Channel { + Channel0(); + auto run() -> void; + + struct State { + uint11 period; + uint5 sampleOffset; + } s; + struct Registers { - //$0080-0081 SND_CH1_PITCH + //$0080-0081 SND_CH0_PITCH uint11 pitch; - //$0088 SND_CH1_VOL + //$0088 SND_CH0_VOL uint4 volumeLeft; uint4 volumeRight; //$0090 SND_CTRL uint1 enable; } r; - } channel1; + } channel0; + + struct Channel1 : Channel { + Channel1(); + auto run() -> void; + + struct State { + uint11 period; + uint5 sampleOffset; + } s; - struct Channel2 { struct Registers { - //$0082-0083 SND_CH2_PITCH + //$0082-0083 SND_CH1_PITCH uint11 pitch; - //$0089 SND_CH2_VOL + //$0089 SND_CH1_VOL uint4 volumeLeft; uint4 volumeRight; //$0090 SND_CTRL uint1 enable; uint1 voice; - } r; - } channel2; - struct Channel3 { + //$0092 SND_VOICE_CTRL + uint2 voiceEnableLeft; + uint2 voiceEnableRight; + } r; + } channel1; + + struct Channel2 : Channel { + Channel2(); + auto sweep() -> void; + auto run() -> void; + + struct State { + uint11 period; + uint5 sampleOffset; + + int sweepCounter; + } s; + struct Registers { - //$0084-0085 SND_CH3_PITCH + //$0084-0085 SND_CH2_PITCH uint11 pitch; - //$008a SND_CH3_VOL + //$008a SND_CH2_VOL uint4 volumeLeft; uint4 volumeRight; @@ -56,39 +114,61 @@ struct APU : Thread, IO { uint1 enable; uint1 sweep; } r; - } channel3; + } channel2; + + struct Channel3 : Channel { + Channel3(); + auto noiseSample() -> uint4; + auto run() -> void; + + struct State { + uint11 period; + uint5 sampleOffset; + + uint1 noiseOutput; + uint15 noiseLFSR; + } s; - struct Channel4 { struct Registers { - //$0086-0087 SND_CH4_PITCH + //$0086-0087 SND_CH3_PITCH uint11 pitch; - //$008b SND_CH4_VOL + //$008b SND_CH3_VOL uint4 volumeLeft; uint4 volumeRight; //$008e SND_NOISE - uint5 noiseMode; + uint3 noiseMode; + uint1 noiseReset; + uint1 noiseUpdate; //$0090 SND_CTRL uint1 enable; uint1 noise; } r; + } channel3; + + struct Channel4 : Channel { + Channel4(); + auto run() -> void; + + struct State { + int8 data; + } s; + + struct Registers { + //$006a HYPER_CTRL + uint2 volume; + uint2 scale; + uint3 speed; + uint1 enable; + + //$006b HYPER_CHAN_CTRL + uint4 unknown; + uint1 leftEnable; + uint1 rightEnable; + } r; } channel4; - - struct Registers { - //$008f SND_WAVE_BASE - uint8 waveBase; - - //$0091 SND_OUTPUT - uint1 speakerEnable; - uint2 speakerShift; - uint1 headphoneEnable; - - //$0092 SND_VOICE_CTRL - uint2 voiceEnableLeft; - uint2 voiceEnableRight; - } r; }; extern APU apu; diff --git a/higan/ws/apu/channel.cpp b/higan/ws/apu/channel.cpp new file mode 100644 index 00000000..c0218fdc --- /dev/null +++ b/higan/ws/apu/channel.cpp @@ -0,0 +1,8 @@ +APU::Channel::Channel(uint id) : id(id) { +} + +auto APU::Channel::sample(uint5 index) -> uint4 { + auto data = iram.read((apu.r.waveBase << 6) + (id << 4) + (index >> 1)); + if(index.bit(0) == 0) return data.bits(0,3); + if(index.bit(0) == 1) return data.bits(4,7); +} diff --git a/higan/ws/apu/channel0.cpp b/higan/ws/apu/channel0.cpp new file mode 100644 index 00000000..548c1425 --- /dev/null +++ b/higan/ws/apu/channel0.cpp @@ -0,0 +1,11 @@ +APU::Channel0::Channel0() : Channel(0) { +} + +auto APU::Channel0::run() -> void { + if(--s.period == r.pitch) { + s.period = 0; + auto output = sample(s.sampleOffset++); + o.left = output * r.volumeLeft; + o.right = output * r.volumeRight; + } +} diff --git a/higan/ws/apu/channel1.cpp b/higan/ws/apu/channel1.cpp new file mode 100644 index 00000000..7c70a887 --- /dev/null +++ b/higan/ws/apu/channel1.cpp @@ -0,0 +1,15 @@ +APU::Channel1::Channel1() : Channel(1) { +} + +auto APU::Channel1::run() -> void { + if(r.voice) { + uint8 volume = r.volumeLeft << 4 | r.volumeRight << 0; + o.left = r.voiceEnableLeft ? volume : (uint8)0x80; + o.right = r.voiceEnableRight ? volume : (uint8)0x80; + } else if(--s.period == r.pitch) { + s.period = 0; + auto output = sample(s.sampleOffset++); + o.left = output * r.volumeLeft; + o.right = output * r.volumeRight; + } +} diff --git a/higan/ws/apu/channel2.cpp b/higan/ws/apu/channel2.cpp new file mode 100644 index 00000000..ec7110dd --- /dev/null +++ b/higan/ws/apu/channel2.cpp @@ -0,0 +1,18 @@ +APU::Channel2::Channel2() : Channel(2) { +} + +auto APU::Channel2::sweep() -> void { + if(r.sweep && --s.sweepCounter < 0) { + s.sweepCounter = r.sweepTime; + r.pitch += r.sweepTime; + } +} + +auto APU::Channel2::run() -> void { + if(--s.period == r.pitch) { + s.period = 0; + auto output = sample(s.sampleOffset++); + o.left = output * r.volumeLeft; + o.right = output * r.volumeRight; + } +} diff --git a/higan/ws/apu/channel3.cpp b/higan/ws/apu/channel3.cpp new file mode 100644 index 00000000..d628fad4 --- /dev/null +++ b/higan/ws/apu/channel3.cpp @@ -0,0 +1,30 @@ +APU::Channel3::Channel3() : Channel(3) { +} + +auto APU::Channel3::noiseSample() -> uint4 { + return s.noiseOutput ? 0xf : 0x0; +} + +auto APU::Channel3::run() -> void { + if(--s.period == r.pitch) { + s.period = 0; + + auto output = r.noise ? noiseSample() : sample(s.sampleOffset++); + o.left = output * r.volumeLeft; + o.right = output * r.volumeRight; + + if(r.noiseReset) { + r.noiseReset = 0; + s.noiseLFSR = 0; + s.noiseOutput = 0; + } + + if(r.noiseUpdate) { + static const int taps[8] = {14, 10, 13, 4, 8, 6, 9, 11}; + auto tap = taps[r.noiseMode]; + + s.noiseOutput = (1 ^ (s.noiseLFSR >> 7) ^ (s.noiseLFSR >> tap)) & 1; + s.noiseLFSR = s.noiseLFSR << 1 | s.noiseOutput; + } + } +} diff --git a/higan/ws/apu/channel4.cpp b/higan/ws/apu/channel4.cpp new file mode 100644 index 00000000..30366416 --- /dev/null +++ b/higan/ws/apu/channel4.cpp @@ -0,0 +1,11 @@ +APU::Channel4::Channel4() : Channel(4) { +} + +auto APU::Channel4::run() -> void { + int16 sample = s.data << 8; + if(r.scale != 3) sample >>= r.volume; + if(r.scale == 1) sample |= 0x70000 >> r.volume; + + o.left = r.leftEnable ? sample : (int16)0; + o.right = r.rightEnable ? sample : (int16)0; +} diff --git a/higan/ws/apu/io.cpp b/higan/ws/apu/io.cpp index 9f1a82ae..14b3ea0d 100644 --- a/higan/ws/apu/io.cpp +++ b/higan/ws/apu/io.cpp @@ -1,65 +1,84 @@ auto APU::portRead(uint16 addr) -> uint8 { + //SND_HYPER_CTRL + if(addr == 0x006a) return ( + channel4.r.volume << 0 + | channel4.r.scale << 2 + | channel4.r.speed << 4 + | channel4.r.enable << 7 + ); + + //SND_HYPER_CHAN_CTRL + if(addr == 0x006b) return ( + channel4.r.unknown << 0 + | channel4.r.leftEnable << 5 + | channel4.r.rightEnable << 6 + ); + + //SND_CH0_PITCH + if(addr == 0x0080) return channel0.r.pitch.bits(0, 7); + if(addr == 0x0081) return channel0.r.pitch.bits(8,11); + //SND_CH1_PITCH - if(addr == 0x0080) return channel1.r.pitch.bits(0, 7); - if(addr == 0x0081) return channel1.r.pitch.bits(8,11); + if(addr == 0x0082) return channel1.r.pitch.bits(0, 7); + if(addr == 0x0083) return channel1.r.pitch.bits(8,11); //SND_CH2_PITCH - if(addr == 0x0082) return channel2.r.pitch.bits(0, 7); - if(addr == 0x0083) return channel2.r.pitch.bits(8,11); + if(addr == 0x0084) return channel2.r.pitch.bits(0, 7); + if(addr == 0x0085) return channel2.r.pitch.bits(8,11); //SND_CH3_PITCH - if(addr == 0x0084) return channel3.r.pitch.bits(0, 7); - if(addr == 0x0085) return channel3.r.pitch.bits(8,11); + if(addr == 0x0086) return channel3.r.pitch.bits(0, 7); + if(addr == 0x0087) return channel3.r.pitch.bits(8,11); - //SND_CH4_PITCH - if(addr == 0x0086) return channel4.r.pitch.bits(0, 7); - if(addr == 0x0087) return channel4.r.pitch.bits(8,11); + //SND_CH0_VOL + if(addr == 0x0088) return ( + channel0.r.volumeRight << 0 + | channel0.r.volumeLeft << 4 + ); //SND_CH1_VOL - if(addr == 0x0088) return ( + if(addr == 0x0089) return ( channel1.r.volumeRight << 0 | channel1.r.volumeLeft << 4 ); //SND_CH2_VOL - if(addr == 0x0089) return ( + if(addr == 0x008a) return ( channel2.r.volumeRight << 0 | channel2.r.volumeLeft << 4 ); //SND_CH3_VOL - if(addr == 0x008a) return ( + if(addr == 0x008b) return ( channel3.r.volumeRight << 0 | channel3.r.volumeLeft << 4 ); - //SND_CH4_VOL - if(addr == 0x008b) return ( - channel4.r.volumeRight << 0 - | channel4.r.volumeLeft << 4 - ); - //SND_SWEEP_VALUE - if(addr == 0x008c) return channel3.r.sweepValue; + if(addr == 0x008c) return channel2.r.sweepValue; //SND_SWEEP_TIME - if(addr == 0x008d) return channel3.r.sweepTime; + if(addr == 0x008d) return channel2.r.sweepTime; //SND_NOISE - if(addr == 0x008e) return channel4.r.noiseMode; + //(noiseReset [bit 3] always reads as zero) + if(addr == 0x008e) return ( + channel3.r.noiseMode << 0 + | channel3.r.noiseUpdate << 4 + ); //SND_WAVE_BASE if(addr == 0x008f) return r.waveBase; //SND_CTRL if(addr == 0x0090) return ( - channel1.r.enable << 0 - | channel2.r.enable << 1 - | channel3.r.enable << 2 - | channel4.r.enable << 3 - | channel2.r.voice << 5 - | channel3.r.sweep << 6 - | channel4.r.noise << 7 + channel0.r.enable << 0 + | channel1.r.enable << 1 + | channel2.r.enable << 2 + | channel3.r.enable << 3 + | channel1.r.voice << 5 + | channel2.r.sweep << 6 + | channel3.r.noise << 7 ); //SND_OUTPUT @@ -71,78 +90,95 @@ auto APU::portRead(uint16 addr) -> uint8 { ); //SND_RANDOM - if(addr == 0x0092) return rand() & 0xff; - if(addr == 0x0093) return rand() & 0x7f; + if(addr == 0x0092) return channel3.s.noiseLFSR.bits(0, 7); + if(addr == 0x0093) return channel3.s.noiseLFSR.bits(8,14); //SND_VOICE_CTRL if(addr == 0x0094) return ( - r.voiceEnableRight << 0 - | r.voiceEnableLeft << 2 + channel1.r.voiceEnableRight << 0 + | channel1.r.voiceEnableLeft << 2 ); return 0x00; } auto APU::portWrite(uint16 addr, uint8 data) -> void { + //SND_HYPER_CTRL + if(addr == 0x006a) { + channel4.r.volume = data.bits(0,1); + channel4.r.scale = data.bits(2,3); + channel4.r.speed = data.bits(4,6); + channel4.r.enable = data.bit (7); + } + + //SND_HYPER_CHAN_CTRL + if(addr == 0x006b) { + channel4.r.unknown = data.bits(0,3); + channel4.r.leftEnable = data.bit (5); + channel4.r.rightEnable = data.bit (6); + } + + //SND_CH0_PITCH + if(addr == 0x0080) { channel0.r.pitch.bits(0, 7) = data.bits(0,7); return; } + if(addr == 0x0081) { channel0.r.pitch.bits(8,11) = data.bits(0,3); return; } + //SND_CH1_PITCH - if(addr == 0x0080) { channel1.r.pitch.bits(0, 7) = data.bits(0,7); return; } - if(addr == 0x0081) { channel1.r.pitch.bits(8,11) = data.bits(0,3); return; } + if(addr == 0x0082) { channel1.r.pitch.bits(0, 7) = data.bits(0,7); return; } + if(addr == 0x0083) { channel1.r.pitch.bits(8,11) = data.bits(0,3); return; } //SND_CH2_PITCH - if(addr == 0x0082) { channel2.r.pitch.bits(0, 7) = data.bits(0,7); return; } - if(addr == 0x0083) { channel2.r.pitch.bits(8,11) = data.bits(0,3); return; } + if(addr == 0x0084) { channel2.r.pitch.bits(0, 7) = data.bits(0,7); return; } + if(addr == 0x0085) { channel2.r.pitch.bits(8,11) = data.bits(0,3); return; } //SND_CH3_PITCH - if(addr == 0x0084) { channel3.r.pitch.bits(0, 7) = data.bits(0,7); return; } - if(addr == 0x0085) { channel3.r.pitch.bits(8,11) = data.bits(0,3); return; } + if(addr == 0x0086) { channel3.r.pitch.bits(0, 7) = data.bits(0,7); return; } + if(addr == 0x0087) { channel3.r.pitch.bits(8,11) = data.bits(0,3); return; } - //SND_CH4_PITCH - if(addr == 0x0086) { channel4.r.pitch.bits(0, 7) = data.bits(0,7); return; } - if(addr == 0x0087) { channel4.r.pitch.bits(8,11) = data.bits(0,3); return; } + //SND_CH0_VOL + if(addr == 0x0088) { + channel0.r.volumeRight = data.bits(0,3); + channel0.r.volumeLeft = data.bits(4,7); + return; + } //SND_CH1_VOL - if(addr == 0x0088) { + if(addr == 0x0089) { channel1.r.volumeRight = data.bits(0,3); channel1.r.volumeLeft = data.bits(4,7); return; } //SND_CH2_VOL - if(addr == 0x0089) { + if(addr == 0x008a) { channel2.r.volumeRight = data.bits(0,3); channel2.r.volumeLeft = data.bits(4,7); return; } //SND_CH3_VOL - if(addr == 0x008a) { + if(addr == 0x008b) { channel3.r.volumeRight = data.bits(0,3); channel3.r.volumeLeft = data.bits(4,7); return; } - //SND_CH4_VOL - if(addr == 0x008b) { - channel4.r.volumeRight = data.bits(0,3); - channel4.r.volumeLeft = data.bits(4,7); - return; - } - //SND_SWEEP_VALUE if(addr == 0x008c) { - channel3.r.sweepValue = data; + channel2.r.sweepValue = data; return; } //SND_SWEEP_TIME if(addr == 0x008d) { - channel3.r.sweepTime = data.bits(0,4); + channel2.r.sweepTime = data.bits(0,4); return; } //SND_NOISE if(addr == 0x008e) { - channel4.r.noiseMode = data.bits(0,4); + channel3.r.noiseMode = data.bits(0,2); + channel3.r.noiseReset = data.bit (3); + channel3.r.noiseUpdate = data.bit (4); return; } @@ -154,13 +190,13 @@ auto APU::portWrite(uint16 addr, uint8 data) -> void { //SND_CTRL if(addr == 0x0090) { - channel1.r.enable = data.bit(0); - channel2.r.enable = data.bit(1); - channel3.r.enable = data.bit(2); - channel4.r.enable = data.bit(3); - channel2.r.voice = data.bit(5); - channel3.r.sweep = data.bit(6); - channel4.r.noise = data.bit(7); + channel0.r.enable = data.bit(0); + channel1.r.enable = data.bit(1); + channel2.r.enable = data.bit(2); + channel3.r.enable = data.bit(3); + channel1.r.voice = data.bit(5); + channel2.r.sweep = data.bit(6); + channel3.r.noise = data.bit(7); return; } @@ -174,8 +210,8 @@ auto APU::portWrite(uint16 addr, uint8 data) -> void { //SND_VOICE_CTRL if(addr == 0x0094) { - r.voiceEnableRight = data.bits(0,1); - r.voiceEnableLeft = data.bits(2,3); + channel1.r.voiceEnableRight = data.bits(0,1); + channel1.r.voiceEnableLeft = data.bits(2,3); return; } } diff --git a/higan/ws/cpu/interrupt.cpp b/higan/ws/cpu/interrupt.cpp index d0cc3b61..858fd274 100644 --- a/higan/ws/cpu/interrupt.cpp +++ b/higan/ws/cpu/interrupt.cpp @@ -1,22 +1,17 @@ auto CPU::poll() -> void { - if(!state.poll || !(r.interruptStatus & r.interruptEnable)) return; - state.halt = false; - if(!V30MZ::r.f.i) return; + if(!V30MZ::r.f.i || !state.poll) return; - //find and execute first pending interrupt in order of priority (7-0) for(int n = 7; n >= 0; n--) { - if(r.interruptStatus & r.interruptEnable & (1 << n)) { - return interrupt(r.interruptBase + n); - } + if(!r.interruptEnable.bit(n)) continue; + if(!r.interruptStatus.bit(n)) continue; + return interrupt(r.interruptBase + n); } } -auto CPU::raise(Interrupt i) -> void { - auto mask = 1 << (uint)i; - r.interruptStatus |= mask; +auto CPU::raise(Interrupt irq) -> void { + r.interruptStatus.bit((uint)irq) = 1; } -auto CPU::lower(Interrupt i) -> void { - auto mask = 1 << (uint)i; - r.interruptStatus &= ~mask; +auto CPU::lower(Interrupt irq) -> void { + r.interruptStatus.bit((uint)irq) = 0; } diff --git a/higan/ws/ppu/io.cpp b/higan/ws/ppu/io.cpp index 596d1515..96f12825 100644 --- a/higan/ws/ppu/io.cpp +++ b/higan/ws/ppu/io.cpp @@ -123,6 +123,30 @@ auto PPU::portRead(uint16 addr) -> uint8 { ); } + //TMR_CTRL + if(addr == 0x00a2) return ( + r.htimerEnable << 0 + | r.htimerRepeat << 1 + | r.vtimerEnable << 2 + | r.vtimerRepeat << 3 + ); + + //HTMR_FREQ + if(addr == 0x00a4) return r.htimerFrequency.byte(0); + if(addr == 0x00a5) return r.htimerFrequency.byte(1); + + //VTMR_FREQ + if(addr == 0x00a6) return r.vtimerFrequency.byte(0); + if(addr == 0x00a7) return r.vtimerFrequency.byte(1); + + //HTMR_CTR + if(addr == 0x00a8) return r.htimerCounter.byte(0); + if(addr == 0x00a9) return r.htimerCounter.byte(1); + + //VTMR_CTR + if(addr == 0x00aa) return r.vtimerCounter.byte(0); + if(addr == 0x00ab) return r.vtimerCounter.byte(1); + return 0x00; } @@ -289,4 +313,20 @@ auto PPU::portWrite(uint16 addr, uint8 data) -> void { r.palette[addr.bits(4,1)].color[addr.bit(0) * 2 + 0] = data.bits(2,0); return; } + + //TMR_CTRL + if(addr == 0x00a2) { + r.htimerEnable = data.bit(0); + r.htimerRepeat = data.bit(1); + r.vtimerEnable = data.bit(2); + r.vtimerRepeat = data.bit(3); + } + + //HTMR_FREQ + if(addr == 0x00a4) r.htimerFrequency.byte(0) = data; + if(addr == 0x00a5) r.htimerFrequency.byte(1) = data; + + //VTMR_FREQ + if(addr == 0x00a6) r.vtimerFrequency.byte(0) = data; + if(addr == 0x00a7) r.vtimerFrequency.byte(1) = data; } diff --git a/higan/ws/ppu/ppu.cpp b/higan/ws/ppu/ppu.cpp index fbd28f5b..995d0a74 100644 --- a/higan/ws/ppu/ppu.cpp +++ b/higan/ws/ppu/ppu.cpp @@ -31,13 +31,21 @@ auto PPU::main() -> void { output[status.vclk * 224 + status.hclk] = pixel.color; step(1); } - for(uint x = 224; x < 256; x++) { - step(1); - } + step(32); } else { step(256); } scanline(); + if(r.htimerEnable && r.htimerCounter < r.htimerFrequency) { + if(++r.htimerCounter == r.htimerFrequency) { + if(r.htimerRepeat) { + r.htimerCounter = 0; + } else { + r.htimerEnable = false; + } + cpu.raise(CPU::Interrupt::HblankTimer); + } + } } auto PPU::scanline() -> void { @@ -48,6 +56,16 @@ auto PPU::scanline() -> void { } if(status.vclk == 144) { cpu.raise(CPU::Interrupt::Vblank); + if(r.vtimerEnable && r.vtimerCounter < r.vtimerFrequency) { + if(++r.vtimerCounter == r.vtimerFrequency) { + if(r.vtimerRepeat) { + r.vtimerCounter = 0; + } else { + r.vtimerEnable = false; + } + cpu.raise(CPU::Interrupt::VblankTimer); + } + } } if(status.vclk == 159) frame(); } @@ -70,6 +88,8 @@ auto PPU::power() -> void { for(uint n = 0x0000; n <= 0x0017; n++) iomap[n] = this; for(uint n = 0x001c; n <= 0x003f; n++) iomap[n] = this; + iomap[0x00a2] = this; + for(uint n = 0x00a4; n <= 0x00ab; n++) iomap[n] = this; for(auto& n : output) n = 0; @@ -110,6 +130,14 @@ auto PPU::power() -> void { r.iconSleep = 0; r.vtotal = 158; r.vblank = 155; + r.htimerEnable = 0; + r.htimerRepeat = 0; + r.vtimerEnable = 0; + r.vtimerRepeat = 0; + r.htimerFrequency = 0; + r.vtimerFrequency = 0; + r.htimerCounter = 0; + r.vtimerCounter = 0; for(auto& color : r.pool) color = 0; for(auto& p : r.palette) for(auto& color : p.color) color = 0; diff --git a/higan/ws/ppu/ppu.hpp b/higan/ws/ppu/ppu.hpp index 67804d06..b63cb23e 100644 --- a/higan/ws/ppu/ppu.hpp +++ b/higan/ws/ppu/ppu.hpp @@ -143,6 +143,24 @@ struct PPU : Thread, IO { struct Palette { uint3 color[4]; } palette[16]; + + //$00a2 TMR_CTRL + uint1 htimerEnable; + uint1 htimerRepeat; + uint1 vtimerEnable; + uint1 vtimerRepeat; + + //$00a4,$00a5 HTMR_FREQ + uint16 htimerFrequency; + + //$00a6,$00a7 VTMR_FREQ + uint16 vtimerFrequency; + + //$00a8,$00a9 HTMR_CTR + uint16 htimerCounter; + + //$00aa,$00ab VTMR_CTR + uint16 vtimerCounter; } r; };