1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-10-04 01:57:54 +00:00

Merge pull request #40 from TomHarte/DiskFileFormats

Establishes the form of a modelled floppy disk plus parts of the most-likely implementation; implemented support for G64 as the first input format
This commit is contained in:
Thomas Harte 2016-07-10 18:37:44 -04:00 committed by GitHub
commit d0200b6fd9
18 changed files with 610 additions and 36 deletions

View File

@ -91,6 +91,11 @@ void Machine::set_rom(const uint8_t *rom)
memcpy(_rom, rom, sizeof(_rom));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
{
_disk = disk;
}
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522)

View File

@ -11,8 +11,11 @@
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace Commodore {
namespace C1540 {
@ -173,6 +176,11 @@ class Machine:
*/
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
/*!
Sets the disk from which this 1540 is reading data.
*/
void set_disk(std::shared_ptr<Storage::Disk> disk);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
@ -186,6 +194,8 @@ class Machine:
std::shared_ptr<SerialPortVIA> _serialPortVIA;
std::shared_ptr<SerialPort> _serialPort;
DriveVIA _driveVIA;
std::shared_ptr<Storage::Disk> _disk;
};
}

View File

@ -54,9 +54,6 @@ Machine::Machine() :
write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory));
write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory));
// TEMPORARY: attach a [diskless] 1540
// set_disc();
// _debugPort.reset(new ::Commodore::Serial::DebugPort);
// _debugPort->set_serial_bus(_serialBus);
// _serialBus->add_port(_debugPort);
@ -212,6 +209,20 @@ void Machine::tape_did_change_input(Tape *tape)
_keyboardVIA->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
}
#pragma mark - Disc
void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
{
// construct the 1540
_c1540.reset(new ::Commodore::C1540::Machine);
// attach it to the serial bus
_c1540->set_serial_bus(_serialBus);
// hand it the disk
_c1540->set_disk(disk);
}
#pragma mark - Typer
int Machine::get_typer_delay()
@ -344,13 +355,3 @@ void Tape::process_input_pulse(Storage::Tape::Pulse pulse)
}
}
#pragma mark - Disc
void Machine::set_disc()
{
// construct the 1540
_c1540.reset(new ::Commodore::C1540::Machine);
// attach it to the serial bus
_c1540->set_serial_bus(_serialBus);
}

View File

@ -9,16 +9,19 @@
#ifndef Vic20_hpp
#define Vic20_hpp
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../1540/C1540.hpp"
#include "../SerialBus.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace Commodore {
namespace Vic20 {
@ -237,7 +240,7 @@ class Machine:
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void add_prg(size_t length, const uint8_t *data);
void set_tape(std::shared_ptr<Storage::Tape> tape);
void set_disc();
void set_disk(std::shared_ptr<Storage::Disk> disk);
void set_key_state(Key key, bool isPressed) { _keyboardVIA->set_key_state(key, isPressed); }
void clear_all_keys() { _keyboardVIA->clear_all_keys(); }

View File

@ -42,6 +42,9 @@
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; };
4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; };
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 */; };
@ -415,6 +418,13 @@
4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = "<group>"; };
4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; };
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = "<group>"; };
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.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>"; };
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>"; };
4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = "<group>"; };
4BB297E51B587D8300A49093 /* start */ = {isa = PBXFileReference; lastKnownFileType = file; path = " start"; sourceTree = "<group>"; };
@ -941,7 +951,9 @@
4B69FB391C4D908A00B5F0AA /* Storage */ = {
isa = PBXGroup;
children = (
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
);
name = Storage;
path = ../../Storage;
@ -969,6 +981,27 @@
path = Formats;
sourceTree = "<group>";
};
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup;
children = (
4BAB62B21D327F7E00DF5BA0 /* Formats */,
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
);
path = Disk;
sourceTree = "<group>";
};
4BAB62B21D327F7E00DF5BA0 /* Formats */ = {
isa = PBXGroup;
children = (
4BAB62B31D327F7E00DF5BA0 /* G64.cpp */,
4BAB62B41D327F7E00DF5BA0 /* G64.hpp */,
);
path = Formats;
sourceTree = "<group>";
};
4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = {
isa = PBXGroup;
children = (
@ -1818,6 +1851,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
@ -1834,11 +1868,13 @@
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
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 */,

View File

@ -48,15 +48,13 @@ class Vic20Document: MachineDocument {
override func readFromURL(url: NSURL, ofType typeName: String) throws {
if let pathExtension = url.pathExtension {
switch pathExtension.lowercaseString {
case "tap":
vic20.openTAPAtURL(url)
return
default: break;
case "tap": vic20.openTAPAtURL(url)
case "g64": vic20.openG64AtURL(url)
default:
let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0))
try self.readFromFileWrapper(fileWrapper, ofType: typeName)
}
}
let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0))
try self.readFromFileWrapper(fileWrapper, ofType: typeName)
}
// MARK: machine setup

