From 3c5a8d9ff3ab8b5d88a43d809ce276cbde5cef51 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 3 Mar 2018 13:08:33 -0500 Subject: [PATCH] Adds Super Game Module support for the ColecoVision. --- Components/AY38910/AY38910.cpp | 6 +- Components/AY38910/AY38910.hpp | 1 + Components/KonamiSCC/KonamiSCC.cpp | 4 +- Components/KonamiSCC/KonamiSCC.hpp | 2 +- Components/SN76489/SN76489.cpp | 4 ++ Components/SN76489/SN76489.hpp | 1 + Machines/ColecoVision/ColecoVision.cpp | 66 +++++++++++++++++-- .../Speaker/Implementation/CompoundSource.hpp | 2 +- .../Speaker/Implementation/SampleSource.hpp | 2 +- 9 files changed, 75 insertions(+), 13 deletions(-) diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 67e74b9a1..62a7612fe 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -108,7 +108,6 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { } evaluate_output_volume(); - printf("%d ", output_volume_); for(int ic = 0; ic < 8 && c < number_of_samples; ic++) { target[c] = output_volume_; @@ -157,6 +156,11 @@ void AY38910::evaluate_output_volume() { ); } +bool AY38910::is_zero_level() { + // Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero. + return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0; +} + // MARK: - Register manipulation void AY38910::select_register(uint8_t r) { diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 93db05d26..1e86607ed 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -85,6 +85,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource { // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption void get_samples(std::size_t number_of_samples, int16_t *target); + bool is_zero_level(); private: Concurrency::DeferringAsyncTaskQueue &task_queue_; diff --git a/Components/KonamiSCC/KonamiSCC.cpp b/Components/KonamiSCC/KonamiSCC.cpp index acb0f7731..b841ecf90 100644 --- a/Components/KonamiSCC/KonamiSCC.cpp +++ b/Components/KonamiSCC/KonamiSCC.cpp @@ -15,12 +15,12 @@ using namespace Konami; SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} -bool SCC::is_silent() { +bool SCC::is_zero_level() { return !(channel_enable_ & 0x1f); } void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { - if(is_silent()) { + if(is_zero_level()) { std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); return; } diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp index 31c49843b..d7abc7eb0 100644 --- a/Components/KonamiSCC/KonamiSCC.hpp +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -27,7 +27,7 @@ class SCC: public ::Outputs::Speaker::SampleSource { SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); /// As per ::SampleSource; provides a broadphase test for silence. - bool is_silent(); + bool is_zero_level(); /// As per ::SampleSource; provides audio output. void get_samples(std::size_t number_of_samples, std::int16_t *target); diff --git a/Components/SN76489/SN76489.cpp b/Components/SN76489/SN76489.cpp index a321b3898..1a5d09e4a 100644 --- a/Components/SN76489/SN76489.cpp +++ b/Components/SN76489/SN76489.cpp @@ -82,6 +82,10 @@ void SN76489::set_register(uint8_t value) { }); } +bool SN76489::is_zero_level() { + return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf; +} + void SN76489::evaluate_output_volume() { output_volume_ = static_cast( channels_[0].level * volumes_[channels_[0].volume] + diff --git a/Components/SN76489/SN76489.hpp b/Components/SN76489/SN76489.hpp index 12f2b2d3f..f87112ab0 100644 --- a/Components/SN76489/SN76489.hpp +++ b/Components/SN76489/SN76489.hpp @@ -30,6 +30,7 @@ class SN76489: public Outputs::Speaker::SampleSource { // As per SampleSource. void get_samples(std::size_t number_of_samples, std::int16_t *target); + bool is_zero_level(); private: int master_divider_ = 0; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 1f92e8fd3..a951a0eb6 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -11,6 +11,7 @@ #include "../../Processors/Z80/Z80.hpp" #include "../../Components/9918/9918.hpp" +#include "../../Components/AY38910/AY38910.hpp" // For the Super Game Module. #include "../../Components/SN76489/SN76489.hpp" #include "../ConfigurationTarget.hpp" @@ -19,10 +20,11 @@ #include "../../ClockReceiver/ForceInline.hpp" +#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" namespace { -const int sn76489_divider = 4; +const int sn76489_divider = 2; } namespace Coleco { @@ -112,7 +114,9 @@ class ConcreteMachine: ConcreteMachine() : z80_(*this), sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider), - speaker_(sn76489_) { + ay_(audio_queue_), + mixer_(sn76489_, ay_), + speaker_(mixer_) { speaker_.set_input_rate(3579545.0f / static_cast(sn76489_divider)); set_clock_rate(3579545); joysticks_.emplace_back(new Joystick); @@ -193,7 +197,13 @@ class ConcreteMachine: case CPU::Z80::PartialMachineCycle::ReadOpcode: case CPU::Z80::PartialMachineCycle::Read: if(address < 0x2000) { - *cycle.value = bios_[address & 0x1fff]; + if(super_game_module_.replace_bios) { + *cycle.value = super_game_module_.ram[address]; + } else { + *cycle.value = bios_[address]; + } + } else if(super_game_module_.replace_ram && address < 0x8000) { + *cycle.value = super_game_module_.ram[address]; } else if(address >= 0x6000 && address < 0x8000) { *cycle.value = ram_[address & 1023]; } else if(address >= 0x8000 && address <= cartridge_address_limit_) { @@ -207,7 +217,11 @@ class ConcreteMachine: break; case CPU::Z80::PartialMachineCycle::Write: - if(address >= 0x6000 && address < 0x8000) { + if(super_game_module_.replace_bios && address < 0x2000) { + super_game_module_.ram[address] = *cycle.value; + } else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) { + super_game_module_.ram[address] = *cycle.value; + } else if(address >= 0x6000 && address < 0x8000) { ram_[address & 1023] = *cycle.value; } else if(is_megacart_ && address >= 0xffc0) { page_megacart(address); @@ -234,7 +248,15 @@ class ConcreteMachine: } break; default: - *cycle.value = 0xff; + switch(address&0xff) { + default: *cycle.value = 0xff; break; + case 0x52: + // Read AY data. + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1)); + *cycle.value = ay_.get_data_output(); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + } break; } break; @@ -258,7 +280,30 @@ class ConcreteMachine: sn76489_.set_register(*cycle.value); break; - default: break; + default: + // Catch Super Game Module accesses; it decodes more thoroughly. + switch(address&0xff) { + default: break; + case 0x7f: + super_game_module_.replace_bios = !((*cycle.value)&0x2); + break; + case 0x50: + // Set AY address. + ay_.set_control_lines(GI::AY38910::BC1); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + case 0x51: + // Set AY data. + ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR)); + ay_.set_data_input(*cycle.value); + ay_.set_control_lines(GI::AY38910::ControlLines(0)); + break; + case 0x53: + super_game_module_.replace_ram = !!((*cycle.value)&0x1); + break; + } + break; } } break; @@ -301,7 +346,9 @@ class ConcreteMachine: Concurrency::DeferringAsyncTaskQueue audio_queue_; TI::SN76489 sn76489_; - Outputs::Speaker::LowpassSpeaker speaker_; + GI::AY38910::AY38910 ay_; + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::LowpassSpeaker> speaker_; std::vector bios_; std::vector cartridge_; @@ -309,6 +356,11 @@ class ConcreteMachine: uint8_t ram_[1024]; bool is_megacart_ = false; uint16_t cartridge_address_limit_ = 0; + struct { + bool replace_bios = false; + bool replace_ram = false; + uint8_t ram[32768]; + } super_game_module_; std::vector> joysticks_; bool joysticks_in_keypad_mode_ = false; diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index 99acee946..5f29e095b 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -37,7 +37,7 @@ template class CompoundSource: CompoundSource(T &source, R &...next) : source_(source), next_source_(next...) {} void get_samples(std::size_t number_of_samples, std::int16_t *target) { - if(source_.is_silent()) { + if(source_.is_zero_level()) { source_.skip_samples(number_of_samples); next_source_.get_samples(number_of_samples, target); } else { diff --git a/Outputs/Speaker/Implementation/SampleSource.hpp b/Outputs/Speaker/Implementation/SampleSource.hpp index b3a43c798..24e45544b 100644 --- a/Outputs/Speaker/Implementation/SampleSource.hpp +++ b/Outputs/Speaker/Implementation/SampleSource.hpp @@ -43,7 +43,7 @@ class SampleSource { fill the target with zeroes; @c false if a call might return all zeroes or might not. */ - bool is_silent() { + bool is_zero_level() { return false; } };