1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 03:32:01 +00:00

Merge pull request #48 from TomHarte/FasterLoading

Introduces an initial attempt at static analysis of emulator programs
This commit is contained in:
Thomas Harte 2016-09-15 19:38:23 -04:00 committed by GitHub
commit cc66e1973f
78 changed files with 3129 additions and 373 deletions

View File

@ -65,9 +65,6 @@ template <class T> class MOS6560 {
// default to NTSC
set_output_mode(OutputMode::NTSC);
// show only the centre
_crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
}
void set_clock_rate(double clock_rate)
@ -125,6 +122,18 @@ template <class T> class MOS6560 {
}
_crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type);
// _crt->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// switch(output_mode)
// {
// case OutputMode::PAL:
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
// break;
// case OutputMode::NTSC:
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
// break;
// }
for(int c = 0; c < 16; c++)
{
_colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);

View File

@ -736,8 +736,12 @@ void Machine::set_switch_is_enabled(Atari2600Switch input, bool state)
}
}
void Machine::set_rom(size_t length, const uint8_t *data)
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
if(!target.cartridges.front()->get_segments().size()) return;
Storage::Cartridge::Cartridge::Segment segment = target.cartridges.front()->get_segments().front();
size_t length = segment.data.size();
_rom_size = 1024;
while(_rom_size < length && _rom_size < 32768) _rom_size <<= 1;
@ -750,7 +754,7 @@ void Machine::set_rom(size_t length, const uint8_t *data)
while(offset < _rom_size)
{
size_t copy_length = std::min(copy_step, _rom_size - offset);
memcpy(&_rom[offset], data, copy_length);
memcpy(&_rom[offset], &segment.data[0], copy_length);
offset += copy_length;
}

View File

