diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 3df84e99a..145f4d8c3 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -973,130 +973,3 @@ void Speaker::set_is_enabled(bool is_enabled) }); } -/* - Tape -*/ - -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) -{} - -inline void Tape::push_tape_bit(uint16_t bit) -{ - _data_register = (uint16_t)((_data_register >> 1) | (bit << 10)); - - if(_input.minimum_bits_until_full) _input.minimum_bits_until_full--; - if(_input.minimum_bits_until_full == 8) _interrupt_status &= ~Interrupt::ReceiveDataFull; - if(!_input.minimum_bits_until_full) - { - if((_data_register&0x3) == 0x1) - { - _interrupt_status |= Interrupt::ReceiveDataFull; - if(_is_in_input_mode) _input.minimum_bits_until_full = 9; - } - } - - if(_output.bits_remaining_until_empty) _output.bits_remaining_until_empty--; - if(!_output.bits_remaining_until_empty) _interrupt_status |= Interrupt::TransmitDataEmpty; - - if(_data_register == 0x3ff) _interrupt_status |= Interrupt::HighToneDetect; - else _interrupt_status &= ~Interrupt::HighToneDetect; - - evaluate_interrupts(); -} - -inline void Tape::evaluate_interrupts() -{ - if(_last_posted_interrupt_status != _interrupt_status) - { - _last_posted_interrupt_status = _interrupt_status; - if(_delegate) _delegate->tape_did_change_interrupt_status(this); - } -} - -inline void Tape::clear_interrupts(uint8_t interrupts) -{ - _interrupt_status &= ~interrupts; - evaluate_interrupts(); -} - -inline void Tape::set_is_in_input_mode(bool is_in_input_mode) -{ - _is_in_input_mode = is_in_input_mode; -} - -inline void Tape::set_counter(uint8_t value) -{ - _output.cycles_into_pulse = 0; - _output.bits_remaining_until_empty = 0; -} - -inline void Tape::set_data_register(uint8_t value) -{ - _data_register = (uint16_t)((value << 2) | 1); - _output.bits_remaining_until_empty = 9; -} - -inline uint8_t Tape::get_data_register() -{ - return (uint8_t)(_data_register >> 2); -} - -inline void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) -{ - _crossings[0] = _crossings[1]; - _crossings[1] = _crossings[2]; - _crossings[2] = _crossings[3]; - - _crossings[3] = Tape::Unrecognised; - if(pulse.type != Storage::Tape::Tape::Pulse::Zero) - { - 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; - } - - if(_crossings[0] == Tape::Long && _crossings[1] == Tape::Long) - { - push_tape_bit(0); - _crossings[0] = _crossings[1] = Tape::Recognised; - } - else - { - if(_crossings[0] == Tape::Short && _crossings[1] == Tape::Short && _crossings[2] == Tape::Short && _crossings[3] == Tape::Short) - { - push_tape_bit(1); - _crossings[0] = _crossings[1] = - _crossings[2] = _crossings[3] = Tape::Recognised; - } - } -} - -inline void Tape::run_for_cycles(unsigned int number_of_cycles) -{ - if(_is_enabled) - { - if(_is_in_input_mode) - { - if(_is_running) - { - TapePlayer::run_for_cycles((int)number_of_cycles); - } - } - else - { - _output.cycles_into_pulse += number_of_cycles; - while(_output.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something - { // that divides the 125,000Hz clock that the sound divider runs off. - _output.cycles_into_pulse -= 1664; - push_tape_bit(1); - } - } - } -} diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 15c6b7dfc..31556a037 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -16,6 +16,8 @@ #include "../CRTMachine.hpp" #include "../Typer.hpp" #include "Plus3.hpp" +#include "Tape.hpp" +#include "Interrupts.hpp" #include #include @@ -35,15 +37,6 @@ enum ROMSlot: uint8_t { ROMSlotOS, ROMSlotDFS, ROMSlotADFS }; -enum Interrupt: uint8_t { - PowerOnReset = 0x02, - DisplayEnd = 0x04, - RealTimeClock = 0x08, - ReceiveDataFull = 0x10, - TransmitDataEmpty = 0x20, - HighToneDetect = 0x40 -}; - enum Key: uint16_t { KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01, KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01, @@ -65,57 +58,6 @@ enum Key: uint16_t { TerminateSequence = 0xffff, NotMapped = 0xfffe, }; -class Tape: public Storage::Tape::TapePlayer { - public: - 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); - inline void set_counter(uint8_t value); - - inline uint8_t get_interrupt_status() { return _interrupt_status; } - inline void clear_interrupts(uint8_t interrupts); - - class Delegate { - public: - virtual void tape_did_change_interrupt_status(Tape *tape) = 0; - }; - inline void set_delegate(Delegate *delegate) { _delegate = delegate; } - - 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::Tape::Pulse pulse); - inline void push_tape_bit(uint16_t bit); - inline void get_next_tape_pulse(); - - struct { - int minimum_bits_until_full; - } _input; - struct { - unsigned int cycles_into_pulse; - unsigned int bits_remaining_until_empty; - } _output; - - bool _is_running; - bool _is_enabled; - bool _is_in_input_mode; - - inline void evaluate_interrupts(); - uint16_t _data_register; - - uint8_t _interrupt_status, _last_posted_interrupt_status; - Delegate *_delegate; - - enum { - Long, Short, Unrecognised, Recognised - } _crossings[4]; -}; - class Speaker: public ::Outputs::Filter { public: void set_divider(uint8_t divider); diff --git a/Machines/Electron/Interrupts.hpp b/Machines/Electron/Interrupts.hpp new file mode 100644 index 000000000..776667d79 --- /dev/null +++ b/Machines/Electron/Interrupts.hpp @@ -0,0 +1,27 @@ +// +// Interrupts.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Interrupts_h +#define Interrupts_h + +#include + +namespace Electron { + +enum Interrupt: uint8_t { + PowerOnReset = 0x02, + DisplayEnd = 0x04, + RealTimeClock = 0x08, + ReceiveDataFull = 0x10, + TransmitDataEmpty = 0x20, + HighToneDetect = 0x40 +}; + +} + +#endif /* Interrupts_h */ diff --git a/Machines/Electron/Tape.cpp b/Machines/Electron/Tape.cpp new file mode 100644 index 000000000..94ed106b2 --- /dev/null +++ b/Machines/Electron/Tape.cpp @@ -0,0 +1,135 @@ +// +// Tape.cpp +// Clock Signal +// +// Created by Thomas Harte on 03/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Tape.hpp" + +using namespace Electron; + +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::push_tape_bit(uint16_t bit) +{ + data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10)); + + if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--; + if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull; + if(!input_.minimum_bits_until_full) + { + if((data_register_&0x3) == 0x1) + { + interrupt_status_ |= Interrupt::ReceiveDataFull; + if(is_in_input_mode_) input_.minimum_bits_until_full = 9; + } + } + + if(output_.bits_remaining_until_empty) output_.bits_remaining_until_empty--; + if(!output_.bits_remaining_until_empty) interrupt_status_ |= Interrupt::TransmitDataEmpty; + + if(data_register_ == 0x3ff) interrupt_status_ |= Interrupt::HighToneDetect; + else interrupt_status_ &= ~Interrupt::HighToneDetect; + + evaluate_interrupts(); +} + +void Tape::evaluate_interrupts() +{ + if(last_posted_interrupt_status_ != interrupt_status_) + { + last_posted_interrupt_status_ = interrupt_status_; + if(delegate_) delegate_->tape_did_change_interrupt_status(this); + } +} + +void Tape::clear_interrupts(uint8_t interrupts) +{ + interrupt_status_ &= ~interrupts; + evaluate_interrupts(); +} + +void Tape::set_is_in_input_mode(bool is_in_input_mode) +{ + is_in_input_mode_ = is_in_input_mode; +} + +void Tape::set_counter(uint8_t value) +{ + output_.cycles_into_pulse = 0; + output_.bits_remaining_until_empty = 0; +} + +void Tape::set_data_register(uint8_t value) +{ + data_register_ = (uint16_t)((value << 2) | 1); + output_.bits_remaining_until_empty = 9; +} + +uint8_t Tape::get_data_register() +{ + return (uint8_t)(data_register_ >> 2); +} + +void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) +{ + crossings_[0] = crossings_[1]; + crossings_[1] = crossings_[2]; + crossings_[2] = crossings_[3]; + + crossings_[3] = Tape::Unrecognised; + if(pulse.type != Storage::Tape::Tape::Pulse::Zero) + { + 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; + } + + if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) + { + push_tape_bit(0); + crossings_[0] = crossings_[1] = Tape::Recognised; + } + else + { + if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) + { + push_tape_bit(1); + crossings_[0] = crossings_[1] = + crossings_[2] = crossings_[3] = Tape::Recognised; + } + } +} + +void Tape::run_for_cycles(unsigned int number_of_cycles) +{ + if(is_enabled_) + { + if(is_in_input_mode_) + { + if(is_running_) + { + TapePlayer::run_for_cycles((int)number_of_cycles); + } + } + else + { + output_.cycles_into_pulse += number_of_cycles; + while(output_.cycles_into_pulse > 1664) // 1664 = the closest you can get to 1200 baud if you're looking for something + { // that divides the 125,000Hz clock that the sound divider runs off. + output_.cycles_into_pulse -= 1664; + push_tape_bit(1); + } + } + } +} diff --git a/Machines/Electron/Tape.hpp b/Machines/Electron/Tape.hpp new file mode 100644 index 000000000..a796e7824 --- /dev/null +++ b/Machines/Electron/Tape.hpp @@ -0,0 +1,72 @@ +// +// Tape.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/12/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Electron_Tape_h +#define Electron_Tape_h + +#include "../../Storage/Tape/Tape.hpp" +#include "Interrupts.hpp" + +#include + +namespace Electron { + +class Tape: public Storage::Tape::TapePlayer { + public: + Tape(); + + void run_for_cycles(unsigned int number_of_cycles); + + uint8_t get_data_register(); + void set_data_register(uint8_t value); + void set_counter(uint8_t value); + + inline uint8_t get_interrupt_status() { return interrupt_status_; } + void clear_interrupts(uint8_t interrupts); + + class Delegate { + public: + virtual void tape_did_change_interrupt_status(Tape *tape) = 0; + }; + inline void set_delegate(Delegate *delegate) { delegate_ = delegate; } + + inline void set_is_running(bool is_running) { is_running_ = is_running; } + inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; } + void set_is_in_input_mode(bool is_in_input_mode); + + private: + void process_input_pulse(Storage::Tape::Tape::Pulse pulse); + inline void push_tape_bit(uint16_t bit); + inline void get_next_tape_pulse(); + + struct { + int minimum_bits_until_full; + } input_; + struct { + unsigned int cycles_into_pulse; + unsigned int bits_remaining_until_empty; + } output_; + + bool is_running_; + bool is_enabled_; + bool is_in_input_mode_; + + inline void evaluate_interrupts(); + uint16_t data_register_; + + uint8_t interrupt_status_, last_posted_interrupt_status_; + Delegate *delegate_; + + enum { + Long, Short, Unrecognised, Recognised + } crossings_[4]; +}; + +} + +#endif /* Electron_Tape_h */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 11eccdc41..b9cb4ff94 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -383,6 +383,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD69F921D98760000243FE1 /* AcornADF.cpp */; }; 4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; }; + 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; }; 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; }; 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; }; @@ -892,6 +893,9 @@ 4BD69F931D98760000243FE1 /* AcornADF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AcornADF.hpp; sourceTree = ""; }; 4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = ""; }; 4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = ""; }; + 4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = ""; }; + 4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = ""; }; + 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = ""; }; 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = ""; }; 4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = ""; }; 4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = ""; }; @@ -1052,10 +1056,13 @@ isa = PBXGroup; children = ( 4B2E2D9B1C3A070400138695 /* Electron.cpp */, - 4B2E2D9C1C3A070400138695 /* Electron.hpp */, 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */, - 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, + 4BEA525D1DF33323007E74F2 /* Tape.cpp */, 4BC8A62B1DCE60E000DAC693 /* Typer.cpp */, + 4B2E2D9C1C3A070400138695 /* Electron.hpp */, + 4BEA52601DF3343A007E74F2 /* Interrupts.hpp */, + 4B30512F1D98ACC600B4FED8 /* Plus3.hpp */, + 4BEA525F1DF333D8007E74F2 /* Tape.hpp */, ); name = Electron; sourceTree = ""; @@ -2314,6 +2321,7 @@ 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */, + 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, 4BC8A62D1DCE60E000DAC693 /* Typer.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */,