mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +00:00
Made an attempt to eliminate creeping tape processing accuracy misses, which implied factoring out the GCM and LCD functions, which I then felt didn't really amount to signal processing.
This commit is contained in:
parent
5c1614ce7b
commit
e55db0cfe8
34
NumberTheory/Factors.cpp
Normal file
34
NumberTheory/Factors.cpp
Normal file
@ -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;
|
||||
}
|
17
NumberTheory/Factors.hpp
Normal file
17
NumberTheory/Factors.hpp
Normal file
@ -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 */
|
@ -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 = "<group>"; };
|
||||
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
|
||||
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
|
||||
4BB697C51D4B558F00248BDF /* Factors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Factors.cpp; path = ../../NumberTheory/Factors.cpp; sourceTree = "<group>"; };
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; };
|
||||
@ -1291,6 +1294,15 @@
|
||||
path = "Wolfgang Lorenz 6502 test suite";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BB697C81D4B559300248BDF /* NumberTheory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BB697C51D4B558F00248BDF /* Factors.cpp */,
|
||||
4BB697C61D4B558F00248BDF /* Factors.hpp */,
|
||||
);
|
||||
name = NumberTheory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
@ -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_;
|
||||
|
@ -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 = 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--)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<DigitalPhaseLockedLoop> _pll;
|
||||
std::shared_ptr<Disk> _disk;
|
||||
std::shared_ptr<Track> _track;
|
||||
int _head_position;
|
||||
unsigned int _cycles_since_index_hole;
|
||||
|
||||
void install_track();
|
||||
|
||||
struct {
|
||||
Track::Event current_event;
|
||||
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
|
||||
uint32_t time_into_pulse;
|
||||
} _input;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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<PCMSegment> 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
|
||||
|
@ -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<Storage::Tape> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user