mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Merge pull request #73 from TomHarte/TapeClasses
Clarifies that a tape parser does not own a tape, it is merely lent one
This commit is contained in:
commit
79dd062941
@ -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<Machine::VIA>(), _cycles_since_ay_update(0) {}
|
||||
Machine::VIA::VIA() :
|
||||
MOS::MOS6522<Machine::VIA>(),
|
||||
_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);
|
||||
}
|
||||
|
@ -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<GI::AY38910> ay8910;
|
||||
std::shared_ptr<TapePlayer> tape;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> keyboard;
|
||||
|
||||
void synchronise();
|
||||
|
@ -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 = "<group>"; };
|
||||
4B6C73BC1D387AE500AFCFCA /* DiskController.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
|
||||
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
|
||||
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
|
||||
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
|
||||
4B8805F31DCFD22A003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Parsers/Commodore.hpp; sourceTree = "<group>"; };
|
||||
4B8805F51DCFF6C9003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Data/Commodore.cpp; sourceTree = "<group>"; };
|
||||
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Data/Commodore.hpp; sourceTree = "<group>"; };
|
||||
4B8805F91DCFF807003085B1 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Parsers/Oric.cpp; sourceTree = "<group>"; };
|
||||
4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; };
|
||||
4B8805FC1DD02552003085B1 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Oric/Tape.cpp; sourceTree = "<group>"; };
|
||||
4B8805FD1DD02552003085B1 /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Oric/Tape.hpp; sourceTree = "<group>"; };
|
||||
4B8E4ECD1DCE483D003716C3 /* KeyboardMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardMachine.hpp; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4BC5E4901D7ED365008CF980 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BC5E4911D7ED365008CF980 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Commodore/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Utilities.cpp; path = ../../StaticAnalyser/Commodore/Utilities.cpp; sourceTree = "<group>"; };
|
||||
4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Utilities.hpp; path = ../../StaticAnalyser/Commodore/Utilities.hpp; sourceTree = "<group>"; };
|
||||
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
||||
@ -854,7 +866,6 @@
|
||||
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = "<group>"; };
|
||||
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
|
||||
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
@ -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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
4B8805F81DCFF6CD003085B1 /* Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B8805F51DCFF6C9003085B1 /* Commodore.cpp */,
|
||||
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */,
|
||||
);
|
||||
name = Data;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@ -1740,6 +1773,8 @@
|
||||
children = (
|
||||
4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */,
|
||||
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */,
|
||||
4B8805FC1DD02552003085B1 /* Tape.cpp */,
|
||||
4B8805FD1DD02552003085B1 /* Tape.hpp */,
|
||||
);
|
||||
name = Oric;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -9,130 +9,27 @@
|
||||
#include "Tape.hpp"
|
||||
|
||||
#include <deque>
|
||||
#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<WaveType, SymbolType> {
|
||||
public:
|
||||
Acorn1200BaudTapeParser(const std::shared_ptr<Storage::Tape::Tape> &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<WaveType> &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<File::Chunk> GetNextChunk(Acorn1200BaudTapeParser &parser)
|
||||
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser)
|
||||
{
|
||||
std::unique_ptr<File::Chunk> 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<File::Chunk> 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<File::Chunk> 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<File::Chunk> 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<File> GetNextFile(std::deque<File::Chunk> &chunks)
|
||||
|
||||
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
Acorn1200BaudTapeParser parser(tape);
|
||||
Storage::Tape::Acorn::Parser parser;
|
||||
|
||||
// populate chunk list
|
||||
std::deque<File::Chunk> chunk_list;
|
||||
while(!parser.is_at_end())
|
||||
while(!tape->is_at_end())
|
||||
{
|
||||
std::unique_ptr<File::Chunk> chunk = GetNextChunk(parser);
|
||||
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
|
||||
if(chunk)
|
||||
{
|
||||
chunk_list.push_back(*chunk);
|
||||
|
@ -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 <limits>
|
||||
#include <vector>
|
||||
@ -234,7 +234,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
{
|
||||
new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
|
||||
}
|
||||
new_file.name = petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
|
||||
|
||||
size_t number_of_sectors = (size_t)directory[header_pointer + 0x1e] + ((size_t)directory[header_pointer + 0x1f] << 8);
|
||||
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
|
||||
|
@ -8,387 +8,28 @@
|
||||
|
||||
#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<uint8_t> data;
|
||||
std::wstring name;
|
||||
std::vector<uint8_t> raw_name;
|
||||
uint16_t starting_address;
|
||||
uint16_t ending_address;
|
||||
bool parity_was_valid;
|
||||
bool duplicate_matched;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
std::vector<uint8_t> data;
|
||||
bool parity_was_valid;
|
||||
bool duplicate_matched;
|
||||
};
|
||||
|
||||
class CommodoreROMTapeParser: public StaticAnalyer::TapeParser<WaveType, SymbolType> {
|
||||
public:
|
||||
CommodoreROMTapeParser(const std::shared_ptr<Storage::Tape::Tape> &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<Header> get_next_header()
|
||||
{
|
||||
return duplicate_match<Header>(
|
||||
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<Data> get_next_data()
|
||||
{
|
||||
return duplicate_match<Data>(
|
||||
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<class ObjectType>
|
||||
std::unique_ptr<ObjectType> duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> 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<ObjectType> *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<Header> get_next_header_body(bool is_original)
|
||||
{
|
||||
std::unique_ptr<Header> 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<Data> get_next_data_body(bool is_original)
|
||||
{
|
||||
std::unique_ptr<Data> 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<WaveType> &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<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
CommodoreROMTapeParser parser(tape);
|
||||
Storage::Tape::Commodore::Parser parser;
|
||||
std::list<File> file_list;
|
||||
|
||||
std::unique_ptr<Header> header = parser.get_next_header();
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> 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<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
new_file.type = File::DataSequence;
|
||||
|
||||
new_file.data.swap(header->data);
|
||||
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<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
}
|
||||
break;
|
||||
|
||||
case Header::RelocatableProgram:
|
||||
case Header::NonRelocatableProgram:
|
||||
case Storage::Tape::Commodore::Header::RelocatableProgram:
|
||||
case Storage::Tape::Commodore::Header::NonRelocatableProgram:
|
||||
{
|
||||
std::unique_ptr<Data> data = parser.get_next_data();
|
||||
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
|
||||
if(data)
|
||||
{
|
||||
File new_file;
|
||||
@ -422,17 +63,17 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
|
||||
new_file.starting_address = header->starting_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;
|
||||
}
|
||||
}
|
||||
|
@ -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<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
|
||||
std::list<StaticAnalyser::Target> &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<File> 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);
|
||||
}
|
||||
|
95
StaticAnalyser/Oric/Tape.cpp
Normal file
95
StaticAnalyser/Oric/Tape.cpp
Normal file
@ -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<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
std::list<File> 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;
|
||||
}
|
38
StaticAnalyser/Oric/Tape.hpp
Normal file
38
StaticAnalyser/Oric/Tape.hpp
Normal file
@ -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 <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Tape_hpp */
|
@ -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;
|
||||
|
@ -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 <string>
|
||||
|
||||
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 */
|
@ -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;
|
||||
|
||||
|
100
Storage/Tape/Parsers/Acorn.cpp
Normal file
100
Storage/Tape/Parsers/Acorn.cpp
Normal file
@ -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<WaveType, SymbolType>(),
|
||||
_crc(0x1021, 0x0000) {}
|
||||
|
||||
int Parser::get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
SymbolType symbol = get_next_symbol(tape);
|
||||
return (symbol == SymbolType::One) ? 1 : 0;
|
||||
}
|
||||
|
||||
int Parser::get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &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<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
int result = get_next_byte(tape);
|
||||
result |= get_next_byte(tape) << 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
int Parser::get_next_word(const std::shared_ptr<Storage::Tape::Tape> &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<WaveType> &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);
|
||||
}
|
48
Storage/Tape/Parsers/Acorn.hpp
Normal file
48
Storage/Tape/Parsers/Acorn.hpp
Normal file
@ -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<WaveType, SymbolType> {
|
||||
public:
|
||||
Parser();
|
||||
|
||||
int get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
int get_next_short(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
int get_next_word(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
void reset_crc();
|
||||
uint16_t get_crc();
|
||||
|
||||
private:
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void inspect_waves(const std::vector<WaveType> &waves);
|
||||
NumberTheory::CRC16 _crc;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Acorn_hpp */
|
313
Storage/Tape/Parsers/Commodore.cpp
Normal file
313
Storage/Tape/Parsers/Commodore.cpp
Normal file
@ -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<WaveType, SymbolType>(),
|
||||
_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<Header> Parser::get_next_header(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
return duplicate_match<Header>(
|
||||
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<Data> Parser::get_next_data(const std::shared_ptr<Storage::Tape::Tape> &tape)
|
||||
{
|
||||
return duplicate_match<Data>(
|
||||
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<class ObjectType>
|
||||
std::unique_ptr<ObjectType> Parser::duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> 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<ObjectType> *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<Header> Parser::get_next_header_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original)
|
||||
{
|
||||
std::unique_ptr<Header> 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<Data> Parser::get_next_data_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original)
|
||||
{
|
||||
std::unique_ptr<Data> 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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<WaveType> &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);
|
||||
}
|
139
Storage/Tape/Parsers/Commodore.hpp
Normal file
139
Storage/Tape/Parsers/Commodore.hpp
Normal file
@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
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<uint8_t> data;
|
||||
std::wstring name;
|
||||
std::vector<uint8_t> raw_name;
|
||||
uint16_t starting_address;
|
||||
uint16_t ending_address;
|
||||
bool parity_was_valid;
|
||||
bool duplicate_matched;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
std::vector<uint8_t> data;
|
||||
bool parity_was_valid;
|
||||
bool duplicate_matched;
|
||||
};
|
||||
|
||||
class Parser: public Storage::Tape::Parser<WaveType, SymbolType> {
|
||||
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<Header> get_next_header(const std::shared_ptr<Storage::Tape::Tape> &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<Data> get_next_data(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
private:
|
||||
/*!
|
||||
Template for the logic in selecting which of two copies of something to consider authoritative,
|
||||
including setting the duplicate_matched flag.
|
||||
*/
|
||||
template<class ObjectType>
|
||||
std::unique_ptr<ObjectType> duplicate_match(std::unique_ptr<ObjectType> first_copy, std::unique_ptr<ObjectType> second_copy);
|
||||
|
||||
std::unique_ptr<Header> get_next_header_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
|
||||
std::unique_ptr<Data> get_next_data_body(const std::shared_ptr<Storage::Tape::Tape> &tape, bool is_original);
|
||||
|
||||
/*!
|
||||
Finds and completes the next landing zone.
|
||||
*/
|
||||
void proceed_to_landing_zone(const std::shared_ptr<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<WaveType> &waves);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Commodore_hpp */
|
196
Storage/Tape/Parsers/Oric.cpp
Normal file
196
Storage/Tape/Parsers/Oric.cpp
Normal file
@ -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<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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<WaveType> &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<WaveType> &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;
|
||||
}
|
58
Storage/Tape/Parsers/Oric.hpp
Normal file
58
Storage/Tape/Parsers/Oric.hpp
Normal file
@ -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<WaveType, SymbolType> {
|
||||
public:
|
||||
int get_next_byte(const std::shared_ptr<Storage::Tape::Tape> &tape, bool use_fast_encoding);
|
||||
bool sync_and_get_encoding_speed(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
private:
|
||||
void process_pulse(Storage::Tape::Tape::Pulse pulse);
|
||||
void inspect_waves(const std::vector<WaveType> &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<WaveType> &waves, Pattern *pattern);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Oric_hpp */
|
@ -9,7 +9,13 @@
|
||||
#ifndef TapeParser_hpp
|
||||
#define TapeParser_hpp
|
||||
|
||||
namespace StaticAnalyer {
|
||||
#include "../Tape.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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 <typename WaveType, typename SymbolType> class TapeParser {
|
||||
template <typename WaveType, typename SymbolType> class Parser {
|
||||
public:
|
||||
/// Instantiates a new parser with the supplied @c tape.
|
||||
TapeParser(const std::shared_ptr<Storage::Tape::Tape> &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<Storage::Tape::Tape> &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 <typename WaveType, typename SymbolType> 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 <typename WaveType, typename SymbolType> 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 <typename WaveType, typename SymbolType> class TapeParser {
|
||||
std::vector<WaveType> _wave_queue;
|
||||
SymbolType _next_symbol;
|
||||
bool _has_next_symbol;
|
||||
|
||||
std::shared_ptr<Storage::Tape::Tape> _tape;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TapeParser_hpp */
|
Loading…
x
Reference in New Issue
Block a user