View File

@ -80,7 +80,7 @@
<string>prg</string>
</array>
<key>CFBundleTypeName</key>
<string>Vic-20 Cartridge</string>
<string>Commodore Program</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
@ -94,9 +94,25 @@
<string>tap</string>
</array>
<key>CFBundleTypeName</key>
<string>Vic-20 Tape Image</string>
<string>Commodore Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>g64</string>
</array>
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
</dict>

View File

@ -19,6 +19,7 @@
- (void)setPRG:(nonnull NSData *)prg;
- (BOOL)openTAPAtURL:(nonnull NSURL *)URL;
- (BOOL)openG64AtURL:(nonnull NSURL *)URL;
@property (nonatomic, assign) BOOL useFastLoadingHack;

View File

@ -10,6 +10,7 @@
#include "Vic20.hpp"
#include "CommodoreTAP.hpp"
#include "G64.hpp"
using namespace Commodore::Vic20;
@ -56,6 +57,17 @@ using namespace Commodore::Vic20;
}
}
- (BOOL)openG64AtURL:(NSURL *)URL {
@synchronized(self) {
try {
std::shared_ptr<Storage::G64> disk(new Storage::G64([URL fileSystemRepresentation]));
_vic20.set_disk(disk);
return YES;
} catch(int exception) {
return NO;
}
}
}
- (void)setPRG:(nonnull NSData *)prg {
@synchronized(self) {

9
Storage/Disk/Disk.cpp Normal file
View File

@ -0,0 +1,9 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Disk.hpp"

76
Storage/Disk/Disk.hpp Normal file
View File

@ -0,0 +1,76 @@
//
// Disk.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Disk_hpp
#define Disk_hpp
#include <memory>
#include "../Storage.hpp"
namespace Storage {
/*!
Models a single track on a disk as a series of events, each event being of arbitrary length
and resulting in either a flux transition or the sensing of an index hole.
Subclasses should implement @c get_next_event.
*/
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
} type;
Time length;
};
/*!
Returns the next event that will be detected during rotation of this disk.
*/
virtual Event get_next_event() = 0;
};
/*!
Models a disk as a collection of tracks, providing a range of possible track positions and allowing
a point sampling of the track beneath any of those positions (if any).
The intention is not that tracks necessarily be evenly spaced; a head_position_count of 3 wih track
A appearing in positions 0 and 1, and track B appearing in position 2 is an appropriate use of this API
if it matches the media.
The track returned is point sampled only; if a particular disk drive has a sufficiently large head to
pick up multiple tracks at once then the drive responsible for asking for multiple tracks and for
merging the results.
*/
class Disk {
public:
/*!
Returns the number of discrete positions that this disk uses to model its complete surface area.
This is not necessarily a track count. There is no implicit guarantee that every position will
return a distinct track, or if the media is holeless will return any track at all.
*/
virtual unsigned int get_head_position_count() = 0;
/*!
Returns the @c Track at @c position if there are any detectable events there; returns @c nullptr otherwise.
*/
virtual std::shared_ptr<Track> get_track_at_position(unsigned int position) = 0;
};
}
#endif /* Disk_hpp */

View File

