Merge pull request #617 from davidgiven/smaky

Add support for the Smaky 6.
This commit is contained in:
David Given
2022-11-27 01:00:21 +01:00
committed by GitHub
25 changed files with 2232 additions and 361 deletions

View File

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

View File

@@ -116,6 +116,7 @@ people who've had it work).
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
| [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }

View File

@@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
static Bytes reverse_bits(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : input)
bw.write_8(reverse_bits(b));
return output;
}
class AesLanierDecoder : public Decoder
{
public:
@@ -43,7 +33,7 @@ public:
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
const auto& reversed = reverse_bits(bytes);
const auto& reversed = bytes.reverseBits();
_sector->logicalTrack = reversed[1];
_sector->logicalSide = 0;

View File

@@ -1,5 +1,7 @@
LIBARCH_SRCS = \
arch/aeslanier/decoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
arch/amiga/amiga.cc \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
@@ -16,18 +18,17 @@ LIBARCH_SRCS = \
arch/ibm/encoder.cc \
arch/macintosh/decoder.cc \
arch/macintosh/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/mx/decoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/smaky6/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/victor9k/decoder.cc \
arch/victor9k/encoder.cc \
arch/zilogmcz/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS))
OBJS += $(LIBARCH_OBJS)

154
arch/smaky6/decoder.cc Normal file
View File

@@ -0,0 +1,154 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "protocol.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "smaky6.h"
#include "bytes.h"
#include "crc.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
#include <string.h>
#include <algorithm>
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
class Smaky6Decoder : public Decoder
{
public:
Smaky6Decoder(const DecoderProto& config):
Decoder(config),
_config(config.smaky6())
{
}
private:
/* Returns the sector ID of the _current_ sector. */
int advanceToNextSector()
{
auto previous = tell();
seekToIndexMark();
auto now = tell();
if ((now.ns() - previous.ns()) < 9e6)
{
seekToIndexMark();
auto next = tell();
if ((next.ns() - now.ns()) < 9e6)
{
/* We just found sector 0. */
_sectorId = 0;
}
else
{
/* Spurious... */
seek(now);
}
}
return _sectorId++;
}
public:
void beginTrack() override
{
/* Find the start-of-track index marks, which will be an interval
* of about 6ms. */
seekToIndexMark();
_sectorId = 99;
for (;;)
{
auto pos = tell();
advanceToNextSector();
if (_sectorId < 99)
{
seek(pos);
break;
}
if (eof())
return;
}
/* Now we know where to start counting, start finding sectors. */
_sectorStarts.clear();
for (;;)
{
auto now = tell();
if (eof())
break;
int id = advanceToNextSector();
if (id < 16)
_sectorStarts.push_back(std::make_pair(id, now));
}
_sectorIndex = 0;
}
nanoseconds_t advanceToNextRecord() override
{
if (_sectorIndex == _sectorStarts.size())
{
seekToIndexMark();
return 0;
}
const auto& p = _sectorStarts[_sectorIndex++];
_sectorId = p.first;
seek(p.second);
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
return clock;
}
void decodeSectorRecord() override
{
readRawBits(33);
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
return;
const auto& rawbytes =
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
/* The Smaky bytes are stored backwards! Backwards! */
const auto& bytes =
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
ByteReader br(bytes);
uint8_t track = br.read_8();
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
uint8_t wantedChecksum = br.read_8();
uint8_t gotChecksum = sumBytes(data) & 0xff;
if (track != _sector->physicalTrack)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = _sectorId;
_sector->data = data;
_sector->status =
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const Smaky6DecoderProto& _config;
nanoseconds_t _startOfTrack;
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
int _sectorId;
int _sectorIndex;
};
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
}

10
arch/smaky6/smaky6.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SMAKY6_H
#define SMAKY6_H
#define SMAKY6_SECTOR_SIZE 256
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
#endif

6
arch/smaky6/smaky6.proto Normal file
View File

@@ -0,0 +1,6 @@
syntax = "proto2";
import "lib/common.proto";
message Smaky6DecoderProto {}

33
doc/disk-smaky6.md Normal file
View File

