1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-29 00:29:34 +00:00

Introduces the MSX keyboard toggle sample source.

In support of which, it also introduces a means of sample source composition.
This commit is contained in:
Thomas Harte 2017-12-19 21:08:10 -05:00
parent b99ba2bc02
commit 2d892da225
3 changed files with 129 additions and 11 deletions

View File

@ -21,10 +21,46 @@
#include "../ConfigurationTarget.hpp" #include "../ConfigurationTarget.hpp"
#include "../KeyboardMachine.hpp" #include "../KeyboardMachine.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace MSX { 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 { struct AYPortHandler: public GI::AY38910::PortHandler {
void set_port_output(bool port_b, uint8_t value) { void set_port_output(bool port_b, uint8_t value) {
// printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value); // printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value);
@ -47,8 +83,10 @@ class ConcreteMachine:
z80_(*this), z80_(*this),
i8255_(i8255_port_handler_), i8255_(i8255_port_handler_),
ay_(audio_queue_), ay_(audio_queue_),
speaker_(ay_), audio_toggle_(audio_queue_),
i8255_port_handler_(*this) { mixer_(ay_, audio_toggle_),
speaker_(mixer_),
i8255_port_handler_(*this, audio_toggle_) {
set_clock_rate(3579545); set_clock_rate(3579545);
std::memset(unpopulated_, 0xff, sizeof(unpopulated_)); std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
clear_all_keys(); clear_all_keys();
@ -96,7 +134,6 @@ class ConcreteMachine:
} }
void page_memory(uint8_t value) { void page_memory(uint8_t value) {
// printf("Page: %02x\n", value);
for(size_t c = 0; c < 4; ++c) { for(size_t c = 0; c < 4; ++c) {
read_pointers_[c] = memory_slots_[value & 3].read_pointers[c]; read_pointers_[c] = memory_slots_[value & 3].read_pointers[c];
write_pointers_[c] = memory_slots_[value & 3].write_pointers[c]; write_pointers_[c] = memory_slots_[value & 3].write_pointers[c];
@ -133,7 +170,7 @@ class ConcreteMachine:
break; break;
case 0xa2: 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::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1)); ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output(); *cycle.value = ay_.get_data_output();
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0)); ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
@ -161,7 +198,7 @@ class ConcreteMachine:
break; break;
case 0xa0: case 0xa1: 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::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0))); ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0)));
ay_.set_data_input(*cycle.value); ay_.set_data_input(*cycle.value);
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0)); ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(0));
@ -195,7 +232,7 @@ class ConcreteMachine:
void flush() { void flush() {
vdp_->run_for(time_since_vdp_update_.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(); audio_queue_.perform();
} }
@ -256,21 +293,34 @@ class ConcreteMachine:
} }
private: private:
void update_audio() {
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
}
class i8255PortHandler: public Intel::i8255::PortHandler { class i8255PortHandler: public Intel::i8255::PortHandler {
public: 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) { void set_value(int port, uint8_t value) {
switch(port) { switch(port) {
case 0: machine_.page_memory(value); break; case 0: machine_.page_memory(value); break;
case 2: case 2: {
// TODO: // TODO:
// b7 keyboard click
// b6 caps lock LED // b6 caps lock LED
// b5 audio output // b5 audio output
// b4 cassette motor relay // 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);
}
// b0b3: keyboard line
machine_.set_keyboard_line(value & 0xf); machine_.set_keyboard_line(value & 0xf);
break; } break;
default: printf("What what what what?\n"); break; default: printf("What what what what?\n"); break;
} }
} }
@ -284,6 +334,7 @@ class ConcreteMachine:
private: private:
ConcreteMachine &machine_; ConcreteMachine &machine_;
AudioToggle &audio_toggle_;
}; };
CPU::Z80::Processor<ConcreteMachine, false, false> z80_; CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
@ -292,7 +343,9 @@ class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_; Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_; GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_; AudioToggle audio_toggle_;
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle>> speaker_;
i8255PortHandler i8255_port_handler_; i8255PortHandler i8255_port_handler_;
AYPortHandler ay_port_handler_; AYPortHandler ay_port_handler_;

View File

@ -840,6 +840,7 @@
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; }; 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; };
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; }; 4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; }; 4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; };
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; }; 4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; }; 4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = "<group>"; }; 4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = "<group>"; };
@ -2023,6 +2024,7 @@
children = ( children = (
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */, 4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */,
4B698D1A1FE768A100696C91 /* SampleSource.hpp */, 4B698D1A1FE768A100696C91 /* SampleSource.hpp */,
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */,
); );
path = Implementation; path = Implementation;
sourceTree = "<group>"; sourceTree = "<group>";

View File

@ -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 <cstring>
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 <typename... T> 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 <typename T, typename... R> class CompoundSource<T, R...>: 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<R...> next_source_;
};
}
}
#endif /* CompoundSource_h */