From 1e9eedc3141384bb063d75c373d9e551c54fb8f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 18:36:52 -0400 Subject: [PATCH] Factored out the PCM track since it's going to be a useful construct for almost every file format. Documented it a little better. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 ++ Storage/Disk/Disk.hpp | 10 ++ Storage/Disk/Formats/G64.cpp | 96 +---------------- Storage/Disk/Formats/G64.hpp | 32 ------ Storage/Disk/PCMTrack.cpp | 102 ++++++++++++++++++ Storage/Disk/PCMTrack.hpp | 80 ++++++++++++++ 6 files changed, 201 insertions(+), 125 deletions(-) create mode 100644 Storage/Disk/PCMTrack.cpp create mode 100644 Storage/Disk/PCMTrack.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 36ad78477..06204d82f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */; }; + 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; 4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; @@ -422,6 +423,8 @@ 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = ""; }; 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = G64.cpp; sourceTree = ""; }; 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = ""; }; + 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = ""; }; + 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = ""; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = ""; }; 4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = ""; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = ""; }; @@ -984,6 +987,8 @@ 4BAB62B21D327F7E00DF5BA0 /* Formats */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, + 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, + 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, ); path = Disk; sourceTree = ""; @@ -1869,6 +1874,7 @@ 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, + 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp index 614169431..7aaf98f79 100644 --- a/Storage/Disk/Disk.hpp +++ b/Storage/Disk/Disk.hpp @@ -22,6 +22,13 @@ namespace Storage { */ class Track { public: + /*! + Describes a detectable track event — either a flux transition or the passing of the index hole, + along with the length of time between the previous event and its occurance. + + The sum of all lengths of time across an entire track should be 1 — if an event is said to be + 1/3 away then that means 1/3 of a rotation. + */ struct Event { enum { IndexHole, FluxTransition @@ -29,6 +36,9 @@ class Track { Time length; }; + /*! + Returns the next event that will be detected during rotation of this disk. + */ virtual Event get_next_event() = 0; }; diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index e0806818c..67b243137 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -8,6 +8,9 @@ #include "G64.hpp" +#include +#include "../PCMTrack.hpp" + using namespace Storage; G64::G64(const char *file_name) @@ -147,96 +150,3 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) return resulting_track; } - -#pragma mark - PCMTrack - -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); - fix_length(); -} - -PCMTrack::PCMTrack(PCMSegment segment) -{ - _segments.push_back(std::move(segment)); - fix_length(); -} - -PCMTrack::Event PCMTrack::get_next_event() -{ - // find the next 1 in the input stream, keeping count of length as we go, and assuming it's going - // to be a flux transition - _next_event.type = Track::Event::FluxTransition; - _next_event.length.length = 0; - while(_segment_pointer < _segments.size()) - { - unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].duration.clock_rate; - const uint8_t *segment_data = _segments[_segment_pointer].data.get(); - while(_bit_pointer < _segments[_segment_pointer].duration.length) - { - // for timing simplicity, bits are modelled as happening at the end of their window - // TODO: should I account for the converse bit ordering? Or can I assume MSB first? - int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7)); - _bit_pointer++; - _next_event.length.length += clock_multiplier; - - if(bit) return _next_event; - } - _bit_pointer = 0; - _segment_pointer++; - } - - // check whether we actually reached the index hole - if(_segment_pointer == _segments.size()) - { - _segment_pointer = 0; - _next_event.type = Track::Event::IndexHole; - } - - return _next_event; -} - -void PCMTrack::fix_length() -{ - // find the least common multiple of all segment clock rates - _track_clock_rate = _segments[0].duration.clock_rate; - for(size_t c = 1; c < _segments.size(); c++) - { - _track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].duration.clock_rate); - } - - // therby determine the total length, storing it to next_event as the divisor - _next_event.length.clock_rate = 0; - for(size_t c = 0; c < _segments.size(); c++) - { - unsigned int multiplier = _track_clock_rate / _segments[c].duration.clock_rate; - _next_event.length.clock_rate += _segments[c].duration.length * multiplier; - } - - _segment_pointer = _bit_pointer = 0; -} diff --git a/Storage/Disk/Formats/G64.hpp b/Storage/Disk/Formats/G64.hpp index 8d97ddd3f..406453039 100644 --- a/Storage/Disk/Formats/G64.hpp +++ b/Storage/Disk/Formats/G64.hpp @@ -10,41 +10,9 @@ #define G64_hpp #include "../Disk.hpp" -#include namespace Storage { -struct PCMSegment { - Time duration; - std::unique_ptr data; -}; - -class PCMTrack: public Track { - public: - PCMTrack(std::vector segments); - PCMTrack(PCMSegment segment); - - virtual Event get_next_event(); - - private: - // storage for the segments that describe this track - std::vector _segments; - - // a helper to determine the overall track clock rate and it's length - void fix_length(); - - // the event perpetually returned; impliedly contains the length of the entire track - // as its clock rate, per the need for everything on a Track to sum to a length of 1 - PCMTrack::Event _next_event; - - // contains the master clock rate - unsigned int _track_clock_rate; - - // a pointer to the first bit to consider as the next event - size_t _segment_pointer; - size_t _bit_pointer; -}; - class G64: public Disk { public: G64(const char *file_name); diff --git a/Storage/Disk/PCMTrack.cpp b/Storage/Disk/PCMTrack.cpp new file mode 100644 index 000000000..a25d31527 --- /dev/null +++ b/Storage/Disk/PCMTrack.cpp @@ -0,0 +1,102 @@ +// +// PCMTrack.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "PCMTrack.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); + fix_length(); +} + +PCMTrack::PCMTrack(PCMSegment segment) +{ + _segments.push_back(std::move(segment)); + fix_length(); +} + +PCMTrack::Event PCMTrack::get_next_event() +{ + // find the next 1 in the input stream, keeping count of length as we go, and assuming it's going + // to be a flux transition + _next_event.type = Track::Event::FluxTransition; + _next_event.length.length = 0; + while(_segment_pointer < _segments.size()) + { + unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].duration.clock_rate; + const uint8_t *segment_data = _segments[_segment_pointer].data.get(); + while(_bit_pointer < _segments[_segment_pointer].duration.length) + { + // for timing simplicity, bits are modelled as happening at the end of their window + // TODO: should I account for the converse bit ordering? Or can I assume MSB first? + int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7)); + _bit_pointer++; + _next_event.length.length += clock_multiplier; + + if(bit) return _next_event; + } + _bit_pointer = 0; + _segment_pointer++; + } + + // check whether we actually reached the index hole + if(_segment_pointer == _segments.size()) + { + _segment_pointer = 0; + _next_event.type = Track::Event::IndexHole; + } + + return _next_event; +} + +void PCMTrack::fix_length() +{ + // find the least common multiple of all segment clock rates + _track_clock_rate = _segments[0].duration.clock_rate; + for(size_t c = 1; c < _segments.size(); c++) + { + _track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].duration.clock_rate); + } + + // therby determine the total length, storing it to next_event as the divisor + _next_event.length.clock_rate = 0; + for(size_t c = 0; c < _segments.size(); c++) + { + unsigned int multiplier = _track_clock_rate / _segments[c].duration.clock_rate; + _next_event.length.clock_rate += _segments[c].duration.length * multiplier; + } + + _segment_pointer = _bit_pointer = 0; +} diff --git a/Storage/Disk/PCMTrack.hpp b/Storage/Disk/PCMTrack.hpp new file mode 100644 index 000000000..0cfc0f451 --- /dev/null +++ b/Storage/Disk/PCMTrack.hpp @@ -0,0 +1,80 @@ +// +// PCMTrack.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef PCMTrack_hpp +#define PCMTrack_hpp + +#include "Disk.hpp" +#include + +namespace Storage { + +/*! + A segment of PCM-sampled data. The clock rate in the duration is taken to be relative to all other + segments that comprise a track rather than absolute, and the length is taken to be the number of + bits from @c data that are actually present. + + Bits from each byte are taken MSB to LSB. + + Actual segment lengths will be calculated such that all segments that comprise a track exactly fill the track. + + So the segment for a track with only a single segment may supply any clock rate other than 0. It will exactly + fill the track, so if it has 7 samples then there will be at most a flux transition every 1/7th of a rotation. + + If a track consists of two segments, one with clock rate 1 and one with clock rate 2, the second will be + clocked twice as fast as the first. +*/ +struct PCMSegment { + Time duration; + std::unique_ptr data; +}; + +/*! + A subclass of @c Track that provides its @c Events by querying a pulse-code modulated record of original + flux detections, with an implied index hole at the very start of the data. + + The data may consist of a single @c PCMSegment or of multiple, allowing a PCM-format track to contain + multiple distinct segments of data, each with a separate clock rate. +*/ +class PCMTrack: public Track { + public: + /*! + Creates a @c PCMTrack consisting of multiple segments of data, permitting multiple clock rates. + */ + PCMTrack(std::vector segments); + + /*! + Creates a @c PCMTrack consisting of a single continuous run of data, implying a constant clock rate. + */ + PCMTrack(PCMSegment segment); + + // as per @c Track + Event get_next_event(); + + private: + // storage for the segments that describe this track + std::vector _segments; + + // a helper to determine the overall track clock rate and it's length + void fix_length(); + + // the event perpetually returned; impliedly contains the length of the entire track + // as its clock rate, per the need for everything on a Track to sum to a length of 1 + PCMTrack::Event _next_event; + + // contains the master clock rate + unsigned int _track_clock_rate; + + // a pointer to the first bit to consider as the next event + size_t _segment_pointer; + size_t _bit_pointer; +}; + +} + +#endif /* PCMTrack_hpp */