1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-08 15:29:09 +00:00

Merge pull request #41 from TomHarte/DiskDrive

Implements something of the foundation for disk drive emulation
This commit is contained in:
Thomas Harte 2016-08-01 04:43:07 -04:00 committed by GitHub
commit a2ca94431e
30 changed files with 1023 additions and 120 deletions

View File

@ -364,7 +364,7 @@ class MOS6522IRQDelegate {
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
}; };
void set_delegate(Delegate *delegate) void set_interrupt_delegate(Delegate *delegate)
{ {
_delegate = delegate; _delegate = delegate;
} }

View File

@ -7,11 +7,14 @@
// //
#include "C1540.hpp" #include "C1540.hpp"
#include <string.h> #include <string>
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
using namespace Commodore::C1540; using namespace Commodore::C1540;
Machine::Machine() Machine::Machine() :
_shift_register(0),
Storage::DiskDrive(1000000, 16, 300)
{ {
// create a serial port and a VIA to run it // create a serial port and a VIA to run it
_serialPortVIA.reset(new SerialPortVIA); _serialPortVIA.reset(new SerialPortVIA);
@ -22,8 +25,12 @@ Machine::Machine()
_serialPortVIA->set_serial_port(_serialPort); _serialPortVIA->set_serial_port(_serialPort);
// set this instance as the delegate to receive interrupt requests from both VIAs // set this instance as the delegate to receive interrupt requests from both VIAs
_serialPortVIA->set_delegate(this); _serialPortVIA->set_interrupt_delegate(this);
_driveVIA.set_interrupt_delegate(this);
_driveVIA.set_delegate(this); _driveVIA.set_delegate(this);
// set a bit rate
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
} }
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus)
@ -33,7 +40,11 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value)
{ {
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF556 && address <= 0xF56D)) printf("%04x\n", address); // static bool log = false;
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xF3C0)) log = true;
// if(operation == CPU6502::BusOperation::ReadOpcode && log) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode && (address >= 0xF510 && address <= 0xF553)) printf("%04x\n", address);
// if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A)); // if(operation == CPU6502::BusOperation::ReadOpcode && (address == 0xE887)) printf("A: %02x\n", get_value_of_register(CPU6502::Register::A));
/* static bool log = false; /* static bool log = false;
@ -91,9 +102,11 @@ void Machine::set_rom(const uint8_t *rom)
memcpy(_rom, rom, sizeof(_rom)); memcpy(_rom, rom, sizeof(_rom));
} }
void Machine::set_disk(std::shared_ptr<Storage::Disk> disk) void Machine::run_for_cycles(int number_of_cycles)
{ {
_disk = disk; CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
if(_driveVIA.get_motor_enabled()) // TODO: motor speed up/down
Storage::DiskDrive::run_for_cycles(number_of_cycles);
} }
#pragma mark - 6522 delegate #pragma mark - 6522 delegate
@ -103,3 +116,46 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522)
// both VIAs are connected to the IRQ line // both VIAs are connected to the IRQ line
set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line()); set_irq_line(_serialPortVIA->get_interrupt_line() || _driveVIA.get_interrupt_line());
} }
#pragma mark - Disk drive
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
_shift_register = (_shift_register << 1) | value;
if((_shift_register & 0x3ff) == 0x3ff)
{
_driveVIA.set_sync_detected(true);
_bit_window_offset = -1;
}
else
{
_driveVIA.set_sync_detected(false);
}
_bit_window_offset++;
if(_bit_window_offset == 8)
{
_driveVIA.set_data_input((uint8_t)_shift_register);
_bit_window_offset = 0;
if(_driveVIA.get_should_set_overflow())
{
set_overflow_line(true);
}
}
else
set_overflow_line(false);
}
// the 1540 does not recognise index holes
void Machine::process_index_hole() {}
#pragma mak - Drive VIA delegate
void Machine::drive_via_did_step_head(void *driveVIA, int direction)
{
step(direction);
}
void Machine::drive_via_did_set_data_density(void *driveVIA, int density)
{
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
}

View File

@ -15,6 +15,7 @@
#include "../SerialBus.hpp" #include "../SerialBus.hpp"
#include "../../../Storage/Disk/Disk.hpp" #include "../../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/DiskDrive.hpp"
namespace Commodore { namespace Commodore {
namespace C1540 { namespace C1540 {
@ -100,12 +101,12 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
An implementation of the drive VIA in a Commodore 1540 the VIA that is used to interface with the disk. An implementation of the drive VIA in a Commodore 1540 the VIA that is used to interface with the disk.
It is wired up such that Port B contains: It is wired up such that Port B contains:
Bits 0/1: head step direction (TODO) Bits 0/1: head step direction
Bit 2: motor control (TODO) Bit 2: motor control
Bit 3: LED control (TODO) Bit 3: LED control (TODO)
Bit 4: write protect photocell status (TODO) Bit 4: write protect photocell status (TODO)
Bits 5/6: write density (TODO) Bits 5/6: read/write density
Bit 7: 0 if sync marks are currently being detected, 1 otherwise; Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction. ... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
@ -114,28 +115,80 @@ class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQD
*/ */
class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate { class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
public: public:
class Delegate {
public:
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
};
void set_delegate(Delegate *delegate)
{
_delegate = delegate;
}
using MOS6522IRQDelegate::set_interrupt_status; using MOS6522IRQDelegate::set_interrupt_status;
// write protect tab uncovered
DriveVIA() : _port_b(0xff), _port_a(0xff), _delegate(nullptr) {}
uint8_t get_port_input(Port port) { uint8_t get_port_input(Port port) {
if(port) return port ? _port_b : _port_a;
{ }
return 0xff; // imply not sync, write protect tab uncovered
void set_sync_detected(bool sync_detected) {
_port_b = (_port_b & 0x7f) | (sync_detected ? 0x00 : 0x80);
}
void set_data_input(uint8_t value) {
_port_a = value;
}
bool get_should_set_overflow() {
return _should_set_overflow;
}
bool get_motor_enabled() {
return _drive_motor;
}
void set_control_line_output(Port port, Line line, bool value) {
if(port == Port::A && line == Line::Two) {
_should_set_overflow = value;
} }
return 0xff;
} }
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) if(port)
{ {
// if(value&4) // record drive motor state
// { _drive_motor = !!(value&4);
// printf("Head step: %d\n", value&3);
// printf("Motor: %s\n", value&4 ? "On" : "Off"); // check for a head step
int step_difference = ((value&3) - (_previous_port_b_output&3))&3;
if(step_difference)
{
if(_delegate) _delegate->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
}
// check for a change in density
int density_difference = (_previous_port_b_output^value) & (3 << 5);
if(density_difference && _delegate)
{
_delegate->drive_via_did_set_data_density(this, (value >> 5)&3);
}
// TODO: something with the drive LED
// printf("LED: %s\n", value&8 ? "On" : "Off"); // printf("LED: %s\n", value&8 ? "On" : "Off");
// printf("Density: %d\n", (value >> 5)&3);
// } _previous_port_b_output = value;
} }
} }
private:
uint8_t _port_b, _port_a;
bool _should_set_overflow;
bool _drive_motor;
uint8_t _previous_port_b_output;
Delegate *_delegate;
}; };
/*! /*!
@ -161,7 +214,9 @@ class SerialPort : public ::Commodore::Serial::Port {
*/ */
class Machine: class Machine:
public CPU6502::Processor<Machine>, public CPU6502::Processor<Machine>,
public MOS::MOS6522IRQDelegate::Delegate { public MOS::MOS6522IRQDelegate::Delegate,
public DriveVIA::Delegate,
public Storage::DiskDrive {
public: public:
Machine(); Machine();
@ -176,10 +231,7 @@ class Machine:
*/ */
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus); void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
/*! void run_for_cycles(int number_of_cycles);
Sets the disk from which this 1540 is reading data.
*/
void set_disk(std::shared_ptr<Storage::Disk> disk);
// to satisfy CPU6502::Processor // to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
@ -187,6 +239,10 @@ class Machine:
// to satisfy MOS::MOS6522::Delegate // to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522); virtual void mos6522_did_change_interrupt_status(void *mos6522);
// to satisfy DriveVIA::Delegate
void drive_via_did_step_head(void *driveVIA, int direction);
void drive_via_did_set_data_density(void *driveVIA, int density);
private: private:
uint8_t _ram[0x800]; uint8_t _ram[0x800];
uint8_t _rom[0x4000]; uint8_t _rom[0x4000];
@ -196,6 +252,10 @@ class Machine:
DriveVIA _driveVIA; DriveVIA _driveVIA;
std::shared_ptr<Storage::Disk> _disk; std::shared_ptr<Storage::Disk> _disk;
int _shift_register, _bit_window_offset;
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
}; };
} }

