From 22ee51c12c0c942f2fb7a49bbd07d3048c14b2a7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 17 Jul 2019 14:41:36 -0400 Subject: [PATCH] Corrects clocking issues around audio, and cuts down queue costs. --- Machines/Apple/Macintosh/Audio.cpp | 62 ++++++++------------------ Machines/Apple/Macintosh/Audio.hpp | 16 ++++--- Machines/Apple/Macintosh/Macintosh.cpp | 6 ++- 3 files changed, 34 insertions(+), 50 deletions(-) diff --git a/Machines/Apple/Macintosh/Audio.cpp b/Machines/Apple/Macintosh/Audio.cpp index 875fc2da4..3dedb6d0a 100644 --- a/Machines/Apple/Macintosh/Audio.cpp +++ b/Machines/Apple/Macintosh/Audio.cpp @@ -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((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((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); } diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp index f123020c7..c1348cbf1 100644 --- a/Machines/Apple/Macintosh/Audio.hpp +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -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 buffer; - std::atomic read_pointer, write_pointer; + std::array 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; diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 1a85ff43b..46730e890 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -112,7 +112,7 @@ template 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 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() {