mirror of
https://github.com/TomHarte/CLK.git
synced 2025-03-02 05:29:11 +00:00
Merge pull request #1426 from TomHarte/C16Plus4Analysis
Begin Plus 4 analyser work, triggering clean-up of tape file classes.
This commit is contained in:
commit
3d2eefc7e7
@ -25,6 +25,7 @@ enum class Machine {
|
||||
MasterSystem,
|
||||
MSX,
|
||||
Oric,
|
||||
Plus4,
|
||||
PCCompatible,
|
||||
Vic20,
|
||||
ZX8081,
|
||||
|
@ -216,34 +216,36 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
|
||||
}
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
std::size_t number_of_sectors =
|
||||
const std::size_t number_of_sectors =
|
||||
size_t(directory[header_pointer + 0x1e]) +
|
||||
(size_t(directory[header_pointer + 0x1f]) << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
if(number_of_sectors) {
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
bool is_first_sector = true;
|
||||
while(next_track) {
|
||||
sector = parser.sector(next_track, next_sector);
|
||||
if(!sector) break;
|
||||
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
next_track = sector->data[0];
|
||||
next_sector = sector->data[1];
|
||||
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + (is_first_sector ? 4 : 2),
|
||||
sector->data.end()
|
||||
);
|
||||
else
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + 2,
|
||||
sector->data.begin() + next_sector
|
||||
);
|
||||
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
|
||||
if(next_track)
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + (is_first_sector ? 4 : 2),
|
||||
sector->data.end()
|
||||
);
|
||||
else
|
||||
new_file.data.insert(
|
||||
new_file.data.end(),
|
||||
sector->data.begin() + 2,
|
||||
sector->data.begin() + next_sector
|
||||
);
|
||||
|
||||
is_first_sector = false;
|
||||
is_first_sector = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!next_track) files.push_back(new_file);
|
||||
|
@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)
|
||||
while(1) {
|
||||
if(size_t(line_address - starting_address) >= data.size() + 2) break;
|
||||
if(size_t(line_address - starting_address) + 1 >= data.size()) break;
|
||||
|
||||
uint16_t next_line_address = data[line_address - starting_address];
|
||||
next_line_address |= data[line_address - starting_address + 1] << 8;
|
||||
@ -33,7 +33,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(size_t(line_address - starting_address) >= data.size() + 5) break;
|
||||
if(size_t(line_address - starting_address) + 3 >= data.size()) break;
|
||||
uint16_t next_line_number = data[line_address - starting_address + 2];
|
||||
next_line_number |= data[line_address - starting_address + 3] << 8;
|
||||
|
||||
|
@ -109,6 +109,10 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(
|
||||
case 0x0401:
|
||||
memory_model = Target::MemoryModel::EightKB;
|
||||
break;
|
||||
|
||||
case 0x1c01:
|
||||
Log::Logger<Log::Source::CommodoreStaticAnalyser>().info().append("Unimplemented: C128");
|
||||
break;
|
||||
}
|
||||
|
||||
target->set_memory_model(memory_model);
|
||||
|
@ -158,7 +158,8 @@ private:
|
||||
};
|
||||
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
|
||||
uint32_t index_8bitdisplacement(uint32_t);
|
||||
} state_;
|
||||
};
|
||||
mutable State state_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -454,7 +454,7 @@ public:
|
||||
|
||||
int cycles_left_while_plausibly_in_data = 50;
|
||||
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
|
||||
while(!tape_.get_tape()->is_at_end()) {
|
||||
while(!tape_.tape()->is_at_end()) {
|
||||
tape_.run_for_input_pulse();
|
||||
--cycles_left_while_plausibly_in_data;
|
||||
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
|
||||
|
@ -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(const Storage::Tape::Pulse &pulse) {
|
||||
shifter_.process_pulse(pulse);
|
||||
}
|
||||
|
||||
|
@ -43,10 +43,10 @@ public:
|
||||
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
|
||||
void set_is_in_input_mode(bool is_in_input_mode);
|
||||
|
||||
void acorn_shifter_output_bit(int value);
|
||||
void acorn_shifter_output_bit(int value) override;
|
||||
|
||||
private:
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
|
||||
void process(const Storage::Tape::Pulse &pulse) override;
|
||||
inline void push_tape_bit(uint16_t bit);
|
||||
inline void get_next_tape_pulse();
|
||||
|
||||
|
@ -721,7 +721,7 @@ public:
|
||||
case 0: return ay_.ay().get_data_output(); // Port A is wired to the AY
|
||||
case 1: return
|
||||
(crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
|
||||
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
||||
(tape_player_.input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
||||
0x7e; // Bits unimplemented:
|
||||
//
|
||||
// Bit 6: printer ready (1 = not)
|
||||
@ -899,8 +899,8 @@ public:
|
||||
// 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());
|
||||
parser.process_pulse(tape_player_.current_pulse());
|
||||
const auto byte = parser.get_byte(tape_player_.tape());
|
||||
auto flags = z80_.value_of(CPU::Z80::Register::Flags);
|
||||
|
||||
if(byte) {
|
||||
|
@ -526,9 +526,9 @@ class ConcreteMachine:
|
||||
// Address 0xf7b2 contains a JSR to 0xf8c0 that will fill the tape buffer with the next header.
|
||||
// So cancel that via a double NOP and fill in the next header programmatically.
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->tape());
|
||||
|
||||
const uint64_t tape_position = tape_->get_tape()->get_offset();
|
||||
const auto tape_position = tape_->tape()->offset();
|
||||
if(header) {
|
||||
// serialise to wherever b2:b3 points
|
||||
const uint16_t tape_buffer_pointer = uint16_t(ram_[0xb2]) | uint16_t(ram_[0xb3] << 8);
|
||||
@ -537,7 +537,7 @@ class ConcreteMachine:
|
||||
logger.info().append("Found header");
|
||||
} else {
|
||||
// no header found, so pretend this hack never interceded
|
||||
tape_->get_tape()->set_offset(tape_position);
|
||||
tape_->tape()->set_offset(tape_position);
|
||||
hold_tape_ = false;
|
||||
logger.info().append("Didn't find header");
|
||||
}
|
||||
@ -551,8 +551,8 @@ class ConcreteMachine:
|
||||
uint8_t x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
|
||||
if(x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
const uint64_t tape_position = tape_->get_tape()->get_offset();
|
||||
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
|
||||
const auto tape_position = tape_->tape()->offset();
|
||||
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->tape());
|
||||
if(data) {
|
||||
uint16_t start_address, end_address;
|
||||
start_address = uint16_t(ram_[0xc1] | (ram_[0xc2] << 8));
|
||||
@ -582,7 +582,7 @@ class ConcreteMachine:
|
||||
hold_tape_ = true;
|
||||
logger.info().append("Found data");
|
||||
} else {
|
||||
tape_->get_tape()->set_offset(tape_position);
|
||||
tape_->tape()->set_offset(tape_position);
|
||||
hold_tape_ = false;
|
||||
logger.info().append("Didn't find data");
|
||||
}
|
||||
@ -670,7 +670,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) final {
|
||||
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
|
||||
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->input());
|
||||
}
|
||||
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
|
@ -82,7 +82,7 @@ class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
return
|
||||
(static_cast<Joystick *>(joysticks_[selected_joystick_].get())->get_state() & 0x3f) |
|
||||
0x40 |
|
||||
(tape_player_.get_input() ? 0x00 : 0x80);
|
||||
(tape_player_.input() ? 0x00 : 0x80);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
@ -893,7 +893,7 @@ class ConcreteMachine:
|
||||
activity_observer_ = observer;
|
||||
if(activity_observer_) {
|
||||
activity_observer_->register_led("Tape motor");
|
||||
activity_observer_->set_led_status("Tape motor", tape_player_.get_motor_control());
|
||||
activity_observer_->set_led_status("Tape motor", tape_player_.motor_control());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,7 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
@returns The next byte from the tape.
|
||||
*/
|
||||
uint8_t get_next_byte(bool use_fast_encoding) {
|
||||
return uint8_t(parser_.get_next_byte(get_tape(), use_fast_encoding));
|
||||
return uint8_t(parser_.get_next_byte(tape(), use_fast_encoding));
|
||||
}
|
||||
|
||||
private:
|
||||
@ -469,7 +469,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502::BusOperation::ReadOpcode &&
|
||||
tape_player_.has_tape() &&
|
||||
!tape_player_.get_tape()->is_at_end()) {
|
||||
!tape_player_.tape()->is_at_end()) {
|
||||
|
||||
uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]);
|
||||
m6502_.set_value_of(CPU::MOS6502Esque::A, next_byte);
|
||||
@ -632,8 +632,8 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
// set CB1
|
||||
via_.set_control_line_input(
|
||||
MOS::MOS6522::Port::B, MOS::MOS6522::Line::One,
|
||||
tape_player_.get_motor_control() ?
|
||||
!tape_player_.get_input() :
|
||||
tape_player_.motor_control() ?
|
||||
!tape_player_.input() :
|
||||
!video_->vsync()
|
||||
);
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
if(!nmi_is_enabled_) set_vsync(true);
|
||||
|
||||
value &= keyboard_.read(address);
|
||||
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
|
||||
value &= ~(tape_player_.input() ? 0x00 : 0x80);
|
||||
}
|
||||
|
||||
// The below emulates the ZonX AY expansion device.
|
||||
@ -232,8 +232,8 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
// Check for use of the fast tape hack.
|
||||
if(use_fast_tape_hack_ && address == tape_trap_address_) {
|
||||
const uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
const int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
const uint64_t prior_offset = tape_player_.tape()->offset();
|
||||
const int next_byte = parser_.get_next_byte(tape_player_.tape());
|
||||
if(next_byte != -1) {
|
||||
const uint16_t hl = z80_.value_of(CPU::Z80::Register::HL);
|
||||
ram_[hl & ram_mask_] = uint8_t(next_byte);
|
||||
@ -246,7 +246,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
tape_advance_delay_ = 1000;
|
||||
return 0;
|
||||
} else {
|
||||
tape_player_.get_tape()->set_offset(prior_offset);
|
||||
tape_player_.tape()->set_offset(prior_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,7 +365,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
|
||||
bool get_tape_is_playing() final {
|
||||
return tape_player_.get_motor_control();
|
||||
return tape_player_.motor_control();
|
||||
}
|
||||
|
||||
// MARK: - Typer timing
|
||||
|
@ -541,7 +541,7 @@ template<Model model> class ConcreteMachine:
|
||||
// b6: tape input
|
||||
|
||||
*cycle.value &= keyboard_.read(address);
|
||||
*cycle.value &= tape_player_.get_input() ? 0xbf : 0xff;
|
||||
*cycle.value &= tape_player_.input() ? 0xbf : 0xff;
|
||||
|
||||
// Add Joystick input on top.
|
||||
if(!(address&0x1000)) *cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_sinclair(0);
|
||||
@ -716,7 +716,7 @@ template<Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
bool get_tape_is_playing() final {
|
||||
return tape_player_.get_motor_control();
|
||||
return tape_player_.motor_control();
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
@ -921,7 +921,7 @@ template<Model model> class ConcreteMachine:
|
||||
if(!(flags & 1)) return false;
|
||||
|
||||
const uint8_t block_type = uint8_t(z80_.value_of(Register::ADash));
|
||||
const auto block = parser.find_block(tape_player_.get_tape());
|
||||
const auto block = parser.find_block(tape_player_.tape());
|
||||
if(!block || block_type != (*block).type) return false;
|
||||
|
||||
uint16_t length = z80_.value_of(Register::DE);
|
||||
@ -930,7 +930,7 @@ template<Model model> class ConcreteMachine:
|
||||
flags = 0x93;
|
||||
uint8_t parity = 0x00;
|
||||
while(length--) {
|
||||
auto next = parser.get_byte(tape_player_.get_tape());
|
||||
auto next = parser.get_byte(tape_player_.tape());
|
||||
if(!next) {
|
||||
flags &= ~1;
|
||||
break;
|
||||
@ -941,7 +941,7 @@ template<Model model> class ConcreteMachine:
|
||||
++target;
|
||||
}
|
||||
|
||||
auto stored_parity = parser.get_byte(tape_player_.get_tape());
|
||||
auto stored_parity = parser.get_byte(tape_player_.tape());
|
||||
if(!stored_parity) {
|
||||
flags &= ~1;
|
||||
} else {
|
||||
|
@ -405,6 +405,7 @@
|
||||
4B670AB12401CB8400D4E002 /* z80docflags.tap in Resources */ = {isa = PBXBuildFile; fileRef = 4B670A9A2401CB8400D4E002 /* z80docflags.tap */; };
|
||||
4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */; };
|
||||
4B680CE423A555CA00451D43 /* 68000 Comparative Tests in Resources */ = {isa = PBXBuildFile; fileRef = 4B680CE323A555CA00451D43 /* 68000 Comparative Tests */; };
|
||||
4B6823A42CFE45B800276DA6 /* CommodoreStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B6823A32CFE45B800276DA6 /* CommodoreStaticAnalyserTests.mm */; };
|
||||
4B683B012727BE700043E541 /* Amiga Blitter Tests in Resources */ = {isa = PBXBuildFile; fileRef = 4B683B002727BE6F0043E541 /* Amiga Blitter Tests */; };
|
||||
4B69DEB62AB79E4F0055B217 /* Instruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69DEB52AB79E4F0055B217 /* Instruction.cpp */; };
|
||||
4B69DEB72AB79E4F0055B217 /* Instruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69DEB52AB79E4F0055B217 /* Instruction.cpp */; };
|
||||
@ -1626,6 +1627,7 @@
|
||||
4B670A9A2401CB8400D4E002 /* z80docflags.tap */ = {isa = PBXFileReference; lastKnownFileType = file; path = z80docflags.tap; sourceTree = "<group>"; };
|
||||
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ComparativeTests.mm; sourceTree = "<group>"; };
|
||||
4B680CE323A555CA00451D43 /* 68000 Comparative Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "68000 Comparative Tests"; sourceTree = "<group>"; };
|
||||
4B6823A32CFE45B800276DA6 /* CommodoreStaticAnalyserTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CommodoreStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
4B683B002727BE6F0043E541 /* Amiga Blitter Tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Amiga Blitter Tests"; sourceTree = "<group>"; };
|
||||
4B698D1A1FE768A100696C91 /* BufferSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BufferSource.hpp; sourceTree = "<group>"; };
|
||||
4B69DEB52AB79E4F0055B217 /* Instruction.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Instruction.cpp; sourceTree = "<group>"; };
|
||||
@ -4653,6 +4655,7 @@
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */,
|
||||
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
|
||||
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
|
||||
4B6823A32CFE45B800276DA6 /* CommodoreStaticAnalyserTests.mm */,
|
||||
4B882F582C2F9C6900D84031 /* CPCShakerTests.mm */,
|
||||
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
|
||||
4BB0CAA627E51B6300672A88 /* DingusdevPowerPCTests.mm */,
|
||||
@ -6465,6 +6468,7 @@
|
||||
4B778F0A23A5EC150000D260 /* TapePRG.cpp in Sources */,
|
||||
4B778F0823A5EC150000D260 /* CSW.cpp in Sources */,
|
||||
4BA6B6AE284EDAC100A3B7A8 /* 68000OldVsNew.mm in Sources */,
|
||||
4B6823A42CFE45B800276DA6 /* CommodoreStaticAnalyserTests.mm in Sources */,
|
||||
4B778F5323A5F23F0000D260 /* SerialBus.cpp in Sources */,
|
||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */,
|
||||
4B7752BA28217F160073E2C5 /* Bitplanes.cpp in Sources */,
|
||||
|
@ -67,6 +67,7 @@
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
disableMainThreadChecker = "YES"
|
||||
disablePerformanceAntipatternChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@ -89,8 +90,9 @@
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
savedToolIdentifier = "CPU Profiler"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "YES"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
@ -0,0 +1,80 @@
|
||||
//
|
||||
// AtariStaticAnalyserTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 11/03/2017.
|
||||
// Copyright 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../../Analyser/Static/Commodore/Target.hpp"
|
||||
|
||||
// This test runs through a whole bunch of files somewhere on disk. These files are not included in the repository
|
||||
// because they are not suitably licensed. So this path is specific to my local system, at the time I happen to be
|
||||
// writing these tests. Update in the future, as necessary.
|
||||
static constexpr const char *const plus4Path =
|
||||
"/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Soft/C16+4";
|
||||
static constexpr const char *const vic20Path =
|
||||
"/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Soft/Vic-20";
|
||||
|
||||
@interface CommodoreStaticAnalyserTests : XCTestCase
|
||||
@end
|
||||
|
||||
struct HitRate {
|
||||
int files = 0;
|
||||
int matches = 0;
|
||||
|
||||
HitRate &operator += (const HitRate &rhs) {
|
||||
files += rhs.files;
|
||||
matches += rhs.matches;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
@implementation CommodoreStaticAnalyserTests
|
||||
|
||||
- (HitRate)hitRateBeneathPath:(NSString *)path forMachine:(Analyser::Machine)machine {
|
||||
HitRate hits{};
|
||||
|
||||
NSDirectoryEnumerator<NSString *> *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
while(NSString *diskItem = [enumerator nextObject]) {
|
||||
const NSString *type = [[enumerator fileAttributes] objectForKey:NSFileType];
|
||||
if(![type isEqual:NSFileTypeRegular]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto list = Analyser::Static::GetTargets([path stringByAppendingPathComponent:diskItem].UTF8String);
|
||||
if(list.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++hits.files;
|
||||
if(list.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto &first = *list.begin();
|
||||
hits.matches += first->machine == machine;
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
- (void)testPlus4 {
|
||||
const auto hitRate =
|
||||
[self hitRateBeneathPath:[NSString stringWithUTF8String:plus4Path] forMachine:Analyser::Machine::Plus4];
|
||||
NSLog(@"Got hit rate of %d in %d, i.e. %0.2f",
|
||||
hitRate.matches, hitRate.files, float(hitRate.matches) / float(hitRate.files));
|
||||
}
|
||||
|
||||
- (void)testVic20 {
|
||||
const auto hitRate =
|
||||
[self hitRateBeneathPath:[NSString stringWithUTF8String:vic20Path] forMachine:Analyser::Machine::Vic20];
|
||||
NSLog(@"Got hit rate of %d in %d, i.e. %0.2f",
|
||||
hitRate.matches, hitRate.files, float(hitRate.matches) / float(hitRate.files));
|
||||
}
|
||||
|
||||
@end
|
@ -133,7 +133,7 @@ void FileHolder::seek(long offset, int whence) {
|
||||
std::fseek(file_, offset, whence);
|
||||
}
|
||||
|
||||
long FileHolder::tell() {
|
||||
long FileHolder::tell() const {
|
||||
return std::ftell(file_);
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ void FileHolder::flush() {
|
||||
std::fflush(file_);
|
||||
}
|
||||
|
||||
bool FileHolder::eof() {
|
||||
bool FileHolder::eof() const {
|
||||
return std::feof(file_);
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ bool FileHolder::check_signature(const char *signature, std::size_t length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string FileHolder::extension() {
|
||||
std::string FileHolder::extension() const {
|
||||
std::size_t pointer = name_.size() - 1;
|
||||
while(pointer > 0 && name_[pointer] != '.') pointer--;
|
||||
if(name_[pointer] == '.') pointer++;
|
||||
@ -178,11 +178,11 @@ void FileHolder::ensure_is_at_least_length(long length) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FileHolder::get_is_known_read_only() {
|
||||
bool FileHolder::get_is_known_read_only() const {
|
||||
return is_read_only_;
|
||||
}
|
||||
|
||||
struct stat &FileHolder::stats() {
|
||||
const struct stat &FileHolder::stats() const {
|
||||
return file_stats_;
|
||||
}
|
||||
|
||||
|
@ -144,13 +144,13 @@ public:
|
||||
void seek(long offset, int whence);
|
||||
|
||||
/*! @returns The current cursor position within this file. */
|
||||
long tell();
|
||||
long tell() const;
|
||||
|
||||
/*! Flushes any queued content that has not yet been written to disk. */
|
||||
void flush();
|
||||
|
||||
/*! @returns @c true if the end-of-file indicator is set, @c false otherwise. */
|
||||
bool eof();
|
||||
bool eof() const;
|
||||
|
||||
class BitStream {
|
||||
public:
|
||||
@ -214,7 +214,7 @@ public:
|
||||
Determines and returns the file extension: everything from the final character
|
||||
back to the first dot. The string is converted to lowercase before being returned.
|
||||
*/
|
||||
std::string extension();
|
||||
std::string extension() const;
|
||||
|
||||
/*!
|
||||
Ensures the file is at least @c length bytes long, appending 0s until it is
|
||||
@ -225,12 +225,12 @@ public:
|
||||
/*!
|
||||
@returns @c true if an attempt was made to read this file in ReadWrite mode but it could be opened only for reading; @c false otherwise.
|
||||
*/
|
||||
bool get_is_known_read_only();
|
||||
bool get_is_known_read_only() const;
|
||||
|
||||
/*!
|
||||
@returns the stat struct describing this file.
|
||||
*/
|
||||
struct stat &stats();
|
||||
const struct stat &stats() const;
|
||||
|
||||
/*!
|
||||
@returns a mutex owned by the file that can be used to serialise file access.
|
||||
|
@ -59,8 +59,10 @@ namespace {
|
||||
const uint8_t ascii_signature[] = TenX(0xea);
|
||||
}
|
||||
|
||||
CAS::CAS(const std::string &file_name) {
|
||||
Storage::FileHolder file(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 {
|
||||
Seeking,
|
||||
@ -168,18 +170,18 @@ CAS::CAS(const std::string &file_name) {
|
||||
}
|
||||
}
|
||||
|
||||
bool CAS::is_at_end() {
|
||||
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::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
|
||||
|
@ -33,36 +33,39 @@ public:
|
||||
ErrorNotCAS
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
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 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -14,9 +14,15 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
CSW::CSW(const std::string &file_name) :
|
||||
source_data_pointer_(0) {
|
||||
Storage::FileHolder file(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;
|
||||
|
||||
// Check signature.
|
||||
@ -28,8 +34,8 @@ CSW::CSW(const std::string &file_name) :
|
||||
if(file.get8() != 0x1a) throw ErrorNotCSW;
|
||||
|
||||
// Get version file number.
|
||||
uint8_t major_version = file.get8();
|
||||
uint8_t minor_version = file.get8();
|
||||
const uint8_t major_version = file.get8();
|
||||
const uint8_t minor_version = file.get8();
|
||||
|
||||
// Reject if this is an unknown version.
|
||||
if(major_version > 2 || !major_version || minor_version > 1) throw ErrorNotCSW;
|
||||
@ -63,7 +69,7 @@ CSW::CSW(const std::string &file_name) :
|
||||
|
||||
// Grab all data remaining in the file.
|
||||
std::vector<uint8_t> file_data;
|
||||
std::size_t remaining_data = size_t(file.stats().st_size) - size_t(file.tell());
|
||||
const std::size_t remaining_data = size_t(file.stats().st_size) - size_t(file.tell());
|
||||
file_data.resize(remaining_data);
|
||||
file.read(file_data.data(), remaining_data);
|
||||
|
||||
@ -86,22 +92,29 @@ 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,
|
||||
const CompressionType compression_type,
|
||||
const bool initial_level,
|
||||
const 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_];
|
||||
|
||||
const 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(
|
||||
|
||||
const uint32_t result = uint32_t(
|
||||
(source_data_[source_data_pointer_ + 0] << 0) |
|
||||
(source_data_[source_data_pointer_ + 1] << 8) |
|
||||
(source_data_[source_data_pointer_ + 2] << 16) |
|
||||
@ -110,19 +123,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() {
|
||||
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::next_pulse() {
|
||||
invert_pulse();
|
||||
pulse_.length.length = get_next_byte();
|
||||
if(!pulse_.length.length) pulse_.length.length = get_next_int32le();
|
||||
|
@ -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();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
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 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,10 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
CommodoreTAP::CommodoreTAP(const std::string &file_name) :
|
||||
file_(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"))
|
||||
throw ErrorNotCommodoreTAP;
|
||||
@ -39,24 +41,24 @@ 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() {
|
||||
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::next_pulse() {
|
||||
if(is_at_end_) {
|
||||
return current_pulse_;
|
||||
}
|
||||
|
||||
if(current_pulse_.type == Pulse::High) {
|
||||
uint32_t next_length;
|
||||
uint8_t next_byte = file_.get8();
|
||||
const uint8_t next_byte = file_.get8();
|
||||
if(!updated_layout_ || next_byte > 0) {
|
||||
next_length = uint32_t(next_byte) << 3;
|
||||
} else {
|
||||
|
@ -32,19 +32,23 @@ public:
|
||||
ErrorNotCommodoreTAP
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
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 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,11 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
OricTAP::OricTAP(const std::string &file_name) :
|
||||
file_(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.
|
||||
while(true) {
|
||||
@ -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::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() {
|
||||
bool OricTAP::Serialiser::is_at_end() const {
|
||||
return phase_ == End;
|
||||
}
|
||||
|
@ -32,24 +32,28 @@ public:
|
||||
ErrorNotOricTAP
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
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 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ Log::Logger<Log::Source::TZX> logger;
|
||||
|
||||
}
|
||||
|
||||
TZX::TZX(const std::string &file_name) :
|
||||
file_(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) {
|
||||
|
||||
// Check for signature followed by a 0x1a
|
||||
@ -30,16 +32,16 @@ TZX::TZX(const std::string &file_name) :
|
||||
if(file_.get8() != 0x1a) throw ErrorNotTZX;
|
||||
|
||||
// Get version number
|
||||
uint8_t major_version = file_.get8();
|
||||
uint8_t minor_version = file_.get8();
|
||||
const uint8_t major_version = file_.get8();
|
||||
const uint8_t minor_version = file_.get8();
|
||||
|
||||
// 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,9 +53,9 @@ void TZX::virtual_reset() {
|
||||
post_gap(500);
|
||||
}
|
||||
|
||||
void TZX::get_next_pulses() {
|
||||
void TZX::Serialiser::push_next_pulses() {
|
||||
while(empty()) {
|
||||
uint8_t chunk_id = file_.get8();
|
||||
const uint8_t chunk_id = file_.get8();
|
||||
if(file_.eof()) {
|
||||
set_is_at_end(true);
|
||||
return;
|
||||
@ -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();
|
||||
@ -111,29 +113,34 @@ void TZX::get_csw_recording_block() {
|
||||
|
||||
std::vector<uint8_t> raw_block = file_.read(block_length - 10);
|
||||
|
||||
CSW csw(std::move(raw_block), (compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE, current_level_, sampling_rate);
|
||||
CSW csw(
|
||||
std::move(raw_block),
|
||||
(compression_type == 2) ? CSW::CompressionType::ZRLE : CSW::CompressionType::RLE,
|
||||
current_level_,
|
||||
sampling_rate
|
||||
);
|
||||
while(!csw.is_at_end()) {
|
||||
Tape::Pulse next_pulse = csw.get_next_pulse();
|
||||
current_level_ = (next_pulse.type == Tape::Pulse::High);
|
||||
emplace_back(std::move(next_pulse));
|
||||
Pulse next_pulse = csw.next_pulse();
|
||||
current_level_ = (next_pulse.type == Pulse::High);
|
||||
push_back(next_pulse);
|
||||
}
|
||||
|
||||
(void)number_of_compressed_pulses;
|
||||
post_gap(pause_after_block);
|
||||
}
|
||||
|
||||
void TZX::get_generalised_data_block() {
|
||||
uint32_t block_length = file_.get32le();
|
||||
long endpoint = file_.tell() + long(block_length);
|
||||
uint16_t pause_after_block = file_.get16le();
|
||||
void TZX::Serialiser::get_generalised_data_block() {
|
||||
const uint32_t block_length = file_.get32le();
|
||||
const long endpoint = file_.tell() + long(block_length);
|
||||
const uint16_t pause_after_block = file_.get16le();
|
||||
|
||||
uint32_t total_pilot_symbols = file_.get32le();
|
||||
uint8_t maximum_pulses_per_pilot_symbol = file_.get8();
|
||||
uint8_t symbols_in_pilot_table = file_.get8();
|
||||
const uint32_t total_pilot_symbols = file_.get32le();
|
||||
const uint8_t maximum_pulses_per_pilot_symbol = file_.get8();
|
||||
const uint8_t symbols_in_pilot_table = file_.get8();
|
||||
|
||||
uint32_t total_data_symbols = file_.get32le();
|
||||
uint8_t maximum_pulses_per_data_symbol = file_.get8();
|
||||
uint8_t symbols_in_data_table = file_.get8();
|
||||
const uint32_t total_data_symbols = file_.get32le();
|
||||
const uint8_t maximum_pulses_per_data_symbol = file_.get8();
|
||||
const uint8_t symbols_in_data_table = file_.get8();
|
||||
|
||||
get_generalised_segment(total_pilot_symbols, maximum_pulses_per_pilot_symbol, symbols_in_pilot_table, false);
|
||||
get_generalised_segment(total_data_symbols, maximum_pulses_per_data_symbol, symbols_in_data_table, true);
|
||||
@ -143,7 +150,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(
|
||||
const uint32_t output_symbols,
|
||||
const uint8_t max_pulses_per_symbol,
|
||||
const uint8_t number_of_symbols,
|
||||
const bool is_data
|
||||
) {
|
||||
if(!output_symbols) return;
|
||||
|
||||
// Construct the symbol table.
|
||||
@ -202,7 +214,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;
|
||||
@ -215,14 +227,14 @@ void TZX::get_standard_speed_data_block() {
|
||||
data_block.data.data_length = file_.get16le();
|
||||
if(!data_block.data.data_length) return;
|
||||
|
||||
uint8_t first_byte = file_.get8();
|
||||
const uint8_t first_byte = file_.get8();
|
||||
data_block.length_of_pilot_tone = (first_byte < 128) ? 8063 : 3223;
|
||||
file_.seek(-1, SEEK_CUR);
|
||||
|
||||
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 +249,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 +260,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 +279,14 @@ void TZX::get_data(const Data &data) {
|
||||
post_gap(data.pause_after_block);
|
||||
}
|
||||
|
||||
void TZX::get_pure_tone_data_block() {
|
||||
uint16_t length_of_pulse = file_.get16le();
|
||||
uint16_t nunber_of_pulses = file_.get16le();
|
||||
void TZX::Serialiser::get_pure_tone_data_block() {
|
||||
const uint16_t length_of_pulse = file_.get16le();
|
||||
const 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 +297,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 +314,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,20 +322,20 @@ 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() {
|
||||
uint16_t duration = file_.get16le();
|
||||
void TZX::Serialiser::get_pause() {
|
||||
const uint16_t duration = file_.get16le();
|
||||
if(!duration) {
|
||||
// TODO (maybe): post a 'pause the tape' suggestion
|
||||
} else {
|
||||
@ -331,13 +343,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 +407,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, const unsigned int length) {
|
||||
while(count--) post_pulse(length);
|
||||
}
|
||||
|
||||
void TZX::post_pulse(unsigned int length) {
|
||||
void TZX::Serialiser::post_pulse(const unsigned int length) {
|
||||
post_pulse(Storage::Time(length, StandardTZXClock));
|
||||
}
|
||||
|
||||
void TZX::post_gap(unsigned int milliseconds) {
|
||||
void TZX::Serialiser::post_gap(const unsigned int milliseconds) {
|
||||
if(!milliseconds) return;
|
||||
if(milliseconds > 1 && !current_level_) {
|
||||
post_pulse(Storage::Time(TZXClockMSMultiplier, StandardTZXClock));
|
||||
@ -413,82 +425,82 @@ 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() {
|
||||
uint8_t length = file_.get8();
|
||||
void TZX::Serialiser::ignore_group_start() {
|
||||
const 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() {
|
||||
uint16_t target = file_.get16le();
|
||||
void TZX::Serialiser::ignore_jump_to_block() {
|
||||
const uint16_t target = file_.get16le();
|
||||
(void)target;
|
||||
}
|
||||
|
||||
void TZX::ignore_loop_start() {
|
||||
uint16_t number_of_repetitions = file_.get16le();
|
||||
void TZX::Serialiser::ignore_loop_start() {
|
||||
const 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() {
|
||||
uint16_t number_of_entries = file_.get16le();
|
||||
void TZX::Serialiser::ignore_call_sequence() {
|
||||
const 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() {
|
||||
uint16_t length_of_block = file_.get16le();
|
||||
void TZX::Serialiser::ignore_select_block() {
|
||||
const 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();
|
||||
const uint32_t length = file_.get32le();
|
||||
file_.seek(length, SEEK_CUR);
|
||||
}
|
||||
|
||||
// MARK: - Messaging
|
||||
|
||||
void TZX::ignore_text_description() {
|
||||
uint8_t length = file_.get8();
|
||||
void TZX::Serialiser::ignore_text_description() {
|
||||
const uint8_t length = file_.get8();
|
||||
file_.seek(length, SEEK_CUR);
|
||||
}
|
||||
|
||||
void TZX::ignore_message_block() {
|
||||
uint8_t time_for_display = file_.get8();
|
||||
uint8_t length = file_.get8();
|
||||
void TZX::Serialiser::ignore_message_block() {
|
||||
const uint8_t time_for_display = file_.get8();
|
||||
const uint8_t length = file_.get8();
|
||||
file_.seek(length, SEEK_CUR);
|
||||
(void)time_for_display;
|
||||
}
|
||||
|
||||
void TZX::ignore_archive_info() {
|
||||
uint16_t length = file_.get16le();
|
||||
void TZX::Serialiser::ignore_archive_info() {
|
||||
const 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();
|
||||
const 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);
|
||||
}
|
||||
|
@ -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();
|
||||
void get_next_pulses();
|
||||
private:
|
||||
Storage::FileHolder file_;
|
||||
|
||||
bool current_level_;
|
||||
void reset() override;
|
||||
void push_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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -48,8 +48,10 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
PRG::PRG(const std::string &file_name) :
|
||||
file_(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,
|
||||
// of if load address + length > 65536 then it's broken.
|
||||
@ -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::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() {
|
||||
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;
|
||||
@ -124,9 +126,9 @@ void PRG::get_next_output_token() {
|
||||
}
|
||||
|
||||
// determine whether a new byte needs to be queued up
|
||||
int block_offset = phase_offset_ - block_leadin_length;
|
||||
int bit_offset = block_offset % 10;
|
||||
int byte_offset = block_offset / 10;
|
||||
const int block_offset = phase_offset_ - block_leadin_length;
|
||||
const int bit_offset = block_offset % 10;
|
||||
const int byte_offset = block_offset / 10;
|
||||
phase_offset_++;
|
||||
|
||||
if(!bit_offset &&
|
||||
|
@ -33,39 +33,42 @@ public:
|
||||
ErrorBadFormat
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
FileHolder file_;
|
||||
Pulse virtual_get_next_pulse();
|
||||
void virtual_reset();
|
||||
struct Serialiser: public TapeSerialiser {
|
||||
Serialiser(const std::string &file_name);
|
||||
private:
|
||||
bool is_at_end() const override;
|
||||
Pulse 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
@ -30,8 +28,7 @@ static float gzgetfloat(gzFile file) {
|
||||
was the first byte read from the UEF, Float[1] the second, etc */
|
||||
|
||||
/* decode mantissa */
|
||||
int mantissa;
|
||||
mantissa = bytes[0] | (bytes[1] << 8) | ((bytes[2]&0x7f)|0x80) << 16;
|
||||
const int mantissa = bytes[0] | (bytes[1] << 8) | ((bytes[2]&0x7f)|0x80) << 16;
|
||||
|
||||
float result = float(mantissa);
|
||||
result = float(ldexp(result, -23));
|
||||
@ -49,38 +46,42 @@ 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];
|
||||
int bytes_read = gzread(file_, identifier, 10);
|
||||
const int bytes_read = gzread(file_, identifier, 10);
|
||||
if(bytes_read < 10 || std::strcmp(identifier, "UEF File!")) {
|
||||
throw ErrorNotUEF;
|
||||
}
|
||||
@ -95,13 +96,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 +110,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 +126,7 @@ bool UEF::get_next_chunk(UEF::Chunk &result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UEF::get_next_pulses() {
|
||||
void UEF::Serialiser::push_next_pulses() {
|
||||
while(empty()) {
|
||||
// read chunk details
|
||||
Chunk next_chunk;
|
||||
@ -171,13 +172,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(const 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 +188,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 +203,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 +216,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 +242,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 +288,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 +298,7 @@ void UEF::queue_implicit_byte(uint8_t byte) {
|
||||
queue_bit(1);
|
||||
}
|
||||
|
||||
void UEF::queue_bit(int bit) {
|
||||
void UEF::Serialiser::queue_bit(const int bit) {
|
||||
int number_of_cycles;
|
||||
Time duration;
|
||||
duration.clock_rate = time_base_ * 4;
|
||||
@ -323,10 +324,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;
|
||||
|
@ -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();
|
||||
TargetPlatform::Type target_platform_type() override;
|
||||
|
||||
void set_platform_type();
|
||||
TargetPlatform::Type target_platform_type();
|
||||
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
|
||||
struct Serialiser: public PulseQueuedSerialiser {
|
||||
Serialiser(const std::string &file_name);
|
||||
~Serialiser();
|
||||
|
||||
gzFile file_;
|
||||
unsigned int time_base_ = 1200;
|
||||
bool is_300_baud_ = false;
|
||||
TargetPlatform::Type target_platform_type();
|
||||
|
||||
struct Chunk {
|
||||
uint16_t id;
|
||||
uint32_t length;
|
||||
z_off_t start_of_next_chunk;
|
||||
};
|
||||
private:
|
||||
void reset() override;
|
||||
|
||||
bool get_next_chunk(Chunk &);
|
||||
void get_next_pulses();
|
||||
void set_platform_type();
|
||||
TargetPlatform::Type platform_type_ = TargetPlatform::Acorn;
|
||||
|
||||
void queue_implicit_bit_pattern(uint32_t length);
|
||||
void queue_explicit_bit_pattern(uint32_t length);
|
||||
gzFile file_;
|
||||
unsigned int time_base_ = 1200;
|
||||
bool is_300_baud_ = false;
|
||||
|
||||
void queue_integer_gap();
|
||||
void queue_floating_point_gap();
|
||||
struct Chunk {
|
||||
uint16_t id;
|
||||
uint32_t length;
|
||||
z_off_t start_of_next_chunk;
|
||||
};
|
||||
|
||||
void queue_carrier_tone();
|
||||
void queue_carrier_tone_with_dummy();
|
||||
bool get_next_chunk(Chunk &);
|
||||
void push_next_pulses() override;
|
||||
|
||||
void queue_security_cycles();
|
||||
void queue_defined_data(uint32_t length);
|
||||
void queue_implicit_bit_pattern(uint32_t length);
|
||||
void queue_explicit_bit_pattern(uint32_t length);
|
||||
|
||||
void queue_bit(int bit);
|
||||
void queue_implicit_byte(uint8_t byte);
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
ZX80O81P::ZX80O81P(const std::string &file_name) {
|
||||
Storage::FileHolder file(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
|
||||
data_.resize(size_t(file.stats().st_size));
|
||||
@ -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() {
|
||||
bool ZX80O81P::Serialiser::has_finished_data() const {
|
||||
return (data_pointer_ == data_.size()) && !wave_pointer_ && !bit_pointer_;
|
||||
}
|
||||
|
||||
bool ZX80O81P::is_at_end() {
|
||||
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::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_;
|
||||
}
|
||||
|
@ -36,25 +36,30 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
// TargetPlatform::TypeDistinguisher.
|
||||
TargetPlatform::Type target_platform_type() override;
|
||||
|
||||
// implemented to satisfy TargetPlatform::TypeDistinguisher
|
||||
TargetPlatform::Type target_platform_type();
|
||||
TargetPlatform::Type platform_type_;
|
||||
struct Serialiser: public TapeSerialiser {
|
||||
Serialiser(const std::string &file_name);
|
||||
TargetPlatform::Type target_platform_type();
|
||||
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
bool has_finished_data();
|
||||
private:
|
||||
bool is_at_end() const override;
|
||||
void reset() override;
|
||||
Pulse next_pulse() override;
|
||||
bool has_finished_data() const;
|
||||
|
||||
uint8_t byte_;
|
||||
int bit_pointer_;
|
||||
int wave_pointer_;
|
||||
bool is_past_silence_, has_ended_final_byte_;
|
||||
bool is_high_;
|
||||
TargetPlatform::Type platform_type_;
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
std::size_t data_pointer_;
|
||||
uint8_t byte_;
|
||||
int bit_pointer_;
|
||||
int wave_pointer_;
|
||||
bool is_past_silence_, has_ended_final_byte_;
|
||||
bool is_high_;
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
std::size_t data_pointer_;
|
||||
} serialiser_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,35 +18,38 @@ using namespace Storage::Tape;
|
||||
https://sinclair.wiki.zxnet.co.uk/wiki/TAP_format
|
||||
*/
|
||||
|
||||
ZXSpectrumTAP::ZXSpectrumTAP(const std::string &file_name) :
|
||||
file_(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
|
||||
// exactly file end.
|
||||
//
|
||||
// To consider: could also check those blocks of type 0
|
||||
// and type ff for valid checksums?
|
||||
while(true) {
|
||||
while(file_.tell() != file_.stats().st_size) {
|
||||
const uint16_t block_length = file_.get16le();
|
||||
if(file_.eof()) throw ErrorNotZXSpectrumTAP;
|
||||
if(file_.eof() || file_.tell() + block_length > file_.stats().st_size) {
|
||||
throw ErrorNotZXSpectrumTAP;
|
||||
}
|
||||
|
||||
file_.seek(block_length, SEEK_CUR);
|
||||
if(file_.tell() == file_.stats().st_size) break;
|
||||
}
|
||||
|
||||
virtual_reset();
|
||||
reset();
|
||||
}
|
||||
|
||||
bool ZXSpectrumTAP::is_at_end() {
|
||||
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::next_pulse() {
|
||||
// Adopt a general pattern of high then low.
|
||||
Pulse pulse;
|
||||
pulse.type = (distance_into_phase_ & 1) ? Pulse::Type::High : Pulse::Type::Low;
|
||||
@ -110,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 {
|
||||
|
@ -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() 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 next_pulse() override;
|
||||
} serialiser_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
using namespace Storage::Tape::MSX;
|
||||
|
||||
std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTapePlayer &tape_player) {
|
||||
if(!tape_player.get_motor_control()) {
|
||||
if(!tape_player.motor_control()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -21,17 +21,17 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
|
||||
"When 1,111 cycles have been found with less than 35 microseconds
|
||||
variation in their lengths a header has been located."
|
||||
*/
|
||||
bool last_level = tape_player.get_input();
|
||||
bool last_level = tape_player.input();
|
||||
float low = std::numeric_limits<float>::max();
|
||||
float high = std::numeric_limits<float>::min();
|
||||
int samples = 0;
|
||||
while(!tape_player.get_tape()->is_at_end()) {
|
||||
while(!tape_player.tape()->is_at_end()) {
|
||||
float next_length = 0.0f;
|
||||
do {
|
||||
next_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
||||
tape_player.run_for_input_pulse();
|
||||
} while(last_level == tape_player.get_input());
|
||||
last_level = tape_player.get_input();
|
||||
} while(last_level == tape_player.input());
|
||||
last_level = tape_player.input();
|
||||
low = std::min(low, next_length);
|
||||
high = std::max(high, next_length);
|
||||
samples++;
|
||||
@ -43,24 +43,24 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
|
||||
if(samples == 1111*2) break; // Cycles are read, not half-cycles.
|
||||
}
|
||||
|
||||
if(tape_player.get_tape()->is_at_end()) return nullptr;
|
||||
if(tape_player.tape()->is_at_end()) return nullptr;
|
||||
|
||||
/*
|
||||
"The next 256 cycles are then read (1B34H) and averaged to determine the cassette HI cycle length."
|
||||
*/
|
||||
float total_length = 0.0f;
|
||||
samples = 512;
|
||||
while(!tape_player.get_tape()->is_at_end()) {
|
||||
while(!tape_player.tape()->is_at_end()) {
|
||||
total_length += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
||||
if(tape_player.get_input() != last_level) {
|
||||
if(tape_player.input() != last_level) {
|
||||
samples--;
|
||||
if(!samples) break;
|
||||
last_level = tape_player.get_input();
|
||||
last_level = tape_player.input();
|
||||
}
|
||||
tape_player.run_for_input_pulse();
|
||||
}
|
||||
|
||||
if(tape_player.get_tape()->is_at_end()) return nullptr;
|
||||
if(tape_player.tape()->is_at_end()) return nullptr;
|
||||
|
||||
/*
|
||||
This figure is multiplied by 1.5 and placed in LOWLIM where it defines the minimum acceptable length
|
||||
@ -88,7 +88,7 @@ std::unique_ptr<Parser::FileSpeed> Parser::find_header(Storage::Tape::BinaryTape
|
||||
-1 otherwise.
|
||||
*/
|
||||
int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &tape_player) {
|
||||
if(!tape_player.get_motor_control()) {
|
||||
if(!tape_player.motor_control()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -103,11 +103,11 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
|
||||
*/
|
||||
const float minimum_start_bit_duration = float(speed.minimum_start_bit_duration) * 0.00001145f * 0.5f;
|
||||
int input = 0;
|
||||
while(!tape_player.get_tape()->is_at_end()) {
|
||||
while(!tape_player.tape()->is_at_end()) {
|
||||
// Find next transition.
|
||||
bool level = tape_player.get_input();
|
||||
bool level = tape_player.input();
|
||||
float duration = 0.0;
|
||||
while(level == tape_player.get_input()) {
|
||||
while(level == tape_player.input()) {
|
||||
duration += float(tape_player.get_cycles_until_next_event()) / float(tape_player.get_input_clock_rate());
|
||||
tape_player.run_for_input_pulse();
|
||||
}
|
||||
@ -131,25 +131,25 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
|
||||
float(tape_player.get_input_clock_rate())
|
||||
);
|
||||
int bits_left = 8;
|
||||
bool level = tape_player.get_input();
|
||||
while(!tape_player.get_tape()->is_at_end() && bits_left--) {
|
||||
bool level = tape_player.input();
|
||||
while(!tape_player.tape()->is_at_end() && bits_left--) {
|
||||
// Count number of transitions within cycles_per_window.
|
||||
int transitions = 0;
|
||||
int cycles_remaining = cycles_per_window;
|
||||
while(!tape_player.get_tape()->is_at_end() && cycles_remaining) {
|
||||
while(!tape_player.tape()->is_at_end() && cycles_remaining) {
|
||||
const int cycles_until_next_event = int(tape_player.get_cycles_until_next_event());
|
||||
const int cycles_to_run_for = std::min(cycles_until_next_event, cycles_remaining);
|
||||
|
||||
cycles_remaining -= cycles_to_run_for;
|
||||
tape_player.run_for(Cycles(cycles_to_run_for));
|
||||
|
||||
if(level != tape_player.get_input()) {
|
||||
level = tape_player.get_input();
|
||||
if(level != tape_player.input()) {
|
||||
level = tape_player.input();
|
||||
transitions++;
|
||||
}
|
||||
}
|
||||
|
||||
if(tape_player.get_tape()->is_at_end()) return -1;
|
||||
if(tape_player.tape()->is_at_end()) return -1;
|
||||
|
||||
int next_bit = 0;
|
||||
switch(transitions) {
|
||||
@ -170,16 +170,16 @@ int Parser::get_byte(const FileSpeed &speed, Storage::Tape::BinaryTapePlayer &ta
|
||||
transition count two more."
|
||||
*/
|
||||
int required_transitions = 2 - (transitions&1);
|
||||
while(!tape_player.get_tape()->is_at_end()) {
|
||||
while(!tape_player.tape()->is_at_end()) {
|
||||
tape_player.run_for_input_pulse();
|
||||
if(level != tape_player.get_input()) {
|
||||
level = tape_player.get_input();
|
||||
if(level != tape_player.input()) {
|
||||
level = tape_player.input();
|
||||
required_transitions--;
|
||||
if(!required_transitions) break;
|
||||
}
|
||||
}
|
||||
|
||||
if(tape_player.get_tape()->is_at_end()) return -1;
|
||||
if(tape_player.tape()->is_at_end()) return -1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -29,7 +29,7 @@ public:
|
||||
*/
|
||||
SymbolType get_next_symbol(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
while(!has_next_symbol_ && !tape->is_at_end()) {
|
||||
process_pulse(tape->get_next_pulse());
|
||||
process_pulse(tape->next_pulse());
|
||||
}
|
||||
if(!has_next_symbol_ && tape->is_at_end()) mark_end();
|
||||
has_next_symbol_ = false;
|
||||
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -10,56 +10,50 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
PulseQueuedTape::PulseQueuedTape() : pulse_pointer_(0), is_at_end_(false) {}
|
||||
|
||||
bool PulseQueuedTape::is_at_end() {
|
||||
bool PulseQueuedSerialiser::is_at_end() const {
|
||||
return is_at_end_;
|
||||
}
|
||||
|
||||
void PulseQueuedTape::set_is_at_end(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() {
|
||||
bool PulseQueuedSerialiser::empty() const {
|
||||
return queued_pulses_.empty();
|
||||
}
|
||||
|
||||
void PulseQueuedTape::emplace_back(Tape::Pulse::Type type, Time length) {
|
||||
void PulseQueuedSerialiser::emplace_back(const Pulse::Type type, const Time length) {
|
||||
queued_pulses_.emplace_back(type, length);
|
||||
}
|
||||
|
||||
void PulseQueuedTape::emplace_back(const Tape::Pulse &&pulse) {
|
||||
queued_pulses_.emplace_back(pulse);
|
||||
void PulseQueuedSerialiser::push_back(const Pulse pulse) {
|
||||
queued_pulses_.push_back(pulse);
|
||||
}
|
||||
|
||||
Tape::Pulse PulseQueuedTape::silence() {
|
||||
Pulse silence;
|
||||
silence.type = Pulse::Zero;
|
||||
silence.length.length = 1;
|
||||
silence.length.clock_rate = 1;
|
||||
return silence;
|
||||
}
|
||||
Pulse PulseQueuedSerialiser::next_pulse() {
|
||||
const auto silence = [] {
|
||||
return Pulse(Pulse::Type::Zero, Storage::Time(1, 1));
|
||||
};
|
||||
|
||||
Tape::Pulse PulseQueuedTape::virtual_get_next_pulse() {
|
||||
if(is_at_end_) {
|
||||
return silence();
|
||||
}
|
||||
|
||||
if(pulse_pointer_ == queued_pulses_.size()) {
|
||||
clear();
|
||||
get_next_pulses();
|
||||
push_next_pulses();
|
||||
|
||||
if(is_at_end_ || pulse_pointer_ == queued_pulses_.size()) {
|
||||
return silence();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t read_pointer = pulse_pointer_;
|
||||
const std::size_t read_pointer = pulse_pointer_;
|
||||
pulse_pointer_++;
|
||||
return queued_pulses_[read_pointer];
|
||||
}
|
||||
|
@ -16,34 +16,30 @@ namespace Storage::Tape {
|
||||
/*!
|
||||
Provides a @c Tape with a queue of upcoming pulses and an is-at-end flag.
|
||||
|
||||
If is-at-end is set then get_next_pulse() returns a second of silence and
|
||||
is_at_end() returns true.
|
||||
If is-at-end is set then @c next_pulse() returns a second of silence and
|
||||
@c is_at_end() returns @c true.
|
||||
|
||||
Otherwise get_next_pulse() returns something from the pulse queue if there is
|
||||
anything there, and otherwise calls get_next_pulses(). get_next_pulses() is
|
||||
Otherwise @c next_pulse() returns something from the pulse queue if there is
|
||||
anything there, and otherwise calls @c push_next_pulses() which 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();
|
||||
|
||||
protected:
|
||||
void emplace_back(Tape::Pulse::Type type, Time length);
|
||||
void emplace_back(const Tape::Pulse &&pulse);
|
||||
void emplace_back(Pulse::Type, Time);
|
||||
void push_back(Pulse);
|
||||
void clear();
|
||||
bool empty();
|
||||
bool empty() const;
|
||||
|
||||
void set_is_at_end(bool);
|
||||
virtual void get_next_pulses() = 0;
|
||||
Pulse next_pulse() override;
|
||||
bool is_at_end() const override;
|
||||
|
||||
virtual void push_next_pulses() = 0;
|
||||
|
||||
private:
|
||||
Pulse virtual_get_next_pulse();
|
||||
Pulse silence();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -16,23 +16,25 @@ 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) {
|
||||
get_next_pulse();
|
||||
next_pulse();
|
||||
next_time += pulse_.length;
|
||||
}
|
||||
}
|
||||
|
||||
Storage::Time Tape::get_current_time() {
|
||||
Storage::Time Tape::current_time() {
|
||||
Time time(0);
|
||||
uint64_t steps = get_offset();
|
||||
uint64_t steps = offset();
|
||||
reset();
|
||||
while(steps--) {
|
||||
get_next_pulse();
|
||||
next_pulse();
|
||||
time += pulse_.length;
|
||||
}
|
||||
return time;
|
||||
@ -40,16 +42,16 @@ 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::next_pulse() {
|
||||
pulse_ = serialiser_.next_pulse();
|
||||
offset_++;
|
||||
return pulse_;
|
||||
}
|
||||
|
||||
uint64_t Tape::get_offset() {
|
||||
uint64_t Tape::offset() const {
|
||||
return offset_;
|
||||
}
|
||||
|
||||
@ -59,9 +61,14 @@ void Tape::set_offset(uint64_t offset) {
|
||||
reset();
|
||||
}
|
||||
offset -= offset_;
|
||||
while(offset--) get_next_pulse();
|
||||
while(offset--) next_pulse();
|
||||
}
|
||||
|
||||
bool Tape::is_at_end() const {
|
||||
return serialiser_.is_at_end();
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Player
|
||||
|
||||
ClockingHint::Preference TapePlayer::preferred_clocking() const {
|
||||
@ -71,33 +78,33 @@ ClockingHint::Preference TapePlayer::preferred_clocking() const {
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_ = tape;
|
||||
reset_timer();
|
||||
get_next_pulse();
|
||||
next_pulse();
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
|
||||
std::shared_ptr<Storage::Tape::Tape> TapePlayer::tape() {
|
||||
return tape_;
|
||||
}
|
||||
|
||||
bool TapePlayer::has_tape() {
|
||||
bool TapePlayer::has_tape() const {
|
||||
return bool(tape_);
|
||||
}
|
||||
|
||||
void TapePlayer::get_next_pulse() {
|
||||
void TapePlayer::next_pulse() {
|
||||
// get the new pulse
|
||||
if(tape_) {
|
||||
current_pulse_ = tape_->get_next_pulse();
|
||||
current_pulse_ = tape_->next_pulse();
|
||||
if(tape_->is_at_end()) update_clocking_observer();
|
||||
} 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::current_pulse() const {
|
||||
return current_pulse_;
|
||||
}
|
||||
|
||||
@ -116,8 +123,8 @@ void TapePlayer::run_for_input_pulse() {
|
||||
}
|
||||
|
||||
void TapePlayer::process_next_event() {
|
||||
process_input_pulse(current_pulse_);
|
||||
get_next_pulse();
|
||||
process(current_pulse_);
|
||||
next_pulse();
|
||||
}
|
||||
|
||||
// MARK: - Binary Player
|
||||
@ -150,7 +157,7 @@ void BinaryTapePlayer::set_activity_observer(Activity::Observer *observer) {
|
||||
}
|
||||
}
|
||||
|
||||
bool BinaryTapePlayer::get_motor_control() const {
|
||||
bool BinaryTapePlayer::motor_control() const {
|
||||
return motor_is_running_;
|
||||
}
|
||||
|
||||
@ -158,7 +165,7 @@ void BinaryTapePlayer::set_tape_output(bool) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bool BinaryTapePlayer::get_input() const {
|
||||
bool BinaryTapePlayer::input() const {
|
||||
return motor_is_running_ && input_level_;
|
||||
}
|
||||
|
||||
@ -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(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);
|
||||
|
@ -8,39 +8,48 @@
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provdes the means for tape serialiserion.
|
||||
*/
|
||||
class TapeSerialiser {
|
||||
public:
|
||||
virtual Pulse 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:
|
||||
- high pulses exit from zero upward before returning to it;
|
||||
- low pulses exit from zero downward before returning to it;
|
||||
- zero pulses run along zero.
|
||||
|
||||
Subclasses should implement at least @c get_next_pulse and @c reset to provide a serial feeding
|
||||
of pulses and the ability to return to the start of the feed. They may also implement @c seek if
|
||||
a better implementation than a linear search from the @c reset time can be implemented.
|
||||
*/
|
||||
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
|
||||
@ -48,80 +57,78 @@ public:
|
||||
|
||||
@returns the pulse that begins at the current cursor position.
|
||||
*/
|
||||
Pulse get_next_pulse();
|
||||
Pulse next_pulse();
|
||||
|
||||
/// Returns the tape to the beginning.
|
||||
void reset();
|
||||
|
||||
/// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise.
|
||||
virtual bool is_at_end() = 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();
|
||||
uint64_t 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 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_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides a helper for: (i) retaining a reference to a tape; and (ii) running the tape at a certain
|
||||
input clock rate.
|
||||
|
||||
Will call @c process_input_pulse instantaneously upon reaching *the end* of a pulse. Therefore a subclass
|
||||
can decode pulses into data within process_input_pulse, using the supplied pulse's @c length and @c type.
|
||||
Will call @c process(Pulse) instantaneously upon reaching *the end* of a pulse. Therefore a subclass
|
||||
can decode pulses into data within @c process, using the supplied pulse's @c length and @c type.
|
||||
*/
|
||||
class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
|
||||
public:
|
||||
TapePlayer(int input_clock_rate);
|
||||
virtual ~TapePlayer() = default;
|
||||
|
||||
void set_tape(std::shared_ptr<Storage::Tape::Tape> tape);
|
||||
bool has_tape();
|
||||
std::shared_ptr<Storage::Tape::Tape> get_tape();
|
||||
void set_tape(std::shared_ptr<Storage::Tape::Tape>);
|
||||
bool has_tape() const;
|
||||
std::shared_ptr<Storage::Tape::Tape> tape();
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
void run_for_input_pulse();
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const override;
|
||||
|
||||
Tape::Pulse get_current_pulse();
|
||||
Pulse 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(const Pulse &) = 0;
|
||||
|
||||
private:
|
||||
inline void get_next_pulse();
|
||||
inline void next_pulse();
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> tape_;
|
||||
Tape::Pulse current_pulse_;
|
||||
Pulse current_pulse_;
|
||||
};
|
||||
|
||||
/*!
|
||||
@ -135,26 +142,26 @@ private:
|
||||
class BinaryTapePlayer : public TapePlayer {
|
||||
public:
|
||||
BinaryTapePlayer(int input_clock_rate);
|
||||
void set_motor_control(bool enabled);
|
||||
bool get_motor_control() const;
|
||||
void set_motor_control(bool);
|
||||
bool motor_control() const;
|
||||
|
||||
void set_tape_output(bool set);
|
||||
bool get_input() const;
|
||||
void set_tape_output(bool);
|
||||
bool input() const;
|
||||
|
||||
void run_for(const Cycles cycles);
|
||||
|
||||
struct Delegate {
|
||||
virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0;
|
||||
virtual void tape_did_change_input(BinaryTapePlayer *) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
protected:
|
||||
Delegate *delegate_ = nullptr;
|
||||
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) final;
|
||||
void process(const Pulse &) final;
|
||||
bool input_level_ = false;
|
||||
bool motor_is_running_ = false;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user