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:
commit
cc66e1973f
@ -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]);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
27
Machines/ConfigurationTarget.hpp
Normal file
27
Machines/ConfigurationTarget.hpp
Normal 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 */
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
@ -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"/>
|
||||
|
@ -10,6 +10,8 @@
|
||||
#import "CSElectron.h"
|
||||
#import "CSVic20.h"
|
||||
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSAudioQueue.h"
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
17
OSBindings/Mac/Clock Signal/Machine/CSMachine+Target.h
Normal file
17
OSBindings/Mac/Clock Signal/Machine/CSMachine+Target.h
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -11,7 +11,6 @@
|
||||
|
||||
@interface CSAtari2600 : CSMachine
|
||||
|
||||
- (void)setROM:(nonnull NSData *)rom;
|
||||
- (void)setState:(BOOL)state forDigitalInput:(Atari2600DigitalInput)digitalInput;
|
||||
- (void)setResetLineEnabled:(BOOL)enabled;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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(...) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
117
StaticAnalyser/Acorn/StaticAnalyser.cpp
Normal file
117
StaticAnalyser/Acorn/StaticAnalyser.cpp
Normal 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);
|
||||
}
|
27
StaticAnalyser/Acorn/StaticAnalyser.hpp
Normal file
27
StaticAnalyser/Acorn/StaticAnalyser.hpp
Normal 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 */
|
269
StaticAnalyser/Acorn/Tape.cpp
Normal file
269
StaticAnalyser/Acorn/Tape.cpp
Normal 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;
|
||||
}
|
51
StaticAnalyser/Acorn/Tape.hpp
Normal file
51
StaticAnalyser/Acorn/Tape.hpp
Normal 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 */
|
28
StaticAnalyser/Atari/StaticAnalyser.cpp
Normal file
28
StaticAnalyser/Atari/StaticAnalyser.cpp
Normal 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);
|
||||
}
|
27
StaticAnalyser/Atari/StaticAnalyser.hpp
Normal file
27
StaticAnalyser/Atari/StaticAnalyser.hpp
Normal 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 */
|
253
StaticAnalyser/Commodore/Disk.cpp
Normal file
253
StaticAnalyser/Commodore/Disk.cpp
Normal 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;
|
||||
}
|
25
StaticAnalyser/Commodore/Disk.hpp
Normal file
25
StaticAnalyser/Commodore/Disk.hpp
Normal 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 */
|
50
StaticAnalyser/Commodore/File.cpp
Normal file
50
StaticAnalyser/Commodore/File.cpp
Normal 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;
|
||||
}
|
42
StaticAnalyser/Commodore/File.hpp
Normal file
42
StaticAnalyser/Commodore/File.hpp
Normal 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 */
|
132
StaticAnalyser/Commodore/StaticAnalyser.cpp
Normal file
132
StaticAnalyser/Commodore/StaticAnalyser.cpp
Normal 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);
|
||||
}
|
27
StaticAnalyser/Commodore/StaticAnalyser.hpp
Normal file
27
StaticAnalyser/Commodore/StaticAnalyser.hpp
Normal 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 */
|
441
StaticAnalyser/Commodore/Tape.cpp
Normal file
441
StaticAnalyser/Commodore/Tape.cpp
Normal 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;
|
||||
}
|
24
StaticAnalyser/Commodore/Tape.hpp
Normal file
24
StaticAnalyser/Commodore/Tape.hpp
Normal 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 */
|
63
StaticAnalyser/Commodore/Utilities.cpp
Normal file
63
StaticAnalyser/Commodore/Utilities.cpp
Normal 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;
|
||||
}
|
22
StaticAnalyser/Commodore/Utilities.hpp
Normal file
22
StaticAnalyser/Commodore/Utilities.hpp
Normal 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 */
|
117
StaticAnalyser/StaticAnalyser.cpp
Normal file
117
StaticAnalyser/StaticAnalyser.cpp
Normal 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;
|
||||
}
|
68
StaticAnalyser/StaticAnalyser.hpp
Normal file
68
StaticAnalyser/StaticAnalyser.hpp
Normal 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 */
|
115
StaticAnalyser/TapeParser.hpp
Normal file
115
StaticAnalyser/TapeParser.hpp
Normal 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 */
|
11
Storage/Cartridge/Cartridge.cpp
Normal file
11
Storage/Cartridge/Cartridge.cpp
Normal 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;
|
63
Storage/Cartridge/Cartridge.hpp
Normal file
63
Storage/Cartridge/Cartridge.hpp
Normal 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 */
|
35
Storage/Cartridge/Formats/BinaryDump.cpp
Normal file
35
Storage/Cartridge/Formats/BinaryDump.cpp
Normal 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));
|
||||
}
|
29
Storage/Cartridge/Formats/BinaryDump.hpp
Normal file
29
Storage/Cartridge/Formats/BinaryDump.hpp
Normal 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 */
|
54
Storage/Cartridge/Formats/PRG.cpp
Normal file
54
Storage/Cartridge/Formats/PRG.cpp
Normal 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));
|
||||
}
|
29
Storage/Cartridge/Formats/PRG.hpp
Normal file
29
Storage/Cartridge/Formats/PRG.hpp
Normal 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 */
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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] = {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../PCMTrack.hpp"
|
||||
#include "../Encodings/CommodoreGCR.hpp"
|
||||
|
||||
using namespace Storage;
|
||||
using namespace Storage::Disk;
|
||||
|
||||
G64::G64(const char *file_name)
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user