diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index c09518130..3b14c40be 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -48,7 +48,8 @@ template class MOS6560 { _horizontal_counter(0), _vertical_counter(0), _cycles_since_speaker_update(0), - _is_odd_frame(false) + _is_odd_frame(false), + _is_odd_line(false) { _crt->set_composite_sampling_function( "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" @@ -82,6 +83,7 @@ template class MOS6560 { */ void set_output_mode(OutputMode output_mode) { + _output_mode = output_mode; uint8_t luminances[16] = { // range is 0–4 0, 4, 1, 3, 2, 2, 1, 3, 2, 1, 2, 1, 2, 3, 2, 3 @@ -160,6 +162,7 @@ template class MOS6560 { } _horizontal_counter = 0; + if(_output_mode == OutputMode::PAL) _is_odd_line ^= true; _horizontal_drawing_latch = false; _vertical_counter ++; @@ -168,7 +171,7 @@ template class MOS6560 { _vertical_counter = 0; _full_frame_counter = 0; - _is_odd_frame ^= true; + if(_output_mode == OutputMode::NTSC) _is_odd_frame ^= true; _current_row = 0; _rows_this_field = -1; _vertical_drawing_latch = false; @@ -250,10 +253,10 @@ template class MOS6560 { { switch(_output_state) { - case State::Sync: _crt->output_sync(_cycles_in_state * 4); break; - case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, _is_odd_frame ? 128 : 0, 0); break; - case State::Border: output_border(_cycles_in_state * 4); break; - case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; + case State::Sync: _crt->output_sync(_cycles_in_state * 4); break; + case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, (_is_odd_frame || _is_odd_line) ? 128 : 0, 0); break; + case State::Border: output_border(_cycles_in_state * 4); break; + case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; } _output_state = _this_state; _cycles_in_state = 0; @@ -456,7 +459,7 @@ template class MOS6560 { // data latched from the bus uint8_t _character_code, _character_colour, _character_value; - bool _is_odd_frame; + bool _is_odd_frame, _is_odd_line; // lookup table from 6560 colour index to appropriate PAL/NTSC value uint8_t _colours[16]; @@ -475,6 +478,7 @@ template class MOS6560 { int lines_per_progressive_field; bool supports_interlacing; } _timing; + OutputMode _output_mode; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 555ae488c..93edde52d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -9,6 +9,7 @@ #include "Vic20.hpp" #include +#include "../../../Storage/Tape/Formats/TapePRG.hpp" using namespace Commodore::Vic20; @@ -62,7 +63,7 @@ void Machine::set_memory_size(MemorySize size) break; } - // install the ROMs and VIC-visible memory + // install the system ROMs and VIC-visible memory write_to_map(_processorReadMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory)); write_to_map(_processorReadMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory)); write_to_map(_processorReadMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory)); @@ -73,6 +74,12 @@ void Machine::set_memory_size(MemorySize size) write_to_map(_processorWriteMemoryMap, _userBASICMemory, 0x0000, sizeof(_userBASICMemory)); write_to_map(_processorWriteMemoryMap, _screenMemory, 0x1000, sizeof(_screenMemory)); write_to_map(_processorWriteMemoryMap, _colorMemory, 0x9400, sizeof(_colorMemory)); + + // install the inserted ROM if there is one + if(_rom) + { + write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length); + } } void Machine::write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length) @@ -95,10 +102,15 @@ Machine::~Machine() unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) { // static int logCount = 0; -// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xee17) logCount = 500; +// if(operation == CPU6502::BusOperation::ReadOpcode && address == 0xf957) logCount = 500; // if(operation == CPU6502::BusOperation::ReadOpcode && logCount) { // logCount--; // printf("%04x\n", address); +// } + +// if(operation == CPU6502::BusOperation::Write && (address >= 0x033C && address < 0x033C + 192)) +// { +// printf("\n[%04x] <- %02x\n", address, *value); // } // run the phase-1 part of this cycle, in which the VIC accesses memory @@ -218,35 +230,23 @@ void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) } } -void Machine::add_prg(size_t length, const uint8_t *data) +void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data) { if(length > 2) { _rom_address = (uint16_t)(data[0] | (data[1] << 8)); _rom_length = (uint16_t)(length - 2); - if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000 && _should_automatically_load_media) - { - set_typer_for_string("RUN\n"); - } + // install in the ROM area if this looks like a ROM; otherwise put on tape and throw into that mechanism if(_rom_address == 0xa000) { - _rom = new uint8_t[length - 2]; + _rom = new uint8_t[0x2000]; memcpy(_rom, &data[2], length - 2); - write_to_map(_processorReadMemoryMap, _rom, _rom_address, _rom_length); + write_to_map(_processorReadMemoryMap, _rom, _rom_address, 0x2000); } else { - // TODO: write to virtual media (tape, probably?), load normally. - data += 2; - while(_rom_length) - { - uint8_t *ram = _processorWriteMemoryMap[_rom_address >> 10]; - if(ram) ram[_rom_address & 0x3ff] = *data; - data++; - _rom_length--; - _rom_address++; - } + set_tape(std::shared_ptr(new Storage::TapePRG(file_name))); } } } diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index e4fb58386..bb4121dd9 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -260,7 +260,7 @@ class Machine: ~Machine(); void set_rom(ROMSlot slot, size_t length, const uint8_t *data); - void add_prg(size_t length, const uint8_t *data); + void set_prg(const char *file_name, size_t length, const uint8_t *data); void set_tape(std::shared_ptr tape); void set_disk(std::shared_ptr disk); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 9a9652a66..060d6ef83 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; + 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; @@ -393,6 +394,8 @@ 4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = ""; }; 4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = ""; }; 4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; + 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; + 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; 4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; @@ -1004,6 +1007,8 @@ 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */, 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */, 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, + 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */, + 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */, ); path = Formats; sourceTree = ""; @@ -1908,6 +1913,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift index b55a42427..6eb656b3b 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -47,6 +47,7 @@ class Vic20Document: MachineDocument { case "tap": vic20.openTAPAtURL(url) case "g64": vic20.openG64AtURL(url) case "d64": vic20.openD64AtURL(url) + case "prg": vic20.openPRGAtURL(url) default: let fileWrapper = try NSFileWrapper(URL: url, options: NSFileWrapperReadingOptions(rawValue: 0)) try self.readFromFileWrapper(fileWrapper, ofType: typeName) @@ -59,10 +60,6 @@ class Vic20Document: MachineDocument { return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20") } - override func readFromData(data: NSData, ofType typeName: String) throws { - vic20.setPRG(data) - } - // MARK: automatic loading tick box @IBOutlet var loadAutomaticallyButton: NSButton? var autoloadingUserDefaultsKey: String { diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h index c8ade6c34..c2e17fe61 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.h @@ -30,7 +30,7 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize) - (void)setCharactersROM:(nonnull NSData *)rom; - (void)setDriveROM:(nonnull NSData *)rom; -- (void)setPRG:(nonnull NSData *)prg; +- (BOOL)openPRGAtURL:(nonnull NSURL *)URL; - (BOOL)openTAPAtURL:(nonnull NSURL *)URL; - (BOOL)openG64AtURL:(nonnull NSURL *)URL; - (BOOL)openD64AtURL:(nonnull NSURL *)URL; diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm index 5108741a1..0f03fb7e2 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSVic20.mm @@ -82,9 +82,15 @@ using namespace Commodore::Vic20; } } -- (void)setPRG:(nonnull NSData *)prg { +- (BOOL)openPRGAtURL:(NSURL *)URL { + NSData *prg = [NSData dataWithContentsOfURL:URL]; @synchronized(self) { - _vic20.add_prg(prg.length, (const uint8_t *)prg.bytes); + try { + _vic20.set_prg(URL.fileSystemRepresentation, prg.length, (const uint8_t *)prg.bytes); + return YES; + } catch(...) { + return NO; + } } } diff --git a/Storage/Tape/Formats/CommodoreTAP.cpp b/Storage/Tape/Formats/CommodoreTAP.cpp index 503f84b40..f574a3fd7 100644 --- a/Storage/Tape/Formats/CommodoreTAP.cpp +++ b/Storage/Tape/Formats/CommodoreTAP.cpp @@ -64,7 +64,7 @@ void CommodoreTAP::reset() _current_pulse.type = Pulse::High; } -CommodoreTAP::Pulse CommodoreTAP::get_next_pulse() +Tape::Pulse CommodoreTAP::get_next_pulse() { if(_current_pulse.type == Pulse::High) { diff --git a/Storage/Tape/Formats/CommodoreTAP.hpp b/Storage/Tape/Formats/CommodoreTAP.hpp index b76055259..7d4a9de17 100644 --- a/Storage/Tape/Formats/CommodoreTAP.hpp +++ b/Storage/Tape/Formats/CommodoreTAP.hpp @@ -15,7 +15,7 @@ namespace Storage { /*! - Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of zero crossings. + Provides a @c Tape containing a Commodore-format tape image, which is simply a timed list of downward-going zero crossings. */ class CommodoreTAP: public Tape { public: diff --git a/Storage/Tape/Formats/TapePRG.cpp b/Storage/Tape/Formats/TapePRG.cpp new file mode 100644 index 000000000..cc3f5db04 --- /dev/null +++ b/Storage/Tape/Formats/TapePRG.cpp @@ -0,0 +1,241 @@ +// +// TapePRG.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "TapePRG.hpp" + +/* + My interpretation of Commodore's tape format is such that a PRG is encoded as: + + [long block of lead-in tone] + [short block of lead-in tone] + [count down][header; 192 bytes fixed length] + [short block of lead-in tone] + [count down][copy of header; 192 bytes fixed length] + [gap] + [short block of lead-in tone] + [count down][data; length as in file] + [short block of lead-in tone] + [count down][copy of data] + ... and repeat ... + + Individual bytes are composed of: + + word marker + least significant bit + ... + most significant bit + parity bit + + Both the header and data blocks additionally end with an end-of-block marker. + + Encoding is via square-wave cycles of four lengths, in ascending order: lead-in, zero, one, marker. + + Lead-in tone is always just repetitions of the lead-in wave. + A word marker is a marker wave followed by a one wave. + An end-of-block marker is a marker wave followed by a zero wave. + A zero bit is a zero wave followed by a one wave. + A one bit is a one wave followed by a zero wave. + + Parity is 1 if there are an even number of bits in the byte; 0 otherwise. +*/ + +#include + +using namespace Storage; + +TapePRG::TapePRG(const char *file_name) : _file(nullptr), _bitPhase(3), _filePhase(FilePhaseLeadIn), _phaseOffset(0), _copy_mask(0x80) +{ + struct stat file_stats; + stat(file_name, &file_stats); + + // There's really no way to validate other than that if this file is larger than 64kb, + // of if load address + length > 65536 then it's broken. + if(file_stats.st_size >= 65538 || file_stats.st_size < 3) + throw ErrorBadFormat; + + _file = fopen(file_name, "rb"); + if(!_file) throw ErrorBadFormat; + + _load_address = (uint16_t)fgetc(_file); + _load_address |= (uint16_t)fgetc(_file) << 8; + _length = (uint16_t)(file_stats.st_size - 2); + + if (_load_address + _length >= 65536) + throw ErrorBadFormat; +} + +TapePRG::~TapePRG() +{ + if(_file) fclose(_file); +} + +Tape::Pulse TapePRG::get_next_pulse() +{ + // these are all microseconds per pole + static const unsigned int leader_zero_length = 179; + static const unsigned int zero_length = 169; + static const unsigned int one_length = 247; + static const unsigned int marker_length = 328; + + _bitPhase = (_bitPhase+1)&3; + if(!_bitPhase) get_next_output_token(); + + Tape::Pulse pulse; + pulse.length.clock_rate = 1000000; + pulse.type = (_bitPhase&1) ? Pulse::High : Pulse::Low; + switch(_outputToken) + { + case Leader: pulse.length.length = leader_zero_length; break; + case Zero: pulse.length.length = (_bitPhase&2) ? one_length : zero_length; break; + case One: pulse.length.length = (_bitPhase&2) ? zero_length : one_length; break; + case WordMarker: pulse.length.length = (_bitPhase&2) ? one_length : marker_length; break; + case EndOfBlock: pulse.length.length = (_bitPhase&2) ? zero_length : marker_length; break; + case Silence: pulse.type = Pulse::Zero; pulse.length.length = 5000; break; + } + return pulse; +} + +void TapePRG::reset() +{ + _bitPhase = 3; + fseek(_file, 2, SEEK_SET); + _filePhase = FilePhaseLeadIn; + _phaseOffset = 0; + _copy_mask = 0x80; +} + +void TapePRG::get_next_output_token() +{ + static const int block_length = 192; // not counting the checksum + static const int countdown_bytes = 9; + static const int leadin_length = 20000; + static const int block_leadin_length = 5000; + + if(_filePhase == FilePhaseHeaderDataGap) + { + _outputToken = Silence; + _filePhase = FilePhaseData; + return; + } + + // the lead-in is 20,000 instances of the lead-in pair; every other phase begins with 5000 + // before doing whatever it should be doing + if(_filePhase == FilePhaseLeadIn || _phaseOffset < block_leadin_length) + { + _outputToken = Leader; + _phaseOffset++; + if(_filePhase == FilePhaseLeadIn && _phaseOffset == leadin_length) + { + _phaseOffset = 0; + _filePhase = (_filePhase == FilePhaseLeadIn) ? FilePhaseHeader : FilePhaseData; + } + return; + } + + // determine whether a new byte needs to be queued up + int block_offset = _phaseOffset - block_leadin_length; + int bit_offset = block_offset % 10; + int byte_offset = block_offset / 10; + _phaseOffset++; + + if(!bit_offset && + ( + (_filePhase == FilePhaseHeader && byte_offset == block_length + countdown_bytes + 1) || + feof(_file) + ) + ) + { + _outputToken = EndOfBlock; + _phaseOffset = 0; + + switch(_filePhase) + { + default: break; + case FilePhaseHeader: + _copy_mask ^= 0x80; + if(_copy_mask) _filePhase = FilePhaseHeaderDataGap; + break; + case FilePhaseData: + _copy_mask ^= 0x80; + fseek(_file, 2, SEEK_SET); + if(_copy_mask) reset(); + break; + } + return; + } + + if(bit_offset == 0) + { + // the first nine bytes are countdown; the high bit is set if this is a header + if(byte_offset < countdown_bytes) + { + _output_byte = (uint8_t)(countdown_bytes - byte_offset) | _copy_mask; + } + else + { + if(_filePhase == FilePhaseHeader) + { + if(byte_offset == countdown_bytes + block_length) + { + _output_byte = _check_digit; + } + else + { + if(byte_offset == countdown_bytes) _check_digit = 0; + if(_filePhase == FilePhaseHeader) + { + switch(byte_offset - countdown_bytes) + { + case 0: _output_byte = 0x03; break; + case 1: _output_byte = _load_address & 0xff; break; + case 2: _output_byte = (_load_address >> 8)&0xff; break; + case 3: _output_byte = (_load_address + _length) & 0xff; break; + case 4: _output_byte = ((_load_address + _length) >> 8) & 0xff; break; + + case 5: _output_byte = 0x50; break; // P + case 6: _output_byte = 0x52; break; // R + case 7: _output_byte = 0x47; break; // G + default: + _output_byte = 0x20; + break; + } + } + } + } + else + { + _output_byte = (uint8_t)fgetc(_file); + if(feof(_file)) + { + _output_byte = _check_digit; + } + } + + _check_digit ^= _output_byte; + } + } + + switch(bit_offset) + { + case 0: + _outputToken = WordMarker; + break; + default: // i.e. 1–8 + _outputToken = (_output_byte & (1 << (bit_offset - 1))) ? One : Zero; + break; + case 9: + { + uint8_t parity = _output_byte; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); + _outputToken = (parity&1) ? Zero : One; + } + break; + } +} diff --git a/Storage/Tape/Formats/TapePRG.hpp b/Storage/Tape/Formats/TapePRG.hpp new file mode 100644 index 000000000..e516a961f --- /dev/null +++ b/Storage/Tape/Formats/TapePRG.hpp @@ -0,0 +1,69 @@ +// +// TapePRG.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/08/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef TapePRG_hpp +#define TapePRG_hpp + +#include "../Tape.hpp" +#include + +namespace Storage { + +/*! + Provides a @c Tape containing a .PRG, which is a direct local file. +*/ +class TapePRG: public Tape { + public: + /*! + Constructs a @c T64 containing content from the file with name @c file_name, of type @c type. + + @param file_name The name of the file to load. + @param type The type of data the file should contain. + @throws ErrorBadFormat if this file could not be opened and recognised as the specified type. + */ + TapePRG(const char *file_name); + ~TapePRG(); + + enum { + ErrorBadFormat + }; + + // implemented to satisfy @c Tape + Pulse get_next_pulse(); + void reset(); + private: + FILE *_file; + uint16_t _load_address; + uint16_t _length; + + enum FilePhase { + FilePhaseLeadIn, + FilePhaseHeader, + FilePhaseHeaderDataGap, + FilePhaseData, + } _filePhase; + int _phaseOffset; + + int _bitPhase; + enum OutputToken { + Leader, + Zero, + One, + WordMarker, + EndOfBlock, + Silence + } _outputToken; + void get_next_output_token(); + uint8_t _output_byte; + uint8_t _check_digit; + uint8_t _copy_mask; +}; + +} + +#endif /* T64_hpp */