diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index e9f4e46df..205fd39e0 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -66,7 +66,7 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_ } void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) { - unsigned int c = 0; + std::size_t c = 0; while((master_divider_&7) && c < number_of_samples) { target[c] = output_volume_; master_divider_++; diff --git a/Components/KonamiSCC/KonamiSCC.cpp b/Components/KonamiSCC/KonamiSCC.cpp new file mode 100644 index 000000000..acb0f7731 --- /dev/null +++ b/Components/KonamiSCC/KonamiSCC.cpp @@ -0,0 +1,107 @@ +// +// KonamiSCC.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "KonamiSCC.hpp" + +#include + +using namespace Konami; + +SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) : + task_queue_(task_queue) {} + +bool SCC::is_silent() { + return !(channel_enable_ & 0x1f); +} + +void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) { + if(is_silent()) { + std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); + return; + } + + std::size_t c = 0; + while((master_divider_&7) && c < number_of_samples) { + target[c] = output_volume_; + master_divider_++; + c++; + } + + while(c < number_of_samples) { + for(int channel = 0; channel < 5; ++channel) { + if(channels_[channel].tone_counter) channels_[channel].tone_counter--; + else { + channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f; + channels_[channel].tone_counter = channels_[channel].period; + } + } + + evaluate_output_volume(); + + for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) { + target[c] = output_volume_; + c++; + master_divider_++; + } + } +} + +void SCC::write(uint16_t address, uint8_t value) { + address &= 0xff; + if(address < 0x80) ram_[address] = value; + + task_queue_.defer([=] { + // Check for a write into waveform memory. + if(address < 0x80) { + waves_[address >> 5].samples[address & 0x1f] = value; + } else switch(address) { + default: break; + + case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: { + int channel = (address - 0x80) >> 1; + channels_[channel].period = (channels_[channel].period & ~0xff) | value; + } break; + + case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: { + int channel = (address - 0x80) >> 1; + channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8); + } break; + + case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: + channels_[address - 0x8a].amplitude = value & 0xf; + break; + + case 0x8f: + channel_enable_ = value; + break; + } + + evaluate_output_volume(); + }); +} + +void SCC::evaluate_output_volume() { + output_volume_ = + static_cast( + ( + (channel_enable_ & 0x01) ? static_cast(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 + + (channel_enable_ & 0x02) ? static_cast(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 + + (channel_enable_ & 0x04) ? static_cast(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 + + (channel_enable_ & 0x08) ? static_cast(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 + + (channel_enable_ & 0x10) ? static_cast(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0 + ) + ); +} + +uint8_t SCC::read(uint16_t address) { + address &= 0xff; + if(address < 0x80) { + return ram_[address]; + } + return 0xff; +} diff --git a/Components/KonamiSCC/KonamiSCC.hpp b/Components/KonamiSCC/KonamiSCC.hpp new file mode 100644 index 000000000..31c49843b --- /dev/null +++ b/Components/KonamiSCC/KonamiSCC.hpp @@ -0,0 +1,72 @@ +// +// KonamiSCC.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef KonamiSCC_hpp +#define KonamiSCC_hpp + +#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" + +namespace Konami { + +/*! + Provides an emulation of Konami's Sound Creative Chip ('SCC'). + + The SCC is a primitive wavetable synthesis chip, offering 32-sample tables, + and five channels of output. The original SCC uses the same wave for channels + four and five, the SCC+ supports different waves for the two channels. +*/ +class SCC: public ::Outputs::Speaker::SampleSource { + public: + /// Creates a new SCC. + SCC(Concurrency::DeferringAsyncTaskQueue &task_queue); + + /// As per ::SampleSource; provides a broadphase test for silence. + bool is_silent(); + + /// As per ::SampleSource; provides audio output. + void get_samples(std::size_t number_of_samples, std::int16_t *target); + + /// Writes to the SCC. + void write(uint16_t address, uint8_t value); + + /// Reads from the SCC. + uint8_t read(uint16_t address); + + private: + Concurrency::DeferringAsyncTaskQueue &task_queue_; + + // State from here on down is accessed ony from the audio thread. + int master_divider_ = 0; + int16_t output_volume_ = 0; + + struct Channel { + int period = 0; + int amplitude = 0; + + int tone_counter = 0; + int offset = 0; + } channels_[5]; + + struct Wavetable { + std::uint8_t samples[32]; + } waves_[4]; + + std::uint8_t channel_enable_ = 0; + std::uint8_t test_register_ = 0; + + void evaluate_output_volume(); + + // This keeps a copy of wave memory that is accessed from the + // main emulation thread. + std::uint8_t ram_[128]; +}; + +} + +#endif /* KonamiSCC_hpp */ diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp index f57d33249..d640b4d70 100644 --- a/Machines/MSX/Cartridges/KonamiWithSCC.hpp +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -10,16 +10,17 @@ #define KonamiWithSCC_hpp #include "../ROMSlotHandler.hpp" +#include "../../../Components/KonamiSCC/KonamiSCC.hpp" namespace MSX { namespace Cartridge { class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { public: - KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot) : - map_(map), slot_(slot) {} + KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) : + map_(map), slot_(slot), scc_(scc) {} - void write(uint16_t address, uint8_t value) { + void write(uint16_t address, uint8_t value) override { switch(address >> 11) { default: break; case 0x0a: @@ -29,7 +30,16 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { map_.map(slot_, value * 8192, 0x6000, 0x2000); break; case 0x12: - map_.map(slot_, value * 8192, 0x8000, 0x2000); + if((value&0x3f) == 0x3f) { + scc_is_visible_ = true; + map_.unmap(slot_, 0x8000, 0x2000); + } else { + scc_is_visible_ = false; + map_.map(slot_, value * 8192, 0x8000, 0x2000); + } + break; + case 0x13: + if(scc_is_visible_) scc_.write(address, value); break; case 0x16: map_.map(slot_, value * 8192, 0xa000, 0x2000); @@ -37,9 +47,18 @@ class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { } } + uint8_t read(uint16_t address) override { + if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) { + return scc_.read(address); + } + return 0xff; + } + private: MSX::MemoryMap &map_; int slot_; + Konami::SCC &scc_; + bool scc_is_visible_ = false; }; } diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 5ba77f305..f3a493871 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -22,6 +22,7 @@ #include "../../Components/9918/9918.hpp" #include "../../Components/8255/i8255.hpp" #include "../../Components/AY38910/AY38910.hpp" +#include "../../Components/KonamiSCC/KonamiSCC.hpp" #include "../../Storage/Tape/Parsers/MSX.hpp" #include "../../Storage/Tape/Tape.hpp" @@ -120,7 +121,8 @@ class ConcreteMachine: i8255_(i8255_port_handler_), ay_(audio_queue_), audio_toggle_(audio_queue_), - mixer_(ay_, audio_toggle_), + scc_(audio_queue_), + mixer_(ay_, audio_toggle_, scc_), speaker_(mixer_), tape_player_(3579545 * 2), i8255_port_handler_(*this, audio_toggle_, tape_player_), @@ -167,7 +169,7 @@ class ConcreteMachine: break; case StaticAnalyser::MSXCartridgeType::KonamiWithSCC: // TODO: enable an SCC. - memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1)); + memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); break; case StaticAnalyser::MSXCartridgeType::ASCII8kb: memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); @@ -317,6 +319,7 @@ class ConcreteMachine: int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; if(memory_slots_[slot_hit].handler) { + update_audio(); memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); memory_slots_[slot_hit].handler->write(address, *cycle.value); } @@ -568,8 +571,9 @@ class ConcreteMachine: Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; AudioToggle audio_toggle_; - Outputs::Speaker::CompoundSource mixer_; - Outputs::Speaker::LowpassSpeaker> speaker_; + Konami::SCC scc_; + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::LowpassSpeaker> speaker_; Storage::Tape::BinaryTapePlayer tape_player_; bool use_fast_tape_ = false; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3ac05131c..b5f02182b 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -205,6 +205,8 @@ 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; + 4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; + 4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; 4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; }; @@ -797,6 +799,8 @@ 4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = ""; }; 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = ""; }; 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = ""; }; + 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = ""; }; + 4B4B1A3B200198C900A0F866 /* KonamiSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = KonamiSCC.hpp; sourceTree = ""; }; 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = ""; }; 4B4DC8201D2C2425003C5BF8 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Vic20.hpp; sourceTree = ""; }; 4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = ""; }; @@ -1801,6 +1805,15 @@ name = AY38910; sourceTree = ""; }; + 4B4B1A39200198C900A0F866 /* KonamiSCC */ = { + isa = PBXGroup; + children = ( + 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */, + 4B4B1A3B200198C900A0F866 /* KonamiSCC.hpp */, + ); + path = KonamiSCC; + sourceTree = ""; + }; 4B4DC81D1D2C2425003C5BF8 /* Commodore */ = { isa = PBXGroup; children = ( @@ -2607,11 +2620,12 @@ 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, - 4B4A762D1DB1A35C007AAE2E /* AY38910 */, 4BE845221F2FF7F400A5EA22 /* 6845 */, 4BD9137C1F3115AC009BCF85 /* 8255 */, 4BBC951F1F368D87008F4C34 /* 8272 */, 4B0E04F71FC9F2C800F43484 /* 9918 */, + 4B4A762D1DB1A35C007AAE2E /* AY38910 */, + 4B4B1A39200198C900A0F866 /* KonamiSCC */, ); name = Components; path = ../../Components; @@ -3319,6 +3333,7 @@ 4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */, 4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */, 4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */, + 4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */, 4B055AE21FAE9B6F0060FFFF /* CRTOpenGL.cpp in Sources */, 4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */, 4B055A9E1FAE85DA0060FFFF /* G64.cpp in Sources */, @@ -3445,6 +3460,7 @@ 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, + 4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */, 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.mm in Sources */, 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B4518811F75E91A00926311 /* PCMPatchedTrack.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index b254c3119..46dd90eea 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -72,6 +72,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + enableASanStackUseAfterReturn = "YES" disableMainThreadChecker = "YES" language = "" launchStyle = "0" diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index c97ce506a..3219db008 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -17,6 +17,7 @@ SOURCES += glob.glob('../../Components/8272/*.cpp') SOURCES += glob.glob('../../Components/9918/*.cpp') SOURCES += glob.glob('../../Components/9918/Implementation/*.cpp') SOURCES += glob.glob('../../Components/AY38910/*.cpp') +SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp') SOURCES += glob.glob('../../Concurrency/*.cpp') diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index eb53d95a3..99acee946 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -31,19 +31,22 @@ template class CompoundSource: public Outputs::Speaker::SampleSo A CompoundSource adds together the sound generated by multiple individual SampleSources. It's an instance of template metaprogramming; this is the recursive case. */ -template class CompoundSource: public Outputs::Speaker::SampleSource { +template class CompoundSource: + public Outputs::Speaker::SampleSource { public: CompoundSource(T &source, R &...next) : source_(source), next_source_(next...) {} void get_samples(std::size_t number_of_samples, std::int16_t *target) { - // This is an ugly solution: get whatever the next source supplies and add the local - // results to it, with the degenerate case performing a memset to supply an array of 0. - // TODO: do better. Might need some alteration of SampleSource. - int16_t next_samples[number_of_samples]; - next_source_.get_samples(number_of_samples, next_samples); - source_.get_samples(number_of_samples, target); - while(number_of_samples--) { - target[number_of_samples] += next_samples[number_of_samples]; + if(source_.is_silent()) { + source_.skip_samples(number_of_samples); + next_source_.get_samples(number_of_samples, target); + } else { + int16_t next_samples[number_of_samples]; + next_source_.get_samples(number_of_samples, next_samples); + source_.get_samples(number_of_samples, target); + while(number_of_samples--) { + target[number_of_samples] += next_samples[number_of_samples]; + } } } @@ -53,6 +56,7 @@ template class CompoundSource: public Outpu } private: + bool is_sleeping_ = false; T &source_; CompoundSource next_source_; }; diff --git a/Outputs/Speaker/Implementation/SampleSource.hpp b/Outputs/Speaker/Implementation/SampleSource.hpp index 4235f2125..b3a43c798 100644 --- a/Outputs/Speaker/Implementation/SampleSource.hpp +++ b/Outputs/Speaker/Implementation/SampleSource.hpp @@ -37,6 +37,15 @@ class SampleSource { std::int16_t scratch_pad[number_of_samples]; get_samples(number_of_samples, scratch_pad); } + + /*! + @returns @c true if it is trivially true that a call to get_samples would just + fill the target with zeroes; @c false if a call might return all zeroes or + might not. + */ + bool is_silent() { + return false; + } }; }