Merge from master.

This commit is contained in:
David Given
2019-02-21 01:16:13 +01:00
21 changed files with 519 additions and 47 deletions

View File

@@ -76,6 +76,8 @@ markers.
- [Brother 120kB and 240kB word processor disks](brother.html); read and
write
- [Macintosh 800kB (and probably 400kB too) disks](macintosh.html); read only
...aaaand that's it. If you want more, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new); I need samples
of floppy disks to scan and play with.

65
doc/macintosh.md Normal file
View File

@@ -0,0 +1,65 @@
Macintosh disks
===============
Macintosh disks come in two varieties: the newer 1440kB ones, which are
perfectly ordinary PC disks you should use `fe-readibm` to read, and the
older 800kB disks (and 400kB for the single sides ones). They have 80 tracks
and up to 12 sectors per track.
They are also completely insane.
It's not just the weird, custom GCR encoding. It's not just the utterly
bizarre additional encoding/checksum built on top of that where [every byte
is mutated according to the previous bytes in the
sector](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/).
It's not just the odd way in which disks think they have four sides, two on
one side and two on the other, so that the track byte stores only the bottom
6 bits of the track number. It's not just the way that Macintosh sectors are
524 bytes long. No, it's the way the Macintosh drive changes speed depending
on which track it's looking at, so that each track contains a different
amount of data.
The reason for this is actually quite sensible: the tracks towards the centre
of the disk are obviously moving more slowly, so you can't pack the bits in
quite as closely (due to limitations in the magnetic media). You can use a
higher bitrate at the edge of the disk than in the middle. Many platforms,
for example the Commodore 64 1541 drive, changed bitrate this way.
But Macintosh disks used a constant bitrate and changed the speed that the
disk spun instead to achieve the same effect...
_Anyway_: FluxEngine will read them fine on a conventional drive. Because
it's clever.
Reading discs
-------------
Just do:
```
.obj/fe-readmac
```
You should end up with an `mac.img` which is 1001888 bytes long (for a normal
DD disk). If you want the single-sided variety, use `-s :s=0`.
**Big warning!** The image may not work in an emulator. Mac disk images are
complicated due to the way the tracks are different sizes and the odd sector
size. FluxEngine chooses to store them in a simple 524 x 12 x 2 x 80 layout,
with holes where missing sectors should be. This was easiest. If anyone can
suggest a better way, please [get in
touch](https://github.com/davidgiven/fluxengine/issues/new).
Useful references
-----------------
- [MAME's ap_dsk35.cpp file]
(https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp),
without which I'd never have managed to do this
- [Crazy Disk Encoding
Schemes](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/), which made
me realise just how nuts the format is
- [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an
epicly detailed writeup of the Apple II disk format (which is closely related)

View File

@@ -11,7 +11,8 @@ class AmigaDecoder : public AbstractDecoder
public:
virtual ~AmigaDecoder() {}
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
SectorVector decodeToSectors(
const RawRecordVector& rawRecords, unsigned physicalTrack);
nanoseconds_t guessClock(Fluxmap& fluxmap) const;
int recordMatcher(uint64_t fifo) const;
};

View File

@@ -63,7 +63,7 @@ static uint32_t checksum(std::vector<uint8_t>::const_iterator input, size_t len)
return checksum & 0x55555555;
}
SectorVector AmigaDecoder::decodeToSectors(const RawRecordVector& rawRecords)
SectorVector AmigaDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
{
std::vector<std::unique_ptr<Sector>> sectors;

View File

@@ -16,7 +16,8 @@ class BrotherDecoder : public AbstractDecoder
public:
virtual ~BrotherDecoder() {}
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
SectorVector decodeToSectors(
const RawRecordVector& rawRecords, unsigned physicalTrack);
int recordMatcher(uint64_t fifo) const;
};

View File

@@ -48,7 +48,7 @@ static int decode_header_gcr(uint16_t word)
return -1;
};
SectorVector BrotherDecoder::decodeToSectors(const RawRecordVector& rawRecords)
SectorVector BrotherDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
{
std::vector<std::unique_ptr<Sector>> sectors;
bool headerIsValid = false;

View File

@@ -43,6 +43,7 @@ void BitAccumulator::push(uint32_t bits, size_t size)
{
_fifo = (_fifo<<1) | (bits >> 31);
_bitcount++;
bits <<= 1;
if (_bitcount == 8)
{
_data.push_back(_fifo);
@@ -51,3 +52,11 @@ void BitAccumulator::push(uint32_t bits, size_t size)
}
}
void BitAccumulator::finish()
{
if (_bitcount != 0)
{
_data.push_back(_fifo);
_bitcount = 0;
}
}

View File

@@ -56,8 +56,10 @@ public:
void reset();
void push(uint32_t bits, size_t size);
size_t size() const { return _data.size(); }
void finish();
operator const std::vector<uint8_t>& () const { return _data; }
operator const std::vector<uint8_t>& ()
{ finish(); return _data; }
private:
uint8_t _fifo;

View File

@@ -5,11 +5,6 @@
#include "protocol.h"
#include "fmt/format.h"
static IntFlag clockDetectionNoiseFloor(
{ "--clock-detection-noise-floor" },
"Noise floor used for clock detection in flux.",
200);
static DoubleFlag clockDecodeThreshold(
{ "--clock-decode-threshold" },
"Pulses below this fraction of a clock tick are considered spurious and ignored.",
@@ -29,45 +24,78 @@ nanoseconds_t Fluxmap::guessClock() const
for (uint8_t interval : _intervals)
buckets[interval]++;
int peaklo = 1;
while (peaklo < 256)
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)/100;
uint32_t signal_level = noise_floor * 5;
/* Find a point solidly within the first pulse. */
int pulseindex = 0;
while (pulseindex < 256)
{
if (buckets[peaklo] > (uint32_t)(clockDetectionNoiseFloor*2))
if (buckets[pulseindex] > signal_level)
break;
peaklo++;
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--;
}
uint32_t peakmaxindex = peaklo;
uint32_t peakmaxvalue = buckets[peakmaxindex];
uint32_t peakhi = peaklo;
while (peakhi < 256)
int peakhi = pulseindex;
while (peakhi < 255)
{
uint32_t v = buckets[peakhi];
if (buckets[peakhi] < (uint32_t)clockDetectionNoiseFloor)
if (buckets[peakhi] < noise_floor)
break;
if (v > peakmaxvalue)
{
peakmaxindex = peakhi;
peakmaxvalue = v;
}
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;
for (int i=0; i<256; i++)
{
std::cout << fmt::format("{:.2f} {}\n", (double)i * US_PER_TICK, buckets[i]);
}
std::cout << fmt::format("{:.2f} {}", (double)i * US_PER_TICK, buckets[i]) << 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, peakmaxindex should now be a good candidate for the (or a) clock.
* 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 peakmaxindex * NS_PER_TICK;
return median * NS_PER_TICK;
}
/* Decodes a fluxmap into a nice aligned array of bits. */

View File

@@ -18,7 +18,8 @@ public:
virtual ~AbstractDecoder() {}
virtual nanoseconds_t guessClock(Fluxmap& fluxmap) const;
virtual SectorVector decodeToSectors(const RawRecordVector& rawrecords) = 0;
virtual SectorVector decodeToSectors(const RawRecordVector& rawrecords,
unsigned physicalTrack) = 0;
};
#endif

