mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-29 13:29:46 +00:00
Merge pull request #31 from TomHarte/VicTAP
Adds basic TAP support for the Vic-20
This commit is contained in:
commit
466ddff777
@ -38,6 +38,16 @@ template <class T> class MOS6522 {
|
||||
};
|
||||
|
||||
public:
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
Two = 1
|
||||
};
|
||||
|
||||
/*! Sets a register value. */
|
||||
inline void set_register(int address, uint8_t value)
|
||||
{
|
||||
@ -47,12 +57,18 @@ template <class T> class MOS6522 {
|
||||
{
|
||||
case 0x0:
|
||||
_registers.output[1] = value;
|
||||
static_cast<T *>(this)->set_port_output(1, value, _registers.data_direction[1]); // TODO: handshake
|
||||
static_cast<T *>(this)->set_port_output(Port::B, value, _registers.data_direction[1]); // TODO: handshake
|
||||
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
case 0xf:
|
||||
case 0x1:
|
||||
_registers.output[0] = value;
|
||||
static_cast<T *>(this)->set_port_output(0, value, _registers.data_direction[0]); // TODO: handshake
|
||||
static_cast<T *>(this)->set_port_output(Port::A, value, _registers.data_direction[0]); // TODO: handshake
|
||||
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
break;
|
||||
// // No handshake, so write directly
|
||||
// _registers.output[0] = value;
|
||||
@ -93,7 +109,9 @@ template <class T> class MOS6522 {
|
||||
|
||||
// Control
|
||||
case 0xb: _registers.auxiliary_control = value; break;
|
||||
case 0xc: _registers.peripheral_control = value; break;
|
||||
case 0xc:
|
||||
_registers.peripheral_control = value;
|
||||
break;
|
||||
|
||||
// Interrupt control
|
||||
case 0xd:
|
||||
@ -117,9 +135,15 @@ template <class T> class MOS6522 {
|
||||
// printf("6522 %p: %d\n", this, address);
|
||||
switch(address)
|
||||
{
|
||||
case 0x0: return get_port_input(1, _registers.data_direction[1], _registers.output[1]);
|
||||
case 0x0:
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::B, _registers.data_direction[1], _registers.output[1]);
|
||||
case 0xf: // TODO: handshake, latching
|
||||
case 0x1: return get_port_input(0, _registers.data_direction[0], _registers.output[0]);
|
||||
case 0x1:
|
||||
_registers.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
|
||||
reevaluate_interrupts();
|
||||
return get_port_input(Port::A, _registers.data_direction[0], _registers.output[0]);
|
||||
|
||||
case 0x2: return _registers.data_direction[1];
|
||||
case 0x3: return _registers.data_direction[0];
|
||||
@ -152,8 +176,25 @@ template <class T> class MOS6522 {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
inline void set_control_line_input(int port, int line, bool value)
|
||||
inline void set_control_line(Port port, Line line, bool value)
|
||||
{
|
||||
switch(line)
|
||||
{
|
||||
case Line::One:
|
||||
if( value != _control_inputs[port].line_one &&
|
||||
value == !!(_registers.peripheral_control & (port ? 0x10 : 0x01))
|
||||
)
|
||||
{
|
||||
_registers.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
|
||||
reevaluate_interrupts();
|
||||
}
|
||||
_control_inputs[port].line_one = value;
|
||||
break;
|
||||
|
||||
case Line::Two:
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -226,12 +267,13 @@ template <class T> class MOS6522 {
|
||||
|
||||
private:
|
||||
// Expected to be overridden
|
||||
uint8_t get_port_input(int port) { return 0xff; }
|
||||
void set_port_output(int port, uint8_t value, uint8_t direction_mask) {}
|
||||
uint8_t get_port_input(Port port) { return 0xff; }
|
||||
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
|
||||
bool get_control_line(Port port, Line line) { return true; }
|
||||
// void set_interrupt_status(bool status) {}
|
||||
|
||||
// Input/output multiplexer
|
||||
uint8_t get_port_input(int port, uint8_t output_mask, uint8_t output)
|
||||
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
|
||||
{
|
||||
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||
return (input & ~output_mask) | (output & output_mask);
|
||||
@ -269,6 +311,11 @@ template <class T> class MOS6522 {
|
||||
last_timer{0, 0}, timer_needs_reload(false) {}
|
||||
} _registers;
|
||||
|
||||
// control state
|
||||
struct {
|
||||
bool line_one, line_two;
|
||||
} _control_inputs[2];
|
||||
|
||||
// Internal state other than the registers
|
||||
bool _timer_is_running[2];
|
||||
};
|
||||
|
@ -901,35 +901,14 @@ void Speaker::set_is_enabled(bool is_enabled)
|
||||
*/
|
||||
|
||||
Tape::Tape() :
|
||||
TapePlayer(2000000),
|
||||
_is_running(false),
|
||||
_data_register(0),
|
||||
_delegate(nullptr),
|
||||
_output({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
_last_posted_interrupt_status(0),
|
||||
_interrupt_status(0) {}
|
||||
|
||||
void Tape::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||
{
|
||||
_tape = tape;
|
||||
get_next_tape_pulse();
|
||||
}
|
||||
|
||||
inline void Tape::get_next_tape_pulse()
|
||||
{
|
||||
_input.time_into_pulse = 0;
|
||||
if(_tape)
|
||||
_input.current_pulse = _tape->get_next_pulse();
|
||||
else
|
||||
{
|
||||
_input.current_pulse.length.length = 1;
|
||||
_input.current_pulse.length.clock_rate = 1;
|
||||
_input.current_pulse.type = Storage::Tape::Pulse::Zero;
|
||||
}
|
||||
if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate())
|
||||
{
|
||||
_input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, 2000000));
|
||||
}
|
||||
}
|
||||
_interrupt_status(0)
|
||||
{}
|
||||
|
||||
inline void Tape::push_tape_bit(uint16_t bit)
|
||||
{
|
||||
@ -992,18 +971,16 @@ inline uint8_t Tape::get_data_register()
|
||||
return (uint8_t)(_data_register >> 2);
|
||||
}
|
||||
|
||||
inline void Tape::run_for_input_pulse()
|
||||
inline void Tape::process_input_pulse(Storage::Tape::Pulse pulse)
|
||||
{
|
||||
get_next_tape_pulse();
|
||||
|
||||
_crossings[0] = _crossings[1];
|
||||
_crossings[1] = _crossings[2];
|
||||
_crossings[2] = _crossings[3];
|
||||
|
||||
_crossings[3] = Tape::Unrecognised;
|
||||
if(_input.current_pulse.type != Storage::Tape::Pulse::Zero)
|
||||
if(pulse.type != Storage::Tape::Pulse::Zero)
|
||||
{
|
||||
float pulse_length = (float)_input.current_pulse.length.length / (float)_input.current_pulse.length.clock_rate;
|
||||
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;
|
||||
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) _crossings[3] = Tape::Long;
|
||||
}
|
||||
@ -1030,16 +1007,9 @@ inline void Tape::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(_is_in_input_mode)
|
||||
{
|
||||
if(_is_running && _tape != nullptr)
|
||||
if(_is_running)
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
_input.time_into_pulse += (unsigned int)_input.pulse_stepper->step();
|
||||
if(_input.time_into_pulse == _input.current_pulse.length.length)
|
||||
{
|
||||
run_for_input_pulse();
|
||||
}
|
||||
}
|
||||
TapePlayer::run_for_cycles(number_of_cycles);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -62,15 +62,11 @@ enum Key: uint16_t {
|
||||
TerminateSequence = 0, NotMapped = 0xfffe,
|
||||
};
|
||||
|
||||
class Tape {
|
||||
class Tape: public Storage::TapePlayer {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
||||
inline bool has_tape()
|
||||
{
|
||||
return (bool)_tape;
|
||||
}
|
||||
inline void run_for_cycles(unsigned int number_of_cycles);
|
||||
|
||||
inline uint8_t get_data_register();
|
||||
inline void set_data_register(uint8_t value);
|
||||
@ -85,23 +81,16 @@ class Tape {
|
||||
};
|
||||
inline void set_delegate(Delegate *delegate) { _delegate = delegate; }
|
||||
|
||||
inline void run_for_cycles(unsigned int number_of_cycles);
|
||||
inline void run_for_input_pulse();
|
||||
|
||||
inline void set_is_running(bool is_running) { _is_running = is_running; }
|
||||
inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; }
|
||||
inline void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
private:
|
||||
void process_input_pulse(Storage::Tape::Pulse pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
std::shared_ptr<Storage::Tape> _tape;
|
||||
|
||||
struct {
|
||||
Storage::Tape::Pulse current_pulse;
|
||||
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
|
||||
uint32_t time_into_pulse;
|
||||
int minimum_bits_until_full;
|
||||
} _input;
|
||||
struct {
|
||||
|
@ -17,6 +17,7 @@ Machine::Machine() :
|
||||
{
|
||||
_userPortVIA.set_delegate(this);
|
||||
_keyboardVIA.set_delegate(this);
|
||||
_tape.set_delegate(this);
|
||||
set_reset_line(true);
|
||||
}
|
||||
|
||||
@ -83,6 +84,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
_userPortVIA.run_for_half_cycles(2);
|
||||
_keyboardVIA.run_for_half_cycles(2);
|
||||
if(_typer) _typer->update(1);
|
||||
_tape.run_for_cycles(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -90,8 +92,9 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
||||
|
||||
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||
{
|
||||
bool irq = _userPortVIA.get_interrupt_line() || _keyboardVIA.get_interrupt_line();
|
||||
set_irq_line(irq);
|
||||
// bool irq = _userPortVIA.get_interrupt_line() || _keyboardVIA.get_interrupt_line();
|
||||
set_nmi_line(_userPortVIA.get_interrupt_line());
|
||||
set_irq_line(_keyboardVIA.get_interrupt_line());
|
||||
}
|
||||
|
||||
#pragma mark - Setup
|
||||
@ -135,6 +138,19 @@ void Machine::add_prg(size_t length, const uint8_t *data)
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mar - Tape
|
||||
|
||||
void Machine::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||
{
|
||||
_tape.set_tape(tape);
|
||||
set_typer_for_string("LOAD\n");
|
||||
}
|
||||
|
||||
void Machine::tape_did_change_input(Tape *tape)
|
||||
{
|
||||
_keyboardVIA.set_control_line(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input());
|
||||
}
|
||||
|
||||
#pragma mark - Typer
|
||||
|
||||
int Machine::get_typer_delay()
|
||||
@ -249,3 +265,20 @@ bool Machine::typer_set_next_character(::Utility::Typer *typer, char character,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark - Tape
|
||||
|
||||
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)
|
||||
{
|
||||
bool new_input_level = pulse.type == Storage::Tape::Pulse::Low;
|
||||
if(_input_level != new_input_level)
|
||||
{
|
||||
_input_level = new_input_level;
|
||||
if(_delegate) _delegate->tape_did_change_input(this);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Vic20_hpp
|
||||
|
||||
#include "../../Processors/6502/CPU6502.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Components/6560/6560.hpp"
|
||||
#include "../../Components/6522/6522.hpp"
|
||||
|
||||
@ -49,10 +50,21 @@ enum Key: uint16_t {
|
||||
};
|
||||
|
||||
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
uint8_t get_port_input(Port port) {
|
||||
if(!port) {
|
||||
return 0x00; // TODO: bit 6 should be high if there is no tape, low otherwise
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
};
|
||||
|
||||
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
KeyboardVIA() {
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
void set_key_state(Key key, bool isPressed) {
|
||||
if(isPressed)
|
||||
_columns[key & 7] &= ~(key >> 3);
|
||||
@ -65,7 +77,7 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
||||
}
|
||||
|
||||
// to satisfy MOS::MOS6522
|
||||
uint8_t get_port_input(int port) {
|
||||
uint8_t get_port_input(Port port) {
|
||||
if(!port) {
|
||||
uint8_t result = 0xff;
|
||||
for(int c = 0; c < 8; c++)
|
||||
@ -79,24 +91,46 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
void set_port_output(int port, uint8_t value, uint8_t mask) {
|
||||
void set_port_output(Port port, uint8_t value, uint8_t mask) {
|
||||
if(port)
|
||||
_activation_mask = (value & mask) | (~mask);
|
||||
}
|
||||
|
||||
KeyboardVIA() {
|
||||
clear_all_keys();
|
||||
}
|
||||
private:
|
||||
uint8_t _columns[8];
|
||||
uint8_t _activation_mask;
|
||||
};
|
||||
|
||||
class Tape: public Storage::TapePlayer {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
void set_motor_control(bool enabled);
|
||||
void set_tape_output(bool set);
|
||||
inline bool get_input() { return _input_level; }
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void tape_did_change_input(Tape *tape) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate)
|
||||
{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
private:
|
||||
Delegate *_delegate;
|
||||
virtual void process_input_pulse(Storage::Tape::Pulse pulse);
|
||||
bool _input_level;
|
||||
};
|
||||
|
||||
|
||||
class Machine:
|
||||
public CPU6502::Processor<Machine>,
|
||||
public CRTMachine::Machine,
|
||||
public MOS::MOS6522IRQDelegate::Delegate,
|
||||
public Utility::TypeRecipient {
|
||||
public Utility::TypeRecipient,
|
||||
public Tape::Delegate {
|
||||
|
||||
public:
|
||||
Machine();
|
||||
@ -104,6 +138,8 @@ class Machine:
|
||||
|
||||
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
||||
void add_prg(size_t length, const uint8_t *data);
|
||||
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
||||
|
||||
void set_key_state(Key key, bool isPressed) { _keyboardVIA.set_key_state(key, isPressed); }
|
||||
void clear_all_keys() { _keyboardVIA.clear_all_keys(); }
|
||||
|
||||
@ -128,6 +164,9 @@ class Machine:
|
||||
virtual int get_typer_frequency();
|
||||
virtual bool typer_set_next_character(Utility::Typer *typer, char character, int phase);
|
||||
|
||||
// for Tape::Delegate
|
||||
virtual void tape_did_change_input(Tape *tape);
|
||||
|
||||
private:
|
||||
uint8_t _characterROM[0x1000];
|
||||
uint8_t _basicROM[0x2000];
|
||||
@ -160,6 +199,7 @@ class Machine:
|
||||
std::unique_ptr<MOS::MOS6560> _mos6560;
|
||||
UserPortVIA _userPortVIA;
|
||||
KeyboardVIA _keyboardVIA;
|
||||
Tape _tape;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -320,6 +320,7 @@
|
||||
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */; };
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
||||
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 */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
@ -701,6 +702,8 @@
|
||||
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; };
|
||||
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>"; };
|
||||
4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = "<group>"; };
|
||||
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
|
||||
@ -900,6 +903,8 @@
|
||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */,
|
||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */,
|
||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */,
|
||||
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */,
|
||||
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@ -1793,6 +1798,7 @@
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
|
||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
|
@ -42,9 +42,6 @@ class ElectronDocument: MachineDocument {
|
||||
}
|
||||
|
||||
override func readFromURL(url: NSURL, ofType typeName: String) throws {
|
||||
print(url)
|
||||
print(typeName)
|
||||
|
||||
if let pathExtension = url.pathExtension {
|
||||
switch pathExtension.lowercaseString {
|
||||
case "uef":
|
||||
|
@ -36,6 +36,20 @@ class Vic20Document: MachineDocument {
|
||||
return "Vic20Document"
|
||||
}
|
||||
|
||||
override func readFromURL(url: NSURL, ofType typeName: String) throws {
|
||||
if let pathExtension = url.pathExtension {
|
||||
switch pathExtension.lowercaseString {
|
||||
case "tap":
|
||||
vic20.openTAPAtURL(url)
|
||||
return
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0))
|
||||
try self.readFromFileWrapper(fileWrapper, ofType: typeName)
|
||||
}
|
||||
|
||||
// MARK: machine setup
|
||||
private func rom(name: String) -> NSData? {
|
||||
return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20")
|
||||
|
@ -88,6 +88,18 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Vic-20 Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
@ -8,9 +8,9 @@
|
||||
|
||||
#import "CSElectron.h"
|
||||
|
||||
#import "Electron.hpp"
|
||||
#include "Electron.hpp"
|
||||
#import "CSMachine+Subclassing.h"
|
||||
#import "TapeUEF.hpp"
|
||||
#include "TapeUEF.hpp"
|
||||
|
||||
@implementation CSElectron {
|
||||
Electron::Machine _electron;
|
||||
|
@ -14,6 +14,8 @@
|
||||
- (void)setKernelROM:(nonnull NSData *)rom;
|
||||
- (void)setBASICROM:(nonnull NSData *)rom;
|
||||
- (void)setCharactersROM:(nonnull NSData *)rom;
|
||||
|
||||
- (void)setPRG:(nonnull NSData *)prg;
|
||||
- (BOOL)openTAPAtURL:(nonnull NSURL *)URL;
|
||||
|
||||
@end
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
#import "CSVic20.h"
|
||||
|
||||
#import "Vic20.hpp"
|
||||
#include "Vic20.hpp"
|
||||
#include "CommodoreTAP.hpp"
|
||||
|
||||
@implementation CSVic20 {
|
||||
Vic20::Machine _vic20;
|
||||
@ -19,7 +20,9 @@
|
||||
}
|
||||
|
||||
- (void)setROM:(nonnull NSData *)rom slot:(Vic20::ROMSlot)slot {
|
||||
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
||||
@synchronized(self) {
|
||||
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKernelROM:(nonnull NSData *)rom {
|
||||
@ -34,8 +37,23 @@
|
||||
[self setROM:rom slot:Vic20::ROMSlotCharacters];
|
||||
}
|
||||
|
||||
- (BOOL)openTAPAtURL:(NSURL *)URL {
|
||||
@synchronized(self) {
|
||||
try {
|
||||
std::shared_ptr<Storage::CommodoreTAP> tape(new Storage::CommodoreTAP([URL fileSystemRepresentation]));
|
||||
_vic20.set_tape(tape);
|
||||
return YES;
|
||||
} catch(int exception) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)setPRG:(nonnull NSData *)prg {
|
||||
_vic20.add_prg(prg.length, (const uint8_t *)prg.bytes);
|
||||
@synchronized(self) {
|
||||
_vic20.add_prg(prg.length, (const uint8_t *)prg.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||
|
@ -23,7 +23,7 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
|
||||
irq_line = new_status;
|
||||
}
|
||||
|
||||
uint8_t get_port_input(int port)
|
||||
uint8_t get_port_input(Port port)
|
||||
{
|
||||
return port ? port_b_value : port_a_value;
|
||||
}
|
||||
|
91
Storage/Tape/Formats/CommodoreTAP.cpp
Normal file
91
Storage/Tape/Formats/CommodoreTAP.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// CommodoreTAP.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CommodoreTAP.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
CommodoreTAP::CommodoreTAP(const char *file_name)
|
||||
{
|
||||
_file = fopen(file_name, "rb");
|
||||
|
||||
if(!_file)
|
||||
throw ErrorNotCommodoreTAP;
|
||||
|
||||
// read and check the file signature
|
||||
char signature[12];
|
||||
if(fread(signature, 1, 12, _file) != 12)
|
||||
throw ErrorNotCommodoreTAP;
|
||||
|
||||
if(memcmp(signature, "C64-TAPE-RAW", 12))
|
||||
throw ErrorNotCommodoreTAP;
|
||||
|
||||
// check the file version
|
||||
int version = fgetc(_file);
|
||||
switch(version)
|
||||
{
|
||||
case 0: _updated_layout = false; break;
|
||||
case 1: _updated_layout = true; break;
|
||||
default: throw ErrorNotCommodoreTAP;
|
||||
}
|
||||
|
||||
// skip reserved bytes
|
||||
fseek(_file, 3, SEEK_CUR);
|
||||
|
||||
// read file size
|
||||
_file_size = (uint32_t)fgetc(_file);
|
||||
_file_size |= (uint32_t)(fgetc(_file) << 8);
|
||||
_file_size |= (uint32_t)(fgetc(_file) << 16);
|
||||
_file_size |= (uint32_t)(fgetc(_file) << 24);
|
||||
|
||||
// set up for pulse output at the PAL clock rate, with each high and
|
||||
// low being half of whatever length values will be read; pretend that
|
||||
// a high pulse has just been distributed to imply that the next thing
|
||||
// that needs to happen is a length check
|
||||
_current_pulse.length.clock_rate = 985248 * 2;
|
||||
_current_pulse.type = Pulse::High;
|
||||
}
|
||||
|
||||
CommodoreTAP::~CommodoreTAP()
|
||||
{
|
||||
fclose(_file);
|
||||
}
|
||||
|
||||
void CommodoreTAP::reset()
|
||||
{
|
||||
fseek(_file, 0x14, SEEK_SET);
|
||||
_current_pulse.type = Pulse::High;
|
||||
}
|
||||
|
||||
CommodoreTAP::Pulse CommodoreTAP::get_next_pulse()
|
||||
{
|
||||
if(_current_pulse.type == Pulse::High)
|
||||
{
|
||||
uint32_t next_length;
|
||||
uint8_t next_byte = (uint8_t)fgetc(_file);
|
||||
if(!_updated_layout || next_byte > 0)
|
||||
{
|
||||
next_length = (uint32_t)next_byte << 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
next_length = (uint32_t)fgetc(_file);
|
||||
next_length |= (uint32_t)(fgetc(_file) << 8);
|
||||
next_length |= (uint32_t)(fgetc(_file) << 16);
|
||||
}
|
||||
|
||||
_current_pulse.length.length = next_length;
|
||||
_current_pulse.type = Pulse::Low;
|
||||
}
|
||||
else
|
||||
_current_pulse.type = Pulse::High;
|
||||
|
||||
return _current_pulse;
|
||||
}
|
39
Storage/Tape/Formats/CommodoreTAP.hpp
Normal file
39
Storage/Tape/Formats/CommodoreTAP.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// CommodoreTAP.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CommodoreTAP_hpp
|
||||
#define CommodoreTAP_hpp
|
||||
|
||||
#include "../Tape.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class CommodoreTAP: public Tape {
|
||||
public:
|
||||
CommodoreTAP(const char *file_name);
|
||||
~CommodoreTAP();
|
||||
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
|
||||
enum {
|
||||
ErrorNotCommodoreTAP
|
||||
};
|
||||
|
||||
private:
|
||||
FILE *_file;
|
||||
bool _updated_layout;
|
||||
uint32_t _file_size;
|
||||
|
||||
Pulse _current_pulse;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CommodoreTAP_hpp */
|
@ -14,3 +14,56 @@ void Tape::seek(Tape::Time seek_time)
|
||||
{
|
||||
// TODO: as best we can
|
||||
}
|
||||
|
||||
TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
_input_clock_rate(input_clock_rate)
|
||||
{}
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape> tape)
|
||||
{
|
||||
_tape = tape;
|
||||
get_next_pulse();
|
||||
}
|
||||
|
||||
bool TapePlayer::has_tape()
|
||||
{
|
||||
return (bool)_tape;
|
||||
}
|
||||
|
||||
void TapePlayer::get_next_pulse()
|
||||
{
|
||||
_input.time_into_pulse = 0;
|
||||
if(_tape)
|
||||
_input.current_pulse = _tape->get_next_pulse();
|
||||
else
|
||||
{
|
||||
_input.current_pulse.length.length = 1;
|
||||
_input.current_pulse.length.clock_rate = 1;
|
||||
_input.current_pulse.type = Storage::Tape::Pulse::Zero;
|
||||
}
|
||||
if(_input.pulse_stepper == nullptr || _input.current_pulse.length.clock_rate != _input.pulse_stepper->get_output_rate())
|
||||
{
|
||||
_input.pulse_stepper.reset(new SignalProcessing::Stepper(_input.current_pulse.length.clock_rate, _input_clock_rate));
|
||||
}
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_cycles(unsigned int number_of_cycles)
|
||||
{
|
||||
if(has_tape())
|
||||
{
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
_input.time_into_pulse += (unsigned int)_input.pulse_stepper->step();
|
||||
while(_input.time_into_pulse >= _input.current_pulse.length.length)
|
||||
{
|
||||
run_for_input_pulse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TapePlayer::run_for_input_pulse()
|
||||
{
|
||||
process_input_pulse(_input.current_pulse);
|
||||
get_next_pulse();
|
||||
}
|
||||
|
@ -9,13 +9,13 @@
|
||||
#ifndef Tape_hpp
|
||||
#define Tape_hpp
|
||||
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include "../../SignalProcessing/Stepper.hpp"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
class Tape {
|
||||
public:
|
||||
|
||||
struct Time {
|
||||
unsigned int length, clock_rate;
|
||||
};
|
||||
@ -33,7 +33,31 @@ class Tape {
|
||||
virtual void seek(Time seek_time);
|
||||
};
|
||||
|
||||
class TapePlayer {
|
||||
public:
|
||||
TapePlayer(unsigned int input_clock_rate);
|
||||
|
||||
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
||||
bool has_tape();
|
||||
|
||||
void run_for_cycles(unsigned int number_of_cycles);
|
||||
void run_for_input_pulse();
|
||||
|
||||
protected:
|
||||
virtual void process_input_pulse(Tape::Pulse pulse) = 0;
|
||||
|
||||
private:
|
||||
inline void get_next_pulse();
|
||||
|
||||
unsigned int _input_clock_rate;
|
||||
std::shared_ptr<Storage::Tape> _tape;
|
||||
struct {
|
||||
Tape::Pulse current_pulse;
|
||||
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
|
||||
uint32_t time_into_pulse;
|
||||
} _input;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* Tape_hpp */
|
||||
|
Loading…
Reference in New Issue
Block a user