mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Corrects clocking issues around audio, and cuts down queue costs.
This commit is contained in:
parent
ee8d853fcb
commit
22ee51c12c
@ -11,7 +11,10 @@
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
const std::size_t sample_length = 352;
|
||||
|
||||
// The sample_length is coupled with the clock rate selected within the Macintosh proper.
|
||||
const std::size_t sample_length = 352 / 2;
|
||||
|
||||
}
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
@ -19,20 +22,17 @@ Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(tas
|
||||
// MARK: - Inputs
|
||||
|
||||
void Audio::post_sample(uint8_t sample) {
|
||||
// Grab the read and write pointers, ensure there's room for a new sample and, if not,
|
||||
// drop this one.
|
||||
const auto write_pointer = sample_queue_.write_pointer.load();
|
||||
const auto read_pointer = sample_queue_.read_pointer.load();
|
||||
const decltype(write_pointer) next_write_pointer = (write_pointer + 1) % sample_queue_.buffer.size();
|
||||
if(next_write_pointer == read_pointer) {
|
||||
return;
|
||||
}
|
||||
|
||||
sample_queue_.buffer[write_pointer] = sample;
|
||||
sample_queue_.write_pointer.store(next_write_pointer);
|
||||
// Store sample directly indexed by current write pointer; this ensures that collected samples
|
||||
// directly map to volume and enabled/disabled states.
|
||||
sample_queue_.buffer[sample_queue_.write_pointer] = sample;
|
||||
sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
|
||||
}
|
||||
|
||||
void Audio::set_volume(int volume) {
|
||||
// Do nothing if the volume hasn't changed.
|
||||
if(posted_volume_ == volume) return;
|
||||
posted_volume_ = volume;
|
||||
|
||||
// Post the volume change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
volume_ = volume;
|
||||
@ -40,9 +40,13 @@ void Audio::set_volume(int volume) {
|
||||
}
|
||||
|
||||
void Audio::set_enabled(bool on) {
|
||||
// Do nothing if the mask hasn't changed.
|
||||
if(posted_enable_mask_ == int(on)) return;
|
||||
posted_enable_mask_ = int(on);
|
||||
|
||||
// Post the enabled mask change as a deferred event.
|
||||
task_queue_.defer([=] () {
|
||||
enabled_mask_ = on ? 1 : 0;
|
||||
enabled_mask_ = int(on);
|
||||
});
|
||||
}
|
||||
|
||||
@ -58,46 +62,18 @@ void Audio::set_sample_volume_range(std::int16_t range) {
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
const auto write_pointer = sample_queue_.write_pointer.load();
|
||||
auto read_pointer = sample_queue_.read_pointer.load();
|
||||
|
||||
// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
|
||||
// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
|
||||
// that's something to return to.
|
||||
|
||||
// TODO: temporary implementation. Very inefficient. Replace.
|
||||
for(std::size_t sample = 0; sample < number_of_samples; ++sample) {
|
||||
// if(volume_ && enabled_mask_) printf("%d\n", sample_queue_.buffer[read_pointer]);
|
||||
target[sample] = volume_multiplier_ * int16_t(sample_queue_.buffer[read_pointer] * volume_ * enabled_mask_);
|
||||
target[sample] = volume_multiplier_ * int16_t(sample_queue_.buffer[sample_queue_.read_pointer] * volume_ * enabled_mask_);
|
||||
++subcycle_offset_;
|
||||
|
||||
if(subcycle_offset_ == sample_length) {
|
||||
// printf("%d: %d\n", sample_queue_.buffer[read_pointer], volume_multiplier_ * int16_t(sample_queue_.buffer[read_pointer]));
|
||||
subcycle_offset_ = 0;
|
||||
const unsigned int next_read_pointer = (read_pointer + 1) % sample_queue_.buffer.size();
|
||||
if(next_read_pointer != write_pointer) {
|
||||
read_pointer = next_read_pointer;
|
||||
}
|
||||
sample_queue_.read_pointer = (sample_queue_.read_pointer + 1) % sample_queue_.buffer.size();
|
||||
}
|
||||
}
|
||||
|
||||
sample_queue_.read_pointer.store(read_pointer);
|
||||
}
|
||||
|
||||
void Audio::skip_samples(std::size_t number_of_samples) {
|
||||
const auto write_pointer = sample_queue_.write_pointer.load();
|
||||
auto read_pointer = sample_queue_.read_pointer.load();
|
||||
|
||||
// Number of samples that would be consumed is (number_of_samples + subcycle_offset_) / sample_length.
|
||||
const unsigned int samples_passed = static_cast<unsigned int>((number_of_samples + subcycle_offset_) / sample_length);
|
||||
subcycle_offset_ = (number_of_samples + subcycle_offset_) % sample_length;
|
||||
|
||||
// Get also number of samples available.
|
||||
const unsigned int samples_available = static_cast<unsigned int>((write_pointer + sample_queue_.buffer.size() - read_pointer) % sample_queue_.buffer.size());
|
||||
|
||||
// Advance by whichever of those is the lower number.
|
||||
const auto samples_to_consume = std::min(samples_available, samples_passed);
|
||||
read_pointer = (read_pointer + samples_to_consume) % sample_queue_.buffer.size();
|
||||
|
||||
sample_queue_.read_pointer.store(read_pointer);
|
||||
}
|
||||
|
@ -20,8 +20,10 @@ namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
/*!
|
||||
Implements the Macintosh's audio output hardware, using a
|
||||
combination
|
||||
Implements the Macintosh's audio output hardware.
|
||||
|
||||
Designed to be clocked at half the rate of the real hardware — i.e.
|
||||
a shade less than 4Mhz.
|
||||
*/
|
||||
class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
@ -51,7 +53,6 @@ class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
|
||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
void skip_samples(std::size_t number_of_samples);
|
||||
bool is_zero_level();
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
|
||||
@ -61,10 +62,15 @@ class Audio: public ::Outputs::Speaker::SampleSource {
|
||||
// A queue of fetched samples; read from by one thread,
|
||||
// written to by another.
|
||||
struct {
|
||||
std::array<uint8_t, 4096> buffer;
|
||||
std::atomic<unsigned int> read_pointer, write_pointer;
|
||||
std::array<uint8_t, 740> buffer;
|
||||
size_t read_pointer = 0, write_pointer = 0;
|
||||
} sample_queue_;
|
||||
|
||||
// Emulator-thread stateful variables, to avoid work posting
|
||||
// deferral updates if possible.
|
||||
int posted_volume_ = 0;
|
||||
int posted_enable_mask_ = 0;
|
||||
|
||||
// Stateful variables, modified from the audio generation
|
||||
// thread only.
|
||||
int volume_ = 0;
|
||||
|
@ -112,7 +112,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
// The Mac runs at 7.8336mHz.
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
audio_.speaker.set_input_rate(float(CLOCK_RATE));
|
||||
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
|
||||
|
||||
// Insert any supplied media.
|
||||
insert_media(target.media);
|
||||
@ -543,7 +543,9 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
|
||||
void run_for(HalfCycles duration) {
|
||||
audio_.time_since_update += duration;
|
||||
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
|
||||
// divided-by-two clock the audio works on.
|
||||
audio_.time_since_update += HalfCycles(duration.as_int() * 5);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user