mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +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 "../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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// b0–b3: 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_;
|
||||||
|
@ -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>";
|
||||||
|
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…
x
Reference in New Issue
Block a user