From 9e3d6b762b4dd6da3c3bcb9f999bc116ff260879 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 08:54:39 -0400 Subject: [PATCH 01/12] Sketched out the generic interface for a disk, documenting it and the tape interface while I'm here. --- .../Clock Signal.xcodeproj/project.pbxproj | 16 +++++ Storage/Disk/Disk.cpp | 9 +++ Storage/Disk/Disk.hpp | 66 +++++++++++++++++++ Storage/Storage.hpp | 20 ++++++ Storage/Tape/Tape.cpp | 2 +- Storage/Tape/Tape.hpp | 25 +++++-- 6 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 Storage/Disk/Disk.cpp create mode 100644 Storage/Disk/Disk.hpp create mode 100644 Storage/Storage.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 302bceebd..69ac802b5 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 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 */; }; 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 +416,9 @@ 4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = ""; }; 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 = ""; }; + 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = ""; }; + 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; + 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Storage.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 = ""; }; @@ -941,7 +945,9 @@ 4B69FB391C4D908A00B5F0AA /* Storage */ = { isa = PBXGroup; children = ( + 4BAB62AA1D3272D200DF5BA0 /* Disk */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, + 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, ); name = Storage; path = ../../Storage; @@ -969,6 +975,15 @@ path = Formats; sourceTree = ""; }; + 4BAB62AA1D3272D200DF5BA0 /* Disk */ = { + isa = PBXGroup; + children = ( + 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, + 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, + ); + path = Disk; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -1818,6 +1833,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 */, diff --git a/Storage/Disk/Disk.cpp b/Storage/Disk/Disk.cpp new file mode 100644 index 000000000..f431bba4d --- /dev/null +++ b/Storage/Disk/Disk.cpp @@ -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" diff --git a/Storage/Disk/Disk.hpp b/Storage/Disk/Disk.hpp new file mode 100644 index 000000000..614169431 --- /dev/null +++ b/Storage/Disk/Disk.hpp @@ -0,0 +1,66 @@ +// +// 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 +#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: + struct Event { + enum { + IndexHole, FluxTransition + } type; + Time length; + }; + + 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 get_track_at_position(unsigned int position) = 0; +}; + +} + +#endif /* Disk_hpp */ diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp new file mode 100644 index 000000000..ac65da093 --- /dev/null +++ b/Storage/Storage.hpp @@ -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 */ diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index b1a9babde..822093b1c 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -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 } diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index eecae2862..47babe011 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -11,15 +11,23 @@ #include #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); From ff49857f5c4ce1bd9437fbf54cdc6457a5f84d06 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 10:17:53 -0400 Subject: [PATCH 02/12] Started sketching out support for the G64 file format. --- .../Clock Signal.xcodeproj/project.pbxproj | 14 +++ Storage/Disk/Formats/G64.cpp | 91 +++++++++++++++++++ Storage/Disk/Formats/G64.hpp | 41 +++++++++ 3 files changed, 146 insertions(+) create mode 100644 Storage/Disk/Formats/G64.cpp create mode 100644 Storage/Disk/Formats/G64.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 69ac802b5..36ad78477 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 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 */; }; 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 */; }; @@ -419,6 +420,8 @@ 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Disk.cpp; sourceTree = ""; }; 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Disk.hpp; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -978,12 +981,22 @@ 4BAB62AA1D3272D200DF5BA0 /* Disk */ = { isa = PBXGroup; children = ( + 4BAB62B21D327F7E00DF5BA0 /* Formats */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, ); path = Disk; sourceTree = ""; }; + 4BAB62B21D327F7E00DF5BA0 /* Formats */ = { + isa = PBXGroup; + children = ( + 4BAB62B31D327F7E00DF5BA0 /* G64.cpp */, + 4BAB62B41D327F7E00DF5BA0 /* G64.hpp */, + ); + path = Formats; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -1850,6 +1863,7 @@ 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 */, diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp new file mode 100644 index 000000000..15bfd98a3 --- /dev/null +++ b/Storage/Disk/Formats/G64.cpp @@ -0,0 +1,91 @@ +// +// G64.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/07/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "G64.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) != 12) + 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; +} + +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 G64::get_track_at_position(unsigned int position) +{ + std::shared_ptr 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, SEEK_SET, (int)((position * 4) + 0xc)); + + // 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, SEEK_SET, (int)track_offset); + + // 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 + uint8_t track_contents[track_length]; + fread(track_contents, 1, track_length, _file); + + // check for speed-zone contents + + // TODO: package track_contents and speed_zones into a PCM track + + return resulting_track; +} diff --git a/Storage/Disk/Formats/G64.hpp b/Storage/Disk/Formats/G64.hpp new file mode 100644 index 000000000..0a6aa2c96 --- /dev/null +++ b/Storage/Disk/Formats/G64.hpp @@ -0,0 +1,41 @@ +// +// 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 PCMTrack: public Track { +}; + +class G64: public Disk { + public: + G64(const char *file_name); + ~G64(); + + enum { + ErrorNotGCR, + ErrorUnknownVersion + }; + + unsigned int get_head_position_count(); + std::shared_ptr get_track_at_position(unsigned int position); + + private: + FILE *_file; + + uint8_t _number_of_tracks; + uint16_t _maximum_track_size; +}; + +}; + +#endif /* G64_hpp */ From 6cfc514c2d022569fdea1d43b7e2c346e6fcc0b9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 12:57:17 -0400 Subject: [PATCH 03/12] Made the rote changes necessary to attempt to open and to supply a G64 to the Vic. --- Machines/Commodore/Vic-20/Vic20.cpp | 23 +++++++++++-------- Machines/Commodore/Vic-20/Vic20.hpp | 19 ++++++++------- .../Documents/Vic20Document.swift | 12 ++++------ OSBindings/Mac/Clock Signal/Info.plist | 20 ++++++++++++++-- .../Clock Signal/Machine/Wrappers/CSVic20.h | 1 + .../Clock Signal/Machine/Wrappers/CSVic20.mm | 12 ++++++++++ 6 files changed, 60 insertions(+), 27 deletions(-) diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 6a44f64e0..78107cf60 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -212,6 +212,19 @@ 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 disk) +{ + // construct the 1540 + _c1540.reset(new ::Commodore::C1540::Machine); + + // attach it to the serial bus + _c1540->set_serial_bus(_serialBus); + + // TODO: push the disk to the C1540 +} + #pragma mark - Typer int Machine::get_typer_delay() @@ -344,13 +357,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); -} diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index fcbac0fbf..279fa5249 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -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 tape); - void set_disc(); + void set_disk(std::shared_ptr disk); void set_key_state(Key key, bool isPressed) { _keyboardVIA->set_key_state(key, isPressed); } void clear_all_keys() { _keyboardVIA->clear_all_keys(); } diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index e74f67274..42e9b97ed 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -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 diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index ade36e2d2..a8ce924f6 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -80,7 +80,7 @@ prg CFBundleTypeName - Vic-20 Cartridge + Commodore Program CFBundleTypeRole Viewer LSTypeIsPackage @@ -94,9 +94,25 @@ tap CFBundleTypeName - Vic-20 Tape Image + Commodore Tape Image CFBundleTypeRole Viewer + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).Vic20Document + + + CFBundleTypeExtensions + + g64 + + CFBundleTypeName + Commodore Disk + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 NSDocumentClass $(PRODUCT_MODULE_NAME).Vic20Document diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index 83f3c7863..ae0dfc86c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -19,6 +19,7 @@ - (void)setPRG:(nonnull NSData *)prg; - (BOOL)openTAPAtURL:(nonnull NSURL *)URL; +- (BOOL)openG64AtURL:(nonnull NSURL *)URL; @property (nonatomic, assign) BOOL useFastLoadingHack; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index 7d7cffc8e..67ec6ace9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -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 disk(new Storage::G64([URL fileSystemRepresentation])); + _vic20.set_disk(disk); + return YES; + } catch(int exception) { + return NO; + } + } +} - (void)setPRG:(nonnull NSData *)prg { @synchronized(self) { From 8ae78ba4e0da6557bad2948313c5194040cdd8e4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 13:32:59 -0400 Subject: [PATCH 04/12] Fixed signature check and `fseek` parameter order. --- Storage/Disk/Formats/G64.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 15bfd98a3..2c88a9e96 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -19,7 +19,7 @@ G64::G64(const char *file_name) // read and check the file signature char signature[8]; - if(fread(signature, 1, 8, _file) != 12) + if(fread(signature, 1, 8, _file) != 8) throw ErrorNotGCR; if(memcmp(signature, "GCR-1541", 8)) @@ -59,7 +59,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) if(position >= _number_of_tracks) return resulting_track; // seek to this track's entry in the track table - fseek(_file, SEEK_SET, (int)((position * 4) + 0xc)); + long offset = (long)((position * 4) + 0xc); + fseek(_file, offset, SEEK_SET); // read the track offset uint32_t track_offset; From 4ae9f5ad5d051735814ae459fb61961271aee1b6 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 13:42:45 -0400 Subject: [PATCH 05/12] Added preliminaries of reading the speed zone information. --- Storage/Disk/Formats/G64.cpp | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 2c88a9e96..e53f56e5e 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -59,8 +59,7 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) if(position >= _number_of_tracks) return resulting_track; // seek to this track's entry in the track table - long offset = (long)((position * 4) + 0xc); - fseek(_file, offset, SEEK_SET); + fseek(_file, (long)((position * 4) + 0xc), SEEK_SET); // read the track offset uint32_t track_offset; @@ -73,7 +72,7 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) if(!track_offset) return resulting_track; // seek to the track start - fseek(_file, SEEK_SET, (int)track_offset); + fseek(_file, (int)track_offset, SEEK_SET); // get the real track length uint16_t track_length; @@ -84,9 +83,31 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) uint8_t track_contents[track_length]; fread(track_contents, 1, track_length, _file); - // check for speed-zone contents + // seek to this track's entry in the speed zone table + fseek(_file, (long)((position * 4) + 0x15c), SEEK_SET); - // TODO: package track_contents and speed_zones into a PCM track + // 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); + } + else + { + } return resulting_track; } From f9510c1b6732b9df6b0d718ebc59680244f90817 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 16:10:05 -0400 Subject: [PATCH 06/12] Put sufficiently much of `PCMTrack` into place to get to a stored list of segments, having determined a common clock rate between them and therefore a complete track length. --- Storage/Disk/Formats/G64.cpp | 77 +++++++++++++++++++++++++++++++++++- Storage/Disk/Formats/G64.hpp | 29 ++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index e53f56e5e..8a321ee66 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -80,8 +80,8 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) track_length |= (uint16_t)fgetc(_file) << 8; // grab the byte contents of this track - uint8_t track_contents[track_length]; - fread(track_contents, 1, track_length, _file); + std::unique_ptr 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); @@ -104,10 +104,83 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) // read the speed zone bytes uint8_t speed_zone_contents[speed_zone_length]; fread(speed_zone_contents, 1, speed_zone_length, _file); + + // TODO: divide into individual PCMSegments (per byte, if necessary), shove into a PCMTrack } else { + PCMSegment segment; + segment.duration.length = track_length * 8; + segment.duration.clock_rate = track_length * 8; + segment.data = std::move(track_contents); + + resulting_track.reset(new PCMTrack(std::move(segment))); } 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() +{ + PCMTrack::Event new_event; + return new_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 0a6aa2c96..8d97ddd3f 100644 --- a/Storage/Disk/Formats/G64.hpp +++ b/Storage/Disk/Formats/G64.hpp @@ -10,10 +10,39 @@ #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 { From 845a00ccef5595194685b01416377a3308de834a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 16:17:25 -0400 Subject: [PATCH 07/12] Attempted via linear search to implement `PCMTrack::get_next_event`. --- Storage/Disk/Formats/G64.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 8a321ee66..c34a4ede4 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -161,8 +161,35 @@ PCMTrack::PCMTrack(PCMSegment segment) PCMTrack::Event PCMTrack::get_next_event() { - PCMTrack::Event new_event; - return new_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 + int bit = segment_data[_bit_pointer >> 3] & (1 << (_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() From 6593caca93c95ec5686727c4bdc9dc17bd8c3802 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 16:21:52 -0400 Subject: [PATCH 08/12] Switched to a probably more helpful way around of expecting bits in bytes. --- Storage/Disk/Formats/G64.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index c34a4ede4..6fd06a494 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -172,7 +172,8 @@ PCMTrack::Event PCMTrack::get_next_event() while(_bit_pointer < _segments[_segment_pointer].duration.length) { // for timing simplicity, bits are modelled as happening at the end of their window - int bit = segment_data[_bit_pointer >> 3] & (1 << (_bit_pointer&7)); + // 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; From ada2f073e0d8c3f86153c18ca9872392352903c2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 16:24:46 -0400 Subject: [PATCH 09/12] Completed handing of the disk all the way to the 1540. --- Machines/Commodore/1540/C1540.cpp | 5 +++++ Machines/Commodore/1540/C1540.hpp | 10 ++++++++++ Machines/Commodore/Vic-20/Vic20.cpp | 6 ++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index 49209d1a4..8724a2f2b 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -91,6 +91,11 @@ void Machine::set_rom(const uint8_t *rom) memcpy(_rom, rom, sizeof(_rom)); } +void Machine::set_disk(std::shared_ptr disk) +{ + _disk = disk; +} + #pragma mark - 6522 delegate void Machine::mos6522_did_change_interrupt_status(void *mos6522) diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 92ceccb6e..1935b1e5d 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -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 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; std::shared_ptr _serialPort; DriveVIA _driveVIA; + + std::shared_ptr _disk; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 78107cf60..2b4f3d3ba 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -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); @@ -222,7 +219,8 @@ void Machine::set_disk(std::shared_ptr disk) // attach it to the serial bus _c1540->set_serial_bus(_serialBus); - // TODO: push the disk to the C1540 + // hand it the disk + _c1540->set_disk(disk); } #pragma mark - Typer From 19ee430d4abc17f1992dde8ab7e6366eceede649 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 18:07:53 -0400 Subject: [PATCH 10/12] Made an effort to support zoned tracks, at least. --- Storage/Disk/Formats/G64.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 6fd06a494..66fc7ba4b 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -36,6 +36,8 @@ G64::G64(const char *file_name) _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() @@ -105,13 +107,36 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) uint8_t speed_zone_contents[speed_zone_length]; fread(speed_zone_contents, 1, speed_zone_length, _file); - // TODO: divide into individual PCMSegments (per byte, if necessary), shove into a PCMTrack + // divide track into appropriately timed PCMSegments + std::vector 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 = current_speed; + 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 = 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))); From 66895d3ac74f1ca34e7c3ef1d6f8052e6d0a7b3d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 18:24:12 -0400 Subject: [PATCH 11/12] Actually, I think this is the correct conversion from received speed to clock rate. It'll become obvious if it's not when I get back to working on the 1541 itself. --- Storage/Disk/Formats/G64.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Storage/Disk/Formats/G64.cpp b/Storage/Disk/Formats/G64.cpp index 66fc7ba4b..e0806818c 100644 --- a/Storage/Disk/Formats/G64.cpp +++ b/Storage/Disk/Formats/G64.cpp @@ -120,7 +120,7 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) PCMSegment segment; segment.duration.length = number_of_bytes * 8; - segment.duration.clock_rate = current_speed; + 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)); @@ -142,6 +142,9 @@ std::shared_ptr G64::get_track_at_position(unsigned int position) 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; } From 1e9eedc3141384bb063d75c373d9e551c54fb8f9 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 10 Jul 2016 18:36:52 -0400 Subject: [PATCH 12/12] 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 */