mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +00:00
Sets up a queue to push memory writes onto the audio thread.
This commit is contained in:
parent
a7051e4e42
commit
4b9fe805e9
@ -10,14 +10,25 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
// TODO: is it safe not to check for back-pressure in pending_stores_?
|
||||
|
||||
using namespace Apple::IIgs::Sound;
|
||||
|
||||
GLU::GLU(Concurrency::DeferringAsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {}
|
||||
|
||||
void GLU::set_data(uint8_t data) {
|
||||
if(control_ & 0x40) {
|
||||
if(local_.control & 0x40) {
|
||||
// RAM access.
|
||||
local_.ram_[address_] = data;
|
||||
|
||||
MemoryWrite write;
|
||||
write.enabled = true;
|
||||
write.address = address_;
|
||||
write.value = data;
|
||||
write.time = pending_store_write_time_;
|
||||
pending_stores_[pending_store_write_].store(write, std::memory_order::memory_order_release);
|
||||
|
||||
pending_store_write_ = (pending_store_write_ + 1) % (StoreBufferSize - 1);
|
||||
} else {
|
||||
// Register access.
|
||||
switch(address_ & 0xe0) {
|
||||
@ -31,7 +42,7 @@ void GLU::set_data(uint8_t data) {
|
||||
// printf("Register write %04x\n", address_);
|
||||
}
|
||||
|
||||
if(control_ & 0x20) {
|
||||
if(local_.control & 0x20) {
|
||||
++address_;
|
||||
}
|
||||
}
|
||||
@ -44,17 +55,34 @@ uint8_t GLU::get_data() {
|
||||
|
||||
void GLU::run_for(Cycles cycles) {
|
||||
// Update local state, without generating audio.
|
||||
local_.skip_audio(cycles.as<size_t>());
|
||||
skip_audio(local_, cycles.as<size_t>());
|
||||
|
||||
// Update the timestamp for memory writes;
|
||||
pending_store_write_time_ += cycles.as<uint32_t>();
|
||||
}
|
||||
|
||||
void GLU::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
// Update remote state, generating audio.
|
||||
remote_.generate_audio(number_of_samples, target, output_range_);
|
||||
generate_audio(number_of_samples, target, output_range_);
|
||||
}
|
||||
|
||||
void GLU::skip_samples(const std::size_t number_of_samples) {
|
||||
// Update remote state, without generating audio.
|
||||
remote_.skip_audio(number_of_samples);
|
||||
skip_audio(remote_, number_of_samples);
|
||||
|
||||
// Apply any pending stores.
|
||||
std::atomic_thread_fence(std::memory_order::memory_order_acquire);
|
||||
const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples);
|
||||
while(true) {
|
||||
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
|
||||
if(!next_store.enabled) break;
|
||||
if(next_store.time >= final_time) break;
|
||||
remote_.ram_[next_store.address] = next_store.value;
|
||||
next_store.enabled = false;
|
||||
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
|
||||
|
||||
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void GLU::set_sample_volume_range(std::int16_t range) {
|
||||
@ -92,16 +120,29 @@ uint8_t GLU::get_address_high() {
|
||||
|
||||
// MARK: - Update logic.
|
||||
|
||||
void GLU::EnsoniqState::generate_audio(size_t number_of_samples, std::int16_t *target, int16_t range) {
|
||||
void GLU::generate_audio(size_t number_of_samples, std::int16_t *target, int16_t range) {
|
||||
(void)number_of_samples;
|
||||
(void)target;
|
||||
(void)range;
|
||||
|
||||
memset(target, 0, number_of_samples * sizeof(int16_t));
|
||||
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
|
||||
for(size_t c = 0; c < number_of_samples; c++) {
|
||||
target[c] = 0;
|
||||
|
||||
// Apply any RAM writes that interleave here.
|
||||
++pending_store_read_time_;
|
||||
if(!next_store.enabled) continue;
|
||||
if(next_store.time != pending_store_read_time_) continue;
|
||||
remote_.ram_[next_store.address] = next_store.value;
|
||||
next_store.enabled = false;
|
||||
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
|
||||
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void GLU::EnsoniqState::skip_audio(size_t number_of_samples) {
|
||||
void GLU::skip_audio(EnsoniqState &state, size_t number_of_samples) {
|
||||
(void)number_of_samples;
|
||||
(void)state;
|
||||
|
||||
// Just advance all oscillator pointers and check for interrupts.
|
||||
// If a read occurs to the current-output level, generate it then.
|
||||
|
@ -9,6 +9,8 @@
|
||||
#ifndef Apple_IIgs_Sound_hpp
|
||||
#define Apple_IIgs_Sound_hpp
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
@ -42,6 +44,26 @@ class GLU: public Outputs::Speaker::SampleSource {
|
||||
|
||||
uint16_t address_ = 0;
|
||||
|
||||
// Use a circular buffer for piping memory alterations onto the audio
|
||||
// thread; it would be prohibitive to defer every write individually.
|
||||
//
|
||||
// Assumed: on most modern architectures, an atomic 64-bit read or
|
||||
// write can be achieved locklessly.
|
||||
struct MemoryWrite {
|
||||
uint32_t time;
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
bool enabled = false;
|
||||
};
|
||||
static_assert(sizeof(MemoryWrite) == 8);
|
||||
constexpr static int StoreBufferSize = 16384;
|
||||
|
||||
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
|
||||
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
|
||||
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
|
||||
|
||||
// Maintain state both 'locally' (i.e. on the emulation thread) and
|
||||
// 'remotely' (i.e. on the audio thread).
|
||||
struct EnsoniqState {
|
||||
uint8_t ram_[65536];
|
||||
struct Oscillator {
|
||||
@ -55,16 +77,18 @@ class GLU: public Outputs::Speaker::SampleSource {
|
||||
uint8_t table_size;
|
||||
} oscillators_[32];
|
||||
|
||||
// TODO: do all of these need to be on the audio thread?
|
||||
uint8_t control;
|
||||
|
||||
void generate_audio(size_t number_of_samples, std::int16_t *target, int16_t range);
|
||||
void skip_audio(size_t number_of_samples);
|
||||
uint8_t interrupt_state;
|
||||
uint8_t oscillator_enable;
|
||||
} local_, remote_;
|
||||
uint8_t interrupt_state_;
|
||||
uint8_t oscillator_enable_;
|
||||
|
||||
uint8_t control_ = 0x00;
|
||||
// Functions to update an EnsoniqState; these don't belong to the state itself
|
||||
// because they also access the pending stores (inter alia).
|
||||
void generate_audio(size_t number_of_samples, std::int16_t *target, int16_t range);
|
||||
void skip_audio(EnsoniqState &state, size_t number_of_samples);
|
||||
|
||||
// Audio-thread state.
|
||||
int16_t output_range_ = 0;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user