Merge pull request #661 from davidgiven/scp

Fix reading 48tpi SCP files.
This commit is contained in:
David Given
2023-04-06 23:51:30 +02:00
committed by GitHub
15 changed files with 222 additions and 91 deletions

View File

@@ -111,6 +111,7 @@ PROTOS = \
arch/micropolis/micropolis.proto \
arch/mx/mx.proto \
arch/northstar/northstar.proto \
arch/rolandd20/rolandd20.proto \
arch/smaky6/smaky6.proto \
arch/tids990/tids990.proto \
arch/victor9k/victor9k.proto \

View File

@@ -24,6 +24,7 @@ LIBARCH_SRCS = \
arch/mx/decoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/rolandd20/decoder.cc \
arch/smaky6/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \

56
arch/rolandd20/decoder.cc Normal file
View File

@@ -0,0 +1,56 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/crc.h"
#include "lib/fluxmap.h"
#include "lib/decoders/fluxmapreader.h"
#include "lib/sector.h"
#include "lib/bytes.h"
#include "rolandd20.h"
#include <string.h>
/* Sector header record:
*
* BF FF FF FF FF FF FE AB
*
* This encodes to:
*
* e d 5 5 5 5 5 5
* 1110 1101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 4 4 4 4 5
* 0101 0101 0101 0100 0100 0100 0100 0101
*/
static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL);
class RolandD20Decoder : public Decoder
{
public:
RolandD20Decoder(const DecoderProto& config):
Decoder(config)
{}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord() override
{
auto rawbits = readRawBits(256);
const auto& bytes = decodeFmMfm(rawbits);
fmt::print("{} ", _sector->clock);
hexdump(std::cout, bytes);
}
};
std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
}

View File

@@ -0,0 +1,4 @@
#pragma once
extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config);

View File

@@ -0,0 +1,5 @@
syntax = "proto2";
message RolandD20DecoderProto {}

View File

@@ -55,6 +55,7 @@ proto_cc_library {
"./arch/micropolis/micropolis.proto",
"./arch/mx/mx.proto",
"./arch/northstar/northstar.proto",
"./arch/rolandd20/rolandd20.proto",
"./arch/tids990/tids990.proto",
"./arch/victor9k/victor9k.proto",
"./arch/zilogmcz/zilogmcz.proto",
@@ -93,6 +94,7 @@ clibrary {
"./arch/mx/decoder.cc",
"./arch/northstar/decoder.cc",
"./arch/northstar/encoder.cc",
"./arch/rolandd20/rolandd20.cc",
"./arch/tids990/decoder.cc",
"./arch/tids990/encoder.cc",
"./arch/victor9k/decoder.cc",

View File

@@ -16,6 +16,7 @@
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/rolandd20/rolandd20.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
@@ -49,6 +50,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kRolandd20, createRolandD20Decoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },

View File

@@ -13,6 +13,7 @@ import "arch/macintosh/macintosh.proto";
import "arch/micropolis/micropolis.proto";
import "arch/mx/mx.proto";
import "arch/northstar/northstar.proto";
import "arch/rolandd20/rolandd20.proto";
import "arch/smaky6/smaky6.proto";
import "arch/tids990/tids990.proto";
import "arch/victor9k/victor9k.proto";
@@ -20,7 +21,7 @@ import "arch/zilogmcz/zilogmcz.proto";
import "lib/fluxsink/fluxsink.proto";
import "lib/common.proto";
//NEXT: 31
//NEXT: 32
message DecoderProto {
optional double pulse_debounce_threshold = 1 [default = 0.30,
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
@@ -47,6 +48,7 @@ message DecoderProto {
MicropolisDecoderProto micropolis = 14;
MxDecoderProto mx = 15;
NorthstarDecoderProto northstar = 24;
RolandD20DecoderProto rolandd20 = 31;
Smaky6DecoderProto smaky6 = 30;
Tids990DecoderProto tids990 = 16;
Victor9kDecoderProto victor9k = 17;

View File

@@ -21,122 +21,130 @@ static int headno(int strack)
static int strackno(int track, int side)
{
return (track << 1) | side;
return (track << 1) | side;
}
class ScpFluxSource : public TrivialFluxSource
{
public:
ScpFluxSource(const ScpFluxSourceProto& config):
_config(config)
ScpFluxSource(const ScpFluxSourceProto& config): _config(config)
{
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}", _config.filename(), strerror(errno));
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}",
_config.filename(),
strerror(errno));
_if.read((char*) &_header, sizeof(_header));
check_for_error();
_if.read((char*)&_header, sizeof(_header));
check_for_error();
if ((_header.file_id[0] != 'S')
|| (_header.file_id[1] != 'C')
|| (_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
if ((_header.file_id[0] != 'S') || (_header.file_id[1] != 'C') ||
(_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
::config.set_tpi((_header.flags & SCP_FLAG_96TPI) ? 96 : 48);
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
trackno(_header.start_track), trackno(_header.end_track), startSide, endSide);
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
trackno(_header.start_track),
trackno(_header.end_track),
startSide,
endSide);
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
public:
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*) &trackheader, sizeof(trackheader));
check_for_error();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*)&trackheader, sizeof(trackheader));
check_for_error();
if ((trackheader.track_id[0] != 'T')
|| (trackheader.track_id[1] != 'R')
|| (trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
if ((trackheader.track_id[0] != 'T') ||
(trackheader.track_id[1] != 'R') ||
(trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*) &trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*)&trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
uint32_t datalength = Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset = Bytes(revs[revolution].offset, 4).reader().read_le32();
uint32_t datalength =
Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset =
Bytes(revs[revolution].offset, 4).reader().read_le32();
Bytes data(datalength*2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*) data.begin(), data.size());
check_for_error();
Bytes data(datalength * 2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*)data.begin(), data.size());
check_for_error();
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval((interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval(
(interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
inputBytes += datalength*2;
}
inputBytes += datalength * 2;
}
return fluxmap;
return fluxmap;
}
void recalibrate() {}
private:
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
private:
const ScpFluxSourceProto& _config;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
};
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(const ScpFluxSourceProto& config)
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(
const ScpFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new ScpFluxSource(config));
}

