mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #879 from TomHarte/CPCTapes
Slightly Improves CPC tape loading times
This commit is contained in:
commit
39a105b48a
@ -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\"\n123";
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.disks.empty()) {
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "../MachineTypes.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Tape/Parsers/Spectrum.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
@ -31,6 +32,8 @@
|
||||
|
||||
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
|
||||
|
||||
#include "../../Numeric/CRC.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@ -915,6 +918,59 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
|
||||
// TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration;
|
||||
// I'm not immediately clear whether that's just because the machine still has to sit through
|
||||
// pilot tone in real time, or just that almost no software uses the ROM loader.
|
||||
if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) {
|
||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||
Parser parser(Parser::MachineType::AmstradCPC);
|
||||
|
||||
const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383];
|
||||
parser.set_cpc_read_speed(speed);
|
||||
|
||||
// Seed with the current pulse; the CPC will have finished the
|
||||
// preceding symbol and be a short way into the pulse that should determine the
|
||||
// first bit of this byte.
|
||||
parser.process_pulse(tape_player_.get_current_pulse());
|
||||
const auto byte = parser.get_byte(tape_player_.get_tape());
|
||||
auto flags = z80_.get_value_of_register(CPU::Z80::Register::Flags);
|
||||
|
||||
if(byte) {
|
||||
// In A ROM-esque fashion, begin the first pulse after the final one
|
||||
// that was just consumed.
|
||||
tape_player_.complete_pulse();
|
||||
|
||||
// Update in-memory CRC.
|
||||
auto crc_value =
|
||||
uint16_t(
|
||||
read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] |
|
||||
(read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8)
|
||||
);
|
||||
|
||||
tape_crc_.set_value(crc_value);
|
||||
tape_crc_.add(*byte);
|
||||
crc_value = tape_crc_.get_value();
|
||||
|
||||
write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value);
|
||||
write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8);
|
||||
|
||||
// Indicate successful byte read.
|
||||
z80_.set_value_of_register(CPU::Z80::Register::A, *byte);
|
||||
flags |= CPU::Z80::Flag::Carry;
|
||||
} else {
|
||||
// TODO: return tape player to previous state and decline to serve.
|
||||
z80_.set_value_of_register(CPU::Z80::Register::A, 0);
|
||||
flags &= ~CPU::Z80::Flag::Carry;
|
||||
}
|
||||
z80_.set_value_of_register(CPU::Z80::Register::Flags, flags);
|
||||
|
||||
// RET.
|
||||
*cycle.value = 0xc9;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
*cycle.value = read_pointers_[address >> 14][address & 16383];
|
||||
break;
|
||||
@ -965,6 +1021,7 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
// Default to nothing answering
|
||||
*cycle.value = 0xff;
|
||||
@ -1114,18 +1171,22 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer([[maybe_unused]] Activity::Observer *observer) final {
|
||||
if constexpr (has_fdc) fdc_.set_activity_observer(observer);
|
||||
tape_player_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::unique_ptr<Reflection::Struct> get_options() final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
options->quickload = allow_fast_tape_hack_;
|
||||
return options;
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
allow_fast_tape_hack_ = options->quickload;
|
||||
set_use_fast_tape_hack();
|
||||
}
|
||||
|
||||
// MARK: - Joysticks
|
||||
@ -1203,6 +1264,18 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
InterruptTimer interrupt_timer_;
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
|
||||
// By luck these values are the same between the 664 and the 6128;
|
||||
// therefore the has_fdc template flag is sufficient to locate them.
|
||||
static constexpr uint16_t tape_read_byte_address = has_fdc ? 0x2b20 : 0x29b0;
|
||||
static constexpr uint16_t tape_speed_value_address = has_fdc ? 0xb1e7 : 0xbc8f;
|
||||
static constexpr uint16_t tape_crc_address = has_fdc ? 0xb1eb : 0xb8d3;
|
||||
CRC::CCITT tape_crc_;
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool allow_fast_tape_hack_ = false;
|
||||
void set_use_fast_tape_hack() {
|
||||
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
|
||||
}
|
||||
|
||||
HalfCycles clock_offset_;
|
||||
HalfCycles crtc_counter_;
|
||||
HalfCycles half_cycles_since_ay_update_;
|
||||
@ -1219,7 +1292,7 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
ROMType upper_rom_;
|
||||
|
||||
uint8_t *ram_pages_[4];
|
||||
uint8_t *read_pointers_[4];
|
||||
const uint8_t *read_pointers_[4];
|
||||
uint8_t *write_pointers_[4];
|
||||
|
||||
KeyboardState key_state_;
|
||||
|
@ -29,12 +29,21 @@ class Machine {
|
||||
static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
/// Defines the runtime options available for an Amstrad CPC.
|
||||
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
|
||||
class Options:
|
||||
public Reflection::StructImpl<Options>,
|
||||
public Configurable::DisplayOption<Options>,
|
||||
public Configurable::QuickloadOption<Options>
|
||||
{
|
||||
friend Configurable::DisplayOption<Options>;
|
||||
friend Configurable::QuickloadOption<Options>;
|
||||
public:
|
||||
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::RGB) {
|
||||
Options(Configurable::OptionsType type) :
|
||||
Configurable::DisplayOption<Options>(Configurable::Display::RGB),
|
||||
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly)
|
||||
{
|
||||
if(needs_declare()) {
|
||||
declare_display_option();
|
||||
declare_quickload_option();
|
||||
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -243,6 +243,8 @@
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; };
|
||||
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
|
||||
4B5D5C9725F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; };
|
||||
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */; };
|
||||
4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
|
||||
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
|
||||
4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; };
|
||||
@ -1252,6 +1254,8 @@
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
|
||||
4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioToggle.hpp; sourceTree = "<group>"; };
|
||||
4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioToggle.cpp; sourceTree = "<group>"; };
|
||||
4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Spectrum.cpp; path = Parsers/Spectrum.cpp; sourceTree = "<group>"; };
|
||||
4B5D5C9625F56FC7001B4623 /* Spectrum.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Spectrum.hpp; path = Parsers/Spectrum.hpp; sourceTree = "<group>"; };
|
||||
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
|
||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
|
||||
@ -3021,11 +3025,13 @@
|
||||
4B8805F21DCFD22A003085B1 /* Commodore.cpp */,
|
||||
4B0E61051FF34737002A9DBD /* MSX.cpp */,
|
||||
4B8805F91DCFF807003085B1 /* Oric.cpp */,
|
||||
4B5D5C9525F56FC7001B4623 /* Spectrum.cpp */,
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */,
|
||||
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */,
|
||||
4B8805F31DCFD22A003085B1 /* Commodore.hpp */,
|
||||
4B0E61061FF34737002A9DBD /* MSX.hpp */,
|
||||
4B8805FA1DCFF807003085B1 /* Oric.hpp */,
|
||||
4B5D5C9625F56FC7001B4623 /* Spectrum.hpp */,
|
||||
4B4518A71F76004200926311 /* TapeParser.hpp */,
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */,
|
||||
);
|
||||
@ -5050,6 +5056,7 @@
|
||||
4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */,
|
||||
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
|
||||
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
|
||||
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */,
|
||||
4B2E86D025D8D8C70024F1E9 /* Keyboard.cpp in Sources */,
|
||||
4B89452F201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B894531201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
@ -5256,6 +5263,7 @@
|
||||
4B894530201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */,
|
||||
4B1B88BB202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
|
||||
4B5D5C9725F56FC7001B4623 /* Spectrum.cpp in Sources */,
|
||||
4B4518A11F75FD1C00926311 /* D64.cpp in Sources */,
|
||||
4B1558C01F844ECD006E9A97 /* BitReverse.cpp in Sources */,
|
||||
4BCE0052227CE8CA000CA200 /* DiskIICard.cpp in Sources */,
|
||||
|
@ -250,6 +250,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
|
||||
- (NSString *)optionsPanelNibName {
|
||||
switch(_targets.front()->machine) {
|
||||
// case Analyser::Machine::AmstradCPC: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
|
||||
case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||
|
@ -19,17 +19,18 @@ Parser::Parser(): crc_(0x1021) {
|
||||
}
|
||||
|
||||
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
const SymbolType symbol = get_next_symbol(tape);
|
||||
return (symbol == SymbolType::One) ? 1 : 0;
|
||||
}
|
||||
|
||||
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
int value = 0;
|
||||
int c = 8;
|
||||
if(get_next_bit(tape)) {
|
||||
set_error_flag();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
int c = 8;
|
||||
while(c--) {
|
||||
value = (value >> 1) | (get_next_bit(tape) << 7);
|
||||
}
|
||||
@ -53,8 +54,8 @@ unsigned int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &t
|
||||
return result;
|
||||
}
|
||||
|
||||
void Parser::reset_crc() { crc_.reset(); }
|
||||
uint16_t Parser::get_crc() { return crc_.get_value(); }
|
||||
void Parser::reset_crc() { crc_.reset(); }
|
||||
uint16_t Parser::get_crc() const { return crc_.get_value(); }
|
||||
|
||||
void Parser::acorn_shifter_output_bit(int value) {
|
||||
push_symbol(value ? SymbolType::One : SymbolType::Zero);
|
||||
@ -74,7 +75,7 @@ Shifter::Shifter() :
|
||||
void Shifter::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
pll_.run_for(Cycles(int(float(PLLClockRate) * pulse.length.get<float>())));
|
||||
|
||||
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
const bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
if(is_high != was_high_) {
|
||||
pll_.add_pulse();
|
||||
}
|
||||
|
@ -55,12 +55,12 @@ class Parser: public Storage::Tape::Parser<SymbolType>, public Shifter::Delegate
|
||||
unsigned int get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
unsigned int get_next_word(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
void reset_crc();
|
||||
uint16_t get_crc();
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
uint16_t get_crc() const;
|
||||
|
||||
private:
|
||||
void acorn_shifter_output_bit(int value) override;
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
|
||||
bool did_update_shifter(int new_value, int length);
|
||||
CRC::Generator<uint16_t, 0x0000, 0x0000, false, false> crc_;
|
||||
Shifter shifter_;
|
||||
|
@ -76,7 +76,7 @@ std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Stora
|
||||
reset_parity_byte();
|
||||
|
||||
// get header type
|
||||
uint8_t header_type = get_next_byte(tape);
|
||||
const uint8_t header_type = get_next_byte(tape);
|
||||
switch(header_type) {
|
||||
default: header->type = Header::Unknown; break;
|
||||
case 0x01: header->type = Header::RelocatableProgram; break;
|
||||
@ -92,7 +92,7 @@ std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Stora
|
||||
header->data.push_back(get_next_byte(tape));
|
||||
}
|
||||
|
||||
uint8_t parity_byte = get_parity_byte();
|
||||
const uint8_t parity_byte = get_parity_byte();
|
||||
header->parity_was_valid = get_next_byte(tape) == parity_byte;
|
||||
|
||||
// parse if this is not pure data
|
||||
@ -110,7 +110,7 @@ std::unique_ptr<Header> Parser::get_next_header_body(const std::shared_ptr<Stora
|
||||
return header;
|
||||
}
|
||||
|
||||
void Header::serialise(uint8_t *target, uint16_t length) {
|
||||
void Header::serialise(uint8_t *target, [[maybe_unused]] uint16_t length) {
|
||||
switch(type) {
|
||||
default: target[0] = 0xff; break;
|
||||
case Header::RelocatableProgram: target[0] = 0x01; break;
|
||||
@ -121,7 +121,6 @@ void Header::serialise(uint8_t *target, uint16_t length) {
|
||||
}
|
||||
|
||||
// TODO: validate length.
|
||||
(void)length;
|
||||
|
||||
std::memcpy(&target[1], data.data(), 191);
|
||||
}
|
||||
@ -137,7 +136,7 @@ std::unique_ptr<Data> Parser::get_next_data_body(const std::shared_ptr<Storage::
|
||||
|
||||
// accumulate until the next non-word marker is hit
|
||||
while(!tape->is_at_end()) {
|
||||
SymbolType start_symbol = get_next_symbol(tape);
|
||||
const SymbolType start_symbol = get_next_symbol(tape);
|
||||
if(start_symbol != SymbolType::Word) break;
|
||||
data->data.push_back(get_next_byte_contents(tape));
|
||||
}
|
||||
@ -172,22 +171,11 @@ void Parser::proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape>
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Swallows symbols until it reaches the first instance of the required symbol, swallows that
|
||||
and returns.
|
||||
*/
|
||||
void Parser::proceed_to_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape, SymbolType required_symbol) {
|
||||
while(!tape->is_at_end()) {
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
if(symbol == required_symbol) return;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Swallows the next byte; sets the error flag if it is not equal to @c value.
|
||||
*/
|
||||
void Parser::expect_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, uint8_t value) {
|
||||
uint8_t next_byte = get_next_byte(tape);
|
||||
const uint8_t next_byte = get_next_byte(tape);
|
||||
if(next_byte != value) set_error_flag();
|
||||
}
|
||||
|
||||
@ -212,7 +200,7 @@ uint8_t Parser::get_next_byte_contents(const std::shared_ptr<Storage::Tape::Tape
|
||||
int byte_plus_parity = 0;
|
||||
int c = 9;
|
||||
while(c--) {
|
||||
SymbolType next_symbol = get_next_symbol(tape);
|
||||
const SymbolType next_symbol = get_next_symbol(tape);
|
||||
if((next_symbol != SymbolType::One) && (next_symbol != SymbolType::Zero)) set_error_flag();
|
||||
byte_plus_parity = (byte_plus_parity >> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8);
|
||||
}
|
||||
@ -247,7 +235,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
// short: 182us => 0.000364s cycle
|
||||
// medium: 262us => 0.000524s cycle
|
||||
// long: 342us => 0.000684s cycle
|
||||
bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
const bool is_high = pulse.type == Storage::Tape::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);
|
||||
|
@ -88,12 +88,6 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
*/
|
||||
void proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
|
||||
|
||||
/*!
|
||||
Swallows symbols until it reaches the first instance of the required symbol, swallows that
|
||||
and returns.
|
||||
*/
|
||||
void proceed_to_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape, SymbolType required_symbol);
|
||||
|
||||
/*!
|
||||
Swallows the next byte; sets the error flag if it is not equal to @c value.
|
||||
*/
|
||||
@ -126,7 +120,7 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
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);
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
bool previous_was_high_ = false;
|
||||
float wave_period_ = 0.0f;
|
||||
|
||||
@ -134,7 +128,7 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
Per the contract with Analyser::Static::TapeParser; produces any of a word marker, an end-of-block marker,
|
||||
a zero, a one or a lead-in symbol based on the currently captured waves.
|
||||
*/
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
void inspect_waves(const std::vector<WaveType> &waves) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
constexpr float maximum_medium_length = 0.000728f;
|
||||
constexpr float maximum_long_length = 0.001456f;
|
||||
|
||||
bool wave_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
const bool wave_is_high = pulse.type == Storage::Tape::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);
|
||||
|
@ -32,8 +32,8 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
private:
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
void inspect_waves(const std::vector<WaveType> &waves) override;
|
||||
|
||||
enum DetectionMode {
|
||||
FastData,
|
||||
@ -45,8 +45,7 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
bool wave_was_high_;
|
||||
float cycle_length_;
|
||||
|
||||
struct Pattern
|
||||
{
|
||||
struct Pattern {
|
||||
WaveType type;
|
||||
int count = 0;
|
||||
};
|
||||
|
220
Storage/Tape/Parsers/Spectrum.cpp
Normal file
220
Storage/Tape/Parsers/Spectrum.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
//
|
||||
// Spectrum.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/03/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Spectrum.hpp"
|
||||
|
||||
#include "../../../Numeric/CRC.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
//
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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.
|
||||
set_cpc_one_zero_boundary(mean * 0.75f);
|
||||
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;
|
||||
}
|
||||
|
||||
// Potentially announce pilot.
|
||||
if(t_states >= is_pilot_) {
|
||||
push_wave(WaveType::Pilot);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise it's either a one or a zero.
|
||||
push_wave(t_states > is_one_ ? WaveType::One : WaveType::Zero);
|
||||
}
|
||||
|
||||
void Parser::set_cpc_read_speed(uint8_t speed) {
|
||||
// This may not be exactly right; I wish there were more science here but
|
||||
// instead it's empirical based on tape speed versus value stored plus
|
||||
// a guess as to where the CPC puts the dividing line.
|
||||
set_cpc_one_zero_boundary(float(speed) * 14.35f);
|
||||
}
|
||||
|
||||
void Parser::set_cpc_one_zero_boundary(float boundary) {
|
||||
is_one_ = boundary;
|
||||
too_long_ = is_one_ * 16.0f / 9.0f;
|
||||
too_short_ = is_one_ * 0.5f;
|
||||
is_pilot_ = too_long_;
|
||||
}
|
||||
|
||||
void Parser::inspect_waves(const std::vector<Storage::Tape::ZXSpectrum::WaveType> &waves) {
|
||||
switch(waves[0]) {
|
||||
// Gap and Pilot map directly.
|
||||
case WaveType::Gap: push_symbol(SymbolType::Gap, 1); break;
|
||||
case WaveType::Pilot: push_symbol(SymbolType::Pilot, 1); break;
|
||||
|
||||
// Both one and zero waves should come in pairs.
|
||||
case WaveType::One:
|
||||
case WaveType::Zero:
|
||||
if(waves.size() < 2) return;
|
||||
if(waves[1] == waves[0]) {
|
||||
push_symbol(waves[0] == WaveType::One ? SymbolType::One : SymbolType::Zero, 2);
|
||||
} else {
|
||||
push_symbol(SymbolType::Gap, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 bit.
|
||||
proceed_to_symbol(tape, SymbolType::Zero);
|
||||
if(is_at_end(tape)) return std::nullopt;
|
||||
|
||||
// Read marker byte.
|
||||
const auto type = get_byte(tape);
|
||||
if(!type) return std::nullopt;
|
||||
|
||||
// That succeeded.
|
||||
Block block = {
|
||||
.type = *type
|
||||
};
|
||||
return block;
|
||||
}
|
||||
|
||||
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) {
|
||||
uint8_t result = 0;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
const SymbolType symbol = get_next_symbol(tape);
|
||||
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;
|
||||
}
|
138
Storage/Tape/Parsers/Spectrum.hpp
Normal file
138
Storage/Tape/Parsers/Spectrum.hpp
Normal file
@ -0,0 +1,138 @@
|
||||
//
|
||||
// Spectrum.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/03/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_Tape_Parsers_Spectrum_hpp
|
||||
#define Storage_Tape_Parsers_Spectrum_hpp
|
||||
|
||||
#include "TapeParser.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
enum class WaveType {
|
||||
// All references to 't-states' below are cycles relative to the
|
||||
// ZX Spectrum's 3.5Mhz processor.
|
||||
|
||||
Pilot, // Nominally 2168 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 {
|
||||
Zero,
|
||||
One,
|
||||
Pilot,
|
||||
Gap,
|
||||
};
|
||||
|
||||
/// 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;
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
|
||||
public:
|
||||
enum class MachineType {
|
||||
ZXSpectrum,
|
||||
Enterprise,
|
||||
SAMCoupe,
|
||||
AmstradCPC
|
||||
};
|
||||
Parser(MachineType);
|
||||
|
||||
/*!
|
||||
Calibrates the expected data speed using a value in the CPC's native tape-speed measurement scale.
|
||||
*/
|
||||
void set_cpc_read_speed(uint8_t);
|
||||
|
||||
/*!
|
||||
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);
|
||||
|
||||
/*!
|
||||
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;
|
||||
|
||||
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 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;
|
||||
|
||||
void set_cpc_one_zero_boundary(float);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Spectrum_hpp */
|
@ -56,6 +56,17 @@ template <typename SymbolType> class Parser {
|
||||
return tape->is_at_end() && !has_next_symbol_;
|
||||
}
|
||||
|
||||
/*!
|
||||
Swallows symbols until it reaches the first instance of the required symbol, swallows that
|
||||
and returns.
|
||||
*/
|
||||
void proceed_to_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape, SymbolType required_symbol) {
|
||||
while(!is_at_end(tape)) {
|
||||
const SymbolType symbol = get_next_symbol(tape);
|
||||
if(symbol == required_symbol) return;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/*!
|
||||
Should be implemented by subclasses. Consumes @c pulse.
|
||||
|
@ -15,8 +15,8 @@ Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {}
|
||||
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
// If this is anything other than a transition from low to high, just add it to the
|
||||
// count of time.
|
||||
bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
bool pulse_did_change = pulse_is_high != pulse_was_high_;
|
||||
const bool pulse_is_high = pulse.type == Storage::Tape::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) {
|
||||
pulse_time_ += pulse.length;
|
||||
@ -31,7 +31,7 @@ void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
void Parser::post_pulse() {
|
||||
constexpr float expected_pulse_length = 300.0f / 1000000.0f;
|
||||
constexpr float expected_gap_length = 1300.0f / 1000000.0f;
|
||||
auto pulse_time = pulse_time_.get<float>();
|
||||
const auto pulse_time = pulse_time_.get<float>();
|
||||
|
||||
if(pulse_time > expected_gap_length * 1.25f) {
|
||||
push_wave(WaveType::LongGap);
|
||||
|
@ -50,10 +50,9 @@ class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolTy
|
||||
Time pulse_time_;
|
||||
void post_pulse();
|
||||
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
void mark_end();
|
||||
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
|
||||
void mark_end() override;
|
||||
void inspect_waves(const std::vector<WaveType> &waves) override;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> get_next_file_data(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
};
|
||||
|
@ -97,6 +97,14 @@ void TapePlayer::get_next_pulse() {
|
||||
set_next_event_time_interval(current_pulse_.length);
|
||||
}
|
||||
|
||||
Tape::Pulse TapePlayer::get_current_pulse() {
|
||||
return current_pulse_;
|
||||
}
|
||||
|
||||
void TapePlayer::complete_pulse() {
|
||||
jump_to_next_event();
|
||||
}
|
||||
|
||||
void TapePlayer::run_for(const Cycles cycles) {
|
||||
if(has_tape()) {
|
||||
TimedEventLoop::run_for(cycles);
|
||||
@ -127,6 +135,18 @@ void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
if(motor_is_running_ != enabled) {
|
||||
motor_is_running_ = enabled;
|
||||
update_clocking_observer();
|
||||
|
||||
if(observer_) {
|
||||
observer_->set_led_status("Tape motor", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::set_activity_observer(Activity::Observer *observer) {
|
||||
observer_ = observer;
|
||||
if(observer) {
|
||||
observer->register_led("Tape motor");
|
||||
observer_->set_led_status("Tape motor", motor_is_running_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
#include "../TimedEventLoop.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
|
||||
@ -110,6 +112,9 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const override;
|
||||
|
||||
Tape::Pulse get_current_pulse();
|
||||
void complete_pulse();
|
||||
|
||||
protected:
|
||||
virtual void process_next_event() override;
|
||||
virtual void process_input_pulse(const Tape::Pulse &pulse) = 0;
|
||||
@ -148,11 +153,15 @@ class BinaryTapePlayer : public TapePlayer {
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
protected:
|
||||
Delegate *delegate_ = nullptr;
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) final;
|
||||
bool input_level_ = false;
|
||||
bool motor_is_running_ = false;
|
||||
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user