mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #661 from davidgiven/scp
Fix reading 48tpi SCP files.
This commit is contained in:
1
Makefile
1
Makefile
@@ -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 \
|
||||
|
||||
@@ -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
56
arch/rolandd20/decoder.cc
Normal 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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
4
arch/rolandd20/rolandd20.h
Normal file
4
arch/rolandd20/rolandd20.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config);
|
||||
|
||||
5
arch/rolandd20/rolandd20.proto
Normal file
5
arch/rolandd20/rolandd20.proto
Normal file
@@ -0,0 +1,5 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message RolandD20DecoderProto {}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ FORMATS = \
|
||||
northstar350 \
|
||||
northstar87 \
|
||||
psos800 \
|
||||
rolandd20 \
|
||||
rx50 \
|
||||
shugart_drive \
|
||||
smaky6 \
|
||||
|
||||
28
src/formats/rolandd20.textpb
Normal file
28
src/formats/rolandd20.textpb
Normal 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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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">"Don't decode" "MFM / M²FM / FM"</property>
|
||||
<property name="choices">"Unencoded bits" "Unencoded bytes" "MFM / M²FM / FM"</property>
|
||||
<property name="close_button">1</property>
|
||||
<property name="context_help"></property>
|
||||
<property name="context_menu">1</property>
|
||||
|
||||
@@ -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!
|
||||
|
||||
Reference in New Issue
Block a user