mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #108 from davidgiven/scp
Add very beta support for scp import and export
This commit is contained in:
18
doc/using.md
18
doc/using.md
@@ -221,11 +221,19 @@ directory.
|
|||||||
file format in a non-backwards-compatible way; this tool will upgrade flux
|
file format in a non-backwards-compatible way; this tool will upgrade flux
|
||||||
files to the new format.
|
files to the new format.
|
||||||
|
|
||||||
- `fluxengine convert`: converts various formats to various other formats.
|
- `fluxengine convert`: converts flux files from various formats to various
|
||||||
You can use this to convert Catweasel or Supercard Pro flux files to
|
other formats. You can use this to convert Catweasel flux files to
|
||||||
FluxEngine's native format, for flux files to various other formats useful
|
FluxEngine's native format, FluxEngine flux files to various other formats
|
||||||
for debugging (including VCD which can be loaded into
|
useful for debugging (including VCD which can be loaded into
|
||||||
[sigrok](http://sigrok.org)).
|
[sigrok](http://sigrok.org)), and bidirectional conversion to and from
|
||||||
|
Supercard Pro `.scp` format.
|
||||||
|
|
||||||
|
**Important SCP note:** import (`fluxengine convert scptoflux`) should be
|
||||||
|
fairly robust, but export (`fluxengine convert fluxtoscp`) should only be
|
||||||
|
done with great caution as FluxEngine files contain features which can't be
|
||||||
|
represented very well in `.scp` format and they're probably pretty dubious.
|
||||||
|
As ever, please [get in
|
||||||
|
touch](https://github.com/davidgiven/fluxengine/issues/new) with any reports.
|
||||||
|
|
||||||
Commands which normally take `--source` or `--dest` get a sensible default if
|
Commands which normally take `--source` or `--dest` get a sensible default if
|
||||||
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
|
left unspecified. `fluxengine read ibm` on its own will read drive 0 and
|
||||||
|
|||||||
88
lib/flags.cc
88
lib/flags.cc
@@ -29,7 +29,7 @@ void FlagGroup::addFlag(Flag* flag)
|
|||||||
_flags.push_back(flag);
|
_flags.push_back(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlagGroup::parseFlags(int argc, const char* argv[])
|
std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
if (_initialised)
|
if (_initialised)
|
||||||
throw std::runtime_error("called parseFlags() twice");
|
throw std::runtime_error("called parseFlags() twice");
|
||||||
@@ -66,6 +66,7 @@ void FlagGroup::parseFlags(int argc, const char* argv[])
|
|||||||
|
|
||||||
/* Now actually parse them. */
|
/* Now actually parse them. */
|
||||||
|
|
||||||
|
std::vector<std::string> filenames;
|
||||||
int index = 1;
|
int index = 1;
|
||||||
while (index < argc)
|
while (index < argc)
|
||||||
{
|
{
|
||||||
@@ -76,52 +77,73 @@ void FlagGroup::parseFlags(int argc, const char* argv[])
|
|||||||
std::string value;
|
std::string value;
|
||||||
bool usesthat = false;
|
bool usesthat = false;
|
||||||
|
|
||||||
if ((thisarg.size() == 0) || (thisarg[0] != '-'))
|
if (thisarg.size() == 0)
|
||||||
Error() << "non-option parameter " << thisarg << " seen (try --help)";
|
|
||||||
if ((thisarg.size() > 1) && (thisarg[1] == '-'))
|
|
||||||
{
|
{
|
||||||
/* Long option. */
|
/* Ignore this argument. */
|
||||||
|
}
|
||||||
auto equals = thisarg.rfind('=');
|
else if (thisarg[0] != '-')
|
||||||
if (equals != std::string::npos)
|
{
|
||||||
{
|
/* This is a filename. */
|
||||||
key = thisarg.substr(0, equals);
|
filenames.push_back(thisarg);
|
||||||
value = thisarg.substr(equals+1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
key = thisarg;
|
|
||||||
value = thatarg;
|
|
||||||
usesthat = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Short option. */
|
/* This is a flag. */
|
||||||
|
|
||||||
if (thisarg.size() > 2)
|
if ((thisarg.size() > 1) && (thisarg[1] == '-'))
|
||||||
{
|
{
|
||||||
key = thisarg.substr(0, 2);
|
/* Long option. */
|
||||||
value = thisarg.substr(2);
|
|
||||||
|
auto equals = thisarg.rfind('=');
|
||||||
|
if (equals != std::string::npos)
|
||||||
|
{
|
||||||
|
key = thisarg.substr(0, equals);
|
||||||
|
value = thisarg.substr(equals+1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
key = thisarg;
|
||||||
|
value = thatarg;
|
||||||
|
usesthat = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
key = thisarg;
|
/* Short option. */
|
||||||
value = thatarg;
|
|
||||||
usesthat = true;
|
if (thisarg.size() > 2)
|
||||||
|
{
|
||||||
|
key = thisarg.substr(0, 2);
|
||||||
|
value = thisarg.substr(2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
key = thisarg;
|
||||||
|
value = thatarg;
|
||||||
|
usesthat = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto flag = flags_by_name.find(key);
|
||||||
|
if (flag == flags_by_name.end())
|
||||||
|
Error() << "unknown flag '" << key << "'; try --help";
|
||||||
|
|
||||||
|
flag->second->set(value);
|
||||||
|
if (usesthat && flag->second->hasArgument())
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto flag = flags_by_name.find(key);
|
|
||||||
if (flag == flags_by_name.end())
|
|
||||||
Error() << "unknown flag '" << key << "'; try --help";
|
|
||||||
|
|
||||||
flag->second->set(value);
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
if (usesthat && flag->second->hasArgument())
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filenames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlagGroup::parseFlags(int argc, const char* argv[])
|
||||||
|
{
|
||||||
|
auto filenames = parseFlagsWithFilenames(argc, argv);
|
||||||
|
if (!filenames.empty())
|
||||||
|
Error() << "non-option parameter " << *filenames.begin() << " seen (try --help)";
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlagGroup::checkInitialised() const
|
void FlagGroup::checkInitialised() const
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
void parseFlags(int argc, const char* argv[]);
|
void parseFlags(int argc, const char* argv[]);
|
||||||
|
std::vector<std::string> parseFlagsWithFilenames(int argc, const char* argv[]);
|
||||||
void addFlag(Flag* flag);
|
void addFlag(Flag* flag);
|
||||||
void checkInitialised() const;
|
void checkInitialised() const;
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ buildlibrary libfrontend.a \
|
|||||||
src/fe-cwftoflux.cc \
|
src/fe-cwftoflux.cc \
|
||||||
src/fe-erase.cc \
|
src/fe-erase.cc \
|
||||||
src/fe-fluxtoau.cc \
|
src/fe-fluxtoau.cc \
|
||||||
|
src/fe-fluxtoscp.cc \
|
||||||
src/fe-fluxtovcd.cc \
|
src/fe-fluxtovcd.cc \
|
||||||
src/fe-inspect.cc \
|
src/fe-inspect.cc \
|
||||||
src/fe-readadfs.cc \
|
src/fe-readadfs.cc \
|
||||||
|
|||||||
188
src/fe-fluxtoscp.cc
Normal file
188
src/fe-fluxtoscp.cc
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#include "globals.h"
|
||||||
|
#include "flags.h"
|
||||||
|
#include "fluxmap.h"
|
||||||
|
#include "sql.h"
|
||||||
|
#include "bytes.h"
|
||||||
|
#include "protocol.h"
|
||||||
|
#include "dataspec.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
#include "decoders/fluxmapreader.h"
|
||||||
|
#include "scp.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
static FlagGroup flags { };
|
||||||
|
|
||||||
|
static SettableFlag fortyTrackMode(
|
||||||
|
{ "--48", "-4" },
|
||||||
|
"set 48 tpi mode; only every other physical track is emitted"
|
||||||
|
);
|
||||||
|
|
||||||
|
static SettableFlag singleSided(
|
||||||
|
{ "--single-sided", "-s" },
|
||||||
|
"only emit side 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
static IntFlag diskType(
|
||||||
|
{ "--disk-type" },
|
||||||
|
"sets the SCP disk type byte",
|
||||||
|
0xff
|
||||||
|
);
|
||||||
|
|
||||||
|
static sqlite3* inputDb;
|
||||||
|
|
||||||
|
static void syntax()
|
||||||
|
{
|
||||||
|
std::cout << "Syntax: fluxengine convert fluxtoscp <fluxfile> <scpfile>\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_le32(uint8_t dest[4], uint32_t v)
|
||||||
|
{
|
||||||
|
dest[0] = v;
|
||||||
|
dest[1] = v >> 8;
|
||||||
|
dest[2] = v >> 16;
|
||||||
|
dest[3] = v >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int strackno(int track, int side)
|
||||||
|
{
|
||||||
|
if (fortyTrackMode)
|
||||||
|
track /= 2;
|
||||||
|
if (singleSided)
|
||||||
|
return track;
|
||||||
|
else
|
||||||
|
return (track << 1) | side;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mainConvertFluxToScp(int argc, const char* argv[])
|
||||||
|
{
|
||||||
|
auto filenames = flags.parseFlagsWithFilenames(argc, argv);
|
||||||
|
if (filenames.size() != 2)
|
||||||
|
syntax();
|
||||||
|
|
||||||
|
inputDb = sqlOpen(filenames[0], SQLITE_OPEN_READONLY);
|
||||||
|
auto tracks = sqlFindFlux(inputDb);
|
||||||
|
|
||||||
|
int maxTrack = 0;
|
||||||
|
int maxSide = 0;
|
||||||
|
for (auto p : tracks)
|
||||||
|
{
|
||||||
|
if (singleSided && (p.second == 1))
|
||||||
|
continue;
|
||||||
|
maxTrack = std::max(maxTrack, (int)p.first);
|
||||||
|
maxSide = std::max(maxSide, (int)p.second);
|
||||||
|
}
|
||||||
|
int maxStrack = strackno(maxTrack, maxSide);
|
||||||
|
|
||||||
|
std::cout << fmt::format("Writing {} {} SCP file containing {} SCP tracks\n",
|
||||||
|
fortyTrackMode ? "48 tpi" : "96 tpi",
|
||||||
|
singleSided ? "single sided" : "double sided",
|
||||||
|
maxStrack + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
ScpHeader fileheader = {0};
|
||||||
|
fileheader.file_id[0] = 'S';
|
||||||
|
fileheader.file_id[1] = 'C';
|
||||||
|
fileheader.file_id[2] = 'P';
|
||||||
|
fileheader.file_id[3] = 0x18; /* Version 1.8 of the spec */
|
||||||
|
fileheader.type = diskType;
|
||||||
|
fileheader.revolutions = 5;
|
||||||
|
fileheader.start_track = 0;
|
||||||
|
fileheader.end_track = maxStrack;
|
||||||
|
fileheader.flags = SCP_FLAG_INDEXED | (fortyTrackMode ? 0 : SCP_FLAG_96TPI);
|
||||||
|
fileheader.cell_width = 0;
|
||||||
|
fileheader.heads = singleSided ? 1 : 0;
|
||||||
|
|
||||||
|
Bytes trackdata;
|
||||||
|
ByteWriter trackdataWriter(trackdata);
|
||||||
|
|
||||||
|
int trackstep = 1 + fortyTrackMode;
|
||||||
|
int maxside = singleSided ? 0 : 1;
|
||||||
|
for (int track = 0; track <= maxTrack; track += trackstep)
|
||||||
|
{
|
||||||
|
for (int side = 0; side <= maxside; side++)
|
||||||
|
{
|
||||||
|
int strack = strackno(track, side);
|
||||||
|
std::cout << fmt::format("FE track {}.{}, SCP track {}: ", track, side, strack) << std::flush;
|
||||||
|
|
||||||
|
auto fluxmap = sqlReadFlux(inputDb, track, side);
|
||||||
|
ScpTrack trackheader = {0};
|
||||||
|
trackheader.track_id[0] = 'T';
|
||||||
|
trackheader.track_id[1] = 'R';
|
||||||
|
trackheader.track_id[2] = 'K';
|
||||||
|
trackheader.strack = strack;
|
||||||
|
|
||||||
|
FluxmapReader fmr(*fluxmap);
|
||||||
|
Bytes fluxdata;
|
||||||
|
ByteWriter fluxdataWriter(fluxdata);
|
||||||
|
|
||||||
|
int revolution = 0;
|
||||||
|
unsigned revTicks = 0;
|
||||||
|
unsigned totalTicks = 0;
|
||||||
|
unsigned ticksSinceLastPulse = 0;
|
||||||
|
uint32_t startOffset = 0;
|
||||||
|
while (revolution < 5)
|
||||||
|
{
|
||||||
|
unsigned ticks;
|
||||||
|
int opcode = fmr.readOpcode(ticks);
|
||||||
|
if (ticks)
|
||||||
|
{
|
||||||
|
ticksSinceLastPulse += ticks;
|
||||||
|
totalTicks += ticks;
|
||||||
|
revTicks += ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (opcode)
|
||||||
|
{
|
||||||
|
case -1: /* end of flux, treat like an index marker */
|
||||||
|
case F_OP_INDEX:
|
||||||
|
{
|
||||||
|
auto* revheader = &trackheader.revolution[revolution];
|
||||||
|
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
|
||||||
|
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
|
||||||
|
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
|
||||||
|
revolution++;
|
||||||
|
revheader++;
|
||||||
|
revTicks = 0;
|
||||||
|
startOffset = fluxdataWriter.pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case F_OP_PULSE:
|
||||||
|
{
|
||||||
|
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
|
||||||
|
while (t >= 0x10000)
|
||||||
|
{
|
||||||
|
fluxdataWriter.write_be16(0);
|
||||||
|
t -= 0x10000;
|
||||||
|
}
|
||||||
|
fluxdataWriter.write_be16(t);
|
||||||
|
ticksSinceLastPulse = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_le32(fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
|
||||||
|
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
|
||||||
|
trackdataWriter += fluxdata;
|
||||||
|
|
||||||
|
std::cout << fmt::format("{} ms in {} bytes\n",
|
||||||
|
totalTicks * MS_PER_TICK,
|
||||||
|
fluxdata.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlClose(inputDb);
|
||||||
|
|
||||||
|
std::cout << "Writing output file...\n";
|
||||||
|
std::ofstream of(filenames[1], std::ios::out | std::ios::binary);
|
||||||
|
if (!of.is_open())
|
||||||
|
Error() << "cannot open output file";
|
||||||
|
of.write((const char*) &fileheader, sizeof(fileheader));
|
||||||
|
of.write((const char*) trackdata.begin(), trackdata.size());
|
||||||
|
of.close();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -4,37 +4,9 @@
|
|||||||
#include "bytes.h"
|
#include "bytes.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
#include "scp.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
struct ScpHeader
|
|
||||||
{
|
|
||||||
char file_id[3]; // file ID - 'SCP'
|
|
||||||
uint8_t version; // major/minor in nibbles
|
|
||||||
uint8_t type; // disk type - subclass/class in nibbles
|
|
||||||
uint8_t revolutions; // up to 5
|
|
||||||
uint8_t start_track; // 0..165
|
|
||||||
uint8_t end_track; // 0..165
|
|
||||||
uint8_t flags; // see below
|
|
||||||
uint8_t cell_width; // in bits, 0 meaning 16
|
|
||||||
uint8_t heads; // 0 = both, 1 = side 0 only, 2 = side 1 only
|
|
||||||
uint8_t resolution; // 25ns * (resolution+1)
|
|
||||||
uint8_t checksum[4]; // of data after this point
|
|
||||||
uint8_t track[165][4]; // track offsets, not necessarily 165
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ScpTrack
|
|
||||||
{
|
|
||||||
char track_id[3]; // 'TRK'
|
|
||||||
uint8_t strack; // SCP track number
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
uint8_t index[4]; // time for one revolution
|
|
||||||
uint8_t length[4]; // number of bitcells
|
|
||||||
uint8_t offset[4]; // offset to bitcell data, relative to track header
|
|
||||||
}
|
|
||||||
revolution[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::ifstream inputFile;
|
static std::ifstream inputFile;
|
||||||
static sqlite3* outputDb;
|
static sqlite3* outputDb;
|
||||||
static ScpHeader header;
|
static ScpHeader header;
|
||||||
@@ -109,6 +81,7 @@ static void read_track(int strack)
|
|||||||
|
|
||||||
Fluxmap fluxmap;
|
Fluxmap fluxmap;
|
||||||
nanoseconds_t pending = 0;
|
nanoseconds_t pending = 0;
|
||||||
|
unsigned inputBytes = 0;
|
||||||
for (int revolution = 0; revolution < header.revolutions; revolution++)
|
for (int revolution = 0; revolution < header.revolutions; revolution++)
|
||||||
{
|
{
|
||||||
if (revolution != 0)
|
if (revolution != 0)
|
||||||
@@ -133,12 +106,14 @@ static void read_track(int strack)
|
|||||||
pending = 0;
|
pending = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
pending += interval;
|
pending += 0x10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputBytes += datalength*2;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << fmt::format(" {} ms in {} output bytes\n",
|
std::cout << fmt::format(" {} ms in {} input bytes and {} output bytes\n",
|
||||||
fluxmap.duration() / 1e6, fluxmap.bytes());
|
fluxmap.duration() / 1e6, inputBytes, fluxmap.bytes());
|
||||||
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
|
sqlWriteFlux(outputDb, trackno(strack), headno(strack), fluxmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ typedef int command_cb(int agrc, const char* argv[]);
|
|||||||
extern command_cb mainErase;
|
extern command_cb mainErase;
|
||||||
extern command_cb mainConvertCwfToFlux;
|
extern command_cb mainConvertCwfToFlux;
|
||||||
extern command_cb mainConvertFluxToAu;
|
extern command_cb mainConvertFluxToAu;
|
||||||
|
extern command_cb mainConvertFluxToScp;
|
||||||
extern command_cb mainConvertFluxToVcd;
|
extern command_cb mainConvertFluxToVcd;
|
||||||
extern command_cb mainConvertScpToFlux;
|
extern command_cb mainConvertScpToFlux;
|
||||||
extern command_cb mainInspect;
|
extern command_cb mainInspect;
|
||||||
@@ -86,6 +87,7 @@ static std::vector<Command> convertables =
|
|||||||
{ "cwftoflux", mainConvertCwfToFlux, "Converts CatWeasel stream files to flux.", },
|
{ "cwftoflux", mainConvertCwfToFlux, "Converts CatWeasel stream files to flux.", },
|
||||||
{ "scptoflux", mainConvertScpToFlux, "Converts Supercard Pro stream files to flux.", },
|
{ "scptoflux", mainConvertScpToFlux, "Converts Supercard Pro stream files to flux.", },
|
||||||
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
|
{ "fluxtoau", mainConvertFluxToAu, "Converts (one track of a) flux file to an .au audio file.", },
|
||||||
|
{ "fluxtoscp", mainConvertFluxToScp, "Converrt a flux file to a Supercard Pro file.", },
|
||||||
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
|
{ "fluxtovcd", mainConvertFluxToVcd, "Converts (one track of a) flux file to a VCD file.", },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
43
src/scp.h
Normal file
43
src/scp.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef SCP_H
|
||||||
|
#define SCP_H
|
||||||
|
|
||||||
|
struct ScpHeader
|
||||||
|
{
|
||||||
|
char file_id[3]; // file ID - 'SCP'
|
||||||
|
uint8_t version; // major/minor in nibbles
|
||||||
|
uint8_t type; // disk type - subclass/class in nibbles
|
||||||
|
uint8_t revolutions; // up to 5
|
||||||
|
uint8_t start_track; // 0..165
|
||||||
|
uint8_t end_track; // 0..165
|
||||||
|
uint8_t flags; // see below
|
||||||
|
uint8_t cell_width; // in bits, 0 meaning 16
|
||||||
|
uint8_t heads; // 0 = both, 1 = side 0 only, 2 = side 1 only
|
||||||
|
uint8_t resolution; // 25ns * (resolution+1)
|
||||||
|
uint8_t checksum[4]; // of data after this point
|
||||||
|
uint8_t track[165][4]; // track offsets, not necessarily 165
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SCP_FLAG_INDEXED = (1<<0),
|
||||||
|
SCP_FLAG_96TPI = (1<<1),
|
||||||
|
SCP_FLAG_360RPM = (1<<2),
|
||||||
|
SCP_FLAG_NORMALIZED = (1<<3),
|
||||||
|
SCP_FLAG_READWRITE = (1<<4),
|
||||||
|
SCP_FLAG_FOOTER = (1<<5)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScpTrack
|
||||||
|
{
|
||||||
|
char track_id[3]; // 'TRK'
|
||||||
|
uint8_t strack; // SCP track number
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t index[4]; // time for one revolution
|
||||||
|
uint8_t length[4]; // number of bitcells
|
||||||
|
uint8_t offset[4]; // offset to bitcell data, relative to track header
|
||||||
|
}
|
||||||
|
revolution[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user