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:
commit
0df8173536
@ -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() {
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
|
@ -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};
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -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 */,
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -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_;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user