diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 64f53df89..a3a5b7b3c 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -17,7 +17,6 @@ Machine::Machine() : _typer_delay(2500000) { set_clock_rate(1000000); - _via.tape.reset(new TapePlayer); _via.set_interrupt_delegate(this); _keyboard.reset(new Keyboard); _via.keyboard = _keyboard; @@ -171,7 +170,10 @@ void Machine::run_for_cycles(int number_of_cycles) #pragma mark - The 6522 -Machine::VIA::VIA() : MOS::MOS6522(), _cycles_since_ay_update(0) {} +Machine::VIA::VIA() : + MOS::MOS6522(), + _cycles_since_ay_update(0), + tape(new TapePlayer) {} void Machine::VIA::set_control_line_output(Port port, Line line, bool value) { @@ -231,79 +233,10 @@ void Machine::VIA::update_ay() #pragma mark - TapePlayer Machine::TapePlayer::TapePlayer() : - Storage::Tape::BinaryTapePlayer(1000000), - _is_catching_bytes(false) + Storage::Tape::BinaryTapePlayer(1000000) {} uint8_t Machine::TapePlayer::get_next_byte(bool fast) { - _is_in_fast_mode = fast; - _is_catching_bytes = true; - - _was_high = get_input(); - _queued_lengths_pointer = 0; - _data_register = 0; - _cycle_length = 0.0f; - - _bit_count = 0; - while(_bit_count < 10) - { - process_next_event(); - } - - _is_catching_bytes = false; - return (uint8_t)(_data_register >> 1); -} - -void Machine::TapePlayer::process_input_pulse(Storage::Tape::Tape::Pulse pulse) -{ - Storage::Tape::BinaryTapePlayer::process_input_pulse(pulse); - - if(_is_catching_bytes) - { - _cycle_length += pulse.length.get_float(); - bool is_high = get_input(); - if(is_high != _was_high) - { - // queue up the new length - _queued_lengths[_queued_lengths_pointer] = _cycle_length; - _cycle_length = 0.0f; - _queued_lengths_pointer++; - - // search for bits - if(_is_in_fast_mode) - { - if(_queued_lengths_pointer >= 2) - { - float first_two = _queued_lengths[0] + _queued_lengths[1]; - if(first_two < 0.000512*2.0 && _queued_lengths[0] >= _queued_lengths[1] - 0.000256) - { - int new_bit = (first_two < 0.000512) ? 1 : 0; - if(_bit_count || !new_bit) - { - _data_register |= (new_bit << _bit_count); - _bit_count++; - } - memmove(_queued_lengths, &_queued_lengths[2], sizeof(float)*14); - _queued_lengths_pointer -= 2; - } - else - { - memmove(_queued_lengths, &_queued_lengths[1], sizeof(float)*15); - _queued_lengths_pointer--; - } - } - } - else - { - // TODO - } - } - _was_high = is_high; - } -} - -void Machine::TapePlayer::run_for_cycles(int number_of_cycles) -{ - if(!_is_catching_bytes) Storage::Tape::BinaryTapePlayer::run_for_cycles(number_of_cycles); + return (uint8_t)_parser.get_next_byte(get_tape(), fast); } diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index d020dd3c6..7a2939992 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -16,6 +16,7 @@ #include "../../Processors/6502/CPU6502.hpp" #include "../../Components/6522/6522.hpp" #include "../../Components/AY38910/AY38910.hpp" +#include "../../Storage/Tape/Parsers/Oric.hpp" #include "Video.hpp" @@ -112,22 +113,9 @@ class Machine: public: TapePlayer(); uint8_t get_next_byte(bool fast); - void run_for_cycles(int number_of_cycles); private: - bool _is_catching_bytes; // `true` to enable tape byte parsing, `false` otherwise - bool _is_in_fast_mode; // `true` to indicate that tape byte parsing should use the Oric's fast encoding, `false` otherwise - - float _cycle_length; // a counter for the amount of time since the tape input changed - bool _was_high; // a latch to spot when the tape input changes - - float _queued_lengths[16]; // a history of previous half-wave lengths - int _queued_lengths_pointer; // a pointer into the history, showing the number of lengths waiting to be parsed - - int _data_register; // the accumulation of input bits - int _bit_count; // a counter of accumulated bits - - virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse); + Storage::Tape::Oric::Parser _parser; }; bool _use_fast_tape_hack; @@ -143,7 +131,7 @@ class Machine: inline void run_for_cycles(unsigned int number_of_cycles); std::shared_ptr ay8910; - std::shared_ptr tape; + std::unique_ptr tape; std::shared_ptr keyboard; void synchronise(); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index e0a7e563f..a0340de54 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -56,6 +56,11 @@ 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 */; }; + 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; }; + 4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F91DCFF807003085B1 /* Oric.cpp */; }; + 4B8805FE1DD02552003085B1 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805FC1DD02552003085B1 /* Tape.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 */; }; @@ -352,7 +357,6 @@ 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */; }; - 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */; }; 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; @@ -497,6 +501,16 @@ 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 = ""; }; + 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Data/Commodore.cpp; sourceTree = ""; }; + 4B8805F61DCFF6C9003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Data/Commodore.hpp; sourceTree = ""; }; + 4B8805F91DCFF807003085B1 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Parsers/Oric.cpp; sourceTree = ""; }; + 4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = ""; }; + 4B8805FC1DD02552003085B1 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Oric/Tape.cpp; sourceTree = ""; }; + 4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.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; }; @@ -826,8 +840,6 @@ 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = ""; }; 4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.cpp; sourceTree = ""; }; 4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.hpp; sourceTree = ""; }; - 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Utilities.cpp; path = ../../StaticAnalyser/Commodore/Utilities.cpp; sourceTree = ""; }; - 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Utilities.hpp; path = ../../StaticAnalyser/Commodore/Utilities.hpp; sourceTree = ""; }; 4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = ""; }; 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; @@ -854,7 +866,6 @@ 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = ""; }; 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; - 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = ""; }; 4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = ""; }; 4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = ""; }; 4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = ""; }; @@ -1158,6 +1169,7 @@ 4BAB62AE1D32730D00DF5BA0 /* Storage.hpp */, 4BB697CA1D4B6D3E00248BDF /* TimedEventLoop.hpp */, 4BEE0A691D72496600532C7B /* Cartridge */, + 4B8805F81DCFF6CD003085B1 /* Data */, 4BAB62AA1D3272D200DF5BA0 /* Disk */, 4B69FB3A1C4D908A00B5F0AA /* Tape */, ); @@ -1169,6 +1181,7 @@ isa = PBXGroup; children = ( 4B69FB411C4D941400B5F0AA /* Formats */, + 4B8805F11DCFC9A2003085B1 /* Parsers */, 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */, 4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */, ); @@ -1191,6 +1204,28 @@ path = Formats; sourceTree = ""; }; + 4B8805F11DCFC9A2003085B1 /* Parsers */ = { + isa = PBXGroup; + children = ( + 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */, + 4B8805EF1DCFC99C003085B1 /* Acorn.hpp */, + 4B8805F21DCFD22A003085B1 /* Commodore.cpp */, + 4B8805F31DCFD22A003085B1 /* Commodore.hpp */, + 4B8805F91DCFF807003085B1 /* Oric.cpp */, + 4B8805FA1DCFF807003085B1 /* Oric.hpp */, + ); + name = Parsers; + sourceTree = ""; + }; + 4B8805F81DCFF6CD003085B1 /* Data */ = { + isa = PBXGroup; + children = ( + 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */, + 4B8805F61DCFF6C9003085B1 /* Commodore.hpp */, + ); + name = Data; + sourceTree = ""; + }; 4BA799961D8B65730045123D /* Atari */ = { isa = PBXGroup; children = ( @@ -1678,8 +1713,6 @@ 4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */, 4BC830CF1D6E7C690000A26F /* Tape.cpp */, 4BC830D01D6E7C690000A26F /* Tape.hpp */, - 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */, - 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */, ); name = Commodore; sourceTree = ""; @@ -1740,6 +1773,8 @@ children = ( 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */, 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */, + 4B8805FC1DD02552003085B1 /* Tape.cpp */, + 4B8805FD1DD02552003085B1 /* Tape.hpp */, ); name = Oric; sourceTree = ""; @@ -1812,7 +1847,6 @@ children = ( 4BF1354A1D6D2C300054B2EA /* StaticAnalyser.cpp */, 4BF1354B1D6D2C300054B2EA /* StaticAnalyser.hpp */, - 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */, 4BD14B121D7462810088EAD6 /* Acorn */, 4BA799961D8B65730045123D /* Atari */, 4BC830D21D6E7C6D0000A26F /* Commodore */, @@ -2236,7 +2270,6 @@ 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, - 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */, 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, @@ -2255,6 +2288,7 @@ 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */, 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */, 4B0BE4281D3481E700D5256B /* DigitalPhaseLockedLoop.cpp in Sources */, + 4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */, 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, @@ -2294,8 +2328,11 @@ 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 */, + 4B8805FE1DD02552003085B1 /* Tape.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 */, @@ -2304,6 +2341,7 @@ 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, 4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */, 4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */, + 4B8805FB1DCFF807003085B1 /* Oric.cpp in Sources */, 4BEE0A701D72496600532C7B /* PRG.cpp in Sources */, 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, diff --git a/StaticAnalyser/Acorn/Tape.cpp b/StaticAnalyser/Acorn/Tape.cpp index 056b05ccb..49d961c85 100644 --- a/StaticAnalyser/Acorn/Tape.cpp +++ b/StaticAnalyser/Acorn/Tape.cpp @@ -9,130 +9,27 @@ #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(const std::shared_ptr &tape) : - TapeParser(tape), - _crc(0x1021, 0x0000) {} - - int get_next_bit() - { - SymbolType symbol = get_next_symbol(); - return (symbol == SymbolType::One) ? 1 : 0; - } - - int get_next_byte() - { - int value = 0; - int c = 8; - if(get_next_bit()) - { - set_error_flag(); - return -1; - } - while(c--) - { - value = (value >> 1) | (get_next_bit() << 7); - } - if(!get_next_bit()) - { - set_error_flag(); - return -1; - } - _crc.add((uint8_t)value); - return value; - } - - int get_next_short() - { - int result = get_next_byte(); - result |= get_next_byte() << 8; - return result; - } - - int get_next_word() - { - int result = get_next_short(); - result |= get_next_short() << 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(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; // TODO: move this into the parser -#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit() << 9) +#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9) // find next area of high tone - while(!parser.is_at_end() && (shift_register != 0x3ff)) + while(!tape->is_at_end() && (shift_register != 0x3ff)) { shift(); } // find next 0x2a (swallowing stop bit) - while(!parser.is_at_end() && (shift_register != 0x254)) + while(!tape->is_at_end() && (shift_register != 0x254)) { shift(); } @@ -145,9 +42,9 @@ static std::unique_ptr GetNextChunk(Acorn1200BaudTapeParser &parser // read out name char name[11]; int name_ptr = 0; - while(!parser.is_at_end() && name_ptr < sizeof(name)) + while(!tape->is_at_end() && name_ptr < sizeof(name)) { - name[name_ptr] = (char)parser.get_next_byte(); + name[name_ptr] = (char)parser.get_next_byte(tape); if(!name[name_ptr]) break; name_ptr++; } @@ -155,15 +52,15 @@ static std::unique_ptr GetNextChunk(Acorn1200BaudTapeParser &parser new_chunk->name = name; // addresses - new_chunk->load_address = (uint32_t)parser.get_next_word(); - new_chunk->execution_address = (uint32_t)parser.get_next_word(); - new_chunk->block_number = (uint16_t)parser.get_next_short(); - new_chunk->block_length = (uint16_t)parser.get_next_short(); - new_chunk->block_flag = (uint8_t)parser.get_next_byte(); - new_chunk->next_address = (uint32_t)parser.get_next_word(); + new_chunk->load_address = (uint32_t)parser.get_next_word(tape); + new_chunk->execution_address = (uint32_t)parser.get_next_word(tape); + new_chunk->block_number = (uint16_t)parser.get_next_short(tape); + new_chunk->block_length = (uint16_t)parser.get_next_short(tape); + new_chunk->block_flag = (uint8_t)parser.get_next_byte(tape); + new_chunk->next_address = (uint32_t)parser.get_next_word(tape); uint16_t calculated_header_crc = parser.get_crc(); - uint16_t stored_header_crc = (uint16_t)parser.get_next_short(); + uint16_t stored_header_crc = (uint16_t)parser.get_next_short(tape); stored_header_crc = (uint16_t)((stored_header_crc >> 8) | (stored_header_crc << 8)); new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc; @@ -171,13 +68,13 @@ static std::unique_ptr GetNextChunk(Acorn1200BaudTapeParser &parser new_chunk->data.reserve(new_chunk->block_length); for(int c = 0; c < new_chunk->block_length; c++) { - new_chunk->data.push_back((uint8_t)parser.get_next_byte()); + new_chunk->data.push_back((uint8_t)parser.get_next_byte(tape)); } if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) { uint16_t calculated_data_crc = parser.get_crc(); - uint16_t stored_data_crc = (uint16_t)parser.get_next_short(); + uint16_t stored_data_crc = (uint16_t)parser.get_next_short(tape); stored_data_crc = (uint16_t)((stored_data_crc >> 8) | (stored_data_crc << 8)); new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc; } @@ -233,13 +130,13 @@ std::unique_ptr GetNextFile(std::deque &chunks) std::list StaticAnalyser::Acorn::GetFiles(const std::shared_ptr &tape) { - Acorn1200BaudTapeParser parser(tape); + Storage::Tape::Acorn::Parser parser; // populate chunk list std::deque chunk_list; - while(!parser.is_at_end()) + while(!tape->is_at_end()) { - std::unique_ptr chunk = GetNextChunk(parser); + std::unique_ptr chunk = GetNextChunk(tape, parser); if(chunk) { chunk_list.push_back(*chunk); diff --git a/StaticAnalyser/Commodore/Disk.cpp b/StaticAnalyser/Commodore/Disk.cpp index 5929fc65e..60078a3a0 100644 --- a/StaticAnalyser/Commodore/Disk.cpp +++ b/StaticAnalyser/Commodore/Disk.cpp @@ -9,7 +9,7 @@ #include "Disk.hpp" #include "../../Storage/Disk/DiskController.hpp" #include "../../Storage/Disk/Encodings/CommodoreGCR.hpp" -#include "Utilities.hpp" +#include "../../Storage/Data/Commodore.hpp" #include #include @@ -234,7 +234,7 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptr 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(tape), - _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() - { - return duplicate_match
( - get_next_header_body(true), - get_next_header_body(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() - { - return duplicate_match( - get_next_data_body(true), - get_next_data_body(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(bool is_original) - { - std::unique_ptr
header(new Header); - reset_error_flag(); - - // find and proceed beyond lead-in tone - proceed_to_symbol(SymbolType::LeadIn); - - // look for landing zone - proceed_to_landing_zone(is_original); - reset_parity_byte(); - - // get header type - uint8_t header_type = get_next_byte(); - 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()); - } - - uint8_t parity_byte = get_parity_byte(); - header->parity_was_valid = get_next_byte() == 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(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(SymbolType::LeadIn); - proceed_to_landing_zone(is_original); - reset_parity_byte(); - - // accumulate until the next non-word marker is hit - while(!is_at_end()) - { - SymbolType start_symbol = get_next_symbol(); - if(start_symbol != SymbolType::Word) break; - data->data.push_back(get_next_byte_contents()); - } - - // 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(bool is_original) - { - uint8_t landing_zone[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - while(!is_at_end()) - { - memmove(landing_zone, &landing_zone[1], sizeof(uint8_t) * 8); - landing_zone[8] = get_next_byte(); - - 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(SymbolType required_symbol) - { - while(!is_at_end()) - { - SymbolType symbol = get_next_symbol(); - if(symbol == required_symbol) return; - } - } - - /*! - Swallows the next byte; sets the error flag if it is not equal to @c value. - */ - void expect_byte(uint8_t value) - { - uint8_t next_byte = get_next_byte(); - 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() - { - proceed_to_symbol(SymbolType::Word); - return get_next_byte_contents(); - } - - /*! - 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() - { - int byte_plus_parity = 0; - int c = 9; - while(c--) - { - SymbolType next_symbol = get_next_symbol(); - 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() - { - uint16_t value = get_next_byte(); - value |= get_next_byte() << 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(); + std::unique_ptr header = parser.get_next_header(tape); - while(!parser.is_at_end()) + while(!tape->is_at_end()) { if(!header) { - header = parser.get_next_header(); + header = parser.get_next_header(tape); continue; } switch(header->type) { - case Header::DataSequenceHeader: + case Storage::Tape::Commodore::Header::DataSequenceHeader: { File new_file; new_file.name = header->name; @@ -398,11 +39,11 @@ std::list StaticAnalyser::Commodore::GetFiles(const std::shared_ptrdata); - while(!parser.is_at_end()) + while(!tape->is_at_end()) { - header = parser.get_next_header(); + header = parser.get_next_header(tape); if(!header) continue; - if(header->type != 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(); + std::unique_ptr data = parser.get_next_data(tape); if(data) { File new_file; @@ -422,17 +63,17 @@ 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); } - header = parser.get_next_header(); + header = parser.get_next_header(tape); } break; default: - header = parser.get_next_header(); + header = parser.get_next_header(tape); break; } } diff --git a/StaticAnalyser/Oric/StaticAnalyser.cpp b/StaticAnalyser/Oric/StaticAnalyser.cpp index 295e92149..a258ad65a 100644 --- a/StaticAnalyser/Oric/StaticAnalyser.cpp +++ b/StaticAnalyser/Oric/StaticAnalyser.cpp @@ -8,6 +8,8 @@ #include "StaticAnalyser.hpp" +#include "Tape.hpp" + using namespace StaticAnalyser::Oric; void StaticAnalyser::Oric::AddTargets( @@ -16,14 +18,20 @@ void StaticAnalyser::Oric::AddTargets( const std::list> &cartridges, std::list &destination) { - // TODO: any sort of sanity checking at all; at the minute just trust the file type - // approximation already performed. Target target; target.machine = Target::Oric; target.probability = 1.0; - target.disks = disks; - target.tapes = tapes; - target.cartridges = cartridges; - target.loadingCommand = "CLOAD\"\"\n"; - destination.push_back(target); + + for(auto tape : tapes) + { + std::list tape_files = GetFiles(tape); + if(tape_files.size()) + { + target.tapes.push_back(tape); + target.loadingCommand = "CLOAD\"\"\n"; + } + } + + if(target.tapes.size() || target.disks.size() || target.cartridges.size()) + destination.push_back(target); } diff --git a/StaticAnalyser/Oric/Tape.cpp b/StaticAnalyser/Oric/Tape.cpp new file mode 100644 index 000000000..155103e29 --- /dev/null +++ b/StaticAnalyser/Oric/Tape.cpp @@ -0,0 +1,95 @@ +// +// Tape.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Tape.hpp" +#include "../../Storage/Tape/Parsers/Oric.hpp" + +using namespace StaticAnalyser::Oric; + +std::list StaticAnalyser::Oric::GetFiles(const std::shared_ptr &tape) +{ + std::list files; + Storage::Tape::Oric::Parser parser; + + tape->reset(); + while(!tape->is_at_end()) + { + // sync to next lead-in, check that it's one of three 0x16s + bool is_fast = parser.sync_and_get_encoding_speed(tape); + int next_bytes[2]; + next_bytes[0] = parser.get_next_byte(tape, is_fast); + next_bytes[1] = parser.get_next_byte(tape, is_fast); + + if(next_bytes[0] != 0x16 || next_bytes[1] != 0x16) continue; + + // get the first byte that isn't a 0x16, check it was a 0x24 + int byte = 0x16; + while(!tape->is_at_end() && byte == 0x16) + { + byte = parser.get_next_byte(tape, is_fast); + } + if(byte != 0x24) continue; + + // skip two empty bytes + parser.get_next_byte(tape, is_fast); + parser.get_next_byte(tape, is_fast); + + // get data and launch types + File new_file; + switch(parser.get_next_byte(tape, is_fast)) + { + case 0x00: new_file.data_type = File::ProgramType::BASIC; break; + case 0x80: new_file.data_type = File::ProgramType::MachineCode; break; + default: new_file.data_type = File::ProgramType::None; break; + } + switch(parser.get_next_byte(tape, is_fast)) + { + case 0x80: new_file.launch_type = File::ProgramType::BASIC; break; + case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break; + default: new_file.launch_type = File::ProgramType::None; break; + } + + // read end and start addresses + new_file.ending_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8); + new_file.ending_address |= (uint16_t)parser.get_next_byte(tape, is_fast); + new_file.starting_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8); + new_file.starting_address |= (uint16_t)parser.get_next_byte(tape, is_fast); + + // skip an empty byte + parser.get_next_byte(tape, is_fast); + + // read file name, up to 16 characters and null terminated + char file_name[17]; + int name_pos = 0; + while(name_pos < 16) + { + file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast); + if(!file_name[name_pos]) break; + name_pos++; + } + file_name[16] = '\0'; + new_file.name = file_name; + + // read body + size_t body_length = new_file.ending_address - new_file.starting_address + 1; + new_file.data.reserve(body_length); + for(size_t c = 0; c < body_length; c++) + { + new_file.data.push_back((uint8_t)parser.get_next_byte(tape, is_fast)); + } + + // only one validation check: was there enough tape? + if(!tape->is_at_end()) + { + files.push_back(new_file); + } + } + tape->reset(); + + return files; +} diff --git a/StaticAnalyser/Oric/Tape.hpp b/StaticAnalyser/Oric/Tape.hpp new file mode 100644 index 000000000..b8d0f39db --- /dev/null +++ b/StaticAnalyser/Oric/Tape.hpp @@ -0,0 +1,38 @@ +// +// Tape.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Oric_Tape_hpp +#define StaticAnalyser_Oric_Tape_hpp + +#include "../../Storage/Tape/Tape.hpp" +#include +#include +#include + +namespace StaticAnalyser { +namespace Oric { + +struct File { + std::string name; + uint16_t starting_address; + uint16_t ending_address; + enum ProgramType { + BASIC, + MachineCode, + None + }; + ProgramType data_type, launch_type; + std::vector data; +}; + +std::list GetFiles(const std::shared_ptr &tape); + +} +} + +#endif /* Tape_hpp */ diff --git a/StaticAnalyser/Commodore/Utilities.cpp b/Storage/Data/Commodore.cpp similarity index 95% rename from StaticAnalyser/Commodore/Utilities.cpp rename to Storage/Data/Commodore.cpp index 2f3a7e093..1d87414fc 100644 --- a/StaticAnalyser/Commodore/Utilities.cpp +++ b/Storage/Data/Commodore.cpp @@ -1,14 +1,14 @@ // -// Utilities.cpp +// Commodore.cpp // Clock Signal // -// Created by Thomas Harte on 06/09/2016. +// Created by Thomas Harte on 06/11/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "Utilities.hpp" +#include "Commodore.hpp" -std::wstring StaticAnalyser::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted) +std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted) { std::wstring result; diff --git a/StaticAnalyser/Commodore/Utilities.hpp b/Storage/Data/Commodore.hpp similarity index 52% rename from StaticAnalyser/Commodore/Utilities.hpp rename to Storage/Data/Commodore.hpp index 1f4672424..6540a8188 100644 --- a/StaticAnalyser/Commodore/Utilities.hpp +++ b/Storage/Data/Commodore.hpp @@ -1,22 +1,24 @@ // -// Utilities.hpp +// Commodore.hpp // Clock Signal // -// Created by Thomas Harte on 06/09/2016. +// Created by Thomas Harte on 06/11/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // -#ifndef Analyser_Commodore_Utilities_hpp -#define Analyser_Commodore_Utilities_hpp +#ifndef Storage_Data_Commodore_hpp +#define Storage_Data_Commodore_hpp #include -namespace StaticAnalyser { +namespace Storage { +namespace Data { namespace Commodore { std::wstring petscii_from_bytes(const uint8_t *string, int length, bool shifted); +} } } -#endif /* Utilities_hpp */ +#endif /* Commodore_hpp */ diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp index 95bbb349f..93360f005 100644 --- a/Storage/Tape/Formats/OricTAP.cpp +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -93,11 +93,11 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() if(_phase_counter == 6) _data_start_address = (uint16_t)(next_byte << 8); if(_phase_counter == 7) _data_start_address |= next_byte; - _phase_counter++; if(_phase_counter >= 9 && !next_byte) // advance after the filename-ending NULL byte { _next_phase = Gap; } + _phase_counter++; break; case Gap: @@ -151,7 +151,7 @@ Tape::Pulse OricTAP::virtual_get_next_pulse() case Gap: _bit_count = 13; - pulse.type = Pulse::Zero; + pulse.type = (_phase_counter&1) ? Pulse::Low : Pulse::High; pulse.length.length = 100; return pulse; 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..d286b7110 --- /dev/null +++ b/Storage/Tape/Parsers/Commodore.cpp @@ -0,0 +1,313 @@ +// +// Commodore.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Commodore.hpp" +#include "../../Data/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 = Storage::Data::Commodore::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/Storage/Tape/Parsers/Oric.cpp b/Storage/Tape/Parsers/Oric.cpp new file mode 100644 index 000000000..1995c11f0 --- /dev/null +++ b/Storage/Tape/Parsers/Oric.cpp @@ -0,0 +1,196 @@ +// +// Oric.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Oric.hpp" + +using namespace Storage::Tape::Oric; + +int Parser::get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding) +{ + _detection_mode = use_fast_encoding ? FastZero : SlowZero; + _cycle_length = 0.0f; + + int result = 0; + int bit_count = 0; + while(bit_count < 11 && !tape->is_at_end()) + { + SymbolType symbol = get_next_symbol(tape); + if(!bit_count && symbol != SymbolType::Zero) continue; + _detection_mode = use_fast_encoding ? FastData : SlowData; + result |= ((symbol == SymbolType::One) ? 1 : 0) << bit_count; + bit_count++; + } + // TODO: check parity? + return tape->is_at_end() ? -1 : ((result >> 1)&0xff); +} + +bool Parser::sync_and_get_encoding_speed(const std::shared_ptr &tape) +{ + _detection_mode = Sync; + while(!tape->is_at_end()) + { + SymbolType symbol = get_next_symbol(tape); + switch(symbol) + { + case SymbolType::FoundSlow: return false; + case SymbolType::FoundFast: return true; + default: break; + } + } + return false; +} + +void Parser::process_pulse(Storage::Tape::Tape::Pulse pulse) +{ + const float length_threshold = 0.0003125f; + + bool wave_is_high = pulse.type == Storage::Tape::Tape::Pulse::High; + if(wave_is_high != _wave_was_high && _cycle_length > 0.0f) + { + if(_cycle_length > 2.0 * length_threshold) + push_wave(WaveType::Unrecognised); + else push_wave(_cycle_length < length_threshold ? WaveType::Short : WaveType::Long); + + _cycle_length = 0.0f; + } + _wave_was_high = wave_is_high; + _cycle_length += pulse.length.get_float(); +} + +void Parser::inspect_waves(const std::vector &waves) +{ + switch(_detection_mode) + { + case FastZero: + if(waves.size() < 2) return; + if(waves[0] == WaveType::Short && waves[1] == WaveType::Long) + { + push_symbol(SymbolType::Zero, 2); + return; + } + break; + + case FastData: + if(waves.size() < 2) return; + if(waves[0] == WaveType::Short && waves[1] != WaveType::Unrecognised) + { + push_symbol((waves[1] == WaveType::Long) ? SymbolType::Zero : SymbolType::One, 2); + return; + } + break; + + case SlowZero: + if(waves.size() < 8) return; + if( + waves[0] == WaveType::Long && waves[1] == WaveType::Long && waves[2] == WaveType::Long && waves[3] == WaveType::Long && + waves[4] == WaveType::Long && waves[5] == WaveType::Long && waves[6] == WaveType::Long && waves[7] == WaveType::Long + ) + { + push_symbol(SymbolType::Zero, 8); + return; + } + break; + + case SlowData: +#define CHECK_RUN(length, type, symbol) \ + if(waves.size() >= length)\ + {\ + size_t c;\ + for(c = 0; c < length; c++) if(waves[c] != type) break;\ + if(c == length)\ + {\ + push_symbol(symbol, 8);\ + return;\ + }\ + } + + CHECK_RUN(8, WaveType::Long, SymbolType::Zero); + CHECK_RUN(16, WaveType::Short, SymbolType::One); +#undef CHECK_RUN + if(waves.size() < 16) return; // TODO, maybe: if there are any inconsistencies in the first 8, don't return + break; + + case Sync: + { + // Sync is 0x16, either encoded fast or slow; i.e. 0 0110 1000 1 + // So, fast: [short, long]*2, [short, short]*2, [short, long], [short, short], [short, long]*3, [short, short] = 20 + // [short, short] = 1; [short, long] = 0 + // Slow: long*16, short*32, long*8, short*16, long*24, short*16 = 112 + Pattern slow_sync[] = + { + {.type = WaveType::Long, 16}, + {.type = WaveType::Short, 32}, + {.type = WaveType::Long, 8}, + {.type = WaveType::Short, 16}, + {.type = WaveType::Long, 24}, + {.type = WaveType::Short, 16}, + {.type = WaveType::Unrecognised} + }; + Pattern fast_sync[] = + { + {.type = WaveType::Short, 1}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 1}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 5}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 3}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 1}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 1}, + {.type = WaveType::Long, 1}, + {.type = WaveType::Short, 2}, + {.type = WaveType::Unrecognised} + }; + + size_t slow_sync_matching_depth = pattern_matching_depth(waves, slow_sync); + size_t fast_sync_matching_depth = pattern_matching_depth(waves, fast_sync); + + if(slow_sync_matching_depth == 112) + { + push_symbol(SymbolType::FoundSlow, 112); + return; + } + if(fast_sync_matching_depth == 20) + { + push_symbol(SymbolType::FoundFast, 20); + return; + } + if(slow_sync_matching_depth < waves.size() && fast_sync_matching_depth < waves.size()) + { + int least_depth = (int)std::min(slow_sync_matching_depth, fast_sync_matching_depth); + remove_waves(least_depth ? least_depth : 1); + } + + return; + } + break; + } + + remove_waves(1); +} + +size_t Parser::pattern_matching_depth(const std::vector &waves, Pattern *pattern) +{ + size_t depth = 0; + int pattern_depth = 0; + while(depth < waves.size() && pattern->type != WaveType::Unrecognised) + { + if(waves[depth] != pattern->type) break; + depth++; + pattern_depth++; + if(pattern_depth == pattern->count) + { + pattern_depth = 0; + pattern++; + } + } + + return depth; +} diff --git a/Storage/Tape/Parsers/Oric.hpp b/Storage/Tape/Parsers/Oric.hpp new file mode 100644 index 000000000..f4678e3bb --- /dev/null +++ b/Storage/Tape/Parsers/Oric.hpp @@ -0,0 +1,58 @@ +// +// Oric.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/11/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Storage_Tape_Parsers_Oric_hpp +#define Storage_Tape_Parsers_Oric_hpp + +#include "TapeParser.hpp" + +namespace Storage { +namespace Tape { +namespace Oric { + +enum class WaveType { + Short, Long, Unrecognised +}; + +enum class SymbolType { + One, Zero, FoundFast, FoundSlow +}; + +class Parser: public Storage::Tape::Parser { + public: + int get_next_byte(const std::shared_ptr &tape, bool use_fast_encoding); + bool sync_and_get_encoding_speed(const std::shared_ptr &tape); + + private: + void process_pulse(Storage::Tape::Tape::Pulse pulse); + void inspect_waves(const std::vector &waves); + + enum DetectionMode { + FastData, + SlowData, + FastZero, + SlowZero, + Sync + } _detection_mode; + bool _wave_was_high; + float _cycle_length; + + struct Pattern + { + WaveType type; + int count; + }; + size_t pattern_matching_depth(const std::vector &waves, Pattern *pattern); +}; + + +} +} +} + +#endif /* Oric_hpp */ diff --git a/StaticAnalyser/TapeParser.hpp b/Storage/Tape/Parsers/TapeParser.hpp similarity index 85% rename from StaticAnalyser/TapeParser.hpp rename to Storage/Tape/Parsers/TapeParser.hpp index 78228a4a4..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,17 +24,35 @@ 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(const std::shared_ptr &tape) : _tape(tape), _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; } /// @returns @c true if an error has occurred since the error flag was last reset; @c false otherwise. bool get_error_flag() { return _error_flag; } - /// @returns @c true if the encapsulated tape has reached its end; @c false otherwise. - bool is_at_end() { return _tape->is_at_end(); } + + /*! + Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol + or the tape runs out, returning the most-recently declared symbol. + */ + SymbolType get_next_symbol(const std::shared_ptr &tape) + { + while(!_has_next_symbol && !tape->is_at_end()) + { + process_pulse(tape->get_next_pulse()); + } + _has_next_symbol = false; + return _next_symbol; + } + + /*! + Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave + or to take no action. + */ + virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0; protected: @@ -67,20 +91,6 @@ template class TapeParser { remove_waves(number_of_waves); } - /*! - Asks the parser to continue taking pulses from the tape until either the subclass next declares a symbol - or the tape runs out, returning the most-recently declared symbol. - */ - SymbolType get_next_symbol() - { - while(!_has_next_symbol && !is_at_end()) - { - process_pulse(_tape->get_next_pulse()); - } - _has_next_symbol = false; - return _next_symbol; - } - void set_error_flag() { _error_flag = true; @@ -89,12 +99,6 @@ template class TapeParser { private: bool _error_flag; - /*! - Should be implemented by subclasses. Consumes @c pulse. Is likely either to call @c push_wave - or to take no action. - */ - virtual void process_pulse(Storage::Tape::Tape::Pulse pulse) = 0; - /*! Should be implemented by subclasses. Inspects @c waves for a potential new symbol. If one is found should call @c push_symbol. May wish alternatively to call @c remove_waves to have entries @@ -106,10 +110,9 @@ template class TapeParser { std::vector _wave_queue; SymbolType _next_symbol; bool _has_next_symbol; - - std::shared_ptr _tape; }; +} } #endif /* TapeParser_hpp */