mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
331 lines
9.2 KiB
C++
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;
|
|
}
|
|
|
|
|