1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-24 12:30:17 +00:00

Factored out the PCM track since it's going to be a useful construct for almost every file format. Documented it a little better.

This commit is contained in:
Thomas Harte 2016-07-10 18:36:52 -04:00
parent 66895d3ac7
commit 1e9eedc314
6 changed files with 201 additions and 125 deletions

View File

@ -44,6 +44,7 @@
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; }; 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */; };
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BAB62B31D327F7E00DF5BA0 /* G64.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 */; }; 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 */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; };
4BB298F11B587D8400A49093 /* start in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E51B587D8300A49093 /* start */; }; 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 = "<group>"; }; 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.hpp; sourceTree = "<group>"; };
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = G64.cpp; sourceTree = "<group>"; }; 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = G64.cpp; sourceTree = "<group>"; };
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; }; 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = G64.hpp; sourceTree = "<group>"; };
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMTrack.cpp; sourceTree = "<group>"; };
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMTrack.hpp; sourceTree = "<group>"; };
4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = "<group>"; }; 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = "<group>"; };
4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = "<group>"; }; 4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = "<group>"; };
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; }; 4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
@ -984,6 +987,8 @@
4BAB62B21D327F7E00DF5BA0 /* Formats */, 4BAB62B21D327F7E00DF5BA0 /* Formats */,
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
); );
path = Disk; path = Disk;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1869,6 +1874,7 @@
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,

View File

@ -22,6 +22,13 @@ namespace Storage {
*/ */
class Track { class Track {
public: 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 { struct Event {
enum { enum {
IndexHole, FluxTransition IndexHole, FluxTransition
@ -29,6 +36,9 @@ class Track {
Time length; Time length;
}; };
/*!
Returns the next event that will be detected during rotation of this disk.
*/
virtual Event get_next_event() = 0; virtual Event get_next_event() = 0;
}; };

View File

@ -8,6 +8,9 @@
#include "G64.hpp" #include "G64.hpp"
#include <vector>
#include "../PCMTrack.hpp"
using namespace Storage; using namespace Storage;
G64::G64(const char *file_name) G64::G64(const char *file_name)
@ -147,96 +150,3 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
return resulting_track; 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<PCMSegment> 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;
}

View File

@ -10,41 +10,9 @@
#define G64_hpp #define G64_hpp
#include "../Disk.hpp" #include "../Disk.hpp"
#include <vector>
namespace Storage { namespace Storage {
struct PCMSegment {
Time duration;
std::unique_ptr<uint8_t> data;
};
class PCMTrack: public Track {
public:
PCMTrack(std::vector<PCMSegment> segments);
PCMTrack(PCMSegment segment);
virtual Event get_next_event();
private:
// storage for the segments that describe this track
std::vector<PCMSegment> _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 { class G64: public Disk {
public: public:
G64(const char *file_name); G64(const char *file_name);

102
Storage/Disk/PCMTrack.cpp Normal file
View File

@ -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<PCMSegment> 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;
}

80
Storage/Disk/PCMTrack.hpp Normal file
View File

@ -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 <vector>
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<uint8_t> 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<PCMSegment> 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<PCMSegment> _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 */