@ -15,6 +15,7 @@
#include "../../Components/6532/6532.hpp"
#include "../CRTMachine.hpp"
#include "../ConfigurationTarget.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
@ -72,13 +73,16 @@ class PIA: public MOS::MOS6532<PIA> {
};
class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine {
public:
Machine();
~Machine();
void set_rom(size_t length, const uint8_t *data);
void configure_as_target(const StaticAnalyser::Target &target);
void switch_region();
void set_digital_input(Atari2600DigitalInput input, bool state);

View File

@ -21,6 +21,8 @@ namespace CRTMachine {
*/
class Machine {
public:
Machine() : clock_is_unlimited_(false) {}
virtual void setup_output(float aspect_ratio) = 0;
virtual void close_output() = 0;
@ -33,23 +35,34 @@ class Machine {
double get_clock_rate() {
return clock_rate_;
}
bool get_clock_is_unlimited() {
return clock_is_unlimited_;
}
class Delegate {
public:
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
void set_delegate(Delegate *delegate) { this->delegate_ = delegate; }
protected:
double clock_rate_;
void set_clock_rate(double clock_rate) {
if(clock_rate_ != clock_rate) {
clock_rate_ = clock_rate;
if(delegate_) delegate_->machine_did_change_clock_rate(this);
}
}
void set_clock_is_unlimited(bool clock_is_unlimited) {
if(clock_is_unlimited != clock_is_unlimited_) {
clock_is_unlimited_ = clock_is_unlimited;
if(delegate_) delegate_->machine_did_change_clock_is_unlimited(this);
}
}
private:
Delegate *delegate_;
double clock_rate_;
bool clock_is_unlimited_;
};
}

View File

@ -14,7 +14,7 @@ using namespace Commodore::C1540;
Machine::Machine() :
_shift_register(0),
Storage::DiskDrive(1000000, 4, 300)
Storage::Disk::Drive(1000000, 4, 300)
{
// create a serial port and a VIA to run it
_serialPortVIA.reset(new SerialPortVIA);
@ -106,7 +106,7 @@ void Machine::run_for_cycles(int number_of_cycles)
{
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);
Storage::Disk::Drive::run_for_cycles(number_of_cycles);
}
#pragma mark - 6522 delegate

View File

@ -216,7 +216,7 @@ class Machine:
public CPU6502::Processor<Machine>,
public MOS::MOS6522IRQDelegate::Delegate,
public DriveVIA::Delegate,
public Storage::DiskDrive {
public Storage::Disk::Drive {
public:
Machine();
@ -251,7 +251,7 @@ class Machine:
std::shared_ptr<SerialPort> _serialPort;
DriveVIA _driveVIA;
std::shared_ptr<Storage::Disk> _disk;
std::shared_ptr<Storage::Disk::Disk> _disk;
int _shift_register, _bit_window_offset;
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);

View File

@ -10,11 +10,13 @@
#include <algorithm>
#include "../../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../../StaticAnalyser/StaticAnalyser.hpp"
using namespace Commodore::Vic20;
Machine::Machine() :
_rom(nullptr)
_rom(nullptr),
_is_running_at_zero_cost(false)
{
// create 6522s, serial port and bus
_userPortVIA.reset(new UserPortVIA);
@ -114,7 +116,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
// }
// run the phase-1 part of this cycle, in which the VIC accesses memory
_mos6560->run_for_cycles(1);
if(!_is_running_at_zero_cost) _mos6560->run_for_cycles(1);
// run the phase-2 part of the cycle, which is whatever the 6502 said it should be
if(isReadOperation(operation))
@ -128,11 +130,13 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
*value = result;
// test for PC at F92F
// This combined with the stuff below constitutes the fast tape hack. Performed here: if the
// PC hits the start of the loop that just waits for an interesting tape interrupt to have
// occurred then skip both 6522s and the tape ahead to the next interrupt without any further
// CPU or 6560 costs.
if(_use_fast_tape_hack && _tape.has_tape() && address == 0xf92f && operation == CPU6502::BusOperation::ReadOpcode)
{
// advance time on the tape and the VIAs until an interrupt is signalled
while(!_userPortVIA->get_interrupt_line() && !_keyboardVIA->get_interrupt_line())
while(!_userPortVIA->get_interrupt_line() && !_keyboardVIA->get_interrupt_line() && !_tape.get_tape()->is_at_end())
{
_userPortVIA->run_for_half_cycles(2);
_keyboardVIA->run_for_half_cycles(2);
@ -161,6 +165,35 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
_tape.run_for_cycles(1);
if(_c1540) _c1540->run_for_cycles(1);
// If using fast tape then:
// if the PC hits 0xf98e, the ROM's tape loading routine, then begin zero cost processing;
// if the PC heads into RAM
//
// Where 'zero cost processing' is taken to be taking the 6560 off the bus (because I know it's
// expensive, and not relevant) then running the tape, the CPU and both 6522s as usual but not
// counting cycles towards the processing budget. So the limit is the host machine.
//
// Note the additional test above for PC hitting 0xf92f, which is a loop in the ROM that waits
// for an interesting interrupt. Up there the fast tape hack goes even further in also cutting
// the CPU out of the action.
if(_use_fast_tape_hack && _tape.has_tape())
{
if(address == 0xf98e && operation == CPU6502::BusOperation::ReadOpcode)
{
_is_running_at_zero_cost = true;
set_clock_is_unlimited(true);
}
if(
(address < 0xe000 && operation == CPU6502::BusOperation::ReadOpcode) ||
_tape.get_tape()->is_at_end()
)
{
_is_running_at_zero_cost = false;
set_clock_is_unlimited(false);
}
}
return 1;
}
@ -241,6 +274,9 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
{
// TEST!
StaticAnalyser::GetTargets(file_name);
if(length > 2)
{
_rom_address = (uint16_t)(data[0] | (data[1] << 8));
@ -255,17 +291,46 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
}
else
{
set_tape(std::shared_ptr<Storage::Tape>(new Storage::TapePRG(file_name)));
set_tape(std::shared_ptr<Storage::Tape::Tape>(new Storage::Tape::PRG(file_name)));
}
}
}
#pragma mar - Tape
void Machine::set_tape(std::shared_ptr<Storage::Tape> tape)
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
_tape.set_tape(tape);
if(_should_automatically_load_media) set_typer_for_string("LOAD\nRUN\n");
if(target.tapes.size())
{
_tape.set_tape(target.tapes.front());
}
if(_should_automatically_load_media)
{
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
set_typer_for_string(target.loadingCommand.c_str());
}
switch(target.vic20.memory_model)
{
case StaticAnalyser::Vic20MemoryModel::Unexpanded:
set_memory_size(Default);
break;
case StaticAnalyser::Vic20MemoryModel::EightKB:
set_memory_size(ThreeKB);
break;
case StaticAnalyser::Vic20MemoryModel::ThirtyTwoKB:
set_memory_size(ThirtyTwoKB);
break;
}
}
}
void Machine::set_tape(std::shared_ptr<Storage::Tape::Tape> tape)
{
// _tape.set_tape(tape);
// if(_should_automatically_load_media) set_typer_for_string("LOAD\nRUN\n");
}
void Machine::tape_did_change_input(Tape *tape)
@ -275,7 +340,7 @@ void Machine::tape_did_change_input(Tape *tape)
#pragma mark - Disc
void Machine::set_disk(std::shared_ptr<Storage::Disk> disk)
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk)
{
// construct the 1540
_c1540.reset(new ::Commodore::C1540::Machine);
@ -415,9 +480,9 @@ Tape::Tape() : TapePlayer(1022727) {}
void Tape::set_motor_control(bool enabled) {}
void Tape::set_tape_output(bool set) {}
void Tape::process_input_pulse(Storage::Tape::Pulse pulse)
void Tape::process_input_pulse(Storage::Tape::PRG::Pulse pulse)
{
bool new_input_level = pulse.type == Storage::Tape::Pulse::Low;
bool new_input_level = pulse.type == Storage::Tape::PRG::Pulse::Low;
if(_input_level != new_input_level)
{
_input_level = new_input_level;

View File

@ -9,6 +9,7 @@
#ifndef Vic20_hpp
#define Vic20_hpp
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
@ -213,7 +214,7 @@ class SerialPort : public ::Commodore::Serial::Port {
std::weak_ptr<UserPortVIA> _userPortVIA;
};
class Tape: public Storage::TapePlayer {
class Tape: public Storage::Tape::TapePlayer {
public:
Tape();
@ -232,7 +233,7 @@ class Tape: public Storage::TapePlayer {
private:
Delegate *_delegate;
virtual void process_input_pulse(Storage::Tape::Pulse pulse);
virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
bool _input_level;
};
@ -253,16 +254,18 @@ class Machine:
public CRTMachine::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Tape::Delegate {
public Tape::Delegate,
public ConfigurationTarget::Machine {
public:
Machine();
~Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void configure_as_target(const StaticAnalyser::Target &target);
void set_prg(const char *file_name, size_t length, const uint8_t *data);
void set_tape(std::shared_ptr<Storage::Tape> tape);
void set_disk(std::shared_ptr<Storage::Disk> disk);
void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
void set_key_state(Key key, bool isPressed) { _keyboardVIA->set_key_state(key, isPressed); }
void clear_all_keys() { _keyboardVIA->clear_all_keys(); }
@ -331,6 +334,7 @@ class Machine:
// Tape
Tape _tape;
bool _use_fast_tape_hack, _should_automatically_load_media;
bool _is_running_at_zero_cost;
// Disk
std::shared_ptr<::Commodore::C1540::Machine> _c1540;

View File

@ -0,0 +1,27 @@
//
// ConfigurationTarget.h
// Clock Signal
//
// Created by Thomas Harte on 08/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef ConfigurationTarget_hpp
#define ConfigurationTarget_hpp
#include "../StaticAnalyser/StaticAnalyser.hpp"
namespace ConfigurationTarget {
/*!
A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target
and configure itself appropriately.
*/
class Machine {
public:
virtual void configure_as_target(const StaticAnalyser::Target &target) =0;
};
}
#endif /* ConfigurationTarget_h */

View File

@ -458,9 +458,17 @@ void Machine::synchronise()
update_audio();
}
void Machine::set_tape(std::shared_ptr<Storage::Tape> tape)
void Machine::configure_as_target(const StaticAnalyser::Target &target)
{
_tape.set_tape(tape);
if(target.tapes.size())
{
_tape.set_tape(target.tapes.front());
}
if(target.loadingCommand.length()) // TODO: and automatic loading option enabled
{
set_typer_for_string(target.loadingCommand.c_str());
}
}
void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data)
@ -971,14 +979,14 @@ inline uint8_t Tape::get_data_register()
return (uint8_t)(_data_register >> 2);
}
inline void Tape::process_input_pulse(Storage::Tape::Pulse pulse)
inline void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse)
{
_crossings[0] = _crossings[1];
_crossings[1] = _crossings[2];
_crossings[2] = _crossings[3];
_crossings[3] = Tape::Unrecognised;
if(pulse.type != Storage::Tape::Pulse::Zero)
if(pulse.type != Storage::Tape::Tape::Pulse::Zero)
{
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) _crossings[3] = Tape::Short;

View File

@ -12,6 +12,7 @@
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
@ -62,7 +63,7 @@ enum Key: uint16_t {
TerminateSequence = 0, NotMapped = 0xfffe,
};
class Tape: public Storage::TapePlayer {
class Tape: public Storage::Tape::TapePlayer {
public:
Tape();
@ -86,7 +87,7 @@ class Tape: public Storage::TapePlayer {
inline void set_is_in_input_mode(bool is_in_input_mode);
private:
void process_input_pulse(Storage::Tape::Pulse pulse);
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
@ -138,14 +139,15 @@ class Speaker: public ::Outputs::Filter<Speaker> {
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
Tape::Delegate,
public Utility::TypeRecipient {
public Tape::Delegate,
public Utility::TypeRecipient,
public ConfigurationTarget::Machine {
public:
Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void set_tape(std::shared_ptr<Storage::Tape> tape);
void configure_as_target(const StaticAnalyser::Target &target);
void set_key_state(Key key, bool isPressed);
void clear_all_keys();

View File

@ -26,6 +26,7 @@
4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; };
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */; };
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */; };
4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */; };
4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; };
@ -39,6 +40,8 @@
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */; };
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
@ -46,6 +49,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 */; };
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA22B051D8817CE0008C640 /* Disk.cpp */; };
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA799931D8B656E0045123D /* StaticAnalyser.cpp */; };
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 */; };
@ -331,16 +337,24 @@
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */; };
4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC830CF1D6E7C690000A26F /* Tape.cpp */; };
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; };
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -402,6 +416,8 @@
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = C1540Bridge.h; sourceTree = "<group>"; };
4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = C1540Bridge.mm; sourceTree = "<group>"; };
@ -425,6 +441,10 @@
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
@ -435,6 +455,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>"; };
4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; };
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
4BA22B051D8817CE0008C640 /* Disk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Disk.cpp; path = ../../StaticAnalyser/Commodore/Disk.cpp; sourceTree = "<group>"; };
4BA22B061D8817CE0008C640 /* Disk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Disk.hpp; path = ../../StaticAnalyser/Commodore/Disk.hpp; sourceTree = "<group>"; };
4BA799931D8B656E0045123D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BA799941D8B656E0045123D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Atari/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ConfigurationTarget.hpp; 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>"; };
@ -744,10 +771,16 @@
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Utilities.cpp; path = ../../StaticAnalyser/Commodore/Utilities.cpp; sourceTree = "<group>"; };
4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Utilities.hpp; path = ../../StaticAnalyser/Commodore/Utilities.hpp; sourceTree = "<group>"; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
4BC830CF1D6E7C690000A26F /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Commodore/Tape.cpp; sourceTree = "<group>"; };
4BC830D01D6E7C690000A26F /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Commodore/Tape.hpp; sourceTree = "<group>"; };
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; };
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
@ -755,11 +788,22 @@
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502InterruptTests.swift; sourceTree = "<group>"; };
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; 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>"; };
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
4BEE0A6E1D72496600532C7B /* PRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PRG.hpp; 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>"; };
4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/StaticAnalyser.hpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -855,10 +899,12 @@
children = (
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */,
4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */,
4B2A53941D117D36003C6002 /* CSMachine+Subclassing.h */,
4B2A53951D117D36003C6002 /* CSMachine.h */,
4B2A53961D117D36003C6002 /* CSMachine.mm */,
4B2A53941D117D36003C6002 /* CSMachine+Subclassing.h */,
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */,
4B2A53971D117D36003C6002 /* KeyCodes.h */,
4B2A53961D117D36003C6002 /* CSMachine.mm */,
4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */,
4B2A53981D117D36003C6002 /* Wrappers */,
);
path = Machine;
@ -976,12 +1022,30 @@
path = Views;
sourceTree = "<group>";
};
4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */ = {
isa = PBXGroup;
children = (
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */,
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */,
);
name = StaticAnalyser;
sourceTree = "<group>";
};
4B643F3D1D77B88000D431D6 /* Document Controller */ = {
isa = PBXGroup;
children = (
4B643F3E1D77B88000D431D6 /* DocumentController.swift */,
);
path = "Document Controller";
sourceTree = "<group>";
};
4B69FB391C4D908A00B5F0AA /* Storage */ = {
isa = PBXGroup;
children = (
4BB697C91D4B6D3E00248BDF /* TimedEventLoop.cpp */,
4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */,
4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */,
4BEE0A691D72496600532C7B /* Cartridge */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
);
@ -1013,6 +1077,15 @@
path = Formats;
sourceTree = "<group>";
};
4BA799961D8B65730045123D /* Atari */ = {
isa = PBXGroup;
children = (
4BA799931D8B656E0045123D /* StaticAnalyser.cpp */,
4BA799941D8B656E0045123D /* StaticAnalyser.hpp */,
);
name = Atari;
sourceTree = "<group>";
};
4BAB62AA1D3272D200DF5BA0 /* Disk */ = {
isa = PBXGroup;
children = (
@ -1345,6 +1418,7 @@
4BB73EDD1B587CA500552FC2 /* Processors */,
4BB73E9F1B587A5100552FC2 /* Products */,
4B2409591C45DF85004DA684 /* SignalProcessing */,
4BF1354D1D6D2C360054B2EA /* StaticAnalyser */,
4B69FB391C4D908A00B5F0AA /* Storage */,
);
indentWidth = 4;
@ -1365,16 +1439,17 @@
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
isa = PBXGroup;
children = (
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4BB73EAD1B587A5100552FC2 /* Info.plist */,
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
4B2A538F1D117D36003C6002 /* Audio */,
4B643F3D1D77B88000D431D6 /* Document Controller */,
4B55CE551C3B7D360093A61B /* Documents */,
4B2A53921D117D36003C6002 /* Machine */,
4BE5F85A1C3E1C2500C43F01 /* Resources */,
4BD5F1961D1352A000631CD1 /* Updater */,
4B55CE5A1C3B7D6F0093A61B /* Views */,
);
@ -1418,6 +1493,7 @@
4B2E2D9E1C3A070900138695 /* Electron */,
4B1E85731D170228001EF87D /* Typer.cpp */,
4B1E85741D170228001EF87D /* Typer.hpp */,
4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */,
);
name = Machines;
path = ../../Machines;
@ -1462,6 +1538,23 @@
path = Shaders;
sourceTree = "<group>";
};
4BC830D21D6E7C6D0000A26F /* Commodore */ = {
isa = PBXGroup;
children = (
4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */,
4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */,
4BC830CF1D6E7C690000A26F /* Tape.cpp */,
4BC830D01D6E7C690000A26F /* Tape.hpp */,
4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */,
4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */,
4BE77A2C1D84ADFB00BC3827 /* File.cpp */,
4BE77A2D1D84ADFB00BC3827 /* File.hpp */,
4BA22B051D8817CE0008C640 /* Disk.cpp */,
4BA22B061D8817CE0008C640 /* Disk.hpp */,
);
name = Commodore;
sourceTree = "<group>";
};
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
@ -1490,6 +1583,17 @@
path = 6560;
sourceTree = "<group>";
};
4BD14B121D7462810088EAD6 /* Acorn */ = {
isa = PBXGroup;
children = (
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */,
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */,
4B96F7201D75119A0058BB2D /* Tape.cpp */,
4B96F7211D75119A0058BB2D /* Tape.hpp */,
);
name = Acorn;
sourceTree = "<group>";
};
4BD5F1961D1352A000631CD1 /* Updater */ = {
isa = PBXGroup;
children = (
@ -1507,6 +1611,40 @@
path = Resources;
sourceTree = "<group>";
};
4BEE0A691D72496600532C7B /* Cartridge */ = {
isa = PBXGroup;
children = (
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */,
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */,
4BEE0A6C1D72496600532C7B /* Formats */,
);
path = Cartridge;
sourceTree = "<group>";
};
4BEE0A6C1D72496600532C7B /* Formats */ = {
isa = PBXGroup;
children = (
4BEE0A6D1D72496600532C7B /* PRG.cpp */,
4BEE0A6E1D72496600532C7B /* PRG.hpp */,
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */,
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */,
);
path = Formats;
sourceTree = "<group>";
};
4BF1354D1D6D2C360054B2EA /* StaticAnalyser */ = {
isa = PBXGroup;
children = (
4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */,
4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */,
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */,
4BD14B121D7462810088EAD6 /* Acorn */,
4BA799961D8B65730045123D /* Atari */,
4BC830D21D6E7C6D0000A26F /* Commodore */,
);
name = StaticAnalyser;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -1916,28 +2054,37 @@
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */,
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */,
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
4BB697C71D4B558F00248BDF /* Factors.cpp in Sources */,
4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */,
4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */,
4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */,
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */,
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */,
4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */,
4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */,
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */,
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
4B6C73BD1D387AE500AFCFCA /* DiskDrive.cpp in Sources */,
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */,
4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */,
4B1E85751D170228001EF87D /* Typer.cpp in Sources */,
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
@ -1945,15 +2092,20 @@
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */,
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */,
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
4B4C83701D4F623200CD541F /* D64.cpp in Sources */,
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
</dependencies>
@ -13,6 +13,7 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Clock_Signal" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="SOe-RA-8of" customClass="DocumentController" customModule="Clock_Signal" customModuleProvider="target"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="Clock Signal" id="1Xt-HY-uBw">

View File

@ -48,8 +48,6 @@
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="134"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<value key="minSize" type="size" width="200" height="103"/>
<value key="maxSize" type="size" width="200" height="103"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="200" height="134"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@ -10,6 +10,8 @@
#import "CSElectron.h"
#import "CSVic20.h"
#import "CSStaticAnalyser.h"
#import "CSOpenGLView.h"
#import "CSAudioQueue.h"
#import "CSBestEffortUpdater.h"

View File

@ -0,0 +1,26 @@
//
// DocumentController.swift
// Clock Signal
//
// Created by Thomas Harte on 18/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
import Cocoa
class DocumentController: NSDocumentController {
override func makeDocumentWithContentsOfURL(url: NSURL, ofType typeName: String) throws -> NSDocument {
if let analyser = CSStaticAnalyser(fileAtURL: url) {
if let documentClass = analyser.documentClass as? NSDocument.Type {
let document = documentClass.init()
if let machineDocument = document as? MachineDocument {
machineDocument.setDisplayName(analyser.displayName)
machineDocument.configureAs(analyser)
return machineDocument
}
}
}
return try! super.makeDocumentWithContentsOfURL(url, ofType: typeName)
}
}

View File

@ -31,10 +31,6 @@ class Atari2600Document: MachineDocument {
return "Atari2600Document"
}
override func readFromData(data: NSData, ofType typeName: String) throws {
atari2600.setROM(data)
}
override func windowControllerDidLoadNib(aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)

View File

@ -44,7 +44,7 @@ class ElectronDocument: MachineDocument {
return "ElectronDocument"
}
override func readFromURL(url: NSURL, ofType typeName: String) throws {
/* override func readFromURL(url: NSURL, ofType typeName: String) throws {
if let pathExtension = url.pathExtension {
switch pathExtension.lowercaseString {
case "uef":
@ -63,7 +63,7 @@ class ElectronDocument: MachineDocument {
electron.setROM(plus1ROM, slot: 12)
}
electron.setROM(data, slot: 15)
}
}*/
// MARK: IBActions
@IBOutlet var displayTypeButton: NSPopUpButton?

View File

@ -73,6 +73,10 @@ class MachineDocument:
setupClockRate()
}
func machineDidChangeClockIsUnlimited(machine: CSMachine!) {
self.bestEffortUpdater.runAsUnlimited = machine.clockIsUnlimited
}
private func setupClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
@ -98,6 +102,11 @@ class MachineDocument:
super.close()
}
// MARK: configuring
func configureAs(analysis: CSStaticAnalyser) {
analysis.applyToMachine(self.machine)
}
// MARK: the pasteboard
func paste(sender: AnyObject!) {
let pasteboard = NSPasteboard.generalPasteboard()

View File

@ -161,18 +161,21 @@ class Vic20Document: MachineDocument {
vic20.shouldLoadAutomatically = loadAutomatically
self.loadAutomaticallyButton?.state = loadAutomatically ? NSOnState : NSOffState
let memorySize = standardUserDefaults.integerForKey(self.memorySizeUserDefaultsKey)
var indexToSelect: Int?
switch memorySize {
case 32: indexToSelect = 2
case 8: indexToSelect = 1
default: indexToSelect = 0
}
if let indexToSelect = indexToSelect {
self.memorySizeButton?.selectItemAtIndex(indexToSelect)
setMemorySize(indexToSelect)
if !loadAutomatically {
let memorySize = standardUserDefaults.integerForKey(self.memorySizeUserDefaultsKey)
var indexToSelect: Int?
switch memorySize {
case 32: indexToSelect = 2
case 8: indexToSelect = 1
default: indexToSelect = 0
}
if let indexToSelect = indexToSelect {
self.memorySizeButton?.selectItemAtIndex(indexToSelect)
setMemorySize(indexToSelect)
}
}
// TODO: this should be part of the configuration
let country = standardUserDefaults.integerForKey(self.countryUserDefaultsKey)
setCountry(country)
self.countryButton?.selectItemAtIndex(country)

View File

@ -35,7 +35,7 @@
<key>CFBundleTypeName</key>
<string>Electron/BBC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array/>
<key>LSTypeIsPackage</key>

View File

@ -0,0 +1,17 @@
//
// Target.h
// Clock Signal
//
// Created by Thomas Harte on 31/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
#include "StaticAnalyser.hpp"
@interface CSMachine(Target)
- (void)applyTarget:(StaticAnalyser::Target)target;
@end

View File

@ -13,6 +13,7 @@
@class CSMachine;
@protocol CSMachineDelegate
- (void)machineDidChangeClockRate:(CSMachine *)machine;
- (void)machineDidChangeClockIsUnlimited:(CSMachine *)machine;
@end
@interface CSMachine : NSObject
@ -28,7 +29,9 @@
@property (nonatomic, strong) CSAudioQueue *audioQueue;
@property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
@property (nonatomic, readonly) double clockRate;
@property (nonatomic, readonly) BOOL clockIsUnlimited;
- (void)paste:(NSString *)string;

View File

@ -8,11 +8,15 @@
#import "CSMachine.h"
#import "CSMachine+Subclassing.h"
#import "CSMachine+Target.h"
#include "Typer.hpp"
#include "ConfigurationTarget.hpp"
@interface CSMachine()
- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
- (void)machineDidChangeClockRate;
- (void)machineDidChangeClockIsUnlimited;
@end
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
@ -27,6 +31,9 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
[machine machineDidChangeClockRate];
}
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
[machine machineDidChangeClockIsUnlimited];
}
};
@implementation CSMachine {
@ -53,6 +60,10 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
[self.delegate machineDidChangeClockRate:self];
}
- (void)machineDidChangeClockIsUnlimited {
[self.delegate machineDidChangeClockIsUnlimited:self];
}
- (void)dealloc {
[_view performWithGLContext:^{
@synchronized(self) {
@ -116,10 +127,22 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
return self.machine->get_clock_rate();
}
- (BOOL)clockIsUnlimited {
return self.machine->get_clock_is_unlimited() ? YES : NO;
}
- (void)paste:(NSString *)paste {
Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(self.machine);
if(typeRecipient)
typeRecipient->set_typer_for_string([paste UTF8String]);
}
- (void)applyTarget:(StaticAnalyser::Target)target {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget =
dynamic_cast<ConfigurationTarget::Machine *>(self.machine);
if(configurationTarget) configurationTarget->configure_as_target(target);
}
}
@end

View File

@ -0,0 +1,21 @@
//
// CSStaticAnalyser.h
// Clock Signal
//
// Created by Thomas Harte on 31/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
@class CSMachine;
@interface CSStaticAnalyser : NSObject
- (instancetype)initWithFileAtURL:(NSURL *)url;
@property(nonatomic, readonly) Class documentClass;
@property(nonatomic, readonly) NSString *displayName;
- (void)applyToMachine:(CSMachine *)machine;
@end

View File

@ -0,0 +1,54 @@
//
// CSStaticAnalyser.m
// Clock Signal
//
// Created by Thomas Harte on 31/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSStaticAnalyser.h"
#import "CSMachine.h"
#import "CSMachine+Target.h"
#import "Clock_Signal-Swift.h"
#include "StaticAnalyser.hpp"
#import "CSMachine+Subclassing.h"
@implementation CSStaticAnalyser
{
StaticAnalyser::Target _target;
}
- (instancetype)initWithFileAtURL:(NSURL *)url
{
self = [super init];
if(self)
{
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]);
if(!targets.size()) return nil;
_target = targets.front();
// TODO: can this better be supplied by the analyser?
_displayName = [[url pathComponents] lastObject];
}
return self;
}
- (Class)documentClass
{
switch(_target.machine)
{
case StaticAnalyser::Target::Electron: return [ElectronDocument class];
case StaticAnalyser::Target::Vic20: return [Vic20Document class];
case StaticAnalyser::Target::Atari2600: return [Atari2600Document class];
}
return nil;
}
- (void)applyToMachine:(CSMachine *)machine
{
[machine applyTarget:_target];
}
@end

View File

@ -11,7 +11,6 @@
@interface CSAtari2600 : CSMachine
- (void)setROM:(nonnull NSData *)rom;
- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput;
- (void)setResetLineEnabled:(BOOL)enabled;

View File

@ -49,12 +49,6 @@ struct CRTDelegate: public Outputs::CRT::Delegate {
}
}
- (void)setROM:(NSData *)rom {
@synchronized(self) {
_atari2600.set_rom(rom.length, (const uint8_t *)rom.bytes);
}
}
- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput {
@synchronized(self) {
_atari2600.set_digital_input(digitalInput, state ? true : false);

View File

@ -10,12 +10,13 @@
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
@class CSStaticAnalyser;
@interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading>
- (void)setOSROM:(nonnull NSData *)rom;
- (void)setBASICROM:(nonnull NSData *)rom;
- (void)setROM:(nonnull NSData *)rom slot:(int)slot;
- (BOOL)openUEFAtURL:(nonnull NSURL *)URL;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useTelevisionOutput;

View File

@ -10,6 +10,7 @@
#include "Electron.hpp"
#import "CSMachine+Subclassing.h"
#include "StaticAnalyser.hpp"
#include "TapeUEF.hpp"
@implementation CSElectron {
@ -20,6 +21,10 @@
return &_electron;
}
- (void)analyse:(NSURL *)url {
StaticAnalyser::GetTargets([url fileSystemRepresentation]);
}
- (void)setOSROM:(nonnull NSData *)rom {
@synchronized(self) {
_electron.set_rom(Electron::ROMSlotOS, rom.length, (const uint8_t *)rom.bytes);
@ -38,17 +43,17 @@
}
}
- (BOOL)openUEFAtURL:(NSURL *)URL {
/*- (BOOL)openUEFAtURL:(NSURL *)URL {
@synchronized(self) {
try {
std::shared_ptr<Storage::UEF> tape(new Storage::UEF([URL fileSystemRepresentation]));
std::shared_ptr<Storage::Tape::UEF> tape(new Storage::Tape::UEF([URL fileSystemRepresentation]));
_electron.set_tape(tape);
return YES;
} catch(...) {
return NO;
}
}
}
}*/
- (void)clearAllKeys {
@synchronized(self) {

View File

@ -49,7 +49,7 @@ using namespace Commodore::Vic20;
- (BOOL)openTAPAtURL:(NSURL *)URL {
@synchronized(self) {
try {
std::shared_ptr<Storage::CommodoreTAP> tape(new Storage::CommodoreTAP([URL fileSystemRepresentation]));
std::shared_ptr<Storage::Tape::CommodoreTAP> tape(new Storage::Tape::CommodoreTAP([URL fileSystemRepresentation]));
_vic20.set_tape(tape);
return YES;
} catch(...) {
@ -59,21 +59,21 @@ using namespace Commodore::Vic20;
}
- (BOOL)openG64AtURL:(NSURL *)URL {
return [self openDisk:^std::shared_ptr<Storage::Disk>{
return std::shared_ptr<Storage::Disk>(new Storage::G64([URL fileSystemRepresentation]));
return [self openDisk:^std::shared_ptr<Storage::Disk::Disk>{
return std::shared_ptr<Storage::Disk::Disk>(new Storage::Disk::G64([URL fileSystemRepresentation]));
}];
}
- (BOOL)openD64AtURL:(NSURL *)URL {
return [self openDisk:^std::shared_ptr<Storage::Disk>{
return std::shared_ptr<Storage::Disk>(new Storage::D64([URL fileSystemRepresentation]));
return [self openDisk:^std::shared_ptr<Storage::Disk::Disk>{
return std::shared_ptr<Storage::Disk::Disk>(new Storage::Disk::D64([URL fileSystemRepresentation]));
}];
}
- (BOOL)openDisk:(std::shared_ptr<Storage::Disk> (^)())opener {
- (BOOL)openDisk:(std::shared_ptr<Storage::Disk::Disk> (^)())opener {
@synchronized(self) {
try {
std::shared_ptr<Storage::Disk> disk = opener();
std::shared_ptr<Storage::Disk::Disk> disk = opener();
_vic20.set_disk(disk);
return YES;
} catch(...) {

View File

@ -7,7 +7,7 @@
//
#import <Foundation/Foundation.h>
@import CoreVideo;
#import <CoreVideo/CoreVideo.h>
@class CSBestEffortUpdater;
@ -21,6 +21,7 @@
@interface CSBestEffortUpdater : NSObject
@property (nonatomic, assign) double clockRate;
@property (nonatomic, assign) BOOL runAsUnlimited;
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
- (void)update;

View File

@ -44,8 +44,10 @@
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
_cyclesError = fmod(cyclesToRunFor, 1.0);
NSUInteger integerCyclesToRunFor = (NSUInteger)cyclesToRunFor;
NSUInteger integerCyclesToRunFor = (NSUInteger)MIN(cyclesToRunFor, self.clockRate * 0.5);
// treat 'unlimited' as running at a factor of 10
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
}
_previousTimeInterval = timeInterval;

View File

@ -34,6 +34,9 @@ class Speaker {
float get_ideal_clock_rate_in_range(float minimum, float maximum)
{
// return twice the cut off, if applicable
if(_high_frequency_cut_off > 0.0f && _input_cycles_per_second >= _high_frequency_cut_off * 2.0f && _input_cycles_per_second <= _high_frequency_cut_off * 2.0f) return _high_frequency_cut_off * 2.0f;
// return exactly the input rate if possible
if(_input_cycles_per_second >= minimum && _input_cycles_per_second <= maximum) return _input_cycles_per_second;

View File

@ -0,0 +1,117 @@
//
// AcornAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Tape.hpp"
using namespace StaticAnalyser::Acorn;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges)
{
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges)
{
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 16 kb in size
Storage::Cartridge::Cartridge::Segment segment = segments.front();
if(segment.data.size() != 0x4000) continue;
// is a copyright string present?
uint8_t copyright_offset = segment.data[7];
if(
segment.data[copyright_offset] != 0x00 ||
segment.data[copyright_offset+1] != 0x28 ||
segment.data[copyright_offset+2] != 0x43 ||
segment.data[copyright_offset+3] != 0x29
) continue;
// is the language entry point valid?
if(!(
(segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) ||
(segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0)
)) continue;
// is the service entry point valid?
if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue;
// probability of a random binary blob that isn't an Acorn ROM proceeding to here:
// 1/(2^32) *
// ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) *
// 1/4
// = something very improbable — around 1/16th of 1 in 2^32, but not exactly.
acorn_cartridges.push_back(cartridge);
}
return acorn_cartridges;
}
void StaticAnalyser::Acorn::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
Target target;
target.machine = Target::Electron;
target.probability = 1.0; // TODO: a proper estimation
// strip out inappropriate cartridges
target.cartridges = AcornCartridgesFrom(cartridges);
// if there are any tapes, attempt to get data from the first
if(tapes.size() > 0)
{
std::shared_ptr<Storage::Tape::Tape> tape = tapes.front();
tape->reset();
std::list<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
if(files.size())
{
bool is_basic = true;
// protected files are always for *RUNning only
if(files.front().is_protected) is_basic = false;
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
size_t pointer = 0;
uint8_t *data = &files.front().data[0];
size_t data_size = files.front().data.size();
while(1)
{
if(pointer >= data_size-1 || data[pointer] != 13)
{
is_basic = false;
break;
}
if((data[pointer+1]&0x7f) == 0x7f) break;
pointer += data[pointer+3];
}
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target.loadingCommand = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target.tapes = tapes;
}
}
// TODO: disks
if(target.tapes.size() || target.cartridges.size())
destination.push_back(target);
}

View File

@ -0,0 +1,27 @@
//
// AcornAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Acorn_StaticAnalyser_hpp
#define StaticAnalyser_Acorn_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Acorn {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
}
}
#endif /* AcornAnalyser_hpp */

View File

@ -0,0 +1,269 @@
//
// Tape.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Tape.hpp"
#include <deque>
#include "../TapeParser.hpp"
using namespace StaticAnalyser::Acorn;
enum class WaveType {
Short, Long, Unrecognised
};
enum class SymbolType {
One, Zero
};
class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser<WaveType, SymbolType> {
public:
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) : TapeParser(tape) {}
int get_next_bit()
{
SymbolType symbol = get_next_symbol();
return (symbol == SymbolType::One) ? 1 : 0;
}
int get_next_byte()
{
int value = 0;
int c = 8;
if(get_next_bit())
{
set_error_flag();
return -1;
}
while(c--)
{
value = (value >> 1) | (get_next_bit() << 7);
}
if(!get_next_bit())
{
set_error_flag();
return -1;
}
add_to_crc((uint8_t)value);
return value;
}
int get_next_short()
{
int result = get_next_byte();
result |= get_next_byte() << 8;
return result;
}
int get_next_word()
{
int result = get_next_short();
result |= get_next_short() << 8;
return result;
}
void reset_crc() { _crc = 0; }
uint16_t get_crc() { return _crc; }
private:
void process_pulse(Storage::Tape::Tape::Pulse pulse)
{
switch(pulse.type)
{
default: break;
case Storage::Tape::Tape::Pulse::High:
case Storage::Tape::Tape::Pulse::Low:
float pulse_length = pulse.length.get_float();
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) { push_wave(WaveType::Short); return; }
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) { push_wave(WaveType::Long); return; }
break;
}
push_wave(WaveType::Unrecognised);
}
void inspect_waves(const std::vector<WaveType> &waves)
{
if(waves.size() < 2) return;
if(waves[0] == WaveType::Long && waves[1] == WaveType::Long)
{
push_symbol(SymbolType::Zero, 2);
return;
}
if(waves.size() < 4) return;
if( waves[0] == WaveType::Short &&
waves[1] == WaveType::Short &&
waves[2] == WaveType::Short &&
waves[3] == WaveType::Short)
{
push_symbol(SymbolType::One, 4);
return;
}
remove_waves(1);
}
void add_to_crc(uint8_t value)
{
_crc ^= (uint16_t)value << 8;
for(int c = 0; c < 8; c++)
{
uint16_t exclusive_or = (_crc&0x8000) ? 0x1021 : 0x0000;
_crc = (uint16_t)(_crc << 1) ^ exclusive_or;
}
}
uint16_t _crc;
};
static std::unique_ptr<File::Chunk> GetNextChunk(Acorn1200BaudTapeParser &parser)
{
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
int shift_register = 0;
// TODO: move this into the parser
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit() << 9)
// find next area of high tone
while(!parser.is_at_end() && (shift_register != 0x3ff))
{
shift();
}
// find next 0x2a (swallowing stop bit)
while(!parser.is_at_end() && (shift_register != 0x254))
{
shift();
}
#undef shift
parser.reset_crc();
parser.reset_error_flag();
// read out name
char name[11];
int name_ptr = 0;
while(!parser.is_at_end() && name_ptr < sizeof(name))
{
name[name_ptr] = (char)parser.get_next_byte();
if(!name[name_ptr]) break;
name_ptr++;
}
name[sizeof(name)-1] = '\0';
new_chunk->name = name;
// addresses
new_chunk->load_address = (uint32_t)parser.get_next_word();
new_chunk->execution_address = (uint32_t)parser.get_next_word();
new_chunk->block_number = (uint16_t)parser.get_next_short();
new_chunk->block_length = (uint16_t)parser.get_next_short();
new_chunk->block_flag = (uint8_t)parser.get_next_byte();
new_chunk->next_address = (uint32_t)parser.get_next_word();
uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = (uint16_t)parser.get_next_short();
stored_header_crc = (uint16_t)((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++)
{
new_chunk->data.push_back((uint8_t)parser.get_next_byte());
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40))
{
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = (uint16_t)parser.get_next_short();
stored_data_crc = (uint16_t)((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
}
else
{
new_chunk->data_crc_matched = true;
}
return parser.get_error_flag() ? nullptr : std::move(new_chunk);
}
std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
{
// find next chunk with a block number of 0
while(chunks.size() && chunks.front().block_number)
{
chunks.pop_front();
}
if(!chunks.size()) return nullptr;
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
std::unique_ptr<File> file(new File);
uint16_t block_number = 0;
while(chunks.size())
{
if(chunks.front().block_number != block_number) return nullptr;
bool was_last = chunks.front().block_flag & 0x80;
file->chunks.push_back(chunks.front());
chunks.pop_front();
block_number++;
if(was_last) break;
}
// accumulate total data, copy flags appropriately
file->name = file->chunks.front().name;
file->load_address = file->chunks.front().load_address;
file->execution_address = file->chunks.front().execution_address;
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// copy all data into a single big block
for(File::Chunk chunk : file->chunks)
{
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
}
return file;
}
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
Acorn1200BaudTapeParser parser(tape);
// populate chunk list
std::deque<File::Chunk> chunk_list;
while(!parser.is_at_end())
{
std::unique_ptr<File::Chunk> chunk = GetNextChunk(parser);
if(chunk)
{
chunk_list.push_back(*chunk);
}
}
// decompose into file list
std::list<File> file_list;
while(chunk_list.size())
{
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
if(next_file)
{
file_list.push_back(*next_file);
}
}
return file_list;
}

View File

@ -0,0 +1,51 @@
//
// Tape.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Acorn_Tape_hpp
#define StaticAnalyser_Acorn_Tape_hpp
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "../../Storage/Tape/Tape.hpp"
namespace StaticAnalyser {
namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
std::vector<uint8_t> data;
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
bool header_crc_matched;
bool data_crc_matched;
std::vector<uint8_t> data;
};
std::list<Chunk> chunks;
};
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
#endif /* Tape_hpp */

View File

@ -0,0 +1,28 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 15/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
using namespace StaticAnalyser::Atari;
void StaticAnalyser::Atari::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
// TODO: any sort of sanity checking at all; at the minute just trust the file type
// approximation already performed.
Target target;
target.machine = Target::Atari2600;
target.probability = 1.0;
target.disks = disks;
target.tapes = tapes;
target.cartridges = cartridges;
destination.push_back(target);
}

View File

@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Atari_StaticAnalyser_hpp
#define StaticAnalyser_Atari_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Atari {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
}
}
#endif /* StaticAnalyser_hpp */

View File

@ -0,0 +1,253 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 13/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Disk.hpp"
#include "../../Storage/Disk/DiskDrive.hpp"
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "Utilities.hpp"
#include <limits>
#include <vector>
#include <array>
using namespace StaticAnalyser::Commodore;
class CommodoreGCRParser: public Storage::Disk::Drive {
public:
CommodoreGCRParser() : Storage::Disk::Drive(4000000, 1, 300), shift_register_(0), track_(1)
{
// Make sure this drive really is at track '1'.
while(!get_is_track_zero()) step(-1);
}
struct Sector
{
uint8_t sector, track;
std::array<uint8_t, 256> data;
bool header_checksum_matched;
bool data_checksum_matched;
};
/*!
Attempts to read the sector located at @c track and @c sector.
@returns a sector if one was found; @c nullptr otherwise.
*/
std::unique_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
{
int difference = (int)track - (int)track_;
track_ = track;
if(difference)
{
int direction = difference < 0 ? -1 : 1;
difference *= 2 * direction;
for(int c = 0; c < difference; c++) step(direction);
unsigned int zone = 3;
if(track >= 18) zone = 2;
else if(track >= 25) zone = 1;
else if(track >= 31) zone = 0;
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(zone));
}
return get_sector(sector);
}
private:
unsigned int shift_register_;
int index_count_;
int bit_count_;
uint8_t track_;
void process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
bit_count_++;
}
unsigned int proceed_to_next_block()
{
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2)
{
run_for_cycles(1);
}
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2)
{
run_for_cycles(1);
}
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
unsigned int get_next_byte()
{
bit_count_ = 0;
while(bit_count_ < 10) run_for_cycles(1);
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
void proceed_to_shift_value(unsigned int shift_value)
{
index_count_ = 0;
while(shift_register_ != shift_value && index_count_ < 2)
{
run_for_cycles(1);
}
}
void process_index_hole()
{
index_count_++;
}
std::unique_ptr<Sector> get_sector(uint8_t sector)
{
std::unique_ptr<Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
std::unique_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
std::unique_ptr<Sector> get_next_sector()
{
std::unique_ptr<Sector> sector(new Sector);
index_count_ = 0;
while(index_count_ < 2)
{
// look for a sector header
while(1)
{
if(proceed_to_next_block() == 0x08) break;
if(index_count_ >= 2) return nullptr;
}
// get sector details, skip if this looks malformed
uint8_t checksum = (uint8_t)get_next_byte();
sector->sector = (uint8_t)get_next_byte();
sector->track = (uint8_t)get_next_byte();
uint8_t disk_id[2];
disk_id[0] = (uint8_t)get_next_byte();
disk_id[1] = (uint8_t)get_next_byte();
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
while(1)
{
if(proceed_to_next_block() == 0x07) break;
if(index_count_ >= 2) return nullptr;
}
checksum = 0;
for(size_t c = 0; c < 256; c++)
{
sector->data[c] = (uint8_t)get_next_byte();
checksum ^= sector->data[c];
}
if(checksum == get_next_byte()) return sector;
}
return nullptr;
}
};
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::list<File> files;
CommodoreGCRParser parser;
parser.set_disk(disk);
// find any sector whatsoever to establish the current track
std::unique_ptr<CommodoreGCRParser::Sector> sector;
// assemble directory
std::vector<uint8_t> directory;
uint8_t next_track = 18;
uint8_t next_sector = 1;
while(1)
{
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
next_track = sector->data[0];
next_sector = sector->data[1];
if(!next_track) break;
}
// parse directory
size_t header_pointer = (size_t)-32;
while(header_pointer+32+31 < directory.size())
{
header_pointer += 32;
File new_file;
switch(directory[header_pointer + 2] & 7)
{
case 0: // DEL files
default: continue; // Unknown file types
case 1: new_file.type = File::DataSequence; break;
case 2: new_file.type = File::RelocatableProgram; break; // TODO: need a "don't know about relocatable" program?
case 3: new_file.type = File::User; break;
// case 4: new_file.type = File::Relative; break; // Can't handle REL files yet
}
next_track = directory[header_pointer + 3];
next_sector = directory[header_pointer + 4];
new_file.raw_name.reserve(16);
for(size_t c = 0; c < 16; c++)
{
new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
}
new_file.name = petscii_from_bytes(&new_file.raw_name[0], 16, false);
size_t number_of_sectors = (size_t)directory[header_pointer + 0x1e] + ((size_t)directory[header_pointer + 0x1f] << 8);
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
while(next_track)
{
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];
next_sector = sector->data[1];
if(is_first_sector) new_file.starting_address = (uint16_t)sector->data[2] | (uint16_t)(sector->data[3] << 8);
if(next_track)
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
else
new_file.data.insert(new_file.data.end(), sector->data.begin() + 2, sector->data.begin() + next_sector);
is_first_sector = false;
}
if(!next_track) files.push_back(new_file);
}
return files;
}

View File

@ -0,0 +1,25 @@
//
// Disk.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Commodore_Disk_hpp
#define StaticAnalyser_Commodore_Disk_hpp
#include "../../Storage/Disk/Disk.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
#endif /* Disk_hpp */

View File

@ -0,0 +1,50 @@
//
// File.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "File.hpp"
bool StaticAnalyser::Commodore::File::is_basic()
{
// BASIC files are always relocatable (?)
if(type != File::RelocatableProgram) return false;
uint16_t line_address = starting_address;
int line_number = -1;
// decide whether this is a BASIC file based on the proposition that:
// (1) they're always relocatable; and
// (2) they have a per-line structure of:
// [4 bytes: address of start of next line]
// [4 bytes: this line number]
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)ß
while(1)
{
if(line_address - starting_address >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
if(!next_line_address)
{
return true;
}
if(next_line_address < line_address + 5) break;
if(line_address - starting_address >= data.size() + 5) break;
uint16_t next_line_number = data[line_address - starting_address + 2];
next_line_number |= data[line_address - starting_address + 3] << 8;
if(next_line_number <= line_number) break;
line_number = (uint16_t)next_line_number;
line_address = next_line_address;
}
return false;
}

View File

@ -0,0 +1,42 @@
//
// File.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef File_hpp
#define File_hpp
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Commodore {
struct File {
File() : is_closed(false), is_locked(false) {}
std::wstring name;
std::vector<uint8_t> raw_name;
uint16_t starting_address;
uint16_t ending_address;
bool is_locked;
bool is_closed;
enum {
RelocatableProgram,
NonRelocatableProgram,
DataSequence,
User,
Relative
} type;
std::vector<uint8_t> data;
bool is_basic();
};
}
}
#endif /* File_hpp */

