diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index e0a7e563f..26e53c38c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; }; + 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; + 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; }; 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; }; 4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */; }; @@ -497,6 +499,10 @@ 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = ""; }; 4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = ""; }; + 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = ""; }; + 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = ""; }; + 4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = ""; }; + 4B8805F31DCFD22A003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Parsers/Commodore.hpp; sourceTree = ""; }; 4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = ""; }; 4B8FE2141DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Atari2600Options.xib"; sourceTree = SOURCE_ROOT; }; 4B8FE2161DA19D5F0090D3CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MachineDocument.xib"; sourceTree = SOURCE_ROOT; }; @@ -1169,6 +1175,7 @@ isa = PBXGroup; children = ( 4B69FB411C4D941400B5F0AA /* Formats */, + 4B8805F11DCFC9A2003085B1 /* Parsers */, 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, ); @@ -1191,6 +1198,17 @@ path = Formats; sourceTree = ""; }; + 4B8805F11DCFC9A2003085B1 /* Parsers */ = { + isa = PBXGroup; + children = ( + 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */, + 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */, + 4B8805F21DCFD22A003085B1 /* Commodore.cpp */, + 4B8805F31DCFD22A003085B1 /* Commodore.hpp */, + ); + name = Parsers; + sourceTree = ""; + }; 4BA799961D8B65730045123D /* Atari */ = { isa = PBXGroup; children = ( @@ -2294,8 +2312,10 @@ 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B8FE2201DA19D7C0090D3CE /* Atari2600OptionsPanel.swift in Sources */, + 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, 4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */, + 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */, 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */, diff --git a/StaticAnalyser/Acorn/Tape.cpp b/StaticAnalyser/Acorn/Tape.cpp index 92a1bd573..49d961c85 100644 --- a/StaticAnalyser/Acorn/Tape.cpp +++ b/StaticAnalyser/Acorn/Tape.cpp @@ -9,115 +9,12 @@ #include "Tape.hpp" #include -#include "../TapeParser.hpp" #include "../../NumberTheory/CRC.hpp" +#include "../../Storage/Tape/Parsers/Acorn.hpp" using namespace StaticAnalyser::Acorn; -enum class WaveType { - Short, Long, Unrecognised -}; - -enum class SymbolType { - One, Zero -}; - -class Acorn1200BaudTapeParser: public StaticAnalyer::TapeParser { - public: - Acorn1200BaudTapeParser() : - TapeParser(), - _crc(0x1021, 0x0000) {} - - int get_next_bit(const std::shared_ptr &tape) - { - SymbolType symbol = get_next_symbol(tape); - return (symbol == SymbolType::One) ? 1 : 0; - } - - int get_next_byte(const std::shared_ptr &tape) - { - int value = 0; - int c = 8; - if(get_next_bit(tape)) - { - set_error_flag(); - return -1; - } - while(c--) - { - value = (value >> 1) | (get_next_bit(tape) << 7); - } - if(!get_next_bit(tape)) - { - set_error_flag(); - return -1; - } - _crc.add((uint8_t)value); - return value; - } - - int get_next_short(const std::shared_ptr &tape) - { - int result = get_next_byte(tape); - result |= get_next_byte(tape) << 8; - return result; - } - - int get_next_word(const std::shared_ptr &tape) - { - int result = get_next_short(tape); - result |= get_next_short(tape) << 8; - return result; - } - - void reset_crc() { _crc.reset(); } - uint16_t get_crc() { return _crc.get_value(); } - - private: - void process_pulse(Storage::Tape::Tape::Pulse pulse) - { - switch(pulse.type) - { - default: break; - case Storage::Tape::Tape::Pulse::High: - case Storage::Tape::Tape::Pulse::Low: - float pulse_length = pulse.length.get_float(); - if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) { push_wave(WaveType::Short); return; } - if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) { push_wave(WaveType::Long); return; } - break; - } - - push_wave(WaveType::Unrecognised); - } - - void inspect_waves(const std::vector &waves) - { - if(waves.size() < 2) return; - - if(waves[0] == WaveType::Long && waves[1] == WaveType::Long) - { - push_symbol(SymbolType::Zero, 2); - return; - } - - if(waves.size() < 4) return; - - if( waves[0] == WaveType::Short && - waves[1] == WaveType::Short && - waves[2] == WaveType::Short && - waves[3] == WaveType::Short) - { - push_symbol(SymbolType::One, 4); - return; - } - - remove_waves(1); - } - - NumberTheory::CRC16 _crc; -}; - -static std::unique_ptr GetNextChunk(const std::shared_ptr &tape, Acorn1200BaudTapeParser &parser) +static std::unique_ptr GetNextChunk(const std::shared_ptr &tape, Storage::Tape::Acorn::Parser &parser) { std::unique_ptr new_chunk(new File::Chunk); int shift_register = 0; @@ -233,7 +130,7 @@ std::unique_ptr GetNextFile(std::deque &chunks) std::list StaticAnalyser::Acorn::GetFiles(const std::shared_ptr &tape) { - Acorn1200BaudTapeParser parser; + Storage::Tape::Acorn::Parser parser; // populate chunk list std::deque chunk_list; diff --git a/StaticAnalyser/Commodore/Tape.cpp b/StaticAnalyser/Commodore/Tape.cpp index 94c65e936..ea2939d6c 100644 --- a/StaticAnalyser/Commodore/Tape.cpp +++ b/StaticAnalyser/Commodore/Tape.cpp @@ -8,375 +8,16 @@ #include "Tape.hpp" -#include "../TapeParser.hpp" -#include "Utilities.hpp" +#include "../../Storage/Tape/Parsers/Commodore.hpp" using namespace StaticAnalyser::Commodore; -enum class WaveType { - Short, Medium, Long, Unrecognised -}; - -enum class SymbolType { - One, Zero, Word, EndOfBlock, LeadIn -}; - -struct Header { - enum { - RelocatableProgram, - NonRelocatableProgram, - DataSequenceHeader, - DataBlock, - EndOfTape, - Unknown - } type; - - std::vector data; - std::wstring name; - std::vector raw_name; - uint16_t starting_address; - uint16_t ending_address; - bool parity_was_valid; - bool duplicate_matched; -}; - -struct Data { - std::vector data; - bool parity_was_valid; - bool duplicate_matched; -}; - -class CommodoreROMTapeParser: public StaticAnalyer::TapeParser { - public: - CommodoreROMTapeParser(const std::shared_ptr &tape) : - TapeParser(), - _wave_period(0.0f), - _previous_was_high(false), - _parity_byte(0) {} - - /*! - Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. - Returns @c nullptr if any wave-encoding level errors are encountered. - */ - std::unique_ptr
get_next_header(const std::shared_ptr &tape) - { - return duplicate_match
( - get_next_header_body(tape, true), - get_next_header_body(tape, false) - ); - } - - /*! - Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it. - Returns @c nullptr if any wave-encoding level errors are encountered. - */ - std::unique_ptr get_next_data(const std::shared_ptr &tape) - { - return duplicate_match( - get_next_data_body(tape, true), - get_next_data_body(tape, false) - ); - } - -// void spin() -// { -// while(!is_at_end()) -// { -// SymbolType symbol = get_next_symbol(); -// switch(symbol) -// { -// case SymbolType::One: printf("1"); break; -// case SymbolType::Zero: printf("0"); break; -// case SymbolType::Word: printf(" "); break; -// case SymbolType::EndOfBlock: printf("\n"); break; -// case SymbolType::LeadIn: printf("-"); break; -// } -// } -// } - - private: - /*! - Template for the logic in selecting which of two copies of something to consider authoritative, - including setting the duplicate_matched flag. - */ - template - std::unique_ptr duplicate_match(std::unique_ptr first_copy, std::unique_ptr second_copy) - { - // if only one copy was parsed successfully, return it - if(!first_copy) return second_copy; - if(!second_copy) return first_copy; - - // if no copies were second_copy, return nullptr - if(!first_copy && !second_copy) return nullptr; - - // otherwise plan to return either one with a correct check digit, doing a comparison with the other - std::unique_ptr *copy_to_return = &first_copy; - if(!first_copy->parity_was_valid && second_copy->parity_was_valid) copy_to_return = &second_copy; - - (*copy_to_return)->duplicate_matched = true; - if(first_copy->data.size() != second_copy->data.size()) - (*copy_to_return)->duplicate_matched = false; - else - (*copy_to_return)->duplicate_matched = !(memcmp(&first_copy->data[0], &second_copy->data[0], first_copy->data.size())); - - return std::move(*copy_to_return); - } - - std::unique_ptr
get_next_header_body(const std::shared_ptr &tape, bool is_original) - { - std::unique_ptr
header(new Header); - reset_error_flag(); - - // find and proceed beyond lead-in tone - proceed_to_symbol(tape, SymbolType::LeadIn); - - // look for landing zone - proceed_to_landing_zone(tape, is_original); - reset_parity_byte(); - - // get header type - uint8_t header_type = get_next_byte(tape); - switch(header_type) - { - default: header->type = Header::Unknown; break; - case 0x01: header->type = Header::RelocatableProgram; break; - case 0x02: header->type = Header::DataBlock; break; - case 0x03: header->type = Header::NonRelocatableProgram; break; - case 0x04: header->type = Header::DataSequenceHeader; break; - case 0x05: header->type = Header::EndOfTape; break; - } - - // grab rest of data - header->data.reserve(191); - for(size_t c = 0; c < 191; c++) - { - header->data.push_back(get_next_byte(tape)); - } - - uint8_t parity_byte = get_parity_byte(); - header->parity_was_valid = get_next_byte(tape) == parity_byte; - - // parse if this is not pure data - if(header->type != Header::DataBlock) - { - header->starting_address = (uint16_t)(header->data[0] | (header->data[1] << 8)); - header->ending_address = (uint16_t)(header->data[2] | (header->data[3] << 8)); - - for(size_t c = 0; c < 16; c++) - { - header->raw_name.push_back(header->data[4 + c]); - } - header->name = petscii_from_bytes(&header->raw_name[0], 16, false); - } - - if(get_error_flag()) return nullptr; - return header; - } - - - std::unique_ptr get_next_data_body(const std::shared_ptr &tape, bool is_original) - { - std::unique_ptr data(new Data); - reset_error_flag(); - - // find and proceed beyond lead-in tone to the next landing zone - proceed_to_symbol(tape, SymbolType::LeadIn); - proceed_to_landing_zone(tape, is_original); - reset_parity_byte(); - - // accumulate until the next non-word marker is hit - while(!tape->is_at_end()) - { - SymbolType start_symbol = get_next_symbol(tape); - if(start_symbol != SymbolType::Word) break; - data->data.push_back(get_next_byte_contents(tape)); - } - - // the above has reead the parity byte to the end of the data; if it matched the calculated parity it'll now be zero - data->parity_was_valid = !get_parity_byte(); - data->duplicate_matched = false; - - // remove the captured parity - data->data.erase(data->data.end()-1); - if(get_error_flag()) return nullptr; - return data; - } - - /*! - Finds and completes the next landing zone. - */ - void proceed_to_landing_zone(const std::shared_ptr &tape, bool is_original) - { - uint8_t landing_zone[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - while(!tape->is_at_end()) - { - memmove(landing_zone, &landing_zone[1], sizeof(uint8_t) * 8); - landing_zone[8] = get_next_byte(tape); - - bool is_landing_zone = true; - for(int c = 0; c < 9; c++) - { - if(landing_zone[c] != ((is_original ? 0x80 : 0x00) | 0x9) - c) - { - is_landing_zone = false; - break; - } - } - if(is_landing_zone) break; - } - } - - /*! - Swallows symbols until it reaches the first instance of the required symbol, swallows that - and returns. - */ - void proceed_to_symbol(const std::shared_ptr &tape, SymbolType required_symbol) - { - while(!tape->is_at_end()) - { - SymbolType symbol = get_next_symbol(tape); - if(symbol == required_symbol) return; - } - } - - /*! - Swallows the next byte; sets the error flag if it is not equal to @c value. - */ - void expect_byte(const std::shared_ptr &tape, uint8_t value) - { - uint8_t next_byte = get_next_byte(tape); - if(next_byte != value) set_error_flag(); - } - - uint8_t _parity_byte; - void reset_parity_byte() { _parity_byte = 0; } - uint8_t get_parity_byte() { return _parity_byte; } - void add_parity_byte(uint8_t byte) { _parity_byte ^= byte; } - - /*! - Proceeds to the next word marker then returns the result of @c get_next_byte_contents. - */ - uint8_t get_next_byte(const std::shared_ptr &tape) - { - proceed_to_symbol(tape, SymbolType::Word); - return get_next_byte_contents(tape); - } - - /*! - Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One. - Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not - ::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight. - */ - uint8_t get_next_byte_contents(const std::shared_ptr &tape) - { - int byte_plus_parity = 0; - int c = 9; - while(c--) - { - SymbolType next_symbol = get_next_symbol(tape); - if((next_symbol != SymbolType::One) && (next_symbol != SymbolType::Zero)) set_error_flag(); - byte_plus_parity = (byte_plus_parity >> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8); - } - - int check = byte_plus_parity; - check ^= (check >> 4); - check ^= (check >> 2); - check ^= (check >> 1); - if((check&1) == (byte_plus_parity >> 8)) - set_error_flag(); - - add_parity_byte((uint8_t)byte_plus_parity); - return (uint8_t)byte_plus_parity; - } - - /*! - Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. - */ - uint16_t get_next_short(const std::shared_ptr &tape) - { - uint16_t value = get_next_byte(tape); - value |= get_next_byte(tape) << 8; - return value; - } - - /*! - Per the contract with StaticAnalyser::TapeParser; sums time across pulses. If this pulse - indicates a high to low transition, inspects the time since the last transition, to produce - a long, medium, short or unrecognised wave period. - */ - void process_pulse(Storage::Tape::Tape::Pulse pulse) - { - // The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of: - // short: 182µs => 0.000364s cycle - // medium: 262µs => 0.000524s cycle - // long: 342µs => 0.000684s cycle - bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; - if(!is_high && _previous_was_high) - { - if(_wave_period >= 0.000764) push_wave(WaveType::Unrecognised); - else if(_wave_period >= 0.000604) push_wave(WaveType::Long); - else if(_wave_period >= 0.000444) push_wave(WaveType::Medium); - else if(_wave_period >= 0.000284) push_wave(WaveType::Short); - else push_wave(WaveType::Unrecognised); - - _wave_period = 0.0f; - } - - _wave_period += pulse.length.get_float(); - _previous_was_high = is_high; - } - bool _previous_was_high; - float _wave_period; - - /*! - Per the contract with StaticAnalyser::TapeParser; produces any of a word marker, an end-of-block marker, - a zero, a one or a lead-in symbol based on the currently captured waves. - */ - void inspect_waves(const std::vector &waves) - { - if(waves.size() < 2) return; - - if(waves[0] == WaveType::Long && waves[1] == WaveType::Medium) - { - push_symbol(SymbolType::Word, 2); - return; - } - - if(waves[0] == WaveType::Long && waves[1] == WaveType::Short) - { - push_symbol(SymbolType::EndOfBlock, 2); - return; - } - - if(waves[0] == WaveType::Short && waves[1] == WaveType::Medium) - { - push_symbol(SymbolType::Zero, 2); - return; - } - - if(waves[0] == WaveType::Medium && waves[1] == WaveType::Short) - { - push_symbol(SymbolType::One, 2); - return; - } - - if(waves[0] == WaveType::Short) - { - push_symbol(SymbolType::LeadIn, 1); - return; - } - - // Otherwise, eject at least one wave as all options are exhausted. - remove_waves(1); - } -}; - std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptr &tape) { - CommodoreROMTapeParser parser(tape); + Storage::Tape::Commodore::Parser parser; std::list file_list; - std::unique_ptr
header = parser.get_next_header(tape); + std::unique_ptr header = parser.get_next_header(tape); while(!tape->is_at_end()) { @@ -388,7 +29,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptrtype) { - case Header::DataSequenceHeader: + case Storage::Tape::Commodore::Header::DataSequenceHeader: { File new_file; new_file.name = header->name; @@ -402,7 +43,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptrtype != Header::DataBlock) break; + if(header->type != Storage::Tape::Commodore::Header::DataBlock) break; std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data)); } @@ -410,10 +51,10 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptr data = parser.get_next_data(tape); + std::unique_ptr data = parser.get_next_data(tape); if(data) { File new_file; @@ -422,7 +63,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptrstarting_address; new_file.ending_address = header->ending_address; new_file.data.swap(data->data); - new_file.type = (header->type == Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram; + new_file.type = (header->type == Storage::Tape::Commodore::Header::RelocatableProgram) ? File::RelocatableProgram : File::NonRelocatableProgram; file_list.push_back(new_file); } diff --git a/Storage/Tape/Parsers/Acorn.cpp b/Storage/Tape/Parsers/Acorn.cpp new file mode 100644 index 000000000..fb10c5708 --- /dev/null +++ b/Storage/Tape/Parsers/Acorn.cpp @@ -0,0 +1,100 @@ +// +// Acorn.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Acorn.hpp" + +using namespace Storage::Tape::Acorn; + +Parser::Parser() : + ::Storage::Tape::Parser(), + _crc(0x1021, 0x0000) {} + +int Parser::get_next_bit(const std::shared_ptr &tape) +{ + SymbolType symbol = get_next_symbol(tape); + return (symbol == SymbolType::One) ? 1 : 0; +} + +int Parser::get_next_byte(const std::shared_ptr &tape) +{ + int value = 0; + int c = 8; + if(get_next_bit(tape)) + { + set_error_flag(); + return -1; + } + while(c--) + { + value = (value >> 1) | (get_next_bit(tape) << 7); + } + if(!get_next_bit(tape)) + { + set_error_flag(); + return -1; + } + _crc.add((uint8_t)value); + return value; +} + +int Parser::get_next_short(const std::shared_ptr &tape) +{ + int result = get_next_byte(tape); + result |= get_next_byte(tape) << 8; + return result; +} + +int Parser::get_next_word(const std::shared_ptr &tape) +{ + int result = get_next_short(tape); + result |= get_next_short(tape) << 8; + return result; +} + +void Parser::reset_crc() { _crc.reset(); } +uint16_t Parser::get_crc() { return _crc.get_value(); } + +void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) +{ + switch(pulse.type) + { + default: break; + case Storage::Tape::Tape::Pulse::High: + case Storage::Tape::Tape::Pulse::Low: + float pulse_length = pulse.length.get_float(); + if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) { push_wave(WaveType::Short); return; } + if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) { push_wave(WaveType::Long); return; } + break; + } + + push_wave(WaveType::Unrecognised); +} + +void Parser::inspect_waves(const std::vector &waves) +{ + if(waves.size() < 2) return; + + if(waves[0] == WaveType::Long && waves[1] == WaveType::Long) + { + push_symbol(SymbolType::Zero, 2); + return; + } + + if(waves.size() < 4) return; + + if( waves[0] == WaveType::Short && + waves[1] == WaveType::Short && + waves[2] == WaveType::Short && + waves[3] == WaveType::Short) + { + push_symbol(SymbolType::One, 4); + return; + } + + remove_waves(1); +} diff --git a/Storage/Tape/Parsers/Acorn.hpp b/Storage/Tape/Parsers/Acorn.hpp new file mode 100644 index 000000000..c59162076 --- /dev/null +++ b/Storage/Tape/Parsers/Acorn.hpp @@ -0,0 +1,48 @@ +// +// Acorn.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Tape_Parsers_Acorn_hpp +#define Storage_Tape_Parsers_Acorn_hpp + +#include "TapeParser.hpp" +#include "../../../NumberTheory/CRC.hpp" + +namespace Storage { +namespace Tape { +namespace Acorn { + +enum class WaveType { + Short, Long, Unrecognised +}; + +enum class SymbolType { + One, Zero +}; + +class Parser: public Storage::Tape::Parser { + public: + Parser(); + + int get_next_bit(const std::shared_ptr &tape); + int get_next_byte(const std::shared_ptr &tape); + int get_next_short(const std::shared_ptr &tape); + int get_next_word(const std::shared_ptr &tape); + void reset_crc(); + uint16_t get_crc(); + + private: + void process_pulse(Storage::Tape::Tape::Pulse pulse); + void inspect_waves(const std::vector &waves); + NumberTheory::CRC16 _crc; +}; + +} +} +} + +#endif /* Acorn_hpp */ diff --git a/Storage/Tape/Parsers/Commodore.cpp b/Storage/Tape/Parsers/Commodore.cpp new file mode 100644 index 000000000..493ef5b9f --- /dev/null +++ b/Storage/Tape/Parsers/Commodore.cpp @@ -0,0 +1,312 @@ +// +// Commodore.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Commodore.hpp" + +using namespace Storage::Tape::Commodore; + +Parser::Parser() : + Storage::Tape::Parser(), + _wave_period(0.0f), + _previous_was_high(false), + _parity_byte(0) {} + +/*! + Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. +*/ +std::unique_ptr
Parser::get_next_header(const std::shared_ptr &tape) +{ + return duplicate_match
( + get_next_header_body(tape, true), + get_next_header_body(tape, false) + ); +} + +/*! + Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. +*/ +std::unique_ptr Parser::get_next_data(const std::shared_ptr &tape) +{ + return duplicate_match( + get_next_data_body(tape, true), + get_next_data_body(tape, false) + ); +} + +/*! + Template for the logic in selecting which of two copies of something to consider authoritative, + including setting the duplicate_matched flag. +*/ +template + std::unique_ptr Parser::duplicate_match(std::unique_ptr first_copy, std::unique_ptr second_copy) +{ + // if only one copy was parsed successfully, return it + if(!first_copy) return second_copy; + if(!second_copy) return first_copy; + + // if no copies were second_copy, return nullptr + if(!first_copy && !second_copy) return nullptr; + + // otherwise plan to return either one with a correct check digit, doing a comparison with the other + std::unique_ptr *copy_to_return = &first_copy; + if(!first_copy->parity_was_valid && second_copy->parity_was_valid) copy_to_return = &second_copy; + + (*copy_to_return)->duplicate_matched = true; + if(first_copy->data.size() != second_copy->data.size()) + (*copy_to_return)->duplicate_matched = false; + else + (*copy_to_return)->duplicate_matched = !(memcmp(&first_copy->data[0], &second_copy->data[0], first_copy->data.size())); + + return std::move(*copy_to_return); +} + +std::unique_ptr
Parser::get_next_header_body(const std::shared_ptr &tape, bool is_original) +{ + std::unique_ptr
header(new Header); + reset_error_flag(); + + // find and proceed beyond lead-in tone + proceed_to_symbol(tape, SymbolType::LeadIn); + + // look for landing zone + proceed_to_landing_zone(tape, is_original); + reset_parity_byte(); + + // get header type + uint8_t header_type = get_next_byte(tape); + switch(header_type) + { + default: header->type = Header::Unknown; break; + case 0x01: header->type = Header::RelocatableProgram; break; + case 0x02: header->type = Header::DataBlock; break; + case 0x03: header->type = Header::NonRelocatableProgram; break; + case 0x04: header->type = Header::DataSequenceHeader; break; + case 0x05: header->type = Header::EndOfTape; break; + } + + // grab rest of data + header->data.reserve(191); + for(size_t c = 0; c < 191; c++) + { + header->data.push_back(get_next_byte(tape)); + } + + uint8_t parity_byte = get_parity_byte(); + header->parity_was_valid = get_next_byte(tape) == parity_byte; + + // parse if this is not pure data + if(header->type != Header::DataBlock) + { + header->starting_address = (uint16_t)(header->data[0] | (header->data[1] << 8)); + header->ending_address = (uint16_t)(header->data[2] | (header->data[3] << 8)); + + for(size_t c = 0; c < 16; c++) + { + header->raw_name.push_back(header->data[4 + c]); + } + header->name = petscii_from_bytes(&header->raw_name[0], 16, false); + } + + if(get_error_flag()) return nullptr; + return header; +} + +std::unique_ptr Parser::get_next_data_body(const std::shared_ptr &tape, bool is_original) +{ + std::unique_ptr data(new Data); + reset_error_flag(); + + // find and proceed beyond lead-in tone to the next landing zone + proceed_to_symbol(tape, SymbolType::LeadIn); + proceed_to_landing_zone(tape, is_original); + reset_parity_byte(); + + // accumulate until the next non-word marker is hit + while(!tape->is_at_end()) + { + SymbolType start_symbol = get_next_symbol(tape); + if(start_symbol != SymbolType::Word) break; + data->data.push_back(get_next_byte_contents(tape)); + } + + // the above has reead the parity byte to the end of the data; if it matched the calculated parity it'll now be zero + data->parity_was_valid = !get_parity_byte(); + data->duplicate_matched = false; + + // remove the captured parity + data->data.erase(data->data.end()-1); + if(get_error_flag()) return nullptr; + return data; +} + +/*! + Finds and completes the next landing zone. +*/ +void Parser::proceed_to_landing_zone(const std::shared_ptr &tape, bool is_original) +{ + uint8_t landing_zone[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + while(!tape->is_at_end()) + { + memmove(landing_zone, &landing_zone[1], sizeof(uint8_t) * 8); + landing_zone[8] = get_next_byte(tape); + + bool is_landing_zone = true; + for(int c = 0; c < 9; c++) + { + if(landing_zone[c] != ((is_original ? 0x80 : 0x00) | 0x9) - c) + { + is_landing_zone = false; + break; + } + } + if(is_landing_zone) break; + } +} + +/*! + Swallows symbols until it reaches the first instance of the required symbol, swallows that + and returns. +*/ +void Parser::proceed_to_symbol(const std::shared_ptr &tape, SymbolType required_symbol) +{ + while(!tape->is_at_end()) + { + SymbolType symbol = get_next_symbol(tape); + if(symbol == required_symbol) return; + } +} + +/*! + Swallows the next byte; sets the error flag if it is not equal to @c value. +*/ +void Parser::expect_byte(const std::shared_ptr &tape, uint8_t value) +{ + uint8_t next_byte = get_next_byte(tape); + if(next_byte != value) set_error_flag(); +} + +void Parser::reset_parity_byte() { _parity_byte = 0; } +uint8_t Parser::get_parity_byte() { return _parity_byte; } +void Parser::add_parity_byte(uint8_t byte) { _parity_byte ^= byte; } + +/*! + Proceeds to the next word marker then returns the result of @c get_next_byte_contents. +*/ +uint8_t Parser::get_next_byte(const std::shared_ptr &tape) +{ + proceed_to_symbol(tape, SymbolType::Word); + return get_next_byte_contents(tape); +} + +/*! + Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One. + Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not + ::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight. +*/ +uint8_t Parser::get_next_byte_contents(const std::shared_ptr &tape) +{ + int byte_plus_parity = 0; + int c = 9; + while(c--) + { + SymbolType next_symbol = get_next_symbol(tape); + if((next_symbol != SymbolType::One) && (next_symbol != SymbolType::Zero)) set_error_flag(); + byte_plus_parity = (byte_plus_parity >> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8); + } + + int check = byte_plus_parity; + check ^= (check >> 4); + check ^= (check >> 2); + check ^= (check >> 1); + if((check&1) == (byte_plus_parity >> 8)) + set_error_flag(); + + add_parity_byte((uint8_t)byte_plus_parity); + return (uint8_t)byte_plus_parity; +} + +/*! + Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. +*/ +uint16_t Parser::get_next_short(const std::shared_ptr &tape) +{ + uint16_t value = get_next_byte(tape); + value |= get_next_byte(tape) << 8; + return value; +} + +/*! + Per the contract with StaticAnalyser::TapeParser; sums time across pulses. If this pulse + indicates a high to low transition, inspects the time since the last transition, to produce + a long, medium, short or unrecognised wave period. +*/ +void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) +{ + // The Complete Commodore Inner Space Anthology, P 97, gives half-cycle lengths of: + // short: 182µs => 0.000364s cycle + // medium: 262µs => 0.000524s cycle + // long: 342µs => 0.000684s cycle + bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; + if(!is_high && _previous_was_high) + { + if(_wave_period >= 0.000764) push_wave(WaveType::Unrecognised); + else if(_wave_period >= 0.000604) push_wave(WaveType::Long); + else if(_wave_period >= 0.000444) push_wave(WaveType::Medium); + else if(_wave_period >= 0.000284) push_wave(WaveType::Short); + else push_wave(WaveType::Unrecognised); + + _wave_period = 0.0f; + } + + _wave_period += pulse.length.get_float(); + _previous_was_high = is_high; +} + +/*! + Per the contract with StaticAnalyser::TapeParser; produces any of a word marker, an end-of-block marker, + a zero, a one or a lead-in symbol based on the currently captured waves. +*/ +void Parser::inspect_waves(const std::vector &waves) +{ + if(waves.size() < 2) return; + + if(waves[0] == WaveType::Long && waves[1] == WaveType::Medium) + { + push_symbol(SymbolType::Word, 2); + return; + } + + if(waves[0] == WaveType::Long && waves[1] == WaveType::Short) + { + push_symbol(SymbolType::EndOfBlock, 2); + return; + } + + if(waves[0] == WaveType::Short && waves[1] == WaveType::Medium) + { + push_symbol(SymbolType::Zero, 2); + return; + } + + if(waves[0] == WaveType::Medium && waves[1] == WaveType::Short) + { + push_symbol(SymbolType::One, 2); + return; + } + + if(waves[0] == WaveType::Short) + { + push_symbol(SymbolType::LeadIn, 1); + return; + } + + // Otherwise, eject at least one wave as all options are exhausted. + remove_waves(1); +} diff --git a/Storage/Tape/Parsers/Commodore.hpp b/Storage/Tape/Parsers/Commodore.hpp new file mode 100644 index 000000000..5714b2dbc --- /dev/null +++ b/Storage/Tape/Parsers/Commodore.hpp @@ -0,0 +1,139 @@ +// +// Commodore.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Tape_Parsers_Commodore_hpp +#define Storage_Tape_Parsers_Commodore_hpp + +#include "TapeParser.hpp" +//#include "Utilities.hpp" +#include +#include + +namespace Storage { +namespace Tape { +namespace Commodore { + +enum class WaveType { + Short, Medium, Long, Unrecognised +}; + +enum class SymbolType { + One, Zero, Word, EndOfBlock, LeadIn +}; + +struct Header { + enum { + RelocatableProgram, + NonRelocatableProgram, + DataSequenceHeader, + DataBlock, + EndOfTape, + Unknown + } type; + + std::vector data; + std::wstring name; + std::vector raw_name; + uint16_t starting_address; + uint16_t ending_address; + bool parity_was_valid; + bool duplicate_matched; +}; + +struct Data { + std::vector data; + bool parity_was_valid; + bool duplicate_matched; +}; + +class Parser: public Storage::Tape::Parser { + public: + Parser(); + + /*! + Advances to the next block on the tape, treating it as a header, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. + */ + std::unique_ptr
get_next_header(const std::shared_ptr &tape); + + /*! + Advances to the next block on the tape, treating it as data, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. + */ + std::unique_ptr get_next_data(const std::shared_ptr &tape); + + private: + /*! + Template for the logic in selecting which of two copies of something to consider authoritative, + including setting the duplicate_matched flag. + */ + template + std::unique_ptr duplicate_match(std::unique_ptr first_copy, std::unique_ptr second_copy); + + std::unique_ptr
get_next_header_body(const std::shared_ptr &tape, bool is_original); + std::unique_ptr get_next_data_body(const std::shared_ptr &tape, bool is_original); + + /*! + Finds and completes the next landing zone. + */ + void proceed_to_landing_zone(const std::shared_ptr &tape, bool is_original); + + /*! + Swallows symbols until it reaches the first instance of the required symbol, swallows that + and returns. + */ + void proceed_to_symbol(const std::shared_ptr &tape, SymbolType required_symbol); + + /*! + Swallows the next byte; sets the error flag if it is not equal to @c value. + */ + void expect_byte(const std::shared_ptr &tape, uint8_t value); + + uint8_t _parity_byte; + void reset_parity_byte(); + uint8_t get_parity_byte(); + void add_parity_byte(uint8_t byte); + + /*! + Proceeds to the next word marker then returns the result of @c get_next_byte_contents. + */ + uint8_t get_next_byte(const std::shared_ptr &tape); + + /*! + Reads the next nine symbols and applies a binary test to each to differentiate between ::One and not-::One. + Returns a byte composed of the first eight of those as bits; sets the error flag if any symbol is not + ::One and not ::Zero, or if the ninth bit is not equal to the odd parity of the other eight. + */ + uint8_t get_next_byte_contents(const std::shared_ptr &tape); + + /*! + Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. + */ + uint16_t get_next_short(const std::shared_ptr &tape); + + /*! + Per the contract with StaticAnalyser::TapeParser; sums time across pulses. If this pulse + indicates a high to low transition, inspects the time since the last transition, to produce + a long, medium, short or unrecognised wave period. + */ + void process_pulse(Storage::Tape::Tape::Pulse pulse); + bool _previous_was_high; + float _wave_period; + + /*! + Per the contract with StaticAnalyser::TapeParser; produces any of a word marker, an end-of-block marker, + a zero, a one or a lead-in symbol based on the currently captured waves. + */ + void inspect_waves(const std::vector &waves); +}; + +} +} +} + +#endif /* Commodore_hpp */ diff --git a/StaticAnalyser/TapeParser.hpp b/Storage/Tape/Parsers/TapeParser.hpp similarity index 93% rename from StaticAnalyser/TapeParser.hpp rename to Storage/Tape/Parsers/TapeParser.hpp index 332c0d089..d58323e9b 100644 --- a/StaticAnalyser/TapeParser.hpp +++ b/Storage/Tape/Parsers/TapeParser.hpp @@ -9,7 +9,13 @@ #ifndef TapeParser_hpp #define TapeParser_hpp -namespace StaticAnalyer { +#include "../Tape.hpp" + +#include +#include + +namespace Storage { +namespace Tape { /*! A partly-abstract base class to help in the authorship of tape format parsers; @@ -18,10 +24,10 @@ namespace StaticAnalyer { Very optional, not intended to box in the approaches taken for analysis. */ -template class TapeParser { +template class Parser { public: /// Instantiates a new parser with the supplied @c tape. - TapeParser() : _has_next_symbol(false), _error_flag(false) {} + Parser() : _has_next_symbol(false), _error_flag(false) {} /// Resets the error flag. void reset_error_flag() { _error_flag = false; } @@ -106,6 +112,7 @@ template class TapeParser { bool _has_next_symbol; }; +} } #endif /* TapeParser_hpp */