View File

@@ -8,7 +8,7 @@
static_assert(std::is_trivially_copyable<IbmIdam>::value);
SectorVector AbstractIbmDecoder::decodeToSectors(const RawRecordVector& rawRecords)
SectorVector AbstractIbmDecoder::decodeToSectors(const RawRecordVector& rawRecords, unsigned)
{
bool idamValid = false;
IbmIdam idam;

View File

@@ -34,7 +34,7 @@ public:
{}
virtual ~AbstractIbmDecoder() {}
SectorVector decodeToSectors(const RawRecordVector& rawRecords);
SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack);
protected:
virtual int skipHeaderBytes() const = 0;

64
lib/macintosh/data_gcr.h Normal file
View File

@@ -0,0 +1,64 @@
GCR_ENTRY(0x96, 0x00)
GCR_ENTRY(0x97, 0x01)
GCR_ENTRY(0x9a, 0x02)
GCR_ENTRY(0x9b, 0x03)
GCR_ENTRY(0x9d, 0x04)
GCR_ENTRY(0x9e, 0x05)
GCR_ENTRY(0x9f, 0x06)
GCR_ENTRY(0xa6, 0x07)
GCR_ENTRY(0xa7, 0x08)
GCR_ENTRY(0xab, 0x09)
GCR_ENTRY(0xac, 0x0a)
GCR_ENTRY(0xad, 0x0b)
GCR_ENTRY(0xae, 0x0c)
GCR_ENTRY(0xaf, 0x0d)
GCR_ENTRY(0xb2, 0x0e)
GCR_ENTRY(0xb3, 0x0f)
GCR_ENTRY(0xb4, 0x10)
GCR_ENTRY(0xb5, 0x11)
GCR_ENTRY(0xb6, 0x12)
GCR_ENTRY(0xb7, 0x13)
GCR_ENTRY(0xb9, 0x14)
GCR_ENTRY(0xba, 0x15)
GCR_ENTRY(0xbb, 0x16)
GCR_ENTRY(0xbc, 0x17)
GCR_ENTRY(0xbd, 0x18)
GCR_ENTRY(0xbe, 0x19)
GCR_ENTRY(0xbf, 0x1a)
GCR_ENTRY(0xcb, 0x1b)
GCR_ENTRY(0xcd, 0x1c)
GCR_ENTRY(0xce, 0x1d)
GCR_ENTRY(0xcf, 0x1e)
GCR_ENTRY(0xd3, 0x1f)
GCR_ENTRY(0xd6, 0x20)
GCR_ENTRY(0xd7, 0x21)
GCR_ENTRY(0xd9, 0x22)
GCR_ENTRY(0xda, 0x23)
GCR_ENTRY(0xdb, 0x24)
GCR_ENTRY(0xdc, 0x25)
GCR_ENTRY(0xdd, 0x26)
GCR_ENTRY(0xde, 0x27)
GCR_ENTRY(0xdf, 0x28)
GCR_ENTRY(0xe5, 0x29)
GCR_ENTRY(0xe6, 0x2a)
GCR_ENTRY(0xe7, 0x2b)
GCR_ENTRY(0xe9, 0x2c)
GCR_ENTRY(0xea, 0x2d)
GCR_ENTRY(0xeb, 0x2e)
GCR_ENTRY(0xec, 0x2f)
GCR_ENTRY(0xed, 0x30)
GCR_ENTRY(0xee, 0x31)
GCR_ENTRY(0xef, 0x32)
GCR_ENTRY(0xf2, 0x33)
GCR_ENTRY(0xf3, 0x34)
GCR_ENTRY(0xf4, 0x35)
GCR_ENTRY(0xf5, 0x36)
GCR_ENTRY(0xf6, 0x37)
GCR_ENTRY(0xf7, 0x38)
GCR_ENTRY(0xf9, 0x39)
GCR_ENTRY(0xfa, 0x3a)
GCR_ENTRY(0xfb, 0x3b)
GCR_ENTRY(0xfc, 0x3c)
GCR_ENTRY(0xfd, 0x3d)
GCR_ENTRY(0xfe, 0x3e)
GCR_ENTRY(0xff, 0x3f)