View File

@ -0,0 +1,132 @@
//
// CommodoreAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "File.hpp"
#include "Tape.hpp"
#include "Disk.hpp"
using namespace StaticAnalyser::Commodore;
void StaticAnalyser::Commodore::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
Target target;
target.machine = Target::Vic20; // TODO: machine estimation
target.probability = 1.0; // TODO: a proper estimation
int device = 0;
std::list<File> files;
bool is_disk = false;
// strip out inappropriate cartridges
// target.cartridges = AcornCartridgesFrom(cartridges);
// check disks
for(auto &disk : disks)
{
std::list<File> disk_files = GetFiles(disk);
if(disk_files.size())
{
is_disk = true;
files.splice(files.end(), disk_files);
target.disks = disks;
if(!device) device = 8;
}
}
// check tapes
for(auto &tape : tapes)
{
std::list<File> tape_files = GetFiles(tape);
if(tape_files.size())
{
files.splice(files.end(), tape_files);
target.tapes = tapes;
if(!device) device = 1;
}
}
if(files.size())
{
target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
if(files.front().is_basic())
{
char command[16];
snprintf(command, 16, "LOAD\"%s\",%d,0\nRUN\n", is_disk ? "*" : "", device);
target.loadingCommand = command;
}
else
{
char command[16];
snprintf(command, 16, "LOAD\"%s\",%d,1\nRUN\n", is_disk ? "*" : "", device);
target.loadingCommand = command;
}
// make a first guess based on loading address
switch(files.front().starting_address)
{
case 0x1001:
default: break;
case 0x1201:
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target.vic20.memory_model = Vic20MemoryModel::EightKB;
break;
}
// General approach: increase memory size conservatively such that the largest file found will fit.
for(File &file : files)
{
size_t file_size = file.data.size();
// bool is_basic = file.is_basic();
/*if(is_basic)
{
// BASIC files may be relocated, so the only limit is size.
//
// An unexpanded machine has 3583 bytes free for BASIC;
// a 3kb expanded machine has 6655 bytes free.
if(file_size > 6655)
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
target.vic20.memory_model = Vic20MemoryModel::EightKB;
}
else
{*/
// if(!file.type == File::NonRelocatableProgram)
// {
// Non-BASIC files may be relocatable but, if so, by what logic?
// Given that this is unknown, take starting address as literal
// and check against memory windows.
//
// (ignoring colour memory...)
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
// A 32kb expanded Vic has memory in the entire low 32kb.
uint16_t starting_address = file.starting_address;
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
if(starting_address + file_size > 0x2000)
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
// }
}
}
if(target.tapes.size() || target.cartridges.size() || target.disks.size())
destination.push_back(target);
}

