From a3abe8ebaa722419826cdeeed9a75b40d4ada083 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 2 Feb 2011 21:38:28 +1100 Subject: [PATCH] Update to v075r05 release. byuu says: Added Game Boy sound emulation, all four channels. It's really, really, really bad. Plenty of bugs, I don't even know what the fuck a high-pass filter is so that isn't there. Hermite resampling from 4MHz down to 44KHz. But it's tolerable. I don't understand what sweep is for at all, and I'm sure I have that insane recursive reload behavior wrong. This is pretty much my own design. I referenced blargg's gb snd emu, blargg's older gb apu ref, Cydrak's APU core, that lousy gbdev wiki article, the completely and utterly worthless pandocs, and received nothing but bad and wrong information that just wasted my time from But I managed to pull it off. It's also painfully slow, like 250fps on my machine slow. Countless optimizations are possible. --- bsnes/Makefile | 2 +- bsnes/gameboy/apu/apu.cpp | 72 +++++++- bsnes/gameboy/apu/apu.hpp | 15 +- bsnes/gameboy/apu/master/master.cpp | 171 +++++++++++------- bsnes/gameboy/apu/master/master.hpp | 33 ++-- bsnes/gameboy/apu/mmio/mmio.cpp | 22 --- bsnes/gameboy/apu/mmio/mmio.hpp | 2 - bsnes/gameboy/apu/noise/noise.cpp | 86 ++++++--- bsnes/gameboy/apu/noise/noise.hpp | 18 +- bsnes/gameboy/apu/serialization.cpp | 4 + bsnes/gameboy/apu/square/square.cpp | 85 --------- bsnes/gameboy/apu/square1/square1.cpp | 146 +++++++++++++++ bsnes/gameboy/apu/square1/square1.hpp | 31 ++++ bsnes/gameboy/apu/square2/square2.cpp | 97 ++++++++++ .../square.hpp => square2/square2.hpp} | 19 +- bsnes/gameboy/apu/wave/wave.cpp | 84 ++++++--- bsnes/gameboy/apu/wave/wave.hpp | 14 +- bsnes/gameboy/cpu/cpu.cpp | 6 - bsnes/gameboy/cpu/cpu.hpp | 6 - bsnes/gameboy/cpu/serialization.cpp | 6 - bsnes/gameboy/cpu/timing/timing.cpp | 32 ++-- bsnes/gameboy/cpu/timing/timing.hpp | 10 +- bsnes/gameboy/gameboy.hpp | 18 +- bsnes/gameboy/interface/interface.hpp | 2 +- bsnes/snes/chip/icd2/interface/interface.cpp | 2 +- bsnes/snes/chip/icd2/interface/interface.hpp | 2 +- bsnes/snes/snes.hpp | 2 +- bsnes/ui-gameboy/interface.cpp | 2 +- bsnes/ui-gameboy/interface.hpp | 2 +- bsnes/ui-gameboy/utility/utility.cpp | 1 - 30 files changed, 657 insertions(+), 335 deletions(-) delete mode 100755 bsnes/gameboy/apu/mmio/mmio.cpp delete mode 100755 bsnes/gameboy/apu/mmio/mmio.hpp delete mode 100755 bsnes/gameboy/apu/square/square.cpp create mode 100755 bsnes/gameboy/apu/square1/square1.cpp create mode 100755 bsnes/gameboy/apu/square1/square1.hpp create mode 100755 bsnes/gameboy/apu/square2/square2.cpp rename bsnes/gameboy/apu/{square/square.hpp => square2/square2.hpp} (58%) diff --git a/bsnes/Makefile b/bsnes/Makefile index 3b20cc03..88a4caaf 100755 --- a/bsnes/Makefile +++ b/bsnes/Makefile @@ -2,7 +2,7 @@ include nall/Makefile snes := snes gameboy := gameboy profile := compatibility -ui := ui +ui := ui-gameboy # compiler c := $(compiler) -std=gnu99 diff --git a/bsnes/gameboy/apu/apu.cpp b/bsnes/gameboy/apu/apu.cpp index b4f4cd92..2b5ce1dd 100755 --- a/bsnes/gameboy/apu/apu.cpp +++ b/bsnes/gameboy/apu/apu.cpp @@ -1,12 +1,10 @@ #include -#include - #define APU_CPP namespace GameBoy { -#include "mmio/mmio.cpp" -#include "square/square.cpp" +#include "square1/square1.cpp" +#include "square2/square2.cpp" #include "wave/wave.cpp" #include "noise/noise.cpp" #include "master/master.cpp" @@ -23,25 +21,45 @@ void APU::main() { scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); } + if((counter & 8191) == 0) { //512hz + if(sequencer == 0 || sequencer == 2 || sequencer == 4 || sequencer == 6) { //256hz + square1.clock_length(); + square2.clock_length(); + wave.clock_length(); + noise.clock_length(); + } + if(sequencer == 2 || sequencer == 6) { //128hz + square1.clock_sweep(); + } + if(sequencer == 7) { //64hz + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } + sequencer = (sequencer + 1) & 7; + } + square1.run(); square2.run(); wave.run(); noise.run(); master.run(); - system.interface->audio_sample((uint16)prng() >> 3, (uint16)prng() >> 3); + system.interface->audio_sample(master.center, master.left, master.right); + + if(++counter == 4194304) counter = 0; if(++clock >= 0) co_switch(scheduler.active_thread = cpu.thread); } } void APU::power() { - create(Main, 4 * 1024 * 1024); - + create(Main, 4194304); for(unsigned n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this; - square1.has_sweep = true; - square2.has_sweep = false; + foreach(n, mmio_data) n = 0x00; + counter = 0; + sequencer = 0; square1.power(); square2.power(); @@ -50,4 +68,40 @@ void APU::power() { master.power(); } +uint8 APU::mmio_read(uint16 addr) { + static const uint8 table[48] = { + 0x80, 0x3f, 0x00, 0xff, 0xbf, //square1 + 0xff, 0x3f, 0x00, 0xff, 0xbf, //square2 + 0x7f, 0xff, 0x9f, 0xff, 0xbf, //wave + 0xff, 0xff, 0x00, 0x00, 0xbf, //noise + 0x00, 0x00, 0x70, //master + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //unmapped + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //wave pattern + }; + + if(addr == 0xff26) { + uint8 data = master.enable << 7; + if(square1.counter && square1.length) data |= 0x01; + if(square2.counter && square2.length) data |= 0x02; + if( wave.counter && wave.length) data |= 0x04; + if( noise.counter && noise.length) data |= 0x08; + return data | table[addr - 0xff10]; + } + + if(addr >= 0xff10 && addr <= 0xff3f) return mmio_data[addr - 0xff10] | table[addr - 0xff10]; + return 0xff; +} + +void APU::mmio_write(uint16 addr, uint8 data) { + if(addr >= 0xff10 && addr <= 0xff3f) mmio_data[addr - 0xff10] = data; + + if(addr >= 0xff10 && addr <= 0xff14) return square1.write (addr - 0xff10, data); + if(addr >= 0xff15 && addr <= 0xff19) return square2.write (addr - 0xff15, data); + if(addr >= 0xff1a && addr <= 0xff1e) return wave.write (addr - 0xff1a, data); + if(addr >= 0xff1f && addr <= 0xff23) return noise.write (addr - 0xff1f, data); + if(addr >= 0xff24 && addr <= 0xff26) return master.write (addr - 0xff24, data); + if(addr >= 0xff30 && addr <= 0xff3f) return wave.write_pattern(addr - 0xff30, data); +} + } diff --git a/bsnes/gameboy/apu/apu.hpp b/bsnes/gameboy/apu/apu.hpp index 89159c44..935d92fe 100755 --- a/bsnes/gameboy/apu/apu.hpp +++ b/bsnes/gameboy/apu/apu.hpp @@ -1,12 +1,16 @@ struct APU : Processor, MMIO { - #include "mmio/mmio.hpp" - #include "square/square.hpp" + #include "square1/square1.hpp" + #include "square2/square2.hpp" #include "wave/wave.hpp" #include "noise/noise.hpp" #include "master/master.hpp" - Square square1; - Square square2; + uint8 mmio_data[48]; + unsigned counter; + unsigned sequencer; + + Square1 square1; + Square2 square2; Wave wave; Noise noise; Master master; @@ -15,6 +19,9 @@ struct APU : Processor, MMIO { void main(); void power(); + uint8 mmio_read(uint16 addr); + void mmio_write(uint16 addr, uint8 data); + void serialize(serializer&); }; diff --git a/bsnes/gameboy/apu/master/master.cpp b/bsnes/gameboy/apu/master/master.cpp index 1f565056..591d52d6 100755 --- a/bsnes/gameboy/apu/master/master.cpp +++ b/bsnes/gameboy/apu/master/master.cpp @@ -1,99 +1,132 @@ #ifdef APU_CPP void APU::Master::run() { -} + signed sample = 0, channels = 4; + sample += apu.square1.output; + sample += apu.square2.output; + sample += apu.wave.output; + sample += apu.noise.output; + sample /= channels; + if(sample < -32768) sample = -32768; + if(sample > +32767) sample = +32767; + center = sample; -uint8 APU::Master::read(unsigned r) { - if(r == 0) { - return (so2_enable << 7) | (so2_volume << 4) | (so1_enable << 3) | (so1_volume << 0); + sample = 0; + channels = 0; + if(channel1_left_enable) { sample += apu.square1.output; channels++; } + if(channel2_left_enable) { sample += apu.square2.output; channels++; } + if(channel3_left_enable) { sample += apu.wave.output; channels++; } + if(channel4_left_enable) { sample += apu.noise.output; channels++; } + if(channels) sample /= channels; + left = sample; + + switch(left_volume) { + case 0: left *= 0.125; break; + case 1: left *= 0.250; break; + case 2: left *= 0.375; break; + case 3: left *= 0.500; break; + case 4: left *= 0.625; break; + case 5: left *= 0.750; break; + case 6: left *= 0.875; break; + case 7: left *= 1.000; break; + } + if(left_enable == false) left = 0; + + sample = 0; + channels = 0; + if(channel1_right_enable) { sample += apu.square1.output; channels++; } + if(channel2_right_enable) { sample += apu.square2.output; channels++; } + if(channel3_right_enable) { sample += apu.wave.output; channels++; } + if(channel4_right_enable) { sample += apu.noise.output; channels++; } + if(channels) sample /= channels; + right = sample; + + switch(right_volume) { + case 0: right *= 0.125; break; + case 1: right *= 0.250; break; + case 2: right *= 0.375; break; + case 3: right *= 0.500; break; + case 4: right *= 0.625; break; + case 5: right *= 0.750; break; + case 6: right *= 0.875; break; + case 7: right *= 1.000; break; + } + if(right_enable == false) right = 0; + + if(left_enable == false && right_enable == false) { + left = center; + right = center; } - if(r == 1) { - return (channel4_so2_enable << 7) - | (channel3_so2_enable << 6) - | (channel2_so2_enable << 5) - | (channel1_so2_enable << 4) - | (channel4_so1_enable << 3) - | (channel3_so1_enable << 2) - | (channel2_so1_enable << 1) - | (channel1_so1_enable << 0); - } - - if(r == 2) { - return (enable << 7) - | (channel4_enable << 3) - | (channel3_enable << 2) - | (channel2_enable << 1) - | (channel1_enable << 0); + if(enable == false) { + center = 0; + left = 0; + right = 0; } } void APU::Master::write(unsigned r, uint8 data) { if(r == 0) { - so2_enable = data & 0x80; - so2_volume = (data >> 4) & 7; - so1_enable = data & 0x08; - so1_volume = (data >> 0) & 7; + left_enable = data & 0x80; + left_volume = (data >> 4) & 7; + right_enable = data & 0x08; + right_volume = (data >> 0) & 7; } if(r == 1) { - channel4_so2_enable = data & 0x80; - channel3_so2_enable = data & 0x40; - channel2_so2_enable = data & 0x20; - channel1_so2_enable = data & 0x10; - channel4_so1_enable = data & 0x08; - channel3_so1_enable = data & 0x04; - channel2_so1_enable = data & 0x02; - channel1_so1_enable = data & 0x01; + 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; } if(r == 2) { enable = data & 0x80; - channel4_enable = data & 0x08; - channel3_enable = data & 0x04; - channel2_enable = data & 0x02; - channel1_enable = data & 0x01; } } void APU::Master::power() { - so2_enable = 0; - so2_volume = 0; - so1_enable = 0; - so1_volume = 0; - channel4_so2_enable = 0; - channel3_so2_enable = 0; - channel2_so2_enable = 0; - channel1_so2_enable = 0; - channel4_so1_enable = 0; - channel3_so1_enable = 0; - channel2_so1_enable = 0; - channel1_so1_enable = 0; + left_enable = 0; + left_volume = 0; + right_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; enable = 0; - channel4_enable = 0; - channel3_enable = 0; - channel2_enable = 0; - channel1_enable = 0; + + center = 0; + left = 0; + right = 0; } void APU::Master::serialize(serializer &s) { - s.integer(so2_enable); - s.integer(so2_volume); - s.integer(so1_enable); - s.integer(so1_volume); - s.integer(channel4_so2_enable); - s.integer(channel3_so2_enable); - s.integer(channel2_so2_enable); - s.integer(channel1_so2_enable); - s.integer(channel4_so1_enable); - s.integer(channel3_so1_enable); - s.integer(channel2_so1_enable); - s.integer(channel1_so1_enable); + s.integer(left_enable); + s.integer(left_volume); + s.integer(right_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(enable); - s.integer(channel4_enable); - s.integer(channel3_enable); - s.integer(channel2_enable); - s.integer(channel1_enable); + + s.integer(center); + s.integer(left); + s.integer(right); } #endif diff --git a/bsnes/gameboy/apu/master/master.hpp b/bsnes/gameboy/apu/master/master.hpp index 86982de4..fb079891 100755 --- a/bsnes/gameboy/apu/master/master.hpp +++ b/bsnes/gameboy/apu/master/master.hpp @@ -1,24 +1,23 @@ struct Master { - bool so2_enable; - unsigned so2_volume; - bool so1_enable; - unsigned so1_volume; - bool channel4_so2_enable; - bool channel3_so2_enable; - bool channel2_so2_enable; - bool channel1_so2_enable; - bool channel4_so1_enable; - bool channel3_so1_enable; - bool channel2_so1_enable; - bool channel1_so1_enable; + bool left_enable; + unsigned left_volume; + bool right_enable; + unsigned 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 enable; - bool channel4_enable; - bool channel3_enable; - bool channel2_enable; - bool channel1_enable; + + int16 center; + int16 left; + int16 right; void run(); - uint8 read(unsigned r); void write(unsigned r, uint8 data); void power(); void serialize(serializer&); diff --git a/bsnes/gameboy/apu/mmio/mmio.cpp b/bsnes/gameboy/apu/mmio/mmio.cpp deleted file mode 100755 index 06cc23e8..00000000 --- a/bsnes/gameboy/apu/mmio/mmio.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifdef APU_CPP - -uint8 APU::mmio_read(uint16 addr) { - if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr - 0xff10); - if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr - 0xff15); - if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr - 0xff1a); - if(addr >= 0xff1f && addr <= 0xff23) return noise.read(addr - 0xff1f); - if(addr >= 0xff24 && addr <= 0xff26) return master.read(addr - 0xff24); - if(addr >= 0xff30 && addr <= 0xff3f) return wave.read_pattern(addr - 0xff30); - return 0x00; -} - -void APU::mmio_write(uint16 addr, uint8 data) { - if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr - 0xff10, data); - if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr - 0xff15, data); - if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr - 0xff1a, data); - if(addr >= 0xff1f && addr <= 0xff23) return noise.write(addr - 0xff1f, data); - if(addr >= 0xff24 && addr <= 0xff26) return master.write(addr - 0xff24, data); - if(addr >= 0xff30 && addr <= 0xff3f) return wave.write_pattern(addr - 0xff30, data); -} - -#endif diff --git a/bsnes/gameboy/apu/mmio/mmio.hpp b/bsnes/gameboy/apu/mmio/mmio.hpp deleted file mode 100755 index 92b8f614..00000000 --- a/bsnes/gameboy/apu/mmio/mmio.hpp +++ /dev/null @@ -1,2 +0,0 @@ -uint8 mmio_read(uint16 addr); -void mmio_write(uint16 addr, uint8 data); diff --git a/bsnes/gameboy/apu/noise/noise.cpp b/bsnes/gameboy/apu/noise/noise.cpp index 1d65a694..31855373 100755 --- a/bsnes/gameboy/apu/noise/noise.cpp +++ b/bsnes/gameboy/apu/noise/noise.cpp @@ -1,66 +1,102 @@ #ifdef APU_CPP void APU::Noise::run() { + if(period && --period == 0) { + period = divisor << frequency; + if(frequency < 14) { + bool bit = (lfsr ^ (lfsr >> 1)) & 1; + lfsr = (lfsr >> 1) ^ (bit << 14); + if(narrow_lfsr) lfsr |= (bit << 6); + } + } + + uint4 sample = (lfsr & 1) ? -volume : volume; + if(counter && length == 0) sample = 0; + + output = (sample * 4369) - 32768; } -uint8 APU::Noise::read(unsigned r) { - if(r == 2) { - return (envelope_volume << 4) | (envelope_direction << 3) | (envelope_period << 0); - } +void APU::Noise::clock_length() { + if(counter && length) length--; +} - if(r == 3) { - return (period << 4) | (step << 3) | (divisor); +void APU::Noise::clock_envelope() { + if(envelope_period && --envelope_period == 0) { + envelope_period = envelope_frequency; + if(envelope_direction == 0 && volume > 0) volume--; + if(envelope_direction == 1 && volume < 15) volume++; } - - if(r == 4) { - return (counter << 6); - } - - return 0x00; } void APU::Noise::write(unsigned r, uint8 data) { if(r == 1) { - length = data & 0x3f; + initial_length = 64 - (data & 0x3f); + + length = initial_length; } if(r == 2) { envelope_volume = data >> 4; envelope_direction = data & 0x08; - envelope_period = data & 0x07; + envelope_frequency = data & 0x07; } if(r == 3) { - period = data >> 4; - step = data & 0x08; - divisor = data & 0x07; + frequency = data >> 4; + narrow_lfsr = data & 0x08; + divisor = (data & 0x07) << 4; + if(divisor == 0) divisor = 8; + + period = divisor << frequency; } if(r == 4) { + bool initialize = data & 0x80; counter = data & 0x40; + + if(initialize) { + lfsr = ~0U; + length = initial_length; + envelope_period = envelope_frequency; + volume = envelope_volume; + } } } void APU::Noise::power() { - length = 0; + initial_length = 0; envelope_volume = 0; envelope_direction = 0; - envelope_period = 0; - period = 0; - step = 0; + envelope_frequency = 0; + frequency = 0; + narrow_lfsr = 0; divisor = 0; counter = 0; + + output = 0; + length = 0; + envelope_period = 0; + volume = 0; + period = 0; + lfsr = 0; } void APU::Noise::serialize(serializer &s) { - s.integer(length); + s.integer(initial_length); s.integer(envelope_volume); s.integer(envelope_direction); - s.integer(envelope_period); - s.integer(period); - s.integer(step); + s.integer(envelope_frequency); + s.integer(frequency); + s.integer(narrow_lfsr); s.integer(divisor); s.integer(counter); + + s.integer(output); + s.integer(length); + s.integer(envelope_period); + s.integer(volume); + s.integer(period); + s.integer(lfsr); } #endif diff --git a/bsnes/gameboy/apu/noise/noise.hpp b/bsnes/gameboy/apu/noise/noise.hpp index a48c2787..2e6c7167 100755 --- a/bsnes/gameboy/apu/noise/noise.hpp +++ b/bsnes/gameboy/apu/noise/noise.hpp @@ -1,15 +1,23 @@ struct Noise { - unsigned length; + unsigned initial_length; unsigned envelope_volume; bool envelope_direction; - unsigned envelope_period; - unsigned period; - bool step; + unsigned envelope_frequency; + unsigned frequency; + bool narrow_lfsr; unsigned divisor; bool counter; + int16 output; + unsigned length; + unsigned envelope_period; + unsigned volume; + unsigned period; + uint15 lfsr; + void run(); - uint8 read(unsigned r); + void clock_length(); + void clock_envelope(); void write(unsigned r, uint8 data); void power(); void serialize(serializer&); diff --git a/bsnes/gameboy/apu/serialization.cpp b/bsnes/gameboy/apu/serialization.cpp index 74c61d03..e781d5a4 100755 --- a/bsnes/gameboy/apu/serialization.cpp +++ b/bsnes/gameboy/apu/serialization.cpp @@ -1,6 +1,10 @@ #ifdef APU_CPP void APU::serialize(serializer &s) { + s.array(mmio_data); + s.integer(counter); + s.integer(sequencer); + square1.serialize(s); square2.serialize(s); wave.serialize(s); diff --git a/bsnes/gameboy/apu/square/square.cpp b/bsnes/gameboy/apu/square/square.cpp deleted file mode 100755 index dbc228d9..00000000 --- a/bsnes/gameboy/apu/square/square.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifdef APU_CPP - -void APU::Square::run() { -} - -uint8 APU::Square::read(unsigned r) { - if(r == 0) { - return (sweep_period << 4) | (sweep_direction << 3) | (sweep_shift << 0); - } - - if(r == 1) { - return (duty << 6); - } - - if(r == 2) { - return (envelope_volume << 4) | (envelope_direction << 3) | (envelope_period << 0); - } - - if(r == 4) { - return (counter << 6); - } - - return 0x00; -} - -void APU::Square::write(unsigned r, uint8 data) { - if(r == 0) { - sweep_period = (data >> 4) & 7; - sweep_direction = data & 0x08; - sweep_shift = data & 0x07; - } - - if(r == 1) { - duty = data >> 6; - length = data & 0x3f; - } - - if(r == 2) { - envelope_volume = data >> 4; - envelope_direction = data & 0x08; - envelope_period = data & 0x07; - } - - if(r == 3) { - frequency = (frequency & 0x0700) | data; - } - - if(r == 4) { - bool initialize = data & 0x80; - counter = data & 0x40; - frequency = ((data & 7) << 8) | (frequency & 0x00ff); - } -} - -void APU::Square::power() { - sweep_period = 0; - sweep_direction = 0; - sweep_shift = 0; - - duty = 0; - length = 0; - - envelope_volume = 0; - envelope_direction = 0; - envelope_period = 0; - - frequency = 0; - - counter = 0; -} - -void APU::Square::serialize(serializer &s) { - s.integer(sweep_period); - s.integer(sweep_direction); - s.integer(sweep_shift); - s.integer(duty); - s.integer(length); - s.integer(envelope_volume); - s.integer(envelope_direction); - s.integer(envelope_period); - s.integer(frequency); - s.integer(counter); -} - -#endif diff --git a/bsnes/gameboy/apu/square1/square1.cpp b/bsnes/gameboy/apu/square1/square1.cpp new file mode 100755 index 00000000..6f31ded4 --- /dev/null +++ b/bsnes/gameboy/apu/square1/square1.cpp @@ -0,0 +1,146 @@ +#ifdef APU_CPP + +void APU::Square1::run() { + if(period && --period == 0) { + period = 4 * (2048 - frequency); + phase = (phase + 1) & 7; + 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; //------__ + } + } + + uint4 sample = (duty_output ? volume : 0); + if(counter && length == 0) sample = 0; + + output = (sample * 4369) - 32768; +} + +void APU::Square1::sweep() { + if(enable == false) return; + + signed offset = frequency_shadow >> sweep_shift; + if(sweep_direction) offset = -offset; + frequency_shadow += offset; + + if(frequency_shadow < 0) { + frequency_shadow = 0; + } else if(frequency_shadow > 2047) { + frequency_shadow = 2048; + enable = false; + } + + if(frequency_shadow <= 2047 && sweep_shift) { + frequency = frequency_shadow; + period = 4 * (2048 - frequency); + } +} + +void APU::Square1::clock_length() { + if(counter && length) length--; +} + +void APU::Square1::clock_sweep() { + if(sweep_frequency && sweep_period && --sweep_period == 0) { + sweep_period = sweep_frequency; + sweep(); + } +} + +void APU::Square1::clock_envelope() { + if(envelope_period && --envelope_period == 0) { + envelope_period = envelope_frequency; + if(envelope_direction == 0 && volume > 0) volume--; + if(envelope_direction == 1 && volume < 15) volume++; + } +} + +void APU::Square1::write(unsigned r, uint8 data) { + if(r == 0) { + sweep_frequency = (data >> 4) & 7; + sweep_direction = data & 0x08; + sweep_shift = data & 0x07; + } + + if(r == 1) { + duty = data >> 6; + length = data & 0x3f; + } + + if(r == 2) { + envelope_volume = data >> 4; + envelope_direction = data & 0x08; + envelope_frequency = data & 0x07; + } + + if(r == 3) { + frequency = (frequency & 0x0700) | data; + } + + if(r == 4) { + bool initialize = data & 0x80; + counter = data & 0x40; + frequency = ((data & 7) << 8) | (frequency & 0x00ff); + + if(initialize) { + envelope_period = envelope_frequency; + volume = envelope_volume; + frequency_shadow = frequency; + sweep_period = sweep_frequency; + enable = sweep_period || sweep_shift; + if(sweep_shift) sweep(); + } + } + + period = 4 * (2048 - frequency); +} + +void APU::Square1::power() { + sweep_frequency = 0; + sweep_direction = 0; + sweep_shift = 0; + duty = 0; + length = 0; + envelope_volume = 0; + envelope_direction = 0; + envelope_frequency = 0; + frequency = 0; + counter = 0; + + output = 0; + duty_output = 0; + phase = 0; + period = 0; + envelope_period = 0; + sweep_period = 0; + frequency_shadow = 0; + enable = 0; + volume = 0; +} + +void APU::Square1::serialize(serializer &s) { + s.integer(sweep_frequency); + s.integer(sweep_direction); + s.integer(sweep_shift); + s.integer(duty); + s.integer(length); + s.integer(envelope_volume); + s.integer(envelope_direction); + s.integer(envelope_frequency); + s.integer(frequency); + s.integer(counter); + + s.integer(output); + s.integer(duty_output); + s.integer(phase); + s.integer(period); + s.integer(envelope_period); + s.integer(sweep_period); + s.integer(frequency_shadow); + s.integer(enable); + s.integer(volume); +} + +#endif diff --git a/bsnes/gameboy/apu/square1/square1.hpp b/bsnes/gameboy/apu/square1/square1.hpp new file mode 100755 index 00000000..eb45df49 --- /dev/null +++ b/bsnes/gameboy/apu/square1/square1.hpp @@ -0,0 +1,31 @@ +struct Square1 { + unsigned sweep_frequency; + unsigned sweep_direction; + unsigned sweep_shift; + unsigned duty; + unsigned length; + unsigned envelope_volume; + unsigned envelope_direction; + unsigned envelope_frequency; + unsigned frequency; + unsigned counter; + + int16 output; + bool duty_output; + unsigned phase; + unsigned period; + unsigned envelope_period; + unsigned sweep_period; + signed frequency_shadow; + bool enable; + unsigned volume; + + void run(); + void sweep(); + void clock_length(); + void clock_sweep(); + void clock_envelope(); + void write(unsigned r, uint8 data); + void power(); + void serialize(serializer&); +}; diff --git a/bsnes/gameboy/apu/square2/square2.cpp b/bsnes/gameboy/apu/square2/square2.cpp new file mode 100755 index 00000000..1191ce48 --- /dev/null +++ b/bsnes/gameboy/apu/square2/square2.cpp @@ -0,0 +1,97 @@ +#ifdef APU_CPP + +void APU::Square2::run() { + if(period && --period == 0) { + period = 4 * (2048 - frequency); + phase = (phase + 1) & 7; + 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; //------__ + } + } + + uint4 sample = (duty_output ? volume : 0); + if(counter && length == 0) sample = 0; + + output = (sample * 4369) - 32768; +} + +void APU::Square2::clock_length() { + if(counter && length) length--; +} + +void APU::Square2::clock_envelope() { + if(envelope_period && --envelope_period == 0) { + envelope_period = envelope_frequency; + if(envelope_direction == 0 && volume > 0) volume--; + if(envelope_direction == 1 && volume < 15) volume++; + } +} + +void APU::Square2::write(unsigned r, uint8 data) { + if(r == 1) { + duty = data >> 6; + length = data & 0x3f; + } + + if(r == 2) { + envelope_volume = data >> 4; + envelope_direction = data & 0x08; + envelope_frequency = data & 0x07; + } + + if(r == 3) { + frequency = (frequency & 0x0700) | data; + } + + if(r == 4) { + bool initialize = data & 0x80; + counter = data & 0x40; + frequency = ((data & 7) << 8) | (frequency & 0x00ff); + + if(initialize) { + envelope_period = envelope_frequency; + volume = envelope_volume; + } + } + + period = 4 * (2048 - frequency); +} + +void APU::Square2::power() { + duty = 0; + length = 0; + envelope_volume = 0; + envelope_direction = 0; + envelope_frequency = 0; + frequency = 0; + counter = 0; + + output = 0; + duty_output = 0; + phase = 0; + period = 0; + envelope_period = 0; + volume = 0; +} + +void APU::Square2::serialize(serializer &s) { + s.integer(duty); + s.integer(length); + s.integer(envelope_volume); + s.integer(envelope_direction); + s.integer(envelope_frequency); + s.integer(frequency); + s.integer(counter); + + s.integer(output); + s.integer(duty_output); + s.integer(phase); + s.integer(period); + s.integer(envelope_period); + s.integer(volume); +} + +#endif diff --git a/bsnes/gameboy/apu/square/square.hpp b/bsnes/gameboy/apu/square2/square2.hpp similarity index 58% rename from bsnes/gameboy/apu/square/square.hpp rename to bsnes/gameboy/apu/square2/square2.hpp index 21068242..6f4a9e1a 100755 --- a/bsnes/gameboy/apu/square/square.hpp +++ b/bsnes/gameboy/apu/square2/square2.hpp @@ -1,19 +1,22 @@ -struct Square { - bool has_sweep; - - unsigned sweep_period; - unsigned sweep_direction; - unsigned sweep_shift; +struct Square2 { unsigned duty; unsigned length; unsigned envelope_volume; unsigned envelope_direction; - unsigned envelope_period; + unsigned envelope_frequency; unsigned frequency; unsigned counter; + int16 output; + bool duty_output; + unsigned phase; + unsigned period; + unsigned envelope_period; + unsigned volume; + void run(); - uint8 read(unsigned r); + void clock_length(); + void clock_envelope(); void write(unsigned r, uint8 data); void power(); void serialize(serializer&); diff --git a/bsnes/gameboy/apu/wave/wave.cpp b/bsnes/gameboy/apu/wave/wave.cpp index 2af1bf2d..449c99df 100755 --- a/bsnes/gameboy/apu/wave/wave.cpp +++ b/bsnes/gameboy/apu/wave/wave.cpp @@ -1,31 +1,40 @@ #ifdef APU_CPP void APU::Wave::run() { + if(period && --period == 0) { + period = 2 * (2048 - frequency); + pattern_offset = (pattern_offset + 1) & 31; + pattern_sample = pattern[pattern_offset]; + } + + uint4 sample = pattern_sample; + if(counter && length == 0) sample = 0; + if(enable == false) sample = 0; + + output = (sample * 4369) - 32768; + switch(volume) { + case 0: output *= 0.00; break; + case 1: output *= 1.00; break; + case 2: output *= 0.50; break; + case 3: output *= 0.25; break; + } } -uint8 APU::Wave::read(unsigned r) { - if(r == 0) { - return (off << 7); - } - - if(r == 2) { - return (volume << 5); - } - - if(r == 4) { - return (counter << 6); - } - - return 0x00; +void APU::Wave::clock_length() { + if(counter && length) length--; } void APU::Wave::write(unsigned r, uint8 data) { if(r == 0) { - off = data & 0x80; + dac_enable = data & 0x80; + + if(dac_enable == false) enable = false; } if(r == 1) { - length = data; + initial_length = 256 - data; + + length = initial_length; } if(r == 2) { @@ -40,36 +49,55 @@ void APU::Wave::write(unsigned r, uint8 data) { bool initialize = data & 0x80; counter = data & 0x40; frequency = ((data & 7) << 8) | (frequency & 0x00ff); - } -} -uint8 APU::Wave::read_pattern(unsigned p) { - p <<= 1; - return (pattern[p + 0] << 4) | (pattern[p + 1] << 0); + if(initialize && dac_enable) { + enable = true; + pattern_offset = 0; + length = initial_length; + } + } + + period = 2 * (2048 - frequency); } void APU::Wave::write_pattern(unsigned p, uint8 data) { p <<= 1; - pattern[p + 0] = data >> 4; - pattern[p + 1] = data >> 0; + pattern[p + 0] = (data >> 4) & 15; + pattern[p + 1] = (data >> 0) & 15; } void APU::Wave::power() { - off = 0; - length = 0; + dac_enable = 0; + initial_length = 0; volume = 0; frequency = 0; counter = 0; - foreach(n, pattern) n = 0; + + random_cyclic r; + foreach(n, pattern) n = r() & 15; + + output = 0; + enable = 0; + length = 0; + period = 0; + pattern_offset = 0; + pattern_sample = 0; } void APU::Wave::serialize(serializer &s) { - s.integer(off); - s.integer(length); + s.integer(dac_enable); + s.integer(initial_length); s.integer(volume); s.integer(frequency); s.integer(counter); s.array(pattern); + + s.integer(output); + s.integer(enable); + s.integer(length); + s.integer(period); + s.integer(pattern_offset); + s.integer(pattern_sample); } #endif diff --git a/bsnes/gameboy/apu/wave/wave.hpp b/bsnes/gameboy/apu/wave/wave.hpp index 73b87c46..d4f19ad1 100755 --- a/bsnes/gameboy/apu/wave/wave.hpp +++ b/bsnes/gameboy/apu/wave/wave.hpp @@ -1,15 +1,21 @@ struct Wave { - bool off; - unsigned length; + bool dac_enable; + unsigned initial_length; unsigned volume; unsigned frequency; bool counter; uint8 pattern[32]; + int16 output; + bool enable; + unsigned length; + unsigned period; + unsigned pattern_offset; + unsigned pattern_sample; + void run(); - uint8 read(unsigned r); + void clock_length(); void write(unsigned r, uint8 data); - uint8 read_pattern(unsigned p); void write_pattern(unsigned p, uint8 data); void power(); void serialize(serializer&); diff --git a/bsnes/gameboy/cpu/cpu.cpp b/bsnes/gameboy/cpu/cpu.cpp index b195da7c..0b30ba1f 100755 --- a/bsnes/gameboy/cpu/cpu.cpp +++ b/bsnes/gameboy/cpu/cpu.cpp @@ -114,13 +114,7 @@ void CPU::power() { status.clock = 0; status.halt = false; status.stop = false; - status.ime = 0; - status.timer0 = 0; - status.timer1 = 0; - status.timer2 = 0; - status.timer3 = 0; - status.timer4 = 0; status.p15 = 0; status.p14 = 0; diff --git a/bsnes/gameboy/cpu/cpu.hpp b/bsnes/gameboy/cpu/cpu.hpp index f06bb551..cbfe193a 100755 --- a/bsnes/gameboy/cpu/cpu.hpp +++ b/bsnes/gameboy/cpu/cpu.hpp @@ -17,13 +17,7 @@ struct CPU : Processor, MMIO { unsigned clock; bool halt; bool stop; - bool ime; - unsigned timer0; - unsigned timer1; - unsigned timer2; - unsigned timer3; - unsigned timer4; //$ff00 JOYP bool p15; diff --git a/bsnes/gameboy/cpu/serialization.cpp b/bsnes/gameboy/cpu/serialization.cpp index 990c1836..f122f97f 100755 --- a/bsnes/gameboy/cpu/serialization.cpp +++ b/bsnes/gameboy/cpu/serialization.cpp @@ -21,13 +21,7 @@ void CPU::serialize(serializer &s) { s.integer(status.clock); s.integer(status.halt); s.integer(status.stop); - s.integer(status.ime); - s.integer(status.timer0); - s.integer(status.timer1); - s.integer(status.timer2); - s.integer(status.timer3); - s.integer(status.timer4); s.integer(status.p15); s.integer(status.p14); diff --git a/bsnes/gameboy/cpu/timing/timing.cpp b/bsnes/gameboy/cpu/timing/timing.cpp index fca4d897..215e8977 100755 --- a/bsnes/gameboy/cpu/timing/timing.cpp +++ b/bsnes/gameboy/cpu/timing/timing.cpp @@ -22,8 +22,12 @@ void CPU::add_clocks(unsigned clocks) { cartridge.mbc3.second(); } - status.timer0 += clocks; - if(status.timer0 >= 16) timer_stage0(); + //4194304 / N(hz) - 1 = mask + if((status.clock & 15) == 0) timer_262144hz(); + if((status.clock & 63) == 0) timer_65536hz(); + if((status.clock & 255) == 0) timer_16384hz(); + if((status.clock & 511) == 0) timer_8192hz(); + if((status.clock & 1023) == 0) timer_4096hz(); lcd.clock -= clocks; if(lcd.clock <= 0) co_switch(scheduler.active_thread = lcd.thread); @@ -32,31 +36,25 @@ void CPU::add_clocks(unsigned clocks) { if(apu.clock <= 0) co_switch(scheduler.active_thread = apu.thread); } -void CPU::timer_stage0() { //262144hz +void CPU::timer_262144hz() { if(status.timer_enable && status.timer_clock == 1) { if(++status.tima == 0) { status.tima = status.tma; interrupt_raise(Interrupt::Timer); } } - - status.timer0 -= 16; - if(++status.timer1 >= 4) timer_stage1(); } -void CPU::timer_stage1() { // 65536hz +void CPU::timer_65536hz() { if(status.timer_enable && status.timer_clock == 2) { if(++status.tima == 0) { status.tima = status.tma; interrupt_raise(Interrupt::Timer); } } - - status.timer1 -= 4; - if(++status.timer2 >= 4) timer_stage2(); } -void CPU::timer_stage2() { // 16384hz +void CPU::timer_16384hz() { if(status.timer_enable && status.timer_clock == 3) { if(++status.tima == 0) { status.tima = status.tma; @@ -65,32 +63,24 @@ void CPU::timer_stage2() { // 16384hz } status.div++; - - status.timer2 -= 4; - if(++status.timer3 >= 2) timer_stage3(); } -void CPU::timer_stage3() { // 8192hz +void CPU::timer_8192hz() { if(status.serial_transfer && status.serial_clock) { if(--status.serial_bits == 0) { status.serial_transfer = 0; interrupt_raise(Interrupt::Serial); } } - - status.timer3 -= 2; - if(++status.timer4 >= 2) timer_stage4(); } -void CPU::timer_stage4() { // 4096hz +void CPU::timer_4096hz() { if(status.timer_enable && status.timer_clock == 0) { if(++status.tima == 0) { status.tima = status.tma; interrupt_raise(Interrupt::Timer); } } - - status.timer4 -= 2; } #endif diff --git a/bsnes/gameboy/cpu/timing/timing.hpp b/bsnes/gameboy/cpu/timing/timing.hpp index 1635f60c..9fead0ea 100755 --- a/bsnes/gameboy/cpu/timing/timing.hpp +++ b/bsnes/gameboy/cpu/timing/timing.hpp @@ -1,9 +1,9 @@ void add_clocks(unsigned clocks); -void timer_stage0(); -void timer_stage1(); -void timer_stage2(); -void timer_stage3(); -void timer_stage4(); +void timer_262144hz(); +void timer_65536hz(); +void timer_16384hz(); +void timer_8192hz(); +void timer_4096hz(); //opcode.cpp void op_io(); diff --git a/bsnes/gameboy/gameboy.hpp b/bsnes/gameboy/gameboy.hpp index 1658ac1f..9a47a5ba 100755 --- a/bsnes/gameboy/gameboy.hpp +++ b/bsnes/gameboy/gameboy.hpp @@ -5,7 +5,7 @@ namespace GameBoy { namespace Info { static const char Name[] = "bgameboy"; - static const char Version[] = "000.15"; + static const char Version[] = "000.16"; static unsigned SerializerVersion = 1; } } @@ -15,22 +15,30 @@ namespace GameBoy { #include #include #include +#include #include #include #include +#include using namespace nall; namespace GameBoy { - typedef int8_t int8; - typedef int16_t int16; - typedef int32_t int32; - typedef int64_t int64; + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; typedef uint8_t uint8; typedef uint16_t uint16; typedef uint32_t uint32; typedef uint64_t uint64; + typedef int_t< 4> int4; + typedef int_t<15> int15; + + typedef uint_t< 4> uint4; + typedef uint_t<15> uint15; + template alwaysinline bool within(uint16 addr) { static const uint16 mask = ~(hi ^ lo); diff --git a/bsnes/gameboy/interface/interface.hpp b/bsnes/gameboy/interface/interface.hpp index 372cbd7f..982fd989 100755 --- a/bsnes/gameboy/interface/interface.hpp +++ b/bsnes/gameboy/interface/interface.hpp @@ -3,7 +3,7 @@ public: virtual void joyp_write(bool p15, bool p14) {} virtual void video_refresh(const uint8_t *data) {} - virtual void audio_sample(signed left, signed right) {} + virtual void audio_sample(int16_t center, int16_t left, int16_t right) {} virtual void input_poll() {} virtual bool input_poll(unsigned id) {} diff --git a/bsnes/snes/chip/icd2/interface/interface.cpp b/bsnes/snes/chip/icd2/interface/interface.cpp index b654ca5d..2dcac5b3 100755 --- a/bsnes/snes/chip/icd2/interface/interface.cpp +++ b/bsnes/snes/chip/icd2/interface/interface.cpp @@ -73,7 +73,7 @@ void ICD2::joyp_write(bool p15, bool p14) { void ICD2::video_refresh(const uint8_t *data) { } -void ICD2::audio_sample(signed left, signed right) { +void ICD2::audio_sample(int16_t center, int16_t left, int16_t right) { audio.coprocessor_sample(left, right); } diff --git a/bsnes/snes/chip/icd2/interface/interface.hpp b/bsnes/snes/chip/icd2/interface/interface.hpp index 2227706f..2f88ca91 100755 --- a/bsnes/snes/chip/icd2/interface/interface.hpp +++ b/bsnes/snes/chip/icd2/interface/interface.hpp @@ -1,6 +1,6 @@ void joyp_write(bool p15, bool p14); void video_refresh(const uint8_t *data); -void audio_sample(signed left, signed right); +void audio_sample(int16_t center, int16_t left, int16_t right); void input_poll(); bool input_poll(unsigned id); diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index 8f76fb7d..f3498ee9 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -1,7 +1,7 @@ namespace SNES { namespace Info { static const char Name[] = "bsnes"; - static const char Version[] = "075.04"; + static const char Version[] = "075.05"; static const unsigned SerializerVersion = 18; } } diff --git a/bsnes/ui-gameboy/interface.cpp b/bsnes/ui-gameboy/interface.cpp index 05c557f6..15a1e0d5 100755 --- a/bsnes/ui-gameboy/interface.cpp +++ b/bsnes/ui-gameboy/interface.cpp @@ -28,7 +28,7 @@ void Interface::video_refresh(const uint8_t *data) { } } -void Interface::audio_sample(signed left, signed right) { +void Interface::audio_sample(int16_t center, int16_t left, int16_t right) { audio.sample(left, right); } diff --git a/bsnes/ui-gameboy/interface.hpp b/bsnes/ui-gameboy/interface.hpp index d6910bb9..efeeca5f 100755 --- a/bsnes/ui-gameboy/interface.hpp +++ b/bsnes/ui-gameboy/interface.hpp @@ -2,7 +2,7 @@ struct Interface : public GameBoy::Interface { int16_t inputState[Scancode::Limit]; void video_refresh(const uint8_t *data); - void audio_sample(signed left, signed right); + void audio_sample(int16_t center, int16_t left, int16_t right); void input_poll(); bool input_poll(unsigned id); diff --git a/bsnes/ui-gameboy/utility/utility.cpp b/bsnes/ui-gameboy/utility/utility.cpp index 02ade276..183ffa73 100755 --- a/bsnes/ui-gameboy/utility/utility.cpp +++ b/bsnes/ui-gameboy/utility/utility.cpp @@ -10,7 +10,6 @@ void Utility::loadCartridge(const char *filename) { fp.close(); cartridge.basename = nall::basename(filename); - print(cartridge.basename, "\n"); GameBoyCartridge info(data, size); GameBoy::cartridge.load(info.xml, data, size);