Files
fluxengine/lib/decoders/decoders.cc
David Given 5ce2acdfb4 The new decoder architecture now works, at least for the FB100. All I need now
is to rewrite every single other decoder.
2019-04-18 00:47:28 +02:00

331 lines
9.2 KiB
C++

#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "fluxmapreader.h"
#include "decoders.h"
#include "record.h"
#include "protocol.h"
#include "rawbits.h"
#include "track.h"
#include "sector.h"
#include "fmt/format.h"
#include <numeric>
static DoubleFlag clockDecodeThreshold(
{ "--clock-decode-threshold" },
"Pulses below this fraction of a clock tick are considered spurious and ignored.",
0.80);
static SettableFlag showClockHistogram(
{ "--show-clock-histogram" },
"Dump the clock detection histogram.");
static DoubleFlag manualClockRate(
{ "--manual-clock-rate-us" },
"If not zero, force this clock rate; if zero, try to autodetect it.",
0.0);
static DoubleFlag noiseFloorFactor(
{ "--noise-floor-factor" },
"Clock detection noise floor (min + (max-min)*factor).",
0.01);
static DoubleFlag signalLevelFactor(
{ "--signal-level-factor" },
"Clock detection signal level (min + (max-min)*factor).",
0.05);
void setDecoderManualClockRate(double clockrate_us)
{
manualClockRate.value = clockrate_us;
}
static const std::string BLOCK_ELEMENTS[] =
{ " ", "", "", "", "", "", "", "", "" };
/*
* Tries to guess the clock by finding the smallest common interval.
* Returns nanoseconds.
*/
nanoseconds_t Fluxmap::guessClock() const
{
uint32_t buckets[256] = {};
FluxmapReader fr(*this);
while (!fr.eof())
{
unsigned interval = fr.readNextMatchingOpcode(F_OP_PULSE);
if (interval > 0xff)
continue;
buckets[interval]++;
}
uint32_t max = *std::max_element(std::begin(buckets), std::end(buckets));
uint32_t min = *std::min_element(std::begin(buckets), std::end(buckets));
uint32_t noise_floor = min + (max-min)*noiseFloorFactor;
uint32_t signal_level = min + (max-min)*signalLevelFactor;
/* Find a point solidly within the first pulse. */
int pulseindex = 0;
while (pulseindex < 256)
{
if (buckets[pulseindex] > signal_level)
break;
pulseindex++;
}
if (pulseindex == -1)
return 0;
/* Find the upper and lower bounds of the pulse. */
int peaklo = pulseindex;
while (peaklo > 0)
{
if (buckets[peaklo] < noise_floor)
break;
peaklo--;
}
int peakhi = pulseindex;
while (peakhi < 255)
{
if (buckets[peakhi] < noise_floor)
break;
peakhi++;
}
/* Find the total accumulated size of the pulse. */
uint32_t total_size = 0;
for (int i = peaklo; i < peakhi; i++)
total_size += buckets[i];
/* Now find the median. */
uint32_t count = 0;
int median = peaklo;
while (median < peakhi)
{
count += buckets[median];
if (count > (total_size/2))
break;
median++;
}
if (showClockHistogram)
{
std::cout << "Clock detection histogram:" << std::endl;
bool skipping = true;
for (int i=0; i<256; i++)
{
uint32_t value = buckets[i];
if (value < noise_floor/2)
{
if (!skipping)
std::cout << "..." << std::endl;
skipping = true;
}
else
{
skipping = false;
int bar = 320*value/max;
int fullblocks = bar / 8;
std::string s;
for (int j=0; j<fullblocks; j++)
s += BLOCK_ELEMENTS[8];
s += BLOCK_ELEMENTS[bar & 7];
std::cout << fmt::format("{:.2f} {:6} {}", (double)i * US_PER_TICK, value, s);
std::cout << std::endl;
}
}
std::cout << fmt::format("Noise floor: {}", noise_floor) << std::endl;
std::cout << fmt::format("Signal level: {}", signal_level) << std::endl;
std::cout << fmt::format("Peak start: {} ({:.2f} us)", peaklo, peaklo*US_PER_TICK) << std::endl;
std::cout << fmt::format("Peak end: {} ({:.2f} us)", peakhi, peakhi*US_PER_TICK) << std::endl;
std::cout << fmt::format("Median: {} ({:.2f} us)", median, median*US_PER_TICK) << std::endl;
}
/*
* Okay, the median should now be a good candidate for the (or a) clock.
* How this maps onto the actual clock rate depends on the encoding.
*/
return median * NS_PER_TICK;
}
/* Decodes a fluxmap into a nice aligned array of bits. */
const RawBits Fluxmap::decodeToBits(nanoseconds_t clockPeriod) const
{
int pulses = duration() / clockPeriod;
nanoseconds_t lowerThreshold = clockPeriod * clockDecodeThreshold;
auto bitmap = std::make_unique<std::vector<bool>>(pulses);
auto indices = std::make_unique<std::vector<size_t>>();
unsigned count = 0;
nanoseconds_t timestamp = 0;
FluxmapReader fr(*this);
for (;;)
{
for (;;)
{
unsigned interval;
int opcode = fr.readOpcode(interval);
timestamp += interval * NS_PER_TICK;
if (opcode == -1)
goto abort;
else if ((opcode == 0x80) && (timestamp >= lowerThreshold))
break;
else if (opcode == 0x81)
indices->push_back(count);
}
int clocks = (timestamp + clockPeriod/2) / clockPeriod;
count += clocks;
if (count >= bitmap->size())
goto abort;
bitmap->at(count) = true;
timestamp = 0;
}
abort:
RawBits rawbits(std::move(bitmap), std::move(indices));
return rawbits;
}
nanoseconds_t AbstractDecoder::guessClock(Track& track) const
{
if (manualClockRate != 0.0)
return manualClockRate * 1000.0;
return guessClockImpl(track);
}
nanoseconds_t AbstractDecoder::guessClockImpl(Track& track) const
{
return track.fluxmap->guessClock();
}
void AbstractSeparatedDecoder::decodeToSectors(Track& track)
{
nanoseconds_t clockPeriod = guessClock(track);
if (clockPeriod == 0)
{
std::cout << " no clock detected; giving up" << std::endl;
return;
}
std::cout << fmt::format(" {:.2f} us clock; ", (double)clockPeriod/1000.0) << std::flush;
const auto& bitmap = track.fluxmap->decodeToBits(clockPeriod);
std::cout << fmt::format("{} bytes encoded; ", bitmap.size()/8) << std::flush;
decodeToSectors(bitmap, track);
}
void AbstractSeparatedDecoder::decodeToSectors(const RawBits& bitmap, Track& track)
{
track.rawrecords = std::make_unique<RawRecordVector>(extractRecords(bitmap));
track.sectors = std::make_unique<SectorVector>(decodeToSectors(*track.rawrecords, track.physicalTrack, track.physicalSide));
}
RawRecordVector AbstractSoftSectorDecoder::extractRecords(const RawBits& rawbits) const
{
RawRecordVector records;
uint64_t fifo = 0;
size_t cursor = 0;
int matchStart = -1;
auto pushRecord = [&](size_t end)
{
if (matchStart == -1)
return;
records.push_back(
std::unique_ptr<RawRecord>(
new RawRecord(
matchStart,
rawbits.begin() + matchStart,
rawbits.begin() + end)
)
);
};
while (cursor < rawbits.size())
{
fifo = (fifo << 1) | rawbits[cursor++];
int match = recordMatcher(fifo);
if (match > 0)
{
pushRecord(cursor - match);
matchStart = cursor - match;
}
}
pushRecord(cursor);
return records;
}
RawRecordVector AbstractHardSectorDecoder::extractRecords(const RawBits& rawbits) const
{
/* This is less easy than it looks.
*
* Hard-sectored disks contain one extra index hole, marking the top of the
* disk. This appears halfway in between two start-of-sector index holes. We
* need to find this and ignore it, otherwise we'll split that sector in two
* (and it won't work).
*
* The routine here is pretty simple and requires this extra hole to have
* valid index holes either side of it --- it can't cope with the extra
* index hole being the first or last seen. Always give it slightly more
* than one revolution of data.
*/
RawRecordVector records;
const auto& indices = rawbits.indices();
if (!indices.empty())
{
unsigned total = 0;
unsigned previous = 0;
for (unsigned index : indices)
{
total += index - previous;
previous = index;
}
total += rawbits.size() - previous;
unsigned sectors_must_be_bigger_than = (total / indices.size()) * 2/3;
previous = 0;
auto pushRecord = [&](size_t end)
{
if ((end - previous) < sectors_must_be_bigger_than)
return;
records.push_back(
std::unique_ptr<RawRecord>(
new RawRecord(
previous,
rawbits.begin() + previous,
rawbits.begin() + end)
)
);
previous = end;
};
for (unsigned index : indices)
pushRecord(index);
pushRecord(rawbits.size());
}
return records;
}