View File

@ -0,0 +1,27 @@
//
// CommodoreAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Commodore_StaticAnalyser_hpp
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Commodore {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
}
}
#endif /* CommodoreAnalyser_hpp */

View File

@ -0,0 +1,441 @@
//
// Tape.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Tape.hpp"
#include "../TapeParser.hpp"
#include "Utilities.hpp"
using namespace StaticAnalyser::Commodore;
enum class WaveType {
Short, Medium, Long, Unrecognised
};
enum class SymbolType {
One, Zero, Word, EndOfBlock, LeadIn
};
struct Header {
enum {
RelocatableProgram,
NonRelocatableProgram,
DataSequenceHeader,
DataBlock,
EndOfTape,
Unknown
} type;
std::vector<uint8_t> data;
std::wstring name;
std::vector<uint8_t> raw_name;
uint16_t starting_address;
uint16_t ending_address;
bool parity_was_valid;
bool duplicate_matched;
};
struct Data {
std::vector<uint8_t> data;
bool parity_was_valid;
bool duplicate_matched;
};
class CommodoreROMTapeParser: public StaticAnalyer::TapeParser<WaveType, SymbolType> {
public:
CommodoreROMTapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) :
TapeParser(tape),
_wave_period(0.0f),
_previous_was_high(false),
_parity_byte(0) {}
/*!
Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Header> get_next_header()
{
return duplicate_match<Header>(
get_next_header_body(true),
get_next_header_body(false)
);
}
/*!
Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it.
Returns @c nullptr if any wave-encoding level errors are encountered.
*/
std::unique_ptr<Data> get_next_data()
{
return duplicate_match<Data>(
get_next_data_body(true),
get_next_data_body(false)
);
}
void spin()
{
while(!is_at_end())
{
SymbolType symbol = get_next_symbol();
switch(symbol)
{
case SymbolType::One: printf("1"); break;
case SymbolType::Zero: printf("0"); break;
case SymbolType::Word: printf(" "); break;
case SymbolType::EndOfBlock: printf("\n"); break;
case SymbolType::LeadIn: printf("-"); break;
}
}
}
private:
/*!
Template for the logic in selecting which of two copies of something to consider authoritative,
including setting the duplicate_matched flag.
*/
template<class ObjectType>
std::unique_ptr<ObjectType> duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> second_copy)
{
// if only one copy was parsed successfully, return it
if(!first_copy) return second_copy;
if(!second_copy) return first_copy;
// if no copies were second_copy, return nullptr
if(!first_copy && !second_copy) return nullptr;
// otherwise plan to return either one with a correct check digit, doing a comparison with the other
std::unique_ptr<ObjectType> *copy_to_return = &first_copy;
if(!first_copy->parity_was_valid && second_copy->parity_was_valid) copy_to_return = &second_copy;
(*copy_to_return)->duplicate_matched = true;
if(first_copy->data.size() != second_copy->data.size())
(*copy_to_return)->duplicate_matched = false;
else
(*copy_to_return)->duplicate_matched = !(memcmp(&first_copy->data[0], &second_copy->data[0], first_copy->data.size()));
return std::move(*copy_to_return);
}
std::unique_ptr<Header> get_next_header_body(bool is_original)
{
std::unique_ptr<Header> header(new Header);
reset_error_flag();
// find and proceed beyond lead-in tone
proceed_to_symbol(SymbolType::LeadIn);
// look for landing zone
proceed_to_landing_zone(is_original);
reset_parity_byte();
// get header type
uint8_t header_type = get_next_byte();
switch(header_type)
{
default: header->type = Header::Unknown; break;
case 0x01: header->type = Header::RelocatableProgram; break;
case 0x02: header->type = Header::DataBlock; break;
case 0x03: header->type = Header::NonRelocatableProgram; break;
case 0x04: header->type = Header::DataSequenceHeader; break;
case 0x05: header->type = Header::EndOfTape; break;
}
// grab rest of data
header->data.reserve(191);
for(size_t c = 0; c < 191; c++)
{
header->data.push_back(get_next_byte());
}
uint8_t parity_byte = get_parity_byte();
header->parity_was_valid = get_next_byte() == parity_byte;
// parse if this is not pure data
if(header->type != Header::DataBlock)
{
header->starting_address = (uint16_t)(header->data[0] | (header->data[1] << 8));
header->ending_address = (uint16_t)(header->data[2] | (header->data[3] << 8));
for(size_t c = 0; c < 16; c++)
{
header->raw_name.push_back(header->data[4 + c]);
}
header->name = petscii_from_bytes(&header->raw_name[0], 16, false);
}
if(get_error_flag()) return nullptr;
return header;
}
std::unique_ptr<Data> get_next_data_body(bool is_original)
{
std::unique_ptr<Data> data(new Data);
reset_error_flag();
// find and proceed beyond lead-in tone to the next landing zone
proceed_to_symbol(SymbolType::LeadIn);
proceed_to_landing_zone(is_original);
reset_parity_byte();
// accumulate until the next non-word marker is hit
while(!is_at_end())
{
SymbolType start_symbol = get_next_symbol();
if(start_symbol != SymbolType::Word) break;
data->data.push_back(get_next_byte_contents());
}
// the above has reead the parity byte to the end of the data; if it matched the calculated parity it'll now be zero
data->parity_was_valid = !get_parity_byte();
data->duplicate_matched = false;
// remove the captured parity
data->data.erase(data->data.end()-1);
if(get_error_flag()) return nullptr;
return data;
}
/*!
Finds and completes the next landing zone.
*/
void proceed_to_landing_zone(bool is_original)
{
uint8_t landing_zone[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
while(!is_at_end())
{
memmove(landing_zone, &landing_zone[1], sizeof(uint8_t) * 8);
landing_zone[8] = get_next_byte();
bool is_landing_zone = true;
for(int c = 0; c < 9; c++)
{
if(landing_zone[c] != ((is_original ? 0x80 : 0x00) | 0x9) - c)
{
is_landing_zone = false;
break;
}
}
if(is_landing_zone) break;
}
}
/*!
Swallows symbols until it reaches the first instance of the required symbol, swallows that
and returns.
*/
void proceed_to_symbol(SymbolType required_symbol)
{
while(!is_at_end())
{
SymbolType symbol = get_next_symbol();
if(symbol == required_symbol) return;
}
}
/*!
Swallows the next byte; sets the error flag if it is not equal to @c value.
*/
void expect_byte(uint8_t value)
{
uint8_t next_byte = get_next_byte();
if(next_byte != value) set_error_flag();
}
uint8_t _parity_byte;
void reset_parity_byte() { _parity_byte = 0; }
uint8_t get_parity_byte() { return _parity_byte; }
void add_parity_byte(uint8_t byte) { _parity_byte ^= byte; }
/*!
Proceeds to the next word marker then returns the result of @c get_next_byte_contents.
*/
uint8_t get_next_byte()
{
proceed_to_symbol(SymbolType::Word);
return get_next_byte_contents();
}
/*!
Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One.
Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not
::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight.
*/
uint8_t get_next_byte_contents()
{
int byte_plus_parity = 0;
int c = 9;
while(c--)
{
SymbolType next_symbol = get_next_symbol();
if((next_symbol != SymbolType::One) && (next_symbol != SymbolType::Zero)) set_error_flag();
byte_plus_parity = (byte_plus_parity >> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8);
}
int check = byte_plus_parity;
check ^= (check >> 4);
check ^= (check >> 2);
check ^= (check >> 1);
if((check&1) == (byte_plus_parity >> 8))
set_error_flag();
add_parity_byte((uint8_t)byte_plus_parity);
return (uint8_t)byte_plus_parity;
}
/*!
Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format.
*/
uint16_t get_next_short()
{
uint16_t value = get_next_byte();
value |= get_next_byte() << 8;
return value;
}
/*!
Per the contract with StaticAnalyser::TapeParser; sums time across pulses. If this pulse
indicates a high to low transition, inspects the time since the last transition, to produce
a long, medium, short or unrecognised wave period.
*/
void process_pulse(Storage::Tape::Tape::Pulse pulse)
{
// The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of:
// short: 182µs => 0.000364s cycle
// medium: 262µs => 0.000524s cycle
// long: 342µs => 0.000684s cycle
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
if(!is_high && _previous_was_high)
{
if(_wave_period >= 0.000764) push_wave(WaveType::Unrecognised);
else if(_wave_period >= 0.000604) push_wave(WaveType::Long);
else if(_wave_period >= 0.000444) push_wave(WaveType::Medium);
else if(_wave_period >= 0.000284) push_wave(WaveType::Short);
else push_wave(WaveType::Unrecognised);
_wave_period = 0.0f;
}
_wave_period += pulse.length.get_float();
_previous_was_high = is_high;
}
bool _previous_was_high;
float _wave_period;
/*!
Per the contract with StaticAnalyser::TapeParser; produces any of a word marker, an end-of-block marker,
a zero, a one or a lead-in symbol based on the currently captured waves.
*/
void inspect_waves(const std::vector<WaveType> &waves)
{
if(waves.size() < 2) return;
if(waves[0] == WaveType::Long && waves[1] == WaveType::Medium)
{
push_symbol(SymbolType::Word, 2);
return;
}
if(waves[0] == WaveType::Long && waves[1] == WaveType::Short)
{
push_symbol(SymbolType::EndOfBlock, 2);
return;
}
if(waves[0] == WaveType::Short && waves[1] == WaveType::Medium)
{
push_symbol(SymbolType::Zero, 2);
return;
}
if(waves[0] == WaveType::Medium && waves[1] == WaveType::Short)
{
push_symbol(SymbolType::One, 2);
return;
}
if(waves[0] == WaveType::Short)
{
push_symbol(SymbolType::LeadIn, 1);
return;
}
// Otherwise, eject at least one wave as all options are exhausted.
remove_waves(1);
}
};
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
CommodoreROMTapeParser parser(tape);
std::list<File> file_list;
std::unique_ptr<Header> header = parser.get_next_header();
while(!parser.is_at_end())
{
if(!header)
{
header = parser.get_next_header();
continue;
}
switch(header->type)
{
case Header::DataSequenceHeader:
{
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
new_file.starting_address = header->starting_address;
new_file.ending_address = header->ending_address;
new_file.type = File::DataSequence;
new_file.data.swap(header->data);
while(!parser.is_at_end())
{
header = parser.get_next_header();
if(!header) continue;
if(header->type != Header::DataBlock) break;
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
}
file_list.push_back(new_file);
}
break;
case Header::RelocatableProgram:
case Header::NonRelocatableProgram:
{
std::unique_ptr<Data> data = parser.get_next_data();
if(data)
{
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
new_file.starting_address = header->starting_address;
new_file.ending_address = header->ending_address;
new_file.data.swap(data->data);
new_file.type = (header->type == Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram;
file_list.push_back(new_file);
}
header = parser.get_next_header();
}
break;
default:
header = parser.get_next_header();
break;
}
}
return file_list;
}

