mirror of
https://github.com/TomHarte/CLK.git
synced 2024-10-07 06:57:15 +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:
|
public:
|
||||||
|
enum Port {
|
||||||
|
A = 0,
|
||||||
|
B = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Line {
|
||||||
|
One = 0,
|
||||||
|
Two = 1
|
||||||
|
};
|
||||||
|
|
||||||
/*! Sets a register value. */
|
/*! Sets a register value. */
|
||||||
inline void set_register(int address, uint8_t value)
|
inline void set_register(int address, uint8_t value)
|
||||||
{
|
{
|
||||||
@ -47,12 +57,18 @@ template <class T> class MOS6522 {
|
|||||||
{
|
{
|
||||||
case 0x0:
|
case 0x0:
|
||||||
_registers.output[1] = value;
|
_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;
|
break;
|
||||||
case 0xf:
|
case 0xf:
|
||||||
case 0x1:
|
case 0x1:
|
||||||
_registers.output[0] = value;
|
_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;
|
break;
|
||||||
// // No handshake, so write directly
|
// // No handshake, so write directly
|
||||||
// _registers.output[0] = value;
|
// _registers.output[0] = value;
|
||||||
@ -93,7 +109,9 @@ template <class T> class MOS6522 {
|
|||||||
|
|
||||||
// Control
|
// Control
|
||||||
case 0xb: _registers.auxiliary_control = value; break;
|
case 0xb: _registers.auxiliary_control = value; break;
|
||||||
case 0xc: _registers.peripheral_control = value; break;
|
case 0xc:
|
||||||
|
_registers.peripheral_control = value;
|
||||||
|
break;
|
||||||
|
|
||||||
// Interrupt control
|
// Interrupt control
|
||||||
case 0xd:
|
case 0xd:
|
||||||
@ -117,9 +135,15 @@ template <class T> class MOS6522 {
|
|||||||
// printf("6522 %p: %d\n", this, address);
|
// printf("6522 %p: %d\n", this, address);
|
||||||
switch(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 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 0x2: return _registers.data_direction[1];
|
||||||
case 0x3: return _registers.data_direction[0];
|
case 0x3: return _registers.data_direction[0];
|
||||||
@ -152,8 +176,25 @@ template <class T> class MOS6522 {
|
|||||||
return 0xff;
|
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:
|
private:
|
||||||
// Expected to be overridden
|
// Expected to be overridden
|
||||||
uint8_t get_port_input(int port) { return 0xff; }
|
uint8_t get_port_input(Port port) { return 0xff; }
|
||||||
void set_port_output(int port, uint8_t value, uint8_t direction_mask) {}
|
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) {}
|
// void set_interrupt_status(bool status) {}
|
||||||
|
|
||||||
// Input/output multiplexer
|
// 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);
|
uint8_t input = static_cast<T *>(this)->get_port_input(port);
|
||||||
return (input & ~output_mask) | (output & output_mask);
|
return (input & ~output_mask) | (output & output_mask);
|
||||||
@ -269,6 +311,11 @@ template <class T> class MOS6522 {
|
|||||||
last_timer{0, 0}, timer_needs_reload(false) {}
|
last_timer{0, 0}, timer_needs_reload(false) {}
|
||||||
} _registers;
|
} _registers;
|
||||||
|
|
||||||
|
// control state
|
||||||
|
struct {
|
||||||
|
bool line_one, line_two;
|
||||||
|
} _control_inputs[2];
|
||||||
|
|
||||||
// Internal state other than the registers
|
// Internal state other than the registers
|
||||||
bool _timer_is_running[2];
|
bool _timer_is_running[2];
|
||||||
};
|
};
|
||||||
|
@ -901,35 +901,14 @@ void Speaker::set_is_enabled(bool is_enabled)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Tape::Tape() :
|
Tape::Tape() :
|
||||||
|
TapePlayer(2000000),
|
||||||
_is_running(false),
|
_is_running(false),
|
||||||
_data_register(0),
|
_data_register(0),
|
||||||
_delegate(nullptr),
|
_delegate(nullptr),
|
||||||
_output({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
_output({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||||
_last_posted_interrupt_status(0),
|
_last_posted_interrupt_status(0),
|
||||||
_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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Tape::push_tape_bit(uint16_t bit)
|
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);
|
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[0] = _crossings[1];
|
||||||
_crossings[1] = _crossings[2];
|
_crossings[1] = _crossings[2];
|
||||||
_crossings[2] = _crossings[3];
|
_crossings[2] = _crossings[3];
|
||||||
|
|
||||||
_crossings[3] = Tape::Unrecognised;
|
_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 / 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;
|
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_in_input_mode)
|
||||||
{
|
{
|
||||||
if(_is_running && _tape != nullptr)
|
if(_is_running)
|
||||||
{
|
{
|
||||||
while(number_of_cycles--)
|
TapePlayer::run_for_cycles(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -62,15 +62,11 @@ enum Key: uint16_t {
|
|||||||
TerminateSequence = 0, NotMapped = 0xfffe,
|
TerminateSequence = 0, NotMapped = 0xfffe,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Tape {
|
class Tape: public Storage::TapePlayer {
|
||||||
public:
|
public:
|
||||||
Tape();
|
Tape();
|
||||||
|
|
||||||
void set_tape(std::shared_ptr<Storage::Tape> tape);
|
inline void run_for_cycles(unsigned int number_of_cycles);
|
||||||
inline bool has_tape()
|
|
||||||
{
|
|
||||||
return (bool)_tape;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t get_data_register();
|
inline uint8_t get_data_register();
|
||||||
inline void set_data_register(uint8_t value);
|
inline void set_data_register(uint8_t value);
|
||||||
@ -85,23 +81,16 @@ class Tape {
|
|||||||
};
|
};
|
||||||
inline void set_delegate(Delegate *delegate) { _delegate = delegate; }
|
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_running(bool is_running) { _is_running = is_running; }
|
||||||
inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; }
|
inline void set_is_enabled(bool is_enabled) { _is_enabled = is_enabled; }
|
||||||
inline void set_is_in_input_mode(bool is_in_input_mode);
|
inline void set_is_in_input_mode(bool is_in_input_mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void process_input_pulse(Storage::Tape::Pulse pulse);
|
||||||
inline void push_tape_bit(uint16_t bit);
|
inline void push_tape_bit(uint16_t bit);
|
||||||
inline void get_next_tape_pulse();
|
inline void get_next_tape_pulse();
|
||||||
|
|
||||||
std::shared_ptr<Storage::Tape> _tape;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Storage::Tape::Pulse current_pulse;
|
|
||||||
std::unique_ptr<SignalProcessing::Stepper> pulse_stepper;
|
|
||||||
uint32_t time_into_pulse;
|
|
||||||
int minimum_bits_until_full;
|
int minimum_bits_until_full;
|
||||||
} _input;
|
} _input;
|
||||||
struct {
|
struct {
|
||||||
|
@ -17,6 +17,7 @@ Machine::Machine() :
|
|||||||
{
|
{
|
||||||
_userPortVIA.set_delegate(this);
|
_userPortVIA.set_delegate(this);
|
||||||
_keyboardVIA.set_delegate(this);
|
_keyboardVIA.set_delegate(this);
|
||||||
|
_tape.set_delegate(this);
|
||||||
set_reset_line(true);
|
set_reset_line(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +84,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
_userPortVIA.run_for_half_cycles(2);
|
_userPortVIA.run_for_half_cycles(2);
|
||||||
_keyboardVIA.run_for_half_cycles(2);
|
_keyboardVIA.run_for_half_cycles(2);
|
||||||
if(_typer) _typer->update(1);
|
if(_typer) _typer->update(1);
|
||||||
|
_tape.run_for_cycles(1);
|
||||||
return 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)
|
void Machine::mos6522_did_change_interrupt_status(void *mos6522)
|
||||||
{
|
{
|
||||||
bool irq = _userPortVIA.get_interrupt_line() || _keyboardVIA.get_interrupt_line();
|
// bool irq = _userPortVIA.get_interrupt_line() || _keyboardVIA.get_interrupt_line();
|
||||||
set_irq_line(irq);
|
set_nmi_line(_userPortVIA.get_interrupt_line());
|
||||||
|
set_irq_line(_keyboardVIA.get_interrupt_line());
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Setup
|
#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
|
#pragma mark - Typer
|
||||||
|
|
||||||
int Machine::get_typer_delay()
|
int Machine::get_typer_delay()
|
||||||
@ -249,3 +265,20 @@ bool Machine::typer_set_next_character(::Utility::Typer *typer, char character,
|
|||||||
|
|
||||||
return true;
|
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
|
#define Vic20_hpp
|
||||||
|
|
||||||
#include "../../Processors/6502/CPU6502.hpp"
|
#include "../../Processors/6502/CPU6502.hpp"
|
||||||
|
#include "../../Storage/Tape/Tape.hpp"
|
||||||
#include "../../Components/6560/6560.hpp"
|
#include "../../Components/6560/6560.hpp"
|
||||||
#include "../../Components/6522/6522.hpp"
|
#include "../../Components/6522/6522.hpp"
|
||||||
|
|
||||||
@ -49,10 +50,21 @@ enum Key: uint16_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
|
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 {
|
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
|
||||||
public:
|
public:
|
||||||
|
KeyboardVIA() {
|
||||||
|
clear_all_keys();
|
||||||
|
}
|
||||||
|
|
||||||
void set_key_state(Key key, bool isPressed) {
|
void set_key_state(Key key, bool isPressed) {
|
||||||
if(isPressed)
|
if(isPressed)
|
||||||
_columns[key & 7] &= ~(key >> 3);
|
_columns[key & 7] &= ~(key >> 3);
|
||||||
@ -65,7 +77,7 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
// to satisfy MOS::MOS6522
|
// to satisfy MOS::MOS6522
|
||||||
uint8_t get_port_input(int port) {
|
uint8_t get_port_input(Port port) {
|
||||||
if(!port) {
|
if(!port) {
|
||||||
uint8_t result = 0xff;
|
uint8_t result = 0xff;
|
||||||
for(int c = 0; c < 8; c++)
|
for(int c = 0; c < 8; c++)
|
||||||
@ -79,24 +91,46 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
|
|||||||
return 0xff;
|
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)
|
if(port)
|
||||||
_activation_mask = (value & mask) | (~mask);
|
_activation_mask = (value & mask) | (~mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardVIA() {
|
|
||||||
clear_all_keys();
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
uint8_t _columns[8];
|
uint8_t _columns[8];
|
||||||
uint8_t _activation_mask;
|
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:
|
class Machine:
|
||||||
public CPU6502::Processor<Machine>,
|
public CPU6502::Processor<Machine>,
|
||||||
public CRTMachine::Machine,
|
public CRTMachine::Machine,
|
||||||
public MOS::MOS6522IRQDelegate::Delegate,
|
public MOS::MOS6522IRQDelegate::Delegate,
|
||||||
public Utility::TypeRecipient {
|
public Utility::TypeRecipient,
|
||||||
|
public Tape::Delegate {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Machine();
|
Machine();
|
||||||
@ -104,6 +138,8 @@ class Machine:
|
|||||||
|
|
||||||
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
|
||||||
void add_prg(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 set_key_state(Key key, bool isPressed) { _keyboardVIA.set_key_state(key, isPressed); }
|
||||||
void clear_all_keys() { _keyboardVIA.clear_all_keys(); }
|
void clear_all_keys() { _keyboardVIA.clear_all_keys(); }
|
||||||
|
|
||||||
@ -128,6 +164,9 @@ class Machine:
|
|||||||
virtual int get_typer_frequency();
|
virtual int get_typer_frequency();
|
||||||
virtual bool typer_set_next_character(Utility::Typer *typer, char character, int phase);
|
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:
|
private:
|
||||||
uint8_t _characterROM[0x1000];
|
uint8_t _characterROM[0x1000];
|
||||||
uint8_t _basicROM[0x2000];
|
uint8_t _basicROM[0x2000];
|
||||||
@ -160,6 +199,7 @@ class Machine:
|
|||||||
std::unique_ptr<MOS::MOS6560> _mos6560;
|
std::unique_ptr<MOS::MOS6560> _mos6560;
|
||||||
UserPortVIA _userPortVIA;
|
UserPortVIA _userPortVIA;
|
||||||
KeyboardVIA _keyboardVIA;
|
KeyboardVIA _keyboardVIA;
|
||||||
|
Tape _tape;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -320,6 +320,7 @@
|
|||||||
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */; };
|
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */; };
|
||||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
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 */; };
|
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
|
||||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
|
||||||
@ -900,6 +903,8 @@
|
|||||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */,
|
4B69FB451C4D950F00B5F0AA /* libz.tbd */,
|
||||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */,
|
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */,
|
||||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */,
|
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */,
|
||||||
|
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */,
|
||||||
|
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
|
||||||
);
|
);
|
||||||
path = Formats;
|
path = Formats;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1793,6 +1798,7 @@
|
|||||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
|
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
|
||||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
||||||
|
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */,
|
||||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -42,9 +42,6 @@ class ElectronDocument: MachineDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func readFromURL(url: NSURL, ofType typeName: String) throws {
|
override func readFromURL(url: NSURL, ofType typeName: String) throws {
|
||||||
print(url)
|
|
||||||
print(typeName)
|
|
||||||
|
|
||||||
if let pathExtension = url.pathExtension {
|
if let pathExtension = url.pathExtension {
|
||||||
switch pathExtension.lowercaseString {
|
switch pathExtension.lowercaseString {
|
||||||
case "uef":
|
case "uef":
|
||||||
|
@ -36,6 +36,20 @@ class Vic20Document: MachineDocument {
|
|||||||
return "Vic20Document"
|
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
|
// MARK: machine setup
|
||||||
private func rom(name: String) -> NSData? {
|
private func rom(name: String) -> NSData? {
|
||||||
return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20")
|
return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20")
|
||||||
|
@ -88,6 +88,18 @@
|
|||||||
<key>NSDocumentClass</key>
|
<key>NSDocumentClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
|
||||||
</dict>
|
</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>
|
</array>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
#import "CSElectron.h"
|
#import "CSElectron.h"
|
||||||
|
|
||||||
#import "Electron.hpp"
|
#include "Electron.hpp"
|
||||||
#import "CSMachine+Subclassing.h"
|
#import "CSMachine+Subclassing.h"
|
||||||
#import "TapeUEF.hpp"
|
#include "TapeUEF.hpp"
|
||||||
|
|
||||||
@implementation CSElectron {
|
@implementation CSElectron {
|
||||||
Electron::Machine _electron;
|
Electron::Machine _electron;
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
- (void)setKernelROM:(nonnull NSData *)rom;
|
- (void)setKernelROM:(nonnull NSData *)rom;
|
||||||
- (void)setBASICROM:(nonnull NSData *)rom;
|
- (void)setBASICROM:(nonnull NSData *)rom;
|
||||||
- (void)setCharactersROM:(nonnull NSData *)rom;
|
- (void)setCharactersROM:(nonnull NSData *)rom;
|
||||||
|
|
||||||
- (void)setPRG:(nonnull NSData *)prg;
|
- (void)setPRG:(nonnull NSData *)prg;
|
||||||
|
- (BOOL)openTAPAtURL:(nonnull NSURL *)URL;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
#import "CSVic20.h"
|
#import "CSVic20.h"
|
||||||
|
|
||||||
#import "Vic20.hpp"
|
#include "Vic20.hpp"
|
||||||
|
#include "CommodoreTAP.hpp"
|
||||||
|
|
||||||
@implementation CSVic20 {
|
@implementation CSVic20 {
|
||||||
Vic20::Machine _vic20;
|
Vic20::Machine _vic20;
|
||||||
@ -19,8 +20,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)setROM:(nonnull NSData *)rom slot:(Vic20::ROMSlot)slot {
|
- (void)setROM:(nonnull NSData *)rom slot:(Vic20::ROMSlot)slot {
|
||||||
|
@synchronized(self) {
|
||||||
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setKernelROM:(nonnull NSData *)rom {
|
- (void)setKernelROM:(nonnull NSData *)rom {
|
||||||
[self setROM:rom slot:Vic20::ROMSlotKernel];
|
[self setROM:rom slot:Vic20::ROMSlotKernel];
|
||||||
@ -34,9 +37,24 @@
|
|||||||
[self setROM:rom slot:Vic20::ROMSlotCharacters];
|
[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 {
|
- (void)setPRG:(nonnull NSData *)prg {
|
||||||
|
@synchronized(self) {
|
||||||
_vic20.add_prg(prg.length, (const uint8_t *)prg.bytes);
|
_vic20.add_prg(prg.length, (const uint8_t *)prg.bytes);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
|
||||||
static NSDictionary<NSNumber *, NSNumber *> *vicKeysByKeys = @{
|
static NSDictionary<NSNumber *, NSNumber *> *vicKeysByKeys = @{
|
||||||
|
@ -23,7 +23,7 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
|
|||||||
irq_line = new_status;
|
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;
|
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
|
// 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
|
#ifndef Tape_hpp
|
||||||
#define Tape_hpp
|
#define Tape_hpp
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <memory>
|
||||||
|
#include "../../SignalProcessing/Stepper.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
|
|
||||||
class Tape {
|
class Tape {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
struct Time {
|
struct Time {
|
||||||
unsigned int length, clock_rate;
|
unsigned int length, clock_rate;
|
||||||
};
|
};
|
||||||
@ -33,7 +33,31 @@ class Tape {
|
|||||||
virtual void seek(Time seek_time);
|
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 */
|
#endif /* Tape_hpp */
|
||||||
|
Loading…
Reference in New Issue
Block a user