@ -0,0 +1,152 @@
//
// G64.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "G64.hpp"
#include <vector>
#include "../PCMTrack.hpp"
using namespace Storage;
G64::G64(const char *file_name)
{
_file = fopen(file_name, "rb");
if(!_file)
throw ErrorNotGCR;
// read and check the file signature
char signature[8];
if(fread(signature, 1, 8, _file) != 8)
throw ErrorNotGCR;
if(memcmp(signature, "GCR-1541", 8))
throw ErrorNotGCR;
// check the version number
int version = fgetc(_file);
if(version != 0)
{
throw ErrorUnknownVersion;
}
// get the number of tracks and track size
_number_of_tracks = (uint8_t)fgetc(_file);
_maximum_track_size = (uint16_t)fgetc(_file);
_maximum_track_size |= (uint16_t)fgetc(_file) << 8;
get_track_at_position(0);
}
G64::~G64()
{
if(_file) fclose(_file);
}
unsigned int G64::get_head_position_count()
{
// give at least 84 tracks, to yield the normal geometry but,
// if there are more, shove them in
return _number_of_tracks > 84 ? _number_of_tracks : 84;
}
std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
{
std::shared_ptr<Track> resulting_track;
// if there's definitely no track here, return the empty track
// (TODO: should be supplying one with an index hole?)
if(position >= _number_of_tracks) return resulting_track;
// seek to this track's entry in the track table
fseek(_file, (long)((position * 4) + 0xc), SEEK_SET);
// read the track offset
uint32_t track_offset;
track_offset = (uint32_t)fgetc(_file);
track_offset |= (uint32_t)fgetc(_file) << 8;
track_offset |= (uint32_t)fgetc(_file) << 16;
track_offset |= (uint32_t)fgetc(_file) << 24;
// if the track offset is zero, this track doesn't exist, so...
if(!track_offset) return resulting_track;
// seek to the track start
fseek(_file, (int)track_offset, SEEK_SET);
// get the real track length
uint16_t track_length;
track_length = (uint16_t)fgetc(_file);
track_length |= (uint16_t)fgetc(_file) << 8;
// grab the byte contents of this track
std::unique_ptr<uint8_t> track_contents(new uint8_t[track_length]);
fread(track_contents.get(), 1, track_length, _file);
// seek to this track's entry in the speed zone table
fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET);
// read the speed zone offsrt
uint32_t speed_zone_offset;
speed_zone_offset = (uint32_t)fgetc(_file);
speed_zone_offset |= (uint32_t)fgetc(_file) << 8;
speed_zone_offset |= (uint32_t)fgetc(_file) << 16;
speed_zone_offset |= (uint32_t)fgetc(_file) << 24;
// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
if(speed_zone_offset > 3)
{
// seek to start of speed zone
fseek(_file, (int)speed_zone_offset, SEEK_SET);
uint16_t speed_zone_length = (track_length + 3) >> 2;
// read the speed zone bytes
uint8_t speed_zone_contents[speed_zone_length];
fread(speed_zone_contents, 1, speed_zone_length, _file);
// divide track into appropriately timed PCMSegments
std::vector<PCMSegment> segments;
unsigned int current_speed = speed_zone_contents[0] >> 6;
unsigned int start_byte_in_current_speed = 0;
for(unsigned int byte = 0; byte < track_length; byte ++)
{
unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
if(byte_speed != current_speed || byte == (track_length-1))
{
unsigned int number_of_bytes = byte - start_byte_in_current_speed;
PCMSegment segment;
segment.duration.length = number_of_bytes * 8;
segment.duration.clock_rate = 4000000 / (13 + current_speed); // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16; TODO: is this the right way around? Is zone 3 the fastest or the slowest?
segment.data.reset(new uint8_t[number_of_bytes]);
memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes);
segments.push_back(std::move(segment));
current_speed = byte_speed;
start_byte_in_current_speed = byte;
}
}
resulting_track.reset(new PCMTrack(std::move(segments)));
}
else
{
PCMSegment segment;
segment.duration.length = track_length * 8;
segment.duration.clock_rate = 1; // this is arbitrary; if supplying only one PCMSegment then it'll naturally fill the track
segment.data = std::move(track_contents);
resulting_track.reset(new PCMTrack(std::move(segment)));
}
// TODO: find out whether it's possible for a G64 to supply only a partial track. I don't think it is, which would make the
// above correct but supposing I'm wrong, the above would produce some incorrectly clocked tracks
return resulting_track;
}

View File

@ -0,0 +1,38 @@
//
// G64.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef G64_hpp
#define G64_hpp
#include "../Disk.hpp"
namespace Storage {
class G64: public Disk {
public:
G64(const char *file_name);
~G64();
enum {
ErrorNotGCR,
ErrorUnknownVersion
};
unsigned int get_head_position_count();
std::shared_ptr<Track> get_track_at_position(unsigned int position);
private:
FILE *_file;
uint8_t _number_of_tracks;
uint16_t _maximum_track_size;
};
};
#endif /* G64_hpp */

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 */

20
Storage/Storage.hpp Normal file
View File

@ -0,0 +1,20 @@
//
// Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_hpp
#define Storage_hpp
namespace Storage {
struct Time {
unsigned int length, clock_rate;
};
}
#endif /* Storage_h */

View File

@ -10,7 +10,7 @@
using namespace Storage;
void Tape::seek(Tape::Time seek_time)
void Tape::seek(Time seek_time)
{
// TODO: as best we can
}

View File

@ -11,15 +11,23 @@
#include <memory>
#include "../../SignalProcessing/Stepper.hpp"
#include "../Storage.hpp"
namespace Storage {
/*!
Models a tape as a sequence of pulses, each pulse being of arbitrary length and described
by their relationship with zero:
- high pulses exit from zero upward before returning to it;
- low pulses exit from zero downward before returning to it;
- zero pulses run along zero.
Subclasses should implement at least @c get_next_pulse and @c reset to provide a serial feeding
of pulses and the ability to return to the start of the feed. They may also implement @c seek if
a better implementation than a linear search from the @c reset time can be implemented.
*/
class Tape {
public:
struct Time {
unsigned int length, clock_rate;
};
struct Pulse {
enum {
High, Low, Zero
@ -30,9 +38,16 @@ class Tape {
virtual Pulse get_next_pulse() = 0;
virtual void reset() = 0;
virtual void seek(Time seek_time);
virtual void seek(Time seek_time); // TODO
};
/*!
Provides a helper for: (i) retaining a reference to a tape; and (ii) running the tape at a certain
input clock rate.
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 {
public:
TapePlayer(unsigned int input_clock_rate);