mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 00:37:27 +00:00
Enables detection of CPC-format tape data.
It turns out that the Spectrum's timings are its alone; speed autodetection added.
This commit is contained in:
parent
4eaf3440bd
commit
f190a1395a
@ -15,8 +15,11 @@
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/CPM.hpp"
|
||||
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
|
||||
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
namespace {
|
||||
|
||||
bool strcmp_insensitive(const char *a, const char *b) {
|
||||
if(std::strlen(a) != std::strlen(b)) return false;
|
||||
while(*a) {
|
||||
if(std::tolower(*a) != std::tolower(*b)) return false;
|
||||
@ -26,20 +29,20 @@ static bool strcmp_insensitive(const char *a, const char *b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_implied_extension(const std::string &extension) {
|
||||
bool is_implied_extension(const std::string &extension) {
|
||||
return
|
||||
extension == " " ||
|
||||
strcmp_insensitive(extension.c_str(), "BAS") ||
|
||||
strcmp_insensitive(extension.c_str(), "BIN");
|
||||
}
|
||||
|
||||
static void right_trim(std::string &string) {
|
||||
void right_trim(std::string &string) {
|
||||
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
|
||||
return !std::isspace(ch);
|
||||
}).base(), string.end());
|
||||
}
|
||||
|
||||
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
// Trim spaces from the name.
|
||||
std::string name = file.name;
|
||||
right_trim(name);
|
||||
@ -58,7 +61,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
|
||||
return command + "\n";
|
||||
}
|
||||
|
||||
static void InspectCatalogue(
|
||||
void InspectCatalogue(
|
||||
const Storage::Disk::CPM::Catalogue &catalogue,
|
||||
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
|
||||
@ -155,7 +158,7 @@ static void InspectCatalogue(
|
||||
target->loading_command = "cat\n";
|
||||
}
|
||||
|
||||
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
|
||||
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
|
||||
@ -179,6 +182,28 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
// Limited sophistication here; look for a CPC-style file header, that is
|
||||
// any Spectrum-esque block with a synchronisation character of 0x2c.
|
||||
//
|
||||
// More could be done here: parse the header, look for 0x16 data records.
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::AmstradCPC);
|
||||
|
||||
while(true) {
|
||||
const auto block = parser.find_block(tape);
|
||||
if(!block) break;
|
||||
|
||||
if(block->type == 0x2c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
TargetList destination;
|
||||
auto target = std::make_unique<Target>();
|
||||
@ -187,13 +212,19 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
|
||||
target->model = Target::Model::CPC6128;
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
// TODO: which of these are actually potentially CPC tapes?
|
||||
target->media.tapes = media.tapes;
|
||||
bool has_cpc_tape = false;
|
||||
for(auto &tape: media.tapes) {
|
||||
has_cpc_tape |= IsAmstradTape(tape);
|
||||
}
|
||||
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||
if(has_cpc_tape) {
|
||||
target->media.tapes = media.tapes;
|
||||
|
||||
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
|
||||
// enter and responding to the follow-on prompt to press a key, so just type for
|
||||
// a while. Yuck!
|
||||
target->loading_command = "|tape\nrun\"\n1234567890";
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
|
@ -14,6 +14,18 @@
|
||||
|
||||
namespace CRC {
|
||||
|
||||
constexpr uint8_t reverse_byte(uint8_t byte) {
|
||||
return
|
||||
((byte & 0x80) ? 0x01 : 0x00) |
|
||||
((byte & 0x40) ? 0x02 : 0x00) |
|
||||
((byte & 0x20) ? 0x04 : 0x00) |
|
||||
((byte & 0x10) ? 0x08 : 0x00) |
|
||||
((byte & 0x08) ? 0x10 : 0x00) |
|
||||
((byte & 0x04) ? 0x20 : 0x00) |
|
||||
((byte & 0x02) ? 0x40 : 0x00) |
|
||||
((byte & 0x01) ? 0x80 : 0x00);
|
||||
}
|
||||
|
||||
/*! Provides a class capable of generating a CRC from source data. */
|
||||
template <typename IntType, IntType reset_value, IntType output_xor, bool reflect_input, bool reflect_output> class Generator {
|
||||
public:
|
||||
@ -90,18 +102,6 @@ template <typename IntType, IntType reset_value, IntType output_xor, bool reflec
|
||||
static constexpr int multibyte_shift = (sizeof(IntType) * 8) - 8;
|
||||
IntType xor_table[256];
|
||||
IntType value_;
|
||||
|
||||
constexpr uint8_t reverse_byte(uint8_t byte) const {
|
||||
return
|
||||
((byte & 0x80) ? 0x01 : 0x00) |
|
||||
((byte & 0x40) ? 0x02 : 0x00) |
|
||||
((byte & 0x20) ? 0x04 : 0x00) |
|
||||
((byte & 0x10) ? 0x08 : 0x00) |
|
||||
((byte & 0x08) ? 0x10 : 0x00) |
|
||||
((byte & 0x04) ? 0x20 : 0x00) |
|
||||
((byte & 0x02) ? 0x40 : 0x00) |
|
||||
((byte & 0x01) ? 0x80 : 0x00);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -8,14 +8,23 @@
|
||||
|
||||
#include "Spectrum.hpp"
|
||||
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
//
|
||||
// Source used for the logic below was primarily https://sinclair.wiki.zxnet.co.uk/wiki/Spectrum_tape_interface
|
||||
// Sources used for the logic below:
|
||||
//
|
||||
// https://sinclair.wiki.zxnet.co.uk/wiki/Spectrum_tape_interface
|
||||
// http://www.cpctech.cpc-live.com/docs/manual/s968se08.pdf
|
||||
// https://www.alessandrogrussu.it/tapir/tzxform120.html
|
||||
//
|
||||
|
||||
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) {
|
||||
push_wave(WaveType::Gap);
|
||||
@ -25,44 +34,102 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
// Only pulse duration matters; the ZX Spectrum et al do not rely on polarity.
|
||||
const float t_states = pulse.length.get<float>() * 3'500'000.0f;
|
||||
|
||||
// Too long => gap.
|
||||
if(t_states > 2400.0f) {
|
||||
switch(speed_phase_) {
|
||||
case SpeedDetectionPhase::WaitingForGap:
|
||||
// A gap is: any 'pulse' of at least 3000 t-states.
|
||||
if(t_states >= 3000.0f) {
|
||||
speed_phase_ = SpeedDetectionPhase::WaitingForPilot;
|
||||
}
|
||||
return;
|
||||
|
||||
case SpeedDetectionPhase::WaitingForPilot:
|
||||
// Pilot tone might be: any pulse of less than 3000 t-states.
|
||||
if(t_states >= 3000.0f) return;
|
||||
speed_phase_ = SpeedDetectionPhase::CalibratingPilot;
|
||||
calibration_pulse_pointer_ = 0;
|
||||
[[fallthrough]];
|
||||
|
||||
case SpeedDetectionPhase::CalibratingPilot: {
|
||||
// Pilot calibration: await at least 8 consecutive pulses of similar length.
|
||||
calibration_pulses_[calibration_pulse_pointer_] = t_states;
|
||||
++calibration_pulse_pointer_;
|
||||
|
||||
// Decide whether it looks like this isn't actually pilot tone.
|
||||
float mean = 0.0f;
|
||||
for(size_t c = 0; c < calibration_pulse_pointer_; c++) {
|
||||
mean += calibration_pulses_[c];
|
||||
}
|
||||
mean /= float(calibration_pulse_pointer_);
|
||||
for(size_t c = 0; c < calibration_pulse_pointer_; c++) {
|
||||
if(calibration_pulses_[c] < mean * 0.9f || calibration_pulses_[c] > mean * 1.1f) {
|
||||
speed_phase_ = SpeedDetectionPhase::WaitingForGap;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance only if 8 are present.
|
||||
if(calibration_pulse_pointer_ == calibration_pulses_.size()) {
|
||||
speed_phase_ = SpeedDetectionPhase::Done;
|
||||
|
||||
// Note at least one full cycle of pilot tone.
|
||||
push_wave(WaveType::Pilot);
|
||||
push_wave(WaveType::Pilot);
|
||||
|
||||
// Configure proper parameters for the autodetection machines.
|
||||
switch(machine_type_) {
|
||||
default: break;
|
||||
|
||||
case MachineType::AmstradCPC:
|
||||
// CPC: pilot tone is length of bit 1; bit 0 is half that.
|
||||
// So no more detecting formal pilot waves.
|
||||
is_one_ = mean * 0.75f;
|
||||
too_long_ = mean * 1.0f / 0.75f;
|
||||
too_short_ = is_one_ * 0.5f;
|
||||
is_pilot_ = too_long_;
|
||||
break;
|
||||
|
||||
case MachineType::Enterprise:
|
||||
// There's a third validation check here: is this one of the two
|
||||
// permitted recording speeds?
|
||||
if(!(
|
||||
(mean >= 742.0f*0.9f && mean <= 742.0f*1.0f/0.9f) ||
|
||||
(mean >= 1750.0f*0.9f && mean <= 1750.0f*1.0f/0.9f)
|
||||
)) {
|
||||
speed_phase_ = SpeedDetectionPhase::WaitingForGap;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: not yet supported. As below, needs to deal with sync != zero.
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
case MachineType::SAMCoupe: {
|
||||
// TODO: not yet supported. Specifically because I don't think my sync = zero
|
||||
// assumption even vaguely works here?
|
||||
assert(false);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Too long or too short => gap.
|
||||
if(t_states >= too_long_ || t_states <= too_short_) {
|
||||
push_wave(WaveType::Gap);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1940–2400 t-states => pilot.
|
||||
if(t_states > 1940.0f) {
|
||||
// Potentially announce pilot.
|
||||
if(t_states >= is_pilot_) {
|
||||
push_wave(WaveType::Pilot);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1282–1940 t-states => one.
|
||||
if(t_states > 1282.0f) {
|
||||
push_wave(WaveType::One);
|
||||
return;
|
||||
}
|
||||
|
||||
// 895–1282 => zero.
|
||||
if(t_states > 795.0f) {
|
||||
push_wave(WaveType::Zero);
|
||||
return;
|
||||
}
|
||||
|
||||
// 701–895 => sync 2.
|
||||
if(t_states > 701.0f) {
|
||||
push_wave(WaveType::Sync2);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anything remaining above 600 => sync 1.
|
||||
if(t_states > 600.0f) {
|
||||
push_wave(WaveType::Sync1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Whatever this was, it's too short. Call it a gap.
|
||||
push_wave(WaveType::Gap);
|
||||
// Otherwise it's either a one or a zero.
|
||||
push_wave(t_states > is_one_ ? WaveType::One : WaveType::Zero);
|
||||
}
|
||||
|
||||
void Parser::inspect_waves(const std::vector<Storage::Tape::ZXSpectrum::WaveType> &waves) {
|
||||
@ -71,21 +138,6 @@ void Parser::inspect_waves(const std::vector<Storage::Tape::ZXSpectrum::WaveType
|
||||
case WaveType::Gap: push_symbol(SymbolType::Gap, 1); break;
|
||||
case WaveType::Pilot: push_symbol(SymbolType::Pilot, 1); break;
|
||||
|
||||
// Encountering a sync 2 on its own is unexpected.
|
||||
case WaveType::Sync2:
|
||||
push_symbol(SymbolType::Gap, 1);
|
||||
break;
|
||||
|
||||
// A sync 1 should be followed by a sync 2 in order to make a sync.
|
||||
case WaveType::Sync1:
|
||||
if(waves.size() < 2) return;
|
||||
if(waves[1] == WaveType::Sync2) {
|
||||
push_symbol(SymbolType::Sync, 2);
|
||||
} else {
|
||||
push_symbol(SymbolType::Gap, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
// Both one and zero waves should come in pairs.
|
||||
case WaveType::One:
|
||||
case WaveType::Zero:
|
||||
@ -99,50 +151,45 @@ void Parser::inspect_waves(const std::vector<Storage::Tape::ZXSpectrum::WaveType
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Header> Parser::find_header(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::optional<Block> Parser::find_block(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
// Decide whether to kick off a speed detection phase.
|
||||
if(should_detect_speed()) {
|
||||
speed_phase_ = SpeedDetectionPhase::WaitingForGap;
|
||||
}
|
||||
|
||||
// Find pilot tone.
|
||||
proceed_to_symbol(tape, SymbolType::Pilot);
|
||||
if(is_at_end(tape)) return std::nullopt;
|
||||
|
||||
// Find sync.
|
||||
proceed_to_symbol(tape, SymbolType::Sync);
|
||||
// Find sync bit.
|
||||
proceed_to_symbol(tape, SymbolType::Zero);
|
||||
if(is_at_end(tape)) return std::nullopt;
|
||||
|
||||
// Read market byte.
|
||||
// Read marker byte.
|
||||
const auto type = get_byte(tape);
|
||||
if(!type) return std::nullopt;
|
||||
|
||||
// TODO: possibly 0x00 is just the Spectrum's preferred identifier; a CPC reference
|
||||
// suggests it might be 0x16 for data, 0x2c for a header on that platform.
|
||||
//
|
||||
// Which would be fantastic for automatically recognising tapes. But we'll see.
|
||||
if(*type != 0x00) return std::nullopt;
|
||||
reset_checksum();
|
||||
|
||||
// Read header contents.
|
||||
uint8_t header_bytes[17];
|
||||
for(size_t c = 0; c < sizeof(header_bytes); c++) {
|
||||
const auto next_byte = get_byte(tape);
|
||||
if(!next_byte) return std::nullopt;
|
||||
header_bytes[c] = *next_byte;
|
||||
}
|
||||
|
||||
// Check checksum.
|
||||
const auto post_checksum = get_byte(tape);
|
||||
if(!post_checksum || *post_checksum) return std::nullopt;
|
||||
|
||||
// Unpack and return.
|
||||
Header header;
|
||||
header.type = header_bytes[0];
|
||||
memcpy(&header.name, &header_bytes[1], 10);
|
||||
header.data_length = uint16_t(header_bytes[11] | (header_bytes[12] << 8));
|
||||
header.parameters[0] = uint16_t(header_bytes[13] | (header_bytes[14] << 8));
|
||||
header.parameters[1] = uint16_t(header_bytes[15] | (header_bytes[16] << 8));
|
||||
return header;
|
||||
// That succeeded.
|
||||
Block block = {
|
||||
.type = *type
|
||||
};
|
||||
return block;
|
||||
}
|
||||
|
||||
void Parser::reset_checksum() {
|
||||
checksum_ = 0;
|
||||
std::vector<uint8_t> Parser::get_block_body(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::vector<uint8_t> result;
|
||||
|
||||
while(true) {
|
||||
const auto next_byte = get_byte(tape);
|
||||
if(!next_byte) break;
|
||||
result.push_back(*next_byte);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Parser::seed_checksum(uint8_t value) {
|
||||
checksum_ = value;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> Parser::get_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
@ -152,6 +199,11 @@ std::optional<uint8_t> Parser::get_byte(const std::shared_ptr<Storage::Tape::Tap
|
||||
if(symbol != SymbolType::One && symbol != SymbolType::Zero) return std::nullopt;
|
||||
result = uint8_t((result << 1) | (symbol == SymbolType::One));
|
||||
}
|
||||
|
||||
if(should_flip_bytes()) {
|
||||
result = CRC::reverse_byte(result);
|
||||
}
|
||||
|
||||
checksum_ ^= result;
|
||||
return result;
|
||||
}
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
#include "TapeParser.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
@ -22,99 +24,99 @@ enum class WaveType {
|
||||
// ZX Spectrum's 3.5Mhz processor.
|
||||
|
||||
Pilot, // Nominally 2168 t-states.
|
||||
Sync1, // 667 t-states.
|
||||
Sync2, // 735 t-states.
|
||||
Zero, // 855 t-states.
|
||||
One, // 1710 t-states.
|
||||
Gap,
|
||||
};
|
||||
|
||||
// Formally, there are two other types of wave:
|
||||
//
|
||||
// Sync1, // 667 t-states.
|
||||
// Sync2, // 735 t-states.
|
||||
//
|
||||
// Non-Spectrum machines often just output a plain zero symbol instead of
|
||||
// a two-step sync; this parser treats anything close enough to a zero
|
||||
// as a sync.
|
||||
|
||||
enum class SymbolType {
|
||||
Pilot,
|
||||
Sync,
|
||||
Zero,
|
||||
One,
|
||||
Pilot,
|
||||
Gap,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
/// A block is anything that follows a period of pilot tone; on a Spectrum that might be a
|
||||
/// file header or the file contents; on a CPC it might be a file header or a single chunk providing
|
||||
/// partial file contents. The Enterprise seems broadly to follow the Spectrum but the internal
|
||||
/// byte structure differs.
|
||||
struct Block {
|
||||
uint8_t type = 0;
|
||||
char name[11]{}; // 10 bytes on tape; always given a NULL terminator in this code.
|
||||
uint16_t data_length = 0;
|
||||
uint16_t parameters[2] = {0, 0};
|
||||
|
||||
enum class Type {
|
||||
Program = 0,
|
||||
NumberArray = 1,
|
||||
CharacterArray = 2,
|
||||
Code = 3,
|
||||
Unknown
|
||||
};
|
||||
Type decoded_type() {
|
||||
if(type > 3) return Type::Unknown;
|
||||
return Type(type);
|
||||
}
|
||||
|
||||
struct BasicParameters {
|
||||
std::optional<uint16_t> autostart_line_number;
|
||||
uint16_t start_of_variable_area;
|
||||
};
|
||||
BasicParameters basic_parameters() {
|
||||
const BasicParameters params = {
|
||||
.autostart_line_number = parameters[0] < 32768 ? std::make_optional(parameters[0]) : std::nullopt,
|
||||
.start_of_variable_area = parameters[1]
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
struct CodeParameters {
|
||||
uint16_t start_address;
|
||||
};
|
||||
CodeParameters code_parameters() {
|
||||
const CodeParameters params = {
|
||||
.start_address = parameters[0]
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
struct DataParameters {
|
||||
char name;
|
||||
enum class Type {
|
||||
Numeric,
|
||||
String
|
||||
} type;
|
||||
};
|
||||
DataParameters data_parameters() {
|
||||
#if TARGET_RT_BIG_ENDIAN
|
||||
const uint8_t data_name = uint8_t(parameters[0]);
|
||||
#else
|
||||
const uint8_t data_name = uint8_t(parameters[0] >> 8);
|
||||
#endif
|
||||
|
||||
using Type = DataParameters::Type;
|
||||
const DataParameters params = {
|
||||
.name = char((data_name & 0x1f) + 'a'),
|
||||
.type = (data_name & 0x40) ? Type::String : Type::Numeric
|
||||
};
|
||||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||
public:
|
||||
/*!
|
||||
Finds the next header from the tape, if any.
|
||||
*/
|
||||
std::optional<Header> find_header(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
enum class MachineType {
|
||||
ZXSpectrum,
|
||||
Enterprise,
|
||||
SAMCoupe,
|
||||
AmstradCPC
|
||||
};
|
||||
Parser(MachineType);
|
||||
|
||||
void reset_checksum();
|
||||
/*!
|
||||
Finds the next block from the tape, if any.
|
||||
|
||||
Following this call the tape will be positioned immediately after the byte that indicated the block type —
|
||||
in Spectrum-world this seems to be called the flag byte. This call can therefore be followed up with one
|
||||
of the get_ methods.
|
||||
*/
|
||||
std::optional<Block> find_block(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
/*!
|
||||
Reads the contents of the rest of this block, until the next gap.
|
||||
*/
|
||||
std::vector<uint8_t> get_block_body(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
/*!
|
||||
Reads a single byte from the tape, if there is one left, updating the internal checksum.
|
||||
|
||||
The checksum is computed as an exclusive OR of all bytes read.
|
||||
*/
|
||||
std::optional<uint8_t> get_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
/*!
|
||||
Seeds the internal checksum.
|
||||
*/
|
||||
void seed_checksum(uint8_t value = 0x00);
|
||||
|
||||
private:
|
||||
const MachineType machine_type_;
|
||||
constexpr bool should_flip_bytes() {
|
||||
return machine_type_ == MachineType::Enterprise;
|
||||
}
|
||||
constexpr bool should_detect_speed() {
|
||||
return machine_type_ != MachineType::ZXSpectrum;
|
||||
}
|
||||
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
void inspect_waves(const std::vector<WaveType> &waves) override;
|
||||
|
||||
uint8_t checksum_ = 0;
|
||||
|
||||
enum class SpeedDetectionPhase {
|
||||
WaitingForGap,
|
||||
WaitingForPilot,
|
||||
CalibratingPilot,
|
||||
Done
|
||||
} speed_phase_ = SpeedDetectionPhase::Done;
|
||||
|
||||
float too_long_ = 2600.0f;
|
||||
float too_short_ = 600.0f;
|
||||
float is_pilot_ = 1939.0f;
|
||||
float is_one_ = 1282.0f;
|
||||
|
||||
std::array<float, 8> calibration_pulses_;
|
||||
size_t calibration_pulse_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user