189
lib/macintosh/decoder.cc Normal file
View File

@@ -0,0 +1,189 @@
#include "globals.h"
#include "fluxmap.h"
#include "protocol.h"
#include "record.h"
#include "decoders.h"
#include "sector.h"
#include "macintosh.h"
#include "bytes.h"
#include "fmt/format.h"
#include <string.h>
#include <algorithm>
static int decode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case gcr: return data;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
};
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
*/
static std::vector<uint8_t> decode_crazy_data(const uint8_t* inp, int& status)
{
std::vector<uint8_t> output;
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
uint8_t b1[LOOKUP_LEN + 1];
uint8_t b2[LOOKUP_LEN + 1];
uint8_t b3[LOOKUP_LEN + 1];
for (int i=0; i<=LOOKUP_LEN; i++)
{
uint8_t w4 = *inp++;
uint8_t w1 = *inp++;
uint8_t w2 = *inp++;
uint8_t w3 = (i != 174) ? *inp++ : 0;
b1[i] = (w1 & 0x3F) | ((w4 << 2) & 0xC0);
b2[i] = (w2 & 0x3F) | ((w4 << 4) & 0xC0);
b3[i] = (w3 & 0x3F) | ((w4 << 6) & 0xC0);
}
/* Copy from the user's buffer to our buffer, while computing
* the three-byte data checksum. */
uint32_t c1 = 0;
uint32_t c2 = 0;
uint32_t c3 = 0;
unsigned count = 0;
for (;;)
{
c1 = (c1 & 0xFF) << 1;
if (c1 & 0x0100)
c1++;
uint8_t val = b1[count] ^ c1;
c3 += val;
if (c1 & 0x0100)
{
c3++;
c1 &= 0xFF;
}
output.push_back(val);
val = b2[count] ^ c3;
c2 += val;
if (c3 > 0xFF)
{
c2++;
c3 &= 0xFF;
}
output.push_back(val);
if (output.size() == 524)
break;
val = b3[count] ^ c2;
c1 += val;
if (c2 > 0xFF)
{
c1++;
c2 &= 0xFF;
}
output.push_back(val);
count++;
}
uint8_t c4 = ((c1 & 0xC0) >> 6) | ((c2 & 0xC0) >> 4) | ((c3 & 0xC0) >> 2);
c1 &= 0x3f;
c2 &= 0x3f;
c3 &= 0x3f;
c4 &= 0x3f;
uint8_t g4 = *inp++;
uint8_t g3 = *inp++;
uint8_t g2 = *inp++;
uint8_t g1 = *inp++;
if ((g4 == c4) && (g3 == c3) && (g2 == c2) && (g1 == c1))
status = Sector::OK;
return output;
}
uint8_t decode_side(uint8_t side)
{
/* Mac disks, being weird, use the side byte to encode both the side (in
* bit 5) and also whether we're above track 0x3f (in bit 6).
*/
return !!(side & 0x40);
}
SectorVector MacintoshDecoder::decodeToSectors(
const RawRecordVector& rawRecords, unsigned physicalTrack)
{
std::vector<std::unique_ptr<Sector>> sectors;
int nextSector;
int nextSide;
bool headerIsValid = false;
for (auto& rawrecord : rawRecords)
{
const std::vector<bool>& rawdata = rawrecord->data;
const std::vector<uint8_t>& rawbytes = toBytes(rawdata);
if (rawbytes.size() < 8)
continue;
uint32_t signature = read_be24(&rawbytes[0]);
switch (signature)
{
case MAC_SECTOR_RECORD:
{
unsigned track = decode_data_gcr(rawbytes[3]);
if (track != (physicalTrack & 0x3f))
break;
nextSector = decode_data_gcr(rawbytes[4]);
nextSide = decode_data_gcr(rawbytes[5]);
uint8_t formatByte = decode_data_gcr(rawbytes[6]);
uint8_t wantedsum = decode_data_gcr(rawbytes[7]);
uint8_t gotsum = (track ^ nextSector ^ nextSide ^ formatByte) & 0x3f;
headerIsValid = (wantedsum == gotsum);
break;
}
case MAC_DATA_RECORD:
{
if (!headerIsValid)
break;
headerIsValid = false;
uint8_t inputbuffer[MAC_SECTOR_LENGTH * 8/6 + 5] = {};
for (unsigned i=0; i<sizeof(inputbuffer); i++)
{
auto p = rawbytes.begin() + 4 + i;
if (p > rawbytes.end())
break;
inputbuffer[i] = decode_data_gcr(*p);
}
int status = Sector::BAD_CHECKSUM;
auto data = decode_crazy_data(inputbuffer, status);
auto sector = std::unique_ptr<Sector>(
new Sector(status, physicalTrack, decode_side(nextSide), nextSector, data));
sectors.push_back(std::move(sector));
break;
}
}
}
return sectors;
}
int MacintoshDecoder::recordMatcher(uint64_t fifo) const
{
uint32_t masked = fifo & 0xffffff;
if ((masked == MAC_SECTOR_RECORD) || (masked == MAC_DATA_RECORD))
return 24;
return 0;
}

