1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-29 04:33:04 +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);
}
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);
}

View File

@ -46,7 +46,7 @@ public:
void acorn_shifter_output_bit(int value);
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 get_next_tape_pulse();

View File

@ -59,7 +59,9 @@ namespace {
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);
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;
}
void CAS::virtual_reset() {
void CAS::Serialiser::reset() {
phase_ = Phase::Header;
chunk_pointer_ = 0;
distance_into_phase_ = 0;
distance_into_bit_ = 0;
}
Tape::Pulse CAS::virtual_get_next_pulse() {
Pulse CAS::Serialiser::get_next_pulse() {
Pulse pulse;
pulse.length.clock_rate = 9600;
// 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
};
// implemented to satisfy @c Tape
bool is_at_end() const override;
private:
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
// Storage for the array of data blobs to transcribe into audio;
// each chunk is preceded by a header which may be long, and is optionally
// also preceded by a gap.
struct Chunk {
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
private:
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) :
has_gap(has_gap), long_header(long_header), data(std::move(data)) {}
};
std::vector<Chunk> chunks_;
// Storage for the array of data blobs to transcribe into audio;
// each chunk is preceded by a header which may be long, and is optionally
// also preceded by a gap.
struct Chunk {
bool has_gap;
bool long_header;
std::vector<std::uint8_t> data;
// Tracker for active state within the file list.
std::size_t chunk_pointer_ = 0;
enum class Phase {
Header,
Bytes,
Gap,
EndOfFile
} phase_ = Phase::Header;
std::size_t distance_into_phase_ = 0;
std::size_t distance_into_bit_ = 0;
Chunk(bool has_gap, bool long_header, const std::vector<std::uint8_t> &data) :
has_gap(has_gap), long_header(long_header), data(std::move(data)) {}
};
std::vector<Chunk> chunks_;
// Tracker for active state within the file list.
std::size_t chunk_pointer_ = 0;
enum class Phase {
Header,
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;
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) {
Storage::FileHolder file(file_name, FileHolder::FileMode::Read);
if(file.stats().st_size < 0x20) throw ErrorNotCSW;
@ -86,20 +93,20 @@ CSW::CSW(const std::string &file_name) :
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_.type = initial_level ? Pulse::High : Pulse::Low;
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;
uint8_t result = source_data_[source_data_pointer_];
source_data_pointer_++;
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;
uint32_t result = uint32_t(
(source_data_[source_data_pointer_ + 0] << 0) |
@ -110,19 +117,19 @@ uint32_t CSW::get_next_int32le() {
return result;
}
void CSW::invert_pulse() {
void CSW::Serialiser::invert_pulse() {
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();
}
void CSW::virtual_reset() {
void CSW::Serialiser::reset() {
source_data_pointer_ = 0;
}
Tape::Pulse CSW::virtual_get_next_pulse() {
Pulse CSW::Serialiser::get_next_pulse() {
invert_pulse();
pulse_.length.length = get_next_byte();
if(!pulse_.length.length) pulse_.length.length = get_next_int32le();

View File

@ -21,6 +21,11 @@ namespace Storage::Tape {
*/
class CSW: public Tape {
public:
enum class CompressionType {
RLE,
ZRLE
};
/*!
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);
enum class CompressionType {
RLE,
ZRLE
};
/*!
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 {
ErrorNotCSW
};
// implemented to satisfy @c Tape
bool is_at_end() const override;
private:
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
Serialiser(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
Pulse pulse_;
CompressionType compression_type_;
private:
// implemented to satisfy @c Tape
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
uint8_t get_next_byte();
uint32_t get_next_int32le();
void invert_pulse();
Pulse pulse_;
CompressionType compression_type_;
std::vector<uint8_t> source_data_;
std::size_t source_data_pointer_;
uint8_t get_next_byte();
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;
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)
{
if(!file_.check_signature("C64-TAPE-RAW"))
@ -39,17 +41,17 @@ CommodoreTAP::CommodoreTAP(const std::string &file_name) :
current_pulse_.type = Pulse::High;
}
void CommodoreTAP::virtual_reset() {
void CommodoreTAP::Serialiser::reset() {
file_.seek(0x14, SEEK_SET);
current_pulse_.type = Pulse::High;
is_at_end_ = false;
}
bool CommodoreTAP::is_at_end() const {
bool CommodoreTAP::Serialiser::is_at_end() const {
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_) {
return current_pulse_;
}

View File

@ -32,19 +32,23 @@ public:
ErrorNotCommodoreTAP
};
// implemented to satisfy @c Tape
bool is_at_end() const override;
private:
Storage::FileHolder file_;
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
bool updated_layout_;
uint32_t file_size_;
private:
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
Pulse current_pulse_;
bool is_at_end_ = false;
Storage::FileHolder file_;
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;
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)
{
// 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
virtual_reset();
// Rewind and start again.
reset();
}
void OricTAP::virtual_reset() {
void OricTAP::Serialiser::reset() {
file_.seek(0, SEEK_SET);
bit_count_ = 13;
phase_ = next_phase_ = LeadIn;
@ -41,7 +44,7 @@ void OricTAP::virtual_reset() {
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.
if(bit_count_ == 13) {
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 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.
Tape::Pulse pulse;
Pulse pulse;
pulse.length.clock_rate = 4800;
int next_bit;
@ -158,6 +161,6 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() {
return pulse;
}
bool OricTAP::is_at_end() const {
bool OricTAP::Serialiser::is_at_end() const {
return phase_ == End;
}

View File

@ -32,24 +32,28 @@ public:
ErrorNotOricTAP
};
// implemented to satisfy @c Tape
bool is_at_end() const override;
private:
Storage::FileHolder file_;
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
// byte serialisation and output
uint16_t current_value_;
int bit_count_;
int pulse_counter_;
private:
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
enum Phase {
LeadIn, Header, Data, Gap, End
} phase_, next_phase_;
int phase_counter_;
uint16_t data_end_address_, data_start_address_;
Storage::FileHolder file_;
// byte serialisation and output
uint16_t current_value_;
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),
current_level_(false) {
@ -36,10 +38,10 @@ TZX::TZX(const std::string &file_name) :
// Reject if an incompatible version
if(major_version != 1 || minor_version > 21) throw ErrorNotTZX;
virtual_reset();
reset();
}
void TZX::virtual_reset() {
void TZX::Serialiser::reset() {
clear();
set_is_at_end(false);
file_.seek(0x0a, SEEK_SET);
@ -51,7 +53,7 @@ void TZX::virtual_reset() {
post_gap(500);
}
void TZX::get_next_pulses() {
void TZX::Serialiser::get_next_pulses() {
while(empty()) {
uint8_t chunk_id = file_.get8();
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 uint16_t pause_after_block = file_.get16le();
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);
while(!csw.is_at_end()) {
Tape::Pulse next_pulse = csw.get_next_pulse();
current_level_ = (next_pulse.type == Tape::Pulse::High);
Pulse next_pulse = csw.get_next_pulse();
current_level_ = (next_pulse.type == Pulse::High);
push_back(next_pulse);
}
@ -122,7 +124,7 @@ void TZX::get_csw_recording_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();
long endpoint = file_.tell() + long(block_length);
uint16_t pause_after_block = file_.get16le();
@ -143,7 +145,12 @@ void TZX::get_generalised_data_block() {
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;
// 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;
data_block.length_of_pilot_pulse = 2168;
data_block.length_of_sync_first_pulse = 667;
@ -222,7 +229,7 @@ void TZX::get_standard_speed_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;
data_block.length_of_pilot_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);
}
void TZX::get_data_block(const DataBlock &data_block) {
void TZX::Serialiser::get_data_block(const DataBlock &data_block) {
// Output pilot tone.
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);
}
void TZX::get_data(const Data &data) {
void TZX::Serialiser::get_data(const Data &data) {
// Output data.
for(decltype(data.data_length) c = 0; c < data.data_length; c++) {
uint8_t next_byte = file_.get8();
@ -267,14 +274,14 @@ void TZX::get_data(const Data &data) {
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 nunber_of_pulses = file_.get16le();
post_pulses(nunber_of_pulses, length_of_pulse);
}
void TZX::get_pure_data_block() {
void TZX::Serialiser::get_pure_data_block() {
Data data;
data.length_of_zero_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);
}
void TZX::get_direct_recording_block() {
void TZX::Serialiser::get_direct_recording_block() {
const Storage::Time length_per_sample(unsigned(file_.get16le()), StandardTZXClock);
const uint16_t pause_after_block = file_.get16le();
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((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;
level = byte&0x80;
}
@ -310,19 +317,19 @@ void TZX::get_direct_recording_block() {
}
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);
}
void TZX::get_pulse_sequence() {
void TZX::Serialiser::get_pulse_sequence() {
uint8_t number_of_pulses = file_.get8();
while(number_of_pulses--) {
post_pulse(file_.get16le());
}
}
void TZX::get_pause() {
void TZX::Serialiser::get_pause() {
uint16_t duration = file_.get16le();
if(!duration) {
// 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);
const uint8_t level = file_.get8();
current_level_ = !!level;
}
void TZX::get_kansas_city_block() {
void TZX::Serialiser::get_kansas_city_block() {
uint32_t block_length = file_.get32le();
const uint16_t pause_after_block = file_.get16le();
@ -395,15 +402,15 @@ void TZX::get_kansas_city_block() {
// 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);
}
void TZX::post_pulse(unsigned int length) {
void TZX::Serialiser::post_pulse(unsigned int length) {
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 > 1 && !current_level_) {
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) {
emplace_back(current_level_ ? Tape::Pulse::High : Tape::Pulse::Low, time);
void TZX::Serialiser::post_pulse(const Storage::Time &time) {
emplace_back(current_level_ ? Pulse::High : Pulse::Low, time);
current_level_ ^= true;
}
// MARK: - Flow control; currently ignored
void TZX::ignore_group_start() {
void TZX::Serialiser::ignore_group_start() {
uint8_t length = file_.get8();
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();
(void)target;
}
void TZX::ignore_loop_start() {
void TZX::Serialiser::ignore_loop_start() {
uint16_t number_of_repetitions = file_.get16le();
(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();
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();
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);
}
void TZX::ignore_custom_info_block() {
void TZX::Serialiser::ignore_custom_info_block() {
file_.seek(0x10, SEEK_CUR);
uint32_t length = file_.get32le();
file_.seek(length, SEEK_CUR);
@ -466,29 +473,29 @@ void TZX::ignore_custom_info_block() {
// MARK: - Messaging
void TZX::ignore_text_description() {
void TZX::Serialiser::ignore_text_description() {
uint8_t length = file_.get8();
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 length = file_.get8();
file_.seek(length, SEEK_CUR);
(void)time_for_display;
}
void TZX::ignore_archive_info() {
void TZX::Serialiser::ignore_archive_info() {
uint16_t length = file_.get16le();
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.
uint8_t number_of_machines = file_.get8();
file_.seek(number_of_machines * 3, SEEK_CUR);
}
void TZX::ignore_glue_block() {
void TZX::Serialiser::ignore_glue_block() {
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.
*/
class TZX: public PulseQueuedTape {
class TZX: public Tape {
public:
/*!
Constructs a @c TZX containing content from the file with name @c file_name.
@ -32,69 +32,79 @@ public:
};
private:
Storage::FileHolder file_;
struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
void virtual_reset() override;
void get_next_pulses() override;
private:
Storage::FileHolder file_;
bool current_level_;
void reset() override;
void get_next_pulses() override;
void get_standard_speed_data_block();
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();
bool current_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 get_standard_speed_data_block();
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 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 ignore_message_block();
void ignore_archive_info();
void get_hardware_type();
void ignore_custom_info_block();
void get_set_signal_level();
void get_kansas_city_block();
void ignore_glue_block();
void ignore_text_description();
void ignore_message_block();
void ignore_archive_info();
void get_hardware_type();
void ignore_custom_info_block();
struct Data {
unsigned int length_of_zero_bit_pulse;
unsigned int length_of_one_bit_pulse;
unsigned int number_of_bits_in_final_byte;
unsigned int pause_after_block;
uint32_t data_length;
};
void get_kansas_city_block();
void ignore_glue_block();
struct DataBlock {
unsigned int length_of_pilot_pulse;
unsigned int length_of_sync_first_pulse;
unsigned int length_of_sync_second_pulse;
unsigned int length_of_pilot_tone;
Data data;
};
struct Data {
unsigned int length_of_zero_bit_pulse;
unsigned int length_of_one_bit_pulse;
unsigned int number_of_bits_in_final_byte;
unsigned int pause_after_block;
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);
void get_data_block(const DataBlock &);
void get_data(const Data &);
struct DataBlock {
unsigned int length_of_pilot_pulse;
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 post_pulse(unsigned int length);
void post_gap(unsigned int milliseconds);
void get_generalised_segment(
uint32_t output_symbols,
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;
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)
{
// 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;
}
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse() {
Storage::Tape::Pulse PRG::Serialiser::get_next_pulse() {
// these are all microseconds per pole
constexpr unsigned int leader_zero_length = 179;
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;
if(!bit_phase_) get_next_output_token();
Tape::Pulse pulse;
Pulse pulse;
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_) {
case Leader: pulse.length.length = leader_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 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 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 Silence: pulse.type = Tape::Pulse::Zero; pulse.length.length = 5000; break;
case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break;
}
return pulse;
}
void PRG::virtual_reset() {
void PRG::Serialiser::reset() {
bit_phase_ = 3;
file_.seek(2, SEEK_SET);
file_phase_ = FilePhaseLeadIn;
@ -95,11 +97,11 @@ void PRG::virtual_reset() {
copy_mask_ = 0x80;
}
bool PRG::is_at_end() const {
bool PRG::Serialiser::is_at_end() const {
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 countdown_bytes = 9;
constexpr int leadin_length = 20000;

View File

@ -33,39 +33,42 @@ public:
ErrorBadFormat
};
// implemented to satisfy @c Tape
bool is_at_end() const override;
private:
FileHolder file_;
Pulse virtual_get_next_pulse() override;
void virtual_reset() override;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
private:
bool is_at_end() const override;
Pulse get_next_pulse() override;
void reset() override;
uint16_t load_address_;
uint16_t length_;
FileHolder file_;
enum FilePhase {
FilePhaseLeadIn,
FilePhaseHeader,
FilePhaseHeaderDataGap,
FilePhaseData,
FilePhaseAtEnd
} file_phase_ = FilePhaseLeadIn;
int phase_offset_ = 0;
uint16_t load_address_;
uint16_t length_;
int bit_phase_ = 3;
enum OutputToken {
Leader,
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;
enum FilePhase {
FilePhaseLeadIn,
FilePhaseHeader,
FilePhaseHeaderDataGap,
FilePhaseData,
FilePhaseAtEnd
} file_phase_ = FilePhaseLeadIn;
int phase_offset_ = 0;
int bit_phase_ = 3;
enum OutputToken {
Leader,
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;
}
// MARK: - ZLib extensions
static float gzgetfloat(gzFile file) {
float gzgetfloat(gzFile file) {
uint8_t bytes[4];
gzread(file, bytes, 4);
@ -49,34 +47,38 @@ static float gzgetfloat(gzFile file) {
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.
uint8_t result;
gzread(file, &result, 1);
return result;
}
static int gzget16(gzFile file) {
int gzget16(gzFile file) {
uint8_t bytes[2];
gzread(file, bytes, 2);
return bytes[0] | (bytes[1] << 8);
}
static int gzget24(gzFile file) {
int gzget24(gzFile file) {
uint8_t bytes[3];
gzread(file, bytes, 3);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16);
}
static int gzget32(gzFile file) {
int gzget32(gzFile file) {
uint8_t bytes[4];
gzread(file, bytes, 4);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
}
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");
char identifier[10];
@ -95,13 +97,13 @@ UEF::UEF(const std::string &file_name) {
set_platform_type();
}
UEF::~UEF() {
UEF::Serialiser::~Serialiser() {
gzclose(file_);
}
// MARK: - Public methods
void UEF::virtual_reset() {
void UEF::Serialiser::reset() {
gzseek(file_, 12, SEEK_SET);
set_is_at_end(false);
clear();
@ -109,7 +111,7 @@ void UEF::virtual_reset() {
// 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 uint32_t chunk_length = uint32_t(gzget32(file_));
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;
}
void UEF::get_next_pulses() {
void UEF::Serialiser::get_next_pulses() {
while(empty()) {
// read chunk details
Chunk next_chunk;
@ -171,13 +173,13 @@ void UEF::get_next_pulses() {
// MARK: - Chunk parsers
void UEF::queue_implicit_bit_pattern(uint32_t length) {
void UEF::Serialiser::queue_implicit_bit_pattern(uint32_t length) {
while(length--) {
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_));
uint8_t current_byte = 0;
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;
duration.length = unsigned(gzget16(file_));
duration.clock_rate = time_base_;
emplace_back(Pulse::Zero, duration);
}
void UEF::queue_floating_point_gap() {
void UEF::Serialiser::queue_floating_point_gap() {
const float length = gzgetfloat(file_);
Time duration;
duration.length = unsigned(length * 4000000);
@ -202,12 +204,12 @@ void UEF::queue_floating_point_gap() {
emplace_back(Pulse::Zero, duration);
}
void UEF::queue_carrier_tone() {
void UEF::Serialiser::queue_carrier_tone() {
unsigned int number_of_cycles = unsigned(gzget16(file_));
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 post_cycles = unsigned(gzget16(file_));
while(pre_cycles--) queue_bit(1);
@ -215,7 +217,7 @@ void UEF::queue_carrier_tone_with_dummy() {
while(post_cycles--) queue_bit(1);
}
void UEF::queue_security_cycles() {
void UEF::Serialiser::queue_security_cycles() {
int number_of_cycles = gzget24(file_);
bool first_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;
const int bits_per_packet = gzget8(file_);
@ -287,7 +289,7 @@ void UEF::queue_defined_data(uint32_t length) {
// MARK: - Queuing helpers
void UEF::queue_implicit_byte(uint8_t byte) {
void UEF::Serialiser::queue_implicit_byte(uint8_t byte) {
queue_bit(0);
int c = 8;
while(c--) {
@ -297,7 +299,7 @@ void UEF::queue_implicit_byte(uint8_t byte) {
queue_bit(1);
}
void UEF::queue_bit(int bit) {
void UEF::Serialiser::queue_bit(int bit) {
int number_of_cycles;
Time duration;
duration.clock_rate = time_base_ * 4;
@ -323,10 +325,14 @@ void UEF::queue_bit(int bit) {
// MARK: - TypeDistinguisher
TargetPlatform::Type UEF::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type UEF::Serialiser::target_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.
// So check and, if so, update the list of machines for which this file thinks it is suitable.
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.
*/
class UEF : public PulseQueuedTape, public TargetPlatform::TypeDistinguisher {
class UEF : public Tape, public TargetPlatform::TypeDistinguisher {
public:
/*!
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.
*/
UEF(const std::string &file_name);
~UEF();
enum {
ErrorNotUEF
};
private:
void virtual_reset() override;
void set_platform_type();
TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
gzFile file_;
unsigned int time_base_ = 1200;
bool is_300_baud_ = false;
struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
~Serialiser();
struct Chunk {
uint16_t id;
uint32_t length;
z_off_t start_of_next_chunk;
};
TargetPlatform::Type target_platform_type();
bool get_next_chunk(Chunk &);
void get_next_pulses() override;
private:
void reset() override;
void queue_implicit_bit_pattern(uint32_t length);
void queue_explicit_bit_pattern(uint32_t length);
void set_platform_type();
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
void queue_integer_gap();
void queue_floating_point_gap();
gzFile file_;
unsigned int time_base_ = 1200;
bool is_300_baud_ = false;
void queue_carrier_tone();
void queue_carrier_tone_with_dummy();
struct Chunk {
uint16_t id;
uint32_t length;
z_off_t start_of_next_chunk;
};
void queue_security_cycles();
void queue_defined_data(uint32_t length);
bool get_next_chunk(Chunk &);
void get_next_pulses() override;
void queue_bit(int bit);
void queue_implicit_byte(uint8_t byte);
void queue_implicit_bit_pattern(uint32_t length);
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;
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);
// Grab the actual file contents
@ -31,10 +33,10 @@ ZX80O81P::ZX80O81P(const std::string &file_name) {
if(!zx_file) throw ErrorNotZX80O81P;
// then rewind and start again
virtual_reset();
reset();
}
void ZX80O81P::virtual_reset() {
void ZX80O81P::Serialiser::reset() {
data_pointer_ = 0;
is_past_silence_ = false;
has_ended_final_byte_ = false;
@ -42,16 +44,16 @@ void ZX80O81P::virtual_reset() {
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_;
}
bool ZX80O81P::is_at_end() const {
bool ZX80O81P::Serialiser::is_at_end() const {
return has_finished_data() && has_ended_final_byte_;
}
Tape::Pulse ZX80O81P::virtual_get_next_pulse() {
Tape::Pulse pulse;
Pulse ZX80O81P::Serialiser::get_next_pulse() {
Pulse pulse;
// Start with 1 second of silence.
if(!is_past_silence_ || has_finished_data()) {
@ -104,5 +106,9 @@ Tape::Pulse ZX80O81P::virtual_get_next_pulse() {
}
TargetPlatform::Type ZX80O81P::target_platform_type() {
return serialiser_.target_platform_type();
}
TargetPlatform::Type ZX80O81P::Serialiser::target_platform_type() {
return platform_type_;
}

View File

@ -36,25 +36,30 @@ public:
};
private:
// implemented to satisfy @c Tape
bool is_at_end() const override;
// implemented to satisfy TargetPlatform::TypeDistinguisher
// TargetPlatform::TypeDistinguisher.
TargetPlatform::Type target_platform_type() override;
TargetPlatform::Type platform_type_;
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
bool has_finished_data() const;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
TargetPlatform::Type target_platform_type();
uint8_t byte_;
int bit_pointer_;
int wave_pointer_;
bool is_past_silence_, has_ended_final_byte_;
bool is_high_;
private:
bool is_at_end() const override;
void reset() override;
Pulse get_next_pulse() override;
bool has_finished_data() const;
std::vector<uint8_t> data_;
std::size_t data_pointer_;
TargetPlatform::Type platform_type_;
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
*/
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)
{
// 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?
while(file_.tell() != file_.stats().st_size) {
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;
}
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;
}
void ZXSpectrumTAP::virtual_reset() {
void ZXSpectrumTAP::Serialiser::reset() {
file_.seek(0, SEEK_SET);
read_next_block();
}
Tape::Pulse ZXSpectrumTAP::virtual_get_next_pulse() {
Pulse ZXSpectrumTAP::Serialiser::get_next_pulse() {
// Adopt a general pattern of high then low.
Pulse pulse;
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;
}
void ZXSpectrumTAP::read_next_block() {
void ZXSpectrumTAP::Serialiser::read_next_block() {
if(file_.tell() == file_.stats().st_size) {
phase_ = Phase::Gap;
} else {

View File

@ -34,23 +34,27 @@ public:
};
private:
Storage::FileHolder file_;
struct Serialiser: public TapeSerialiser {
Serialiser(const std::string &file_name);
private:
Storage::FileHolder file_;
uint16_t block_length_ = 0;
uint8_t block_type_ = 0;
uint8_t data_byte_ = 0;
enum Phase {
PilotTone,
Data,
Gap
} phase_ = Phase::PilotTone;
int distance_into_phase_ = 0;
void read_next_block();
uint16_t block_length_ = 0;
uint8_t block_type_ = 0;
uint8_t data_byte_ = 0;
enum Phase {
PilotTone,
Data,
Gap
} phase_ = Phase::PilotTone;
int distance_into_phase_ = 0;
void read_next_block();
// Implemented to satisfy @c Tape.
bool is_at_end() const override;
void virtual_reset() override;
Pulse virtual_get_next_pulse() override;
// Implemented to satisfy @c Tape.
bool is_at_end() const override;
void reset() 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);
}
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
shifter_.process_pulse(pulse);
}
@ -72,10 +72,10 @@ Shifter::Shifter() :
input_pattern_(0),
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>())));
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_) {
pll_.add_pulse();
}

View File

@ -18,7 +18,7 @@ class Shifter {
public:
Shifter();
void process_pulse(const Storage::Tape::Tape::Pulse &);
void process_pulse(const Storage::Tape::Pulse &);
struct Delegate {
virtual void acorn_shifter_output_bit(int value) = 0;
@ -55,7 +55,7 @@ public:
private:
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);
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
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:
// short: 182us => 0.000364s cycle
// medium: 262us => 0.000524s 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(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised);
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
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;
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;
}
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_medium_length = 0.000728f;
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(cycle_length_ < maximum_short_length) push_wave(WaveType::Short);
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);
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;
enum DetectionMode {

View File

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

View File

@ -95,7 +95,7 @@ public:
Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful
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:
const MachineType machine_type_;

View File

@ -69,7 +69,7 @@ protected:
/*!
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
@ -104,7 +104,7 @@ protected:
*/
template <typename WaveType, typename SymbolType> class PulseClassificationParser: public Parser<SymbolType> {
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.

View File

@ -12,10 +12,10 @@ using namespace Storage::Tape::ZX8081;
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
// 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_;
pulse_was_high_ = pulse_is_high;
if(!pulse_did_change || !pulse_is_high) {

View File

@ -47,7 +47,7 @@ private:
Time pulse_time_;
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 inspect_waves(const std::vector<WaveType> &waves) override;

View File

@ -10,36 +10,34 @@
using namespace Storage::Tape;
PulseQueuedTape::PulseQueuedTape() : pulse_pointer_(0), is_at_end_(false) {}
bool PulseQueuedTape::is_at_end() const {
bool PulseQueuedSerialiser::is_at_end() const {
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;
}
void PulseQueuedTape::clear() {
void PulseQueuedSerialiser::clear() {
queued_pulses_.clear();
pulse_pointer_ = 0;
}
bool PulseQueuedTape::empty() const {
bool PulseQueuedSerialiser::empty() const {
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);
}
void PulseQueuedTape::push_back(const Tape::Pulse pulse) {
void PulseQueuedSerialiser::push_back(const Pulse pulse) {
queued_pulses_.push_back(pulse);
}
Tape::Pulse PulseQueuedTape::virtual_get_next_pulse() {
Pulse PulseQueuedSerialiser::get_next_pulse() {
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_) {

View File

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

View File

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

View File

@ -8,17 +8,36 @@
#pragma once
#include <memory>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../TimedEventLoop.hpp"
#include "../../Activity/Source.hpp"
#include "../TargetPlatforms.hpp"
#include <memory>
#include <optional>
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
by their relationship with zero:
@ -32,15 +51,6 @@ namespace Storage::Tape {
*/
class Tape {
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
@ -54,38 +64,37 @@ public:
void reset();
/// @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
required to be at least to the whole pulse. Greater numbers are later than earlier numbers,
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.
*/
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.
*/
virtual Time get_current_time();
Time get_current_time();
/*!
Seeks to @c time. Potentially expensive.
*/
virtual void seek(Time &time);
void seek(Time);
Tape(TapeSerialiser &);
virtual ~Tape() = default;
private:
uint64_t offset_;
Tape::Pulse pulse_;
virtual Pulse virtual_get_next_pulse() = 0;
virtual void virtual_reset() = 0;
Pulse pulse_;
TapeSerialiser &serialiser_;
};
/*!
@ -101,7 +110,7 @@ public:
virtual ~TapePlayer() = default;
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();
void run_for(const Cycles cycles);
@ -110,18 +119,18 @@ public:
ClockingHint::Preference preferred_clocking() const override;
Tape::Pulse get_current_pulse();
Pulse get_current_pulse() const;
void complete_pulse();
protected:
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:
inline void get_next_pulse();
std::shared_ptr<Storage::Tape::Tape> tape_;
Tape::Pulse current_pulse_;
Pulse current_pulse_;
};
/*!
@ -154,7 +163,7 @@ public:
protected:
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 motor_is_running_ = false;