diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 509e83109..c9de1eab2 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -38,6 +38,16 @@ template 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 MOS6522 { { case 0x0: _registers.output[1] = value; - static_cast(this)->set_port_output(1, value, _registers.data_direction[1]); // TODO: handshake + static_cast(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(this)->set_port_output(0, value, _registers.data_direction[0]); // TODO: handshake + static_cast(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 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 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 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 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(this)->get_port_input(port); return (input & ~output_mask) | (output & output_mask); @@ -269,6 +311,11 @@ template 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]; }; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index fbbb1b91e..c65516912 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -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 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 diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 6fe964641..2571b7538 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -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 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 _tape; - struct { - Storage::Tape::Pulse current_pulse; - std::unique_ptr pulse_stepper; - uint32_t time_into_pulse; int minimum_bits_until_full; } _input; struct { diff --git a/Machines/Vic-20/Vic20.cpp b/Machines/Vic-20/Vic20.cpp index 6e1644d9d..d21644edf 100644 --- a/Machines/Vic-20/Vic20.cpp +++ b/Machines/Vic-20/Vic20.cpp @@ -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 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); + } +} diff --git a/Machines/Vic-20/Vic20.hpp b/Machines/Vic-20/Vic20.hpp index 7b888de7d..bf81a4dba 100644 --- a/Machines/Vic-20/Vic20.hpp +++ b/Machines/Vic-20/Vic20.hpp @@ -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, 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, 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, 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, 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, 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 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 _mos6560; UserPortVIA _userPortVIA; KeyboardVIA _keyboardVIA; + Tape _tape; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a7878cfd3..4f4669761 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 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 = ""; }; + 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = ""; }; 4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = ""; }; 4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = ""; }; 4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = ""; }; @@ -900,6 +903,8 @@ 4B69FB451C4D950F00B5F0AA /* libz.tbd */, 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */, 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, + 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */, + 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, ); path = Formats; sourceTree = ""; @@ -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 */, ); diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index ab727c6d4..492b05ef1 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -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": diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index 6339c4276..32e4390fb 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -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") diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 212e82a8e..ade36e2d2 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -88,6 +88,18 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).Vic20Document + + CFBundleTypeExtensions + + tap + + CFBundleTypeName + Vic-20 Tape Image + CFBundleTypeRole + Viewer + NSDocumentClass + $(PRODUCT_MODULE_NAME).Vic20Document + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index c7b82425e..2785ce6d1 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -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; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index 3a8339dd6..5a181e9a9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -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 diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index cd5a0c18f..2ad687707 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -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 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 { diff --git a/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm index 8dd444a05..ac05933f5 100644 --- a/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm +++ b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm @@ -23,7 +23,7 @@ class VanillaVIA: public MOS::MOS6522 { 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; } diff --git a/Storage/Tape/Formats/CommodoreTAP.cpp b/Storage/Tape/Formats/CommodoreTAP.cpp new file mode 100644 index 000000000..503f84b40 --- /dev/null +++ b/Storage/Tape/Formats/CommodoreTAP.cpp @@ -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 +#include + +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; +} diff --git a/Storage/Tape/Formats/CommodoreTAP.hpp b/Storage/Tape/Formats/CommodoreTAP.hpp new file mode 100644 index 000000000..d840516f7 --- /dev/null +++ b/Storage/Tape/Formats/CommodoreTAP.hpp @@ -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 + +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 */ diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index e90165245..b1a9babde 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -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 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(); +} diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 9ee6c1fd0..eecae2862 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -9,13 +9,13 @@ #ifndef Tape_hpp #define Tape_hpp -#include +#include +#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 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 _tape; + struct { + Tape::Pulse current_pulse; + std::unique_ptr pulse_stepper; + uint32_t time_into_pulse; + } _input; +}; + } - #endif /* Tape_hpp */