mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-23 20:29:42 +00:00
It's a bit of a mess but this is probably close to appropriate for Oric TAP files.
This commit is contained in:
parent
00e3ad9b04
commit
df01c78039
@ -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 = "<group>"; };
|
||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
|
||||
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
|
||||
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
|
||||
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
|
||||
@ -1138,6 +1141,8 @@
|
||||
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */,
|
||||
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */,
|
||||
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */,
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */,
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -94,7 +94,7 @@
|
||||
<string>tap</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Commodore Tape Image</string>
|
||||
<string>Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Target> 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
|
||||
|
@ -33,7 +33,8 @@ struct Target {
|
||||
enum {
|
||||
Atari2600,
|
||||
Electron,
|
||||
Vic20
|
||||
Vic20,
|
||||
Oric
|
||||
} machine;
|
||||
float probability;
|
||||
|
||||
|
171
Storage/Tape/Formats/OricTAP.cpp
Normal file
171
Storage/Tape/Formats/OricTAP.cpp
Normal file
@ -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 <sys/stat.h>
|
||||
|
||||
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;
|
||||
}
|
59
Storage/Tape/Formats/OricTAP.hpp
Normal file
59
Storage/Tape/Formats/OricTAP.hpp
Normal file
@ -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 <stdint.h>
|
||||
|
||||
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 */
|
Loading…
Reference in New Issue
Block a user