mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-23 20:29:42 +00:00
Separates the component parts of running an audio stream: task deferral, filtering and generation.
Walking towards improving opportunities for composition.
This commit is contained in:
parent
eb6b612052
commit
ac80d10cd8
@ -12,14 +12,18 @@
|
||||
|
||||
using namespace MOS;
|
||||
|
||||
void Speaker::set_volume(uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
audio_queue_.defer([=]() {
|
||||
volume_ = volume;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_control(int channel, uint8_t value) {
|
||||
enqueue([=]() {
|
||||
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||
audio_queue_.defer([=]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
@ -101,7 +105,7 @@ static uint8_t noise_pattern[] = {
|
||||
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
|
||||
// means every second cycle, etc.
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
@ -119,7 +123,7 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
update(0, 2, shift);
|
||||
update(1, 1, shift);
|
||||
|
@ -9,22 +9,27 @@
|
||||
#ifndef _560_hpp
|
||||
#define _560_hpp
|
||||
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp"
|
||||
|
||||
namespace MOS {
|
||||
|
||||
// audio state
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
void set_volume(uint8_t volume);
|
||||
void set_control(int channel, uint8_t value);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
uint8_t control_registers_[4] = {0, 0, 0, 0};
|
||||
@ -43,7 +48,9 @@ template <class T> class MOS6560 {
|
||||
public:
|
||||
MOS6560() :
|
||||
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
|
||||
speaker_(new Speaker) {
|
||||
audio_generator_(audio_queue_),
|
||||
speaker_(audio_generator_)
|
||||
{
|
||||
crt_->set_composite_sampling_function(
|
||||
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
|
||||
"{"
|
||||
@ -59,11 +66,11 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate) {
|
||||
speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
|
||||
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
|
||||
Outputs::Speaker::Speaker *get_speaker() { return nullptr; } // speaker_;
|
||||
|
||||
enum OutputMode {
|
||||
PAL, NTSC
|
||||
@ -318,7 +325,10 @@ template <class T> class MOS6560 {
|
||||
/*!
|
||||
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
|
||||
*/
|
||||
inline void flush() { update_audio(); speaker_->flush(); }
|
||||
inline void flush() {
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes to a 6560 register.
|
||||
@ -356,13 +366,13 @@ template <class T> class MOS6560 {
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
update_audio();
|
||||
speaker_->set_control(address - 0xa, value);
|
||||
audio_generator_.set_control(address - 0xa, value);
|
||||
break;
|
||||
|
||||
case 0xe:
|
||||
update_audio();
|
||||
registers_.auxiliary_colour = colours_[value >> 4];
|
||||
speaker_->set_volume(value & 0xf);
|
||||
audio_generator_.set_volume(value & 0xf);
|
||||
break;
|
||||
|
||||
case 0xf: {
|
||||
@ -398,12 +408,15 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
|
||||
}
|
||||
|
||||
// register state
|
||||
|
@ -72,8 +72,8 @@ TMS9918::TMS9918(Personality p) {
|
||||
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> TMS9918::get_crt() {
|
||||
return crt_;
|
||||
Outputs::CRT::CRT *TMS9918::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
void TMS9918Base::test_sprite(int sprite_number) {
|
||||
|
@ -52,7 +52,7 @@ class TMS9918: public TMS9918Base {
|
||||
void set_tv_standard(TVStandard standard);
|
||||
|
||||
/*! Provides the CRT this TMS is connected to. */
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
|
@ -21,7 +21,7 @@ class TMS9918Base {
|
||||
protected:
|
||||
TMS9918Base();
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
uint8_t ram_[16384];
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using namespace GI::AY38910;
|
||||
|
||||
AY38910::AY38910() {
|
||||
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
// set up envelope lookup tables
|
||||
for(int c = 0; c < 16; c++) {
|
||||
for(int p = 0; p < 32; p++) {
|
||||
@ -63,11 +63,7 @@ AY38910::AY38910() {
|
||||
volumes_[0] = 0;
|
||||
}
|
||||
|
||||
void AY38910::set_clock_rate(double clock_rate) {
|
||||
set_input_rate(static_cast<float>(clock_rate));
|
||||
}
|
||||
|
||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
unsigned int c = 0;
|
||||
while((master_divider_&7) && c < number_of_samples) {
|
||||
target[c] = output_volume_;
|
||||
@ -169,7 +165,7 @@ void AY38910::set_register_value(uint8_t value) {
|
||||
registers_[selected_register_] = value;
|
||||
if(selected_register_ < 14) {
|
||||
int selected_register = selected_register_;
|
||||
enqueue([=] () {
|
||||
task_queue_.defer([=] () {
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register) {
|
||||
case 0: case 2: case 4:
|
||||
|
@ -9,7 +9,8 @@
|
||||
#ifndef AY_3_8910_hpp
|
||||
#define AY_3_8910_hpp
|
||||
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
@ -55,13 +56,10 @@ enum ControlLines {
|
||||
noise generator and a volume envelope generator, which also provides two bidirectional
|
||||
interface ports.
|
||||
*/
|
||||
class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910();
|
||||
|
||||
/// Sets the clock rate at which this AY38910 will be run.
|
||||
void set_clock_rate(double clock_rate);
|
||||
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
@ -86,9 +84,11 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
||||
void set_port_handler(PortHandler *);
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16];
|
||||
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
@ -88,6 +88,7 @@ void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::perform() {
|
||||
if(!deferred_tasks_) return;
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
|
||||
deferred_tasks_.reset();
|
||||
enqueue([deferred_tasks] {
|
||||
|
@ -115,14 +115,8 @@ class InterruptTimer {
|
||||
class AYDeferrer {
|
||||
public:
|
||||
/// Constructs a new AY instance and sets its clock rate.
|
||||
inline void setup_output() {
|
||||
ay_.reset(new GI::AY38910::AY38910);
|
||||
ay_->set_input_rate(1000000);
|
||||
}
|
||||
|
||||
/// Destructs the AY.
|
||||
inline void close_output() {
|
||||
ay_.reset();
|
||||
AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
|
||||
speaker_.set_input_rate(1000000);
|
||||
}
|
||||
|
||||
/// Adds @c half_cycles half cycles to the amount of time that has passed.
|
||||
@ -132,26 +126,28 @@ class AYDeferrer {
|
||||
|
||||
/// Enqueues an update-to-now into the AY's deferred queue.
|
||||
inline void update() {
|
||||
ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4)));
|
||||
speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4)));
|
||||
}
|
||||
|
||||
/// Issues a request to the AY to perform all processing up to the current time.
|
||||
inline void flush() {
|
||||
ay_->flush();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
/// @returns the speaker the AY is using for output.
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() {
|
||||
return ay_;
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
/// @returns the AY itself.
|
||||
GI::AY38910::AY38910 *ay() {
|
||||
return ay_.get();
|
||||
GI::AY38910::AY38910 &ay() {
|
||||
return ay_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay_;
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
HalfCycles cycles_since_update_;
|
||||
};
|
||||
|
||||
@ -319,8 +315,8 @@ class CRTCBusHandler {
|
||||
}
|
||||
|
||||
/// @returns the CRT.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
|
||||
return crt_;
|
||||
Outputs::CRT::CRT *get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -503,7 +499,7 @@ class CRTCBusHandler {
|
||||
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
|
||||
int cycles_into_hsync_ = 0;
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
|
||||
|
||||
uint8_t *ram_ = nullptr;
|
||||
@ -623,7 +619,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
case 0:
|
||||
// Port A is connected to the AY's data bus.
|
||||
ay_.update();
|
||||
ay_.ay()->set_data_input(value);
|
||||
ay_.ay().set_data_input(value);
|
||||
break;
|
||||
case 1:
|
||||
// Port B is an input only. So output goes nowehere.
|
||||
@ -639,7 +635,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
tape_player_.set_tape_output((value & 0x20) ? true : false);
|
||||
|
||||
// Bits 6 and 7 set BDIR and BC1 for the AY.
|
||||
ay_.ay()->set_control_lines(
|
||||
ay_.ay().set_control_lines(
|
||||
(GI::AY38910::ControlLines)(
|
||||
((value & 0x80) ? GI::AY38910::BDIR : 0) |
|
||||
((value & 0x40) ? GI::AY38910::BC1 : 0) |
|
||||
@ -652,7 +648,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
||||
/// The i8255 will call this to obtain a new input for @c port.
|
||||
uint8_t get_value(int port) {
|
||||
switch(port) {
|
||||
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
|
||||
case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY
|
||||
case 1: return
|
||||
(crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
|
||||
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
||||
@ -703,6 +699,8 @@ class ConcreteMachine:
|
||||
|
||||
tape_player_.set_sleep_observer(this);
|
||||
tape_player_is_sleeping_ = tape_player_.is_sleeping();
|
||||
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
}
|
||||
|
||||
/// The entry point for performing a partial Z80 machine cycle.
|
||||
@ -845,23 +843,20 @@ class ConcreteMachine:
|
||||
/// A CRTMachine function; indicates that outputs should be created now.
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
crtc_bus_handler_.setup_output(aspect_ratio);
|
||||
ay_.setup_output();
|
||||
ay_.ay()->set_port_handler(&key_state_);
|
||||
}
|
||||
|
||||
/// A CRTMachine function; indicates that outputs should be destroyed now.
|
||||
void close_output() override final {
|
||||
crtc_bus_handler_.close_output();
|
||||
ay_.close_output();
|
||||
}
|
||||
|
||||
/// @returns the CRT in use.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
Outputs::CRT::CRT *get_crt() override final {
|
||||
return crtc_bus_handler_.get_crt();
|
||||
}
|
||||
|
||||
/// @returns the speaker in use.
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
return ay_.get_speaker();
|
||||
}
|
||||
|
||||
|
@ -137,8 +137,7 @@ class ConcreteMachine:
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) override {
|
||||
bus_->tia_.reset(new TIA);
|
||||
bus_->speaker_.reset(new Speaker);
|
||||
bus_->speaker_->set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->tia_->get_crt()->set_delegate(this);
|
||||
}
|
||||
|
||||
@ -146,12 +145,12 @@ class ConcreteMachine:
|
||||
bus_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
|
||||
Outputs::CRT::CRT *get_crt() override {
|
||||
return bus_->tia_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override {
|
||||
return bus_->speaker_;
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &bus_->speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
@ -189,8 +188,8 @@ class ConcreteMachine:
|
||||
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
|
||||
}
|
||||
|
||||
bus_->speaker_->set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->speaker_->set_high_frequency_cut_off(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
|
||||
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
|
||||
bus_->speaker_.set_high_frequency_cutoff(static_cast<float>(clock_rate / (static_cast<double>(CPUTicksPerAudioTick) * 2.0)));
|
||||
set_clock_rate(clock_rate);
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
#include "Atari2600.hpp"
|
||||
#include "PIA.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "TIA.hpp"
|
||||
#include "TIASound.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
@ -21,6 +21,8 @@ namespace Atari2600 {
|
||||
class Bus {
|
||||
public:
|
||||
Bus() :
|
||||
tia_sound_(audio_queue_),
|
||||
speaker_(tia_sound_),
|
||||
tia_input_value_{0xff, 0xff},
|
||||
cycles_since_speaker_update_(0) {}
|
||||
|
||||
@ -30,7 +32,10 @@ class Bus {
|
||||
// the RIOT, TIA and speaker
|
||||
PIA mos6532_;
|
||||
std::shared_ptr<TIA> tia_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2];
|
||||
@ -39,7 +44,7 @@ class Bus {
|
||||
// speaker backlog accumlation counter
|
||||
Cycles cycles_since_speaker_update_;
|
||||
inline void update_audio() {
|
||||
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
|
||||
}
|
||||
|
||||
// video backlog accumulation counter
|
||||
|
@ -148,11 +148,11 @@ template<class T> class Cartridge:
|
||||
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
|
||||
|
||||
case 0x15:
|
||||
case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
|
||||
case 0x17:
|
||||
case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
|
||||
case 0x19:
|
||||
case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break;
|
||||
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,7 @@ template<class T> class Cartridge:
|
||||
void flush() {
|
||||
update_audio();
|
||||
update_video();
|
||||
speaker_->flush();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -73,11 +73,11 @@ class TIA {
|
||||
uint8_t get_collision_flags(int offset);
|
||||
void clear_collision_flags();
|
||||
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
|
||||
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
|
||||
|
||||
private:
|
||||
TIA(bool create_crt);
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::function<void(uint8_t *output_buffer)> line_end_function_;
|
||||
|
||||
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
|
||||
|
@ -6,31 +6,32 @@
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Speaker.hpp"
|
||||
#include "TIASound.hpp"
|
||||
|
||||
using namespace Atari2600;
|
||||
|
||||
Atari2600::Speaker::Speaker() :
|
||||
Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue),
|
||||
poly4_counter_{0x00f, 0x00f},
|
||||
poly5_counter_{0x01f, 0x01f},
|
||||
poly9_counter_{0x1ff, 0x1ff}
|
||||
{}
|
||||
|
||||
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
|
||||
enqueue([=]() {
|
||||
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
|
||||
audio_queue_.defer([=]() {
|
||||
volume_[channel] = volume & 0xf;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
|
||||
audio_queue_.defer([=]() {
|
||||
divider_[channel] = divider & 0x1f;
|
||||
divider_counter_[channel] = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
||||
enqueue([=]() {
|
||||
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
|
||||
audio_queue_.defer([=]() {
|
||||
control_[channel] = control & 0xf;
|
||||
});
|
||||
}
|
||||
@ -39,7 +40,7 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) {
|
||||
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
|
||||
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
|
||||
|
||||
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(unsigned int c = 0; c < number_of_samples; c++) {
|
||||
target[c] = 0;
|
||||
for(int channel = 0; channel < 2; channel++) {
|
@ -1,15 +1,16 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// TIASound.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Atari2600_Speaker_hpp
|
||||
#define Atari2600_Speaker_hpp
|
||||
#ifndef Atari2600_TIASound_hpp
|
||||
#define Atari2600_TIASound_hpp
|
||||
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Atari2600 {
|
||||
|
||||
@ -17,17 +18,19 @@ namespace Atari2600 {
|
||||
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
|
||||
const int CPUTicksPerAudioTick = 2;
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
class TIASound: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Speaker();
|
||||
TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
void set_volume(int channel, uint8_t volume);
|
||||
void set_divider(int channel, uint8_t divider);
|
||||
void set_control(int channel, uint8_t control);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
uint8_t volume_[2];
|
||||
uint8_t divider_[2];
|
||||
uint8_t control_[2];
|
@ -10,7 +10,7 @@
|
||||
#define CRTMachine_hpp
|
||||
|
||||
#include "../Outputs/CRT/CRT.hpp"
|
||||
#include "../Outputs/Speaker.hpp"
|
||||
#include "../Outputs/Speaker/Speaker.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
@ -36,10 +36,10 @@ class Machine: public ROMMachine::Machine {
|
||||
virtual void close_output() = 0;
|
||||
|
||||
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
|
||||
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0;
|
||||
virtual Outputs::CRT::CRT *get_crt() = 0;
|
||||
|
||||
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
|
||||
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
|
||||
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
|
||||
|
||||
/// Runs the machine for @c cycles.
|
||||
virtual void run_for(const Cycles cycles) = 0;
|
||||
|
@ -631,7 +631,7 @@ class ConcreteMachine:
|
||||
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
mos6560_.reset(new Vic6560());
|
||||
mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
// mos6560_->get_speaker()->set_high_frequency_cut_off(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
|
||||
// Make a guess: PAL. Without setting a clock rate the 6560 isn't fully set up so contractually something must be set.
|
||||
set_pal_6560();
|
||||
}
|
||||
@ -640,12 +640,13 @@ class ConcreteMachine:
|
||||
mos6560_ = nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
Outputs::CRT::CRT *get_crt() override final {
|
||||
return mos6560_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return mos6560_->get_speaker();
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
return nullptr;
|
||||
// return mos6560_->get_speaker();
|
||||
}
|
||||
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override final {
|
||||
|
@ -8,18 +8,19 @@
|
||||
|
||||
#include "Electron.hpp"
|
||||
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp"
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Plus3.hpp"
|
||||
#include "Speaker.hpp"
|
||||
#include "SoundGenerator.hpp"
|
||||
#include "Tape.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
@ -37,13 +38,18 @@ class ConcreteMachine:
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient {
|
||||
public:
|
||||
ConcreteMachine() : m6502_(*this) {
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
|
||||
tape_.set_delegate(this);
|
||||
set_clock_rate(2000000);
|
||||
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
}
|
||||
|
||||
void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) override final {
|
||||
@ -180,7 +186,7 @@ class ConcreteMachine:
|
||||
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||
if(new_speaker_is_enabled != speaker_is_enabled_) {
|
||||
update_audio();
|
||||
speaker_->set_is_enabled(new_speaker_is_enabled);
|
||||
sound_generator_.set_is_enabled(new_speaker_is_enabled);
|
||||
speaker_is_enabled_ = new_speaker_is_enabled;
|
||||
}
|
||||
|
||||
@ -241,7 +247,7 @@ class ConcreteMachine:
|
||||
case 0xfe06:
|
||||
if(!isReadOperation(operation)) {
|
||||
update_audio();
|
||||
speaker_->set_divider(*value);
|
||||
sound_generator_.set_divider(*value);
|
||||
tape_.set_counter(*value);
|
||||
}
|
||||
break;
|
||||
@ -372,29 +378,23 @@ class ConcreteMachine:
|
||||
forceinline void flush() {
|
||||
update_display();
|
||||
update_audio();
|
||||
speaker_->flush();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
|
||||
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
|
||||
// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So
|
||||
// run the speaker at a 2000000Hz input rate, at least for the time being.
|
||||
speaker_.reset(new Speaker);
|
||||
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
|
||||
}
|
||||
|
||||
void close_output() override final {
|
||||
video_output_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
Outputs::CRT::CRT *get_crt() override final {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return speaker_;
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
@ -469,9 +469,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
inline void update_audio() {
|
||||
if(cycles_since_audio_update_ > 0) {
|
||||
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
|
||||
}
|
||||
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider)));
|
||||
}
|
||||
|
||||
inline void signal_interrupt(Interrupt interrupt) {
|
||||
@ -531,7 +529,11 @@ class ConcreteMachine:
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
std::shared_ptr<Speaker> speaker_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
SoundGenerator sound_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<SoundGenerator> speaker_;
|
||||
|
||||
bool speaker_is_enabled_ = false;
|
||||
};
|
||||
|
||||
|
@ -6,11 +6,14 @@
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Speaker.hpp"
|
||||
#include "SoundGenerator.hpp"
|
||||
|
||||
using namespace Electron;
|
||||
|
||||
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
if(is_enabled_) {
|
||||
while(number_of_samples--) {
|
||||
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
|
||||
@ -22,18 +25,18 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
|
||||
}
|
||||
}
|
||||
|
||||
void Speaker::skip_samples(unsigned int number_of_samples) {
|
||||
void SoundGenerator::skip_samples(std::size_t number_of_samples) {
|
||||
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
|
||||
}
|
||||
|
||||
void Speaker::set_divider(uint8_t divider) {
|
||||
enqueue([=]() {
|
||||
void SoundGenerator::set_divider(uint8_t divider) {
|
||||
audio_queue_.defer([=]() {
|
||||
divider_ = divider * 32 / clock_rate_divider;
|
||||
});
|
||||
}
|
||||
|
||||
void Speaker::set_is_enabled(bool is_enabled) {
|
||||
enqueue([=]() {
|
||||
void SoundGenerator::set_is_enabled(bool is_enabled) {
|
||||
audio_queue_.defer([=]() {
|
||||
is_enabled_ = is_enabled;
|
||||
counter_ = 0;
|
||||
});
|
39
Machines/Electron/SoundGenerator.hpp
Normal file
39
Machines/Electron/SoundGenerator.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// SoundGenerator.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Electron_SoundGenerator_hpp
|
||||
#define Electron_SoundGenerator_hpp
|
||||
|
||||
#include "../../Outputs/Speaker/Implementation/FilteringSpeaker.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class SoundGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
void set_divider(uint8_t divider);
|
||||
|
||||
void set_is_enabled(bool is_enabled);
|
||||
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
|
||||
static const unsigned int clock_rate_divider = 8;
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
@ -1,35 +0,0 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Electron_Speaker_hpp
|
||||
#define Electron_Speaker_hpp
|
||||
|
||||
#include "../../Outputs/Speaker.hpp"
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||
public:
|
||||
void set_divider(uint8_t divider);
|
||||
|
||||
void set_is_enabled(bool is_enabled);
|
||||
|
||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||
void skip_samples(unsigned int number_of_samples);
|
||||
|
||||
static const unsigned int clock_rate_divider = 8;
|
||||
|
||||
private:
|
||||
unsigned int counter_ = 0;
|
||||
unsigned int divider_ = 0;
|
||||
bool is_enabled_ = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
@ -66,8 +66,8 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
|
||||
|
||||
// MARK: - CRT getter
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
Outputs::CRT::CRT *VideoOutput::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
// MARK: - Display update methods
|
||||
|
@ -31,7 +31,7 @@ class VideoOutput {
|
||||
VideoOutput(uint8_t *memory);
|
||||
|
||||
/// @returns the CRT to which output is being painted.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
/// Produces the next @c cycles of video output.
|
||||
void run_for(const Cycles cycles);
|
||||
@ -111,7 +111,7 @@ class VideoOutput {
|
||||
uint8_t *initial_output_target_ = nullptr;
|
||||
unsigned int current_output_divider_ = 1;
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
struct DrawAction {
|
||||
enum Type {
|
||||
|
@ -44,30 +44,31 @@ class ConcreteMachine:
|
||||
ConcreteMachine():
|
||||
z80_(*this),
|
||||
i8255_(i8255_port_handler_),
|
||||
ay_(audio_queue_),
|
||||
speaker_(ay_),
|
||||
i8255_port_handler_(*this) {
|
||||
set_clock_rate(3579545);
|
||||
std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
|
||||
clear_all_keys();
|
||||
|
||||
ay_.set_port_handler(&ay_port_handler_);
|
||||
speaker_.set_input_rate(3579545.0f / 2.0f);
|
||||
}
|
||||
|
||||
void setup_output(float aspect_ratio) override {
|
||||
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
|
||||
ay_.reset(new GI::AY38910::AY38910());
|
||||
ay_->set_port_handler(&ay_port_handler_);
|
||||
ay_->set_input_rate(3579545.0f / 2.0f);
|
||||
}
|
||||
|
||||
void close_output() override {
|
||||
vdp_.reset();
|
||||
ay_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
|
||||
Outputs::CRT::CRT *get_crt() override {
|
||||
return vdp_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override {
|
||||
return ay_;
|
||||
Outputs::Speaker::Speaker *get_speaker() override {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override {
|
||||
@ -130,10 +131,10 @@ class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 0xa2:
|
||||
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
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));
|
||||
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
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));
|
||||
break;
|
||||
|
||||
case 0xa8: case 0xa9:
|
||||
@ -158,10 +159,10 @@ class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 0xa0: case 0xa1:
|
||||
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
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));
|
||||
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
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));
|
||||
break;
|
||||
|
||||
case 0xa8: case 0xa9:
|
||||
@ -192,8 +193,8 @@ class ConcreteMachine:
|
||||
|
||||
void flush() {
|
||||
vdp_->run_for(time_since_vdp_update_.flush());
|
||||
ay_->run_for(time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
ay_->flush();
|
||||
speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2)));
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
// Obtains the system ROMs.
|
||||
@ -286,7 +287,10 @@ class ConcreteMachine:
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
std::unique_ptr<TI::TMS9918> vdp_;
|
||||
Intel::i8255::i8255<i8255PortHandler> i8255_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
|
||||
i8255PortHandler i8255_port_handler_;
|
||||
AYPortHandler ay_port_handler_;
|
||||
|
@ -113,7 +113,8 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
*/
|
||||
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {}
|
||||
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, GI::AY38910::AY38910 &ay8910, Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
|
||||
|
||||
/*!
|
||||
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
|
||||
@ -123,7 +124,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
if(line) {
|
||||
if(port) ay_bdir_ = value; else ay_bc1_ = value;
|
||||
update_ay();
|
||||
ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
ay8910_.set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +138,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
tape_player_.set_motor_control(value & 0x40);
|
||||
} else {
|
||||
update_ay();
|
||||
ay8910_->set_data_input(value);
|
||||
ay8910_.set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,10 +147,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
*/
|
||||
uint8_t get_port_input(MOS::MOS6522::Port port) {
|
||||
if(port) {
|
||||
uint8_t column = ay8910_->get_port_output(false) ^ 0xff;
|
||||
uint8_t column = ay8910_.get_port_output(false) ^ 0xff;
|
||||
return keyboard_.query_column(column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910_->get_data_output();
|
||||
return ay8910_.get_data_output();
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,24 +163,21 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
|
||||
/// Flushes any queued behaviour (which, specifically, means on the AY).
|
||||
void flush() {
|
||||
ay8910_->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910_->flush();
|
||||
}
|
||||
|
||||
/// Sets the AY in use by the machine the VIA that uses this port handler sits within.
|
||||
void set_ay(GI::AY38910::AY38910 *ay) {
|
||||
ay8910_ = ay;
|
||||
update_ay();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
ay8910_->run_for(cycles_since_ay_update_.flush());
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush());
|
||||
}
|
||||
bool ay_bdir_ = false;
|
||||
bool ay_bc1_ = false;
|
||||
Cycles cycles_since_ay_update_;
|
||||
|
||||
GI::AY38910::AY38910 *ay8910_ = nullptr;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
GI::AY38910::AY38910 &ay8910_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> &speaker_;
|
||||
TapePlayer &tape_player_;
|
||||
Keyboard &keyboard_;
|
||||
};
|
||||
@ -195,7 +193,9 @@ class ConcreteMachine:
|
||||
public:
|
||||
ConcreteMachine() :
|
||||
m6502_(*this),
|
||||
via_port_handler_(tape_player_, keyboard_),
|
||||
ay8910_(audio_queue_),
|
||||
speaker_(ay8910_),
|
||||
via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
|
||||
via_(via_port_handler_),
|
||||
paged_rom_(rom_) {
|
||||
set_clock_rate(1000000);
|
||||
@ -371,9 +371,7 @@ class ConcreteMachine:
|
||||
|
||||
// to satisfy CRTMachine::Machine
|
||||
void setup_output(float aspect_ratio) override final {
|
||||
ay8910_.reset(new GI::AY38910::AY38910());
|
||||
ay8910_->set_clock_rate(1000000);
|
||||
via_port_handler_.set_ay(ay8910_.get());
|
||||
speaker_.set_input_rate(1000000.0f);
|
||||
|
||||
video_output_.reset(new VideoOutput(ram_));
|
||||
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
|
||||
@ -382,16 +380,14 @@ class ConcreteMachine:
|
||||
|
||||
void close_output() override final {
|
||||
video_output_.reset();
|
||||
ay8910_.reset();
|
||||
via_port_handler_.set_ay(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
Outputs::CRT::CRT *get_crt() override final {
|
||||
return video_output_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return ay8910_;
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
@ -488,7 +484,10 @@ class ConcreteMachine:
|
||||
|
||||
// Outputs
|
||||
std::unique_ptr<VideoOutput> video_output_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910 ay8910_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
|
||||
|
||||
// Inputs
|
||||
Oric::KeyboardMapper keyboard_mapper_;
|
||||
|
@ -67,8 +67,8 @@ void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
|
||||
return crt_;
|
||||
Outputs::CRT::CRT *VideoOutput::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
||||
void VideoOutput::run_for(const Cycles cycles) {
|
||||
|
@ -17,14 +17,14 @@ namespace Oric {
|
||||
class VideoOutput {
|
||||
public:
|
||||
VideoOutput(uint8_t *memory);
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
void run_for(const Cycles cycles);
|
||||
void set_colour_rom(const std::vector<uint8_t> &rom);
|
||||
void set_output_device(Outputs::CRT::OutputDevice output_device);
|
||||
|
||||
private:
|
||||
uint8_t *ram_;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
// Counters and limits
|
||||
int counter_ = 0, frame_counter_ = 0;
|
||||
|
@ -100,6 +100,6 @@ void Video::output_byte(uint8_t byte) {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> Video::get_crt() {
|
||||
return crt_;
|
||||
Outputs::CRT::CRT *Video::get_crt() {
|
||||
return crt_.get();
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class Video {
|
||||
/// Constructs an instance of the video feed; a CRT is also created.
|
||||
Video();
|
||||
/// @returns The CRT this video feed is feeding.
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt();
|
||||
Outputs::CRT::CRT *get_crt();
|
||||
|
||||
/// Advances time by @c cycles.
|
||||
void run_for(const HalfCycles);
|
||||
@ -46,7 +46,7 @@ class Video {
|
||||
uint8_t *line_data_ = nullptr;
|
||||
uint8_t *line_data_pointer_ = nullptr;
|
||||
unsigned int cycles_since_update_ = 0;
|
||||
std::shared_ptr<Outputs::CRT::CRT> crt_;
|
||||
std::unique_ptr<Outputs::CRT::CRT> crt_;
|
||||
|
||||
void flush(bool next_sync);
|
||||
};
|
||||
|
@ -225,11 +225,11 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
video_.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
Outputs::CRT::CRT *get_crt() override final {
|
||||
return video_->get_crt();
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
Outputs::Speaker::Speaker *get_speaker() override final {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@
|
||||
4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
|
||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; };
|
||||
4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
|
||||
4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
|
||||
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; };
|
||||
4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
|
||||
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
||||
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; };
|
||||
@ -88,7 +88,7 @@
|
||||
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
||||
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; };
|
||||
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; };
|
||||
4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; };
|
||||
4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||
4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; };
|
||||
@ -251,6 +251,7 @@
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
|
||||
4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F91DCFF807003085B1 /* Oric.cpp */; };
|
||||
4B8805FE1DD02552003085B1 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805FC1DD02552003085B1 /* Tape.cpp */; };
|
||||
4B8EF6081FE5AF830076CCDD /* FilteringSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */; };
|
||||
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; };
|
||||
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; };
|
||||
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */; };
|
||||
@ -577,8 +578,8 @@
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
|
||||
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
|
||||
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */; };
|
||||
4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* TIASound.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
@ -672,7 +673,6 @@
|
||||
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
||||
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
||||
4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OricOptionsPanel.swift; sourceTree = "<group>"; };
|
||||
@ -873,6 +873,8 @@
|
||||
4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.hpp; sourceTree = "<group>"; };
|
||||
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
|
||||
4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FilteringSpeaker.cpp; sourceTree = "<group>"; };
|
||||
4B8EF6071FE5AF830076CCDD /* FilteringSpeaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FilteringSpeaker.hpp; sourceTree = "<group>"; };
|
||||
4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B8FE2181DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ElectronOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
@ -1232,6 +1234,7 @@
|
||||
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSOric.mm; sourceTree = "<group>"; };
|
||||
4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = "<group>"; };
|
||||
@ -1254,10 +1257,10 @@
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
|
||||
4BEA52611DF339D7007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = Electron/Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52621DF339D7007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = Electron/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Speaker.cpp; sourceTree = "<group>"; };
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoundGenerator.cpp; path = Electron/SoundGenerator.cpp; sourceTree = "<group>"; };
|
||||
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SoundGenerator.hpp; path = Electron/SoundGenerator.hpp; sourceTree = "<group>"; };
|
||||
4BEA52641DF3472B007E74F2 /* TIASound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIASound.cpp; sourceTree = "<group>"; };
|
||||
4BEA52651DF3472B007E74F2 /* TIASound.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIASound.hpp; sourceTree = "<group>"; };
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PIA.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0811E7E0DF800EE56B2 /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEAC0821E7E0DF800EE56B2 /* ActivisionStack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ActivisionStack.hpp; sourceTree = "<group>"; };
|
||||
@ -1360,10 +1363,10 @@
|
||||
4B0CCC411C62D0B3001CAC5F /* CRT */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBF99071C8FBA6F0075DAFB /* Internals */,
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */,
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */,
|
||||
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */,
|
||||
4BBF99071C8FBA6F0075DAFB /* Internals */,
|
||||
);
|
||||
name = CRT;
|
||||
path = ../../Outputs/CRT;
|
||||
@ -1533,13 +1536,13 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */,
|
||||
4BEA52641DF3472B007E74F2 /* Speaker.cpp */,
|
||||
4BEA52641DF3472B007E74F2 /* TIASound.cpp */,
|
||||
4BE7C9161E3D397100A5496D /* TIA.cpp */,
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */,
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */,
|
||||
4BEAC08D1E7E0E1A00EE56B2 /* Bus.hpp */,
|
||||
4BEA52671DF34909007E74F2 /* PIA.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* Speaker.hpp */,
|
||||
4BEA52651DF3472B007E74F2 /* TIASound.hpp */,
|
||||
4BE7C9171E3D397100A5496D /* TIA.hpp */,
|
||||
4BEAC0801E7E0DF800EE56B2 /* Cartridges */,
|
||||
);
|
||||
@ -1552,14 +1555,14 @@
|
||||
4B2E2D9B1C3A070400138695 /* Electron.cpp */,
|
||||
4B54C0C61F8D91E50050900F /* Keyboard.cpp */,
|
||||
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */,
|
||||
4BEA52611DF339D7007E74F2 /* Speaker.cpp */,
|
||||
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */,
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */,
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */,
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */,
|
||||
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */,
|
||||
4B54C0C71F8D91E50050900F /* Keyboard.hpp */,
|
||||
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */,
|
||||
4BEA52621DF339D7007E74F2 /* Speaker.hpp */,
|
||||
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */,
|
||||
4BEA525F1DF333D8007E74F2 /* Tape.hpp */,
|
||||
4B7913CB1DFCD80E00175A82 /* Video.hpp */,
|
||||
);
|
||||
@ -1605,7 +1608,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */,
|
||||
4BD060A41FE49D3C006E14BE /* Speaker */,
|
||||
);
|
||||
name = Outputs;
|
||||
sourceTree = "<group>";
|
||||
@ -2016,6 +2019,15 @@
|
||||
name = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8EF6051FE5AF830076CCDD /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8EF6061FE5AF830076CCDD /* FilteringSpeaker.cpp */,
|
||||
4B8EF6071FE5AF830076CCDD /* FilteringSpeaker.hpp */,
|
||||
);
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BA799961D8B65730045123D /* Atari */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2603,6 +2615,16 @@
|
||||
name = Oric;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD060A41FE49D3C006E14BE /* Speaker */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */,
|
||||
4B8EF6051FE5AF830076CCDD /* Implementation */,
|
||||
);
|
||||
name = Speaker;
|
||||
path = ../../Outputs/Speaker;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD14B121D7462810088EAD6 /* Acorn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -3250,7 +3272,7 @@
|
||||
4B055A801FAE85350060FFFF /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
|
||||
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
|
||||
4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */,
|
||||
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */,
|
||||
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055A881FAE85530060FFFF /* Disassembler6502.cpp in Sources */,
|
||||
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
|
||||
@ -3287,7 +3309,7 @@
|
||||
4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
|
||||
4B055A841FAE85450060FFFF /* Disk.cpp in Sources */,
|
||||
4B055A831FAE85410060FFFF /* Tape.cpp in Sources */,
|
||||
4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */,
|
||||
4B055AC61FAE9AEE0060FFFF /* TIASound.cpp in Sources */,
|
||||
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
|
||||
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
|
||||
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
|
||||
@ -3360,6 +3382,7 @@
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||
4B8EF6081FE5AF830076CCDD /* FilteringSpeaker.cpp in Sources */,
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
||||
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
|
||||
@ -3384,7 +3407,7 @@
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
4B1BA08A1FD4967800CB4ADA /* CSMSX.mm in Sources */,
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */,
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */,
|
||||
@ -3440,7 +3463,7 @@
|
||||
4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */,
|
||||
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,
|
||||
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
|
||||
4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BEA52661DF3472B007E74F2 /* TIASound.cpp in Sources */,
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
|
||||
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
|
||||
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
|
||||
|
@ -22,7 +22,7 @@
|
||||
#import "NSData+StdVector.h"
|
||||
|
||||
@interface CSMachine()
|
||||
- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)machineDidChangeClockRate;
|
||||
- (void)machineDidChangeClockIsUnlimited;
|
||||
@end
|
||||
@ -34,8 +34,8 @@ struct LockProtectedDelegate {
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Delegate, public LockProtectedDelegate {
|
||||
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate {
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
[machineAccessLock lock];
|
||||
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
||||
[machineAccessLock unlock];
|
||||
@ -95,7 +95,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length {
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length {
|
||||
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->crt_machine()->get_speaker();
|
||||
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
||||
if(speaker) {
|
||||
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
|
||||
}
|
||||
@ -142,9 +142,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
||||
@synchronized(self) {
|
||||
std::shared_ptr<Outputs::Speaker> speaker = _machine->crt_machine()->get_speaker();
|
||||
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
||||
if(speaker) {
|
||||
speaker->set_output_rate(sampleRate, (int)bufferSize);
|
||||
speaker->set_delegate(delegate);
|
||||
|
@ -42,10 +42,10 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat
|
||||
};
|
||||
|
||||
// This is set to a relatively large number for now.
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
|
||||
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
static const int buffer_size = 1024;
|
||||
|
||||
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) {
|
||||
std::lock_guard<std::mutex> lock_guard(audio_buffer_mutex_);
|
||||
if(audio_buffer_.size() > buffer_size) {
|
||||
audio_buffer_.erase(audio_buffer_.begin(), audio_buffer_.end() - buffer_size);
|
||||
|
@ -1,257 +0,0 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/01/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Speaker_hpp
|
||||
#define Speaker_hpp
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "../SignalProcessing/Stepper.hpp"
|
||||
#include "../SignalProcessing/FIRFilter.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Outputs {
|
||||
|
||||
/*!
|
||||
Provides the base class for an audio output source, with an input rate (the speed at which the source will
|
||||
provide data), an output rate (the speed at which the destination will receive data), a delegate to receive
|
||||
the output and some help for the output in picking an appropriate rate once the input rate is known.
|
||||
|
||||
Intended to be a parent class, allowing descendants to pick the strategy by which input samples are mapped to
|
||||
output samples.
|
||||
*/
|
||||
class Speaker {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0;
|
||||
};
|
||||
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
// return twice the cut off, if applicable
|
||||
if(high_frequency_cut_off_ > 0.0f && input_cycles_per_second_ >= high_frequency_cut_off_ * 3.0f && input_cycles_per_second_ <= high_frequency_cut_off_ * 3.0f) return high_frequency_cut_off_ * 3.0f;
|
||||
|
||||
// return exactly the input rate if possible
|
||||
if(input_cycles_per_second_ >= minimum && input_cycles_per_second_ <= maximum) return input_cycles_per_second_;
|
||||
|
||||
// if the input rate is lower, return the minimum
|
||||
if(input_cycles_per_second_ < minimum) return minimum;
|
||||
|
||||
// otherwise, return the maximum
|
||||
return maximum;
|
||||
}
|
||||
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
output_cycles_per_second_ = cycles_per_second;
|
||||
buffer_in_progress_.resize(static_cast<std::size_t>(buffer_size));
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_output_quality(int number_of_taps) {
|
||||
requested_number_of_taps_ = static_cast<std::size_t>(number_of_taps);
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void set_input_rate(float cycles_per_second) {
|
||||
input_cycles_per_second_ = cycles_per_second;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the cut-off frequency for a low-pass filter attached to the output of this speaker; optional.
|
||||
*/
|
||||
void set_high_frequency_cut_off(float high_frequency) {
|
||||
high_frequency_cut_off_ = high_frequency;
|
||||
set_needs_updated_filter_coefficients();
|
||||
}
|
||||
|
||||
Speaker() : _queue(new Concurrency::AsyncTaskQueue) {}
|
||||
|
||||
/*!
|
||||
Ensures any deferred processing occurs now.
|
||||
*/
|
||||
void flush() {
|
||||
if(!queued_functions_) return;
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions = queued_functions_;
|
||||
queued_functions_.reset();
|
||||
_queue->enqueue([queued_functions] {
|
||||
for(auto &function : *queued_functions) {
|
||||
function();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
void enqueue(std::function<void(void)> function) {
|
||||
if(!queued_functions_) queued_functions_.reset(new std::list<std::function<void(void)>>);
|
||||
queued_functions_->push_back(function);
|
||||
}
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> queued_functions_;
|
||||
|
||||
std::vector<int16_t> buffer_in_progress_;
|
||||
float high_frequency_cut_off_ = -1.0;
|
||||
std::size_t buffer_in_progress_pointer_ = 0;
|
||||
std::size_t number_of_taps_;
|
||||
std::size_t requested_number_of_taps_ = 0;
|
||||
bool coefficients_are_dirty_;
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
||||
float input_cycles_per_second_ = 0.0f;
|
||||
float output_cycles_per_second_ = 0.0f;
|
||||
|
||||
void set_needs_updated_filter_coefficients() {
|
||||
coefficients_are_dirty_ = true;
|
||||
}
|
||||
|
||||
void get_samples(unsigned int quantity, int16_t *target) {}
|
||||
void skip_samples(unsigned int quantity) {
|
||||
int16_t throwaway_samples[quantity];
|
||||
get_samples(quantity, throwaway_samples);
|
||||
}
|
||||
|
||||
std::unique_ptr<Concurrency::AsyncTaskQueue> _queue;
|
||||
};
|
||||
|
||||
/*!
|
||||
A concrete descendant of Speaker that uses a FIR filter to map from input data to output data when scaling
|
||||
and a copy-through buffer when input and output rates are the same.
|
||||
|
||||
Audio sources should use @c Filter as both a template and a parent, implementing at least
|
||||
`get_samples(unsigned int quantity, int16_t *target)` and ideally also `skip_samples(unsigned int quantity)`
|
||||
to provide source data.
|
||||
|
||||
Call `run_for` to request that the next period of input data is collected.
|
||||
*/
|
||||
template <class T> class Filter: public Speaker {
|
||||
public:
|
||||
~Filter() {
|
||||
_queue->flush();
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
enqueue([=]() {
|
||||
unsigned int cycles_remaining = static_cast<unsigned int>(cycles.as_int());
|
||||
if(coefficients_are_dirty_) update_filter_coefficients();
|
||||
|
||||
// if input and output rates exactly match, just accumulate results and pass on
|
||||
if(input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ < 0.0) {
|
||||
while(cycles_remaining) {
|
||||
unsigned int cycles_to_read = static_cast<unsigned int>(buffer_in_progress_.size() - static_cast<std::size_t>(buffer_in_progress_pointer_));
|
||||
if(cycles_to_read > cycles_remaining) cycles_to_read = cycles_remaining;
|
||||
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &buffer_in_progress_[static_cast<std::size_t>(buffer_in_progress_pointer_)]);
|
||||
buffer_in_progress_pointer_ += cycles_to_read;
|
||||
|
||||
// announce to delegate if full
|
||||
if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) {
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_);
|
||||
}
|
||||
}
|
||||
|
||||
cycles_remaining -= cycles_to_read;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if the output rate is less than the input rate, use the filter
|
||||
if(input_cycles_per_second_ > output_cycles_per_second_ || (input_cycles_per_second_ == output_cycles_per_second_ && high_frequency_cut_off_ >= 0.0)) {
|
||||
while(cycles_remaining) {
|
||||
unsigned int cycles_to_read = static_cast<unsigned int>(std::min(static_cast<std::size_t>(cycles_remaining), number_of_taps_ - input_buffer_depth_));
|
||||
static_cast<T *>(this)->get_samples(cycles_to_read, &input_buffer_[static_cast<std::size_t>(input_buffer_depth_)]);
|
||||
cycles_remaining -= cycles_to_read;
|
||||
input_buffer_depth_ += cycles_to_read;
|
||||
|
||||
if(input_buffer_depth_ == number_of_taps_) {
|
||||
buffer_in_progress_[static_cast<std::size_t>(buffer_in_progress_pointer_)] = filter_->apply(input_buffer_.data());
|
||||
buffer_in_progress_pointer_++;
|
||||
|
||||
// announce to delegate if full
|
||||
if(buffer_in_progress_pointer_ == buffer_in_progress_.size()) {
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, buffer_in_progress_);
|
||||
}
|
||||
}
|
||||
|
||||
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
uint64_t steps = stepper_->step();
|
||||
if(steps < number_of_taps_) {
|
||||
int16_t *input_buffer = input_buffer_.data();
|
||||
memmove(input_buffer, &input_buffer[steps], sizeof(int16_t) * (static_cast<std::size_t>(number_of_taps_) - static_cast<std::size_t>(steps)));
|
||||
input_buffer_depth_ -= steps;
|
||||
} else {
|
||||
if(steps > number_of_taps_)
|
||||
static_cast<T *>(this)->skip_samples(static_cast<unsigned int>(steps) - static_cast<unsigned int>(number_of_taps_));
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: input rate is less than output rate
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SignalProcessing::Stepper> stepper_;
|
||||
std::unique_ptr<SignalProcessing::FIRFilter> filter_;
|
||||
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::size_t input_buffer_depth_;
|
||||
|
||||
void update_filter_coefficients() {
|
||||
// make a guess at a good number of taps if this hasn't been provided explicitly
|
||||
if(requested_number_of_taps_) {
|
||||
number_of_taps_ = requested_number_of_taps_;
|
||||
} else {
|
||||
number_of_taps_ = static_cast<std::size_t>(ceilf((input_cycles_per_second_ + output_cycles_per_second_) / output_cycles_per_second_));
|
||||
number_of_taps_ *= 2;
|
||||
number_of_taps_ |= 1;
|
||||
}
|
||||
|
||||
coefficients_are_dirty_ = false;
|
||||
buffer_in_progress_pointer_ = 0;
|
||||
|
||||
stepper_.reset(new SignalProcessing::Stepper((uint64_t)input_cycles_per_second_, (uint64_t)output_cycles_per_second_));
|
||||
|
||||
float high_pass_frequency;
|
||||
if(high_frequency_cut_off_ > 0.0) {
|
||||
high_pass_frequency = std::min(output_cycles_per_second_ / 2.0f, high_frequency_cut_off_);
|
||||
} else {
|
||||
high_pass_frequency = output_cycles_per_second_ / 2.0f;
|
||||
}
|
||||
filter_.reset(new SignalProcessing::FIRFilter(static_cast<unsigned int>(number_of_taps_), static_cast<float>(input_cycles_per_second_), 0.0, high_pass_frequency, SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
|
||||
input_buffer_.resize(static_cast<std::size_t>(number_of_taps_));
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
9
Outputs/Speaker/Implementation/FilteringSpeaker.cpp
Normal file
9
Outputs/Speaker/Implementation/FilteringSpeaker.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// FilteringSpeaker.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/12/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "FilteringSpeaker.hpp"
|
238
Outputs/Speaker/Implementation/FilteringSpeaker.hpp
Normal file
238
Outputs/Speaker/Implementation/FilteringSpeaker.hpp
Normal file
@ -0,0 +1,238 @@
|
||||
//
|
||||
// FilteringSpeaker.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/12/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef FilteringSpeaker_h
|
||||
#define FilteringSpeaker_h
|
||||
|
||||
#include "../Speaker.hpp"
|
||||
#include "../../../SignalProcessing/Stepper.hpp"
|
||||
#include "../../../SignalProcessing/FIRFilter.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
namespace Outputs {
|
||||
namespace Speaker {
|
||||
|
||||
/*!
|
||||
A sample source is something that can provide a stream of audio.
|
||||
This optional base class provides the interface expected to be exposed
|
||||
by the template parameter to LowpassSpeaker.
|
||||
*/
|
||||
class SampleSource {
|
||||
public:
|
||||
/*!
|
||||
Should write the next @c number_of_samples to @c target.
|
||||
*/
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target) {}
|
||||
|
||||
/*!
|
||||
Should skip the next @c number_of_samples. Subclasses of this SampleSource
|
||||
need not implement this if it would no more efficient to do so than it is
|
||||
merely to call get_samples and throw the result away, as per the default
|
||||
implementation below.
|
||||
*/
|
||||
void skip_samples(const std::size_t number_of_samples) {
|
||||
int16_t scratch_pad[number_of_samples];
|
||||
get_samples(number_of_samples, scratch_pad);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
The low-pass speaker
|
||||
*/
|
||||
template <typename T> class LowpassSpeaker: public Speaker {
|
||||
public:
|
||||
LowpassSpeaker(T &sample_source) : sample_source_(sample_source) {}
|
||||
|
||||
// Implemented as per Speaker.
|
||||
float get_ideal_clock_rate_in_range(float minimum, float maximum) {
|
||||
// return twice the cut off, if applicable
|
||||
if( filter_parameters_.high_frequency_cutoff > 0.0f &&
|
||||
filter_parameters_.input_cycles_per_second >= filter_parameters_.high_frequency_cutoff * 3.0f &&
|
||||
filter_parameters_.input_cycles_per_second <= filter_parameters_.high_frequency_cutoff * 3.0f)
|
||||
return filter_parameters_.high_frequency_cutoff * 3.0f;
|
||||
|
||||
// return exactly the input rate if possible
|
||||
if( filter_parameters_.input_cycles_per_second >= minimum &&
|
||||
filter_parameters_.input_cycles_per_second <= maximum)
|
||||
return filter_parameters_.input_cycles_per_second;
|
||||
|
||||
// if the input rate is lower, return the minimum
|
||||
if(filter_parameters_.input_cycles_per_second < minimum)
|
||||
return minimum;
|
||||
|
||||
// otherwise, return the maximum
|
||||
return maximum;
|
||||
}
|
||||
|
||||
// Implemented as per Speaker.
|
||||
void set_output_rate(float cycles_per_second, int buffer_size) {
|
||||
filter_parameters_.output_cycles_per_second = cycles_per_second;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
output_buffer_.resize(static_cast<std::size_t>(buffer_size));
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the clock rate of the input audio.
|
||||
*/
|
||||
void set_input_rate(float cycles_per_second) {
|
||||
filter_parameters_.input_cycles_per_second = cycles_per_second;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
Allows a cut-off frequency to be specified for audio. Ordinarily this low-pass speaker
|
||||
will determine a cut-off based on the output audio rate. A caller can manually select
|
||||
an alternative cut-off. This allows machines with a low-pass filter on their audio output
|
||||
path to be explicit about its effect, and get that simulation for free.
|
||||
*/
|
||||
void set_high_frequency_cutoff(float high_frequency) {
|
||||
filter_parameters_.high_frequency_cutoff = high_frequency;
|
||||
filter_parameters_.parameters_are_dirty = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
Advances by the number of cycles specified, obtaining data from the sample source supplied
|
||||
at construction, filtering it and passing it on to the speaker's delegate if there is one.
|
||||
*/
|
||||
void run_for(const Cycles cycles) {
|
||||
std::size_t cycles_remaining = static_cast<size_t>(cycles.as_int());
|
||||
if(!cycles_remaining) return;
|
||||
if(filter_parameters_.parameters_are_dirty) update_filter_coefficients();
|
||||
|
||||
// If input and output rates exactly match, and no additional cut-off has been specified,
|
||||
// just accumulate results and pass on.
|
||||
if( filter_parameters_.input_cycles_per_second == filter_parameters_.output_cycles_per_second &&
|
||||
filter_parameters_.high_frequency_cutoff < 0.0) {
|
||||
while(cycles_remaining) {
|
||||
std::size_t cycles_to_read = std::min(output_buffer_.size() - output_buffer_pointer_, cycles_remaining);
|
||||
|
||||
sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_]);
|
||||
output_buffer_pointer_ += cycles_to_read;
|
||||
|
||||
// announce to delegate if full
|
||||
if(output_buffer_pointer_ == output_buffer_.size()) {
|
||||
output_buffer_pointer_ = 0;
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, output_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
cycles_remaining -= cycles_to_read;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if the output rate is less than the input rate, or an additional cut-off has been specified, use the filter.
|
||||
if( filter_parameters_.input_cycles_per_second > filter_parameters_.output_cycles_per_second ||
|
||||
(filter_parameters_.input_cycles_per_second == filter_parameters_.output_cycles_per_second && filter_parameters_.high_frequency_cutoff >= 0.0)) {
|
||||
while(cycles_remaining) {
|
||||
std::size_t cycles_to_read = std::min(cycles_remaining, input_buffer_.size() - input_buffer_depth_);
|
||||
sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]);
|
||||
cycles_remaining -= cycles_to_read;
|
||||
input_buffer_depth_ += cycles_to_read;
|
||||
|
||||
if(input_buffer_depth_ == input_buffer_.size()) {
|
||||
output_buffer_[output_buffer_pointer_] = filter_->apply(input_buffer_.data());
|
||||
output_buffer_pointer_++;
|
||||
|
||||
// Announce to delegate if full.
|
||||
if(output_buffer_pointer_ == output_buffer_.size()) {
|
||||
output_buffer_pointer_ = 0;
|
||||
if(delegate_) {
|
||||
delegate_->speaker_did_complete_samples(this, output_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
uint64_t steps = stepper_->step();
|
||||
if(steps < input_buffer_.size()) {
|
||||
int16_t *input_buffer = input_buffer_.data();
|
||||
memmove( input_buffer,
|
||||
&input_buffer[steps],
|
||||
sizeof(int16_t) * (input_buffer_.size() - steps));
|
||||
input_buffer_depth_ -= steps;
|
||||
} else {
|
||||
if(steps > input_buffer_.size())
|
||||
sample_source_.skip_samples(steps - input_buffer_.size());
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: input rate is less than output rate
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides a convenience shortcut for deferring a call to run_for.
|
||||
*/
|
||||
void run_for(Concurrency::DeferringAsyncTaskQueue &queue, const Cycles cycles) {
|
||||
queue.defer([this, cycles] {
|
||||
run_for(cycles);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
T &sample_source_;
|
||||
|
||||
std::size_t output_buffer_pointer_ = 0;
|
||||
std::size_t input_buffer_depth_ = 0;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::vector<int16_t> output_buffer_;
|
||||
|
||||
std::unique_ptr<SignalProcessing::Stepper> stepper_;
|
||||
std::unique_ptr<SignalProcessing::FIRFilter> filter_;
|
||||
|
||||
struct FilterParameters {
|
||||
float input_cycles_per_second = 0.0f;
|
||||
float output_cycles_per_second = 0.0f;
|
||||
float high_frequency_cutoff = -1.0;
|
||||
|
||||
bool parameters_are_dirty = true;
|
||||
} filter_parameters_;
|
||||
|
||||
void update_filter_coefficients() {
|
||||
// Make a guess at a good number of taps.
|
||||
std::size_t number_of_taps = static_cast<std::size_t>(
|
||||
ceilf((filter_parameters_.input_cycles_per_second + filter_parameters_.output_cycles_per_second) / filter_parameters_.output_cycles_per_second)
|
||||
);
|
||||
number_of_taps = (number_of_taps * 2) | 1;
|
||||
|
||||
filter_parameters_.parameters_are_dirty = false;
|
||||
output_buffer_pointer_ = 0;
|
||||
|
||||
stepper_.reset(new SignalProcessing::Stepper(
|
||||
static_cast<uint64_t>(filter_parameters_.input_cycles_per_second),
|
||||
static_cast<uint64_t>(filter_parameters_.output_cycles_per_second)));
|
||||
|
||||
float high_pass_frequency = filter_parameters_.output_cycles_per_second / 2.0f;
|
||||
if(filter_parameters_.high_frequency_cutoff > 0.0) {
|
||||
high_pass_frequency = std::min(filter_parameters_.output_cycles_per_second / 2.0f, high_pass_frequency);
|
||||
}
|
||||
filter_.reset(new SignalProcessing::FIRFilter(
|
||||
static_cast<unsigned int>(number_of_taps),
|
||||
filter_parameters_.input_cycles_per_second,
|
||||
0.0,
|
||||
high_pass_frequency,
|
||||
SignalProcessing::FIRFilter::DefaultAttenuation));
|
||||
|
||||
input_buffer_.resize(static_cast<std::size_t>(number_of_taps));
|
||||
input_buffer_depth_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* FilteringSpeaker_h */
|
43
Outputs/Speaker/Speaker.hpp
Normal file
43
Outputs/Speaker/Speaker.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Speaker.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/01/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Speaker_hpp
|
||||
#define Speaker_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Outputs {
|
||||
namespace Speaker {
|
||||
|
||||
/*!
|
||||
Provides a communication point for sound; machines that have a speaker provide an
|
||||
audio output.
|
||||
*/
|
||||
class Speaker {
|
||||
public:
|
||||
virtual ~Speaker() {}
|
||||
|
||||
virtual float get_ideal_clock_rate_in_range(float minimum, float maximum) = 0;
|
||||
virtual void set_output_rate(float cycles_per_second, int buffer_size) = 0;
|
||||
|
||||
struct Delegate {
|
||||
virtual void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
protected:
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Speaker_hpp */
|
Loading…
Reference in New Issue
Block a user