View File

@ -30,8 +30,8 @@ Machine::Machine() :
_serialPort->set_user_port_via(_userPortVIA); _serialPort->set_user_port_via(_userPortVIA);
// wire up the 6522s, tape and machine // wire up the 6522s, tape and machine
_userPortVIA->set_delegate(this); _userPortVIA->set_interrupt_delegate(this);
_keyboardVIA->set_delegate(this); _keyboardVIA->set_interrupt_delegate(this);
_tape.set_delegate(this); _tape.set_delegate(this);
// establish the memory maps // establish the memory maps
@ -169,6 +169,11 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
_c1540->set_rom(data); _c1540->set_rom(data);
_c1540->run_for_cycles(2000000); // pretend it booted a couple of seconds ago _c1540->run_for_cycles(2000000); // pretend it booted a couple of seconds ago
} }
else
{
_driveROM.reset(new uint8_t[length]);
memcpy(_driveROM.get(), data, length);
}
return; return;
} }
@ -221,6 +226,13 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
// hand it the disk // hand it the disk
_c1540->set_disk(disk); _c1540->set_disk(disk);
// install the ROM if it was previously set
if(_driveROM)
{
_c1540->set_rom(_driveROM.get());
_driveROM.reset();
}
} }
#pragma mark - Typer #pragma mark - Typer

View File

@ -287,6 +287,7 @@ class Machine:
uint8_t _screenMemory[0x1000]; uint8_t _screenMemory[0x1000];
uint8_t _colorMemory[0x0400]; uint8_t _colorMemory[0x0400];
uint8_t _junkMemory[0x0400]; uint8_t _junkMemory[0x0400];
std::unique_ptr<uint8_t> _driveROM;
uint8_t *_videoMemoryMap[16]; uint8_t *_videoMemoryMap[16];
uint8_t *_processorReadMemoryMap[64]; uint8_t *_processorReadMemoryMap[64];

View File

@ -1008,7 +1008,7 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles)
{ {
if(_is_running) if(_is_running)
{ {
TapePlayer::run_for_cycles(number_of_cycles); TapePlayer::run_for_cycles((int)number_of_cycles);
} }
} }
else else

34
NumberTheory/Factors.cpp Normal file
View File

@ -0,0 +1,34 @@
//
// Factors.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Factors.hpp"
unsigned int NumberTheory::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 NumberTheory::least_common_multiple(unsigned int a, unsigned int b)
{
unsigned int gcd = greatest_common_divisor(a, b);
return (a*b) / gcd;
}

17
NumberTheory/Factors.hpp Normal file
View File

