mirror of
https://github.com/TomHarte/CLK.git
synced 2024-07-10 12:29:01 +00:00
Split the normal tape parser class into two in order to add a new option: a PLL-driven tape parser. Decided to see what happens if I attempt to use that to parse CSW Acorn data.
This commit is contained in:
parent
6ca712b498
commit
253f9603ed
@ -10,8 +10,12 @@
|
|||||||
|
|
||||||
using namespace Storage::Tape::Acorn;
|
using namespace Storage::Tape::Acorn;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const int PLLClockRate = 1920000;
|
||||||
|
}
|
||||||
|
|
||||||
Parser::Parser() :
|
Parser::Parser() :
|
||||||
::Storage::Tape::Parser<WaveType, SymbolType>(),
|
::Storage::Tape::PLLParser<SymbolType>(PLLClockRate, PLLClockRate / 4800, 100),
|
||||||
crc_(0x1021, 0x0000) {}
|
crc_(0x1021, 0x0000) {}
|
||||||
|
|
||||||
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||||
@ -52,38 +56,14 @@ int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
|||||||
void Parser::reset_crc() { crc_.reset(); }
|
void Parser::reset_crc() { crc_.reset(); }
|
||||||
uint16_t Parser::get_crc() { return crc_.get_value(); }
|
uint16_t Parser::get_crc() { return crc_.get_value(); }
|
||||||
|
|
||||||
void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) {
|
bool Parser::did_update_shifter(int new_value, int length) {
|
||||||
switch(pulse.type) {
|
if(length < 4) return false;
|
||||||
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);
|
switch(new_value & 0xf) {
|
||||||
}
|
case 0x5: printf("0"); push_symbol(SymbolType::Zero); return true;
|
||||||
|
case 0xf: printf("1"); push_symbol(SymbolType::One); return true;
|
||||||
void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
default:
|
||||||
if(waves.size() < 2) return;
|
printf("?");
|
||||||
|
return false;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,17 @@
|
|||||||
|
|
||||||
#include "TapeParser.hpp"
|
#include "TapeParser.hpp"
|
||||||
#include "../../../NumberTheory/CRC.hpp"
|
#include "../../../NumberTheory/CRC.hpp"
|
||||||
|
#include "../../Disk/DigitalPhaseLockedLoop.hpp"
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
namespace Tape {
|
||||||
namespace Acorn {
|
namespace Acorn {
|
||||||
|
|
||||||
enum class WaveType {
|
|
||||||
Short, Long, Unrecognised
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SymbolType {
|
enum class SymbolType {
|
||||||
One, Zero
|
One, Zero
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
class Parser: public Storage::Tape::PLLParser<SymbolType> {
|
||||||
public:
|
public:
|
||||||
Parser();
|
Parser();
|
||||||
|
|
||||||
@ -35,9 +32,9 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
|||||||
void reset_crc();
|
void reset_crc();
|
||||||
uint16_t get_crc();
|
uint16_t get_crc();
|
||||||
|
|
||||||
|
bool did_update_shifter(int new_value, int length);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
|
||||||
void inspect_waves(const std::vector<WaveType> &waves);
|
|
||||||
NumberTheory::CRC16 crc_;
|
NumberTheory::CRC16 crc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
using namespace Storage::Tape::Commodore;
|
using namespace Storage::Tape::Commodore;
|
||||||
|
|
||||||
Parser::Parser() :
|
Parser::Parser() :
|
||||||
Storage::Tape::Parser<WaveType, SymbolType>(),
|
Storage::Tape::PulseClassificationParser<WaveType, SymbolType>(),
|
||||||
wave_period_(0.0f),
|
wave_period_(0.0f),
|
||||||
previous_was_high_(false),
|
previous_was_high_(false),
|
||||||
parity_byte_(0) {}
|
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
|
indicates a high to low transition, inspects the time since the last transition, to produce
|
||||||
a long, medium, short or unrecognised wave period.
|
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:
|
// The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of:
|
||||||
// short: 182µs => 0.000364s cycle
|
// short: 182µs => 0.000364s cycle
|
||||||
|
@ -56,7 +56,7 @@ struct Data {
|
|||||||
bool duplicate_matched;
|
bool duplicate_matched;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||||
public:
|
public:
|
||||||
Parser();
|
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
|
indicates a high to low transition, inspects the time since the last transition, to produce
|
||||||
a long, medium, short or unrecognised wave period.
|
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_;
|
bool previous_was_high_;
|
||||||
float wave_period_;
|
float wave_period_;
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ bool Parser::sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Ta
|
|||||||
return false;
|
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_short_length = 0.000512f;
|
||||||
const float maximum_medium_length = 0.000728f;
|
const float maximum_medium_length = 0.000728f;
|
||||||
|
@ -26,13 +26,13 @@ enum class SymbolType {
|
|||||||
One, Zero, FoundFast, FoundSlow
|
One, Zero, FoundFast, FoundSlow
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||||
public:
|
public:
|
||||||
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding);
|
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);
|
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||||
|
|
||||||
private:
|
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);
|
void inspect_waves(const std::vector<WaveType> &waves);
|
||||||
|
|
||||||
enum DetectionMode {
|
enum DetectionMode {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#define TapeParser_hpp
|
#define TapeParser_hpp
|
||||||
|
|
||||||
#include "../Tape.hpp"
|
#include "../Tape.hpp"
|
||||||
|
#include "../../Disk/DigitalPhaseLockedLoop.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -18,18 +19,9 @@
|
|||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Tape {
|
namespace Tape {
|
||||||
|
|
||||||
/*!
|
template <typename SymbolType> class Parser {
|
||||||
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 {
|
|
||||||
public:
|
public:
|
||||||
/// Instantiates a new parser with the supplied @c tape.
|
|
||||||
Parser() : has_next_symbol_(false), error_flag_(false) {}
|
Parser() : has_next_symbol_(false), error_flag_(false) {}
|
||||||
|
|
||||||
/// Resets the error flag.
|
/// Resets the error flag.
|
||||||
void reset_error_flag() { error_flag_ = false; }
|
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.
|
/// @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;
|
next_symbol_ = symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave
|
@returns `true` if there is no data left on the tape and the WaveType queue has been exhausted; `false` otherwise.
|
||||||
or to take no action.
|
|
||||||
*/
|
*/
|
||||||
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
|
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() {}
|
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:
|
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.
|
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);
|
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:
|
private:
|
||||||
bool error_flag_;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is
|
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
|
found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries
|
||||||
@ -129,8 +145,68 @@ template <typename WaveType, typename SymbolType> class Parser {
|
|||||||
virtual void inspect_waves(const std::vector<WaveType> &waves) = 0;
|
virtual void inspect_waves(const std::vector<WaveType> &waves) = 0;
|
||||||
|
|
||||||
std::vector<WaveType> wave_queue_;
|
std::vector<WaveType> 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 <typename SymbolType> class PLLParser:
|
||||||
|
public Storage::DigitalPhaseLockedLoop::Delegate,
|
||||||
|
public Parser<SymbolType> {
|
||||||
|
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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ using namespace Storage::Tape::ZX8081;
|
|||||||
|
|
||||||
Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {}
|
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;
|
pulse_time_ += pulse.length;
|
||||||
bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ enum class SymbolType {
|
|||||||
One, Zero, FileGap, Unrecognised
|
One, Zero, FileGap, Unrecognised
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||||
public:
|
public:
|
||||||
Parser();
|
Parser();
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
|||||||
Time pulse_time_;
|
Time pulse_time_;
|
||||||
void post_pulse();
|
void post_pulse();
|
||||||
|
|
||||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||||
void mark_end();
|
void mark_end();
|
||||||
|
|
||||||
void inspect_waves(const std::vector<WaveType> &waves);
|
void inspect_waves(const std::vector<WaveType> &waves);
|
||||||
|
Loading…
Reference in New Issue
Block a user