mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-24 12:30:17 +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:
parent
b99ba2bc02
commit
2d892da225
@ -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::ControlLines>(GI::AY38910::BC2 | GI::AY38910::BC1));
|
||||
*cycle.value = ay_.get_data_output();
|
||||
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(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::ControlLines>(GI::AY38910::BDIR | GI::AY38910::BC2 | ((port == 0xa0) ? GI::AY38910::BC1 : 0)));
|
||||
ay_.set_data_input(*cycle.value);
|
||||
ay_.set_control_lines(static_cast<GI::AY38910::ControlLines>(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<ConcreteMachine, false, false> z80_;
|
||||
@ -292,7 +343,9 @@ class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
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_;
|
||||
AYPortHandler ay_port_handler_;
|
||||
|
@ -840,6 +840,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -2023,6 +2024,7 @@
|
||||
children = (
|
||||
4B8EF6071FE5AF830076CCDD /* LowpassSpeaker.hpp */,
|
||||
4B698D1A1FE768A100696C91 /* SampleSource.hpp */,
|
||||
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
|
63
Outputs/Speaker/Implementation/CompoundSource.hpp
Normal file
63
Outputs/Speaker/Implementation/CompoundSource.hpp
Normal 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 */
|
Loading…
Reference in New Issue
Block a user