@ -0,0 +1,17 @@
//
// Factors.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Factors_hpp
#define Factors_hpp
namespace NumberTheory {
unsigned int greatest_common_divisor(unsigned int a, unsigned int b);
unsigned int least_common_multiple(unsigned int a, unsigned int b);
}
#endif /* Factors_hpp */

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */; };
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; }; 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
@ -39,6 +40,7 @@
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */; };
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; }; 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; };
4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; }; 4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; };
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
@ -312,6 +314,9 @@
4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; }; 4BB299F71B587D8400A49093 /* txan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EB1B587D8400A49093 /* txan */; };
4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; }; 4BB299F81B587D8400A49093 /* txsn in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298EC1B587D8400A49093 /* txsn */; };
4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; }; 4BB299F91B587D8400A49093 /* tyan in Resources */ = {isa = PBXBuildFile; fileRef = 4BB298ED1B587D8400A49093 /* tyan */; };
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C51D4B558F00248BDF /* Factors.cpp */; };
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */; };
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */; };
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; }; 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */; };
4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; }; 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */; };
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; }; 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EA81B587A5100552FC2 /* Assets.xcassets */; };
@ -332,6 +337,8 @@
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -354,6 +361,8 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; }; 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; }; 4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigitalPhaseLockedLoop.cpp; sourceTree = "<group>"; };
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DigitalPhaseLockedLoop.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; }; 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; }; 4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; }; 4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
@ -415,6 +424,8 @@
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; }; 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskDrive.cpp; sourceTree = "<group>"; };
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskDrive.hpp; sourceTree = "<group>"; };
4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = "<group>"; }; 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; }; 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>"; }; 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
@ -692,6 +703,12 @@
4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; }; 4BB298EB1B587D8400A49093 /* txan */ = {isa = PBXFileReference; lastKnownFileType = file; path = txan; sourceTree = "<group>"; };
4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; }; 4BB298EC1B587D8400A49093 /* txsn */ = {isa = PBXFileReference; lastKnownFileType = file; path = txsn; sourceTree = "<group>"; };
4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; }; 4BB298ED1B587D8400A49093 /* tyan */ = {isa = PBXFileReference; lastKnownFileType = file; path = tyan; sourceTree = "<group>"; };
4BB697C51D4B558F00248BDF /* Factors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Factors.cpp; path = ../../NumberTheory/Factors.cpp; sourceTree = "<group>"; };
4BB697C61D4B558F00248BDF /* Factors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Factors.hpp; path = ../../NumberTheory/Factors.hpp; sourceTree = "<group>"; };
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimedEventLoop.cpp; sourceTree = "<group>"; };
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TimedEventLoop.hpp; sourceTree = "<group>"; };
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreGCR.cpp; path = Encodings/CommodoreGCR.cpp; sourceTree = "<group>"; };
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreGCR.hpp; path = Encodings/CommodoreGCR.hpp; sourceTree = "<group>"; };
4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4BB73E9E1B587A5100552FC2 /* Clock Signal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock Signal.app"; sourceTree = BUILT_PRODUCTS_DIR; };
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 4BB73EA11B587A5100552FC2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; }; 4BB73EA61B587A5100552FC2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Atari2600Document.xib; sourceTree = "<group>"; };
@ -734,6 +751,9 @@
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; }; 4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; }; 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLLTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -884,13 +904,15 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */, 4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */,
4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */,
4B3BA0C71D318B44005DD7A7 /* Clock SignalTests-Bridging-Header.h */, 4B3BA0C71D318B44005DD7A7 /* Clock SignalTests-Bridging-Header.h */,
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */,
4B3BA0C81D318B44005DD7A7 /* MOS6522Bridge.h */, 4B3BA0C81D318B44005DD7A7 /* MOS6522Bridge.h */,
4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */,
4B3BA0CA1D318B44005DD7A7 /* MOS6532Bridge.h */, 4B3BA0CA1D318B44005DD7A7 /* MOS6532Bridge.h */,
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */,
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */, 4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */,
4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */,
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */,
4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */,
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */,
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */, 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */,
); );
path = Bridges; path = Bridges;
@ -951,9 +973,11 @@
4B69FB391C4D908A00B5F0AA /* Storage */ = { 4B69FB391C4D908A00B5F0AA /* Storage */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */,
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */,
4BAB62AA1D3272D200DF5BA0 /* Disk */, 4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B69FB3A1C4D908A00B5F0AA /* Tape */, 4B69FB3A1C4D908A00B5F0AA /* Tape */,
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
); );
name = Storage; name = Storage;
path = ../../Storage; path = ../../Storage;
@ -984,11 +1008,16 @@
4BAB62AA1D3272D200DF5BA0 /* Disk */ = { 4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4BAB62B21D327F7E00DF5BA0 /* Formats */, 4B0BE4261D3481E700D5256B /* DigitalPhaseLockedLoop.cpp */,
4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */, 4BAB62AB1D3272D200DF5BA0 /* Disk.cpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */, 4B6C73BB1D387AE500AFCFCA /* DiskDrive.cpp */,
4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */, 4BAB62B61D3302CA00DF5BA0 /* PCMTrack.cpp */,
4B0BE4271D3481E700D5256B /* DigitalPhaseLockedLoop.hpp */,
4BAB62AC1D3272D200DF5BA0 /* Disk.hpp */,
4B6C73BC1D387AE500AFCFCA /* DiskDrive.hpp */,
4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */, 4BAB62B71D3302CA00DF5BA0 /* PCMTrack.hpp */,
4BB697CF1D4BA44900248BDF /* Encodings */,
4BAB62B21D327F7E00DF5BA0 /* Formats */,
); );
path = Disk; path = Disk;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1274,6 +1303,24 @@
path = "Wolfgang Lorenz 6502 test suite"; path = "Wolfgang Lorenz 6502 test suite";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4BB697C81D4B559300248BDF /* NumberTheory */ = {
isa = PBXGroup;
children = (
4BB697C51D4B558F00248BDF /* Factors.cpp */,
4BB697C61D4B558F00248BDF /* Factors.hpp */,
);
name = NumberTheory;
sourceTree = "<group>";
};
4BB697CF1D4BA44900248BDF /* Encodings */ = {
isa = PBXGroup;
children = (
4BB697CC1D4BA44400248BDF /* CommodoreGCR.cpp */,
4BB697CD1D4BA44400248BDF /* CommodoreGCR.hpp */,
);
name = Encodings;
sourceTree = "<group>";
};
4BB73E951B587A5100552FC2 = { 4BB73E951B587A5100552FC2 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1283,13 +1330,17 @@
4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */,
4BC9DF4A1D04691600F44158 /* Components */, 4BC9DF4A1D04691600F44158 /* Components */,
4BB73EDC1B587CA500552FC2 /* Machines */, 4BB73EDC1B587CA500552FC2 /* Machines */,
4BB697C81D4B559300248BDF /* NumberTheory */,
4B366DFD1B5C165F0026627B /* Outputs */, 4B366DFD1B5C165F0026627B /* Outputs */,
4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73EDD1B587CA500552FC2 /* Processors */,
4BB73E9F1B587A5100552FC2 /* Products */, 4BB73E9F1B587A5100552FC2 /* Products */,
4B2409591C45DF85004DA684 /* SignalProcessing */, 4B2409591C45DF85004DA684 /* SignalProcessing */,
4B69FB391C4D908A00B5F0AA /* Storage */, 4B69FB391C4D908A00B5F0AA /* Storage */,
); );
indentWidth = 4;
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 4;
usesTabs = 1;
}; };
4BB73E9F1B587A5100552FC2 /* Products */ = { 4BB73E9F1B587A5100552FC2 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@ -1330,6 +1381,7 @@
4B1E85801D176468001EF87D /* 6532Tests.swift */, 4B1E85801D176468001EF87D /* 6532Tests.swift */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */, 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */, 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */, 4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */, 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */, 4B3BA0C41D318B44005DD7A7 /* Bridges */,
@ -1853,14 +1905,17 @@
files = ( files = (
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */,
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */, 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */, 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
@ -1871,12 +1926,14 @@
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */, 4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */,
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */, 4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */, 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */, 4BAB62B81D3302CA00DF5BA0 /* PCMTrack.cpp in Sources */,
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */, 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
@ -1897,10 +1954,12 @@
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */, 4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */,
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */,
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */,
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */,
4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */, 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */,
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */,
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */,
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */, 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */,

View File

@ -32,7 +32,7 @@ class Vic20Document: MachineDocument {
vic20.setCharactersROM(characters) vic20.setCharactersROM(characters)
} }
if let drive = dataForResource("1541", ofType: "bin", inDirectory: "ROMImages/Commodore1540") { if let drive = dataForResource("1540", ofType: "bin", inDirectory: "ROMImages/Commodore1540") {
vic20.setDriveROM(drive) vic20.setDriveROM(drive)
} }
} }