@@ -0,0 +1,33 @@
Disk: Smaky 6
=============
The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based
around a Z80 processor and has one or two Micropolis 5.25" drives which use
16-sector hard sectored disks. The disk format is single-sided with 77 tracks
and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom
sector record scheme. It was later superceded by a 68000-based Smaky which used
different disks.
FluxEngine supports these, although because the Micropolis drives use a 100tpi
track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You
will have to find a 100tpi drive from somewhere (they're rare).
Reading disks
-------------
You must use a 100-tpi 80-track 5.25" floppy drive.
To read a Smaky 6 floppy, do:
```
fluxengine read smaky6
```
You should end up with a `smaky6.img` file.
Useful references
-----------------
- [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo)

View File

@@ -205,6 +205,16 @@ std::vector<bool> Bytes::toBits() const
return bits;
}
Bytes Bytes::reverseBits() const
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : *this)
bw.write_8(reverse_bits(b));
return output;
}
uint8_t toByte(
std::vector<bool>::const_iterator start,
std::vector<bool>::const_iterator end)

View File

@@ -62,6 +62,7 @@ public:
Bytes compress() const;
Bytes decompress() const;
std::vector<bool> toBits() const;
Bytes reverseBits() const;
ByteReader reader() const;
ByteWriter writer();

View File

@@ -1,223 +1,226 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->position = start.bytes;
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}

View File

@@ -71,6 +71,11 @@ public:
return _fmr->tell();
}
void rewind()
{
_fmr->rewind();
}
void seek(const Fluxmap::Position& pos)
{
return _fmr->seek(pos);

View File

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

View File

@@ -10,32 +10,32 @@ class TrackInfo;
struct Record
{
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
Bytes rawData;
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
uint32_t position = 0;
Bytes rawData;
};
struct TrackDataFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
};
struct TrackFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
};
struct DiskFlux
{
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
};
#endif

View File

@@ -63,6 +63,7 @@ FORMATS = \
northstar87 \
rx50 \
shugart_drive \
smaky6 \
tids990 \
victor9k_ds \
victor9k_ss \

29
src/formats/smaky6.textpb Normal file
View File

