diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 6bf9cab22..23fe0db44 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -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: diff --git a/Machines/ZX8081/ZX8081.hpp b/Machines/ZX8081/ZX8081.hpp index 23d773dbf..4b6bf9dda 100644 --- a/Machines/ZX8081/ZX8081.hpp +++ b/Machines/ZX8081/ZX8081.hpp @@ -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_; }; diff --git a/NumberTheory/Factors.hpp b/NumberTheory/Factors.hpp index 03ffc8593..98832d247 100644 --- a/NumberTheory/Factors.hpp +++ b/NumberTheory/Factors.hpp @@ -9,15 +9,15 @@ #ifndef Factors_hpp #define Factors_hpp +#include + namespace NumberTheory { /*! @returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm. */ template T greatest_common_divisor(T a, T b) { if(a < b) { - T swap = b; - b = a; - a = swap; + std::swap(a, b); } while(1) { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cf82f9e73..fe0f78dc4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4B3F1B441E0388D200DB26EE /* PCMPatchedTrack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCMPatchedTrack.cpp; sourceTree = ""; }; 4B3F1B451E0388D200DB26EE /* PCMPatchedTrack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PCMPatchedTrack.hpp; sourceTree = ""; }; + 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TZX.cpp; sourceTree = ""; }; + 4B448E801F1C45A00009ABD6 /* TZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TZX.hpp; sourceTree = ""; }; + 4B448E821F1C4C480009ABD6 /* PulseQueuedTape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PulseQueuedTape.cpp; sourceTree = ""; }; + 4B448E831F1C4C480009ABD6 /* PulseQueuedTape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PulseQueuedTape.hpp; sourceTree = ""; }; 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = AllSuiteA.bin; path = AllSuiteA/AllSuiteA.bin; sourceTree = ""; }; 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 6502_functional_test.bin; path = "Klaus Dormann/6502_functional_test.bin"; sourceTree = ""; }; 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */ = {isa = PBXFileReference; lastKnownFileType = file; name = BCDTEST_beeb; path = BCDTest/BCDTEST_beeb; sourceTree = ""; }; @@ -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 */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 4ca666175..bd6e68c48 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -212,6 +212,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).MachineDocument + + CFBundleTypeExtensions + + tzx + + CFBundleTypeIconFile + cassette + CFBundleTypeName + Tape Image + CFBundleTypeRole + Viewer + NSDocumentClass + $(PRODUCT_MODULE_NAME).MachineDocument + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 41c1b87ce..2d42fffbe 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -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 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 diff --git a/StaticAnalyser/ZX8081/StaticAnalyser.cpp b/StaticAnalyser/ZX8081/StaticAnalyser.cpp index 93fd276c0..2451d1aee 100644 --- a/StaticAnalyser/ZX8081/StaticAnalyser.cpp +++ b/StaticAnalyser/ZX8081/StaticAnalyser.cpp @@ -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; diff --git a/Storage/Data/ZX8081.cpp b/Storage/Data/ZX8081.cpp index 836c85dde..d706dfc4b 100644 --- a/Storage/Data/ZX8081.cpp +++ b/Storage/Data/ZX8081.cpp @@ -50,8 +50,10 @@ static std::shared_ptr ZX81FileFromData(const std::vector &data) // Look for a file name. size_t data_pointer = 0; + std::vector 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 ZX81FileFromData(const std::vector &data) // TODO: check that the line numbers declared above exist (?) std::shared_ptr file(new File); + file->name = StringFromData(name_data, true); file->data = data; file->isZX81 = true; return file; diff --git a/Storage/Data/ZX8081.hpp b/Storage/Data/ZX8081.hpp index 148479556..780cc8a15 100644 --- a/Storage/Data/ZX8081.hpp +++ b/Storage/Data/ZX8081.hpp @@ -19,7 +19,7 @@ namespace ZX8081 { struct File { std::vector data; - std::string name; + std::wstring name; bool isZX81; }; diff --git a/Storage/FileHolder.hpp b/Storage/FileHolder.hpp index 458927608..a2be837bd 100644 --- a/Storage/FileHolder.hpp +++ b/Storage/FileHolder.hpp @@ -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_; diff --git a/Storage/Storage.hpp b/Storage/Storage.hpp index d2eb84b36..bb6ccd3aa 100644 --- a/Storage/Storage.hpp +++ b/Storage/Storage.hpp @@ -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::max() && long_clock_rate <= std::numeric_limits::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; diff --git a/Storage/Tape/Formats/CSW.cpp b/Storage/Tape/Formats/CSW.cpp index 6018341a7..036ad1258 100644 --- a/Storage/Tape/Formats/CSW.cpp +++ b/Storage/Tape/Formats/CSW.cpp @@ -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. diff --git a/Storage/Tape/Formats/TZX.cpp b/Storage/Tape/Formats/TZX.cpp new file mode 100644 index 000000000..37b0cb169 --- /dev/null +++ b/Storage/Tape/Formats/TZX.cpp @@ -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 pulse_lengths; + }; + std::vector 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; +} diff --git a/Storage/Tape/Formats/TZX.hpp b/Storage/Tape/Formats/TZX.hpp new file mode 100644 index 000000000..d0a3eedfd --- /dev/null +++ b/Storage/Tape/Formats/TZX.hpp @@ -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 */ diff --git a/Storage/Tape/Formats/TapeUEF.cpp b/Storage/Tape/Formats/TapeUEF.cpp index 7a6926eb8..a17883044 100644 --- a/Storage/Tape/Formats/TapeUEF.cpp +++ b/Storage/Tape/Formats/TapeUEF.cpp @@ -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); } } diff --git a/Storage/Tape/Formats/TapeUEF.hpp b/Storage/Tape/Formats/TapeUEF.hpp index 300dff29a..58f6b5ab8 100644 --- a/Storage/Tape/Formats/TapeUEF.hpp +++ b/Storage/Tape/Formats/TapeUEF.hpp @@ -9,7 +9,7 @@ #ifndef TapeUEF_hpp #define TapeUEF_hpp -#include "../Tape.hpp" +#include "../PulseQueuedTape.hpp" #include #include #include @@ -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 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); diff --git a/Storage/Tape/Parsers/ZX8081.cpp b/Storage/Tape/Parsers/ZX8081.cpp index 960992dd5..68d3fc4b1 100644 --- a/Storage/Tape/Parsers/ZX8081.cpp +++ b/Storage/Tape/Parsers/ZX8081.cpp @@ -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 &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 &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,9 +86,9 @@ void Parser::inspect_waves(const std::vector &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; - default: push_symbol(SymbolType::Unrecognised, 1); 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 &waves) { int Parser::get_next_byte(const std::shared_ptr &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> 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> Parser::get_next_file_data(const std::shar std::shared_ptr Parser::get_next_file(const std::shared_ptr &tape) { std::shared_ptr> file_data = get_next_file_data(tape); - if(!file_data) return nullptr; + if(!file_data) { + return nullptr; + } return Storage::Data::ZX8081::FileFromData(*file_data); } diff --git a/Storage/Tape/PulseQueuedTape.cpp b/Storage/Tape/PulseQueuedTape.cpp new file mode 100644 index 000000000..82f2d40e9 --- /dev/null +++ b/Storage/Tape/PulseQueuedTape.cpp @@ -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]; +} diff --git a/Storage/Tape/PulseQueuedTape.hpp b/Storage/Tape/PulseQueuedTape.hpp new file mode 100644 index 000000000..634412c50 --- /dev/null +++ b/Storage/Tape/PulseQueuedTape.hpp @@ -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 + +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 queued_pulses_; + size_t pulse_pointer_; + bool is_at_end_; +}; + +} +} + +#endif /* PulseQueuedTape_hpp */ diff --git a/Storage/Tape/Tape.cpp b/Storage/Tape/Tape.cpp index b615fcf50..e0e3e81e3 100644 --- a/Storage/Tape/Tape.cpp +++ b/Storage/Tape/Tape.cpp @@ -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) { diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index 2cd6c028b..6f72ee4f7 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -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;