diff --git a/Storage/Tape/Parsers/Acorn.cpp b/Storage/Tape/Parsers/Acorn.cpp index 1f7e2d8c7..51de84ca7 100644 --- a/Storage/Tape/Parsers/Acorn.cpp +++ b/Storage/Tape/Parsers/Acorn.cpp @@ -10,8 +10,12 @@ using namespace Storage::Tape::Acorn; +namespace { +const int PLLClockRate = 1920000; +} + Parser::Parser() : - ::Storage::Tape::Parser(), + ::Storage::Tape::PLLParser(PLLClockRate, PLLClockRate / 4800, 100), crc_(0x1021, 0x0000) {} int Parser::get_next_bit(const std::shared_ptr &tape) { @@ -52,38 +56,14 @@ int Parser::get_next_word(const std::shared_ptr &tape) { void Parser::reset_crc() { crc_.reset(); } uint16_t Parser::get_crc() { return crc_.get_value(); } -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) { - switch(pulse.type) { - default: break; - case Storage::Tape::Tape::Pulse::High: - case Storage::Tape::Tape::Pulse::Low: - float pulse_length = pulse.length.get_float(); - if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 1200.0) { - push_wave(pulse_length > 1.0 / 3000.0 ? WaveType::Long : WaveType::Short); return; - } - break; - } +bool Parser::did_update_shifter(int new_value, int length) { + if(length < 4) return false; - push_wave(WaveType::Unrecognised); -} - -void Parser::inspect_waves(const std::vector &waves) { - if(waves.size() < 2) return; - - if(waves[0] == WaveType::Long && waves[1] == WaveType::Long) { - push_symbol(SymbolType::Zero, 2); - return; - } - - if(waves.size() < 4) return; - - if( waves[0] == WaveType::Short && - waves[1] == WaveType::Short && - waves[2] == WaveType::Short && - waves[3] == WaveType::Short) { - push_symbol(SymbolType::One, 4); - return; - } - - remove_waves(1); + switch(new_value & 0xf) { + case 0x5: printf("0"); push_symbol(SymbolType::Zero); return true; + case 0xf: printf("1"); push_symbol(SymbolType::One); return true; + default: + printf("?"); + return false; + } } diff --git a/Storage/Tape/Parsers/Acorn.hpp b/Storage/Tape/Parsers/Acorn.hpp index 71f3ecccc..734daf19c 100644 --- a/Storage/Tape/Parsers/Acorn.hpp +++ b/Storage/Tape/Parsers/Acorn.hpp @@ -11,20 +11,17 @@ #include "TapeParser.hpp" #include "../../../NumberTheory/CRC.hpp" +#include "../../Disk/DigitalPhaseLockedLoop.hpp" namespace Storage { namespace Tape { namespace Acorn { -enum class WaveType { - Short, Long, Unrecognised -}; - enum class SymbolType { One, Zero }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::PLLParser { public: Parser(); @@ -35,9 +32,9 @@ class Parser: public Storage::Tape::Parser { void reset_crc(); uint16_t get_crc(); + bool did_update_shifter(int new_value, int length); + private: - void process_pulse(Storage::Tape::Tape::Pulse pulse); - void inspect_waves(const std::vector &waves); NumberTheory::CRC16 crc_; }; diff --git a/Storage/Tape/Parsers/Commodore.cpp b/Storage/Tape/Parsers/Commodore.cpp index 628ad823d..85f8fb659 100644 --- a/Storage/Tape/Parsers/Commodore.cpp +++ b/Storage/Tape/Parsers/Commodore.cpp @@ -12,7 +12,7 @@ using namespace Storage::Tape::Commodore; Parser::Parser() : - Storage::Tape::Parser(), + Storage::Tape::PulseClassificationParser(), wave_period_(0.0f), previous_was_high_(false), parity_byte_(0) {} @@ -262,7 +262,7 @@ uint16_t Parser::get_next_short(const std::shared_ptr &tape indicates a high to low transition, inspects the time since the last transition, to produce a long, medium, short or unrecognised wave period. */ -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) +void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { // The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of: // short: 182µs => 0.000364s cycle diff --git a/Storage/Tape/Parsers/Commodore.hpp b/Storage/Tape/Parsers/Commodore.hpp index 5b36a83c1..48f5fd38d 100644 --- a/Storage/Tape/Parsers/Commodore.hpp +++ b/Storage/Tape/Parsers/Commodore.hpp @@ -56,7 +56,7 @@ struct Data { bool duplicate_matched; }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::PulseClassificationParser { public: Parser(); @@ -126,7 +126,7 @@ class Parser: public Storage::Tape::Parser { indicates a high to low transition, inspects the time since the last transition, to produce a long, medium, short or unrecognised wave period. */ - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); bool previous_was_high_; float wave_period_; diff --git a/Storage/Tape/Parsers/Oric.cpp b/Storage/Tape/Parsers/Oric.cpp index 8a2f10a72..1c64dc687 100644 --- a/Storage/Tape/Parsers/Oric.cpp +++ b/Storage/Tape/Parsers/Oric.cpp @@ -45,7 +45,7 @@ bool Parser::sync_and_get_encoding_speed(const std::shared_ptr { +class Parser: public Storage::Tape::PulseClassificationParser { public: int get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding); bool sync_and_get_encoding_speed(const std::shared_ptr &tape); private: - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); void inspect_waves(const std::vector &waves); enum DetectionMode { diff --git a/Storage/Tape/Parsers/TapeParser.hpp b/Storage/Tape/Parsers/TapeParser.hpp index 2ae7b2d13..28b5e8e68 100644 --- a/Storage/Tape/Parsers/TapeParser.hpp +++ b/Storage/Tape/Parsers/TapeParser.hpp @@ -10,6 +10,7 @@ #define TapeParser_hpp #include "../Tape.hpp" +#include "../../Disk/DigitalPhaseLockedLoop.hpp" #include #include @@ -18,18 +19,9 @@ namespace Storage { namespace Tape { -/*! - A partly-abstract base class to help in the authorship of tape format parsers; - provides hooks for pulse classification from pulses to waves and for symbol identification from - waves. - - Very optional, not intended to box in the approaches taken for analysis. -*/ -template class Parser { +template class Parser { public: - /// Instantiates a new parser with the supplied @c tape. Parser() : has_next_symbol_(false), error_flag_(false) {} - /// Resets the error flag. void reset_error_flag() { error_flag_ = false; } /// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise. @@ -59,12 +51,18 @@ template class Parser { next_symbol_ = symbol; } - /*! - Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave - or to take no action. + @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. */ - virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0; + bool is_at_end(const std::shared_ptr &tape) { + return tape->is_at_end() && !has_next_symbol_; + } + + protected: + /*! + Should be implemented by subclasses. Consumes @c pulse. + */ + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; /*! An optional implementation for subclasses; called to announce that the tape has ended: that @@ -72,7 +70,50 @@ template class Parser { */ virtual void mark_end() {} + /*! + Sets @c symbol as the newly-recognised symbol. + */ + void push_symbol(SymbolType symbol) { + has_next_symbol_ = true; + next_symbol_ = symbol; + } + + void set_error_flag() { + error_flag_ = true; + } + + bool error_flag_; + SymbolType next_symbol_; + bool has_next_symbol_; +}; + +/*! + A partly-abstract base class to help in the authorship of tape format parsers; + provides hooks for receipt of pulses, which are intended to be classified into waves, + and for symbol identification from waves. + + Very optional, not intended to box in the approaches taken for analysis. See also + the PLLParser. +*/ +template class PulseClassificationParser: public Parser { + public: + virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; + + /* + process_pulse should either call @c push_wave or to take no action. + */ + protected: + /*! + Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. + + Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves + waves together represent @c symbol. + */ + void push_symbol(SymbolType symbol, int number_of_waves) { + Parser::push_symbol(symbol); + remove_waves(number_of_waves); + } /*! Adds @c wave to the back of the list of recognised waves and calls @c inspect_waves to check for a new symbol. @@ -94,32 +135,7 @@ template class Parser { wave_queue_.erase(wave_queue_.begin(), wave_queue_.begin()+number_of_waves); } - /*! - Sets @c symbol as the newly-recognised symbol and removes @c nunber_of_waves waves from the front of the list. - - Expected to be called by subclasses from @c process_pulse when it recognises that the first @c number_of_waves - waves together represent @c symbol. - */ - void push_symbol(SymbolType symbol, int number_of_waves) { - has_next_symbol_ = true; - next_symbol_ = symbol; - remove_waves(number_of_waves); - } - - void set_error_flag() { - error_flag_ = true; - } - - /*! - @returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise. - */ - bool is_at_end(const std::shared_ptr &tape) { - return tape->is_at_end() && wave_queue_.empty() && !has_next_symbol_; - } - private: - bool error_flag_; - /*! Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries @@ -129,8 +145,68 @@ template class Parser { virtual void inspect_waves(const std::vector &waves) = 0; std::vector wave_queue_; - SymbolType next_symbol_; - bool has_next_symbol_; +}; + +/** + A partly-abstract base class to help in the authorship of tape format parsers; + provides a phase-locked loop, an overridable hook for deriving when to push + bits to the PLL, a shift register populated by the PLL and a shout-out whenever + the shift register changes. Subclasses are hence intended to push symbols. +*/ +template class PLLParser: + public Storage::DigitalPhaseLockedLoop::Delegate, + public Parser { + public: + /// Instantiates a new parser with the supplied @c tape. + PLLParser(int clock_rate, int clocks_per_bit, int tolerance) : + clock_rate_(clock_rate), + pll_(clocks_per_bit, tolerance, 3), + input_bit_counter_(0), + input_pattern_(0), + was_high_(false) { + pll_.set_delegate(this); + } + + protected: + + /*! + The default implementation marks transitions between high and not high with + PLL-pushed bits. + */ + void process_pulse(const Storage::Tape::Tape::Pulse &pulse) { + pll_.run_for_cycles((int)((float)clock_rate_ * pulse.length.get_float())); + + bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; + if(is_high != was_high_) { + pll_.add_pulse(); + } + was_high_ = is_high; + } + + /*! + Communicates to a subclass that a shift register shifting from left to right and + clocked by the PLL now has value @c value and its running length total is now at @c length bits. + + If this function returns true then the running length total is zeroed. + + So expected usage is: if there are at least enough bits available to make a meaningful + value, inspect the shifter. If the low bits of the shifter make a meaningful value, + call push_symbol and return true. Otherwise return false. + */ + virtual bool did_update_shifter(int new_value, int length) = 0; + + void digital_phase_locked_loop_output_bit(int value) { + input_pattern_ = (input_pattern_ << 1) | value; + input_bit_counter_++; + if(did_update_shifter(input_pattern_, input_bit_counter_)) input_bit_counter_ = 0; + } + + private: + Storage::DigitalPhaseLockedLoop pll_; + int clock_rate_; + int input_pattern_; + int input_bit_counter_; + bool was_high_; }; } diff --git a/Storage/Tape/Parsers/ZX8081.cpp b/Storage/Tape/Parsers/ZX8081.cpp index f4678dc6f..960992dd5 100644 --- a/Storage/Tape/Parsers/ZX8081.cpp +++ b/Storage/Tape/Parsers/ZX8081.cpp @@ -12,7 +12,7 @@ using namespace Storage::Tape::ZX8081; Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {} -void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) { +void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { pulse_time_ += pulse.length; bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High; diff --git a/Storage/Tape/Parsers/ZX8081.hpp b/Storage/Tape/Parsers/ZX8081.hpp index 8fc5873fb..32a607163 100644 --- a/Storage/Tape/Parsers/ZX8081.hpp +++ b/Storage/Tape/Parsers/ZX8081.hpp @@ -29,7 +29,7 @@ enum class SymbolType { One, Zero, FileGap, Unrecognised }; -class Parser: public Storage::Tape::Parser { +class Parser: public Storage::Tape::PulseClassificationParser { public: Parser(); @@ -50,7 +50,7 @@ class Parser: public Storage::Tape::Parser { Time pulse_time_; void post_pulse(); - void process_pulse(Storage::Tape::Tape::Pulse pulse); + void process_pulse(const Storage::Tape::Tape::Pulse &pulse); void mark_end(); void inspect_waves(const std::vector &waves);