1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-05 21:32:55 +00:00

Finally eliminate all that virtual_ nonsense.

This commit is contained in:
Thomas Harte 2024-12-03 22:28:57 -05:00
parent 598003ea39
commit 6d4ff0b89a
35 changed files with 488 additions and 394 deletions

View File

@ -68,7 +68,7 @@ uint8_t Tape::get_data_register() {
return uint8_t(data_register_ >> 2); return uint8_t(data_register_ >> 2);
} }
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Tape::process_input_pulse(const Storage::Tape::Pulse &pulse) {
shifter_.process_pulse(pulse); shifter_.process_pulse(pulse);
} }

View File

@ -46,7 +46,7 @@ public:
void acorn_shifter_output_bit(int value); void acorn_shifter_output_bit(int value);
private: private:
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse); void process_input_pulse(const Storage::Tape::Pulse &pulse);
inline void push_tape_bit(uint16_t bit); inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse(); inline void get_next_tape_pulse();

View File

@ -59,7 +59,9 @@ namespace {
const uint8_t ascii_signature[] = TenX(0xea); const uint8_t ascii_signature[] = TenX(0xea);
} }
CAS::CAS(const std::string &file_name) { CAS::CAS(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CAS::Serialiser::Serialiser(const std::string &file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read); Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
enum class Mode { enum class Mode {
@ -168,18 +170,18 @@ CAS::CAS(const std::string &file_name) {
} }
} }
bool CAS::is_at_end() const { bool CAS::Serialiser::is_at_end() const {
return phase_ == Phase::EndOfFile; return phase_ == Phase::EndOfFile;
} }
void CAS::virtual_reset() { void CAS::Serialiser::reset() {
phase_ = Phase::Header; phase_ = Phase::Header;
chunk_pointer_ = 0; chunk_pointer_ = 0;
distance_into_phase_ = 0; distance_into_phase_ = 0;
distance_into_bit_ = 0; distance_into_bit_ = 0;
} }
Tape::Pulse CAS::virtual_get_next_pulse() { Pulse CAS::Serialiser::get_next_pulse() {
Pulse pulse; Pulse pulse;
pulse.length.clock_rate = 9600; pulse.length.clock_rate = 9600;
// Clock rate is four times the baud rate (of 2400), because the quickest thing that might need // Clock rate is four times the baud rate (of 2400), because the quickest thing that might need

View File

@ -33,36 +33,39 @@ public:
ErrorNotCAS ErrorNotCAS
}; };
// implemented to satisfy @c Tape
bool is_at_end() const override;
private: private:
void virtual_reset() override; struct Serialiser: public TapeSerialiser {
Pulse virtual_get_next_pulse() override; Serialiser(const std::string &file_name);
// Storage for the array of data blobs to transcribe into audio; private:
// each chunk is preceded by a header which may be long, and is optionally bool is_at_end() const override;
// also preceded by a gap. void reset() override;
struct Chunk { Pulse get_next_pulse() override;
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) : // Storage for the array of data blobs to transcribe into audio;
has_gap(has_gap), long_header(long_header), data(std::move(data)) {} // each chunk is preceded by a header which may be long, and is optionally
}; // also preceded by a gap.
std::vector<Chunk> chunks_; struct Chunk {
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
// Tracker for active state within the file list. Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) :
std::size_t chunk_pointer_ = 0; has_gap(has_gap), long_header(long_header), data(std::move(data)) {}
enum class Phase { };
Header, std::vector<Chunk> chunks_;
Bytes,
Gap, // Tracker for active state within the file list.
EndOfFile std::size_t chunk_pointer_ = 0;
} phase_ = Phase::Header; enum class Phase {
std::size_t distance_into_phase_ = 0; Header,
std::size_t distance_into_bit_ = 0; Bytes,
Gap,
EndOfFile
} phase_ = Phase::Header;
std::size_t distance_into_phase_ = 0;
std::size_t distance_into_bit_ = 0;
} serialiser_;
}; };
} }

View File

