diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index d0a9d811e..e59a4e20e 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -24,40 +24,36 @@ constexpr bool is_power_of_two(const int v) { Provides a class that wraps a plain int, providing most of the basic arithmetic and Boolean operators, but forcing callers and receivers to be explicit as to usage. */ -template -requires (is_power_of_two(ClockScale)) +template +requires (is_power_of_two(ClockDenominator)) class Clocks { public: - static constexpr int Scale = ClockScale; - static constexpr int Mask = ~(ClockScale - 1); + static constexpr int Denominator = ClockDenominator; using IntType = int64_t; - constexpr Clocks(const IntType rhs) noexcept : length_(rhs * ClockScale) {} + constexpr Clocks(const IntType rhs) noexcept : length_(rhs) {} constexpr Clocks() noexcept : length_(0) {} // Assignments are implemented anywhere they can't lose data. template - requires (SourceClocks::Mask <= Mask) - constexpr Clocks(const SourceClocks rhs) noexcept : length_(rhs.raw() & Mask) {} - - Clocks operator =(const Clocks rhs) { - length_ = rhs.length_ & Mask; - return *this; + requires (SourceClocks::Denominator <= Denominator) + constexpr Clocks(const SourceClocks rhs) noexcept { + *this = rhs.template reduce(); } Clocks operator +=(const Clocks rhs) { length_ += rhs.length_; return *this; } Clocks operator -=(const Clocks rhs) { length_ -= rhs.length_; return *this; } - Clocks operator ++() { length_ += ClockScale; return *this; } - Clocks operator --() { length_ -= ClockScale; return *this; } + Clocks operator ++() { ++length_; return *this; } + Clocks operator --() { --length_; return *this; } Clocks operator ++(int) { const Clocks result = *this; - length_ += ClockScale; + ++length_; return result; } Clocks operator --(int) { const Clocks result = *this; - length_ -= ClockScale; + --length_; return result; } @@ -106,14 +102,23 @@ public: return Type(std::clamp(length_, low, high)); } - /// @returns The underlying int, in its native form. + /// @returns The underlying int, in its native form, potentially scaled upward. + template + requires (GetDenominator >= Denominator) constexpr IntType get() const { - return length_ / ClockScale; + return length_ << (GetDenominator - Denominator); } - constexpr IntType raw() const { - return length_; + /// @returns This value, optionally converted to another time base. Potentially loses precision. + template + constexpr TargetClocks reduce() const { + if constexpr (TargetClocks::Denominator >= Denominator) { + return TargetClocks(length_ << (TargetClocks::Denominator - Denominator)); + } else { + return TargetClocks(length_ >> (Denominator - TargetClocks::Denominator)); + } } + // operator int() is deliberately not provided, to avoid accidental subtitution of // classes that use this template. @@ -121,27 +126,32 @@ public: Caculates `*this / divisor`, converting that to `DestinationClocks`. Sets `*this = *this % divisor`. */ -// template -// DestinationClocks divide(const DestinationClocks divisor) { -// // -// -// -// Clocks result; -// result.length_ = length_ / divisor.length_; -// length_ %= divisor.length_; -// return result; -// } + template + requires (DestinationClocks::Denominator >= Denominator) + DestinationClocks divide(const Clocks divisor) { + Clocks result; + result.length_ = length_ / divisor.length_; + length_ %= divisor.length_; + return reduce(); + } /*! Extracts a whole number of `DestinationClock`s from `*this`. Leaves the residue here. */ template - requires (DestinationClocks::Mask <= Mask) DestinationClocks flush() { - const auto result = DestinationClocks(length_); - length_ &= Mask ^ DestinationClocks::Mask; - return result; + if constexpr (DestinationClocks::Denominator >= Denominator) { + const auto result = DestinationClocks(get()); + length_ = 0; + return result; + } else { + static constexpr int ShiftRight = Denominator - DestinationClocks::Denominator; + static constexpr IntType ResidueMask = (1 << ShiftRight) - 1; + const auto result = reduce(); + length_ &= ResidueMask; + return result; + } } static Clocks max() { return Clocks(std::numeric_limits::max()); } @@ -167,9 +177,9 @@ private: }; /// Reasons Clocks into being a count of querter cycles, building half- and whole-cycles from there. -using Cycles = Clocks<4>; +using Cycles = Clocks<1>; using HalfCycles = Clocks<2>; -using QuarterCycles = Clocks<1>; +using QuarterCycles = Clocks<4>; /*! Provides automated boilerplate for connecting an owner that works in one clock base to a receiver that works in another. diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index ae066b088..9473e9d86 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -186,9 +186,14 @@ public: const auto duration = time_since_update_.template flush(); object_.run_for(duration); } else { - const auto duration = time_since_update_.template divide(LocalTimeScale(divider)); - if(duration > TargetTimeScale(0)) - object_.run_for(duration); + // Get the result of clock dividing; flush that to the other time scale potentially to generate + // extra residue and retain that back in the original counter. + auto local_duration = time_since_update_.divide(LocalTimeScale(divider)); + const auto target_duration = local_duration.template flush(); + time_since_update_ += target_duration; + if(target_duration > TargetTimeScale(0)) { + object_.run_for(target_duration); + } } } } diff --git a/Components/6526/Implementation/6526Implementation.hpp b/Components/6526/Implementation/6526Implementation.hpp index 1ef2337f4..bf0d26952 100644 --- a/Components/6526/Implementation/6526Implementation.hpp +++ b/Components/6526/Implementation/6526Implementation.hpp @@ -183,7 +183,7 @@ uint8_t MOS6526::read(int address) { template void MOS6526::run_for(const HalfCycles half_cycles) { half_divider_ += half_cycles; - int sub = half_divider_.divide_cycles().template as(); + int sub = half_divider_.flush().template as(); while(sub--) { pending_ <<= 1; diff --git a/Machines/Amiga/Chipset.cpp b/Machines/Amiga/Chipset.cpp index 21ecd2045..f2c820b91 100644 --- a/Machines/Amiga/Chipset.cpp +++ b/Machines/Amiga/Chipset.cpp @@ -795,7 +795,7 @@ template Chipset::Changes Chipset::run(HalfCycles length) { // Update the disk controller, if any drives are active. if(!disk_controller_is_sleeping_) { - disk_controller_.run_for(changes.duration.cycles()); + disk_controller_.run_for(changes.duration.flush()); } // Record the interrupt level. diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index e8f201089..12fcdd8b2 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -140,7 +140,7 @@ public: /// Enqueues an update-to-now into the AY's deferred queue. inline void update() { - speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4))); + speaker_.run_for(audio_queue_, cycles_since_update_.divide(HalfCycles(8)).reduce()); } /// Issues a request to the AY to perform all processing up to the current time. @@ -881,7 +881,7 @@ public: // will do as it's safe to conclude that nobody else has touched video RAM // during that whole window. crtc_counter_ += cycle.length; - const Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); + const Cycles crtc_cycles = crtc_counter_.divide(HalfCycles(8)).template reduce(); if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); // Check whether that prompted a change in the interrupt line. If so then date diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index d6752b79a..ba149c47f 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -614,7 +614,7 @@ private: // Consider updating the real-time clock. real_time_clock_ += duration; - auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).get(); + auto ticks = real_time_clock_.divide(HalfCycles(CLOCK_RATE << 1)).template reduce().get(); while(ticks--) { clock_.update(); // TODO: leave a delay between toggling the input rather than using this coupled hack. diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 0ffd194ac..9d7c9c496 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -498,7 +498,7 @@ private: } void update_audio() { - speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide_cycles(Cycles(4))); + speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(HalfCycles(8)).template reduce()); } CPU::MC68000::Processor mc68000_; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index d4c128c2a..f5f55fced 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -379,7 +379,10 @@ private: cartridge_pages_[1] = &cartridge_[selected_start]; } inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider))); + speaker_.run_for( + audio_queue_, + time_since_sn76489_update_.divide(HalfCycles(sn76489_divider << 1)).reduce() + ); } CPU::Z80::Processor z80_; diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 0a2026b6b..ccca99261 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -841,7 +841,10 @@ private: static constexpr int dave_divider = 8; JustInTimeActor dave_timer_; inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider))); + speaker_.run_for( + audio_queue_, + time_since_audio_update_.divide(HalfCycles(dave_divider << 1)).template reduce() + ); } // MARK: - EXDos card. diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 7afbf4b31..181c0d35b 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -848,7 +848,10 @@ public: private: void update_audio() { - speaker_.speaker.run_for(speaker_.audio_queue, time_since_ay_update_.divide_cycles(Cycles(2))); + speaker_.speaker.run_for( + speaker_.audio_queue, + time_since_ay_update_.divide(HalfCycles(4)).template reduce() + ); } class i8255PortHandler: public Intel::i8255::PortHandler { diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 04cb3234f..d1fcffc78 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -459,7 +459,10 @@ private: } inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(audio_divider))); + speaker_.run_for( + audio_queue_, + time_since_sn76489_update_.divide(HalfCycles(audio_divider << 1)).template reduce() + ); } void set_mixer_levels(uint8_t mode) { diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index e552e51aa..ef162f67c 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -432,7 +432,7 @@ private: Sinclair::ZX::Keyboard::Keyboard keyboard_; Sinclair::ZX::Keyboard::KeyboardMapper keyboard_mapper_; - HalfClockReceiver tape_player_; + ConvertedClockReceiver tape_player_; Storage::Tape::ZX8081::Parser parser_; bool nmi_is_enabled_ = false; @@ -486,7 +486,7 @@ private: return GI::AY38910::Utility::read(ay_); } inline void update_audio() { - speaker_.run_for(audio_queue_, time_since_ay_update_.divide_cycles(Cycles(2))); + speaker_.run_for(audio_queue_, time_since_ay_update_.divide(HalfCycles(4)).template reduce()); } }; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index aab18876b..964fe8489 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -875,7 +875,7 @@ private: HalfCycles time_since_audio_update_; void update_audio() { - speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(2))); + speaker_.run_for(audio_queue_, time_since_audio_update_.divide(HalfCycles(4)).template reduce()); } // MARK: - Video.