View File

@@ -64,6 +64,7 @@ FORMATS = \
northstar350 \
northstar87 \
psos800 \
rolandd20 \
rx50 \
shugart_drive \
smaky6 \

View File

@@ -0,0 +1,28 @@
comment: 'Roland D20'
image_writer {
filename: "rolandd20.img"
type: IMG
}
layout {
tracks: 78
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
decoder {
brother {}
}
drive {
head_bias: 1
}

View File

@@ -8,6 +8,23 @@
#include "layout.h"
#include "jobqueue.h"
static Bytes fakeBits(const std::vector<bool>& bits)
{
Bytes result;
ByteWriter bw(result);
auto it = bits.begin();
while (it != bits.end())
{
uint8_t b = (*it++) << 4;
if (it != bits.end())
b |= *it++;
bw.write_8(b);
}
return result;
}
class ExplorerPanelImpl :
public ExplorerPanelGen,
public ExplorerPanel,
@@ -175,11 +192,15 @@ private:
Bytes bytes;
switch (explorerDecodeChoice->GetSelection())
{
case 0:
case 0:
bytes = fakeBits(bits);
break;
case 1:
bytes = toBytes(bits);
break;
case 1:
case 2:
bytes = decodeFmMfm(bits.begin(), bits.end());
break;
}

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-234-gd93c9fc0-dirty)
// C++ code generated with wxFormBuilder (version 3.10.1-1fa5400)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@@ -937,7 +937,7 @@ ExplorerPanelGen::ExplorerPanelGen( wxWindow* parent, wxWindowID id, const wxPoi
fgSizer121->Add( m_staticText24, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
guessButton = new wxButton( this, wxID_ANY, wxT("Guess"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
fgSizer121->Add( guessButton, 0, wxALIGN_BOTTOM|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALIGN_TOP, 5 );
fgSizer121->Add( guessButton, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT, 5 );
fgSizer10->Add( fgSizer121, 1, wxEXPAND, 5 );
@@ -957,7 +957,7 @@ ExplorerPanelGen::ExplorerPanelGen( wxWindow* parent, wxWindowID id, const wxPoi
m_staticText27->Wrap( -1 );
fgSizer10->Add( m_staticText27, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
wxString explorerDecodeChoiceChoices[] = { wxT("Don't decode"), wxT("MFM / M²FM / FM") };
wxString explorerDecodeChoiceChoices[] = { wxT("Unencoded bits"), wxT("Unencoded bytes"), wxT("MFM / M²FM / FM") };
int explorerDecodeChoiceNChoices = sizeof( explorerDecodeChoiceChoices ) / sizeof( wxString );
explorerDecodeChoice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, explorerDecodeChoiceNChoices, explorerDecodeChoiceChoices, 0 );
explorerDecodeChoice->SetSelection( 0 );

View File

@@ -5387,7 +5387,7 @@
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALIGN_BOTTOM|wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALIGN_TOP</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="1">
<property name="BottomDockable">1</property>
@@ -5734,7 +5734,7 @@
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="choices">&quot;Don&apos;t decode&quot; &quot;MFM / M²FM / FM&quot;</property>
<property name="choices">&quot;Unencoded bits&quot; &quot;Unencoded bytes&quot; &quot;MFM / M²FM / FM&quot;</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-234-gd93c9fc0-dirty)
// C++ code generated with wxFormBuilder (version 3.10.1-1fa5400)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!