1
0
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:
Thomas Harte 2024-12-03 23:16:10 -05:00 committed by GitHub
commit 3d2eefc7e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 774 additions and 582 deletions

View File

@ -25,6 +25,7 @@ enum class Machine {
MasterSystem,
MSX,
Oric,
Plus4,
PCCompatible,
Vic20,
ZX8081,

View File

@ -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);

View 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;

View File

@ -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);

View File

@ -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_;
};
}

View File

@ -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;

View File

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

View File

@ -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();

View File

@ -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) {

View File

@ -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 {

View File

@ -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());
}
}

View File

@ -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()
);
}

View File

@ -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

View File

@ -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 {

View File

@ -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 */,

View File

@ -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">

View File

@ -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

View File

@ -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_;
}

View File

@ -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.

View File

@ -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

View File

@ -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_;
};
}

View File

@ -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();

View File

@ -21,6 +21,11 @@ namespace Storage::Tape {
*/
class CSW: public Tape {
public:
enum class CompressionType {
RLE,
ZRLE
};
/*!
Constructs a @c CSW containing content from the file with name @c file_name.
@ -28,36 +33,36 @@ public:
*/
CSW(const std::string &file_name);
enum class CompressionType {
RLE,
ZRLE
};
/*!
Constructs a @c CSW containing content as specified. Does not throw.
*/
CSW(const std::vector<uint8_t> &&data, CompressionType compression_type, bool initial_level, uint32_t sampling_rate);
CSW(const std::vector<uint8_t> &&data, CompressionType, bool initial_level, uint32_t sampling_rate);
enum {
ErrorNotCSW
};
// implemented to satisfy @c Tape
bool is_at_end();
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_;
};
}

View File

@ -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 {

View File

@ -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_;
};
}

View File

@ -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;
}

View File

@ -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_;
};
}

View File

@ -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);
}

View File

@ -18,7 +18,7 @@ namespace Storage::Tape {
/*!
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling.
*/
class TZX: public PulseQueuedTape {
class TZX: public Tape {
public:
/*!
Constructs a @c TZX containing content from the file with name @c file_name.
@ -32,69 +32,79 @@ public:
};
private:
Storage::FileHolder file_;
struct Serialiser: public PulseQueuedSerialiser {
Serialiser(const std::string &file_name);
void virtual_reset();
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_;
};
}

View File

@ -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 &&

View File

@ -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_;
};
}

View File

@ -18,11 +18,9 @@ namespace {
Log::Logger<Log::Source::TapeUEF> logger;
}
// MARK: - ZLib extensions
static float gzgetfloat(gzFile file) {
float gzgetfloat(gzFile file) {
uint8_t bytes[4];
gzread(file, bytes, 4);
@ -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;

View File

@ -21,7 +21,7 @@ namespace Storage::Tape {
/*!
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
*/
class UEF : public PulseQueuedTape, public TargetPlatform::TypeDistinguisher {
class UEF : public Tape, public TargetPlatform::TypeDistinguisher {
public:
/*!
Constructs a @c UEF containing content from the file with name @c file_name.
@ -29,46 +29,54 @@ public:
@throws ErrorNotUEF if this file could not be opened and recognised as a valid UEF.
*/
UEF(const std::string &file_name);
~UEF();
enum {
ErrorNotUEF
};
private:
void virtual_reset();
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_;
};
}

View File

@ -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_;
}

View File

@ -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_;
};
}

View File

@ -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 {

View File

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

View File

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

View File

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

View File

@ -230,12 +230,12 @@ uint16_t Parser::get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape
indicates a high to low transition, inspects the time since the last transition, to produce
a long, medium, short or unrecognised wave period.
*/
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
// The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of:
// short: 182us => 0.000364s cycle
// medium: 262us => 0.000524s cycle
// long: 342us => 0.000684s cycle
const bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
const bool is_high = pulse.type == Storage::Tape::Pulse::High;
if(!is_high && previous_was_high_) {
if(wave_period_ >= 0.000764) push_wave(WaveType::Unrecognised);
else if(wave_period_ >= 0.000604) push_wave(WaveType::Long);

View File

@ -117,7 +117,7 @@ private:
indicates a high to low transition, inspects the time since the last transition, to produce
a long, medium, short or unrecognised wave period.
*/
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
void process_pulse(const Storage::Tape::Pulse &pulse) override;
bool previous_was_high_ = false;
float wave_period_ = 0.0f;

View File

@ -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;
}

View File

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

View File

@ -29,7 +29,7 @@ public:
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape);
private:
void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;
void process_pulse(const Storage::Tape::Pulse &pulse) override;
void inspect_waves(const std::vector<WaveType> &waves) override;
enum DetectionMode {

View File

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

View File

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

View File

@ -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.

View File

@ -12,10 +12,10 @@ using namespace Storage::Tape::ZX8081;
Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {}
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
void Parser::process_pulse(const Storage::Tape::Pulse &pulse) {
// If this is anything other than a transition from low to high, just add it to the
// count of time.
const bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
const bool pulse_is_high = pulse.type == Storage::Tape::Pulse::High;
const bool pulse_did_change = pulse_is_high != pulse_was_high_;
pulse_was_high_ = pulse_is_high;
if(!pulse_did_change || !pulse_is_high) {

View File

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

View File

@ -10,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];
}

View File

@ -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;
};
}

View File

@ -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);

View File

@ -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;