22
lib/macintosh/macintosh.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef MACINTOSH_H
#define MACINTOSH_H
#define MAC_SECTOR_RECORD 0xd5aa96
#define MAC_DATA_RECORD 0xd5aaad
#define MAC_SECTOR_LENGTH 524 /* yes, really */
class Sector;
class Fluxmap;
class MacintoshDecoder : public AbstractDecoder
{
public:
virtual ~MacintoshDecoder() {}
SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack);
int recordMatcher(uint64_t fifo) const;
};
#endif

View File

@@ -124,7 +124,7 @@ void readDiskCommand(AbstractDecoder& decoder, const std::string& outputFilename
auto rawrecords = decoder.extractRecords(bitmap);
std::cout << fmt::format("{} records", rawrecords.size()) << std::endl;
auto sectors = decoder.decodeToSectors(rawrecords);
auto sectors = decoder.decodeToSectors(rawrecords, track->track);
std::cout << " " << sectors.size() << " sectors; ";
for (auto& sector : sectors)

View File

@@ -49,11 +49,12 @@ std::unique_ptr<Fluxmap> readStream(const std::string& path, unsigned track, uns
case 0x0d: /* OOB block */
{
int blocktype = f.get();
(void) blocktype;
int blocklen = f.get() | (f.get()<<8);
if (f.fail() || f.eof())
goto finished;
f.seekg(here + blocklen, std::ios_base::beg);
f.seekg(here + blocklen + 3, std::ios_base::beg);
break;
}
@@ -72,12 +73,13 @@ std::unique_ptr<Fluxmap> readStream(const std::string& path, unsigned track, uns
else if (b == 0x09)
{
/* Nop2: skip one byte */
f.seekg(1, std::ios_base::cur);
f.get();
}
else if (b == 0x0a)
{
/* Nop3: skip two bytes */
f.seekg(2, std::ios_base::cur);
f.get();
f.get();
}
else if (b == 0x0b)
{ /* Ovl16: the next block is 0x10000 sclks longer than normal.

View File

@@ -119,6 +119,15 @@ ibmdecoderlib = shared_library('ibmdecoderlib',
)
ibminc = include_directories('lib/ibm')
macdecoderlib = shared_library('macdecoderlib',
[
'lib/macintosh/decoder.cc',
],
include_directories: [feinc, fmtinc, decoderinc],
link_with: [felib, fmtlib, decoderlib]
)
macinc = include_directories('lib/macintosh')
executable('fe-erase', ['src/fe-erase.cc'], include_directories: [feinc], link_with: [felib, writerlib])
executable('fe-inspect', ['src/fe-inspect.cc'], include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib])
executable('fe-readadfs', ['src/fe-readadfs.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
@@ -126,6 +135,7 @@ executable('fe-readamiga', ['src/fe-readamiga.cc'], include_dire
executable('fe-readbrother', ['src/fe-readbrother.cc'], include_directories: [feinc, fmtinc, decoderinc, brotherinc], link_with: [felib, readerlib, decoderlib, brotherdecoderlib, fmtlib])
executable('fe-readdfs', ['src/fe-readdfs.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
executable('fe-readibm', ['src/fe-readibm.cc'], include_directories: [feinc, fmtinc, decoderinc, ibminc], link_with: [felib, readerlib, decoderlib, ibmdecoderlib, fmtlib])
executable('fe-readmac', ['src/fe-readmac.cc'], include_directories: [feinc, fmtinc, decoderinc, macinc], link_with: [felib, readerlib, decoderlib, macdecoderlib, fmtlib])
executable('fe-rpm', ['src/fe-rpm.cc'], include_directories: [feinc], link_with: [felib])
executable('fe-seek', ['src/fe-seek.cc'], include_directories: [feinc], link_with: [felib])
executable('fe-testbulktransport', ['src/fe-testbulktransport.cc'], include_directories: [feinc], link_with: [felib])
@@ -135,6 +145,7 @@ executable('fe-writetestpattern', ['src/fe-writetestpattern.cc'], include_dire
executable('brother120tool', ['tools/brother120tool.cc'], include_directories: [feinc, fmtinc], link_with: [felib, fmtlib])
test('DataSpec', executable('dataspec-test', ['tests/dataspec.cc'], include_directories: [feinc], link_with: [felib]))
test('Flags', executable('flags-test', ['tests/flags.cc'], include_directories: [feinc], link_with: [felib]))
test('FmMfm', executable('fmmfm-test', ['tests/fmmfm.cc'], include_directories: [feinc, decoderinc], link_with: [felib, decoderlib]))
test('DataSpec', executable('dataspec-test', ['tests/dataspec.cc'], include_directories: [feinc], link_with: [felib]))
test('Flags', executable('flags-test', ['tests/flags.cc'], include_directories: [feinc], link_with: [felib]))
test('FmMfm', executable('fmmfm-test', ['tests/fmmfm.cc'], include_directories: [feinc, decoderinc], link_with: [felib, decoderlib]))
test('BitAccumulator', executable('bitaccumulator-test', ['tests/bitaccumulator.cc'], include_directories: [feinc], link_with: [felib]))

View File

@@ -25,6 +25,11 @@ static IntFlag fluxmapResolutionFlag(
"Resolution of flux visualisation (nanoseconds). 0 to autoscale",
0);
static DoubleFlag seekFlag(
{ "--seek", "-S" },
"Seek this many milliseconds into the track before displaying it.",
0.0);
int main(int argc, const char* argv[])
{
Flag::parseFlags(argc, argv);
@@ -54,13 +59,29 @@ int main(int argc, const char* argv[])
if (resolution == 0)
resolution = clockPeriod / 4;
nanoseconds_t now = 0;
nanoseconds_t nextclock = clockPeriod;
nanoseconds_t now = 0;
int cursor = 0;
nanoseconds_t seekto = seekFlag*1000000.0;
int ticks = 0;
std::cout << fmt::format("{: 10.3f}:-", 0.0);
for (int cursor=0; cursor<fluxmap->bytes(); cursor++)
while (cursor < fluxmap->bytes())
{
int interval = (*fluxmap)[cursor];
int interval = (*fluxmap)[cursor++];
if (interval == 0)
interval = 0x100;
ticks += interval;
now = ticks * NS_PER_TICK;
if (now >= seekto)
break;
}
std::cout << fmt::format("{: 10.3f}:-", ticks*US_PER_TICK);
nanoseconds_t lasttransition = 0;
while (cursor < fluxmap->bytes())
{
int interval = (*fluxmap)[cursor++];
if (interval == 0)
interval = 0x100;
ticks += interval;
@@ -82,7 +103,12 @@ int main(int argc, const char* argv[])
now = next;
}
std::cout << fmt::format("==== {: 10.3f}", (double)transition / 1000.0);
nanoseconds_t length = transition - lasttransition;
std::cout << fmt::format("==== {: 10.3f} +{:.3f} = {:.1f} clocks",
(double)transition / 1000.0,
(double)length / 1000.0,
(double)length / clockPeriod);
lasttransition = transition;
}
}
@@ -92,8 +118,13 @@ int main(int argc, const char* argv[])
<< " follows:" << std::endl
<< std::endl;
for (bool bit : bitmap)
std::cout << (bit ? 'X' : '-');
size_t cursor = seekFlag*1000000.0 / clockPeriod;
while (cursor < bitmap.size())
{
std::cout << (bitmap[cursor] ? 'X' : '-');
cursor++;
}
std::cout << std::endl;
}

29
src/fe-readmac.cc Normal file
View File

@@ -0,0 +1,29 @@
#include "globals.h"
#include "flags.h"
#include "reader.h"
#include "fluxmap.h"
#include "decoders.h"
#include "macintosh.h"
#include "sector.h"
#include "sectorset.h"
#include "image.h"
#include "record.h"
#include <fmt/format.h>
#include <fstream>
static StringFlag outputFilename(
{ "--output", "-o" },
"The output image file to write to.",
"mac.img");
int main(int argc, const char* argv[])
{
setReaderDefaultSource(":t=0-79:s=0-1");
setReaderRevolutions(2);
Flag::parseFlags(argc, argv);
MacintoshDecoder decoder;
readDiskCommand(decoder, outputFilename);
return 0;
}

15
tests/bitaccumulator.cc Normal file
View File

@@ -0,0 +1,15 @@
#include "globals.h"
#include "bytes.h"
#include <assert.h>
int main(int argc, const char* argv[])
{
BitAccumulator ba;
ba.reset();
ba.push(0x1e, 5);
assert((std::vector<uint8_t>)ba == std::vector<uint8_t>{ 0x1e });
return 0;
}