diff --git a/NumberTheory/Factors.cpp b/NumberTheory/Factors.cpp new file mode 100644 index 000000000..067615653 --- /dev/null +++ b/NumberTheory/Factors.cpp @@ -0,0 +1,34 @@ +// +// Factors.cpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Factors.hpp" + +unsigned int NumberTheory::greatest_common_divisor(unsigned int a, unsigned int b) +{ + if(a < b) + { + unsigned int swap = b; + b = a; + a = swap; + } + + while(1) { + if(!a) return b; + if(!b) return a; + + unsigned int remainder = a%b; + a = b; + b = remainder; + } +} + +unsigned int NumberTheory::least_common_multiple(unsigned int a, unsigned int b) +{ + unsigned int gcd = greatest_common_divisor(a, b); + return (a*b) / gcd; +} diff --git a/NumberTheory/Factors.hpp b/NumberTheory/Factors.hpp new file mode 100644 index 000000000..bdc92c011 --- /dev/null +++ b/NumberTheory/Factors.hpp @@ -0,0 +1,17 @@ +// +// Factors.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Factors_hpp +#define Factors_hpp + +namespace NumberTheory { + unsigned int greatest_common_divisor(unsigned int a, unsigned int b); + unsigned int least_common_multiple(unsigned int a, unsigned int b); +} + +#endif /* Factors_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 1b0319dee..43bc778e9 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -314,6 +314,7 @@ 4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; }; 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 */; }; 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 */; }; @@ -700,6 +701,8 @@ 4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = ""; }; 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1291,6 +1294,15 @@ path = "Wolfgang Lorenz 6502 test suite"; sourceTree = ""; }; + 4BB697C81D4B559300248BDF /* NumberTheory */ = { + isa = PBXGroup; + children = ( + 4BB697C51D4B558F00248BDF /* Factors.cpp */, + 4BB697C61D4B558F00248BDF /* Factors.hpp */, + ); + name = NumberTheory; + sourceTree = ""; + }; 4BB73E951B587A5100552FC2 = { isa = PBXGroup; children = ( @@ -1300,6 +1312,7 @@ 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, 4BC9DF4A1D04691600F44158 /* Components */, 4BB73EDC1B587CA500552FC2 /* Machines */, + 4BB697C81D4B559300248BDF /* NumberTheory */, 4B366DFD1B5C165F0026627B /* Outputs */, 4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73E9F1B587A5100552FC2 /* Products */, @@ -1876,6 +1889,7 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, + 4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index f91beb2a0..2137104b6 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -13,11 +13,25 @@ namespace SignalProcessing { +/*! + Allows a repeating action running at an input rate to determine how many times it should + trigger an action that runs at an unrelated output rate; therefore it allows something with one + clock to sample something with another. + + Uses a Bresenham-like error term internally for full-integral storage with no drift. +*/ class Stepper { public: + /*! + Establishes a stepper with a one-to-one conversion rate. + */ Stepper() : Stepper(1,1) {} + /*! + Establishes a stepper that will receive steps at the @c input_rate and dictate the number + of steps that should be taken at the @c output_rate. + */ Stepper(uint64_t output_rate, uint64_t input_rate) : accumulated_error_(0), input_rate_(input_rate), @@ -26,6 +40,11 @@ class Stepper adjustment_up_((int64_t)(output_rate % input_rate) << 1), adjustment_down_((int64_t)input_rate << 1) {} + /*! + Advances one step at the input rate. + + @returns the number of output steps. + */ inline uint64_t step() { uint64_t update = whole_step_; @@ -38,11 +57,34 @@ class Stepper return update; } + /*! + Advances by @c number_of_steps steps at the input rate. + + @returns the number of output steps. + */ + inline uint64_t step(uint64_t number_of_steps) + { + uint64_t update = whole_step_ * number_of_steps; + accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps; + if(accumulated_error_ > 0) + { + update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_); + accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_; + } + return update; + } + + /*! + @returns the output rate. + */ inline uint64_t get_output_rate() { return output_rate_; } + /*! + @returns the input rate. + */ inline uint64_t get_input_rate() { return input_rate_; diff --git a/Storage/Disk/DiskDrive.cpp b/Storage/Disk/DiskDrive.cpp index 762bc16b2..de0dda28d 100644 --- a/Storage/Disk/DiskDrive.cpp +++ b/Storage/Disk/DiskDrive.cpp @@ -12,13 +12,48 @@ using namespace Storage; DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int revolutions_per_minute) : _clock_rate(clock_rate), - _revolutions_per_minute(revolutions_per_minute) {} + _revolutions_per_minute(revolutions_per_minute), + _head_position(0) {} void DiskDrive::set_expected_bit_length(Time bit_length) { _bit_length = bit_length; } +void DiskDrive::set_disk(std::shared_ptr disk) +{ + _disk = disk; +// _track.reset(); +} + +bool DiskDrive::has_disk() +{ + return (bool)_disk; +} + +bool DiskDrive::get_is_track_zero() +{ + return _head_position == 0; +} + +void DiskDrive::step(int direction) +{ + _head_position = std::max(_head_position + direction, 0); + _track = _disk->get_track_at_position((unsigned int)_head_position); +} + void DiskDrive::digital_phase_locked_loop_output_bit(int value) { + process_input_bit(value, _cycles_since_index_hole); +} + +void DiskDrive::run_for_cycles(unsigned int number_of_cycles) +{ + if(has_disk()) + { + while(number_of_cycles--) + { + + } + } } diff --git a/Storage/Disk/DiskDrive.hpp b/Storage/Disk/DiskDrive.hpp index de4b6a551..c3e857ec8 100644 --- a/Storage/Disk/DiskDrive.hpp +++ b/Storage/Disk/DiskDrive.hpp @@ -11,6 +11,7 @@ #include "Disk.hpp" #include "DigitalPhaseLockedLoop.hpp" +#include "../../SignalProcessing/Stepper.hpp" namespace Storage { @@ -40,6 +41,20 @@ class DiskDrive: public DigitalPhaseLockedLoop::Delegate { Time _bit_length; unsigned int _clock_rate; unsigned int _revolutions_per_minute; + + std::shared_ptr _pll; + std::shared_ptr _disk; + std::shared_ptr _track; + int _head_position; + unsigned int _cycles_since_index_hole; + + void install_track(); + + struct { + Track::Event current_event; + std::unique_ptr pulse_stepper; + uint32_t time_into_pulse; + } _input; }; } diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp index d76529d61..77550870f 100644 --- a/Storage/Disk/PCMTrack.cpp +++ b/Storage/Disk/PCMTrack.cpp @@ -7,34 +7,10 @@ // #include "PCMTrack.hpp" +#include "../../NumberTheory/Factors.hpp" using namespace Storage; -unsigned int greatest_common_divisor(unsigned int a, unsigned int b) -{ - if(a < b) - { - unsigned int swap = b; - b = a; - a = swap; - } - - while(1) { - if(!a) return b; - if(!b) return a; - - unsigned int remainder = a%b; - a = b; - b = remainder; - } -} - -unsigned int least_common_multiple(unsigned int a, unsigned int b) -{ - unsigned int gcd = greatest_common_divisor(a, b); - return (a*b) / gcd; -} - PCMTrack::PCMTrack(std::vector segments) { _segments = std::move(segments); @@ -87,7 +63,7 @@ void PCMTrack::fix_length() _track_clock_rate = _segments[0].length_of_a_bit.clock_rate; for(size_t c = 1; c < _segments.size(); c++) { - _track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.clock_rate); + _track_clock_rate = NumberTheory::least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.clock_rate); } // therby determine the total length, storing it to next_event as the track-total divisor diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index 822093b1c..3fea5be4d 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -7,6 +7,7 @@ // #include "Tape.hpp" +#include "../../NumberTheory/Factors.hpp" using namespace Storage; @@ -22,6 +23,8 @@ TapePlayer::TapePlayer(unsigned int input_clock_rate) : void TapePlayer::set_tape(std::shared_ptr tape) { _tape = tape; + _input.pulse_stepper.reset(); + get_next_pulse(); } @@ -32,7 +35,18 @@ bool TapePlayer::has_tape() void TapePlayer::get_next_pulse() { - _input.time_into_pulse = 0; + 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(); else @@ -41,6 +55,24 @@ void TapePlayer::get_next_pulse() _input.current_pulse.length.clock_rate = 1; _input.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)); @@ -51,13 +83,10 @@ void TapePlayer::run_for_cycles(unsigned int number_of_cycles) { if(has_tape()) { - while(number_of_cycles--) + _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(number_of_cycles); + while(_input.time_into_pulse >= _input.current_pulse.length.length) { - _input.time_into_pulse += (unsigned int)_input.pulse_stepper->step(); - while(_input.time_into_pulse >= _input.current_pulse.length.length) - { - run_for_input_pulse(); - } + run_for_input_pulse(); } } } @@ -66,4 +95,5 @@ void TapePlayer::run_for_input_pulse() { process_input_pulse(_input.current_pulse); get_next_pulse(); + _input.time_into_pulse = 0; }