diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 43bc778e9..84e134da4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -315,6 +315,7 @@ 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; 4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C51D4B558F00248BDF /* Factors.cpp */; }; + 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; }; 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; }; @@ -703,6 +704,8 @@ 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = ""; }; 4BB697C51D4B558F00248BDF /* Factors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Factors.cpp; path = ../../NumberTheory/Factors.cpp; sourceTree = ""; }; 4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = ""; }; + 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = ""; }; + 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = ""; }; 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = ""; }; @@ -967,9 +970,11 @@ 4B69FB391C4D908A00B5F0AA /* Storage */ = { isa = PBXGroup; children = ( + 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */, + 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, + 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */, 4BAB62AA1D3272D200DF5BA0 /* Disk */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, - 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, ); name = Storage; path = ../../Storage; @@ -1914,6 +1919,7 @@ 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, + 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index 3fea5be4d..a2848dc13 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -17,14 +17,13 @@ void Tape::seek(Time seek_time) } TapePlayer::TapePlayer(unsigned int input_clock_rate) : - _input_clock_rate(input_clock_rate) + TimedEventLoop(input_clock_rate) {} void TapePlayer::set_tape(std::shared_ptr tape) { _tape = tape; - _input.pulse_stepper.reset(); - + reset_timer(); get_next_pulse(); } @@ -35,65 +34,34 @@ bool TapePlayer::has_tape() void TapePlayer::get_next_pulse() { - unsigned int previous_clock_rate = 0; - - // figure out how much time has been run since the last bit ended - if(_input.pulse_stepper == nullptr) - _input.time_into_pulse = 0; - else - { - _input.time_into_pulse -= _input.current_pulse.length.length; - previous_clock_rate = _input.current_pulse.length.clock_rate; - } - // get the new pulse if(_tape) - _input.current_pulse = _tape->get_next_pulse(); + _current_pulse = _tape->get_next_pulse(); else { - _input.current_pulse.length.length = 1; - _input.current_pulse.length.clock_rate = 1; - _input.current_pulse.type = Storage::Tape::Pulse::Zero; + _current_pulse.length.length = 1; + _current_pulse.length.clock_rate = 1; + _current_pulse.type = Storage::Tape::Pulse::Zero; } - // if there was any time left over, map into the new time base - if(_input.pulse_stepper && _input.time_into_pulse) - { - // simplify the quotient - unsigned int common_divisor = NumberTheory::greatest_common_divisor(_input.time_into_pulse, previous_clock_rate); - _input.time_into_pulse /= common_divisor; - previous_clock_rate /= common_divisor; - - // build a quotient that is the sum of the time overrun plus the incoming time and adjust the time overrun - // to be in terms of the new quotient - unsigned int denominator = NumberTheory::least_common_multiple(previous_clock_rate, _input.current_pulse.length.clock_rate); - _input.current_pulse.length.length *= denominator / _input.current_pulse.length.clock_rate; - _input.current_pulse.length.clock_rate = denominator; - _input.time_into_pulse *= denominator / previous_clock_rate; - } - - // adjust stepper if required - if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate()) - { - _input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, _input_clock_rate)); - } + set_next_event_time_interval(_current_pulse.length); } void TapePlayer::run_for_cycles(unsigned int number_of_cycles) { if(has_tape()) { - _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(number_of_cycles); - while(_input.time_into_pulse >= _input.current_pulse.length.length) - { - run_for_input_pulse(); - } + ::TimedEventLoop::run_for_cycles(number_of_cycles); } } void TapePlayer::run_for_input_pulse() { - process_input_pulse(_input.current_pulse); - get_next_pulse(); - _input.time_into_pulse = 0; + jump_to_next_event(); +} + +void TapePlayer::process_next_event() +{ + process_input_pulse(_current_pulse); + get_next_pulse(); } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 47babe011..3b9ad5e61 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -10,8 +10,7 @@ #define Tape_hpp #include -#include "../../SignalProcessing/Stepper.hpp" -#include "../Storage.hpp" +#include "../TimedEventLoop.hpp" namespace Storage { @@ -48,7 +47,7 @@ class Tape { Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type. */ -class TapePlayer { +class TapePlayer: public TimedEventLoop { public: TapePlayer(unsigned int input_clock_rate); @@ -59,18 +58,14 @@ class TapePlayer { void run_for_input_pulse(); protected: + virtual void process_next_event(); virtual void process_input_pulse(Tape::Pulse pulse) = 0; private: inline void get_next_pulse(); - unsigned int _input_clock_rate; std::shared_ptr _tape; - struct { - Tape::Pulse current_pulse; - std::unique_ptr pulse_stepper; - uint32_t time_into_pulse; - } _input; + Tape::Pulse _current_pulse; }; } diff --git a/Storage/TimedEventLoop.cpp b/Storage/TimedEventLoop.cpp new file mode 100644 index 000000000..c90e9d558 --- /dev/null +++ b/Storage/TimedEventLoop.cpp @@ -0,0 +1,72 @@ +// +// TimedEventLoop.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TimedEventLoop.hpp" +#include "../NumberTheory/Factors.hpp" + +using namespace Storage; + +TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) : + _input_clock_rate(input_clock_rate) {} + +void TimedEventLoop::run_for_cycles(unsigned int number_of_cycles) +{ + _time_into_interval += (unsigned int)_stepper->step(number_of_cycles); + while(_time_into_interval >= _event_interval.length) + { + process_next_event(); + } +} + +void TimedEventLoop::reset_timer() +{ + _time_into_interval = 0; + _stepper.reset(); +} + +void TimedEventLoop::jump_to_next_event() +{ + reset_timer(); + process_next_event(); +} + +void TimedEventLoop::set_next_event_time_interval(Time interval) +{ + // figure out how much time has been run since the last bit ended + if(_stepper) + { + _time_into_interval -= _event_interval.length; + if(_time_into_interval) + { + // simplify the quotient + unsigned int common_divisor = NumberTheory::greatest_common_divisor(_time_into_interval, _event_interval.clock_rate); + _time_into_interval /= common_divisor; + _event_interval.clock_rate /= common_divisor; + + // build a quotient that is the sum of the time overrun plus the incoming time and adjust the time overrun + // to be in terms of the new quotient + unsigned int denominator = NumberTheory::least_common_multiple(_event_interval.clock_rate, interval.clock_rate); + interval.length *= denominator / interval.clock_rate; + interval.clock_rate = denominator; + _time_into_interval *= denominator / _event_interval.clock_rate; + } + } + else + { + _time_into_interval = 0; + } + + // store new interval + _event_interval = interval; + + // adjust stepper if required + if(!_stepper || _event_interval.clock_rate != _stepper->get_output_rate()) + { + _stepper.reset(new SignalProcessing::Stepper(_event_interval.clock_rate, _input_clock_rate)); + } +} diff --git a/Storage/TimedEventLoop.hpp b/Storage/TimedEventLoop.hpp new file mode 100644 index 000000000..6c64158c2 --- /dev/null +++ b/Storage/TimedEventLoop.hpp @@ -0,0 +1,40 @@ +// +// TimedEventLoop.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TimedEventLoop_hpp +#define TimedEventLoop_hpp + +#include "Storage.hpp" + +#include +#include "../SignalProcessing/Stepper.hpp" + +namespace Storage { + + class TimedEventLoop { + public: + TimedEventLoop(unsigned int input_clock_rate); + void run_for_cycles(unsigned int number_of_cycles); + + protected: + void reset_timer(); + void set_next_event_time_interval(Time interval); + void jump_to_next_event(); + + virtual void process_next_event() = 0; + + private: + unsigned int _input_clock_rate; + Time _event_interval; + std::unique_ptr _stepper; + uint32_t _time_into_interval; + }; + +} + +#endif /* TimedEventLoop_hpp */