From df01c7803917e761611c18ba9d53d9b47775ff43 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 11 Oct 2016 07:39:48 -0400 Subject: [PATCH] It's a bit of a mess but this is probably close to appropriate for Oric TAP files. --- .../Clock Signal.xcodeproj/project.pbxproj | 6 + OSBindings/Mac/Clock Signal/Info.plist | 2 +- .../StaticAnalyser/CSStaticAnalyser.mm | 4 +- StaticAnalyser/StaticAnalyser.cpp | 7 +- StaticAnalyser/StaticAnalyser.hpp | 3 +- Storage/Tape/Formats/OricTAP.cpp | 171 ++++++++++++++++++ Storage/Tape/Formats/OricTAP.hpp | 59 ++++++ 7 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 Storage/Tape/Formats/OricTAP.cpp create mode 100644 Storage/Tape/Formats/OricTAP.hpp diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 984fb1149..29149a3e4 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; + 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; @@ -462,6 +463,8 @@ 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = ""; }; + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = ""; }; 4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = ""; }; 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = ""; }; 4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = ""; }; @@ -1138,6 +1141,8 @@ 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */, 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */, + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */, + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */, ); path = Formats; sourceTree = ""; @@ -2160,6 +2165,7 @@ 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 */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 81a0c5b10..bdc20fa2b 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -94,7 +94,7 @@ tap CFBundleTypeName - Commodore Tape Image + Tape Image CFBundleTypeRole Viewer LSTypeIsPackage diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 1cf33e3ee..beb380dea 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -41,9 +41,8 @@ case StaticAnalyser::Target::Electron: return @"ElectronOptions"; case StaticAnalyser::Target::Vic20: return @"Vic20Options"; case StaticAnalyser::Target::Atari2600: return @"Atari2600Options"; + default: return nil; } - - return nil; } - (CSMachine *)newMachine @@ -53,6 +52,7 @@ case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; + default: return nil; } } diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 78647447e..9abbe86b1 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -27,6 +27,7 @@ // Tapes #include "../Storage/Tape/Formats/CommodoreTAP.hpp" +#include "../Storage/Tape/Formats/OricTAP.hpp" #include "../Storage/Tape/Formats/TapePRG.hpp" #include "../Storage/Tape/Formats/TapeUEF.hpp" @@ -34,7 +35,8 @@ typedef int TargetPlatformType; enum class TargetPlatform: TargetPlatformType { Acorn = 1 << 0, Atari2600 = 1 << 1, - Commodore = 1 << 2 + Commodore = 1 << 2, + Oric = 1 << 3 }; using namespace StaticAnalyser; @@ -104,7 +106,8 @@ std::list StaticAnalyser::GetTargets(const char *file_name) Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD - Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP + Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) + Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) #undef Format diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 94df52560..ca20b271a 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -33,7 +33,8 @@ struct Target { enum { Atari2600, Electron, - Vic20 + Vic20, + Oric } machine; float probability; diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp new file mode 100644 index 000000000..5a247f536 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -0,0 +1,171 @@ +// +// OricTAP.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "OricTAP.hpp" + +#include + +using namespace Storage::Tape; + +OricTAP::OricTAP(const char *file_name) : _file(NULL) +{ + struct stat file_stats; + stat(file_name, &file_stats); + _file_length = (size_t)file_stats.st_size; + + _file = fopen(file_name, "rb"); + + if(!_file) + throw ErrorNotOricTAP; + + // read and check the file signature + uint8_t signature[4]; + if(fread(signature, 1, 4, _file) != 4) + throw ErrorNotOricTAP; + + if(signature[0] != 0x16 || signature[1] != 0x16 || signature[2] != 0x16 || signature[3] != 0x24) + throw ErrorNotOricTAP; + + // then rewind and start again + virtual_reset(); +} + +OricTAP::~OricTAP() +{ + if(_file) fclose(_file); +} + +void OricTAP::virtual_reset() +{ + fseek(_file, 0, SEEK_SET); + _bit_count = 13; + _phase = LeadIn; + _phase_counter = 0; + _pulse_counter = 0; +} + +Tape::Pulse OricTAP::virtual_get_next_pulse() +{ + // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s. + if(_bit_count == 13) + { + if(_next_phase != _phase) + { + _phase = _next_phase; + _phase_counter = 0; + } + + _bit_count = 0; + uint8_t next_byte = 0; + switch(_phase) + { + case LeadIn: + next_byte = 0x16; + _phase_counter++; + if(_phase_counter == 259) // TODO + { + _next_phase = Header; + } + break; + + case Header: + next_byte = (uint8_t)fgetc(_file); + + // TODO + if(_phase_counter == 4) _body_length = next_byte; + if(_phase_counter == 6) _body_length |= (uint16_t)next_byte << 8; + + _phase_counter++; + if(_phase_counter == 10) // TODO + { + _next_phase = Pause; + } + break; + + case Data: + next_byte = (uint8_t)fgetc(_file); + _phase_counter++; + if(_phase_counter == _body_length) + { + _phase_counter = 0; + if((size_t)ftell(_file) == _file_length) + { + _next_phase = End; + } + else + { + _next_phase = LeadIn; + } + } + break; + + case Pause: + _phase_counter++; + if(_phase_counter == 2) + { + _phase_counter = 0; + _next_phase = Data; + } + break; + + case End: + break; + } + + // TODO: which way round are bytes streamed? + uint8_t parity = next_byte; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); // TODO: parity odd or even? + _current_value = (uint16_t)(((uint16_t)next_byte << 1) | (7 << 10) | ((parity&1) << 9)); + } + + // In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz. + // In fast mode, a 1 is a single period of 2400 Hz, a 0 is a 2400 Hz pulse followed by a 1200 Hz pulse. + // This code models fast mode. + Tape::Pulse pulse; + pulse.length.clock_rate = 4800; + + switch(_phase) + { + case Pause: + pulse.type = Pulse::High; // TODO + pulse.length.length = 20; // TODO + _bit_count = 13; + return pulse; + + case End: + pulse.type = Pulse::Zero; + pulse.length.length = 4800; + return pulse; + + default: + if(_current_value & 1) + { + pulse.length.length = 1; + } + else + { + pulse.length.length = _pulse_counter ? 2 : 1; + } + pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO + + _pulse_counter ^= 1; + if(!_pulse_counter) + { + _current_value >>= 1; + _bit_count++; + } + return pulse; + } +} + +bool OricTAP::is_at_end() +{ + return _phase == End; +} diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp new file mode 100644 index 000000000..a45153044 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -0,0 +1,59 @@ +// +// OricTAP.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef OricTAP_hpp +#define OricTAP_hpp + +#include "../Tape.hpp" +#include + +namespace Storage { +namespace Tape { + +/*! + Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. +*/ +class OricTAP: public Tape { + public: + /*! + Constructs an @c OricTAP containing content from the file with name @c file_name. + + @throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP. + */ + OricTAP(const char *file_name); + ~OricTAP(); + + enum { + ErrorNotOricTAP + }; + + // implemented to satisfy @c Tape + bool is_at_end(); + + private: + void virtual_reset(); + Pulse virtual_get_next_pulse(); + + FILE *_file; + size_t _file_length; + + uint16_t _current_value; + int _bit_count; + int _pulse_counter; + int _phase_counter; + + enum Phase { + LeadIn, Header, Pause, Data, End + } _phase, _next_phase; + uint16_t _body_length; +}; + +} +} + +#endif /* OricTAP_hpp */