View File

@ -6,3 +6,4 @@
#import "MOS6522Bridge.h" #import "MOS6522Bridge.h"
#import "MOS6532Bridge.h" #import "MOS6532Bridge.h"
#import "C1540Bridge.h" #import "C1540Bridge.h"
#import "DigitalPhaseLockedLoopBridge.h"

View File

@ -0,0 +1,20 @@
//
// DigitalPhaseLockedLoopBridge.h
// Clock Signal
//
// Created by Thomas Harte on 12/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface DigitalPhaseLockedLoopBridge : NSObject
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength;
- (void)runForCycles:(NSUInteger)cycles;
- (void)addPulse;
@property(nonatomic) NSUInteger stream;
@end

View File

@ -0,0 +1,61 @@
//
// DigitalPhaseLockedLoopBridge.m
// Clock Signal
//
// Created by Thomas Harte on 12/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "DigitalPhaseLockedLoopBridge.h"
#include "DigitalPhaseLockedLoop.hpp"
#include <memory>
@interface DigitalPhaseLockedLoopBridge(BitPushing)
- (void)pushBit:(int)value;
@end
class DigitalPhaseLockedLoopDelegate: public Storage::DigitalPhaseLockedLoop::Delegate {
public:
__weak DigitalPhaseLockedLoopBridge *bridge;
void digital_phase_locked_loop_output_bit(int value)
{
[bridge pushBit:value ? 1 : 0];
}
};
@implementation DigitalPhaseLockedLoopBridge
{
std::unique_ptr<Storage::DigitalPhaseLockedLoop> _digitalPhaseLockedLoop;
DigitalPhaseLockedLoopDelegate _delegate;
}
- (instancetype)initWithClocksPerBit:(NSUInteger)clocksPerBit tolerance:(NSUInteger)tolerance historyLength:(NSUInteger)historyLength
{
self = [super init];
if(self)
{
_digitalPhaseLockedLoop.reset(new Storage::DigitalPhaseLockedLoop((unsigned int)clocksPerBit, (unsigned int)tolerance, (unsigned int)historyLength));
_delegate.bridge = self;
_digitalPhaseLockedLoop->set_delegate(&_delegate);
}
return self;
}
- (void)runForCycles:(NSUInteger)cycles
{
_digitalPhaseLockedLoop->run_for_cycles((unsigned int)cycles);
}
- (void)addPulse
{
_digitalPhaseLockedLoop->add_pulse();
}
- (void)pushBit:(int)value
{
_stream = (_stream << 1) | value;
}
@end

View File

@ -0,0 +1,61 @@
//
// DPLLTests.swift
// Clock Signal
//
// Created by Thomas Harte on 12/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
import XCTest
class DPLLTests: XCTestCase {
func testRegularNibblesOnPLL(pll: DigitalPhaseLockedLoopBridge, bitLength: UInt) {
// clock in two 1s, a 0, and a 1, 200 times over
for _ in 0 ..< 200 {
pll.runForCycles(bitLength/2)
pll.addPulse()
pll.runForCycles(bitLength)
pll.addPulse()
pll.runForCycles(bitLength*2)
pll.addPulse()
pll.runForCycles(bitLength/2)
}
XCTAssert((pll.stream&0xffffffff) == 0xdddddddd, "PLL should have synchronised and clocked repeating 0xd nibbles; got \(String(pll.stream, radix: 16, uppercase: false))")
}
func testPerfectInput() {
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
testRegularNibblesOnPLL(pll, bitLength: 100)
}
func testFastButRegular() {
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
testRegularNibblesOnPLL(pll, bitLength: 90)
}
func testSlowButRegular() {
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
testRegularNibblesOnPLL(pll, bitLength: 110)
}
func testTwentyPercentSinePattern() {
let pll = DigitalPhaseLockedLoopBridge(clocksPerBit: 100, tolerance: 20, historyLength: 3)
var angle = 0.0
// clock in two 1s, a 0, and a 1, 200 times over
for _ in 0 ..< 200 {
let bitLength: UInt = UInt(100 + 20 * sin(angle))
pll.runForCycles(bitLength/2)
pll.addPulse()
pll.runForCycles((bitLength*3)/2)
angle = angle + 0.1
}
let endOfStream = pll.stream&0xffffffff;
XCTAssert(endOfStream == 0xaaaaaaaa || endOfStream == 0x55555555, "PLL should have synchronised and clocked repeating 0xa or 0x5 nibbles; got \(String(pll.stream, radix: 16, uppercase: false))")
}
}

View File