@@ -0,0 +1,29 @@
comment: 'Smaky 6 308kB 5.25" 77-track 16-sector SSDD, hard sectored'
image_writer {
filename: "smaky6.img"
type: IMG
}
layout {
tracks: 77
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
drive {
hard_sector_count: 16
sync_with_index: false
revolutions: 2.2
}
decoder {
smaky6 {}
}

View File

@@ -7,6 +7,7 @@
#include "lib/sector.h"
#include "lib/layout.h"
#include "lib/decoders/fluxmapreader.h"
#include "lib/crc.h"
DECLARE_COLOUR(BACKGROUND, 192, 192, 192);
DECLARE_COLOUR(READ_SEPARATOR, 255, 0, 0);
@@ -414,6 +415,14 @@ void FluxViewerControl::ShowSectorMenu(std::shared_ptr<const Sector> sector)
{
wxMenu menu;
menu.Bind(
wxEVT_MENU,
[&](wxCommandEvent&)
{
DisplaySectorSummary(sector);
},
menu.Append(wxID_ANY, "Show sector summary")->GetId());
menu.Bind(
wxEVT_MENU,
[&](wxCommandEvent&)
@@ -461,7 +470,22 @@ static void dumpSectorMetadata(
sector->physicalSide)
<< fmt::format("Clock: {:.2f}us / {:.0f}kHz\n",
sector->clock / 1000.0,
1000000.0 / sector->clock);
1000000.0 / sector->clock)
<< fmt::format("Bytecode position: {}\n", sector->position)
<< fmt::format(
"Data CRC16: {:4x}\n", crc16(CCITT_POLY, sector->data));
}
static void dumpRecordMetadata(
std::ostream& s, std::shared_ptr<const Record> record)
{
s << fmt::format("Bytecode position: {}\n", record->position)
<< fmt::format(
"Start: {:.2f}ms\n", record->startTime / 1000000.0)
<< fmt::format(
"End: {:.2f}ms\n", record->endTime / 1000000.0)
<< fmt::format(
"Data CRC16: {:4x}\n", crc16(CCITT_POLY, record->rawData));
}
void FluxViewerControl::DisplayDecodedData(std::shared_ptr<const Sector> sector)
@@ -481,6 +505,44 @@ void FluxViewerControl::DisplayDecodedData(std::shared_ptr<const Sector> sector)
TextViewerWindow::Create(this, title, s.str())->Show();
}
void FluxViewerControl::DisplaySectorSummary(
std::shared_ptr<const Sector> sector)
{
std::stringstream s;
auto title = fmt::format("Sector summary c{}.h{}.s{}",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector);
s << title << '\n';
std::vector<std::shared_ptr<const Sector>> sectors;
for (auto& trackdata : _flux->trackDatas)
{
if ((trackdata->trackInfo->logicalTrack == sector->logicalTrack) &&
(trackdata->trackInfo->logicalSide == sector->logicalSide))
{
for (auto& sec : trackdata->sectors)
{
if (*sec == *sector)
sectors.push_back(sec);
}
}
}
s << fmt::format("Number of times seen: {}\n", sectors.size());
for (int i = 0; i < sectors.size(); i++)
{
auto& sec = sectors[i];
s << fmt::format("\nInstance {}:\n\n", i);
dumpSectorMetadata(s, sec);
s << '\n';
}
TextViewerWindow::Create(this, title, s.str())->Show();
}
void FluxViewerControl::DisplayRawData(std::shared_ptr<const Sector> sector)
{
std::stringstream s;
@@ -493,8 +555,11 @@ void FluxViewerControl::DisplayRawData(std::shared_ptr<const Sector> sector)
dumpSectorMetadata(s, sector);
s << fmt::format("Number of records: {}\n", sector->records.size());
for (auto& record : sector->records)
for (int i = 0; i < sector->records.size(); i++)
{
auto& record = sector->records[i];
s << fmt::format("\nRecord {}:\n\n", i);
dumpRecordMetadata(s, record);
s << '\n';
hexdump(s, record->rawData);
}
@@ -511,7 +576,9 @@ void FluxViewerControl::DisplayRawData(std::shared_ptr<const TrackInfo>& layout,
layout->physicalTrack,
layout->physicalSide,
record->startTime / 1e6);
s << title << "\n\n";
s << title << "\n";
dumpRecordMetadata(s, record);
s << '\n';
hexdump(s, record->rawData);
TextViewerWindow::Create(this, title, s.str())->Show();

View File

@@ -30,6 +30,7 @@ private:
void ShowRecordMenu(std::shared_ptr<const TrackInfo>& layout,
std::shared_ptr<const Record> record);
void DisplayDecodedData(std::shared_ptr<const Sector> sector);
void DisplaySectorSummary(std::shared_ptr<const Sector> sector);
void DisplayRawData(std::shared_ptr<const Sector> sector);
void DisplayRawData(std::shared_ptr<const TrackInfo>& layout,
std::shared_ptr<const Record> record);

View File

@@ -6,6 +6,7 @@
class ExecEvent;
class MainWindow;
extern void postToUiThread(std::function<void()> callback);
extern void runOnUiThread(std::function<void()> callback);
extern void runOnWorkerThread(std::function<void()> callback);

View File

@@ -81,7 +81,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
wxString driveChoiceChoices[] = { wxT("drive:0"), wxT("drive:1") };
int driveChoiceNChoices = sizeof( driveChoiceChoices ) / sizeof( wxString );
driveChoice = new wxChoice( realDiskRadioButtonPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, driveChoiceNChoices, driveChoiceChoices, 0 );
driveChoice->SetSelection( 0 );
driveChoice->SetSelection( 1 );
driveChoice->SetToolTip( wxT("Which drive on the device to use.") );
bSizer3->Add( driveChoice, 0, wxALL|wxEXPAND, 5 );
@@ -174,7 +174,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
fgSizer8->Add( m_staticText19, 0, wxALIGN_CENTER|wxALL, 5 );
wxGridSizer* gSizer9;
gSizer9 = new wxGridSizer( 1, 0, 0, 0 );
gSizer9 = new wxGridSizer( 2, 3, 0, 0 );
readButton = new wxButton( idlePanel, wxID_ANY, wxT("Read disk"), wxDefaultPosition, wxDefaultSize, 0 );
readButton->SetLabelMarkup( wxT("Read disk") );
@@ -191,7 +191,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
gSizer9->Add( writeButton, 0, wxALL|wxEXPAND, 5 );
browseButton = new wxButton( idlePanel, wxID_ANY, wxT("Browse disk"), wxDefaultPosition, wxDefaultSize, 0 );
browseButton = new wxButton( idlePanel, wxID_ANY, wxT("Browse files"), wxDefaultPosition, wxDefaultSize, 0 );
browseButton->SetBitmap( wxArtProvider::GetBitmap( wxART_FOLDER_OPEN, wxART_TOOLBAR ) );
browseButton->SetToolTip( wxT("Access the files on the disk directly without needing to image it.") );
@@ -203,6 +203,11 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
formatButton->SetBitmap( wxArtProvider::GetBitmap( wxART_DELETE, wxART_BUTTON ) );
gSizer9->Add( formatButton, 0, wxALL|wxEXPAND, 5 );
exploreButton = new wxButton( idlePanel, wxID_ANY, wxT("Explore disk"), wxDefaultPosition, wxDefaultSize, 0 );
exploreButton->SetBitmap( wxArtProvider::GetBitmap( wxART_INFORMATION, wxART_TOOLBAR ) );
gSizer9->Add( exploreButton, 0, wxALL|wxEXPAND, 5 );
fgSizer8->Add( gSizer9, 1, wxEXPAND, 5 );
@@ -372,6 +377,102 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
browsePanel->Layout();
fgSizer23->Fit( browsePanel );
dataNotebook->AddPage( browsePanel, wxT("a page"), false );
explorePanel = new wxPanel( dataNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer411;
bSizer411 = new wxBoxSizer( wxVERTICAL );
explorerToolbar = new wxAuiToolBar( explorePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_TB_HORZ_LAYOUT|wxAUI_TB_TEXT );
explorerBackTool = explorerToolbar->AddTool( wxID_ANY, wxT("Back"), wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
explorerRefreshTool = explorerToolbar->AddTool( wxID_ANY, wxT("Refresh"), wxArtProvider::GetBitmap( wxART_REDO, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
explorerToolbar->Realize();
bSizer411->Add( explorerToolbar, 0, wxEXPAND, 5 );
wxFlexGridSizer* fgSizer12;
fgSizer12 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer12->AddGrowableCol( 1 );
fgSizer12->AddGrowableRow( 0 );
fgSizer12->SetFlexibleDirection( wxBOTH );
fgSizer12->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
wxFlexGridSizer* fgSizer10;
fgSizer10 = new wxFlexGridSizer( 0, 2, 0, 0 );
fgSizer10->AddGrowableCol( 1 );
fgSizer10->SetFlexibleDirection( wxHORIZONTAL );
fgSizer10->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
m_staticText22 = new wxStaticText( explorePanel, wxID_ANY, wxT("Track"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText22->Wrap( -1 );
fgSizer10->Add( m_staticText22, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
explorerTrackSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 82, 0 );
fgSizer10->Add( explorerTrackSpinCtrl, 0, wxALL, 5 );
m_staticText26 = new wxStaticText( explorePanel, wxID_ANY, wxT("Side"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText26->Wrap( -1 );
fgSizer10->Add( m_staticText26, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
explorerSideSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 1, 0 );
fgSizer10->Add( explorerSideSpinCtrl, 0, wxALL, 5 );
m_staticText231 = new wxStaticText( explorePanel, wxID_ANY, wxT("Start time (ms)"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText231->Wrap( -1 );
fgSizer10->Add( m_staticText231, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
explorerStartTimeSpinCtrl = new wxSpinCtrlDouble( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 99999, 0.000000, 1 );
explorerStartTimeSpinCtrl->SetDigits( 3 );
fgSizer10->Add( explorerStartTimeSpinCtrl, 0, wxALL, 5 );
m_staticText24 = new wxStaticText( explorePanel, wxID_ANY, wxT("Clock (us)"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText24->Wrap( -1 );
fgSizer10->Add( m_staticText24, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
explorerClockSpinCtrl = new wxSpinCtrlDouble( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 100, 4, 1 );
explorerClockSpinCtrl->SetDigits( 1 );
fgSizer10->Add( explorerClockSpinCtrl, 0, wxALL, 5 );
m_staticText25 = new wxStaticText( explorePanel, wxID_ANY, wxT("Raw bit offset"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText25->Wrap( -1 );
fgSizer10->Add( m_staticText25, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
explorerBitOffsetSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 99999999, 0 );
fgSizer10->Add( explorerBitOffsetSpinCtrl, 0, wxALL, 5 );
m_staticText27 = new wxStaticText( explorePanel, wxID_ANY, wxT("Decode as"), wxDefaultPosition, wxDefaultSize, 0 );
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") };
int explorerDecodeChoiceNChoices = sizeof( explorerDecodeChoiceChoices ) / sizeof( wxString );
explorerDecodeChoice = new wxChoice( explorePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, explorerDecodeChoiceNChoices, explorerDecodeChoiceChoices, 0 );
explorerDecodeChoice->SetSelection( 0 );
fgSizer10->Add( explorerDecodeChoice, 0, wxALL|wxEXPAND, 5 );
m_staticText241 = new wxStaticText( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_staticText241->Wrap( -1 );
fgSizer10->Add( m_staticText241, 0, wxALL, 5 );
explorerReverseCheckBox = new wxCheckBox( explorePanel, wxID_ANY, wxT("Reverse bytes"), wxDefaultPosition, wxDefaultSize, 0 );
fgSizer10->Add( explorerReverseCheckBox, 0, wxALL, 5 );
fgSizer12->Add( fgSizer10, 1, wxEXPAND, 5 );
explorerText = new wxTextCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_PROCESS_TAB|wxTE_READONLY );
explorerText->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
fgSizer12->Add( explorerText, 0, wxALL|wxEXPAND, 5 );
bSizer411->Add( fgSizer12, 1, wxEXPAND, 5 );
explorePanel->SetSizer( bSizer411 );
explorePanel->Layout();
bSizer411->Fit( explorePanel );
dataNotebook->AddPage( explorePanel, wxT("a page"), false );
bSizer4->Add( dataNotebook, 1, wxEXPAND, 5 );
@@ -401,6 +502,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
writeButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
formatButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
exploreButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnExploreButton ), NULL, this );
this->Connect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
imagerSaveImageButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
imagerSaveFluxButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
@@ -422,6 +524,15 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
browserDiscardButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
browserCommitButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
this->Connect( explorerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
this->Connect( explorerRefreshTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerRefreshButton ) );
explorerTrackSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerSideSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerStartTimeSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerClockSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerBitOffsetSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerDecodeChoice->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerReverseCheckBox->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
}
MainWindowGen::~MainWindowGen()
@@ -442,6 +553,7 @@ MainWindowGen::~MainWindowGen()
writeButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
formatButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
exploreButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnExploreButton ), NULL, this );
this->Disconnect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
imagerSaveImageButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
imagerSaveFluxButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
@@ -459,6 +571,15 @@ MainWindowGen::~MainWindowGen()
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
browserDiscardButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
browserCommitButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
this->Disconnect( explorerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
this->Disconnect( explorerRefreshTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerRefreshButton ) );
explorerTrackSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerSideSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerStartTimeSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerClockSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerBitOffsetSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerDecodeChoice->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
explorerReverseCheckBox->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
delete browserMoreMenu;
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -34,9 +34,10 @@
#include "visualisationcontrol.h"
#include <wx/dataview.h>
#include <wx/gauge.h>
#include <wx/spinctrl.h>
#include <wx/textctrl.h>
#include <wx/simplebook.h>
#include <wx/frame.h>
#include <wx/textctrl.h>
#include <wx/dialog.h>
#include "fluxviewercontrol.h"
#include <wx/scrolbar.h>
@@ -80,6 +81,7 @@ protected:
wxButton* writeButton;
wxButton* browseButton;
wxButton* formatButton;
wxButton* exploreButton;
wxPanel* imagePanel;
wxAuiToolBar* imagerToolbar;
wxAuiToolBarItem* imagerBackTool;
@@ -109,6 +111,25 @@ protected:
wxButton* browserDiscardButton;
wxButton* browserCommitButton;
wxStaticText* m_staticText12;
wxPanel* explorePanel;
wxAuiToolBar* explorerToolbar;
wxAuiToolBarItem* explorerBackTool;
wxAuiToolBarItem* explorerRefreshTool;
wxStaticText* m_staticText22;
wxSpinCtrl* explorerTrackSpinCtrl;
wxStaticText* m_staticText26;
wxSpinCtrl* explorerSideSpinCtrl;
wxStaticText* m_staticText231;
wxSpinCtrlDouble* explorerStartTimeSpinCtrl;
wxStaticText* m_staticText24;
wxSpinCtrlDouble* explorerClockSpinCtrl;
wxStaticText* m_staticText25;
wxSpinCtrl* explorerBitOffsetSpinCtrl;
wxStaticText* m_staticText27;
wxChoice* explorerDecodeChoice;
wxStaticText* m_staticText241;
wxCheckBox* explorerReverseCheckBox;
wxTextCtrl* explorerText;
// Virtual event handlers, override them in your derived class
virtual void OnClose(wxCloseEvent& event)
@@ -163,6 +184,10 @@ protected:
{
event.Skip();
}
virtual void OnExploreButton(wxCommandEvent& event)
{
event.Skip();
}
virtual void OnBackButton(wxCommandEvent& event)
{
event.Skip();
@@ -243,6 +268,22 @@ protected:
{
event.Skip();
}
virtual void OnExplorerRefreshButton(wxCommandEvent& event)
{
event.Skip();
}
virtual void OnExplorerSettingChange(wxSpinEvent& event)
{
event.Skip();
}
virtual void OnExplorerSettingChange(wxSpinDoubleEvent& event)
{
event.Skip();
}
virtual void OnExplorerSettingChange(wxCommandEvent& event)
{
event.Skip();
}
public:
MainWindowGen(wxWindow* parent,

View File

@@ -14,8 +14,9 @@ wxDEFINE_EVENT(EXEC_EVENT_TYPE, ExecEvent);
class ExecEvent : public wxThreadEvent
{
public:
ExecEvent(wxEventType commandType = EXEC_EVENT_TYPE, int id = 0):
wxThreadEvent(commandType, id)
ExecEvent(bool synchronous = true):
wxThreadEvent(EXEC_EVENT_TYPE, 0),
_synchronous(synchronous)
{
}
@@ -35,6 +36,11 @@ public:
_callback = callback;
}
bool IsSynchronous() const
{
return _synchronous;
}
void RunCallback() const
{
_callback();
@@ -42,6 +48,7 @@ public:
private:
std::function<void()> _callback;
bool _synchronous;
};
bool FluxEngineApp::OnInit()
@@ -69,9 +76,11 @@ wxThread::ExitCode FluxEngineApp::Entry()
Logger() << EmergencyStopMessage();
}
runOnUiThread(
postToUiThread(
[&]
{
GetThread()->Wait();
_callback = nullptr;
SendUpdateEvent();
});
@@ -114,7 +123,8 @@ bool FluxEngineApp::IsWorkerThreadRunning() const
void FluxEngineApp::OnExec(const ExecEvent& event)
{
event.RunCallback();
execSemaphore.Post();
if (event.IsSynchronous())
execSemaphore.Post();
}
void runOnUiThread(std::function<void()> callback)
@@ -125,4 +135,11 @@ void runOnUiThread(std::function<void()> callback)
execSemaphore.Wait();
}
void postToUiThread(std::function<void()> callback)
{
ExecEvent* event = new ExecEvent(false);
event->SetCallback(callback);
wxGetApp().QueueEvent(event);
}
wxIMPLEMENT_APP(FluxEngineApp);

View File

@@ -55,6 +55,30 @@ class MainWindow : public MainWindowGen
private:
class FilesystemOperation;
enum State
{
STATE_IDLE,
STATE_IDLE__LAST,
STATE_READING_WORKING,
STATE_READING_FAILED,
STATE_READING_SUCCEEDED,
STATE_READING__LAST,
STATE_WRITING_WORKING,
STATE_WRITING_FAILED,
STATE_WRITING_SUCCEEDED,
STATE_WRITING__LAST,
STATE_BROWSING_WORKING,
STATE_BROWSING_IDLE,
STATE_BROWSING__LAST,
STATE_EXPLORING_WORKING,
STATE_EXPLORING_IDLE,
STATE_EXPLORING__LAST,
};
public:
MainWindow():
MainWindowGen(nullptr),
@@ -997,20 +1021,20 @@ public:
void QueueBrowserOperation(std::function<void()> f)
{
_filesystemQueue.push_back(f);
KickBrowserQueue();
_jobQueue.push_back(f);
KickQueue(STATE_BROWSING_WORKING, STATE_BROWSING_IDLE);
}
void KickBrowserQueue()
void KickQueue(State workingState, State idleState)
{
bool running = wxGetApp().IsWorkerThreadRunning();
if (!running)
{
_state = STATE_BROWSING_WORKING;
_errorState = STATE_BROWSING_IDLE;
_state = workingState;
_errorState = idleState;
UpdateState();
runOnWorkerThread(
[this]()
[this, idleState]()
{
for (;;)
{
@@ -1019,10 +1043,10 @@ public:
[&]()
{
UpdateState();
if (!_filesystemQueue.empty())
if (!_jobQueue.empty())
{
f = _filesystemQueue.front();
_filesystemQueue.pop_front();
f = _jobQueue.front();
_jobQueue.pop_front();
}
});
if (!f)
@@ -1034,7 +1058,7 @@ public:
runOnUiThread(
[&]()
{
_state = STATE_BROWSING_IDLE;
_state = idleState;
UpdateState();
});
});
@@ -1046,11 +1070,144 @@ public:
UpdateState();
}
/* --- Config management
* ------------------------------------------------ */
/* --- Explorer -------------------------------------------------------- */
void OnExploreButton(wxCommandEvent& event) override
{
try
{
PrepareConfig();
visualiser->Clear();
_filesystemModel->Clear(Path());
_currentDisk = nullptr;
_state = STATE_EXPLORING_IDLE;
UpdateState();
ShowConfig();
_explorerFluxmap = nullptr;
_explorerTrack = -1;
_explorerSide = -1;
_explorerUpdatePending = false;
UpdateExplorerData();
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
_state = STATE_IDLE;
}
}
void OnExplorerSettingChange(wxSpinEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerSettingChange(wxSpinDoubleEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerSettingChange(wxCommandEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerRefreshButton(wxCommandEvent& event) override
{
_explorerFluxmap = nullptr;
_explorerTrack = -1;
_explorerSide = -1;
_explorerUpdatePending = false;
UpdateExplorerData();
}
private:
void UpdateExplorerData()
{
if (!_jobQueue.empty())
{
_explorerUpdatePending = true;
return;
}
_explorerUpdatePending = false;
QueueExplorerOperation(
[this]()
{
/* You need to call this if the config changes to invalidate
* any caches. */
Environment::reset();
int desiredTrack = explorerTrackSpinCtrl->GetValue();
int desiredSide = explorerSideSpinCtrl->GetValue();
if (!_explorerFluxmap || (desiredTrack != _explorerTrack) ||
(desiredSide != _explorerSide))
{
auto fluxSource = FluxSource::create(config.flux_source());
_explorerFluxmap =
fluxSource->readFlux(desiredTrack, desiredSide)->next();
_explorerTrack = desiredTrack;
_explorerSide = desiredSide;
}
runOnUiThread(
[&]()
{
_state = STATE_EXPLORING_IDLE;
UpdateState();
FluxmapReader fmr(*_explorerFluxmap);
fmr.seek(explorerStartTimeSpinCtrl->GetValue() * 1e6);
FluxDecoder fluxDecoder(&fmr,
explorerClockSpinCtrl->GetValue() * 1e3,
DecoderProto());
fluxDecoder.readBits(
explorerBitOffsetSpinCtrl->GetValue());
auto bits = fluxDecoder.readBits();
Bytes bytes;
switch (explorerDecodeChoice->GetSelection())
{
case 0:
bytes = toBytes(bits);
break;
case 1:
bytes = decodeFmMfm(bits.begin(), bits.end());
break;
}
if (explorerReverseCheckBox->GetValue())
bytes = bytes.reverseBits();
std::stringstream s;
hexdump(s, bytes);
explorerText->SetValue(s.str());
if (_explorerUpdatePending)
UpdateExplorerData();
});
});
}
void QueueExplorerOperation(std::function<void()> f)
{
_jobQueue.push_back(f);
KickQueue(STATE_EXPLORING_WORKING, STATE_EXPLORING_IDLE);
}
/* --- Config management ----------------------------------------------- */
/* This sets the *global* config object. That's safe provided the worker
* thread isn't running, otherwise you'll get a race. */
public:
void PrepareConfig()
{
assert(!wxGetApp().IsWorkerThreadRunning());
@@ -1169,7 +1326,7 @@ public:
_statusBar->HideProgressBar();
_statusBar->SetRightLabel("");
_state = _errorState;
_filesystemQueue.clear();
_jobQueue.clear();
UpdateState();
},
@@ -1362,8 +1519,15 @@ public:
{
dataNotebook->SetSelection(0);
readButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE);
writeButton->Enable(_selectedSource == SELECTEDSOURCE_REAL);
bool hasFormat = formatChoice->GetSelection() != wxNOT_FOUND;
readButton->Enable(
(_selectedSource != SELECTEDSOURCE_IMAGE) && hasFormat);
writeButton->Enable(
(_selectedSource == SELECTEDSOURCE_REAL) && hasFormat);
browseButton->Enable(hasFormat);
formatButton->Enable(hasFormat);
exploreButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE);
}
else if (_state < STATE_READING__LAST)
{
@@ -1391,7 +1555,7 @@ public:
{
dataNotebook->SetSelection(2);
bool running = !_filesystemQueue.empty();
bool running = !_jobQueue.empty();
bool selection = browserTree->GetSelection().IsOk();
browserToolbar->EnableTool(
@@ -1421,6 +1585,13 @@ public:
browserDiscardButton->Enable(!running && needsFlushing);
browserCommitButton->Enable(!running && needsFlushing);
}
else if (_state < STATE_EXPLORING__LAST)
{
dataNotebook->SetSelection(3);
explorerToolbar->EnableTool(
explorerBackTool->GetId(), _state == STATE_EXPLORING_IDLE);
}
Refresh();
}
@@ -1461,26 +1632,6 @@ private:
SELECTEDSOURCE_IMAGE
};
enum
{
STATE_IDLE,
STATE_IDLE__LAST,
STATE_READING_WORKING,
STATE_READING_FAILED,
STATE_READING_SUCCEEDED,
STATE_READING__LAST,
STATE_WRITING_WORKING,
STATE_WRITING_FAILED,
STATE_WRITING_SUCCEEDED,
STATE_WRITING__LAST,
STATE_BROWSING_WORKING,
STATE_BROWSING_IDLE,
STATE_BROWSING__LAST
};
class FilesystemOperation
{
public:
@@ -1539,8 +1690,8 @@ private:
wxDataFormat _dndFormat;
std::vector<std::string> _formatNames;
std::vector<std::unique_ptr<const CandidateDevice>> _devices;
int _state = STATE_IDLE;
int _errorState;
State _state = STATE_IDLE;
State _errorState;
int _selectedSource;
bool _dontSaveConfig = false;
std::shared_ptr<const DiskFlux> _currentDisk;
@@ -1554,7 +1705,11 @@ private:
bool _filesystemIsReadOnly;
bool _filesystemNeedsFlushing;
FilesystemModel* _filesystemModel;
std::deque<std::function<void()>> _filesystemQueue;
std::deque<std::function<void()>> _jobQueue;
int _explorerTrack;
int _explorerSide;
bool _explorerUpdatePending;
std::unique_ptr<const Fluxmap> _explorerFluxmap;
};
wxWindow* FluxEngineApp::CreateMainWindow()

View File

@@ -272,7 +272,8 @@ void VisualisationControl::Clear()
void VisualisationControl::SetTrackData(std::shared_ptr<const TrackFlux> track)
{
key_t key = {track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
key_t key = {
track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
_tracks[key] = track;
_sectors.erase(key);
for (auto& sector : track->sectors)
@@ -286,7 +287,8 @@ void VisualisationControl::SetDiskData(std::shared_ptr<const DiskFlux> disk)
_sectors.clear();
for (const auto& track : disk->tracks)
{
key_t key = {track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
key_t key = {
track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
_tracks[key] = track;
}