1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-25 18:30:07 +00:00

Started relocating the tape parsers down from static analyser to storage, to signify that they may be used by the emulation (if fast loading is supported on that machine).

This commit is contained in:
Thomas Harte 2016-11-06 16:13:13 -05:00
parent 093eb55fc6
commit 1b15bc3a6c
8 changed files with 641 additions and 477 deletions

View File

@ -56,6 +56,8 @@
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */; };
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */; };
4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */; };
4B8FE21D1DA19D5F0090D3CE /* ElectronOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */; };
@ -497,6 +499,10 @@
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
4B6C73BB1D387AE500AFCFCA /* DiskController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskController.cpp; sourceTree = "<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>"; };
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; };
@ -1169,6 +1175,7 @@
isa = PBXGroup;
children = (
4B69FB411C4D941400B5F0AA /* Formats */,
4B8805F11DCFC9A2003085B1 /* Parsers */,
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */,
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */,
);
@ -1191,6 +1198,17 @@
path = Formats;
sourceTree = "<group>";
};
4B8805F11DCFC9A2003085B1 /* Parsers */ = {
isa = PBXGroup;
children = (
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */,
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */,
4B8805F21DCFD22A003085B1 /* Commodore.cpp */,
4B8805F31DCFD22A003085B1 /* Commodore.hpp */,
);
name = Parsers;
sourceTree = "<group>";
};
4BA799961D8B65730045123D /* Atari */ = {
isa = PBXGroup;
children = (
@ -2294,8 +2312,10 @@
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
4B8FE2201DA19D7C0090D3CE /* Atari2600OptionsPanel.swift in Sources */,
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */,
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */,
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */,
4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */,
4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */,
4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,

View File

@ -9,115 +9,12 @@
#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() :
TapeParser(),
_crc(0x1021, 0x0000) {}
int get_next_bit(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
SymbolType symbol = get_next_symbol(tape);
return (symbol == SymbolType::One) ? 1 : 0;
}
int 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 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 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 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(const std::shared_ptr<Storage::Tape::Tape> &tape, 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;
@ -233,7 +130,7 @@ 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;
Storage::Tape::Acorn::Parser parser;
// populate chunk list
std::deque<File::Chunk> chunk_list;

View File

@ -8,375 +8,16 @@
#include "Tape.hpp"
#include "../TapeParser.hpp"
#include "Utilities.hpp"
#include "../../Storage/Tape/Parsers/Commodore.hpp"
using namespace StaticAnalyser::Commodore;
enum class WaveType {
Short, Medium, Long, Unrecognised
};
enum class SymbolType {
One, Zero, Word, EndOfBlock, LeadIn
};
struct Header {
enum {
RelocatableProgram,
NonRelocatableProgram,
DataSequenceHeader,
DataBlock,
EndOfTape,
Unknown
} type;
std::vector<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(),
_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(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> 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)
);
}
// 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(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 = 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(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 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 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 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();
}
uint8_t _parity_byte;
void reset_parity_byte() { _parity_byte = 0; }
uint8_t get_parity_byte() { return _parity_byte; }
void add_parity_byte(uint8_t byte) { _parity_byte ^= byte; }
/*!
Proceeds to the next word marker then returns the result of @c get_next_byte_contents.
*/
uint8_t get_next_byte(const std::shared_ptr<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 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 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 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(tape);
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
while(!tape->is_at_end())
{
@ -388,7 +29,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
switch(header->type)
{
case Header::DataSequenceHeader:
case Storage::Tape::Commodore::Header::DataSequenceHeader:
{
File new_file;
new_file.name = header->name;
@ -402,7 +43,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
{
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(tape);
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
if(data)
{
File new_file;
@ -422,7 +63,7 @@ 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);
}

View 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);
}

View 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 */

View File

@ -0,0 +1,312 @@
//
// Commodore.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Commodore.hpp"
using namespace Storage::Tape::Commodore;
Parser::Parser() :
Storage::Tape::Parser<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 = 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);
}

View 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 */

View File

@ -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,10 +24,10 @@ 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() : _has_next_symbol(false), _error_flag(false) {}
Parser() : _has_next_symbol(false), _error_flag(false) {}
/// Resets the error flag.
void reset_error_flag() { _error_flag = false; }
@ -106,6 +112,7 @@ template <typename WaveType, typename SymbolType> class TapeParser {
bool _has_next_symbol;
};
}
}
#endif /* TapeParser_hpp */