@ -14,7 +14,14 @@
using namespace Storage::Tape; using namespace Storage::Tape;
CSW::CSW(const std::string &file_name) : CSW::CSW(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CSW::CSW(const std::vector<uint8_t> &&data, CompressionType type, bool initial_level, uint32_t sampling_rate) :
Tape(serialiser_),
serialiser_(std::move(data), type, initial_level, sampling_rate) {}
CSW::Serialiser::Serialiser(const std::string &file_name) :
source_data_pointer_(0) { source_data_pointer_(0) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read); Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
if(file.stats().st_size < 0x20) throw ErrorNotCSW; if(file.stats().st_size < 0x20) throw ErrorNotCSW;
@ -86,20 +93,20 @@ CSW::CSW(const std::string &file_name) :
invert_pulse(); invert_pulse();
} }
CSW::CSW(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate) : compression_type_(compression_type) { CSW::Serialiser::Serialiser(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate) : compression_type_(compression_type) {
pulse_.length.clock_rate = sampling_rate; pulse_.length.clock_rate = sampling_rate;
pulse_.type = initial_level ? Pulse::High : Pulse::Low; pulse_.type = initial_level ? Pulse::High : Pulse::Low;
source_data_ = std::move(data); source_data_ = std::move(data);
} }
uint8_t CSW::get_next_byte() { uint8_t CSW::Serialiser::get_next_byte() {
if(source_data_pointer_ == source_data_.size()) return 0xff; if(source_data_pointer_ == source_data_.size()) return 0xff;
uint8_t result = source_data_[source_data_pointer_]; uint8_t result = source_data_[source_data_pointer_];
source_data_pointer_++; source_data_pointer_++;
return result; return result;
} }
uint32_t CSW::get_next_int32le() { uint32_t CSW::Serialiser::get_next_int32le() {
if(source_data_pointer_ > source_data_.size() - 4) return 0xffff; if(source_data_pointer_ > source_data_.size() - 4) return 0xffff;
uint32_t result = uint32_t( uint32_t result = uint32_t(
(source_data_[source_data_pointer_ + 0] << 0) | (source_data_[source_data_pointer_ + 0] << 0) |
@ -110,19 +117,19 @@ uint32_t CSW::get_next_int32le() {
return result; return result;
} }
void CSW::invert_pulse() { void CSW::Serialiser::invert_pulse() {
pulse_.type = (pulse_.type == Pulse::High) ? Pulse::Low : Pulse::High; pulse_.type = (pulse_.type == Pulse::High) ? Pulse::Low : Pulse::High;
} }
bool CSW::is_at_end() const { bool CSW::Serialiser::is_at_end() const {
return source_data_pointer_ == source_data_.size(); return source_data_pointer_ == source_data_.size();
} }
void CSW::virtual_reset() { void CSW::Serialiser::reset() {
source_data_pointer_ = 0; source_data_pointer_ = 0;
} }
Tape::Pulse CSW::virtual_get_next_pulse() { Pulse CSW::Serialiser::get_next_pulse() {
invert_pulse(); invert_pulse();
pulse_.length.length = get_next_byte(); pulse_.length.length = get_next_byte();
if(!pulse_.length.length) pulse_.length.length = get_next_int32le(); if(!pulse_.length.length) pulse_.length.length = get_next_int32le();

View File

@ -21,6 +21,11 @@ namespace Storage::Tape {
*/ */
class CSW: public Tape { class CSW: public Tape {
public: public:
enum class CompressionType {
RLE,
ZRLE
};
/*! /*!
Constructs a @c CSW containing content from the file with name @c file_name. Constructs a @c CSW containing content from the file with name @c file_name.
@ -28,36 +33,36 @@ public:
*/ */
CSW(const std::string &file_name); CSW(const std::string &file_name);
enum class CompressionType {
RLE,
ZRLE
};
/*! /*!
Constructs a @c CSW containing content as specified. Does not throw. Constructs a @c CSW containing content as specified. Does not throw.
*/ */
CSW(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate); CSW(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
enum { enum {
ErrorNotCSW ErrorNotCSW
}; };
// implemented to satisfy @c Tape
bool is_at_end() const override;
private: private:
void virtual_reset() override; struct Serialiser: public TapeSerialiser {
Pulse virtual_get_next_pulse() override; Serialiser(const std::string &file_name);
Serialiser(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
Pulse pulse_; private:
CompressionType compression_type_; // implemented to satisfy @c Tape
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
uint8_t get_next_byte(); Pulse pulse_;
uint32_t get_next_int32le(); CompressionType compression_type_;
void invert_pulse();
std::vector<uint8_t> source_data_; uint8_t get_next_byte();
std::size_t source_data_pointer_; uint32_t get_next_int32le();
void invert_pulse();
std::vector<uint8_t> source_data_;
std::size_t source_data_pointer_;
} serialiser_;
}; };
} }

View File

@ -12,7 +12,9 @@
using namespace Storage::Tape; using namespace Storage::Tape;
CommodoreTAP::CommodoreTAP(const std::string &file_name) : CommodoreTAP::CommodoreTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
CommodoreTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read) file_(file_name, FileHolder::FileMode::Read)
{ {
if(!file_.check_signature("C64-TAPE-RAW")) if(!file_.check_signature("C64-TAPE-RAW"))
@ -39,17 +41,17 @@ CommodoreTAP::CommodoreTAP(const std::string &file_name) :
current_pulse_.type = Pulse::High; current_pulse_.type = Pulse::High;
} }
void CommodoreTAP::virtual_reset() { void CommodoreTAP::Serialiser::reset() {
file_.seek(0x14, SEEK_SET); file_.seek(0x14, SEEK_SET);
current_pulse_.type = Pulse::High; current_pulse_.type = Pulse::High;
is_at_end_ = false; is_at_end_ = false;
} }
bool CommodoreTAP::is_at_end() const { bool CommodoreTAP::Serialiser::is_at_end() const {
return is_at_end_; return is_at_end_;
} }
Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse() { Storage::Tape::Pulse CommodoreTAP::Serialiser::get_next_pulse() {
if(is_at_end_) { if(is_at_end_) {
return current_pulse_; return current_pulse_;
} }

View File

@ -32,19 +32,23 @@ public:
ErrorNotCommodoreTAP ErrorNotCommodoreTAP
}; };
// implemented to satisfy @c Tape
bool is_at_end() const override;
private: private:
Storage::FileHolder file_; struct Serialiser: public TapeSerialiser {
void virtual_reset() override; Serialiser(const std::string &file_name);
Pulse virtual_get_next_pulse() override;
bool updated_layout_; private:
uint32_t file_size_; bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
Pulse current_pulse_; Storage::FileHolder file_;
bool is_at_end_ = false;
bool updated_layout_;
uint32_t file_size_;
Pulse current_pulse_;
bool is_at_end_ = false;
} serialiser_;
}; };
} }

View File

@ -12,7 +12,10 @@
using namespace Storage::Tape; using namespace Storage::Tape;
OricTAP::OricTAP(const std::string &file_name) : OricTAP::OricTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
OricTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read) file_(file_name, FileHolder::FileMode::Read)
{ {
// Check for a sequence of at least three 0x16s followed by a 0x24. // Check for a sequence of at least three 0x16s followed by a 0x24.
@ -29,11 +32,11 @@ OricTAP::OricTAP(const std::string &file_name) :
} }
} }
// then rewind and start again // Rewind and start again.
virtual_reset(); reset();
} }
void OricTAP::virtual_reset() { void OricTAP::Serialiser::reset() {
file_.seek(0, SEEK_SET); file_.seek(0, SEEK_SET);
bit_count_ = 13; bit_count_ = 13;
phase_ = next_phase_ = LeadIn; phase_ = next_phase_ = LeadIn;
@ -41,7 +44,7 @@ void OricTAP::virtual_reset() {
pulse_counter_ = 0; pulse_counter_ = 0;
} }
Tape::Pulse OricTAP::virtual_get_next_pulse() { Pulse OricTAP::Serialiser::get_next_pulse() {
// Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s. // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s.
if(bit_count_ == 13) { if(bit_count_ == 13) {
if(next_phase_ != phase_) { if(next_phase_ != phase_) {
@ -122,7 +125,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() {
// In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz. // In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz.
// In fast mode, a 1 is a single period of 2400 Hz, a 0 is a 2400 Hz pulse followed by a 1200 Hz pulse. // In fast mode, a 1 is a single period of 2400 Hz, a 0 is a 2400 Hz pulse followed by a 1200 Hz pulse.
// This code models fast mode. // This code models fast mode.
Tape::Pulse pulse; Pulse pulse;
pulse.length.clock_rate = 4800; pulse.length.clock_rate = 4800;
int next_bit; int next_bit;
@ -158,6 +161,6 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() {
return pulse; return pulse;
} }
bool OricTAP::is_at_end() const { bool OricTAP::Serialiser::is_at_end() const {
return phase_ == End; return phase_ == End;
} }

View File

@ -32,24 +32,28 @@ public:
ErrorNotOricTAP ErrorNotOricTAP
}; };
// implemented to satisfy @c Tape
bool is_at_end() const override;
private: private:
Storage::FileHolder file_; struct Serialiser: public TapeSerialiser {
void virtual_reset() override; Serialiser(const std::string &file_name);
Pulse virtual_get_next_pulse() override;
// byte serialisation and output private:
uint16_t current_value_; bool is_at_end() const override;
int bit_count_; void reset() override;
int pulse_counter_; Pulse get_next_pulse() override;
enum Phase { Storage::FileHolder file_;
LeadIn, Header, Data, Gap, End
} phase_, next_phase_; // byte serialisation and output
int phase_counter_; uint16_t current_value_;
uint16_t data_end_address_, data_start_address_; int bit_count_;
int pulse_counter_;
enum Phase {
LeadIn, Header, Data, Gap, End
} phase_, next_phase_;
int phase_counter_;
uint16_t data_end_address_, data_start_address_;
} serialiser_;
}; };
} }

View File

@ -21,7 +21,9 @@ Log::Logger<Log::Source::TZX> logger;
} }
TZX::TZX(const std::string &file_name) : TZX::TZX(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
TZX::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read), file_(file_name, FileHolder::FileMode::Read),
current_level_(false) { current_level_(false) {
@ -36,10 +38,10 @@ TZX::TZX(const std::string &file_name) :
// Reject if an incompatible version // Reject if an incompatible version
if(major_version != 1 || minor_version > 21) throw ErrorNotTZX; if(major_version != 1 || minor_version > 21) throw ErrorNotTZX;
virtual_reset(); reset();
} }
void TZX::virtual_reset() { void TZX::Serialiser::reset() {
clear(); clear();
set_is_at_end(false); set_is_at_end(false);
file_.seek(0x0a, SEEK_SET); file_.seek(0x0a, SEEK_SET);
@ -51,7 +53,7 @@ void TZX::virtual_reset() {
post_gap(500); post_gap(500);
} }
void TZX::get_next_pulses() { void TZX::Serialiser::get_next_pulses() {
while(empty()) { while(empty()) {
uint8_t chunk_id = file_.get8(); uint8_t chunk_id = file_.get8();
if(file_.eof()) { if(file_.eof()) {
@ -102,7 +104,7 @@ void TZX::get_next_pulses() {
} }
} }
void TZX::get_csw_recording_block() { void TZX::Serialiser::get_csw_recording_block() {
const uint32_t block_length = file_.get32le(); const uint32_t block_length = file_.get32le();
const uint16_t pause_after_block = file_.get16le(); const uint16_t pause_after_block = file_.get16le();
const uint32_t sampling_rate = file_.get24le(); const uint32_t sampling_rate = file_.get24le();
@ -113,8 +115,8 @@ void TZX::get_csw_recording_block() {
CSW csw(std::move(raw_block), (compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE, current_level_, sampling_rate); CSW csw(std::move(raw_block), (compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE, current_level_, sampling_rate);
while(!csw.is_at_end()) { while(!csw.is_at_end()) {
Tape::Pulse next_pulse = csw.get_next_pulse(); Pulse next_pulse = csw.get_next_pulse();
current_level_ = (next_pulse.type == Tape::Pulse::High); current_level_ = (next_pulse.type == Pulse::High);
push_back(next_pulse); push_back(next_pulse);
} }
@ -122,7 +124,7 @@ void TZX::get_csw_recording_block() {
post_gap(pause_after_block); post_gap(pause_after_block);
} }
void TZX::get_generalised_data_block() { void TZX::Serialiser::get_generalised_data_block() {
uint32_t block_length = file_.get32le(); uint32_t block_length = file_.get32le();
long endpoint = file_.tell() + long(block_length); long endpoint = file_.tell() + long(block_length);
uint16_t pause_after_block = file_.get16le(); uint16_t pause_after_block = file_.get16le();
@ -143,7 +145,12 @@ void TZX::get_generalised_data_block() {
file_.seek(endpoint, SEEK_SET); file_.seek(endpoint, SEEK_SET);
} }
void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data) { void TZX::Serialiser::get_generalised_segment(
uint32_t output_symbols,
uint8_t max_pulses_per_symbol,
uint8_t number_of_symbols,
bool is_data
) {
if(!output_symbols) return; if(!output_symbols) return;
// Construct the symbol table. // Construct the symbol table.
@ -202,7 +209,7 @@ void TZX::get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_pe
} }
} }
void TZX::get_standard_speed_data_block() { void TZX::Serialiser::get_standard_speed_data_block() {
DataBlock data_block; DataBlock data_block;
data_block.length_of_pilot_pulse = 2168; data_block.length_of_pilot_pulse = 2168;
data_block.length_of_sync_first_pulse = 667; data_block.length_of_sync_first_pulse = 667;
@ -222,7 +229,7 @@ void TZX::get_standard_speed_data_block() {
get_data_block(data_block); get_data_block(data_block);
} }
void TZX::get_turbo_speed_data_block() { void TZX::Serialiser::get_turbo_speed_data_block() {
DataBlock data_block; DataBlock data_block;
data_block.length_of_pilot_pulse = file_.get16le(); data_block.length_of_pilot_pulse = file_.get16le();
data_block.length_of_sync_first_pulse = file_.get16le(); data_block.length_of_sync_first_pulse = file_.get16le();
@ -237,7 +244,7 @@ void TZX::get_turbo_speed_data_block() {
get_data_block(data_block); get_data_block(data_block);
} }
void TZX::get_data_block(const DataBlock &data_block) { void TZX::Serialiser::get_data_block(const DataBlock &data_block) {
// Output pilot tone. // Output pilot tone.
post_pulses(data_block.length_of_pilot_tone, data_block.length_of_pilot_pulse); post_pulses(data_block.length_of_pilot_tone, data_block.length_of_pilot_pulse);
@ -248,7 +255,7 @@ void TZX::get_data_block(const DataBlock &data_block) {
get_data(data_block.data); get_data(data_block.data);
} }
void TZX::get_data(const Data &data) { void TZX::Serialiser::get_data(const Data &data) {
// Output data. // Output data.
for(decltype(data.data_length) c = 0; c < data.data_length; c++) { for(decltype(data.data_length) c = 0; c < data.data_length; c++) {
uint8_t next_byte = file_.get8(); uint8_t next_byte = file_.get8();
@ -267,14 +274,14 @@ void TZX::get_data(const Data &data) {
post_gap(data.pause_after_block); post_gap(data.pause_after_block);
} }
void TZX::get_pure_tone_data_block() { void TZX::Serialiser::get_pure_tone_data_block() {
uint16_t length_of_pulse = file_.get16le(); uint16_t length_of_pulse = file_.get16le();
uint16_t nunber_of_pulses = file_.get16le(); uint16_t nunber_of_pulses = file_.get16le();
post_pulses(nunber_of_pulses, length_of_pulse); post_pulses(nunber_of_pulses, length_of_pulse);
} }
void TZX::get_pure_data_block() { void TZX::Serialiser::get_pure_data_block() {
Data data; Data data;
data.length_of_zero_bit_pulse = file_.get16le(); data.length_of_zero_bit_pulse = file_.get16le();
data.length_of_one_bit_pulse = file_.get16le(); data.length_of_one_bit_pulse = file_.get16le();
@ -285,7 +292,7 @@ void TZX::get_pure_data_block() {
get_data(data); get_data(data);
} }
void TZX::get_direct_recording_block() { void TZX::Serialiser::get_direct_recording_block() {
const Storage::Time length_per_sample(unsigned(file_.get16le()), StandardTZXClock); const Storage::Time length_per_sample(unsigned(file_.get16le()), StandardTZXClock);
const uint16_t pause_after_block = file_.get16le(); const uint16_t pause_after_block = file_.get16le();
uint8_t used_bits_in_final_byte = file_.get8(); uint8_t used_bits_in_final_byte = file_.get8();
@ -302,7 +309,7 @@ void TZX::get_direct_recording_block() {
if(!bit) level = byte&0x80; if(!bit) level = byte&0x80;
if((byte&0x80) != level) { if((byte&0x80) != level) {
emplace_back(level ? Tape::Pulse::High : Tape::Pulse::Low, length_per_sample * bits_at_level); emplace_back(level ? Pulse::High : Pulse::Low, length_per_sample * bits_at_level);
bits_at_level = 0; bits_at_level = 0;
level = byte&0x80; level = byte&0x80;
} }
@ -310,19 +317,19 @@ void TZX::get_direct_recording_block() {
} }
current_level_ = !!(level); current_level_ = !!(level);
emplace_back(level ? Tape::Pulse::High : Tape::Pulse::Low, length_per_sample * bits_at_level); emplace_back(level ? Pulse::High : Pulse::Low, length_per_sample * bits_at_level);
post_gap(pause_after_block); post_gap(pause_after_block);
} }
void TZX::get_pulse_sequence() { void TZX::Serialiser::get_pulse_sequence() {
uint8_t number_of_pulses = file_.get8(); uint8_t number_of_pulses = file_.get8();
while(number_of_pulses--) { while(number_of_pulses--) {
post_pulse(file_.get16le()); post_pulse(file_.get16le());
} }
} }
void TZX::get_pause() { void TZX::Serialiser::get_pause() {
uint16_t duration = file_.get16le(); uint16_t duration = file_.get16le();
if(!duration) { if(!duration) {
// TODO (maybe): post a 'pause the tape' suggestion // TODO (maybe): post a 'pause the tape' suggestion
@ -331,13 +338,13 @@ void TZX::get_pause() {
} }
} }
void TZX::get_set_signal_level() { void TZX::Serialiser::get_set_signal_level() {
file_.seek(4, SEEK_CUR); file_.seek(4, SEEK_CUR);
const uint8_t level = file_.get8(); const uint8_t level = file_.get8();
current_level_ = !!level; current_level_ = !!level;
} }
void TZX::get_kansas_city_block() { void TZX::Serialiser::get_kansas_city_block() {
uint32_t block_length = file_.get32le(); uint32_t block_length = file_.get32le();
const uint16_t pause_after_block = file_.get16le(); const uint16_t pause_after_block = file_.get16le();
@ -395,15 +402,15 @@ void TZX::get_kansas_city_block() {
// MARK: - Output // MARK: - Output
void TZX::post_pulses(unsigned int count, unsigned int length) { void TZX::Serialiser::post_pulses(unsigned int count, unsigned int length) {
while(count--) post_pulse(length); while(count--) post_pulse(length);
} }
void TZX::post_pulse(unsigned int length) { void TZX::Serialiser::post_pulse(unsigned int length) {
post_pulse(Storage::Time(length, StandardTZXClock)); post_pulse(Storage::Time(length, StandardTZXClock));
} }
void TZX::post_gap(unsigned int milliseconds) { void TZX::Serialiser::post_gap(unsigned int milliseconds) {
if(!milliseconds) return; if(!milliseconds) return;
if(milliseconds > 1 && !current_level_) { if(milliseconds > 1 && !current_level_) {
post_pulse(Storage::Time(TZXClockMSMultiplier, StandardTZXClock)); post_pulse(Storage::Time(TZXClockMSMultiplier, StandardTZXClock));
@ -413,52 +420,52 @@ void TZX::post_gap(unsigned int milliseconds) {
} }
} }
void TZX::post_pulse(const Storage::Time &time) { void TZX::Serialiser::post_pulse(const Storage::Time &time) {
emplace_back(current_level_ ? Tape::Pulse::High : Tape::Pulse::Low, time); emplace_back(current_level_ ? Pulse::High : Pulse::Low, time);
current_level_ ^= true; current_level_ ^= true;
} }
// MARK: - Flow control; currently ignored // MARK: - Flow control; currently ignored
void TZX::ignore_group_start() { void TZX::Serialiser::ignore_group_start() {
uint8_t length = file_.get8(); uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR); file_.seek(length, SEEK_CUR);
} }
void TZX::ignore_group_end() { void TZX::Serialiser::ignore_group_end() {
} }
void TZX::ignore_jump_to_block() { void TZX::Serialiser::ignore_jump_to_block() {
uint16_t target = file_.get16le(); uint16_t target = file_.get16le();
(void)target; (void)target;
} }
void TZX::ignore_loop_start() { void TZX::Serialiser::ignore_loop_start() {
uint16_t number_of_repetitions = file_.get16le(); uint16_t number_of_repetitions = file_.get16le();
(void)number_of_repetitions; (void)number_of_repetitions;
} }
void TZX::ignore_loop_end() { void TZX::Serialiser::ignore_loop_end() {
} }
void TZX::ignore_call_sequence() { void TZX::Serialiser::ignore_call_sequence() {
uint16_t number_of_entries = file_.get16le(); uint16_t number_of_entries = file_.get16le();
file_.seek(number_of_entries * sizeof(uint16_t), SEEK_CUR); file_.seek(number_of_entries * sizeof(uint16_t), SEEK_CUR);
} }
void TZX::ignore_return_from_sequence() { void TZX::Serialiser::ignore_return_from_sequence() {
} }
void TZX::ignore_select_block() { void TZX::Serialiser::ignore_select_block() {
uint16_t length_of_block = file_.get16le(); uint16_t length_of_block = file_.get16le();
file_.seek(length_of_block, SEEK_CUR); file_.seek(length_of_block, SEEK_CUR);
} }
void TZX::ignore_stop_tape_if_in_48kb_mode() { void TZX::Serialiser::ignore_stop_tape_if_in_48kb_mode() {
file_.seek(4, SEEK_CUR); file_.seek(4, SEEK_CUR);
} }
void TZX::ignore_custom_info_block() { void TZX::Serialiser::ignore_custom_info_block() {
file_.seek(0x10, SEEK_CUR); file_.seek(0x10, SEEK_CUR);
uint32_t length = file_.get32le(); uint32_t length = file_.get32le();
file_.seek(length, SEEK_CUR); file_.seek(length, SEEK_CUR);
@ -466,29 +473,29 @@ void TZX::ignore_custom_info_block() {
// MARK: - Messaging // MARK: - Messaging
void TZX::ignore_text_description() { void TZX::Serialiser::ignore_text_description() {
uint8_t length = file_.get8(); uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR); file_.seek(length, SEEK_CUR);
} }
void TZX::ignore_message_block() { void TZX::Serialiser::ignore_message_block() {
uint8_t time_for_display = file_.get8(); uint8_t time_for_display = file_.get8();
uint8_t length = file_.get8(); uint8_t length = file_.get8();
file_.seek(length, SEEK_CUR); file_.seek(length, SEEK_CUR);
(void)time_for_display; (void)time_for_display;
} }
void TZX::ignore_archive_info() { void TZX::Serialiser::ignore_archive_info() {
uint16_t length = file_.get16le(); uint16_t length = file_.get16le();
file_.seek(length, SEEK_CUR); file_.seek(length, SEEK_CUR);
} }
void TZX::get_hardware_type() { void TZX::Serialiser::get_hardware_type() {
// TODO: pick a way to retain and communicate this. // TODO: pick a way to retain and communicate this.
uint8_t number_of_machines = file_.get8(); uint8_t number_of_machines = file_.get8();
file_.seek(number_of_machines * 3, SEEK_CUR); file_.seek(number_of_machines * 3, SEEK_CUR);
} }
void TZX::ignore_glue_block() { void TZX::Serialiser::ignore_glue_block() {
file_.seek(9, SEEK_CUR); file_.seek(9, SEEK_CUR);
} }

View File

@ -18,7 +18,7 @@ namespace Storage::Tape {
/*! /*!
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling. Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling.
*/ */
class TZX: public PulseQueuedTape { class TZX: public Tape {
public: public:
/*! /*!
Constructs a @c TZX containing content from the file with name @c file_name. Constructs a @c TZX containing content from the file with name @c file_name.
@ -32,69 +32,79 @@ public:
}; };
private: private:
Storage::FileHolder file_; struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
void virtual_reset() override; private:
void get_next_pulses() override; Storage::FileHolder file_;
bool current_level_; void reset() override;
void get_next_pulses() override;
void get_standard_speed_data_block(); bool current_level_;
void get_turbo_speed_data_block();
void get_pure_tone_data_block();
void get_pulse_sequence();
void get_pure_data_block();
void get_direct_recording_block();
void get_csw_recording_block();
void get_generalised_data_block();
void get_pause();
void ignore_group_start(); void get_standard_speed_data_block();
void ignore_group_end(); void get_turbo_speed_data_block();
void ignore_jump_to_block(); void get_pure_tone_data_block();
void ignore_loop_start(); void get_pulse_sequence();
void ignore_loop_end(); void get_pure_data_block();
void ignore_call_sequence(); void get_direct_recording_block();
void ignore_return_from_sequence(); void get_csw_recording_block();
void ignore_select_block(); void get_generalised_data_block();
void ignore_stop_tape_if_in_48kb_mode(); void get_pause();
void get_set_signal_level(); void ignore_group_start();
void ignore_group_end();
void ignore_jump_to_block();
void ignore_loop_start();
void ignore_loop_end();
void ignore_call_sequence();
void ignore_return_from_sequence();
void ignore_select_block();
void ignore_stop_tape_if_in_48kb_mode();
void ignore_text_description(); void get_set_signal_level();
void ignore_message_block();
void ignore_archive_info();
void get_hardware_type();
void ignore_custom_info_block();
void get_kansas_city_block(); void ignore_text_description();
void ignore_glue_block(); void ignore_message_block();
void ignore_archive_info();
void get_hardware_type();
void ignore_custom_info_block();
struct Data { void get_kansas_city_block();
unsigned int length_of_zero_bit_pulse; void ignore_glue_block();
unsigned int length_of_one_bit_pulse;
unsigned int number_of_bits_in_final_byte;
unsigned int pause_after_block;
uint32_t data_length;
};
struct DataBlock { struct Data {
unsigned int length_of_pilot_pulse; unsigned int length_of_zero_bit_pulse;
unsigned int length_of_sync_first_pulse; unsigned int length_of_one_bit_pulse;
unsigned int length_of_sync_second_pulse; unsigned int number_of_bits_in_final_byte;
unsigned int length_of_pilot_tone; unsigned int pause_after_block;
Data data; uint32_t data_length;
}; };
void get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data); struct DataBlock {
void get_data_block(const DataBlock &); unsigned int length_of_pilot_pulse;
void get_data(const Data &); unsigned int length_of_sync_first_pulse;
unsigned int length_of_sync_second_pulse;
unsigned int length_of_pilot_tone;
Data data;
};
void post_pulses(unsigned int count, unsigned int length); void get_generalised_segment(
void post_pulse(unsigned int length); uint32_t output_symbols,
void post_gap(unsigned int milliseconds); uint8_t max_pulses_per_symbol,
uint8_t number_of_symbols,
bool is_data
);
void get_data_block(const DataBlock &);
void get_data(const Data &);
void post_pulse(const Storage::Time &time); void post_pulses(unsigned int count, unsigned int length);
void post_pulse(unsigned int length);
void post_gap(unsigned int milliseconds);
void post_pulse(const Storage::Time &time);
} serialiser_;
}; };
} }

View File

@ -48,7 +48,9 @@
using namespace Storage::Tape; using namespace Storage::Tape;
PRG::PRG(const std::string &file_name) : PRG::PRG(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
PRG::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read) file_(file_name, FileHolder::FileMode::Read)
{ {
// There's really no way to validate other than that if this file is larger than 64kb, // There's really no way to validate other than that if this file is larger than 64kb,
@ -63,7 +65,7 @@ PRG::PRG(const std::string &file_name) :
throw ErrorBadFormat; throw ErrorBadFormat;
} }
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() { Storage::Tape::Pulse PRG::Serialiser::get_next_pulse() {
// these are all microseconds per pole // these are all microseconds per pole
constexpr unsigned int leader_zero_length = 179; constexpr unsigned int leader_zero_length = 179;
constexpr unsigned int zero_length = 169; constexpr unsigned int zero_length = 169;
@ -73,21 +75,21 @@ Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() {
bit_phase_ = (bit_phase_+1)&3; bit_phase_ = (bit_phase_+1)&3;
if(!bit_phase_) get_next_output_token(); if(!bit_phase_) get_next_output_token();
Tape::Pulse pulse; Pulse pulse;
pulse.length.clock_rate = 1000000; pulse.length.clock_rate = 1000000;
pulse.type = (bit_phase_&1) ? Tape::Pulse::High : Tape::Pulse::Low; pulse.type = (bit_phase_&1) ? Pulse::High : Pulse::Low;
switch(output_token_) { switch(output_token_) {
case Leader: pulse.length.length = leader_zero_length; break; case Leader: pulse.length.length = leader_zero_length; break;
case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break; case Zero: pulse.length.length = (bit_phase_&2) ? one_length : zero_length; break;
case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break; case One: pulse.length.length = (bit_phase_&2) ? zero_length : one_length; break;
case WordMarker: pulse.length.length = (bit_phase_&2) ? one_length : marker_length; break; case WordMarker: pulse.length.length = (bit_phase_&2) ? one_length : marker_length; break;
case EndOfBlock: pulse.length.length = (bit_phase_&2) ? zero_length : marker_length; break; case EndOfBlock: pulse.length.length = (bit_phase_&2) ? zero_length : marker_length; break;
case Silence: pulse.type = Tape::Pulse::Zero; pulse.length.length = 5000; break; case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
} }
return pulse; return pulse;
} }
void PRG::virtual_reset() { void PRG::Serialiser::reset() {
bit_phase_ = 3; bit_phase_ = 3;
file_.seek(2, SEEK_SET); file_.seek(2, SEEK_SET);
file_phase_ = FilePhaseLeadIn; file_phase_ = FilePhaseLeadIn;
@ -95,11 +97,11 @@ void PRG::virtual_reset() {
copy_mask_ = 0x80; copy_mask_ = 0x80;
} }
bool PRG::is_at_end() const { bool PRG::Serialiser::is_at_end() const {
return file_phase_ == FilePhaseAtEnd; return file_phase_ == FilePhaseAtEnd;
} }
void PRG::get_next_output_token() { void PRG::Serialiser::get_next_output_token() {
constexpr int block_length = 192; // not counting the checksum constexpr int block_length = 192; // not counting the checksum
constexpr int countdown_bytes = 9; constexpr int countdown_bytes = 9;
constexpr int leadin_length = 20000; constexpr int leadin_length = 20000;

View File

@ -33,39 +33,42 @@ public:
ErrorBadFormat ErrorBadFormat
}; };
// implemented to satisfy @c Tape
bool is_at_end() const override;
private: private:
FileHolder file_; struct Serialiser: public TapeSerialiser {
Pulse virtual_get_next_pulse() override; Serialiser(const std::string &file_name);
void virtual_reset() override; private:
bool is_at_end() const override;
Pulse get_next_pulse() override;
void reset() override;
uint16_t load_address_; FileHolder file_;
uint16_t length_;
enum FilePhase { uint16_t load_address_;
FilePhaseLeadIn, uint16_t length_;
FilePhaseHeader,
FilePhaseHeaderDataGap,
FilePhaseData,
FilePhaseAtEnd
} file_phase_ = FilePhaseLeadIn;
int phase_offset_ = 0;
int bit_phase_ = 3; enum FilePhase {
enum OutputToken { FilePhaseLeadIn,
Leader, FilePhaseHeader,
Zero, FilePhaseHeaderDataGap,
One, FilePhaseData,
WordMarker, FilePhaseAtEnd
EndOfBlock, } file_phase_ = FilePhaseLeadIn;
Silence int phase_offset_ = 0;
} output_token_;
void get_next_output_token(); int bit_phase_ = 3;
uint8_t output_byte_; enum OutputToken {
uint8_t check_digit_; Leader,
uint8_t copy_mask_ = 0x80; Zero,
One,
WordMarker,
EndOfBlock,
Silence
} output_token_;
void get_next_output_token();
uint8_t output_byte_;
uint8_t check_digit_;
uint8_t copy_mask_ = 0x80;
} serialiser_;
}; };
} }

View File

@ -18,11 +18,9 @@ namespace {
Log::Logger<Log::Source::TapeUEF> logger; Log::Logger<Log::Source::TapeUEF> logger;
}
// MARK: - ZLib extensions // MARK: - ZLib extensions
static float gzgetfloat(gzFile file) { float gzgetfloat(gzFile file) {
uint8_t bytes[4]; uint8_t bytes[4];
gzread(file, bytes, 4); gzread(file, bytes, 4);
@ -49,34 +47,38 @@ static float gzgetfloat(gzFile file) {
return result; return result;
} }
static uint8_t gzget8(gzFile file) { uint8_t gzget8(gzFile file) {
// This is a workaround for gzgetc, which seems to be broken in ZLib 1.2.8. // This is a workaround for gzgetc, which seems to be broken in ZLib 1.2.8.
uint8_t result; uint8_t result;
gzread(file, &result, 1); gzread(file, &result, 1);
return result; return result;
} }
static int gzget16(gzFile file) { int gzget16(gzFile file) {
uint8_t bytes[2]; uint8_t bytes[2];
gzread(file, bytes, 2); gzread(file, bytes, 2);
return bytes[0] | (bytes[1] << 8); return bytes[0] | (bytes[1] << 8);
} }
static int gzget24(gzFile file) { int gzget24(gzFile file) {
uint8_t bytes[3]; uint8_t bytes[3];
gzread(file, bytes, 3); gzread(file, bytes, 3);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16);
} }
static int gzget32(gzFile file) { int gzget32(gzFile file) {
uint8_t bytes[4]; uint8_t bytes[4];
gzread(file, bytes, 4); gzread(file, bytes, 4);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
} }
}
using namespace Storage::Tape; using namespace Storage::Tape;
UEF::UEF(const std::string &file_name) { UEF::UEF(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
UEF::Serialiser::Serialiser(const std::string &file_name) {
file_ = gzopen(file_name.c_str(), "rb"); file_ = gzopen(file_name.c_str(), "rb");
char identifier[10]; char identifier[10];
@ -95,13 +97,13 @@ UEF::UEF(const std::string &file_name) {
set_platform_type(); set_platform_type();
} }
UEF::~UEF() { UEF::Serialiser::~Serialiser() {
gzclose(file_); gzclose(file_);
} }
// MARK: - Public methods // MARK: - Public methods
void UEF::virtual_reset() { void UEF::Serialiser::reset() {
gzseek(file_, 12, SEEK_SET); gzseek(file_, 12, SEEK_SET);
set_is_at_end(false); set_is_at_end(false);
clear(); clear();
@ -109,7 +111,7 @@ void UEF::virtual_reset() {
// MARK: - Chunk navigator // MARK: - Chunk navigator
bool UEF::get_next_chunk(UEF::Chunk &result) { bool UEF::Serialiser::get_next_chunk(Chunk &result) {
const uint16_t chunk_id = uint16_t(gzget16(file_)); const uint16_t chunk_id = uint16_t(gzget16(file_));
const uint32_t chunk_length = uint32_t(gzget32(file_)); const uint32_t chunk_length = uint32_t(gzget32(file_));
const z_off_t start_of_next_chunk = gztell(file_) + chunk_length; const z_off_t start_of_next_chunk = gztell(file_) + chunk_length;
@ -125,7 +127,7 @@ bool UEF::get_next_chunk(UEF::Chunk &result) {
return true; return true;
} }
void UEF::get_next_pulses() { void UEF::Serialiser::get_next_pulses() {
while(empty()) { while(empty()) {
// read chunk details // read chunk details
Chunk next_chunk; Chunk next_chunk;
@ -171,13 +173,13 @@ void UEF::get_next_pulses() {
// MARK: - Chunk parsers // MARK: - Chunk parsers
void UEF::queue_implicit_bit_pattern(uint32_t length) { void UEF::Serialiser::queue_implicit_bit_pattern(uint32_t length) {
while(length--) { while(length--) {
queue_implicit_byte(gzget8(file_)); queue_implicit_byte(gzget8(file_));
} }
} }
void UEF::queue_explicit_bit_pattern(uint32_t length) { void UEF::Serialiser::queue_explicit_bit_pattern(uint32_t length) {
const std::size_t length_in_bits = (length << 3) - size_t(gzget8(file_)); const std::size_t length_in_bits = (length << 3) - size_t(gzget8(file_));
uint8_t current_byte = 0; uint8_t current_byte = 0;
for(std::size_t bit = 0; bit < length_in_bits; bit++) { for(std::size_t bit = 0; bit < length_in_bits; bit++) {
@ -187,14 +189,14 @@ void UEF::queue_explicit_bit_pattern(uint32_t length) {
} }
} }
void UEF::queue_integer_gap() { void UEF::Serialiser::queue_integer_gap() {
Time duration; Time duration;
duration.length = unsigned(gzget16(file_)); duration.length = unsigned(gzget16(file_));
duration.clock_rate = time_base_; duration.clock_rate = time_base_;
emplace_back(Pulse::Zero, duration); emplace_back(Pulse::Zero, duration);
} }
void UEF::queue_floating_point_gap() { void UEF::Serialiser::queue_floating_point_gap() {
const float length = gzgetfloat(file_); const float length = gzgetfloat(file_);
Time duration; Time duration;
duration.length = unsigned(length * 4000000); duration.length = unsigned(length * 4000000);
@ -202,12 +204,12 @@ void UEF::queue_floating_point_gap() {
emplace_back(Pulse::Zero, duration); emplace_back(Pulse::Zero, duration);
} }
void UEF::queue_carrier_tone() { void UEF::Serialiser::queue_carrier_tone() {
unsigned int number_of_cycles = unsigned(gzget16(file_)); unsigned int number_of_cycles = unsigned(gzget16(file_));
while(number_of_cycles--) queue_bit(1); while(number_of_cycles--) queue_bit(1);
} }
void UEF::queue_carrier_tone_with_dummy() { void UEF::Serialiser::queue_carrier_tone_with_dummy() {
unsigned int pre_cycles = unsigned(gzget16(file_)); unsigned int pre_cycles = unsigned(gzget16(file_));
unsigned int post_cycles = unsigned(gzget16(file_)); unsigned int post_cycles = unsigned(gzget16(file_));
while(pre_cycles--) queue_bit(1); while(pre_cycles--) queue_bit(1);
@ -215,7 +217,7 @@ void UEF::queue_carrier_tone_with_dummy() {
while(post_cycles--) queue_bit(1); while(post_cycles--) queue_bit(1);
} }
void UEF::queue_security_cycles() { void UEF::Serialiser::queue_security_cycles() {
int number_of_cycles = gzget24(file_); int number_of_cycles = gzget24(file_);
bool first_is_pulse = gzget8(file_) == 'P'; bool first_is_pulse = gzget8(file_) == 'P';
bool last_is_pulse = gzget8(file_) == 'P'; bool last_is_pulse = gzget8(file_) == 'P';
@ -241,7 +243,7 @@ void UEF::queue_security_cycles() {
} }
} }
void UEF::queue_defined_data(uint32_t length) { void UEF::Serialiser::queue_defined_data(uint32_t length) {
if(length < 3) return; if(length < 3) return;
const int bits_per_packet = gzget8(file_); const int bits_per_packet = gzget8(file_);
@ -287,7 +289,7 @@ void UEF::queue_defined_data(uint32_t length) {
// MARK: - Queuing helpers // MARK: - Queuing helpers
void UEF::queue_implicit_byte(uint8_t byte) { void UEF::Serialiser::queue_implicit_byte(uint8_t byte) {
queue_bit(0); queue_bit(0);
int c = 8; int c = 8;
while(c--) { while(c--) {
@ -297,7 +299,7 @@ void UEF::queue_implicit_byte(uint8_t byte) {
queue_bit(1); queue_bit(1);
} }
void UEF::queue_bit(int bit) { void UEF::Serialiser::queue_bit(int bit) {
int number_of_cycles; int number_of_cycles;
Time duration; Time duration;
duration.clock_rate = time_base_ * 4; duration.clock_rate = time_base_ * 4;
@ -323,10 +325,14 @@ void UEF::queue_bit(int bit) {
// MARK: - TypeDistinguisher // MARK: - TypeDistinguisher
TargetPlatform::Type UEF::target_platform_type() { TargetPlatform::Type UEF::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type UEF::Serialiser::target_platform_type() {
return platform_type_; return platform_type_;
} }
void UEF::set_platform_type() { void UEF::Serialiser::set_platform_type() {
// If a chunk of type 0005 exists anywhere in the UEF then the UEF specifies its target machine. // If a chunk of type 0005 exists anywhere in the UEF then the UEF specifies its target machine.
// So check and, if so, update the list of machines for which this file thinks it is suitable. // So check and, if so, update the list of machines for which this file thinks it is suitable.
Chunk next_chunk; Chunk next_chunk;

View File

@ -21,7 +21,7 @@ namespace Storage::Tape {
/*! /*!
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses. Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
*/ */
class UEF : public PulseQueuedTape, public TargetPlatform::TypeDistinguisher { class UEF : public Tape, public TargetPlatform::TypeDistinguisher {
public: public:
/*! /*!
Constructs a @c UEF containing content from the file with name @c file_name. Constructs a @c UEF containing content from the file with name @c file_name.
@ -29,46 +29,54 @@ public:
@throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF. @throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF.
*/ */
UEF(const std::string &file_name); UEF(const std::string &file_name);
~UEF();
enum { enum {
ErrorNotUEF ErrorNotUEF
}; };
private: private:
void virtual_reset() override;
void set_platform_type();
TargetPlatform::Type target_platform_type() override; TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
gzFile file_; struct Serialiser: public PulseQueuedSerialiser {
unsigned int time_base_ = 1200; Serialiser(const std::string &file_name);
bool is_300_baud_ = false; ~Serialiser();
struct Chunk { TargetPlatform::Type target_platform_type();
uint16_t id;
uint32_t length;
z_off_t start_of_next_chunk;
};
bool get_next_chunk(Chunk &); private:
void get_next_pulses() override; void reset() override;
void queue_implicit_bit_pattern(uint32_t length); void set_platform_type();
void queue_explicit_bit_pattern(uint32_t length); TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
void queue_integer_gap(); gzFile file_;
void queue_floating_point_gap(); unsigned int time_base_ = 1200;
bool is_300_baud_ = false;
void queue_carrier_tone(); struct Chunk {
void queue_carrier_tone_with_dummy(); uint16_t id;
uint32_t length;
z_off_t start_of_next_chunk;
};
void queue_security_cycles(); bool get_next_chunk(Chunk &);
void queue_defined_data(uint32_t length); void get_next_pulses() override;
void queue_bit(int bit); void queue_implicit_bit_pattern(uint32_t length);
void queue_implicit_byte(uint8_t byte); void queue_explicit_bit_pattern(uint32_t length);
void queue_integer_gap();
void queue_floating_point_gap();
void queue_carrier_tone();
void queue_carrier_tone_with_dummy();
void queue_security_cycles();
void queue_defined_data(uint32_t length);
void queue_bit(int bit);
void queue_implicit_byte(uint8_t byte);
} serialiser_;
}; };
} }

View File

@ -11,7 +11,9 @@
using namespace Storage::Tape; using namespace Storage::Tape;
ZX80O81P::ZX80O81P(const std::string &file_name) { ZX80O81P::ZX80O81P(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
ZX80O81P::Serialiser::Serialiser(const std::string &file_name) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read); Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
// Grab the actual file contents // Grab the actual file contents
@ -31,10 +33,10 @@ ZX80O81P::ZX80O81P(const std::string &file_name) {
if(!zx_file) throw ErrorNotZX80O81P; if(!zx_file) throw ErrorNotZX80O81P;
// then rewind and start again // then rewind and start again
virtual_reset(); reset();
} }
void ZX80O81P::virtual_reset() { void ZX80O81P::Serialiser::reset() {
data_pointer_ = 0; data_pointer_ = 0;
is_past_silence_ = false; is_past_silence_ = false;
has_ended_final_byte_ = false; has_ended_final_byte_ = false;
@ -42,16 +44,16 @@ void ZX80O81P::virtual_reset() {
bit_pointer_ = wave_pointer_ = 0; bit_pointer_ = wave_pointer_ = 0;
} }
bool ZX80O81P::has_finished_data() const { bool ZX80O81P::Serialiser::has_finished_data() const {
return (data_pointer_ == data_.size()) && !wave_pointer_ && !bit_pointer_; return (data_pointer_ == data_.size()) && !wave_pointer_ && !bit_pointer_;
} }
bool ZX80O81P::is_at_end() const { bool ZX80O81P::Serialiser::is_at_end() const {
return has_finished_data() && has_ended_final_byte_; return has_finished_data() && has_ended_final_byte_;
} }
Tape::Pulse ZX80O81P::virtual_get_next_pulse() { Pulse ZX80O81P::Serialiser::get_next_pulse() {
Tape::Pulse pulse; Pulse pulse;
// Start with 1 second of silence. // Start with 1 second of silence.
if(!is_past_silence_ || has_finished_data()) { if(!is_past_silence_ || has_finished_data()) {
@ -104,5 +106,9 @@ Tape::Pulse ZX80O81P::virtual_get_next_pulse() {
} }
TargetPlatform::Type ZX80O81P::target_platform_type() { TargetPlatform::Type ZX80O81P::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type ZX80O81P::Serialiser::target_platform_type() {
return platform_type_; return platform_type_;
} }

View File

@ -36,25 +36,30 @@ public:
}; };
private: private:
// implemented to satisfy @c Tape // TargetPlatform::TypeDistinguisher.
bool is_at_end() const override;
// implemented to satisfy TargetPlatform::TypeDistinguisher
TargetPlatform::Type target_platform_type() override; TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type platform_type_;
void virtual_reset() override; struct Serialiser: public TapeSerialiser {
Pulse virtual_get_next_pulse() override; Serialiser(const std::string &file_name);
bool has_finished_data() const; TargetPlatform::Type target_platform_type();
uint8_t byte_; private:
int bit_pointer_; bool is_at_end() const override;
int wave_pointer_; void reset() override;
bool is_past_silence_, has_ended_final_byte_; Pulse get_next_pulse() override;
bool is_high_; bool has_finished_data() const;
std::vector<uint8_t> data_; TargetPlatform::Type platform_type_;
std::size_t data_pointer_;
uint8_t byte_;
int bit_pointer_;
int wave_pointer_;
bool is_past_silence_, has_ended_final_byte_;
bool is_high_;
std::vector<uint8_t> data_;
std::size_t data_pointer_;
} serialiser_;
}; };
} }

View File

@ -18,7 +18,9 @@ using namespace Storage::Tape;
https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format
*/ */
ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) : Tape(serialiser_), serialiser_(file_name) {}
ZXSpectrumTAP::Serialiser::Serialiser(const std::string &file_name) :
file_(file_name, FileHolder::FileMode::Read) file_(file_name, FileHolder::FileMode::Read)
{ {
// Check for a continuous series of blocks through to // Check for a continuous series of blocks through to
@ -28,26 +30,26 @@ ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) :
// and type ff for valid checksums? // and type ff for valid checksums?
while(file_.tell() != file_.stats().st_size) { while(file_.tell() != file_.stats().st_size) {
const uint16_t block_length = file_.get16le(); const uint16_t block_length = file_.get16le();
if(file_.eof() || file_.tell() + block_length >= file_.stats().st_size) { if(file_.eof() || file_.tell() + block_length > file_.stats().st_size) {
throw ErrorNotZXSpectrumTAP; throw ErrorNotZXSpectrumTAP;
} }
file_.seek(block_length, SEEK_CUR); file_.seek(block_length, SEEK_CUR);
} }
virtual_reset(); reset();
} }
bool ZXSpectrumTAP::is_at_end() const { bool ZXSpectrumTAP::Serialiser::is_at_end() const {
return file_.tell() == file_.stats().st_size && phase_ == Phase::Gap; return file_.tell() == file_.stats().st_size && phase_ == Phase::Gap;
} }
void ZXSpectrumTAP::virtual_reset() { void ZXSpectrumTAP::Serialiser::reset() {
file_.seek(0, SEEK_SET); file_.seek(0, SEEK_SET);
read_next_block(); read_next_block();
} }
Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() { Pulse ZXSpectrumTAP::Serialiser::get_next_pulse() {
// Adopt a general pattern of high then low. // Adopt a general pattern of high then low.
Pulse pulse; Pulse pulse;
pulse.type = (distance_into_phase_ & 1) ? Pulse::Type::High : Pulse::Type::Low; pulse.type = (distance_into_phase_ & 1) ? Pulse::Type::High : Pulse::Type::Low;
@ -111,7 +113,7 @@ Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() {
return pulse; return pulse;
} }
void ZXSpectrumTAP::read_next_block() { void ZXSpectrumTAP::Serialiser::read_next_block() {
if(file_.tell() == file_.stats().st_size) { if(file_.tell() == file_.stats().st_size) {
phase_ = Phase::Gap; phase_ = Phase::Gap;
} else { } else {

View File

@ -34,23 +34,27 @@ public:
}; };
private: private:
Storage::FileHolder file_; struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
private:
Storage::FileHolder file_;
uint16_t block_length_ = 0; uint16_t block_length_ = 0;
uint8_t block_type_ = 0; uint8_t block_type_ = 0;
uint8_t data_byte_ = 0; uint8_t data_byte_ = 0;
enum Phase { enum Phase {
PilotTone, PilotTone,
Data, Data,
Gap Gap
} phase_ = Phase::PilotTone; } phase_ = Phase::PilotTone;
int distance_into_phase_ = 0; int distance_into_phase_ = 0;
void read_next_block(); void read_next_block();
// Implemented to satisfy @c Tape. // Implemented to satisfy @c Tape.
bool is_at_end() const override; bool is_at_end() const override;
void virtual_reset() override; void reset() override;
Pulse virtual_get_next_pulse() override; Pulse get_next_pulse() override;
} serialiser_;
}; };
} }

View File

@ -61,7 +61,7 @@ void Parser::acorn_shifter_output_bit(int value) {
push_symbol(value ? SymbolType::One : SymbolType::Zero); push_symbol(value ? SymbolType::One : SymbolType::Zero);
} }
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
shifter_.process_pulse(pulse); shifter_.process_pulse(pulse);
} }
@ -72,10 +72,10 @@ Shifter::Shifter() :
input_pattern_(0), input_pattern_(0),
delegate_(nullptr) {} delegate_(nullptr) {}
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Shifter::process_pulse(const Storage::Tape::Pulse &pulse) {
pll_.run_for(Cycles(int(float(PLLClockRate) * pulse.length.get<float>()))); pll_.run_for(Cycles(int(float(PLLClockRate) * pulse.length.get<float>())));
const bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; const bool is_high = pulse.type == Storage::Tape::Pulse::High;
if(is_high != was_high_) { if(is_high != was_high_) {
pll_.add_pulse(); pll_.add_pulse();
} }

View File

@ -18,7 +18,7 @@ class Shifter {
public: public:
Shifter(); Shifter();
void process_pulse(const Storage::Tape::Tape::Pulse &); void process_pulse(const Storage::Tape::Pulse &);
struct Delegate { struct Delegate {
virtual void acorn_shifter_output_bit(int value) = 0; virtual void acorn_shifter_output_bit(int value) = 0;
@ -55,7 +55,7 @@ public:
private: private:
void acorn_shifter_output_bit(int value) override; void acorn_shifter_output_bit(int value) override;
void process_pulse(const Storage::Tape::Tape::Pulse &) override; void process_pulse(const Storage::Tape::Pulse &) override;
bool did_update_shifter(int new_value, int length); bool did_update_shifter(int new_value, int length);
CRC::Generator<uint16_t, 0x0000, 0x0000, false, false> crc_; CRC::Generator<uint16_t, 0x0000, 0x0000, false, false> crc_;

View File

@ -230,12 +230,12 @@ 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(const Storage::Tape::Tape::Pulse &pulse) { void Parser::process_pulse(const Storage::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: 182us => 0.000364s cycle // short: 182us => 0.000364s cycle
// medium: 262us => 0.000524s cycle // medium: 262us => 0.000524s cycle
// long: 342us => 0.000684s cycle // long: 342us => 0.000684s cycle
const bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; const bool is_high = pulse.type == Storage::Tape::Pulse::High;
if(!is_high && previous_was_high_) { if(!is_high && previous_was_high_) {
if(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised); if(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised);
else if(wave_period_ >= 0.000604) push_wave(WaveType::Long); else if(wave_period_ >= 0.000604) push_wave(WaveType::Long);

View File

@ -117,7 +117,7 @@ private:
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(const Storage::Tape::Tape::Pulse &pulse) override; void process_pulse(const Storage::Tape::Pulse &pulse) override;
bool previous_was_high_ = false; bool previous_was_high_ = false;
float wave_period_ = 0.0f; float wave_period_ = 0.0f;

View File

@ -40,12 +40,12 @@ bool Parser::sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Ta
return false; return false;
} }
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
constexpr float maximum_short_length = 0.000512f; constexpr float maximum_short_length = 0.000512f;
constexpr float maximum_medium_length = 0.000728f; constexpr float maximum_medium_length = 0.000728f;
constexpr float maximum_long_length = 0.001456f; constexpr float maximum_long_length = 0.001456f;
const bool wave_is_high = pulse.type == Storage::Tape::Tape::Pulse::High; const bool wave_is_high = pulse.type == Storage::Tape::Pulse::High;
if(!wave_was_high_ && wave_is_high != wave_was_high_) { if(!wave_was_high_ && wave_is_high != wave_was_high_) {
if(cycle_length_ < maximum_short_length) push_wave(WaveType::Short); if(cycle_length_ < maximum_short_length) push_wave(WaveType::Short);
else if(cycle_length_ < maximum_medium_length) push_wave(WaveType::Medium); else if(cycle_length_ < maximum_medium_length) push_wave(WaveType::Medium);

View File

@ -29,7 +29,7 @@ public:
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(const Storage::Tape::Tape::Pulse &pulse) override; void process_pulse(const Storage::Tape::Pulse &pulse) override;
void inspect_waves(const std::vector<WaveType> &waves) override; void inspect_waves(const std::vector<WaveType> &waves) override;
enum DetectionMode { enum DetectionMode {

View File

@ -25,8 +25,8 @@ using namespace Storage::Tape::ZXSpectrum;
Parser::Parser(MachineType machine_type) : Parser::Parser(MachineType machine_type) :
machine_type_(machine_type) {} machine_type_(machine_type) {}
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) { void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
if(pulse.type == Storage::Tape::Tape::Pulse::Type::Zero) { if(pulse.type == Storage::Tape::Pulse::Type::Zero) {
push_wave(WaveType::Gap); push_wave(WaveType::Gap);
return; return;
} }

View File

@ -95,7 +95,7 @@ public:
Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful
for picking up fast loading from an ongoing tape. for picking up fast loading from an ongoing tape.
*/ */
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; void process_pulse(const Storage::Tape::Pulse &pulse) override;
private: private:
const MachineType machine_type_; const MachineType machine_type_;

View File

@ -69,7 +69,7 @@ protected:
/*! /*!
Should be implemented by subclasses. Consumes @c pulse. Should be implemented by subclasses. Consumes @c pulse.
*/ */
virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; virtual void process_pulse(const Storage::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
@ -104,7 +104,7 @@ protected:
*/ */
template <typename WaveType, typename SymbolType> class PulseClassificationParser: public Parser<SymbolType> { template <typename WaveType, typename SymbolType> class PulseClassificationParser: public Parser<SymbolType> {
public: public:
virtual void process_pulse(const Storage::Tape::Tape::Pulse &pulse) = 0; virtual void process_pulse(const Storage::Tape::Pulse &pulse) = 0;
/* /*
process_pulse should either call @c push_wave or to take no action. process_pulse should either call @c push_wave or to take no action.

View File

@ -12,10 +12,10 @@ 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(const Storage::Tape::Tape::Pulse &pulse) { void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
// If this is anything other than a transition from low to high, just add it to the // If this is anything other than a transition from low to high, just add it to the
// count of time. // count of time.
const bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High; const bool pulse_is_high = pulse.type == Storage::Tape::Pulse::High;
const bool pulse_did_change = pulse_is_high != pulse_was_high_; const bool pulse_did_change = pulse_is_high != pulse_was_high_;
pulse_was_high_ = pulse_is_high; pulse_was_high_ = pulse_is_high;
if(!pulse_did_change || !pulse_is_high) { if(!pulse_did_change || !pulse_is_high) {

View File

@ -47,7 +47,7 @@ private:
Time pulse_time_; Time pulse_time_;
void post_pulse(); void post_pulse();
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override; void process_pulse(const Storage::Tape::Pulse &pulse) override;
void mark_end() override; void mark_end() override;
void inspect_waves(const std::vector<WaveType> &waves) override; void inspect_waves(const std::vector<WaveType> &waves) override;

View File

@ -10,36 +10,34 @@
using namespace Storage::Tape; using namespace Storage::Tape;
PulseQueuedTape::PulseQueuedTape() : pulse_pointer_(0), is_at_end_(false) {} bool PulseQueuedSerialiser::is_at_end() const {
bool PulseQueuedTape::is_at_end() const {
return is_at_end_; return is_at_end_;
} }
void PulseQueuedTape::set_is_at_end(const bool is_at_end) { void PulseQueuedSerialiser::set_is_at_end(const bool is_at_end) {
is_at_end_ = is_at_end; is_at_end_ = is_at_end;
} }
void PulseQueuedTape::clear() { void PulseQueuedSerialiser::clear() {
queued_pulses_.clear(); queued_pulses_.clear();
pulse_pointer_ = 0; pulse_pointer_ = 0;
} }
bool PulseQueuedTape::empty() const { bool PulseQueuedSerialiser::empty() const {
return queued_pulses_.empty(); return queued_pulses_.empty();
} }
void PulseQueuedTape::emplace_back(const Tape::Pulse::Type type, const Time length) { void PulseQueuedSerialiser::emplace_back(const Pulse::Type type, const Time length) {
queued_pulses_.emplace_back(type, length); queued_pulses_.emplace_back(type, length);
} }
void PulseQueuedTape::push_back(const Tape::Pulse pulse) { void PulseQueuedSerialiser::push_back(const Pulse pulse) {
queued_pulses_.push_back(pulse); queued_pulses_.push_back(pulse);
} }
Tape::Pulse PulseQueuedTape::virtual_get_next_pulse() { Pulse PulseQueuedSerialiser::get_next_pulse() {
const auto silence = [] { const auto silence = [] {
return Tape::Pulse(Tape::Pulse::Type::Zero, Storage::Time(1, 1)); return Pulse(Pulse::Type::Zero, Storage::Time(1, 1));
}; };
if(is_at_end_) { if(is_at_end_) {

View File

@ -23,26 +23,23 @@ namespace Storage::Tape {
anything there, and otherwise calls get_next_pulses(). get_next_pulses() is anything there, and otherwise calls get_next_pulses(). get_next_pulses() is
virtual, giving subclasses a chance to provide the next batch of pulses. virtual, giving subclasses a chance to provide the next batch of pulses.
*/ */
class PulseQueuedTape: public Tape { class PulseQueuedSerialiser: public TapeSerialiser {
public: public:
PulseQueuedTape(); void emplace_back(Pulse::Type, Time);
bool is_at_end() const override; void push_back(Pulse);
protected:
void emplace_back(Tape::Pulse::Type type, Time length);
void push_back(Tape::Pulse);
void clear(); void clear();
bool empty() const; bool empty() const;
void set_is_at_end(bool); void set_is_at_end(bool);
Pulse get_next_pulse() override;
bool is_at_end() const override;
virtual void get_next_pulses() = 0; virtual void get_next_pulses() = 0;
private: private:
Pulse virtual_get_next_pulse() override;
std::vector<Pulse> queued_pulses_; std::vector<Pulse> queued_pulses_;
std::size_t pulse_pointer_; std::size_t pulse_pointer_ = 0;
bool is_at_end_; bool is_at_end_ = false;
}; };
} }

View File

@ -16,9 +16,11 @@ TapePlayer::TapePlayer(int input_clock_rate) :
TimedEventLoop(input_clock_rate) TimedEventLoop(input_clock_rate)
{} {}
Tape::Tape(TapeSerialiser &serialiser) : serialiser_(serialiser) {}
// MARK: - Seeking // MARK: - Seeking
void Storage::Tape::Tape::seek(Time &seek_time) { void Storage::Tape::Tape::seek(const Time seek_time) {
Time next_time(0); Time next_time(0);
reset(); reset();
while(next_time <= seek_time) { while(next_time <= seek_time) {
@ -40,11 +42,11 @@ Storage::Time Tape::get_current_time() {
void Storage::Tape::Tape::reset() { void Storage::Tape::Tape::reset() {
offset_ = 0; offset_ = 0;
virtual_reset(); serialiser_.reset();
} }
Tape::Pulse Tape::get_next_pulse() { Pulse Tape::get_next_pulse() {
pulse_ = virtual_get_next_pulse(); pulse_ = serialiser_.get_next_pulse();
offset_++; offset_++;
return pulse_; return pulse_;
} }
@ -62,6 +64,11 @@ void Tape::set_offset(uint64_t offset) {
while(offset--) get_next_pulse(); while(offset--) get_next_pulse();
} }
bool Tape::is_at_end() const {
return serialiser_.is_at_end();
}
// MARK: - Player // MARK: - Player
ClockingHint::Preference TapePlayer::preferred_clocking() const { ClockingHint::Preference TapePlayer::preferred_clocking() const {
@ -79,7 +86,7 @@ std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
return tape_; return tape_;
} }
bool TapePlayer::has_tape() { bool TapePlayer::has_tape() const {
return bool(tape_); return bool(tape_);
} }
@ -91,13 +98,13 @@ void TapePlayer::get_next_pulse() {
} else { } else {
current_pulse_.length.length = 1; current_pulse_.length.length = 1;
current_pulse_.length.clock_rate = 1; current_pulse_.length.clock_rate = 1;
current_pulse_.type = Tape::Pulse::Zero; current_pulse_.type = Pulse::Zero;
} }
set_next_event_time_interval(current_pulse_.length); set_next_event_time_interval(current_pulse_.length);
} }
Tape::Pulse TapePlayer::get_current_pulse() { Pulse TapePlayer::get_current_pulse() const {
return current_pulse_; return current_pulse_;
} }
@ -170,8 +177,8 @@ void BinaryTapePlayer::set_delegate(Delegate *delegate) {
delegate_ = delegate; delegate_ = delegate;
} }
void BinaryTapePlayer::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) { void BinaryTapePlayer::process_input_pulse(const Storage::Tape::Pulse &pulse) {
bool new_input_level = pulse.type == Tape::Pulse::High; bool new_input_level = pulse.type == Pulse::High;
if(input_level_ != new_input_level) { if(input_level_ != new_input_level) {
input_level_ = new_input_level; input_level_ = new_input_level;
if(delegate_) delegate_->tape_did_change_input(this); if(delegate_) delegate_->tape_did_change_input(this);

View File

@ -8,17 +8,36 @@
#pragma once #pragma once
#include <memory>
#include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp" #include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../TimedEventLoop.hpp" #include "../TimedEventLoop.hpp"
#include "../../Activity/Source.hpp" #include "../../Activity/Source.hpp"
#include "../TargetPlatforms.hpp"
#include <memory>
#include <optional>
namespace Storage::Tape { namespace Storage::Tape {
struct Pulse {
enum Type {
High, Low, Zero
} type;
Time length;
Pulse(Type type, Time length) : type(type), length(length) {}
Pulse() = default;
};
class TapeSerialiser {
public:
virtual Pulse get_next_pulse() = 0;
virtual void reset() = 0;
virtual bool is_at_end() const = 0;
};
/*! /*!
Models a tape as a sequence of pulses, each pulse being of arbitrary length and described Models a tape as a sequence of pulses, each pulse being of arbitrary length and described
by their relationship with zero: by their relationship with zero:
@ -32,15 +51,6 @@ namespace Storage::Tape {
*/ */
class Tape { class Tape {
public: public:
struct Pulse {
enum Type {
High, Low, Zero
} type;
Time length;
Pulse(Type type, Time length) : type(type), length(length) {}
Pulse() = default;
};
/*! /*!
If at the start of the tape returns the first stored pulse. Otherwise advances past If at the start of the tape returns the first stored pulse. Otherwise advances past
@ -54,38 +64,37 @@ public:
void reset(); void reset();
/// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise. /// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise.
virtual bool is_at_end() const = 0; bool is_at_end() const;
/*! /*!
Returns a numerical representation of progression into the tape. Precision is arbitrary but Returns a numerical representation of progression into the tape. Precision is arbitrary but
required to be at least to the whole pulse. Greater numbers are later than earlier numbers, required to be at least to the whole pulse. Greater numbers are later than earlier numbers,
but not necessarily continuous. but not necessarily continuous.
*/ */
virtual uint64_t get_offset() const; uint64_t get_offset() const;
/*! /*!
Moves the tape to the first time at which the specified offset would be returned by get_offset. Moves the tape to the first time at which the specified offset would be returned by get_offset.
*/ */
virtual void set_offset(uint64_t); void set_offset(uint64_t);
/*! /*!
Calculates and returns the amount of time that has elapsed since the time began. Potentially expensive. Calculates and returns the amount of time that has elapsed since the time began. Potentially expensive.
*/ */
virtual Time get_current_time(); Time get_current_time();
/*! /*!
Seeks to @c time. Potentially expensive. Seeks to @c time. Potentially expensive.
*/ */
virtual void seek(Time &time); void seek(Time);
Tape(TapeSerialiser &);
virtual ~Tape() = default; virtual ~Tape() = default;
private: private:
uint64_t offset_; uint64_t offset_;
Tape::Pulse pulse_; Pulse pulse_;
TapeSerialiser &serialiser_;
virtual Pulse virtual_get_next_pulse() = 0;
virtual void virtual_reset() = 0;
}; };
/*! /*!
@ -101,7 +110,7 @@ public:
virtual ~TapePlayer() = default; virtual ~TapePlayer() = default;
void set_tape(std::shared_ptr<Storage::Tape::Tape> tape); void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
bool has_tape(); bool has_tape() const;
std::shared_ptr<Storage::Tape::Tape> get_tape(); std::shared_ptr<Storage::Tape::Tape> get_tape();
void run_for(const Cycles cycles); void run_for(const Cycles cycles);
@ -110,18 +119,18 @@ public:
ClockingHint::Preference preferred_clocking() const override; ClockingHint::Preference preferred_clocking() const override;
Tape::Pulse get_current_pulse(); Pulse get_current_pulse() const;
void complete_pulse(); void complete_pulse();
protected: protected:
virtual void process_next_event() override; virtual void process_next_event() override;
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0; virtual void process_input_pulse(const Pulse &pulse) = 0;
private: private:
inline void get_next_pulse(); inline void get_next_pulse();
std::shared_ptr<Storage::Tape::Tape> tape_; std::shared_ptr<Storage::Tape::Tape> tape_;
Tape::Pulse current_pulse_; Pulse current_pulse_;
}; };
/*! /*!
@ -154,7 +163,7 @@ public:
protected: protected:
Delegate *delegate_ = nullptr; Delegate *delegate_ = nullptr;
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) final; void process_input_pulse(const Storage::Tape::Pulse &pulse) final;
bool input_level_ = false; bool input_level_ = false;
bool motor_is_running_ = false; bool motor_is_running_ = false;