mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-14 13:33:42 +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:
commit
d0200b6fd9
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(); }
|
||||
|
@ -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 */,
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
- (void)setPRG:(nonnull NSData *)prg;
|
||||
- (BOOL)openTAPAtURL:(nonnull NSURL *)URL;
|
||||
- (BOOL)openG64AtURL:(nonnull NSURL *)URL;
|
||||
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
|
||||
|
@ -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
9
Storage/Disk/Disk.cpp
Normal 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
76
Storage/Disk/Disk.hpp
Normal 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 */
|
152
Storage/Disk/Formats/G64.cpp
Normal file
152
Storage/Disk/Formats/G64.cpp
Normal 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;
|
||||
}
|
38
Storage/Disk/Formats/G64.hpp
Normal file
38
Storage/Disk/Formats/G64.hpp
Normal 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
102
Storage/Disk/PCMTrack.cpp
Normal 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
80
Storage/Disk/PCMTrack.hpp
Normal 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
20
Storage/Storage.hpp
Normal 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 */
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user