mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Merge pull request #151 from TomHarte/TZX
Adds partial TZX file format support
This commit is contained in:
commit
d6e60c8e3a
@ -24,11 +24,8 @@ Machine::Machine() :
|
||||
tape_player_(ZX8081ClockRate),
|
||||
use_fast_tape_hack_(false),
|
||||
tape_advance_delay_(0),
|
||||
tape_is_automatically_playing_(false),
|
||||
tape_is_playing_(false),
|
||||
has_latched_video_byte_(false) {
|
||||
set_clock_rate(ZX8081ClockRate);
|
||||
tape_player_.set_motor_control(true);
|
||||
clear_all_keys();
|
||||
}
|
||||
|
||||
@ -58,7 +55,7 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
|
||||
if(is_zx81_) horizontal_counter_ %= 207;
|
||||
if(!tape_advance_delay_) {
|
||||
if(tape_is_automatically_playing_ || tape_is_playing_) tape_player_.run_for_cycles(cycle.length);
|
||||
tape_player_.run_for_cycles(cycle.length);
|
||||
} else {
|
||||
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, 0);
|
||||
}
|
||||
@ -133,7 +130,7 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcodeWait:
|
||||
// Check for use of the fast tape hack.
|
||||
if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) {
|
||||
Storage::Time time = tape_player_.get_tape()->get_current_time();
|
||||
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
|
||||
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
|
||||
if(next_byte != -1) {
|
||||
uint16_t hl = get_value_of_register(CPU::Z80::Register::HL);
|
||||
@ -147,12 +144,14 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
tape_advance_delay_ = 1000;
|
||||
return 0;
|
||||
} else {
|
||||
tape_player_.get_tape()->seek(time);
|
||||
tape_player_.get_tape()->set_offset(prior_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for automatic tape control.
|
||||
tape_is_automatically_playing_ = use_automatic_tape_motor_control_ && (address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_);
|
||||
if(use_automatic_tape_motor_control_) {
|
||||
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
|
||||
}
|
||||
is_opcode_read = true;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
|
@ -69,9 +69,11 @@ class Machine:
|
||||
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
|
||||
inline void set_use_automatic_tape_motor_control(bool enabled) {
|
||||
use_automatic_tape_motor_control_ = enabled;
|
||||
if(!enabled) tape_is_automatically_playing_ = false;
|
||||
if(!enabled) {
|
||||
tape_player_.set_motor_control(false);
|
||||
}
|
||||
inline void set_tape_is_playing(bool is_playing) { tape_is_playing_ = is_playing; }
|
||||
}
|
||||
inline void set_tape_is_playing(bool is_playing) { tape_player_.set_motor_control(is_playing); }
|
||||
|
||||
// for Utility::TypeRecipient::Delegate
|
||||
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
|
||||
@ -113,7 +115,6 @@ class Machine:
|
||||
|
||||
bool use_fast_tape_hack_;
|
||||
bool use_automatic_tape_motor_control_;
|
||||
bool tape_is_playing_, tape_is_automatically_playing_;
|
||||
int tape_advance_delay_;
|
||||
};
|
||||
|
||||
|
@ -9,15 +9,15 @@
|
||||
#ifndef Factors_hpp
|
||||
#define Factors_hpp
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace NumberTheory {
|
||||
/*!
|
||||
@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm.
|
||||
*/
|
||||
template<class T> T greatest_common_divisor(T a, T b) {
|
||||
if(a < b) {
|
||||
T swap = b;
|
||||
b = a;
|
||||
a = swap;
|
||||
std::swap(a, b);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
|
@ -53,6 +53,8 @@
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; };
|
||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; };
|
||||
4B3F1B461E0388D200DB26EE /* PCMPatchedTrack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */; };
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
|
||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */; };
|
||||
4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */; };
|
||||
4B44EBF71DC9883B00A7820C /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */; };
|
||||
4B44EBF91DC9898E00A7820C /* BCDTEST_beeb in Resources */ = {isa = PBXBuildFile; fileRef = 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */; };
|
||||
@ -544,6 +546,10 @@
|
||||
4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; };
|
||||
4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = "<group>"; };
|
||||
4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = "<group>"; };
|
||||
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = "<group>"; };
|
||||
4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = "<group>"; };
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = "<group>"; };
|
||||
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = "<group>"; };
|
||||
4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = "<group>"; };
|
||||
4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = "<group>"; };
|
||||
@ -1388,6 +1394,8 @@
|
||||
children = (
|
||||
4B69FB411C4D941400B5F0AA /* Formats */,
|
||||
4B8805F11DCFC9A2003085B1 /* Parsers */,
|
||||
4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */,
|
||||
4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */,
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
|
||||
);
|
||||
@ -1402,12 +1410,14 @@
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */,
|
||||
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */,
|
||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */,
|
||||
4B448E7F1F1C45A00009ABD6 /* TZX.cpp */,
|
||||
4B1497861EE4A1DA00CE2596 /* ZX80O81P.cpp */,
|
||||
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
|
||||
4B3BF5AF1F146264005B6C36 /* CSW.hpp */,
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */,
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */,
|
||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */,
|
||||
4B448E801F1C45A00009ABD6 /* TZX.hpp */,
|
||||
4B1497871EE4A1DA00CE2596 /* ZX80O81P.hpp */,
|
||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */,
|
||||
);
|
||||
@ -2575,6 +2585,7 @@
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
|
||||
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */,
|
||||
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
|
||||
@ -2605,6 +2616,7 @@
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */,
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
|
||||
4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
4BEA52631DF339D7007E74F2 /* Speaker.cpp in Sources */,
|
||||
4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */,
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
|
@ -212,6 +212,20 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>tzx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "../Storage/Tape/Formats/OricTAP.hpp"
|
||||
#include "../Storage/Tape/Formats/TapePRG.hpp"
|
||||
#include "../Storage/Tape/Formats/TapeUEF.hpp"
|
||||
#include "../Storage/Tape/Formats/TZX.hpp"
|
||||
#include "../Storage/Tape/Formats/ZX80O81P.hpp"
|
||||
|
||||
typedef int TargetPlatformType;
|
||||
@ -124,6 +125,7 @@ std::list<Target> StaticAnalyser::GetTargets(const char *file_name)
|
||||
Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tzx", tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
|
||||
#undef Format
|
||||
|
@ -40,9 +40,9 @@ void StaticAnalyser::ZX8081::AddTargets(
|
||||
StaticAnalyser::Target target;
|
||||
target.machine = Target::ZX8081;
|
||||
target.zx8081.isZX81 = files.front().isZX81;
|
||||
if(files.front().data.size() > 16384) {
|
||||
/*if(files.front().data.size() > 16384) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
|
||||
} else if(files.front().data.size() > 1024) {
|
||||
} else*/ if(files.front().data.size() > 1024) {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::SixteenKB;
|
||||
} else {
|
||||
target.zx8081.memory_model = ZX8081MemoryModel::Unexpanded;
|
||||
|
@ -50,8 +50,10 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
|
||||
|
||||
// Look for a file name.
|
||||
size_t data_pointer = 0;
|
||||
std::vector<uint8_t> name_data;
|
||||
int c = 11;
|
||||
while(c < data.size() && c--) {
|
||||
name_data.push_back(data[data_pointer] & 0x3f);
|
||||
if(data[data_pointer] & 0x80) break;
|
||||
data_pointer++;
|
||||
}
|
||||
@ -80,6 +82,7 @@ static std::shared_ptr<File> ZX81FileFromData(const std::vector<uint8_t> &data)
|
||||
// TODO: check that the line numbers declared above exist (?)
|
||||
|
||||
std::shared_ptr<File> file(new File);
|
||||
file->name = StringFromData(name_data, true);
|
||||
file->data = data;
|
||||
file->isZX81 = true;
|
||||
return file;
|
||||
|
@ -19,7 +19,7 @@ namespace ZX8081 {
|
||||
|
||||
struct File {
|
||||
std::vector<uint8_t> data;
|
||||
std::string name;
|
||||
std::wstring name;
|
||||
bool isZX81;
|
||||
};
|
||||
|
||||
|
@ -71,6 +71,49 @@ class FileHolder {
|
||||
*/
|
||||
void ensure_file_is_at_least_length(long length);
|
||||
|
||||
class BitStream {
|
||||
public:
|
||||
BitStream(FILE *f, bool lsb_first) :
|
||||
file_(f),
|
||||
lsb_first_(lsb_first),
|
||||
next_value_(0),
|
||||
bits_remaining_(0) {}
|
||||
|
||||
uint8_t get_bits(int q) {
|
||||
uint8_t result = 0;
|
||||
while(q--) {
|
||||
result = (uint8_t)((result << 1) | get_bit());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
FILE *file_;
|
||||
bool lsb_first_;
|
||||
uint8_t next_value_;
|
||||
int bits_remaining_;
|
||||
|
||||
uint8_t get_bit() {
|
||||
if(!bits_remaining_) {
|
||||
bits_remaining_ = 8;
|
||||
next_value_ = (uint8_t)fgetc(file_);
|
||||
}
|
||||
|
||||
uint8_t bit;
|
||||
if(lsb_first_) {
|
||||
bit = next_value_ & 1;
|
||||
next_value_ >>= 1;
|
||||
} else {
|
||||
bit = next_value_ >> 7;
|
||||
next_value_ <<= 1;
|
||||
}
|
||||
|
||||
bits_remaining_--;
|
||||
|
||||
return bit;
|
||||
}
|
||||
};
|
||||
|
||||
FILE *file_;
|
||||
struct stat file_stats_;
|
||||
bool is_read_only_;
|
||||
|
@ -25,15 +25,13 @@ struct Time {
|
||||
Time() : length(0), clock_rate(1) {}
|
||||
Time(unsigned int unsigned_int_value) : length(unsigned_int_value), clock_rate(1) {}
|
||||
Time(int int_value) : Time((unsigned int)int_value) {}
|
||||
Time(unsigned int length, unsigned int clock_rate) : length(length), clock_rate(clock_rate) { simplify(); }
|
||||
Time(unsigned int length, unsigned int clock_rate) : length(length), clock_rate(clock_rate) {}
|
||||
Time(int length, int clock_rate) : Time((unsigned int)length, (unsigned int)clock_rate) {}
|
||||
Time(uint64_t length, uint64_t clock_rate) {
|
||||
install_result(length, clock_rate);
|
||||
simplify();
|
||||
}
|
||||
Time(float value) {
|
||||
install_float(value);
|
||||
simplify();
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -94,6 +92,10 @@ struct Time {
|
||||
|
||||
inline Time &operator += (const Time &other) {
|
||||
if(!other.length) return *this;
|
||||
if(!length) {
|
||||
*this = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint64_t result_length;
|
||||
uint64_t result_clock_rate;
|
||||
@ -207,6 +209,12 @@ struct Time {
|
||||
|
||||
private:
|
||||
inline void install_result(uint64_t long_length, uint64_t long_clock_rate) {
|
||||
if(long_length <= std::numeric_limits<unsigned int>::max() && long_clock_rate <= std::numeric_limits<unsigned int>::max()) {
|
||||
length = (unsigned int)long_length;
|
||||
clock_rate = (unsigned int)long_clock_rate;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: switch to appropriate values if the result is too large or small to fit, even with trimmed accuracy.
|
||||
if(!long_length) {
|
||||
length = 0;
|
||||
@ -214,6 +222,11 @@ struct Time {
|
||||
return;
|
||||
}
|
||||
|
||||
while(!(long_length&0xf) && !(long_clock_rate&0xf)) {
|
||||
long_length >>= 4;
|
||||
long_clock_rate >>= 4;
|
||||
}
|
||||
|
||||
while(!(long_length&1) && !(long_clock_rate&1)) {
|
||||
long_length >>= 1;
|
||||
long_clock_rate >>= 1;
|
||||
|
@ -18,7 +18,7 @@ CSW::CSW(const char *file_name) :
|
||||
// Check signature.
|
||||
char identifier[22];
|
||||
char signature[] = "Compressed Square Wave";
|
||||
fread(identifier, 1, 22, file_);
|
||||
fread(identifier, 1, strlen(signature), file_);
|
||||
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotCSW;
|
||||
|
||||
// Check terminating byte.
|
||||
|
225
Storage/Tape/Formats/TZX.cpp
Normal file
225
Storage/Tape/Formats/TZX.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
//
|
||||
// TZX.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "TZX.hpp"
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
namespace {
|
||||
const unsigned int StandardTZXClock = 3500000;
|
||||
const unsigned int TZXClockMSMultiplier = 3500;
|
||||
}
|
||||
|
||||
TZX::TZX(const char *file_name) :
|
||||
Storage::FileHolder(file_name),
|
||||
current_level_(false) {
|
||||
|
||||
// Check for signature followed by a 0x1a
|
||||
char identifier[7];
|
||||
char signature[] = "ZXTape!";
|
||||
fread(identifier, 1, strlen(signature), file_);
|
||||
if(memcmp(identifier, signature, strlen(signature))) throw ErrorNotTZX;
|
||||
if(fgetc(file_) != 0x1a) throw ErrorNotTZX;
|
||||
|
||||
// Get version number
|
||||
uint8_t major_version = (uint8_t)fgetc(file_);
|
||||
uint8_t minor_version = (uint8_t)fgetc(file_);
|
||||
|
||||
// Reject if an incompatible version
|
||||
if(major_version != 1 || minor_version > 20) throw ErrorNotTZX;
|
||||
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
void TZX::virtual_reset() {
|
||||
clear();
|
||||
set_is_at_end(false);
|
||||
fseek(file_, 0x0a, SEEK_SET);
|
||||
|
||||
// This is a workaround for arguably dodgy ZX80/ZX81 TZXs; they launch straight
|
||||
// into data but both machines require a gap before data begins. So impose
|
||||
// an initial gap, in the form of a very long wave.
|
||||
current_level_ = false;
|
||||
post_gap(500);
|
||||
}
|
||||
|
||||
void TZX::get_next_pulses() {
|
||||
while(empty()) {
|
||||
uint8_t chunk_id = (uint8_t)fgetc(file_);
|
||||
if(feof(file_)) {
|
||||
set_is_at_end(true);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x10: get_standard_speed_data_block(); break;
|
||||
case 0x11: get_turbo_speed_data_block(); break;
|
||||
case 0x12: get_pure_tone_data_block(); break;
|
||||
case 0x13: get_pulse_sequence(); break;
|
||||
case 0x19: get_generalised_data_block(); break;
|
||||
|
||||
case 0x30: {
|
||||
// Text description. Ripe for ignoring.
|
||||
int length = fgetc(file_);
|
||||
fseek(file_, length, SEEK_CUR);
|
||||
} break;
|
||||
|
||||
default:
|
||||
// In TZX each chunk has a different way of stating or implying its length,
|
||||
// so there is no route past an unimplemented chunk.
|
||||
printf("!!Unknown %02x!!", chunk_id);
|
||||
set_is_at_end(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TZX::get_generalised_data_block() {
|
||||
uint32_t block_length = fgetc32le();
|
||||
long endpoint = ftell(file_) + (long)block_length;
|
||||
uint16_t pause_after_block = fgetc16le();
|
||||
|
||||
uint32_t total_pilot_symbols = fgetc32le();
|
||||
uint8_t maximum_pulses_per_pilot_symbol = (uint8_t)fgetc(file_);
|
||||
uint8_t symbols_in_pilot_table = (uint8_t)fgetc(file_);
|
||||
|
||||
uint32_t total_data_symbols = fgetc32le();
|
||||
uint8_t maximum_pulses_per_data_symbol = (uint8_t)fgetc(file_);
|
||||
uint8_t symbols_in_data_table = (uint8_t)fgetc(file_);
|
||||
|
||||
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);
|
||||
post_gap(pause_after_block);
|
||||
|
||||
// This should be unnecessary, but intends to preserve sanity.
|
||||
fseek(file_, 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) {
|
||||
if(!output_symbols) return;
|
||||
|
||||
// Construct the symbol table.
|
||||
struct Symbol {
|
||||
uint8_t flags;
|
||||
std::vector<uint16_t> pulse_lengths;
|
||||
};
|
||||
std::vector<Symbol> symbol_table;
|
||||
for(int c = 0; c < number_of_symbols; c++) {
|
||||
Symbol symbol;
|
||||
symbol.flags = (uint8_t)fgetc(file_);
|
||||
for(int ic = 0; ic < max_pulses_per_symbol; ic++) {
|
||||
symbol.pulse_lengths.push_back(fgetc16le());
|
||||
}
|
||||
symbol_table.push_back(symbol);
|
||||
}
|
||||
|
||||
// Hence produce the output.
|
||||
BitStream stream(file_, false);
|
||||
int base = 2;
|
||||
int bits = 1;
|
||||
while(base < number_of_symbols) {
|
||||
base <<= 1;
|
||||
bits++;
|
||||
}
|
||||
for(int c = 0; c < output_symbols; c++) {
|
||||
uint8_t symbol_value;
|
||||
int count;
|
||||
if(is_data) {
|
||||
symbol_value = stream.get_bits(bits);
|
||||
count = 1;
|
||||
} else {
|
||||
symbol_value = (uint8_t)fgetc(file_);
|
||||
count = fgetc16le();
|
||||
}
|
||||
if(symbol_value > number_of_symbols) {
|
||||
continue;
|
||||
}
|
||||
Symbol &symbol = symbol_table[symbol_value];
|
||||
|
||||
while(count--) {
|
||||
// Mutate initial output level.
|
||||
switch(symbol.flags & 3) {
|
||||
case 0: break;
|
||||
case 1: current_level_ ^= true; break;
|
||||
case 2: current_level_ = true; break;
|
||||
case 3: current_level_ = false; break;
|
||||
}
|
||||
|
||||
// Output waves.
|
||||
for(auto length : symbol.pulse_lengths) {
|
||||
if(!length) break;
|
||||
post_pulse(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TZX::get_standard_speed_data_block() {
|
||||
__unused uint16_t pause_after_block = fgetc16le();
|
||||
uint16_t data_length = fgetc16le();
|
||||
if(!data_length) return;
|
||||
|
||||
uint8_t first_byte = (uint8_t)fgetc(file_);
|
||||
__unused int pilot_tone_pulses = (first_byte < 128) ? 8063 : 3223;
|
||||
ungetc(first_byte, file_);
|
||||
|
||||
// TODO: output pilot_tone_pulses pulses
|
||||
// TODO: output data_length bytes, in the Spectrum encoding
|
||||
fseek(file_, data_length, SEEK_CUR);
|
||||
// TODO: output a pause of length paused_after_block ms
|
||||
}
|
||||
|
||||
void TZX::get_turbo_speed_data_block() {
|
||||
__unused uint16_t length_of_pilot_pulse = fgetc16le();
|
||||
__unused uint16_t length_of_sync_first_pulse = fgetc16le();
|
||||
__unused uint16_t length_of_sync_second_pulse = fgetc16le();
|
||||
__unused uint16_t length_of_zero_bit_pulse = fgetc16le();
|
||||
__unused uint16_t length_of_one_bit_pulse = fgetc16le();
|
||||
__unused uint16_t length_of_pilot_tone = fgetc16le();
|
||||
__unused uint8_t number_of_bits_in_final_byte = (uint8_t)fgetc(file_);
|
||||
__unused uint16_t pause_after_block = fgetc16le();
|
||||
uint16_t data_length = fgetc16le();
|
||||
|
||||
// TODO output as described
|
||||
fseek(file_, data_length, SEEK_CUR);
|
||||
}
|
||||
|
||||
void TZX::get_pure_tone_data_block() {
|
||||
__unused uint16_t length_of_pulse = fgetc16le();
|
||||
__unused uint16_t nunber_of_pulses = fgetc16le();
|
||||
|
||||
while(nunber_of_pulses--) post_pulse(length_of_pulse);
|
||||
}
|
||||
|
||||
void TZX::get_pulse_sequence() {
|
||||
uint8_t number_of_pulses = (uint8_t)fgetc(file_);
|
||||
while(number_of_pulses--) {
|
||||
post_pulse(fgetc16le());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Output
|
||||
|
||||
void TZX::post_pulse(unsigned int length) {
|
||||
post_pulse(Storage::Time(length, StandardTZXClock));
|
||||
}
|
||||
|
||||
void TZX::post_gap(unsigned int milliseconds) {
|
||||
if(!milliseconds) return;
|
||||
if(milliseconds > 1 && !current_level_) {
|
||||
post_pulse(Storage::Time(TZXClockMSMultiplier, StandardTZXClock));
|
||||
post_pulse(Storage::Time((milliseconds - 1u)*TZXClockMSMultiplier, StandardTZXClock));
|
||||
} else {
|
||||
post_pulse(Storage::Time(milliseconds*TZXClockMSMultiplier, StandardTZXClock));
|
||||
}
|
||||
}
|
||||
|
||||
void TZX::post_pulse(const Storage::Time &time) {
|
||||
emplace_back(current_level_ ? Tape::Pulse::High : Tape::Pulse::Low, time);
|
||||
current_level_ ^= true;
|
||||
}
|
56
Storage/Tape/Formats/TZX.hpp
Normal file
56
Storage/Tape/Formats/TZX.hpp
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// TZX.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef TZX_hpp
|
||||
#define TZX_hpp
|
||||
|
||||
#include "../PulseQueuedTape.hpp"
|
||||
#include "../../FileHolder.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace Tape {
|
||||
|
||||
/*!
|
||||
Provides a @c Tape containing a CSW tape image, which is a compressed 1-bit sampling.
|
||||
*/
|
||||
class TZX: public PulseQueuedTape, public Storage::FileHolder {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c TZX containing content from the file with name @c file_name.
|
||||
|
||||
@throws ErrorNotTZX if this file could not be opened and recognised as a valid TZX file.
|
||||
*/
|
||||
TZX(const char *file_name);
|
||||
|
||||
enum {
|
||||
ErrorNotTZX
|
||||
};
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
void get_next_pulses();
|
||||
|
||||
bool current_level_;
|
||||
|
||||
void get_standard_speed_data_block();
|
||||
void get_turbo_speed_data_block();
|
||||
void get_pure_tone_data_block();
|
||||
void get_pulse_sequence();
|
||||
void get_generalised_data_block();
|
||||
|
||||
void get_generalised_segment(uint32_t output_symbols, uint8_t max_pulses_per_symbol, uint8_t number_of_symbols, bool is_data);
|
||||
|
||||
void post_pulse(unsigned int length);
|
||||
void post_gap(unsigned int milliseconds);
|
||||
|
||||
void post_pulse(const Storage::Time &time);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif /* TZX_hpp */
|
@ -14,8 +14,7 @@
|
||||
|
||||
#pragma mark - ZLib extensions
|
||||
|
||||
static float gzgetfloat(gzFile file)
|
||||
{
|
||||
static float gzgetfloat(gzFile file) {
|
||||
uint8_t bytes[4];
|
||||
gzread(file, bytes, 4);
|
||||
|
||||
@ -42,30 +41,26 @@ static float gzgetfloat(gzFile file)
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t gzget8(gzFile file)
|
||||
{
|
||||
static 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)
|
||||
{
|
||||
static int gzget16(gzFile file) {
|
||||
uint8_t bytes[2];
|
||||
gzread(file, bytes, 2);
|
||||
return bytes[0] | (bytes[1] << 8);
|
||||
}
|
||||
|
||||
static int gzget24(gzFile file)
|
||||
{
|
||||
static 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)
|
||||
{
|
||||
static int gzget32(gzFile file) {
|
||||
uint8_t bytes[4];
|
||||
gzread(file, bytes, 4);
|
||||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
|
||||
@ -75,28 +70,21 @@ using namespace Storage::Tape;
|
||||
|
||||
UEF::UEF(const char *file_name) :
|
||||
time_base_(1200),
|
||||
is_at_end_(false),
|
||||
pulse_pointer_(0),
|
||||
is_300_baud_(false)
|
||||
{
|
||||
is_300_baud_(false) {
|
||||
file_ = gzopen(file_name, "rb");
|
||||
|
||||
char identifier[10];
|
||||
int bytes_read = gzread(file_, identifier, 10);
|
||||
if(bytes_read < 10 || strcmp(identifier, "UEF File!"))
|
||||
{
|
||||
if(bytes_read < 10 || strcmp(identifier, "UEF File!")) {
|
||||
throw ErrorNotUEF;
|
||||
}
|
||||
|
||||
uint8_t version[2];
|
||||
gzread(file_, version, 2);
|
||||
|
||||
if(version[1] > 0 || version[0] > 10)
|
||||
{
|
||||
if(version[1] > 0 || version[0] > 10) {
|
||||
throw ErrorNotUEF;
|
||||
}
|
||||
|
||||
parse_next_tape_chunk();
|
||||
}
|
||||
|
||||
UEF::~UEF()
|
||||
@ -106,47 +94,16 @@ UEF::~UEF()
|
||||
|
||||
#pragma mark - Public methods
|
||||
|
||||
void UEF::virtual_reset()
|
||||
{
|
||||
void UEF::virtual_reset() {
|
||||
gzseek(file_, 12, SEEK_SET);
|
||||
is_at_end_ = false;
|
||||
parse_next_tape_chunk();
|
||||
}
|
||||
|
||||
bool UEF::is_at_end()
|
||||
{
|
||||
return is_at_end_;
|
||||
}
|
||||
|
||||
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
|
||||
{
|
||||
Pulse next_pulse;
|
||||
|
||||
if(is_at_end_)
|
||||
{
|
||||
next_pulse.type = Pulse::Zero;
|
||||
next_pulse.length.length = time_base_ * 4;
|
||||
next_pulse.length.clock_rate = time_base_ * 4;
|
||||
return next_pulse;
|
||||
}
|
||||
|
||||
next_pulse = queued_pulses_[pulse_pointer_];
|
||||
pulse_pointer_++;
|
||||
if(pulse_pointer_ == queued_pulses_.size())
|
||||
{
|
||||
queued_pulses_.clear();
|
||||
pulse_pointer_ = 0;
|
||||
parse_next_tape_chunk();
|
||||
}
|
||||
return next_pulse;
|
||||
set_is_at_end(false);
|
||||
clear();
|
||||
}
|
||||
|
||||
#pragma mark - Chunk navigator
|
||||
|
||||
void UEF::parse_next_tape_chunk()
|
||||
{
|
||||
while(queued_pulses_.empty())
|
||||
{
|
||||
void UEF::get_next_pulses() {
|
||||
while(empty()) {
|
||||
// read chunk details
|
||||
uint16_t chunk_id = (uint16_t)gzget16(file_);
|
||||
uint32_t chunk_length = (uint32_t)gzget32(file_);
|
||||
@ -154,14 +111,12 @@ void UEF::parse_next_tape_chunk()
|
||||
// figure out where the next chunk will start
|
||||
z_off_t start_of_next_chunk = gztell(file_) + chunk_length;
|
||||
|
||||
if(gzeof(file_))
|
||||
{
|
||||
is_at_end_ = true;
|
||||
if(gzeof(file_)) {
|
||||
set_is_at_end(true);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(chunk_id)
|
||||
{
|
||||
switch(chunk_id) {
|
||||
case 0x0100: queue_implicit_bit_pattern(chunk_length); break;
|
||||
case 0x0102: queue_explicit_bit_pattern(chunk_length); break;
|
||||
case 0x0112: queue_integer_gap(); break;
|
||||
@ -173,16 +128,15 @@ void UEF::parse_next_tape_chunk()
|
||||
case 0x0114: queue_security_cycles(); break;
|
||||
case 0x0104: queue_defined_data(chunk_length); break;
|
||||
|
||||
case 0x0113: // change of base rate
|
||||
{
|
||||
// change of base rate
|
||||
case 0x0113: {
|
||||
// TODO: something smarter than just converting this to an int
|
||||
float new_time_base = gzgetfloat(file_);
|
||||
time_base_ = (unsigned int)roundf(new_time_base);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0117:
|
||||
{
|
||||
case 0x0117: {
|
||||
int baud_rate = gzget16(file_);
|
||||
is_300_baud_ = (baud_rate == 300);
|
||||
}
|
||||
@ -199,16 +153,14 @@ void UEF::parse_next_tape_chunk()
|
||||
|
||||
#pragma mark - Chunk parsers
|
||||
|
||||
void UEF::queue_implicit_bit_pattern(uint32_t length)
|
||||
{
|
||||
void UEF::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::queue_explicit_bit_pattern(uint32_t length) {
|
||||
size_t length_in_bits = (length << 3) - (size_t)gzget8(file_);
|
||||
uint8_t current_byte = 0;
|
||||
for(size_t bit = 0; bit < length_in_bits; bit++)
|
||||
@ -219,31 +171,27 @@ void UEF::queue_explicit_bit_pattern(uint32_t length)
|
||||
}
|
||||
}
|
||||
|
||||
void UEF::queue_integer_gap()
|
||||
{
|
||||
void UEF::queue_integer_gap() {
|
||||
Time duration;
|
||||
duration.length = (unsigned int)gzget16(file_);
|
||||
duration.clock_rate = time_base_;
|
||||
queued_pulses_.emplace_back(Pulse::Zero, duration);
|
||||
emplace_back(Pulse::Zero, duration);
|
||||
}
|
||||
|
||||
void UEF::queue_floating_point_gap()
|
||||
{
|
||||
void UEF::queue_floating_point_gap() {
|
||||
float length = gzgetfloat(file_);
|
||||
Time duration;
|
||||
duration.length = (unsigned int)(length * 4000000);
|
||||
duration.clock_rate = 4000000;
|
||||
queued_pulses_.emplace_back(Pulse::Zero, duration);
|
||||
emplace_back(Pulse::Zero, duration);
|
||||
}
|
||||
|
||||
void UEF::queue_carrier_tone()
|
||||
{
|
||||
void UEF::queue_carrier_tone() {
|
||||
unsigned int number_of_cycles = (unsigned int)gzget16(file_);
|
||||
while(number_of_cycles--) queue_bit(1);
|
||||
}
|
||||
|
||||
void UEF::queue_carrier_tone_with_dummy()
|
||||
{
|
||||
void UEF::queue_carrier_tone_with_dummy() {
|
||||
unsigned int pre_cycles = (unsigned int)gzget16(file_);
|
||||
unsigned int post_cycles = (unsigned int)gzget16(file_);
|
||||
while(pre_cycles--) queue_bit(1);
|
||||
@ -251,15 +199,13 @@ void UEF::queue_carrier_tone_with_dummy()
|
||||
while(post_cycles--) queue_bit(1);
|
||||
}
|
||||
|
||||
void UEF::queue_security_cycles()
|
||||
{
|
||||
void UEF::queue_security_cycles() {
|
||||
int number_of_cycles = gzget24(file_);
|
||||
bool first_is_pulse = gzget8(file_) == 'P';
|
||||
bool last_is_pulse = gzget8(file_) == 'P';
|
||||
|
||||
uint8_t current_byte = 0;
|
||||
for(int cycle = 0; cycle < number_of_cycles; cycle++)
|
||||
{
|
||||
for(int cycle = 0; cycle < number_of_cycles; cycle++) {
|
||||
if(!(cycle&7)) current_byte = gzget8(file_);
|
||||
int bit = (current_byte >> 7);
|
||||
current_byte <<= 1;
|
||||
@ -268,24 +214,18 @@ void UEF::queue_security_cycles()
|
||||
duration.length = bit ? 1 : 2;
|
||||
duration.clock_rate = time_base_ * 4;
|
||||
|
||||
if(!cycle && first_is_pulse)
|
||||
{
|
||||
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||
}
|
||||
else if(cycle == number_of_cycles-1 && last_is_pulse)
|
||||
{
|
||||
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||
if(!cycle && first_is_pulse) {
|
||||
emplace_back(Pulse::High, duration);
|
||||
} else if(cycle == number_of_cycles-1 && last_is_pulse) {
|
||||
emplace_back(Pulse::Low, duration);
|
||||
} else {
|
||||
emplace_back(Pulse::Low, duration);
|
||||
emplace_back(Pulse::High, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UEF::queue_defined_data(uint32_t length)
|
||||
{
|
||||
void UEF::queue_defined_data(uint32_t length) {
|
||||
if(length < 3) return;
|
||||
|
||||
int bits_per_packet = gzget8(file_);
|
||||
@ -296,8 +236,7 @@ void UEF::queue_defined_data(uint32_t length)
|
||||
number_of_stop_bits = abs(number_of_stop_bits);
|
||||
|
||||
length -= 3;
|
||||
while(length--)
|
||||
{
|
||||
while(length--) {
|
||||
uint8_t byte = gzget8(file_);
|
||||
|
||||
uint8_t parity_value = byte;
|
||||
@ -307,14 +246,12 @@ void UEF::queue_defined_data(uint32_t length)
|
||||
|
||||
queue_bit(0);
|
||||
int c = bits_per_packet;
|
||||
while(c--)
|
||||
{
|
||||
while(c--) {
|
||||
queue_bit(byte&1);
|
||||
byte >>= 1;
|
||||
}
|
||||
|
||||
switch(parity_type)
|
||||
{
|
||||
switch(parity_type) {
|
||||
default: break;
|
||||
case 'E': queue_bit(parity_value&1); break;
|
||||
case 'O': queue_bit((parity_value&1) ^ 1); break;
|
||||
@ -322,45 +259,38 @@ void UEF::queue_defined_data(uint32_t length)
|
||||
|
||||
int stop_bits = number_of_stop_bits;
|
||||
while(stop_bits--) queue_bit(1);
|
||||
if(has_extra_stop_wave)
|
||||
{
|
||||
if(has_extra_stop_wave) {
|
||||
Time duration;
|
||||
duration.length = 1;
|
||||
duration.clock_rate = time_base_ * 4;
|
||||
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||
emplace_back(Pulse::Low, duration);
|
||||
emplace_back(Pulse::High, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Queuing helpers
|
||||
|
||||
void UEF::queue_implicit_byte(uint8_t byte)
|
||||
{
|
||||
void UEF::queue_implicit_byte(uint8_t byte) {
|
||||
queue_bit(0);
|
||||
int c = 8;
|
||||
while(c--)
|
||||
{
|
||||
while(c--) {
|
||||
queue_bit(byte&1);
|
||||
byte >>= 1;
|
||||
}
|
||||
queue_bit(1);
|
||||
}
|
||||
|
||||
void UEF::queue_bit(int bit)
|
||||
{
|
||||
void UEF::queue_bit(int bit) {
|
||||
int number_of_cycles;
|
||||
Time duration;
|
||||
duration.clock_rate = time_base_ * 4;
|
||||
|
||||
if(bit)
|
||||
{
|
||||
if(bit) {
|
||||
// encode high-frequency waves
|
||||
duration.length = 1;
|
||||
number_of_cycles = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// encode low-frequency waves
|
||||
duration.length = 2;
|
||||
number_of_cycles = 1;
|
||||
@ -368,9 +298,8 @@ void UEF::queue_bit(int bit)
|
||||
|
||||
if(is_300_baud_) number_of_cycles *= 4;
|
||||
|
||||
while(number_of_cycles--)
|
||||
{
|
||||
queued_pulses_.emplace_back(Pulse::Low, duration);
|
||||
queued_pulses_.emplace_back(Pulse::High, duration);
|
||||
while(number_of_cycles--) {
|
||||
emplace_back(Pulse::Low, duration);
|
||||
emplace_back(Pulse::High, duration);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
#ifndef TapeUEF_hpp
|
||||
#define TapeUEF_hpp
|
||||
|
||||
#include "../Tape.hpp"
|
||||
#include "../PulseQueuedTape.hpp"
|
||||
#include <zlib.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@ -20,7 +20,7 @@ namespace Tape {
|
||||
/*!
|
||||
Provides a @c Tape containing a UEF tape image, a slightly-convoluted description of pulses.
|
||||
*/
|
||||
class UEF : public Tape {
|
||||
class UEF : public PulseQueuedTape {
|
||||
public:
|
||||
/*!
|
||||
Constructs a @c UEF containing content from the file with name @c file_name.
|
||||
@ -34,22 +34,14 @@ class UEF : public Tape {
|
||||
ErrorNotUEF
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
|
||||
gzFile file_;
|
||||
unsigned int time_base_;
|
||||
bool is_at_end_;
|
||||
bool is_300_baud_;
|
||||
|
||||
std::vector<Pulse> queued_pulses_;
|
||||
size_t pulse_pointer_;
|
||||
|
||||
void parse_next_tape_chunk();
|
||||
void get_next_pulses();
|
||||
|
||||
void queue_implicit_bit_pattern(uint32_t length);
|
||||
void queue_explicit_bit_pattern(uint32_t length);
|
||||
|
@ -13,30 +13,33 @@ using namespace Storage::Tape::ZX8081;
|
||||
Parser::Parser() : pulse_was_high_(false), pulse_time_(0) {}
|
||||
|
||||
void Parser::process_pulse(const Storage::Tape::Tape::Pulse &pulse) {
|
||||
pulse_time_ += pulse.length;
|
||||
// If this is anything other than a transition from low to high, just add it to the
|
||||
// count of time.
|
||||
bool pulse_is_high = pulse.type == Storage::Tape::Tape::Pulse::High;
|
||||
|
||||
if(pulse_is_high == pulse_was_high_) return;
|
||||
bool pulse_did_change = pulse_is_high != pulse_was_high_;
|
||||
pulse_was_high_ = pulse_is_high;
|
||||
if(!pulse_did_change || !pulse_is_high) {
|
||||
pulse_time_ += pulse.length;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise post a new pulse.
|
||||
post_pulse();
|
||||
pulse_time_ = pulse.length;
|
||||
}
|
||||
|
||||
void Parser::post_pulse() {
|
||||
const float expected_pulse_length = 150.0f / 1000000.0f;
|
||||
const float expected_pulse_length = 300.0f / 1000000.0f;
|
||||
const float expected_gap_length = 1300.0f / 1000000.0f;
|
||||
float pulse_time = pulse_time_.get_float();
|
||||
pulse_time_.set_zero();
|
||||
|
||||
if(pulse_time > expected_gap_length * 1.25f) {
|
||||
push_wave(WaveType::LongGap);
|
||||
}
|
||||
else if(pulse_time > expected_pulse_length * 1.25f) {
|
||||
} else if(pulse_time > expected_pulse_length * 1.25f) {
|
||||
push_wave(WaveType::Gap);
|
||||
}
|
||||
else if(pulse_time >= expected_pulse_length * 0.75f && pulse_time <= expected_pulse_length * 1.25f) {
|
||||
} else if(pulse_time >= expected_pulse_length * 0.75f && pulse_time <= expected_pulse_length * 1.25f) {
|
||||
push_wave(WaveType::Pulse);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
push_wave(WaveType::Unrecognised);
|
||||
}
|
||||
}
|
||||
@ -55,7 +58,12 @@ void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(waves.size() >= 9) {
|
||||
if(waves[0] == WaveType::Unrecognised) {
|
||||
push_symbol(SymbolType::Unrecognised, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if(waves.size() >= 4) {
|
||||
size_t wave_offset = 0;
|
||||
// If the very first thing is a gap, swallow it.
|
||||
if(waves[0] == WaveType::Gap) {
|
||||
@ -70,10 +78,7 @@ void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
||||
|
||||
// If those pulses were followed by a gap then they might be
|
||||
// a recognised symbol.
|
||||
if(number_of_pulses > 17 || number_of_pulses < 7) {
|
||||
push_symbol(SymbolType::Unrecognised, 1);
|
||||
}
|
||||
else if(number_of_pulses + wave_offset < waves.size() &&
|
||||
if(number_of_pulses + wave_offset < waves.size() &&
|
||||
(waves[number_of_pulses + wave_offset] == WaveType::LongGap || waves[number_of_pulses + wave_offset] == WaveType::Gap)) {
|
||||
// A 1 is 18 up/down waves, a 0 is 8. But the final down will be indistinguishable from
|
||||
// the gap that follows the bit due to the simplified "high is high, everything else is low"
|
||||
@ -81,8 +86,8 @@ void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
||||
// 17 and/or 7 pulses.
|
||||
size_t gaps_to_swallow = wave_offset + ((waves[number_of_pulses + wave_offset] == WaveType::Gap) ? 1 : 0);
|
||||
switch(number_of_pulses) {
|
||||
case 18: case 17: push_symbol(SymbolType::One, (int)(number_of_pulses + gaps_to_swallow)); break;
|
||||
case 8: case 7: push_symbol(SymbolType::Zero, (int)(number_of_pulses + gaps_to_swallow)); break;
|
||||
case 8: push_symbol(SymbolType::One, (int)(number_of_pulses + gaps_to_swallow)); break;
|
||||
case 3: push_symbol(SymbolType::Zero, (int)(number_of_pulses + gaps_to_swallow)); break;
|
||||
default: push_symbol(SymbolType::Unrecognised, 1); break;
|
||||
}
|
||||
}
|
||||
@ -92,17 +97,18 @@ void Parser::inspect_waves(const std::vector<WaveType> &waves) {
|
||||
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
int c = 8;
|
||||
int result = 0;
|
||||
while(c--) {
|
||||
while(c) {
|
||||
if(is_at_end(tape)) return -1;
|
||||
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
if(symbol == SymbolType::FileGap) {
|
||||
if(symbol != SymbolType::One && symbol != SymbolType::Zero) {
|
||||
if(c == 8) continue;
|
||||
return_symbol(symbol);
|
||||
return -1;
|
||||
}
|
||||
if(symbol != SymbolType::One && symbol != SymbolType::Zero) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
result = (result << 1) | (symbol == SymbolType::One ? 1 : 0);
|
||||
c--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -113,7 +119,7 @@ std::shared_ptr<std::vector<uint8_t>> Parser::get_next_file_data(const std::shar
|
||||
if(symbol != SymbolType::FileGap) {
|
||||
return nullptr;
|
||||
}
|
||||
while(symbol == SymbolType::FileGap && !is_at_end(tape)) {
|
||||
while((symbol == SymbolType::FileGap || symbol == SymbolType::Unrecognised) && !is_at_end(tape)) {
|
||||
symbol = get_next_symbol(tape);
|
||||
}
|
||||
if(is_at_end(tape)) return nullptr;
|
||||
@ -131,6 +137,8 @@ std::shared_ptr<std::vector<uint8_t>> Parser::get_next_file_data(const std::shar
|
||||
|
||||
std::shared_ptr<Storage::Data::ZX8081::File> Parser::get_next_file(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
||||
std::shared_ptr<std::vector<uint8_t>> file_data = get_next_file_data(tape);
|
||||
if(!file_data) return nullptr;
|
||||
if(!file_data) {
|
||||
return nullptr;
|
||||
}
|
||||
return Storage::Data::ZX8081::FileFromData(*file_data);
|
||||
}
|
||||
|
61
Storage/Tape/PulseQueuedTape.cpp
Normal file
61
Storage/Tape/PulseQueuedTape.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// PulseQueuedTape.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "PulseQueuedTape.hpp"
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
PulseQueuedTape::PulseQueuedTape() : pulse_pointer_(0) {}
|
||||
|
||||
bool PulseQueuedTape::is_at_end() {
|
||||
return is_at_end_;
|
||||
}
|
||||
|
||||
void PulseQueuedTape::set_is_at_end(bool is_at_end) {
|
||||
is_at_end_ = is_at_end;
|
||||
}
|
||||
|
||||
void PulseQueuedTape::clear() {
|
||||
queued_pulses_.clear();
|
||||
pulse_pointer_ = 0;
|
||||
}
|
||||
|
||||
bool PulseQueuedTape::empty() {
|
||||
return queued_pulses_.empty();
|
||||
}
|
||||
|
||||
void PulseQueuedTape::emplace_back(Tape::Pulse::Type type, Time length) {
|
||||
queued_pulses_.emplace_back(type, length);
|
||||
}
|
||||
|
||||
Tape::Pulse PulseQueuedTape::silence() {
|
||||
Pulse silence;
|
||||
silence.type = Pulse::Zero;
|
||||
silence.length.length = 1;
|
||||
silence.length.clock_rate = 1;
|
||||
return silence;
|
||||
}
|
||||
|
||||
Tape::Pulse PulseQueuedTape::virtual_get_next_pulse() {
|
||||
if(is_at_end_) {
|
||||
return silence();
|
||||
}
|
||||
|
||||
if(pulse_pointer_ == queued_pulses_.size()) {
|
||||
clear();
|
||||
get_next_pulses();
|
||||
|
||||
if(is_at_end_ || pulse_pointer_ == queued_pulses_.size()) {
|
||||
return silence();
|
||||
}
|
||||
}
|
||||
|
||||
size_t read_pointer = pulse_pointer_;
|
||||
pulse_pointer_++;
|
||||
return queued_pulses_[read_pointer];
|
||||
}
|
53
Storage/Tape/PulseQueuedTape.hpp
Normal file
53
Storage/Tape/PulseQueuedTape.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// PulseQueuedTape.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PulseQueuedTape_hpp
|
||||
#define PulseQueuedTape_hpp
|
||||
|
||||
#include "Tape.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace 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.
|
||||
|
||||
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
|
||||
virtual, giving subclasses a chance to provide the next batch of pulses.
|
||||
*/
|
||||
class PulseQueuedTape: public Tape {
|
||||
public:
|
||||
PulseQueuedTape();
|
||||
bool is_at_end();
|
||||
|
||||
protected:
|
||||
void emplace_back(Tape::Pulse::Type type, Time length);
|
||||
void clear();
|
||||
bool empty();
|
||||
|
||||
void set_is_at_end(bool);
|
||||
virtual void get_next_pulses() = 0;
|
||||
|
||||
private:
|
||||
Pulse virtual_get_next_pulse();
|
||||
Pulse silence();
|
||||
|
||||
std::vector<Pulse> queued_pulses_;
|
||||
size_t pulse_pointer_;
|
||||
bool is_at_end_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PulseQueuedTape_hpp */
|
@ -20,22 +20,47 @@ TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
#pragma mark - Seeking
|
||||
|
||||
void Storage::Tape::Tape::seek(Time &seek_time) {
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
while(next_time_ <= seek_time) get_next_pulse();
|
||||
Time next_time(0);
|
||||
reset();
|
||||
while(next_time <= seek_time) {
|
||||
get_next_pulse();
|
||||
next_time += pulse_.length;
|
||||
}
|
||||
}
|
||||
|
||||
Storage::Time Tape::get_current_time() {
|
||||
Time time(0);
|
||||
uint64_t steps = get_offset();
|
||||
reset();
|
||||
while(steps--) {
|
||||
get_next_pulse();
|
||||
time += pulse_.length;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
void Storage::Tape::Tape::reset() {
|
||||
current_time_.set_zero();
|
||||
next_time_.set_zero();
|
||||
offset_ = 0;
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
Tape::Pulse Tape::get_next_pulse() {
|
||||
Tape::Pulse pulse = virtual_get_next_pulse();
|
||||
current_time_ = next_time_;
|
||||
next_time_ += pulse.length;
|
||||
return pulse;
|
||||
pulse_ = virtual_get_next_pulse();
|
||||
offset_++;
|
||||
return pulse_;
|
||||
}
|
||||
|
||||
uint64_t Tape::get_offset() {
|
||||
return offset_;
|
||||
}
|
||||
|
||||
void Tape::set_offset(uint64_t offset) {
|
||||
if(offset == offset_) return;
|
||||
if(offset < offset_) {
|
||||
reset();
|
||||
}
|
||||
offset -= offset_;
|
||||
while(offset--) get_next_pulse();
|
||||
}
|
||||
|
||||
#pragma mark - Player
|
||||
@ -85,7 +110,7 @@ void TapePlayer::process_next_event() {
|
||||
#pragma mark - Binary Player
|
||||
|
||||
BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
|
||||
TapePlayer(input_clock_rate), motor_is_running_(false)
|
||||
TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false)
|
||||
{}
|
||||
|
||||
void BinaryTapePlayer::set_motor_control(bool enabled) {
|
||||
@ -97,7 +122,7 @@ void BinaryTapePlayer::set_tape_output(bool set) {
|
||||
}
|
||||
|
||||
bool BinaryTapePlayer::get_input() {
|
||||
return input_level_;
|
||||
return motor_is_running_ && input_level_;
|
||||
}
|
||||
|
||||
void BinaryTapePlayer::run_for_cycles(int number_of_cycles) {
|
||||
|
@ -52,16 +52,33 @@ class Tape {
|
||||
/// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise.
|
||||
virtual bool is_at_end() = 0;
|
||||
|
||||
/// @returns the amount of time preceeding the most recently-returned pulse.
|
||||
virtual Time get_current_time() { return current_time_; }
|
||||
/*!
|
||||
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();
|
||||
|
||||
/// Advances or reverses the tape to the last time before or at @c time from which a pulse starts.
|
||||
/*!
|
||||
Moves the tape to the first time at which the specified offset would be returned by get_offset.
|
||||
*/
|
||||
virtual 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();
|
||||
|
||||
/*!
|
||||
Seeks to @c time. Potentially expensive.
|
||||
*/
|
||||
virtual void seek(Time &time);
|
||||
|
||||
virtual ~Tape() {};
|
||||
|
||||
private:
|
||||
Time current_time_, next_time_;
|
||||
uint64_t offset_;
|
||||
Tape::Pulse pulse_;
|
||||
|
||||
virtual Pulse virtual_get_next_pulse() = 0;
|
||||
virtual void virtual_reset() = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user