1
0
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:
Thomas Harte 2016-06-26 22:02:08 -04:00 committed by GitHub
commit 466ddff777
17 changed files with 416 additions and 81 deletions

View File

@ -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];
};

View File

@ -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

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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;
};
}

View File

@ -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 */,
);

View File

@ -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":

View File

@ -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")

View File

@ -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>

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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;
}

View 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;
}

View 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 */

View File

@ -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();
}

View File

@ -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 */