From e65cd4cf0623383e3118a01b7d46674a9988f9e7 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 19 Jan 2016 22:05:34 -0500 Subject: [PATCH] Some data is marginally reaching the CPU from the tape. --- Machines/Electron/Electron.cpp | 82 ++++++++++++++++++- Machines/Electron/Electron.hpp | 45 ++++++++-- .../Documents/ElectronDocument.swift | 17 ++-- .../Mac/Clock Signal/Wrappers/CSElectron.h | 2 +- .../Mac/Clock Signal/Wrappers/CSElectron.mm | 11 ++- Outputs/Speaker.hpp | 2 +- SignalProcessing/Stepper.hpp | 28 +++++-- Storage/Tape/Formats/TapeUEF.cpp | 28 ++++--- Storage/Tape/Formats/TapeUEF.hpp | 6 +- 9 files changed, 184 insertions(+), 37 deletions(-) diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index ee5d4bf44..290bee98e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -23,7 +23,8 @@ Machine::Machine() : _displayOutputPosition(0), _audioOutputPosition(0), _audioOutputPositionError(0), - _currentOutputLine(0) + _currentOutputLine(0), + _tape({.is_running = false, .dataRegister = 0}) { memset(_keyStates, 0, sizeof(_keyStates)); memset(_palette, 0xf, sizeof(_palette)); @@ -100,6 +101,10 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _startScreenAddress = (_startScreenAddress & 0x01ff) | (uint16_t)(((*value) & 0x3f) << 9); break; case 0x4: + if(isReadOperation(operation)) + { + *value = (uint8_t)_tape.dataRegister; + } printf("Cassette\n"); break; case 0x5: @@ -170,6 +175,7 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin } // TODO: tape mode, tape motor, caps lock LED + _tape.is_running = ((*value)&0x40) ? true : false; } break; default: @@ -270,12 +276,86 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin _audioOutputPosition = 0; _currentOutputLine = 0; break; + } + if(_tape.is_running && _tape.media != nullptr) + { + _tape.time_into_pulse += (unsigned int)_tape.pulseStepper->step(); + if(_tape.time_into_pulse == _tape.currentPulse.length.length) + { + get_next_tape_pulse(); + + _tape.crossings[0] = _tape.crossings[1]; + _tape.crossings[1] = _tape.crossings[2]; + _tape.crossings[2] = _tape.crossings[3]; + + _tape.crossings[3] = Tape::Unrecognised; + if(_tape.currentPulse.type != Storage::Tape::Pulse::Zero) + { + float pulse_length = (float)_tape.currentPulse.length.length / (float)_tape.currentPulse.length.clock_rate; + if(pulse_length > 0.4 / 2400.0 && pulse_length < 0.6 / 2400.0) _tape.crossings[3] = Tape::Short; + if(pulse_length > 0.4 / 1200.0 && pulse_length < 0.6 / 1200.0) _tape.crossings[3] = Tape::Long; + } + + if(_tape.crossings[0] == Tape::Long && _tape.crossings[1] == Tape::Long) + { + push_tape_bit(0); + _tape.crossings[1] = Tape::Unrecognised; + } + else + { + if(_tape.crossings[0] == Tape::Short && _tape.crossings[1] == Tape::Short && _tape.crossings[2] == Tape::Short && _tape.crossings[3] == Tape::Short) + { + push_tape_bit(1); + _tape.crossings[3] = Tape::Unrecognised; + } + } + } } return cycles; } +inline void Machine::get_next_tape_pulse() +{ + _tape.time_into_pulse = 0; + _tape.currentPulse = _tape.media->get_next_pulse(); + if(_tape.pulseStepper == nullptr || _tape.currentPulse.length.clock_rate != _tape.pulseStepper->get_output_rate()) + { + _tape.pulseStepper = std::shared_ptr(new SignalProcessing::Stepper(_tape.currentPulse.length.clock_rate, 2000000)); + } +} + +inline void Machine::push_tape_bit(uint16_t bit) +{ + _tape.dataRegister = (uint16_t)((_tape.dataRegister >> 1) | (bit << 9)); + + if(_tape.dataRegister == 0x3ff) + _interruptStatus |= InterruptHighToneDetect; + else + _interruptStatus &= !InterruptHighToneDetect; + + if(_tape.bits_since_start > 0) + { + _tape.bits_since_start--; + + if(_tape.bits_since_start == 0) + { + printf("%02x [%c]\n", _tape.dataRegister&0xff, _tape.dataRegister&0x7f); + _interruptStatus |= InterruptTransmitDataEmpty; + } + } + + if(!bit && !_tape.bits_since_start) + { + _tape.bits_since_start = 10; + } + + printf("."); + + evaluate_interrupts(); +} + void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) { uint8_t *target = nullptr; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 24041cee2..26d943619 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -12,6 +12,7 @@ #include "../../Processors/6502/CPU6502.hpp" #include "../../Outputs/CRT.hpp" #include "../../Outputs/Speaker.hpp" +#include "../../Storage/Tape/Tape.hpp" #include #include "Atari2600Inputs.h" @@ -67,6 +68,8 @@ class Machine: public CPU6502::Processor { unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void set_tape(std::shared_ptr tape) { _tape.media = tape; get_next_tape_pulse(); } + void set_key_state(Key key, bool isPressed); Outputs::CRT *get_crt() { return _crt; } @@ -74,27 +77,53 @@ class Machine: public CPU6502::Processor { const char *get_signal_decoder(); private: + + inline void update_display(); + inline void update_audio(); + inline void signal_interrupt(Interrupt interrupt); + inline void evaluate_interrupts(); + + inline void get_next_tape_pulse(); + inline void push_tape_bit(uint16_t bit); + + // Things that directly constitute the memory map. uint8_t _roms[16][16384]; uint8_t _os[16384], _ram[32768]; + + // Things affected by registers, explicitly or otherwise. uint8_t _interruptStatus, _interruptControl; uint8_t _palette[16]; uint8_t _keyStates[14]; ROMSlot _activeRom; uint8_t _screenMode; uint16_t _screenModeBaseAddress; + uint16_t _startScreenAddress; - Outputs::CRT *_crt; - + // Counters related to simultaneous subsystems; int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError; - uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress; + // Display generation. + uint16_t _startLineAddress, _currentScreenAddress; int _currentOutputLine; uint8_t *_currentLine; - inline void update_display(); - inline void update_audio(); - inline void signal_interrupt(Interrupt interrupt); - inline void evaluate_interrupts(); + // Tape. + struct Tape { + std::shared_ptr media; + Storage::Tape::Pulse currentPulse; + std::shared_ptr pulseStepper; + uint32_t time_into_pulse; + bool is_running; + uint16_t dataRegister; + int bits_since_start; + + enum { + Long, Short, Unrecognised + } crossings[4]; + } _tape; + + // Outputs. + Outputs::CRT *_crt; class Speaker: public ::Outputs::Filter { public: @@ -115,8 +144,6 @@ class Machine: public CPU6502::Processor { bool _is_enabled; int16_t _output_level; -// FILE *rawStream; - } _speaker; }; diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index e735c1a26..2e2f31dc4 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -38,13 +38,18 @@ class ElectronDocument: MachineDocument { override func readFromURL(url: NSURL, ofType typeName: String) throws { print(url) print(typeName) - switch typeName { - case "Electron/BBC Tape Image": // this somewhat implies I've misunderstood the info.plist, doesn't it? - electron.openUEFAtURL(url) - default: - let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) - try self.readFromFileWrapper(fileWrapper, ofType: typeName) + + if let pathExtension = url.pathExtension { + switch pathExtension.lowercaseString { + case "uef": + electron.openUEFAtURL(url) + return + default: break; + } } + + let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) + try self.readFromFileWrapper(fileWrapper, ofType: typeName) } override func readFromData(data: NSData, ofType typeName: String) throws { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 306e91ef7..474c6f726 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -14,7 +14,7 @@ - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; -- (void)openUEFAtURL:(NSURL *)URL; +- (BOOL)openUEFAtURL:(NSURL *)URL; - (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm index eaf1c4cac..e04ce5154 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.mm @@ -48,9 +48,14 @@ _electron.get_crt()->set_delegate(delegate); } -- (void)openUEFAtURL:(NSURL *)URL { - Storage::UEF tape([URL fileSystemRepresentation]); -// _electron. +- (BOOL)openUEFAtURL:(NSURL *)URL { + try { + std::shared_ptr tape(new Storage::UEF([URL fileSystemRepresentation])); + _electron.set_tape(tape); + return YES; + } catch(int exception) { + return NO; + } } - (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 5b42e651a..00faf36b3 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -107,7 +107,7 @@ template class Filter: public Speaker { } // determine how many source samples to step - uint64_t steps = _stepper->update(); + uint64_t steps = _stepper->step(); if(steps > 1) static_cast(this)->skip_samples((unsigned int)(steps-1)); input_cycles -= steps; diff --git a/SignalProcessing/Stepper.hpp b/SignalProcessing/Stepper.hpp index bb4ae754d..53e046354 100644 --- a/SignalProcessing/Stepper.hpp +++ b/SignalProcessing/Stepper.hpp @@ -16,14 +16,21 @@ namespace SignalProcessing { class Stepper { public: - Stepper(uint64_t output_rate, uint64_t update_rate) + Stepper() { - whole_step_ = output_rate / update_rate; - adjustment_up_ = (int64_t)(output_rate % update_rate) << 1; - adjustment_down_ = (int64_t)update_rate << 1; + Stepper(1, 1); } - inline uint64_t update() + Stepper(uint64_t output_rate, uint64_t input_rate) + { + input_rate_ = input_rate; + output_rate_ = output_rate; + whole_step_ = output_rate / input_rate; + adjustment_up_ = (int64_t)(output_rate % input_rate) << 1; + adjustment_down_ = (int64_t)input_rate << 1; + } + + inline uint64_t step() { uint64_t update = whole_step_; accumulated_error_ += adjustment_up_; @@ -35,10 +42,21 @@ class Stepper return update; } + inline uint64_t get_output_rate() + { + return output_rate_; + } + + inline uint64_t get_input_rate() + { + return input_rate_; + } + private: uint64_t whole_step_; int64_t adjustment_up_, adjustment_down_; int64_t accumulated_error_; + uint64_t input_rate_, output_rate_; }; } diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 4df4dffc3..ba6d4b6de 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -19,7 +19,7 @@ Storage::UEF::UEF(const char *file_name) : int bytes_read = gzread(_file, identifier, 10); if(bytes_read < 10 || strcmp(identifier, "UEF File!")) { - // exception? + throw ErrorNotUEF; } int minor, major; @@ -28,7 +28,7 @@ Storage::UEF::UEF(const char *file_name) : if(major > 0 || minor > 10 || major < 0 || minor < 0) { - // exception? + throw ErrorNotUEF; } find_next_tape_chunk(); @@ -53,8 +53,6 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() find_next_tape_chunk(); } - next_pulse.length.clock_rate = _time_base * 2; - switch(_chunk_id) { case 0x0100: case 0x0102: @@ -70,12 +68,14 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = _current_bit ? 1 : 2; + next_pulse.length.clock_rate = _time_base * 4; _bit_position = (_bit_position+1)&(_current_bit ? 3 : 1); } break; case 0x0110: next_pulse.type = (_bit_position&1) ? Pulse::High : Pulse::Low; next_pulse.length.length = 1; + next_pulse.length.clock_rate = _time_base * 4; _bit_position ^= 1; if(!_bit_position) _chunk_position++; @@ -84,7 +84,7 @@ Storage::Tape::Pulse Storage::UEF::get_next_pulse() case 0x0112: case 0x0116: next_pulse.type = Pulse::Zero; - next_pulse.length.length = _tone_length; + next_pulse.length = _chunk_duration; _chunk_position++; break; } @@ -120,16 +120,24 @@ void Storage::UEF::find_next_tape_chunk() switch(_chunk_id) { case 0x0100: case 0x0102: // implicit and explicit bit patterns - case 0x0112: case 0x0116: // gaps + return; + + case 0x0112: + _chunk_duration.length = (uint16_t)gzgetc(_file); + _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); + _chunk_duration.clock_rate = _time_base; + return; + + case 0x0116: // gaps return; case 0x0110: // carrier tone - _tone_length = (uint16_t)gzgetc(_file); - _tone_length |= (uint16_t)(gzgetc(_file) << 8); + _chunk_duration.length = (uint16_t)gzgetc(_file); + _chunk_duration.length |= (uint16_t)(gzgetc(_file) << 8); gzseek(_file, _chunk_length - 2, SEEK_CUR); return; case 0x0111: // carrier tone with dummy byte - // TODO: read length + // TODO: read lengths return; case 0x0114: // security cycles // TODO: read number, Ps and Ws @@ -151,7 +159,7 @@ bool Storage::UEF::chunk_is_finished() { case 0x0100: return (_chunk_position / 10) == _chunk_length; case 0x0102: return (_chunk_position / 8) == _chunk_length; - case 0x0110: return _chunk_position == _tone_length; + case 0x0110: return _chunk_position == _chunk_duration.length; case 0x0112: case 0x0116: return _chunk_position ? true : false; diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 586647d22..e04ee7092 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -23,6 +23,10 @@ class UEF : public Tape { Pulse get_next_pulse(); void reset(); + enum { + ErrorNotUEF + }; + private: gzFile _file; unsigned int _time_base; @@ -36,7 +40,7 @@ class UEF : public Tape { bool _current_bit; uint32_t _bit_position; - uint16_t _tone_length; + Time _chunk_duration; void find_next_tape_chunk(); bool get_next_bit();