mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
Merge pull request #150 from TomHarte/PLLParsing
Introduces PLL-driven tape parsing of Acorn tapes
This commit is contained in:
commit
5a6b7219b9
@ -17,7 +17,9 @@ Tape::Tape() :
|
||||
delegate_(nullptr),
|
||||
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
|
||||
last_posted_interrupt_status_(0),
|
||||
interrupt_status_(0) {}
|
||||
interrupt_status_(0) {
|
||||
shifter_.set_delegate(this);
|
||||
}
|
||||
|
||||
void Tape::push_tape_bit(uint16_t bit) {
|
||||
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
|
||||
@ -71,30 +73,11 @@ uint8_t Tape::get_data_register() {
|
||||
}
|
||||
|
||||
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
|
||||
crossings_[0] = crossings_[1];
|
||||
crossings_[1] = crossings_[2];
|
||||
crossings_[2] = crossings_[3];
|
||||
shifter_.process_pulse(pulse);
|
||||
}
|
||||
|
||||
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 / 1200.0) {
|
||||
crossings_[3] = pulse_length > 1.0 / 3000.0 ? Tape::Long : Tape::Short;
|
||||
}
|
||||
// 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::acorn_shifter_output_bit(int value) {
|
||||
push_tape_bit((uint16_t)value);
|
||||
}
|
||||
|
||||
void Tape::run_for_cycles(unsigned int number_of_cycles) {
|
||||
|
@ -10,13 +10,16 @@
|
||||
#define Electron_Tape_h
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Acorn.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class Tape: public Storage::Tape::TapePlayer {
|
||||
class Tape:
|
||||
public Storage::Tape::TapePlayer,
|
||||
public Storage::Tape::Acorn::Shifter::Delegate {
|
||||
public:
|
||||
Tape();
|
||||
|
||||
@ -39,6 +42,8 @@ class Tape: public Storage::Tape::TapePlayer {
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
|
||||
private:
|
||||
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
@ -62,9 +67,7 @@ class Tape: public Storage::Tape::TapePlayer {
|
||||
uint8_t interrupt_status_, last_posted_interrupt_status_;
|
||||
Delegate *delegate_;
|
||||
|
||||
enum {
|
||||
Long, Short, Unrecognised, Recognised
|
||||
} crossings_[4];
|
||||
::Storage::Tape::Acorn::Shifter shifter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -102,7 +102,6 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
|
||||
NSMutableArray<CSTestMachineZ80BusOperationCapture *> *_busOperationCaptures;
|
||||
int _timeSeekingReadOpcode;
|
||||
int _lastOpcodeTime;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
@ -184,9 +183,6 @@ static CPU::Z80::Register registerForRegister(CSTestMachineZ80Register reg) {
|
||||
}
|
||||
|
||||
- (void)testMachineDidPerformBusOperation:(CPU::Z80::PartialMachineCycle::Operation)operation address:(uint16_t)address value:(uint8_t)value timeStamp:(int)timeStamp {
|
||||
int length = timeStamp - _lastOpcodeTime;
|
||||
_lastOpcodeTime = timeStamp;
|
||||
|
||||
if(self.captureBusActivity) {
|
||||
CSTestMachineZ80BusOperationCapture *capture = [[CSTestMachineZ80BusOperationCapture alloc] init];
|
||||
switch(operation) {
|
||||
|
@ -12,15 +12,16 @@
|
||||
|
||||
using namespace Storage;
|
||||
|
||||
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history) :
|
||||
DigitalPhaseLockedLoop::DigitalPhaseLockedLoop(int clocks_per_bit, size_t length_of_history) :
|
||||
clocks_per_bit_(clocks_per_bit),
|
||||
tolerance_(tolerance),
|
||||
phase_(0),
|
||||
window_length_(clocks_per_bit),
|
||||
phase_error_pointer_(0),
|
||||
phase_error_history_(length_of_history, 0) {}
|
||||
offset_history_pointer_(0),
|
||||
offset_history_(length_of_history, 0),
|
||||
offset_(0) {}
|
||||
|
||||
void DigitalPhaseLockedLoop::run_for_cycles(int number_of_cycles) {
|
||||
offset_ += number_of_cycles;
|
||||
phase_ += number_of_cycles;
|
||||
if(phase_ >= window_length_) {
|
||||
int windows_crossed = phase_ / window_length_;
|
||||
@ -41,25 +42,31 @@ void DigitalPhaseLockedLoop::add_pulse() {
|
||||
if(!window_was_filled_) {
|
||||
if(delegate_) delegate_->digital_phase_locked_loop_output_bit(1);
|
||||
window_was_filled_ = true;
|
||||
post_phase_error(phase_ - (window_length_ >> 1));
|
||||
post_phase_offset(phase_, offset_);
|
||||
offset_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalPhaseLockedLoop::post_phase_error(int error) {
|
||||
void DigitalPhaseLockedLoop::post_phase_offset(int phase, int offset) {
|
||||
offset_history_[offset_history_pointer_] = offset;
|
||||
offset_history_pointer_ = (offset_history_pointer_ + 1) % offset_history_.size();
|
||||
|
||||
// use an unweighted average of the stored offsets to compute current window size,
|
||||
// bucketing them by rounding to the nearest multiple of the base clocks per bit
|
||||
int total_spacing = 0;
|
||||
int total_divisor = 0;
|
||||
for(int offset : offset_history_) {
|
||||
int multiple = (offset + (clocks_per_bit_ >> 1)) / clocks_per_bit_;
|
||||
if(!multiple) continue;
|
||||
total_divisor += multiple;
|
||||
total_spacing += offset;
|
||||
}
|
||||
if(total_divisor) {
|
||||
window_length_ = total_spacing / total_divisor;
|
||||
}
|
||||
|
||||
int error = phase - (window_length_ >> 1);
|
||||
|
||||
// use a simple spring mechanism as a lowpass filter for phase
|
||||
phase_ -= (error + 1) >> 1;
|
||||
|
||||
// use the average of the last few errors to affect frequency
|
||||
size_t phase_error_history_size = phase_error_history_.size();
|
||||
|
||||
phase_error_history_[phase_error_pointer_] = error;
|
||||
phase_error_pointer_ = (phase_error_pointer_ + 1)%phase_error_history_size;
|
||||
|
||||
int total_error = 0;
|
||||
for(size_t c = 0; c < phase_error_history_size; c++) {
|
||||
total_error += phase_error_history_[c];
|
||||
}
|
||||
int denominator = (int)(phase_error_history_size * 4);
|
||||
window_length_ += (total_error + (denominator >> 1)) / denominator;
|
||||
window_length_ = std::max(std::min(window_length_, clocks_per_bit_ + tolerance_), clocks_per_bit_ - tolerance_);
|
||||
}
|
||||
|
@ -20,10 +20,9 @@ class DigitalPhaseLockedLoop {
|
||||
Instantiates a @c DigitalPhaseLockedLoop.
|
||||
|
||||
@param clocks_per_bit The expected number of cycles between each bit of input.
|
||||
@param tolerance The maximum tolerance for bit windows — extremes will be clocks_per_bit ± tolerance.
|
||||
@param length_of_history The number of historic pulses to consider in locking to phase.
|
||||
*/
|
||||
DigitalPhaseLockedLoop(int clocks_per_bit, int tolerance, size_t length_of_history);
|
||||
DigitalPhaseLockedLoop(int clocks_per_bit, size_t length_of_history);
|
||||
|
||||
/*!
|
||||
Runs the loop, impliedly posting no pulses during that period.
|
||||
@ -51,9 +50,11 @@ class DigitalPhaseLockedLoop {
|
||||
private:
|
||||
Delegate *delegate_;
|
||||
|
||||
void post_phase_error(int error);
|
||||
std::vector<int> phase_error_history_;
|
||||
size_t phase_error_pointer_;
|
||||
void post_phase_offset(int phase, int offset);
|
||||
|
||||
std::vector<int> offset_history_;
|
||||
size_t offset_history_pointer_;
|
||||
int offset_;
|
||||
|
||||
int phase_;
|
||||
int window_length_;
|
||||
|
@ -166,7 +166,7 @@ void Controller::set_expected_bit_length(Time bit_length) {
|
||||
// this conversion doesn't need to be exact because there's a lot of variation to be taken
|
||||
// account of in rotation speed, air turbulence, etc, so a direct conversion will do
|
||||
int clocks_per_bit = (int)cycles_per_bit_.get_unsigned_int();
|
||||
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, clocks_per_bit / 5, 3));
|
||||
pll_.reset(new DigitalPhaseLockedLoop(clocks_per_bit, 3));
|
||||
pll_->set_delegate(this);
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,13 @@
|
||||
|
||||
using namespace Storage::Tape::Acorn;
|
||||
|
||||
Parser::Parser() :
|
||||
::Storage::Tape::Parser<WaveType, SymbolType>(),
|
||||
crc_(0x1021, 0x0000) {}
|
||||
namespace {
|
||||
const int PLLClockRate = 1920000;
|
||||
}
|
||||
|
||||
Parser::Parser() : crc_(0x1021, 0x0000) {
|
||||
shifter_.set_delegate(this);
|
||||
}
|
||||
|
||||
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
@ -52,38 +56,38 @@ int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &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;
|
||||
}
|
||||
|
||||
push_wave(WaveType::Unrecognised);
|
||||
void Parser::acorn_shifter_output_bit(int value) {
|
||||
push_symbol(value ? SymbolType::One : SymbolType::Zero);
|
||||
}
|
||||
|
||||
void Parser::inspect_waves(const std::vector<WaveType> &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);
|
||||
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
shifter_.process_pulse(pulse);
|
||||
}
|
||||
|
||||
|
||||
Shifter::Shifter() :
|
||||
pll_(PLLClockRate / 4800, 15),
|
||||
was_high_(false),
|
||||
input_pattern_(0),
|
||||
input_bit_counter_(0) {
|
||||
pll_.set_delegate(this);
|
||||
}
|
||||
|
||||
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
pll_.run_for_cycles((int)((float)PLLClockRate * 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;
|
||||
}
|
||||
|
||||
void Shifter::digital_phase_locked_loop_output_bit(int value) {
|
||||
input_pattern_ = ((input_pattern_ << 1) | (unsigned int)value) & 0xf;
|
||||
switch(input_pattern_) {
|
||||
case 0x5: delegate_->acorn_shifter_output_bit(0); input_pattern_ = 0; break;
|
||||
case 0xf: delegate_->acorn_shifter_output_bit(1); input_pattern_ = 0; break;
|
||||
default: break;;
|
||||
}
|
||||
}
|
||||
|
@ -11,20 +11,43 @@
|
||||
|
||||
#include "TapeParser.hpp"
|
||||
#include "../../../NumberTheory/CRC.hpp"
|
||||
#include "../../Disk/DigitalPhaseLockedLoop.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
namespace Acorn {
|
||||
|
||||
enum class WaveType {
|
||||
Short, Long, Unrecognised
|
||||
class Shifter: public Storage::DigitalPhaseLockedLoop::Delegate {
|
||||
public:
|
||||
Shifter();
|
||||
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void acorn_shifter_output_bit(int value) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
void digital_phase_locked_loop_output_bit(int value);
|
||||
|
||||
private:
|
||||
Storage::DigitalPhaseLockedLoop pll_;
|
||||
bool was_high_;
|
||||
|
||||
unsigned int input_pattern_;
|
||||
unsigned int input_bit_counter_;
|
||||
|
||||
Delegate *delegate_;
|
||||
};
|
||||
|
||||
enum class SymbolType {
|
||||
One, Zero
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
class Parser: public Storage::Tape::Parser<SymbolType>, public Shifter::Delegate {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
@ -35,10 +58,13 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
void reset_crc();
|
||||
uint16_t get_crc();
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
|
||||
private:
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
bool did_update_shifter(int new_value, int length);
|
||||
NumberTheory::CRC16 crc_;
|
||||
Shifter shifter_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
using namespace Storage::Tape::Commodore;
|
||||
|
||||
Parser::Parser() :
|
||||
Storage::Tape::Parser<WaveType, SymbolType>(),
|
||||
Storage::Tape::PulseClassificationParser<WaveType, SymbolType>(),
|
||||
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<Storage::Tape::Tape> &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
|
||||
|
@ -56,7 +56,7 @@ struct Data {
|
||||
bool duplicate_matched;
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
@ -126,7 +126,7 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
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_;
|
||||
|
||||
|
@ -45,7 +45,7 @@ bool Parser::sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Ta
|
||||
return false;
|
||||
}
|
||||
|
||||
void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse)
|
||||
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse)
|
||||
{
|
||||
const float maximum_short_length = 0.000512f;
|
||||
const float maximum_medium_length = 0.000728f;
|
||||
|
@ -26,13 +26,13 @@ enum class SymbolType {
|
||||
One, Zero, FoundFast, FoundSlow
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||
public:
|
||||
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding);
|
||||
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &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<WaveType> &waves);
|
||||
|
||||
enum DetectionMode {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define TapeParser_hpp
|
||||
|
||||
#include "../Tape.hpp"
|
||||
#include "../../Disk/DigitalPhaseLockedLoop.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -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 <typename WaveType, typename SymbolType> class Parser {
|
||||
template <typename SymbolType> 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 <typename WaveType, typename SymbolType> 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<Storage::Tape::Tape> &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 <typename WaveType, typename SymbolType> 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 <typename WaveType, typename SymbolType> class PulseClassificationParser: public Parser<SymbolType> {
|
||||
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<SymbolType>::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 <typename WaveType, typename SymbolType> 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<Storage::Tape::Tape> &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,6 @@ template <typename WaveType, typename SymbolType> class Parser {
|
||||
virtual void inspect_waves(const std::vector<WaveType> &waves) = 0;
|
||||
|
||||
std::vector<WaveType> wave_queue_;
|
||||
SymbolType next_symbol_;
|
||||
bool has_next_symbol_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -29,7 +29,7 @@ enum class SymbolType {
|
||||
One, Zero, FileGap, Unrecognised
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
@ -50,7 +50,7 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
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<WaveType> &waves);
|
||||
|
Loading…
Reference in New Issue
Block a user