1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-08 10:52:58 +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:
Thomas Harte 2017-12-17 21:26:06 -05:00
parent eb6b612052
commit ac80d10cd8
37 changed files with 594 additions and 508 deletions

View File

@ -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);

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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];

View File

@ -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:

View File

@ -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};

View File

@ -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] {

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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++) {

View File

@ -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];

View File

@ -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;

View File

@ -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 {

View File

@ -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;
};

View File

@ -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;
});

View 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 */

View File

@ -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 */

View File

@ -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

View File

@ -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 {

View File

@ -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_;

View File

@ -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_;

View File

@ -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) {

View File

@ -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;

View File

@ -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();
}

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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 */,

View File

@ -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);

View File

@ -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);

View File

@ -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 */

View 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"

View 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 */

View 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 */