@ -472,6 +472,7 @@ template <class T> class Processor {
bool _ready_line_is_enabled; bool _ready_line_is_enabled;
bool _irq_line_is_enabled, _irq_request_history; bool _irq_line_is_enabled, _irq_request_history;
bool _nmi_line_is_enabled, _set_overflow_line_is_enabled;
/*! /*!
Gets the program representing an RST response. Gets the program representing an RST response.
@ -549,7 +550,10 @@ template <class T> class Processor {
_interruptFlag(Flag::Interrupt), _interruptFlag(Flag::Interrupt),
_s(0), _s(0),
_nextBusOperation(BusOperation::None), _nextBusOperation(BusOperation::None),
_interrupt_requests(InterruptRequestFlags::PowerOn) _interrupt_requests(InterruptRequestFlags::PowerOn),
_irq_line_is_enabled(false),
_nmi_line_is_enabled(false),
_set_overflow_line_is_enabled(false)
{ {
// only the interrupt flag is defined upon reset but get_flags isn't going to // only the interrupt flag is defined upon reset but get_flags isn't going to
// mask the other flags so we need to do that, at least // mask the other flags so we need to do that, at least
@ -1237,6 +1241,19 @@ template <class T> class Processor {
_irq_line_is_enabled = active; _irq_line_is_enabled = active;
} }
/*!
Sets the current level of the set overflow line.
@param active @c true if the line is logically active; @c false otherwise.
*/
inline void set_overflow_line(bool active)
{
// a leading edge will set the overflow flag
if(active && !_set_overflow_line_is_enabled)
_overflowFlag = Flag::Overflow;
_set_overflow_line_is_enabled = active;
}
/*! /*!
Sets the current level of the NMI line. Sets the current level of the NMI line.
@ -1245,7 +1262,9 @@ template <class T> class Processor {
inline void set_nmi_line(bool active) inline void set_nmi_line(bool active)
{ {
// NMI is edge triggered, not level // NMI is edge triggered, not level
_interrupt_requests |= (active ? InterruptRequestFlags::NMI : 0); if(active && !_nmi_line_is_enabled)
_interrupt_requests |= InterruptRequestFlags::NMI;
_nmi_line_is_enabled = active;
} }
/*! /*!

View File

@ -13,19 +13,41 @@
namespace SignalProcessing { namespace SignalProcessing {
/*!
Allows a repeating action running at an input rate to determine how many times it should
trigger an action that runs at an unrelated output rate; therefore it allows something with one
clock to sample something with another.
Uses a Bresenham-like error term internally for full-integral storage with no drift.
Pegs the beginning of both clocks to the time at which the stepper is created. So e.g. a stepper
that converts from an input clock of 1200 to an output clock of 2 will first fire on cycle 600.
*/
class Stepper class Stepper
{ {
public: public:
/*!
Establishes a stepper with a one-to-one conversion rate.
*/
Stepper() : Stepper(1,1) {} Stepper() : Stepper(1,1) {}
/*!
Establishes a stepper that will receive steps at the @c input_rate and dictate the number
of steps that should be taken at the @c output_rate.
*/
Stepper(uint64_t output_rate, uint64_t input_rate) : Stepper(uint64_t output_rate, uint64_t input_rate) :
accumulated_error_(0), accumulated_error_(-((int64_t)input_rate << 1)),
input_rate_(input_rate), input_rate_(input_rate),
output_rate_(output_rate), output_rate_(output_rate),
whole_step_(output_rate / input_rate), whole_step_(output_rate / input_rate),
adjustment_up_((int64_t)(output_rate % input_rate) << 1), adjustment_up_((int64_t)(output_rate % input_rate) << 1),
adjustment_down_((int64_t)input_rate << 1) {} adjustment_down_((int64_t)input_rate << 1) {}
/*!
Advances one step at the input rate.
@returns the number of output steps.
*/
inline uint64_t step() inline uint64_t step()
{ {
uint64_t update = whole_step_; uint64_t update = whole_step_;
@ -38,11 +60,34 @@ class Stepper
return update; return update;
} }
/*!
Advances by @c number_of_steps steps at the input rate.
@returns the number of output steps.
*/
inline uint64_t step(uint64_t number_of_steps)
{
uint64_t update = whole_step_ * number_of_steps;
accumulated_error_ += adjustment_up_ * (int64_t)number_of_steps;
if(accumulated_error_ > 0)
{
update += 1 + (uint64_t)(accumulated_error_ / adjustment_down_);
accumulated_error_ = (accumulated_error_ % adjustment_down_) - adjustment_down_;
}
return update;
}
/*!
@returns the output rate.
*/
inline uint64_t get_output_rate() inline uint64_t get_output_rate()
{ {
return output_rate_; return output_rate_;
} }
/*!
@returns the input rate.
*/
inline uint64_t get_input_rate() inline uint64_t get_input_rate()
{ {
return input_rate_; return input_rate_;

View File

@ -0,0 +1,76 @@
//
// DigitalPhaseLockedLoop.cpp
// Clock Signal
//
// Created by Thomas Harte on 11/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "DigitalPhaseLockedLoop.hpp"
#include <algorithm>
#include <cstdlib>
using namespace Storage;
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) :
_clocks_per_bit(clocks_per_bit),
_tolerance(tolerance),
_phase(0),
_window_length(clocks_per_bit),
_phase_error_pointer(0)
{
_phase_error_history.reset(new std::vector<int>(length_of_history, 0));
}
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles)
{
_phase += number_of_cycles;
int windows_crossed = _phase / _window_length;
if(windows_crossed)
{
// check whether this triggers any 0s, if anybody cares
if(_delegate)
{
if(_window_was_filled) windows_crossed--;
for(int c = 0; c < windows_crossed; c++)
_delegate->digital_phase_locked_loop_output_bit(0);
}
_window_was_filled = false;
_phase %= _window_length;
}
}
void DigitalPhaseLockedLoop::add_pulse()
{
if(!_window_was_filled)
{
if(_delegate) _delegate->digital_phase_locked_loop_output_bit(1);
_window_was_filled = true;
post_phase_error(_phase - (_window_length >> 1));
}
}
void DigitalPhaseLockedLoop::post_phase_error(int error)
{
// use a simple spring mechanism as a lowpass filter for phase
_phase -= (error + 1) >> 1;
// use the average of the last few errors to affect frequency
std::vector<int> *phase_error_history = _phase_error_history.get();
size_t phase_error_history_size = phase_error_history->size();
(*phase_error_history)[_phase_error_pointer] = error;
_phase_error_pointer = (_phase_error_pointer + 1)%phase_error_history_size;
int total_error = 0;
for(size_t c = 0; c < phase_error_history_size; c++)
{
total_error += (*phase_error_history)[c];
}
int denominator = (int)(phase_error_history_size * 4);
_window_length += (total_error + (denominator >> 1)) / denominator;
_window_length = std::max(std::min(_window_length, _clocks_per_bit + _tolerance), _clocks_per_bit - _tolerance);
}

View File

@ -0,0 +1,69 @@
//
// DigitalPhaseLockedLoop.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef DigitalPhaseLockedLoop_hpp
#define DigitalPhaseLockedLoop_hpp
#include <memory>
#include <vector>
namespace Storage {
class DigitalPhaseLockedLoop {
public:
/*!
Instantiates a @c DigitalPhaseLockedLoop.
@param clocks_per_bit The expected number of cycles between each bit of input.
@param tolerance The maximum tolerance for bit windows extremes will be clocks_per_bit ± tolerance.
@param length_of_history The number of historic pulses to consider in locking to phase.
*/
DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history);
/*!
Runs the loop, impliedly posting no pulses during that period.
@c number_of_cycles The time to run the loop for.
*/
void run_for_cycles(int number_of_cycles);
/*!
Announces a pulse at the current time.
*/
void add_pulse();
/*!
A receiver for PCM output data; called upon every recognised bit.
*/
class Delegate {
public:
virtual void digital_phase_locked_loop_output_bit(int value) = 0;
};
void set_delegate(Delegate *delegate)
{
_delegate = delegate;
}
private:
Delegate *_delegate;
void post_phase_error(int error);
std::unique_ptr<std::vector<int>> _phase_error_history;
size_t _phase_error_pointer;
int _phase;
int _window_length;
bool _window_was_filled;
int _clocks_per_bit;
int _tolerance;
};
}
#endif /* DigitalPhaseLockedLoop_hpp */

118
Storage/Disk/DiskDrive.cpp Normal file
View File

@ -0,0 +1,118 @@
//
// DiskDrive.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "DiskDrive.hpp"
using namespace Storage;
DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
_clock_rate(clock_rate * clock_rate_multiplier),
_clock_rate_multiplier(clock_rate_multiplier),
_revolutions_per_minute(revolutions_per_minute),
_head_position(0),
TimedEventLoop(clock_rate * clock_rate_multiplier)
{}
void DiskDrive::set_expected_bit_length(Time bit_length)
{
_bit_length = bit_length;
// this conversion doesn't need to be exact because there's a lot of variation to be taken
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
int clocks_per_bit = (int)((bit_length.length * _clock_rate) / bit_length.clock_rate);
_pll.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
_pll->set_delegate(this);
}
void DiskDrive::set_disk(std::shared_ptr<Disk> disk)
{
_disk = disk;
set_track();
}
bool DiskDrive::has_disk()
{
return (bool)_disk;
}
bool DiskDrive::get_is_track_zero()
{
return _head_position == 0;
}
void DiskDrive::step(int direction)
{
_head_position = std::max(_head_position + direction, 0);
set_track();
}
void DiskDrive::set_track()
{
_track = _disk->get_track_at_position((unsigned int)_head_position);
reset_timer();
get_next_event();
}
void DiskDrive::run_for_cycles(int number_of_cycles)
{
if(has_disk())
{
number_of_cycles *= _clock_rate_multiplier;
while(number_of_cycles--)
{
_cycles_since_index_hole ++;
_pll->run_for_cycles(1);
TimedEventLoop::run_for_cycles(1);
}
}
}
#pragma mark - Track timed event loop
void DiskDrive::get_next_event()
{
if(_track)
_current_event = _track->get_next_event();
else
{
_current_event.length.length = 1;
_current_event.length.clock_rate = 1;
_current_event.type = Track::Event::IndexHole;
}
// divide interval, which is in terms of a rotation of the disk, by rotation speed, and
// convert it into revolutions per second
Time event_interval = _current_event.length;
event_interval.length *= 60;
event_interval.clock_rate *= _revolutions_per_minute;
event_interval.simplify();
set_next_event_time_interval(event_interval);
}
void DiskDrive::process_next_event()
{
switch(_current_event.type)
{
case Track::Event::FluxTransition:
_pll->add_pulse();
break;
case Track::Event::IndexHole:
_cycles_since_index_hole = 0;
process_index_hole();
break;
}
get_next_event();
}
#pragma mark - PLL delegate
void DiskDrive::digital_phase_locked_loop_output_bit(int value)
{
process_input_bit(value, _cycles_since_index_hole);
}

View File

@ -0,0 +1,62 @@
//
// DiskDrive.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef DiskDrive_hpp
#define DiskDrive_hpp
#include "Disk.hpp"
#include "DigitalPhaseLockedLoop.hpp"
#include "../TimedEventLoop.hpp"
namespace Storage {
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
public:
DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
void set_expected_bit_length(Time bit_length);
void set_disk(std::shared_ptr<Disk> disk);
bool has_disk();
void run_for_cycles(int number_of_cycles);
bool get_is_track_zero();
void step(int direction);
void set_motor_on(bool motor_on);
// to satisfy DigitalPhaseLockedLoop::Delegate
void digital_phase_locked_loop_output_bit(int value);
protected:
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole) = 0;
virtual void process_index_hole() = 0;
// for TimedEventLoop
virtual void process_next_event();
private:
Time _bit_length;
unsigned int _clock_rate;
unsigned int _clock_rate_multiplier;
unsigned int _revolutions_per_minute;
std::shared_ptr<DigitalPhaseLockedLoop> _pll;
std::shared_ptr<Disk> _disk;
std::shared_ptr<Track> _track;
int _head_position;
unsigned int _cycles_since_index_hole;
void set_track();
inline void get_next_event();
Track::Event _current_event;
};
}
#endif /* DiskDrive_hpp */

