diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index d61d19c81..cb69a7899 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -21,10 +21,46 @@ #include "../ConfigurationTarget.hpp" #include "../KeyboardMachine.hpp" +#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" namespace MSX { +/*! + Provides a sample source that can programmatically be set to one of two values. +*/ +class AudioToggle: public Outputs::Speaker::SampleSource { + public: + AudioToggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + + void get_samples(std::size_t number_of_samples, std::int16_t *target) { + for(std::size_t sample = 0; sample < number_of_samples; ++sample) { + target[sample] = level_; + } + } + + void skip_samples(const std::size_t number_of_samples) {} + + void set_output(bool enabled) { + if(is_enabled_ == enabled) return; + is_enabled_ = enabled; + audio_queue_.defer([=] { + level_ = enabled ? 4096 : 0; + }); + } + + bool get_output() { + return is_enabled_; + } + + private: + bool is_enabled_ = false; + int16_t level_ = 0; + Concurrency::DeferringAsyncTaskQueue &audio_queue_; +}; + struct AYPortHandler: public GI::AY38910::PortHandler { void set_port_output(bool port_b, uint8_t value) { // printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value); @@ -47,8 +83,10 @@ class ConcreteMachine: z80_(*this), i8255_(i8255_port_handler_), ay_(audio_queue_), - speaker_(ay_), - i8255_port_handler_(*this) { + audio_toggle_(audio_queue_), + mixer_(ay_, audio_toggle_), + speaker_(mixer_), + i8255_port_handler_(*this, audio_toggle_) { set_clock_rate(3579545); std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); clear_all_keys(); @@ -96,7 +134,6 @@ class ConcreteMachine: } void page_memory(uint8_t value) { -// printf("Page: %02x\n", value); for(size_t c = 0; c < 4; ++c) { read_pointers_[c] = memory_slots_[value & 3].read_pointers[c]; write_pointers_[c] = memory_slots_[value & 3].write_pointers[c]; @@ -133,7 +170,7 @@ class ConcreteMachine: break; case 0xa2: - speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + update_audio(); ay_.set_control_lines(static_cast(GI::AY38910::BC2 | GI::AY38910::BC1)); *cycle.value = ay_.get_data_output(); ay_.set_control_lines(static_cast(0)); @@ -161,7 +198,7 @@ class ConcreteMachine: break; case 0xa0: case 0xa1: - speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + update_audio(); ay_.set_control_lines(static_cast(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); ay_.set_data_input(*cycle.value); ay_.set_control_lines(static_cast(0)); @@ -195,7 +232,7 @@ class ConcreteMachine: void flush() { vdp_->run_for(time_since_vdp_update_.flush()); - speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + update_audio(); audio_queue_.perform(); } @@ -256,21 +293,34 @@ class ConcreteMachine: } private: + void update_audio() { + speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + } + class i8255PortHandler: public Intel::i8255::PortHandler { public: - i8255PortHandler(ConcreteMachine &machine) : machine_(machine) {} + i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle) : + machine_(machine), audio_toggle_(audio_toggle) {} void set_value(int port, uint8_t value) { switch(port) { case 0: machine_.page_memory(value); break; - case 2: + case 2: { // TODO: - // b7 keyboard click // b6 caps lock LED // b5 audio output // b4 cassette motor relay + + // b7: keyboard click + bool new_audio_level = !!(value & 0x80); + if(audio_toggle_.get_output() != new_audio_level) { + machine_.update_audio(); + audio_toggle_.set_output(new_audio_level); + } + + // b0–b3: keyboard line machine_.set_keyboard_line(value & 0xf); - break; + } break; default: printf("What what what what?\n"); break; } } @@ -284,6 +334,7 @@ class ConcreteMachine: private: ConcreteMachine &machine_; + AudioToggle &audio_toggle_; }; CPU::Z80::Processor z80_; @@ -292,7 +343,9 @@ class ConcreteMachine: Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; - Outputs::Speaker::LowpassSpeaker speaker_; + AudioToggle audio_toggle_; + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::LowpassSpeaker> speaker_; i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index e396e0cb1..13aeec8d4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -840,6 +840,7 @@ 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; 4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = ""; }; 4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = ""; }; + 4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = ""; }; 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = ""; }; 4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = ""; }; 4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = ""; }; @@ -2023,6 +2024,7 @@ children = ( 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */, 4B698D1A1FE768A100696C91 /* SampleSource.hpp */, + 4B770A961FE9EE770026DC70 /* CompoundSource.hpp */, ); path = Implementation; sourceTree = ""; diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp new file mode 100644 index 000000000..eb53d95a3 --- /dev/null +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -0,0 +1,63 @@ +// +// CompoundSource.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/12/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef CompoundSource_h +#define CompoundSource_h + +#include "SampleSource.hpp" + +#include + +namespace Outputs { +namespace Speaker { + +/*! + A CompoundSource adds together the sound generated by multiple individual SampleSources. + It's an instance of template metaprogramming; this is the base case. +*/ +template class CompoundSource: public Outputs::Speaker::SampleSource { + public: + void get_samples(std::size_t number_of_samples, std::int16_t *target) { + std::memset(target, 0, sizeof(std::int16_t) * number_of_samples); + } +}; + +/*! + 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 { + 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]; + } + } + + void skip_samples(const std::size_t number_of_samples) { + source_.skip_samples(number_of_samples); + next_source_.skip_samples(number_of_samples); + } + + private: + T &source_; + CompoundSource next_source_; +}; + +} +} + +#endif /* CompoundSource_h */