View File

@ -0,0 +1,24 @@
//
// Tape.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Commodore_Tape_hpp
#define StaticAnalyser_Commodore_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
#endif /* Tape_hpp */

View File

@ -0,0 +1,63 @@
//
// Utilities.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Utilities.hpp"
std::wstring StaticAnalyser::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted)
{
std::wstring result;
wchar_t unshifted_characters[256] =
{
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/',
L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'"', L';', L'<', L'=', L'>', L'?',
L'@', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O',
L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'[', L'£', L']', L'', L'',
L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'<EFBFBD>', L'', L'', L'<EFBFBD>',
L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'<EFBFBD>', L'', L'', L'<EFBFBD>', L'', L'π', L'',
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'', L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'<EFBFBD>', L'', L'', L'<EFBFBD>',
L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'<EFBFBD>', L'', L'', L'<EFBFBD>', L'', L'π', L'',
L' ', L'', L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'<EFBFBD>', L'', L'', L'', L'', L'π',
};
wchar_t shifted_characters[256] =
{
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/',
L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'"', L';', L'<', L'=', L'>', L'?',
L'@', L'a', L'b', L'c', L'd', L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l', L'm', L'n', L'o',
L'p', L'q', L'r', L's', L't', L'u', L'v', L'w', L'x', L'y', L'z', L'[', L'£', L']', L'', L'',
L'', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O',
L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'', L'<EFBFBD>', L'', L'', L'',
L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0',
L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0',
L' ', L'', L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'', L'', L'', L'',
L'', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O',
L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'', L'<EFBFBD>', L'', L'', L'<EFBFBD>',
L' ', L'', L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'', L'',
L'', L'', L'', L'', L'', L'', L'<EFBFBD>', L'<EFBFBD>', L'<EFBFBD>', L'', L'', L'', L'', L'', L'', L'',
};
wchar_t *table = shifted ? shifted_characters : unshifted_characters;
for(int c = 0; c < length; c++)
{
wchar_t next_character = table[string[c]];
if(next_character) result.push_back(next_character);
}
return result;
}

View File

@ -0,0 +1,22 @@
//
// Utilities.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Commodore_Utilities_hpp
#define Analyser_Commodore_Utilities_hpp
#include <string>
namespace StaticAnalyser {
namespace Commodore {
std::wstring petscii_from_bytes(const uint8_t *string, int length, bool shifted);
}
}
#endif /* Utilities_hpp */

View File