View File

@ -0,0 +1,20 @@
//
// CommodoreGCR.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "CommodoreGCR.hpp"
using namespace Storage;
Time Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned int time_zone)
{
Time duration;
// the speed zone divides a 4Mhz clock by 13, 14, 15 or 16, with higher-numbered zones being faster (i.e. each bit taking less time)
duration.length = 16 - time_zone;
duration.clock_rate = 4000000;
return duration;
}

View File

@ -0,0 +1,22 @@
//
// CommodoreGCR.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef CommodoreGCR_hpp
#define CommodoreGCR_hpp
#include "../../Storage.hpp"
namespace Storage {
namespace Encodings {
namespace CommodoreGCR {
Time length_of_a_bit_in_time_zone(unsigned int time_zone);
}
}
}
#endif /* CommodoreGCR_hpp */

View File

@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "../PCMTrack.hpp" #include "../PCMTrack.hpp"
#include "../Encodings/CommodoreGCR.hpp"
using namespace Storage; using namespace Storage;
@ -40,7 +41,8 @@ G64::G64(const char *file_name)
_maximum_track_size = (uint16_t)fgetc(_file); _maximum_track_size = (uint16_t)fgetc(_file);
_maximum_track_size |= (uint16_t)fgetc(_file) << 8; _maximum_track_size |= (uint16_t)fgetc(_file) << 8;
get_track_at_position(0); // for(size_t c = 0; c < _number_of_tracks; c++)
// get_track_at_position(c);
} }
G64::~G64() G64::~G64()
@ -116,14 +118,14 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
unsigned int start_byte_in_current_speed = 0; unsigned int start_byte_in_current_speed = 0;
for(unsigned int byte = 0; byte < track_length; byte ++) for(unsigned int byte = 0; byte < track_length; byte ++)
{ {
unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2); unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
if(byte_speed != current_speed || byte == (track_length-1)) if(byte_speed != current_speed || byte == (track_length-1))
{ {
unsigned int number_of_bytes = byte - start_byte_in_current_speed; unsigned int number_of_bytes = byte - start_byte_in_current_speed;
PCMSegment segment; PCMSegment segment;
segment.duration.length = number_of_bytes * 8; segment.number_of_bits = 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.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed);
segment.data.reset(new uint8_t[number_of_bytes]); segment.data.reset(new uint8_t[number_of_bytes]);
memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes); memcpy(segment.data.get(), &track_contents.get()[start_byte_in_current_speed], number_of_bytes);
segments.push_back(std::move(segment)); segments.push_back(std::move(segment));
@ -138,8 +140,8 @@ std::shared_ptr<Track> G64::get_track_at_position(unsigned int position)
else else
{ {
PCMSegment segment; PCMSegment segment;
segment.duration.length = track_length * 8; segment.number_of_bits = track_length * 8;
segment.duration.clock_rate = 1; // this is arbitrary; if supplying only one PCMSegment then it'll naturally fill the track segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)speed_zone_offset);
segment.data = std::move(track_contents); segment.data = std::move(track_contents);
resulting_track.reset(new PCMTrack(std::move(segment))); resulting_track.reset(new PCMTrack(std::move(segment)));

View File

@ -7,34 +7,10 @@
// //
#include "PCMTrack.hpp" #include "PCMTrack.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage; 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) PCMTrack::PCMTrack(std::vector<PCMSegment> segments)
{ {
_segments = std::move(segments); _segments = std::move(segments);
@ -55,15 +31,17 @@ PCMTrack::Event PCMTrack::get_next_event()
_next_event.length.length = 0; _next_event.length.length = 0;
while(_segment_pointer < _segments.size()) while(_segment_pointer < _segments.size())
{ {
unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].duration.clock_rate; unsigned int clock_multiplier = _track_clock_rate / _segments[_segment_pointer].length_of_a_bit.clock_rate;
unsigned int bit_length = clock_multiplier * _segments[_segment_pointer].length_of_a_bit.length;
const uint8_t *segment_data = _segments[_segment_pointer].data.get(); const uint8_t *segment_data = _segments[_segment_pointer].data.get();
while(_bit_pointer < _segments[_segment_pointer].duration.length) while(_bit_pointer < _segments[_segment_pointer].number_of_bits)
{ {
// for timing simplicity, bits are modelled as happening at the end of their window // 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? // 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)); int bit = segment_data[_bit_pointer >> 3] & (0x80 >> (_bit_pointer&7));
_bit_pointer++; _bit_pointer++;
_next_event.length.length += clock_multiplier; _next_event.length.length += bit_length;
if(bit) return _next_event; if(bit) return _next_event;
} }
@ -84,18 +62,18 @@ PCMTrack::Event PCMTrack::get_next_event()
void PCMTrack::fix_length() void PCMTrack::fix_length()
{ {
// find the least common multiple of all segment clock rates // find the least common multiple of all segment clock rates
_track_clock_rate = _segments[0].duration.clock_rate; _track_clock_rate = _segments[0].length_of_a_bit.clock_rate;
for(size_t c = 1; c < _segments.size(); c++) for(size_t c = 1; c < _segments.size(); c++)
{ {
_track_clock_rate = least_common_multiple(_track_clock_rate, _segments[c].duration.clock_rate); _track_clock_rate = NumberTheory::least_common_multiple(_track_clock_rate, _segments[c].length_of_a_bit.clock_rate);
} }
// therby determine the total length, storing it to next_event as the divisor // thereby determine the total length, storing it to next_event as the track-total divisor
_next_event.length.clock_rate = 0; _next_event.length.clock_rate = 0;
for(size_t c = 0; c < _segments.size(); c++) for(size_t c = 0; c < _segments.size(); c++)
{ {
unsigned int multiplier = _track_clock_rate / _segments[c].duration.clock_rate; unsigned int multiplier = _track_clock_rate / _segments[c].length_of_a_bit.clock_rate;
_next_event.length.clock_rate += _segments[c].duration.length * multiplier; _next_event.length.clock_rate += _segments[c].length_of_a_bit.length * _segments[c].number_of_bits * multiplier;
} }
_segment_pointer = _bit_pointer = 0; _segment_pointer = _bit_pointer = 0;

View File

@ -15,22 +15,13 @@
namespace Storage { namespace Storage {
/*! /*!
A segment of PCM-sampled data. The clock rate in the duration is taken to be relative to all other A segment of PCM-sampled data.
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. 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 { struct PCMSegment {
Time duration; Time length_of_a_bit;
unsigned int number_of_bits;
std::unique_ptr<uint8_t> data; std::unique_ptr<uint8_t> data;
}; };

View File

@ -9,10 +9,24 @@
#ifndef Storage_hpp #ifndef Storage_hpp
#define Storage_hpp #define Storage_hpp
#include "../NumberTheory/Factors.hpp"
namespace Storage { namespace Storage {
struct Time { struct Time {
unsigned int length, clock_rate; unsigned int length, clock_rate;
inline void simplify()
{
unsigned int common_divisor = NumberTheory::greatest_common_divisor(length, clock_rate);
length /= common_divisor;
clock_rate /= common_divisor;
}
inline float get_float()
{
return (float)length / (float)clock_rate;
}
}; };
} }

View File

@ -7,6 +7,7 @@
// //
#include "Tape.hpp" #include "Tape.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage; using namespace Storage;
@ -16,12 +17,13 @@ void Tape::seek(Time seek_time)
} }
TapePlayer::TapePlayer(unsigned int input_clock_rate) : TapePlayer::TapePlayer(unsigned int input_clock_rate) :
_input_clock_rate(input_clock_rate) TimedEventLoop(input_clock_rate)
{} {}
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape) void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape)
{ {
_tape = tape; _tape = tape;
reset_timer();
get_next_pulse(); get_next_pulse();
} }
@ -32,38 +34,34 @@ bool TapePlayer::has_tape()
void TapePlayer::get_next_pulse() void TapePlayer::get_next_pulse()
{ {
_input.time_into_pulse = 0; // get the new pulse
if(_tape) if(_tape)
_input.current_pulse = _tape->get_next_pulse(); _current_pulse = _tape->get_next_pulse();
else else
{ {
_input.current_pulse.length.length = 1; _current_pulse.length.length = 1;
_input.current_pulse.length.clock_rate = 1; _current_pulse.length.clock_rate = 1;
_input.current_pulse.type = Storage::Tape::Pulse::Zero; _current_pulse.type = Storage::Tape::Pulse::Zero;
}
if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate())
{
_input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, _input_clock_rate));
} }
set_next_event_time_interval(_current_pulse.length);
} }
void TapePlayer::run_for_cycles(unsigned int number_of_cycles) void TapePlayer::run_for_cycles(int number_of_cycles)
{ {
if(has_tape()) if(has_tape())
{ {
while(number_of_cycles--) ::TimedEventLoop::run_for_cycles(number_of_cycles);
{
_input.time_into_pulse += (unsigned int)_input.pulse_stepper->step();
while(_input.time_into_pulse >= _input.current_pulse.length.length)
{
run_for_input_pulse();
}
}
} }
} }
void TapePlayer::run_for_input_pulse() void TapePlayer::run_for_input_pulse()
{ {
process_input_pulse(_input.current_pulse); jump_to_next_event();
}
void TapePlayer::process_next_event()
{
process_input_pulse(_current_pulse);
get_next_pulse(); get_next_pulse();
} }

View File

@ -10,8 +10,7 @@
#define Tape_hpp #define Tape_hpp
#include <memory> #include <memory>
#include "../../SignalProcessing/Stepper.hpp" #include "../TimedEventLoop.hpp"
#include "../Storage.hpp"
namespace Storage { namespace Storage {
@ -48,29 +47,25 @@ class Tape {
Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass 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. can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type.
*/ */
class TapePlayer { class TapePlayer: public TimedEventLoop {
public: public:
TapePlayer(unsigned int input_clock_rate); TapePlayer(unsigned int input_clock_rate);
void set_tape(std::shared_ptr<Storage::Tape> tape); void set_tape(std::shared_ptr<Storage::Tape> tape);
bool has_tape(); bool has_tape();
void run_for_cycles(unsigned int number_of_cycles); void run_for_cycles(int number_of_cycles);
void run_for_input_pulse(); void run_for_input_pulse();
protected: protected:
virtual void process_next_event();
virtual void process_input_pulse(Tape::Pulse pulse) = 0; virtual void process_input_pulse(Tape::Pulse pulse) = 0;
private: private:
inline void get_next_pulse(); inline void get_next_pulse();
unsigned int _input_clock_rate;
std::shared_ptr<Storage::Tape> _tape; std::shared_ptr<Storage::Tape> _tape;
struct { Tape::Pulse _current_pulse;
Tape::Pulse current_pulse;
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
uint32_t time_into_pulse;
} _input;
}; };
} }

View File

@ -0,0 +1,72 @@
//
// TimedEventLoop.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "TimedEventLoop.hpp"
#include "../NumberTheory/Factors.hpp"
using namespace Storage;
TimedEventLoop::TimedEventLoop(unsigned int input_clock_rate) :
_input_clock_rate(input_clock_rate) {}
void TimedEventLoop::run_for_cycles(int number_of_cycles)
{
_time_into_interval += (unsigned int)_stepper->step((uint64_t)number_of_cycles);
while(_time_into_interval >= _event_interval.length)
{
process_next_event();
}
}
void TimedEventLoop::reset_timer()
{
_time_into_interval = 0;
_stepper.reset();
}
void TimedEventLoop::jump_to_next_event()
{
reset_timer();
process_next_event();
}
void TimedEventLoop::set_next_event_time_interval(Time interval)
{
// figure out how much time has been run since the last bit ended
if(_stepper)
{
_time_into_interval -= _event_interval.length;
if(_time_into_interval)
{
// simplify the quotient
unsigned int common_divisor = NumberTheory::greatest_common_divisor(_time_into_interval, _event_interval.clock_rate);
_time_into_interval /= common_divisor;
_event_interval.clock_rate /= common_divisor;
// build a quotient that is the sum of the time overrun plus the incoming time and adjust the time overrun
// to be in terms of the new quotient
unsigned int denominator = NumberTheory::least_common_multiple(_event_interval.clock_rate, interval.clock_rate);
interval.length *= denominator / interval.clock_rate;
interval.clock_rate = denominator;
_time_into_interval *= denominator / _event_interval.clock_rate;
}
}
else
{
_time_into_interval = 0;
}
// store new interval
_event_interval = interval;
// adjust stepper if required
if(!_stepper || _event_interval.clock_rate != _stepper->get_output_rate())
{
_stepper.reset(new SignalProcessing::Stepper(_event_interval.clock_rate, _input_clock_rate));
}
}

View File

@ -0,0 +1,40 @@
//
// TimedEventLoop.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef TimedEventLoop_hpp
#define TimedEventLoop_hpp
#include "Storage.hpp"
#include <memory>
#include "../SignalProcessing/Stepper.hpp"
namespace Storage {
class TimedEventLoop {
public:
TimedEventLoop(unsigned int input_clock_rate);
void run_for_cycles(int number_of_cycles);
protected:
void reset_timer();
void set_next_event_time_interval(Time interval);
void jump_to_next_event();
virtual void process_next_event() = 0;
private:
unsigned int _input_clock_rate;
Time _event_interval;
std::unique_ptr<SignalProcessing::Stepper> _stepper;
uint32_t _time_into_interval;
};
}
#endif /* TimedEventLoop_hpp */