1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 01:31:42 +00:00

Merge branch 'master' into Amiga

This commit is contained in:
Thomas Harte 2021-11-24 08:58:03 -05:00
commit 0df8173536
20 changed files with 265 additions and 218 deletions

View File

@ -435,7 +435,7 @@ template <class BusHandler> class MOS6560 {
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {

View File

@ -158,7 +158,7 @@ class AYDeferrer {
private:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<true> ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<true>> speaker_;
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<true>> speaker_;
HalfCycles cycles_since_update_;
};

View File

@ -97,7 +97,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
// MARK: - Cards

View File

@ -1151,7 +1151,7 @@ class ConcreteMachine:
Audio::Toggle audio_toggle_;
using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>;
AudioSource mixer_;
Outputs::Speaker::LowpassSpeaker<AudioSource> speaker_;
Outputs::Speaker::PullLowpass<AudioSource> speaker_;
Cycles cycles_since_audio_update_;
Cycles cycles_until_audio_event_;
static constexpr int audio_divider = 16;

View File

@ -18,7 +18,7 @@ namespace Macintosh {
struct DeferredAudio {
Concurrency::DeferringAsyncTaskQueue queue;
Audio audio;
Outputs::Speaker::LowpassSpeaker<Audio> speaker;
Outputs::Speaker::PullLowpass<Audio> speaker;
HalfCycles time_since_update;
DeferredAudio() : audio(queue), speaker(audio) {}

View File

@ -41,7 +41,7 @@ class Bus {
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TIASound tia_sound_;
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
Outputs::Speaker::PullLowpass<TIASound> speaker_;
// joystick state
uint8_t tia_input_value_[2] = {0xff, 0xff};

View File

@ -483,7 +483,7 @@ class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<false>> speaker_;
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<false>> speaker_;
HalfCycles cycles_since_audio_update_;
JustInTimeActor<DMAController> dma_;

View File

@ -381,7 +381,7 @@ class ConcreteMachine:
TI::SN76489 sn76489_;
GI::AY38910::AY38910<false> ay_;
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
std::vector<uint8_t> bios_;
std::vector<uint8_t> cartridge_;

View File

@ -768,7 +768,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
SoundGenerator sound_generator_;
Outputs::Speaker::LowpassSpeaker<SoundGenerator> speaker_;
Outputs::Speaker::PullLowpass<SoundGenerator> speaker_;
bool speaker_is_enabled_ = false;

View File

@ -703,7 +703,7 @@ template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave::Audio dave_audio_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
Outputs::Speaker::PullLowpass<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
HalfCycles dave_delay_ = HalfCycles(2);

View File

@ -748,7 +748,7 @@ class ConcreteMachine:
Audio::Toggle audio_toggle_;
Konami::SCC scc_;
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC>> speaker_;
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle, Konami::SCC>> speaker_;
Storage::Tape::BinaryTapePlayer tape_player_;
bool tape_player_is_sleeping_ = false;

View File

@ -487,7 +487,7 @@ class ConcreteMachine:
TI::SN76489 sn76489_;
Yamaha::OPL::OPLL opll_;
Outputs::Speaker::CompoundSource<decltype(sn76489_), decltype(opll_)> mixer_;
Outputs::Speaker::LowpassSpeaker<decltype(mixer_)> speaker_;
Outputs::Speaker::PullLowpass<decltype(mixer_)> speaker_;
uint8_t opll_detection_word_ = 0xff;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;

View File

@ -84,7 +84,7 @@ namespace Oric {
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
using Processor = Analyser::Static::Oric::Target::Processor;
using AY = GI::AY38910::AY38910<false>;
using Speaker = Outputs::Speaker::LowpassSpeaker<AY>;
using Speaker = Outputs::Speaker::PullLowpass<AY>;
enum ROM {
BASIC10 = 0, BASIC11, Microdisc, Colour

View File

@ -466,7 +466,7 @@ template<bool is_zx81> class ConcreteMachine:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
using AY = GI::AY38910::AY38910<false>;
AY ay_;
Outputs::Speaker::LowpassSpeaker<AY> speaker_;
Outputs::Speaker::PullLowpass<AY> speaker_;
HalfCycles time_since_ay_update_;
inline void ay_set_register(uint8_t value) {
update_audio();

View File

@ -848,7 +848,7 @@ template<Model model> class ConcreteMachine:
GI::AY38910::AY38910<false> ay_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle> mixer_;
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle>> speaker_;
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910<false>, Audio::Toggle>> speaker_;
HalfCycles time_since_audio_update_;
void update_audio() {

View File

@ -104,7 +104,6 @@
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; };
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; };
4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
@ -295,7 +294,6 @@
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */; };
4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; };
4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; };
@ -350,7 +348,6 @@
4B778F1423A5EC960000D260 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
4B778F1523A5EC980000D260 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
4B778F1623A5ECA00000D260 /* Z80AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322DFD1F5A2981004EB04C /* Z80AllRAM.cpp */; };
4B778F1823A5ED1B0000D260 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
4B778F1923A5ED1B0000D260 /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; };
4B778F1A23A5ED320000D260 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
4B778F1B23A5ED380000D260 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0ACC0823775819008902D0 /* Video.cpp */; };
@ -1379,7 +1376,6 @@
4B6A4C8E1F58F09E00E3F787 /* 6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502.hpp; sourceTree = "<group>"; };
4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; };
4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; };
4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MassStorageDevice.cpp; sourceTree = "<group>"; };
4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MassStorageDevice.hpp; sourceTree = "<group>"; };
4B6AAEA6230E40250078E864 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
@ -3049,7 +3045,6 @@
4B6A4C931F58F09E00E3F787 /* Implementation */ = {
isa = PBXGroup;
children = (
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */,
4B8334851F5DA3780097E338 /* 6502Storage.cpp */,
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */,
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */,
@ -5430,7 +5425,6 @@
4B0ACC2F23775819008902D0 /* TIA.cpp in Sources */,
4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */,
4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */,
4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */,
4BF8D4D6251C11DD00BBE21B /* 65816Storage.cpp in Sources */,
4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */,
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
@ -5734,7 +5728,6 @@
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */,
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,
4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */,
4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */,
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
@ -5941,7 +5934,6 @@
4B778EFE23A5EB910000D260 /* CPCDSK.cpp in Sources */,
4B778F5823A5F2C60000D260 /* Tape.cpp in Sources */,
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,
4B778F1823A5ED1B0000D260 /* 6502Base.cpp in Sources */,
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */,
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,

View File

@ -1,19 +1,20 @@
//
// FilteringSpeaker.h
// LowpassSpeaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/12/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef FilteringSpeaker_h
#define FilteringSpeaker_h
#ifndef LowpassSpeaker_hpp
#define LowpassSpeaker_hpp
#include "../Speaker.hpp"
#include "../../../SignalProcessing/FIRFilter.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp"
#include <algorithm>
#include <mutex>
#include <cstring>
#include <cmath>
@ -21,64 +22,8 @@
namespace Outputs {
namespace Speaker {
/*!
The low-pass speaker expects an Outputs::Speaker::SampleSource-derived
template class, and uses the instance supplied to its constructor as the
source of a high-frequency stream of audio which it filters down to a
lower-frequency output.
*/
template <typename SampleSource> class LowpassSpeaker: public Speaker {
template <typename ConcreteT, bool is_stereo> class LowpassBase: public Speaker {
public:
LowpassSpeaker(SampleSource &sample_source) : sample_source_(sample_source) {
// Propagate an initial volume level.
sample_source.set_sample_volume_range(32767);
}
void set_output_volume(float volume) final {
// Clamp to the acceptable range, and set.
volume = std::min(std::max(0.0f, volume), 1.0f);
sample_source_.set_sample_volume_range(int16_t(32767.0f * volume));
}
// Implemented as per Speaker.
float get_ideal_clock_rate_in_range(float minimum, float maximum) final {
std::lock_guard lock_guard(filter_parameters_mutex_);
// 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_computed_output_rate(float cycles_per_second, int buffer_size, bool) final {
std::lock_guard lock_guard(filter_parameters_mutex_);
if(filter_parameters_.output_cycles_per_second == cycles_per_second && size_t(buffer_size) == output_buffer_.size()) {
return;
}
filter_parameters_.output_cycles_per_second = cycles_per_second;
filter_parameters_.parameters_are_dirty = true;
output_buffer_.resize(std::size_t(buffer_size) * (SampleSource::get_is_stereo() ? 2 : 1));
}
bool get_is_stereo() final {
return SampleSource::get_is_stereo();
}
/*!
Sets the clock rate of the input audio.
*/
@ -107,90 +52,42 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
filter_parameters_.parameters_are_dirty = true;
}
/*!
Schedules an advancement by the number of cycles specified on the provided queue.
The speaker will advance by 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(Concurrency::DeferringAsyncTaskQueue &queue, const Cycles cycles) {
queue.defer([this, cycles] {
run_for(cycles);
});
}
private:
enum class Conversion {
ResampleSmaller,
Copy,
ResampleLarger
} conversion_ = Conversion::Copy;
/*!
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) {
const auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
const int scale = get_scale();
std::size_t cycles_remaining = size_t(cycles.as_integral());
if(!cycles_remaining) return;
FilterParameters filter_parameters;
{
float get_ideal_clock_rate_in_range(float minimum, float maximum) final {
std::lock_guard lock_guard(filter_parameters_mutex_);
filter_parameters = filter_parameters_;
filter_parameters_.parameters_are_dirty = false;
filter_parameters_.input_rate_changed = false;
}
if(filter_parameters.parameters_are_dirty) update_filter_coefficients(filter_parameters);
if(filter_parameters.input_rate_changed) {
delegate->speaker_did_change_input_clock(this);
// 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;
}
switch(conversion_) {
case Conversion::Copy:
while(cycles_remaining) {
const auto cycles_to_read = std::min((output_buffer_.size() - output_buffer_pointer_) / (SampleSource::get_is_stereo() ? 2 : 1), cycles_remaining);
sample_source_.get_samples(cycles_to_read, &output_buffer_[output_buffer_pointer_ ]);
output_buffer_pointer_ += cycles_to_read * (SampleSource::get_is_stereo() ? 2 : 1);
// TODO: apply scale.
// Announce to delegate if full.
if(output_buffer_pointer_ == output_buffer_.size()) {
output_buffer_pointer_ = 0;
did_complete_samples(this, output_buffer_, SampleSource::get_is_stereo());
// Implemented as per Speaker.
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool) final {
std::lock_guard lock_guard(filter_parameters_mutex_);
if(filter_parameters_.output_cycles_per_second == cycles_per_second && size_t(buffer_size) == output_buffer_.size()) {
return;
}
cycles_remaining -= cycles_to_read;
}
break;
case Conversion::ResampleSmaller:
while(cycles_remaining) {
const auto cycles_to_read = std::min((input_buffer_.size() - input_buffer_depth_) / (SampleSource::get_is_stereo() ? 2 : 1), cycles_remaining);
sample_source_.get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]);
input_buffer_depth_ += cycles_to_read * (SampleSource::get_is_stereo() ? 2 : 1);
if(input_buffer_depth_ == input_buffer_.size()) {
resample_input_buffer(scale);
filter_parameters_.output_cycles_per_second = cycles_per_second;
filter_parameters_.parameters_are_dirty = true;
output_buffer_.resize(std::size_t(buffer_size) * (is_stereo + 1));
}
cycles_remaining -= cycles_to_read;
}
break;
case Conversion::ResampleLarger:
// TODO: input rate is less than output rate.
break;
}
}
SampleSource &sample_source_;
// MARK: - Filtering.
std::size_t output_buffer_pointer_ = 0;
std::size_t input_buffer_depth_ = 0;
@ -233,7 +130,6 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
high_pass_frequency,
SignalProcessing::FIRFilter::DefaultAttenuation);
// Pick the new conversion function.
if( filter_parameters.input_cycles_per_second == filter_parameters.output_cycles_per_second &&
filter_parameters.high_frequency_cutoff < 0.0) {
@ -249,7 +145,7 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
}
// Do something sensible with any dangling input, if necessary.
const int scale = get_scale();
const int scale = static_cast<ConcreteT *>(this)->get_scale();
switch(conversion_) {
// Neither direct copying nor resampling larger currently use any temporary input.
// Although in the latter case that's just because it's unimplemented. But, regardless,
@ -260,7 +156,7 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
// Reize the input buffer only if absolutely necessary; if sizing downward
// such that a sample would otherwise be lost then output it now. Keep anything
// currently in the input buffer that hasn't yet been processed.
const size_t required_buffer_size = size_t(number_of_taps) * (SampleSource::get_is_stereo() ? 2 : 1);
const size_t required_buffer_size = size_t(number_of_taps) * (is_stereo + 1);
if(input_buffer_.size() != required_buffer_size) {
if(input_buffer_depth_ >= required_buffer_size) {
resample_input_buffer(scale);
@ -273,7 +169,7 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
}
inline void resample_input_buffer(int scale) {
if constexpr (SampleSource::get_is_stereo()) {
if constexpr (is_stereo) {
output_buffer_[output_buffer_pointer_ + 0] = filter_->apply(input_buffer_.data(), 2);
output_buffer_[output_buffer_pointer_ + 1] = filter_->apply(input_buffer_.data() + 1, 2);
output_buffer_pointer_+= 2;
@ -284,8 +180,8 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
// Apply scale, if supplied, clamping appropriately.
if(scale != 65536) {
#define SCALE(x) x = int16_t(std::max(std::min((int(x) * scale) >> 16, 32767), -32768))
if constexpr (SampleSource::get_is_stereo()) {
#define SCALE(x) x = int16_t(std::clamp((int(x) * scale) >> 16, -32768, 32767))
if constexpr (is_stereo) {
SCALE(output_buffer_[output_buffer_pointer_ - 2]);
SCALE(output_buffer_[output_buffer_pointer_ - 1]);
} else {
@ -297,13 +193,13 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
// Announce to delegate if full.
if(output_buffer_pointer_ == output_buffer_.size()) {
output_buffer_pointer_ = 0;
did_complete_samples(this, output_buffer_, SampleSource::get_is_stereo());
did_complete_samples(this, output_buffer_, is_stereo);
}
// 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.
const size_t steps = size_t(step_rate_ + position_error_) * (SampleSource::get_is_stereo() ? 2 : 1);
const size_t steps = size_t(step_rate_ + position_error_) * (is_stereo + 1);
position_error_ = fmodf(step_rate_ + position_error_, 1.0f);
if(steps < input_buffer_.size()) {
auto *const input_buffer = input_buffer_.data();
@ -313,18 +209,190 @@ template <typename SampleSource> class LowpassSpeaker: public Speaker {
input_buffer_depth_ -= steps;
} else {
if(steps > input_buffer_.size()) {
sample_source_.skip_samples((steps - input_buffer_.size()) / (SampleSource::get_is_stereo() ? 2 : 1));
static_cast<ConcreteT *>(this)->skip_samples((steps - input_buffer_.size()) / (1 + is_stereo));
}
input_buffer_depth_ = 0;
}
}
enum class Conversion {
ResampleSmaller,
Copy,
ResampleLarger
} conversion_ = Conversion::Copy;
bool recalculate_filter_if_dirty() {
FilterParameters filter_parameters;
{
std::lock_guard lock_guard(filter_parameters_mutex_);
filter_parameters = filter_parameters_;
filter_parameters_.parameters_are_dirty = false;
filter_parameters_.input_rate_changed = false;
}
if(filter_parameters.parameters_are_dirty) update_filter_coefficients(filter_parameters);
return filter_parameters.input_rate_changed;
}
protected:
void process(size_t length) {
const auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
const int scale = static_cast<ConcreteT *>(this)->get_scale();
if(recalculate_filter_if_dirty()) {
delegate->speaker_did_change_input_clock(this);
}
switch(conversion_) {
case Conversion::Copy:
while(length) {
const auto samples_to_read = std::min((output_buffer_.size() - output_buffer_pointer_) / (1 + is_stereo), length);
static_cast<ConcreteT *>(this)->get_samples(samples_to_read, &output_buffer_[output_buffer_pointer_ ]);
output_buffer_pointer_ += samples_to_read * (1 + is_stereo);
// TODO: apply scale.
// Announce to delegate if full.
if(output_buffer_pointer_ == output_buffer_.size()) {
output_buffer_pointer_ = 0;
did_complete_samples(this, output_buffer_, is_stereo);
}
length -= samples_to_read;
}
break;
case Conversion::ResampleSmaller:
while(length) {
const auto cycles_to_read = std::min((input_buffer_.size() - input_buffer_depth_) / (1 + is_stereo), length);
static_cast<ConcreteT *>(this)->get_samples(cycles_to_read, &input_buffer_[input_buffer_depth_]);
input_buffer_depth_ += cycles_to_read * (1 + is_stereo);
if(input_buffer_depth_ == input_buffer_.size()) {
resample_input_buffer(scale);
}
length -= cycles_to_read;
}
break;
case Conversion::ResampleLarger:
// TODO: input rate is less than output rate.
break;
}
}
};
/*!
Provides a low-pass speaker to which blocks of samples are pushed.
*/
template <bool is_stereo> class PushLowpass: public LowpassBase<PushLowpass<is_stereo>, is_stereo> {
private:
using BaseT = LowpassBase<PushLowpass<is_stereo>, is_stereo>;
friend BaseT;
using BaseT::process;
std::atomic<uint16_t> scale_ = 32767;
int get_scale() {
return scale_;
}
const int16_t *buffer_ = nullptr;
void skip_samples(size_t count) {
buffer_ += count;
}
void get_samples(size_t length, int16_t *target) {
memcpy(target, buffer_, length);
buffer_ += length;
}
public:
void set_output_volume(float volume) final {
scale_.store(uint16_t(std::clamp(volume * 65535.0f, 0.0f, 65535.0f)));
}
bool get_is_stereo() final {
return is_stereo;
}
/*!
Filters and posts onward the provided buffer, on the calling thread.
*/
void push(const int16_t *buffer, size_t length) {
buffer_ = buffer;
process(length);
}
};
/*!
The low-pass speaker expects an Outputs::Speaker::SampleSource-derived
template class, and uses the instance supplied to its constructor as the
source of a high-frequency stream of audio which it filters down to a
lower-frequency output.
*/
template <typename SampleSource> class PullLowpass: public LowpassBase<PullLowpass<SampleSource>, SampleSource::get_is_stereo()> {
public:
PullLowpass(SampleSource &sample_source) : sample_source_(sample_source) {
// Propagate an initial volume level.
sample_source.set_sample_volume_range(32767);
}
void set_output_volume(float volume) final {
// Clamp to the acceptable range, and set.
volume = std::clamp(volume, 0.0f, 1.0f);
sample_source_.set_sample_volume_range(int16_t(32767.0f * volume));
}
bool get_is_stereo() final {
return SampleSource::get_is_stereo();
}
/*!
Schedules an advancement by the number of cycles specified on the provided queue.
The speaker will advance by 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(Concurrency::DeferringAsyncTaskQueue &queue, const Cycles cycles) {
queue.defer([this, cycles] {
run_for(cycles);
});
}
private:
using BaseT = LowpassBase<PullLowpass<SampleSource>, SampleSource::get_is_stereo()>;
friend BaseT;
using BaseT::process;
/*!
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 = size_t(cycles.as_integral());
if(!cycles_remaining) return;
process(cycles_remaining);
}
SampleSource &sample_source_;
void skip_samples(size_t count) {
sample_source_.skip_samples(count);
}
int get_scale() {
return int(65536.0 / sample_source_.get_average_output_peak());
};
}
void get_samples(size_t length, int16_t *target) {
sample_source_.get_samples(length, target);
}
};
}
}
#endif /* FilteringSpeaker_h */
#endif /* LowpassSpeaker_hpp */

View File

@ -44,9 +44,9 @@ enum Personality {
#define has_stpwai(p) ((p) >= Personality::PWDC65C02)
/*!
An opcode that is guaranteed to cause the CPU to jam.
An opcode that is guaranteed to cause a 6502 to jam.
*/
extern const uint8_t JamOpcode;
constexpr uint8_t JamOpcode = 0xf2;
#include "Implementation/6502Storage.hpp"
@ -65,7 +65,7 @@ class ProcessorBase: public ProcessorStorage {
@param r The register to set.
@returns The value of the register. 8-bit registers will be returned as unsigned.
*/
uint16_t get_value_of_register(Register r) const;
inline uint16_t get_value_of_register(Register r) const;
/*!
Sets the value of a register.
@ -75,7 +75,7 @@ class ProcessorBase: public ProcessorStorage {
@param r The register to set.
@param value The value to set. If the register is only 8 bit, the value will be truncated.
*/
void set_value_of_register(Register r, uint16_t value);
inline void set_value_of_register(Register r, uint16_t value);
/*!
Sets the current level of the RST line.
@ -124,7 +124,7 @@ class ProcessorBase: public ProcessorStorage {
@returns @c true if the 6502 is jammed; @c false otherwise.
*/
bool is_jammed() const;
inline bool is_jammed() const;
};
/*!

View File

@ -1,42 +0,0 @@
//
// 6502Base.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/08/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "../6502.hpp"
using namespace CPU::MOS6502;
const uint8_t CPU::MOS6502::JamOpcode = 0xf2;
uint16_t ProcessorBase::get_value_of_register(Register r) const {
switch (r) {
case Register::ProgramCounter: return pc_.full;
case Register::LastOperationAddress: return last_operation_pc_.full;
case Register::StackPointer: return s_;
case Register::Flags: return get_flags();
case Register::A: return a_;
case Register::X: return x_;
case Register::Y: return y_;
default: return 0;
}
}
void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
switch (r) {
case Register::ProgramCounter: pc_.full = value; break;
case Register::StackPointer: s_ = uint8_t(value); break;
case Register::Flags: set_flags(uint8_t(value)); break;
case Register::A: a_ = uint8_t(value); break;
case Register::X: x_ = uint8_t(value); break;
case Register::Y: y_ = uint8_t(value); break;
default: break;
}
}
bool ProcessorBase::is_jammed() const {
return is_jammed_;
}

View File

@ -700,3 +700,32 @@ uint8_t ProcessorStorage::get_flags() const {
void ProcessorStorage::set_flags(uint8_t flags) {
flags_.set(flags);
}
bool ProcessorBase::is_jammed() const {
return is_jammed_;
}
uint16_t ProcessorBase::get_value_of_register(Register r) const {
switch (r) {
case Register::ProgramCounter: return pc_.full;
case Register::LastOperationAddress: return last_operation_pc_.full;
case Register::StackPointer: return s_;
case Register::Flags: return get_flags();
case Register::A: return a_;
case Register::X: return x_;
case Register::Y: return y_;
default: return 0;
}
}
void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
switch (r) {
case Register::ProgramCounter: pc_.full = value; break;
case Register::StackPointer: s_ = uint8_t(value); break;
case Register::Flags: set_flags(uint8_t(value)); break;
case Register::A: a_ = uint8_t(value); break;
case Register::X: x_ = uint8_t(value); break;
case Register::Y: y_ = uint8_t(value); break;
default: break;
}
}