@ -0,0 +1,117 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include <cstdlib>
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
// Cartridges
#include "../Storage/Cartridge/Formats/BinaryDump.hpp"
#include "../Storage/Cartridge/Formats/PRG.hpp"
// Disks
#include "../Storage/Disk/Formats/D64.hpp"
#include "../Storage/Disk/Formats/G64.hpp"
// Tapes
#include "../Storage/Tape/Formats/CommodoreTAP.hpp"
#include "../Storage/Tape/Formats/TapePRG.hpp"
#include "../Storage/Tape/Formats/TapeUEF.hpp"
typedef int TargetPlatformType;
enum class TargetPlatform: TargetPlatformType {
Acorn = 1 << 0,
Atari2600 = 1 << 1,
Commodore = 1 << 2
};
using namespace StaticAnalyser;
std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
{
std::list<Target> targets;
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
const char *mixed_case_extension = strrchr(file_name, '.');
char *lowercase_extension = nullptr;
if(mixed_case_extension)
{
lowercase_extension = strdup(mixed_case_extension+1);
char *parser = lowercase_extension;
while(*parser)
{
*parser = (char)tolower(*parser);
parser++;
}
}
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
std::list<std::shared_ptr<Storage::Disk::Disk>> disks;
std::list<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
TargetPlatformType potential_platforms = 0;
#define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\
potential_platforms |= (TargetPlatformType)(platforms);\
#define TryInsert(list, class, platforms) \
try {\
Insert(list, class, platforms) \
} catch(...) {}
#define Format(extension, list, class, platforms) \
if(!strcmp(lowercase_extension, extension)) \
{ \
TryInsert(list, class, platforms) \
}
Format("a26", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("bin", cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // BIN
Format("d64", disks, Disk::D64, TargetPlatform::Commodore) // D64
Format("g64", disks, Disk::G64, TargetPlatform::Commodore) // G64
// PRG
if(!strcmp(lowercase_extension, "prg"))
{
// try instantiating as a ROM; failing that accept as a tape
try {
Insert(cartridges, Cartridge::PRG, TargetPlatform::Commodore)
}
catch(...)
{
try {
Insert(tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
}
}
// ROM
Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM
Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP
Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
#undef Format
#undef Insert
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them. (TODO)
if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets);
if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets);
free(lowercase_extension);
return targets;
}

View File

@ -0,0 +1,68 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_hpp
#define StaticAnalyser_hpp
#include "../Storage/Tape/Tape.hpp"
#include "../Storage/Disk/Disk.hpp"
#include "../Storage/Cartridge/Cartridge.hpp"
#include <string>
#include <list>
#include <vector>
namespace StaticAnalyser {
enum class Vic20MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
/*!
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
enum {
Atari2600,
Electron,
Vic20
} machine;
float probability;
union {
struct {
Vic20MemoryModel memory_model;
bool has_c1540;
} vic20;
struct {
bool has_adfs;
bool has_dfs;
} acorn;
};
std::string loadingCommand;
std::list<std::shared_ptr<Storage::Disk::Disk>> disks;
std::list<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
};
/*!
Attempts, through any available means, to return a list of potential targets for the file with the given name.
@returns The list of potential targets, sorted from most to least probable.
*/
std::list<Target> GetTargets(const char *file_name);
}
#endif /* StaticAnalyser_hpp */

View File

@ -0,0 +1,115 @@
//
// TapeParser.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef TapeParser_hpp
#define TapeParser_hpp
namespace StaticAnalyer {
/*!
A partly-abstract base class to help in the authorship of tape format parsers;
provides hooks for pulse classification from pulses to waves and for symbol identification from
waves.
Very optional, not intended to box in the approaches taken for analysis.
*/
template <typename WaveType, typename SymbolType> class TapeParser {
public:
/// Instantiates a new parser with the supplied @c tape.
TapeParser(const std::shared_ptr<Storage::Tape::Tape> &tape) : _tape(tape), _has_next_symbol(false), _error_flag(false) {}
/// Resets the error flag.
void reset_error_flag() { _error_flag = false; }
/// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise.
bool get_error_flag() { return _error_flag; }
/// @returns @c true if the encapsulated tape has reached its end; @c false otherwise.
bool is_at_end() { return _tape->is_at_end(); }
protected:
/*!
Adds @c wave to the back of the list of recognised waves and calls @c inspect_waves to check for a new symbol.
Expected to be called by subclasses from @c process_pulse as and when recognised waves arise.
*/
void push_wave(WaveType wave)
{
_wave_queue.push_back(wave);
inspect_waves(_wave_queue);
}
/*!
Removes @c nunber_of_waves waves from the front of the list.
Expected to be called by subclasses from @c process_pulse if it is recognised that the first set of waves
do not form a valid symbol.
*/
void remove_waves(int number_of_waves)
{
_wave_queue.erase(_wave_queue.begin(), _wave_queue.begin()+number_of_waves);
}
/*!
Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list.
Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves
waves together represent @c symbol.
*/
void push_symbol(SymbolType symbol, int number_of_waves)
{
_has_next_symbol = true;
_next_symbol = symbol;
remove_waves(number_of_waves);
}
/*!
Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol
or the tape runs out, returning the most-recently declared symbol.
*/
SymbolType get_next_symbol()
{
while(!_has_next_symbol && !is_at_end())
{
process_pulse(_tape->get_next_pulse());
}
_has_next_symbol = false;
return _next_symbol;
}
void set_error_flag()
{
_error_flag = true;
}
private:
bool _error_flag;
/*!
Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave
or to take no action.
*/
virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0;
/*!
Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is
found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries
removed from the start of @c waves that cannot form a valid symbol. Need not do anything while
the waves at the start of @c waves may end up forming a symbol but the symbol is not yet complete.
*/
virtual void inspect_waves(const std::vector<WaveType> &waves) = 0;
std::vector<WaveType> _wave_queue;
SymbolType _next_symbol;
bool _has_next_symbol;
std::shared_ptr<Storage::Tape::Tape> _tape;
};
}
#endif /* TapeParser_hpp */

View File

@ -0,0 +1,11 @@
//
// ROM.cpp
// Clock Signal
//
// Created by Thomas Harte on 27/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Cartridge.hpp"
const int Storage::Cartridge::Cartridge::Segment::UnknownAddress = -1;

View File

@ -0,0 +1,63 @@
//
// Cartridge.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Cartridge_hpp
#define Storage_Cartridge_hpp
#include <list>
#include <vector>
#include <memory>
namespace Storage {
namespace Cartridge {
/*!
Provides a base class for cartridges; the bus provided to cartridges and therefore
the interface they support is extremely machine-dependent so unlike disks and tapes,
no model is imposed; this class seeks merely to be a base class for fully-descriptive
summaries of the contents of emulator files that themselves describe cartridges.
Consumers will almost certainly seek to dynamic_cast to something more appropriate,
however some cartridge container formats have no exposition beyond the ROM dump,
making the base class 100% descriptive.
*/
class Cartridge {
public:
struct Segment {
Segment(int start_address, int end_address, std::vector<uint8_t> data) :
start_address(start_address), end_address(end_address), data(std::move(data)) {}
/// Indicates that an address is unknown.
static const int UnknownAddress;
/// The initial CPU-exposed starting address for this segment; may be @c UnknownAddress.
int start_address;
/*!
The initial CPU-exposed ending address for this segment; may be @c UnknownAddress. Not necessarily equal
to start_address + data_length due to potential paging.
*/
int end_address;
/*!
The data contents for this segment. If @c start_address and @c end_address are suppled then
the first end_address - start_address bytes will be those initially visible. The size will
not necessarily be the same as @c end_address - @c start_address due to potential paging.
*/
std::vector<uint8_t> data;
};
const std::list<Segment> &get_segments() { return _segments; }
protected:
std::list<Segment> _segments;
};
}
}
#endif /* ROM_hpp */

View File

@ -0,0 +1,35 @@
//
// BinaryDump.cpp
// Clock Signal
//
// Created by Thomas Harte on 28/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "BinaryDump.hpp"
#include <cstdio>
#include <sys/stat.h>
using namespace Storage::Cartridge;
BinaryDump::BinaryDump(const char *file_name)
{
// the file should be exactly 16 kb
struct stat file_stats;
stat(file_name, &file_stats);
// grab contents
FILE *file = fopen(file_name, "rb");
if(!file) throw ErrorNotAccessible;
size_t data_length = (size_t)file_stats.st_size;
std::vector<uint8_t> contents(data_length);
fread(&contents[0], 1, (size_t)(data_length), file);
fclose(file);
// enshrine
_segments.emplace_back(
::Storage::Cartridge::Cartridge::Segment::UnknownAddress,
::Storage::Cartridge::Cartridge::Segment::UnknownAddress,
std::move(contents));
}

View File

@ -0,0 +1,29 @@
//
// BinaryDump.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Cartridge_BinaryDump_hpp
#define Storage_Cartridge_BinaryDump_hpp
#include "../Cartridge.hpp"
namespace Storage {
namespace Cartridge {
class BinaryDump : public Cartridge {
public:
BinaryDump(const char *file_name);
enum {
ErrorNotAccessible
};
};
}
}
#endif /* AcornROM_hpp */

View File

@ -0,0 +1,54 @@
//
// PRG.cpp
// Clock Signal
//
// Created by Thomas Harte on 27/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "PRG.hpp"
#include <cstdio>
#include <sys/stat.h>
using namespace Storage::Cartridge;
PRG::PRG(const char *file_name)
{
struct stat file_stats;
stat(file_name, &file_stats);
// accept only files sized 1, 2, 4 or 8kb
if(
file_stats.st_size != 0x400 + 2 &&
file_stats.st_size != 0x800 + 2 &&
file_stats.st_size != 0x1000 + 2 &&
file_stats.st_size != 0x2000 + 2)
throw ErrorNotROM;
// get the loading address, and the rest of the contents
FILE *file = fopen(file_name, "rb");
int loading_address = fgetc(file);
loading_address |= fgetc(file) << 8;
size_t data_length = (size_t)file_stats.st_size - 2;
std::vector<uint8_t> contents(data_length);
fread(&contents[0], 1, (size_t)(data_length), file);
fclose(file);
// accept only files intended to load at 0xa000
if(loading_address != 0xa000)
throw ErrorNotROM;
// also accept only cartridges with the proper signature
if(
contents[4] != 0x41 ||
contents[5] != 0x30 ||
contents[6] != 0xc3 ||
contents[7] != 0xc2 ||
contents[8] != 0xcd)
throw ErrorNotROM;
_segments.emplace_back(0xa000, 0xa000 + data_length, std::move(contents));
}

View File

@ -0,0 +1,29 @@
//
// PRG.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Storage_Cartridge_PRG_hpp
#define Storage_Cartridge_PRG_hpp
#include "../Cartridge.hpp"
namespace Storage {
namespace Cartridge {
class PRG : public Cartridge {
public:
PRG(const char *file_name);
enum {
ErrorNotROM
};
};
}
}
#endif /* PRG_hpp */

View File

@ -13,6 +13,7 @@
#include "../Storage.hpp"
namespace Storage {
namespace Disk {
/*!
Models a single track on a disk as a series of events, each event being of arbitrary length
@ -78,6 +79,7 @@ class Disk {
virtual std::shared_ptr<Track> get_track_at_position(unsigned int position) = 0;
};
}
}
#endif /* Disk_hpp */

View File

@ -8,9 +8,9 @@
#include "DiskDrive.hpp"
using namespace Storage;
using namespace Storage::Disk;
DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute) :
Drive::Drive(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),
_head_position(0),
@ -22,7 +22,7 @@ DiskDrive::DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier
_rotational_multiplier.simplify();
}
void DiskDrive::set_expected_bit_length(Time bit_length)
void Drive::set_expected_bit_length(Time bit_length)
{
_bit_length = bit_length;
@ -33,23 +33,23 @@ void DiskDrive::set_expected_bit_length(Time bit_length)
_pll->set_delegate(this);
}
void DiskDrive::set_disk(std::shared_ptr<Disk> disk)
void Drive::set_disk(std::shared_ptr<Disk> disk)
{
_disk = disk;
set_track(Time());
}
bool DiskDrive::has_disk()
bool Drive::has_disk()
{
return (bool)_disk;
}
bool DiskDrive::get_is_track_zero()
bool Drive::get_is_track_zero()
{
return _head_position == 0;
}
void DiskDrive::step(int direction)
void Drive::step(int direction)
{
_head_position = std::max(_head_position + direction, 0);
Time extra_time = get_time_into_next_event() / _rotational_multiplier;
@ -58,7 +58,7 @@ void DiskDrive::step(int direction)
set_track(_time_into_track);
}
void DiskDrive::set_track(Time initial_offset)
void Drive::set_track(Time initial_offset)
{
_track = _disk->get_track_at_position((unsigned int)_head_position);
// TODO: probably a better implementation of the empty track?
@ -80,7 +80,7 @@ void DiskDrive::set_track(Time initial_offset)
reset_timer_to_offset(offset * _rotational_multiplier);
}
void DiskDrive::run_for_cycles(int number_of_cycles)
void Drive::run_for_cycles(int number_of_cycles)
{
if(has_disk())
{
@ -96,7 +96,7 @@ void DiskDrive::run_for_cycles(int number_of_cycles)
#pragma mark - Track timed event loop
void DiskDrive::get_next_event()
void Drive::get_next_event()
{
if(_track)
_current_event = _track->get_next_event();
@ -112,7 +112,7 @@ void DiskDrive::get_next_event()
set_next_event_time_interval(_current_event.length * _rotational_multiplier);
}
void DiskDrive::process_next_event()
void Drive::process_next_event()
{
switch(_current_event.type)
{
@ -131,7 +131,7 @@ void DiskDrive::process_next_event()
#pragma mark - PLL delegate
void DiskDrive::digital_phase_locked_loop_output_bit(int value)
void Drive::digital_phase_locked_loop_output_bit(int value)
{
process_input_bit(value, _cycles_since_index_hole);
}

View File

@ -14,6 +14,7 @@
#include "../TimedEventLoop.hpp"
namespace Storage {
namespace Disk {
/*!
Provides the shell for emulating a disk drive something that takes a disk and has a drive head
@ -26,13 +27,13 @@ namespace Storage {
TODO: double sided disks, communication of head size and permissible stepping extents, appropriate
simulation of gain.
*/
class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
class Drive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop {
public:
/*!
Constructs a @c DiskDrive that will be run at @c clock_rate and runs its PLL at @c clock_rate*clock_rate_multiplier,
spinning inserted disks at @c revolutions_per_minute.
*/
DiskDrive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
Drive(unsigned int clock_rate, unsigned int clock_rate_multiplier, unsigned int revolutions_per_minute);
/*!
Communicates to the PLL the expected length of a bit.
@ -106,6 +107,7 @@ class DiskDrive: public DigitalPhaseLockedLoop::Delegate, public TimedEventLoop
Time _time_into_track;
};
}
}
#endif /* DiskDrive_hpp */

View File

@ -7,6 +7,7 @@
//
#include "CommodoreGCR.hpp"
#include <limits>
using namespace Storage;
@ -23,34 +24,47 @@ unsigned int Storage::Encodings::CommodoreGCR::encoding_for_nibble(uint8_t nibbl
{
switch(nibble & 0xf)
{
case 0x0: return 0x0a;
case 0x1: return 0x0b;
case 0x2: return 0x12;
case 0x3: return 0x13;
case 0x4: return 0x0e;
case 0x5: return 0x0f;
case 0x6: return 0x16;
case 0x7: return 0x17;
case 0x8: return 0x09;
case 0x9: return 0x19;
case 0xa: return 0x1a;
case 0xb: return 0x1b;
case 0xc: return 0x0d;
case 0xd: return 0x1d;
case 0xe: return 0x1e;
case 0xf: return 0x15;
case 0x0: return 0x0a; case 0x1: return 0x0b;
case 0x2: return 0x12; case 0x3: return 0x13;
case 0x4: return 0x0e; case 0x5: return 0x0f;
case 0x6: return 0x16; case 0x7: return 0x17;
case 0x8: return 0x09; case 0x9: return 0x19;
case 0xa: return 0x1a; case 0xb: return 0x1b;
case 0xc: return 0x0d; case 0xd: return 0x1d;
case 0xe: return 0x1e; case 0xf: return 0x15;
// for the benefit of the compiler; clearly unreachable
default: return 0xff;
}
}
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_quintet(unsigned int quintet)
{
switch(quintet & 0x1f)
{
case 0x0a: return 0x0; case 0x0b: return 0x1;
case 0x12: return 0x2; case 0x13: return 0x3;
case 0x0e: return 0x4; case 0x0f: return 0x5;
case 0x16: return 0x6; case 0x17: return 0x7;
case 0x09: return 0x8; case 0x19: return 0x9;
case 0x1a: return 0xa; case 0x1b: return 0xb;
case 0x0d: return 0xc; case 0x1d: return 0xd;
case 0x1e: return 0xe; case 0x15: return 0xf;
default: return std::numeric_limits<unsigned int>::max();
}
}
unsigned int Storage::Encodings::CommodoreGCR::encoding_for_byte(uint8_t byte)
{
return encoding_for_nibble(byte) | (encoding_for_nibble(byte >> 4) << 5);
}
unsigned int Storage::Encodings::CommodoreGCR::decoding_from_dectet(unsigned int dectet)
{
return decoding_from_quintet(dectet) | (decoding_from_quintet(dectet >> 5) << 4);
}
void Storage::Encodings::CommodoreGCR::encode_block(uint8_t *destination, uint8_t *source)
{
unsigned int encoded_bytes[4] = {

View File

@ -36,6 +36,16 @@ namespace CommodoreGCR {
A block is defined to be four source bytes, which encodes to five GCR bytes.
*/
void encode_block(uint8_t *destination, uint8_t *source);
/*!
@returns the four bit nibble for the five-bit GCR @c quintet if a valid GCR value; INT_MAX otherwise.
*/
unsigned int decoding_from_quintet(unsigned int quintet);
/*!
@returns the byte composted of the low five bit five-bit GCR
*/
unsigned int decoding_from_dectet(unsigned int dectet);
}
}
}

View File

@ -13,7 +13,7 @@
#include "../PCMTrack.hpp"
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
using namespace Storage;
using namespace Storage::Disk;
D64::D64(const char *file_name)
{

View File

@ -12,6 +12,7 @@
#include "../Disk.hpp"
namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a D64 disk image a decoded sector dump of a C1540-format disk.
@ -42,5 +43,7 @@ class D64: public Disk {
uint16_t _disk_id;
};
};
}
}
#endif /* D64_hpp */

View File

@ -12,7 +12,7 @@
#include "../PCMTrack.hpp"
#include "../Encodings/CommodoreGCR.hpp"
using namespace Storage;
using namespace Storage::Disk;
G64::G64(const char *file_name)
{

View File

@ -12,6 +12,7 @@
#include "../Disk.hpp"
namespace Storage {
namespace Disk {
/*!
Provies a @c Disk containing a G64 disk image a raw but perfectly-clocked GCR stream.
@ -45,6 +46,7 @@ class G64: public Disk {
uint16_t _maximum_track_size;
};
};
}
}
#endif /* G64_hpp */

View File

@ -9,7 +9,7 @@
#include "PCMTrack.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage;
using namespace Storage::Disk;
PCMTrack::PCMTrack(std::vector<PCMSegment> segments)
{
@ -61,7 +61,7 @@ PCMTrack::Event PCMTrack::get_next_event()
return _next_event;
}
Time PCMTrack::seek_to(Time time_since_index_hole)
Storage::Time PCMTrack::seek_to(Time time_since_index_hole)
{
_segment_pointer = 0;

View File

@ -13,6 +13,7 @@
#include <vector>
namespace Storage {
namespace Disk {
/*!
A segment of PCM-sampled data.
@ -68,6 +69,7 @@ class PCMTrack: public Track {
size_t _bit_pointer;
};
}
}
#endif /* PCMTrack_hpp */

View File

@ -10,9 +10,9 @@
#include <cstdio>
#include <cstring>
using namespace Storage;
using namespace Storage::Tape;
CommodoreTAP::CommodoreTAP(const char *file_name)
CommodoreTAP::CommodoreTAP(const char *file_name) : _is_at_end(false)
{
_file = fopen(file_name, "rb");
@ -58,14 +58,25 @@ CommodoreTAP::~CommodoreTAP()
fclose(_file);
}
void CommodoreTAP::reset()
void CommodoreTAP::virtual_reset()
{
fseek(_file, 0x14, SEEK_SET);
_current_pulse.type = Pulse::High;
_is_at_end = false;
}
Tape::Pulse CommodoreTAP::get_next_pulse()
bool CommodoreTAP::is_at_end()
{
return _is_at_end;
}
Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse()
{
if(_is_at_end)
{
return _current_pulse;
}
if(_current_pulse.type == Pulse::High)
{
uint32_t next_length;
@ -81,8 +92,17 @@ Tape::Pulse CommodoreTAP::get_next_pulse()
next_length |= (uint32_t)(fgetc(_file) << 16);
}
_current_pulse.length.length = next_length;
_current_pulse.type = Pulse::Low;
if(feof(_file))
{
_is_at_end = true;
_current_pulse.length.length = _current_pulse.length.clock_rate;
_current_pulse.type = Pulse::Zero;
}
else
{
_current_pulse.length.length = next_length;
_current_pulse.type = Pulse::Low;
}
}
else
_current_pulse.type = Pulse::High;

View File

@ -13,6 +13,7 @@
#include <stdint.h>
namespace Storage {
namespace Tape {
/*!
Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings.
@ -32,17 +33,21 @@ class CommodoreTAP: public Tape {
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
bool is_at_end();
private:
void virtual_reset();
Pulse virtual_get_next_pulse();
FILE *_file;
bool _updated_layout;
uint32_t _file_size;
Pulse _current_pulse;
bool _is_at_end;
};
}
}
#endif /* CommodoreTAP_hpp */

View File

@ -46,9 +46,9 @@
#include <sys/stat.h>
using namespace Storage;
using namespace Storage::Tape;
TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80)
PRG::PRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80)
{
struct stat file_stats;
stat(file_name, &file_stats);
@ -69,12 +69,12 @@ TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePha
throw ErrorBadFormat;
}
TapePRG::~TapePRG()
PRG::~PRG()
{
if(_file) fclose(_file);
}
Tape::Pulse TapePRG::get_next_pulse()
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
{
// these are all microseconds per pole
static const unsigned int leader_zero_length = 179;
@ -87,7 +87,7 @@ Tape::Pulse TapePRG::get_next_pulse()
Tape::Pulse pulse;
pulse.length.clock_rate = 1000000;
pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low;
pulse.type = (_bitPhase&1) ? Tape::Pulse::High : Tape::Pulse::Low;
switch(_outputToken)
{
case Leader: pulse.length.length = leader_zero_length; break;
@ -95,12 +95,12 @@ Tape::Pulse TapePRG::get_next_pulse()
case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break;
case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break;
case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break;
case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
case Silence: pulse.type = Tape::Pulse::Zero; pulse.length.length = 5000; break;
}
return pulse;
}
void TapePRG::reset()
void PRG::virtual_reset()
{
_bitPhase = 3;
fseek(_file, 2, SEEK_SET);
@ -109,17 +109,22 @@ void TapePRG::reset()
_copy_mask = 0x80;
}
void TapePRG::get_next_output_token()
bool PRG::is_at_end()
{
return _filePhase == FilePhaseAtEnd;
}
void PRG::get_next_output_token()
{
static const int block_length = 192; // not counting the checksum
static const int countdown_bytes = 9;
static const int leadin_length = 20000;
static const int block_leadin_length = 5000;
if(_filePhase == FilePhaseHeaderDataGap)
if(_filePhase == FilePhaseHeaderDataGap || _filePhase == FilePhaseAtEnd)
{
_outputToken = Silence;
_filePhase = FilePhaseData;
if(_filePhase != FilePhaseAtEnd) _filePhase = FilePhaseData;
return;
}

View File

@ -6,18 +6,19 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef TapePRG_hpp
#define TapePRG_hpp
#ifndef Storage_Tape_PRG_hpp
#define Storage_Tape_PRG_hpp
#include "../Tape.hpp"
#include <stdint.h>
namespace Storage {
namespace Tape {
/*!
Provides a @c Tape containing a .PRG, which is a direct local file.
*/
class TapePRG: public Tape {
class PRG: public Tape {
public:
/*!
Constructs a @c T64 containing content from the file with name @c file_name, of type @c type.
@ -26,17 +27,20 @@ class TapePRG: public Tape {
@param type The type of data the file should contain.
@throws ErrorBadFormat if this file could not be opened and recognised as the specified type.
*/
TapePRG(const char *file_name);
~TapePRG();
PRG(const char *file_name);
~PRG();
enum {
ErrorBadFormat
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
bool is_at_end();
private:
Pulse virtual_get_next_pulse();
void virtual_reset();
FILE *_file;
uint16_t _load_address;
uint16_t _length;
@ -46,6 +50,7 @@ class TapePRG: public Tape {
FilePhaseHeader,
FilePhaseHeaderDataGap,
FilePhaseData,
FilePhaseAtEnd
} _filePhase;
int _phaseOffset;
@ -64,6 +69,7 @@ class TapePRG: public Tape {
uint8_t _copy_mask;
};
}
}
#endif /* T64_hpp */

View File

@ -7,8 +7,12 @@
//
#include "TapeUEF.hpp"
#include <string.h>
#include <math.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#pragma mark - ZLib extensions
static float gzgetfloat(gzFile file)
{
@ -41,9 +45,34 @@ static float gzgetfloat(gzFile file)
return result;
}
Storage::UEF::UEF(const char *file_name) :
_chunk_id(0), _chunk_length(0), _chunk_position(0),
_time_base(1200)
static int gzget16(gzFile file)
{
int result = gzgetc(file);
result |= (gzgetc(file) << 8);
return result;
}
static int gzget24(gzFile file)
{
int result = gzget16(file);
result |= (gzgetc(file) << 16);
return result;
}
static int gzget32(gzFile file)
{
int result = gzget16(file);
result |= (gzget16(file) << 16);
return result;
}
using namespace Storage::Tape;
UEF::UEF(const char *file_name) :
_time_base(1200),
_is_at_end(false),
_pulse_pointer(0),
_is_300_baud(false)
{
_file = gzopen(file_name, "rb");
@ -63,163 +92,84 @@ Storage::UEF::UEF(const char *file_name) :
throw ErrorNotUEF;
}
_start_of_next_chunk = gztell(_file);
find_next_tape_chunk();
parse_next_tape_chunk();
}
Storage::UEF::~UEF()
UEF::~UEF()
{
gzclose(_file);
}
void Storage::UEF::reset()
#pragma mark - Public methods
void UEF::virtual_reset()
{
gzseek(_file, 12, SEEK_SET);
_is_at_end = false;
parse_next_tape_chunk();
}
Storage::Tape::Pulse Storage::UEF::get_next_pulse()
bool UEF::is_at_end()
{
return _is_at_end;
}
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
{
Pulse next_pulse;
if(!_bit_position && chunk_is_finished())
if(_is_at_end)
{
find_next_tape_chunk();
next_pulse.type = Pulse::Zero;
next_pulse.length.length = _time_base * 4;
next_pulse.length.clock_rate = _time_base * 4;
return next_pulse;
}
switch(_chunk_id)
next_pulse = _queued_pulses[_pulse_pointer];
_pulse_pointer++;
if(_pulse_pointer == _queued_pulses.size())
{
case 0x0100: case 0x0102:
// In the ordinary ("1200 baud") data encoding format,
// a zero bit is encoded as one complete cycle at the base frequency.
// A one bit is two complete cycles at twice the base frequency.
if(!_bit_position)
{
_current_bit = get_next_bit();
}
next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low;
next_pulse.length.length = _current_bit ? 1 : 2;
next_pulse.length.clock_rate = _time_base * 4;
_bit_position = (_bit_position+1)&(_current_bit ? 3 : 1);
break;
case 0x0110:
next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low;
next_pulse.length.length = 1;
next_pulse.length.clock_rate = _time_base * 4;
_bit_position ^= 1;
if(!_bit_position) _chunk_position++;
break;
case 0x0114:
if(!_bit_position)
{
_current_bit = get_next_bit();
if(_first_is_pulse && !_chunk_position)
{
_bit_position++;
}
}
next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low;
next_pulse.length.length = _current_bit ? 1 : 2;
next_pulse.length.clock_rate = _time_base * 4;
_bit_position ^= 1;
if((_chunk_id == 0x0114) && (_chunk_position == _chunk_duration.length-1) && _last_is_pulse)
{
_chunk_position++;
}
break;
case 0x0112:
case 0x0116:
next_pulse.type = Pulse::Zero;
next_pulse.length = _chunk_duration;
_chunk_position++;
break;
_queued_pulses.clear();
_pulse_pointer = 0;
parse_next_tape_chunk();
}
return next_pulse;
}
void Storage::UEF::find_next_tape_chunk()
#pragma mark - Chunk navigator
void UEF::parse_next_tape_chunk()
{
int reset_count = 0;
_chunk_position = 0;
_bit_position = 0;
while(1)
while(!_queued_pulses.size())
{
gzseek(_file, _start_of_next_chunk, SEEK_SET);
// read chunk details
uint16_t chunk_id = (uint16_t)gzget16(_file);
uint32_t chunk_length = (uint32_t)gzget32(_file);
// read chunk ID
_chunk_id = (uint16_t)gzgetc(_file);
_chunk_id |= (uint16_t)(gzgetc(_file) << 8);
_chunk_length = (uint32_t)(gzgetc(_file) << 0);
_chunk_length |= (uint32_t)(gzgetc(_file) << 8);
_chunk_length |= (uint32_t)(gzgetc(_file) << 16);
_chunk_length |= (uint32_t)(gzgetc(_file) << 24);
_start_of_next_chunk = gztell(_file) + _chunk_length;
// figure out where the next chunk will start
z_off_t start_of_next_chunk = gztell(_file) + chunk_length;
if(gzeof(_file))
{
reset_count++;
if(reset_count == 2) break;
reset();
continue;
_is_at_end = true;
return;
}
switch(_chunk_id)
switch(chunk_id)
{
case 0x0100: // implicit bit pattern
_implicit_data_chunk.position = 0;
return;
case 0x0100: queue_implicit_bit_pattern(chunk_length); break;
case 0x0102: queue_explicit_bit_pattern(chunk_length); break;
case 0x0112: queue_integer_gap(); break;
case 0x0116: queue_floating_point_gap(); break;
case 0x0102: // explicit bit patterns
_explicit_data_chunk.position = 0;
return;
case 0x0110: queue_carrier_tone(); break;
case 0x0111: queue_carrier_tone_with_dummy(); break;
case 0x0112: // integer gap
_chunk_duration.length = (uint16_t)gzgetc(_file);
_chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8);
_chunk_duration.clock_rate = _time_base;
return;
case 0x0114: queue_security_cycles(); break;
case 0x0104: queue_defined_data(chunk_length); break;
case 0x0116: // floating point gap
{
float length = gzgetfloat(_file);
_chunk_duration.length = (unsigned int)(length * 4000000);
_chunk_duration.clock_rate = 4000000;
}
return;
case 0x0110: // carrier tone
_chunk_duration.length = (uint16_t)gzgetc(_file);
_chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8);
gzseek(_file, _chunk_length - 2, SEEK_CUR);
return;
// case 0x0111: // carrier tone with dummy byte
// TODO: read lengths
// return;
case 0x0114: // security cycles
{
// read number of cycles
_chunk_duration.length = (uint32_t)gzgetc(_file);
_chunk_duration.length |= (uint32_t)gzgetc(_file) << 8;
_chunk_duration.length |= (uint32_t)gzgetc(_file) << 16;
// Ps and Ws
_first_is_pulse = gzgetc(_file) == 'P';
_last_is_pulse = gzgetc(_file) == 'P';
}
break;
case 0x113: // change of base rate
case 0x0113: // change of base rate
{
// TODO: something smarter than just converting this to an int
float new_time_base = gzgetfloat(_file);
@ -227,77 +177,196 @@ void Storage::UEF::find_next_tape_chunk()
}
break;
case 0x0117:
{
int baud_rate = gzget16(_file);
_is_300_baud = (baud_rate == 300);
}
break;
default:
gzseek(_file, _chunk_length, SEEK_CUR);
printf("!!! Skipping %04x\n", chunk_id);
break;
}
gzseek(_file, start_of_next_chunk, SEEK_SET);
}
}
bool Storage::UEF::chunk_is_finished()
#pragma mark - Chunk parsers
void UEF::queue_implicit_bit_pattern(uint32_t length)
{
switch(_chunk_id)
while(length--)
{
case 0x0100: return (_implicit_data_chunk.position / 10) == _chunk_length;
case 0x0102: return (_explicit_data_chunk.position / 8) == _chunk_length;
case 0x0114:
case 0x0110: return _chunk_position == _chunk_duration.length;
case 0x0112:
case 0x0116: return _chunk_position ? true : false;
default: return true;
queue_implicit_byte((uint8_t)gzgetc(_file));
}
}
bool Storage::UEF::get_next_bit()
void UEF::queue_explicit_bit_pattern(uint32_t length)
{
switch(_chunk_id)
size_t length_in_bits = (length << 3) - (size_t)gzgetc(_file);
uint8_t current_byte = 0;
for(size_t bit = 0; bit < length_in_bits; bit++)
{
case 0x0100:
{
uint32_t bit_position = _implicit_data_chunk.position%10;
_implicit_data_chunk.position++;
if(!bit_position) _implicit_data_chunk.current_byte = (uint8_t)gzgetc(_file);
if(bit_position == 0) return false;
if(bit_position == 9) return true;
bool result = (_implicit_data_chunk.current_byte&1) ? true : false;
_implicit_data_chunk.current_byte >>= 1;
return result;
}
break;
case 0x0102:
{
uint32_t bit_position = _explicit_data_chunk.position%8;
_explicit_data_chunk.position++;
if(!bit_position) _explicit_data_chunk.current_byte = (uint8_t)gzgetc(_file);
bool result = (_explicit_data_chunk.current_byte&1) ? true : false;
_explicit_data_chunk.current_byte >>= 1;
return result;
}
break;
// TODO: 0x0104, 0x0111
case 0x0114:
{
uint32_t bit_position = _chunk_position%8;
_chunk_position++;
if(!bit_position && _chunk_position < _chunk_duration.length)
{
_current_byte = (uint8_t)gzgetc(_file);
}
bool result = (_current_byte&1) ? true : false;
_current_byte >>= 1;
return result;
}
break;
case 0x0110:
_chunk_position++;
return true;
default: return true;
if(!(bit&7)) current_byte = (uint8_t)gzgetc(_file);
queue_bit(current_byte&1);
current_byte >>= 1;
}
}
void UEF::queue_integer_gap()
{
Time duration;
duration.length = (unsigned int)gzget16(_file);
duration.clock_rate = _time_base;
_queued_pulses.emplace_back(Pulse::Zero, duration);
}
void UEF::queue_floating_point_gap()
{
float length = gzgetfloat(_file);
Time duration;
duration.length = (unsigned int)(length * 4000000);
duration.clock_rate = 4000000;
_queued_pulses.emplace_back(Pulse::Zero, duration);
}
void UEF::queue_carrier_tone()
{
unsigned int number_of_cycles = (unsigned int)gzget16(_file);
while(number_of_cycles--) queue_bit(1);
}
void UEF::queue_carrier_tone_with_dummy()
{
unsigned int pre_cycles = (unsigned int)gzget16(_file);
unsigned int post_cycles = (unsigned int)gzget16(_file);
while(pre_cycles--) queue_bit(1);
queue_implicit_byte(0xaa);
while(post_cycles--) queue_bit(1);
}
void UEF::queue_security_cycles()
{
int number_of_cycles = gzget24(_file);
bool first_is_pulse = gzgetc(_file) == 'P';
bool last_is_pulse = gzgetc(_file) == 'P';
uint8_t current_byte = 0;
for(int cycle = 0; cycle < number_of_cycles; cycle++)
{
if(!(cycle&7)) current_byte = (uint8_t)gzgetc(_file);
int bit = (current_byte >> 7);
current_byte <<= 1;
Time duration;
duration.length = bit ? 1 : 2;
duration.clock_rate = _time_base * 4;
if(!cycle && first_is_pulse)
{
_queued_pulses.emplace_back(Pulse::High, duration);
}
else if(cycle == number_of_cycles-1 && last_is_pulse)
{
_queued_pulses.emplace_back(Pulse::Low, duration);
}
else
{
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
}
}
}
void UEF::queue_defined_data(uint32_t length)
{
if(length < 3) return;
int bits_per_packet = gzgetc(_file);
char parity_type = (char)gzgetc(_file);
int number_of_stop_bits = gzgetc(_file);
bool has_extra_stop_wave = (number_of_stop_bits < 0);
number_of_stop_bits = abs(number_of_stop_bits);
length -= 3;
while(length--)
{
uint8_t byte = (uint8_t)gzgetc(_file);
uint8_t parity_value = byte;
parity_value ^= (parity_value >> 4);
parity_value ^= (parity_value >> 2);
parity_value ^= (parity_value >> 1);
queue_bit(0);
int c = bits_per_packet;
while(c--)
{
queue_bit(byte&1);
byte >>= 1;
}
switch(parity_type)
{
default: break;
case 'E': queue_bit(parity_value&1); break;
case 'O': queue_bit((parity_value&1) ^ 1); break;
}
int stop_bits = number_of_stop_bits;
while(stop_bits--) queue_bit(1);
if(has_extra_stop_wave)
{
Time duration;
duration.length = 1;
duration.clock_rate = _time_base * 4;
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
}
}
}
#pragma mark - Queuing helpers
void UEF::queue_implicit_byte(uint8_t byte)
{
queue_bit(0);
int c = 8;
while(c--)
{
queue_bit(byte&1);
byte >>= 1;
}
queue_bit(1);
}
void UEF::queue_bit(int bit)
{
int number_of_cycles;
Time duration;
duration.clock_rate = _time_base * 4;
if(bit)
{
// encode high-frequency waves
duration.length = 1;
number_of_cycles = 2;
}
else
{
// encode low-frequency waves
duration.length = 2;
number_of_cycles = 1;
}
if(_is_300_baud) number_of_cycles *= 4;
while(number_of_cycles--)
{
_queued_pulses.emplace_back(Pulse::Low, duration);
_queued_pulses.emplace_back(Pulse::High, duration);
}
}

View File

@ -11,9 +11,11 @@
#include "../Tape.hpp"
#include <zlib.h>
#include <stdint.h>
#include <cstdint>
#include <vector>
namespace Storage {
namespace Tape {
/*!
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
@ -33,45 +35,39 @@ class UEF : public Tape {
};
// implemented to satisfy @c Tape
Pulse get_next_pulse();
void reset();
bool is_at_end();
private:
void virtual_reset();
Pulse virtual_get_next_pulse();
gzFile _file;
unsigned int _time_base;
z_off_t _start_of_next_chunk;
bool _is_at_end;
bool _is_300_baud;
uint16_t _chunk_id;
uint32_t _chunk_length;
std::vector<Pulse> _queued_pulses;
size_t _pulse_pointer;
union {
struct {
uint8_t current_byte;
uint32_t position;
} _implicit_data_chunk;
void parse_next_tape_chunk();
struct {
uint8_t current_byte;
uint32_t position;
} _explicit_data_chunk;
};
void queue_implicit_bit_pattern(uint32_t length);
void queue_explicit_bit_pattern(uint32_t length);
uint8_t _current_byte;
uint32_t _chunk_position;
void queue_integer_gap();
void queue_floating_point_gap();
bool _current_bit;
uint32_t _bit_position;
void queue_carrier_tone();
void queue_carrier_tone_with_dummy();
Time _chunk_duration;
void queue_security_cycles();
void queue_defined_data(uint32_t length);
bool _first_is_pulse;
bool _last_is_pulse;
void find_next_tape_chunk();
bool get_next_bit();
bool chunk_is_finished();
void queue_bit(int bit);
void queue_implicit_byte(uint8_t byte);
};
}
}
#endif /* TapeUEF_hpp */

View File

@ -9,24 +9,52 @@
#include "Tape.hpp"
#include "../../NumberTheory/Factors.hpp"
using namespace Storage;
using namespace Storage::Tape;
void Tape::seek(Time seek_time)
{
// TODO: as best we can
}
#pragma mark - Lifecycle
TapePlayer::TapePlayer(unsigned int input_clock_rate) :
TimedEventLoop(input_clock_rate)
{}
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape)
#pragma mark - Seeking
void Storage::Tape::Tape::seek(Time &seek_time)
{
_current_time.set_zero();
_next_time.set_zero();
while(_next_time < seek_time) get_next_pulse();
}
void Storage::Tape::Tape::reset()
{
_current_time.set_zero();
_next_time.set_zero();
virtual_reset();
}
Tape::Pulse Tape::get_next_pulse()
{
Tape::Pulse pulse = virtual_get_next_pulse();
_current_time = _next_time;
_next_time += pulse.length;
return pulse;
}
#pragma mark - Player
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape)
{
_tape = tape;
reset_timer();
get_next_pulse();
}
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape()
{
return _tape;
}
bool TapePlayer::has_tape()
{
return (bool)_tape;
@ -41,7 +69,7 @@ void TapePlayer::get_next_pulse()
{
_current_pulse.length.length = 1;
_current_pulse.length.clock_rate = 1;
_current_pulse.type = Storage::Tape::Pulse::Zero;
_current_pulse.type = Tape::Pulse::Zero;
}
set_next_event_time_interval(_current_pulse.length);
@ -51,7 +79,7 @@ void TapePlayer::run_for_cycles(int number_of_cycles)
{
if(has_tape())
{
::TimedEventLoop::run_for_cycles(number_of_cycles);
TimedEventLoop::run_for_cycles(number_of_cycles);
}
}

View File

@ -13,6 +13,7 @@
#include "../TimedEventLoop.hpp"
namespace Storage {
namespace Tape {
/*!
Models a tape as a sequence of pulses, each pulse being of arbitrary length and described
@ -28,16 +29,40 @@ namespace Storage {
class Tape {
public:
struct Pulse {
enum {
enum Type {
High, Low, Zero
} type;
Time length;
Pulse(Type type, Time length) : type(type), length(length) {}
Pulse() {}
};
virtual Pulse get_next_pulse() = 0;
virtual void reset() = 0;
/*!
If at the start of the tape returns the first stored pulse. Otherwise advances past
the last-returned pulse and returns the next.
virtual void seek(Time seek_time); // TODO
@returns the pulse that begins at the current cursor position.
*/
Pulse get_next_pulse();
/// Returns the tape to the beginning.
void reset();
/// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise.
virtual bool is_at_end() = 0;
/// @returns the amount of time preceeding the most recently-returned pulse.
virtual Time get_current_time() { return _current_time; }
/// Advances or reverses the tape to the last time before or at @c time from which a pulse starts.
virtual void seek(Time &time);
private:
Time _current_time, _next_time;
virtual Pulse virtual_get_next_pulse() = 0;
virtual void virtual_reset() = 0;
};
/*!
@ -51,8 +76,9 @@ class TapePlayer: public TimedEventLoop {
public:
TapePlayer(unsigned int input_clock_rate);
void set_tape(std::shared_ptr<Storage::Tape> tape);
void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
bool has_tape();
std::shared_ptr<Storage::Tape::Tape> get_tape();
void run_for_cycles(int number_of_cycles);
void run_for_input_pulse();
@ -64,10 +90,11 @@ class TapePlayer: public TimedEventLoop {
private:
inline void get_next_pulse();
std::shared_ptr<Storage::Tape> _tape;
std::shared_ptr<Storage::Tape::Tape> _tape;
Tape::Pulse _current_pulse;
};
}
}
#endif /* Tape_hpp */