mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	We can now decode IBM MFM disks all the way to an image, although with no CRC
checking as yet.
This commit is contained in:
		
							
								
								
									
										89
									
								
								lib/decoders/decoders.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								lib/decoders/decoders.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "protocol.h" | ||||
|  | ||||
| static IntFlag clockDetectionNoiseFloor( | ||||
|     { "--clock-detection-noise-floor" }, | ||||
|     "Noise floor used for clock detection in flux.", | ||||
|     50); | ||||
|  | ||||
| static DoubleFlag clockDecodeThreshold( | ||||
|     { "--clock-decode-threshold" }, | ||||
|     "Pulses below this fraction of a clock tick are considered spurious and ignored.", | ||||
|     0.80); | ||||
|  | ||||
| /*  | ||||
|  * Tries to guess the clock by finding the smallest common interval. | ||||
|  * Returns nanoseconds. | ||||
|  */ | ||||
| nanoseconds_t Fluxmap::guessClock() const | ||||
| { | ||||
|     uint32_t buckets[256] = {}; | ||||
|     for (uint8_t interval : _intervals) | ||||
|         buckets[interval]++; | ||||
|      | ||||
|     int peaklo = 0; | ||||
|     while (peaklo < 256) | ||||
|     { | ||||
|         if (buckets[peaklo] > 100) | ||||
|             break; | ||||
|         peaklo++; | ||||
|     } | ||||
|  | ||||
|     uint32_t peakmaxindex = peaklo; | ||||
|     uint32_t peakmaxvalue = buckets[peakmaxindex]; | ||||
|     uint32_t peakhi = peaklo; | ||||
|     while (peakhi < 256) | ||||
|     { | ||||
|         uint32_t v = buckets[peakhi]; | ||||
|         if (buckets[peakhi] < (uint32_t)clockDetectionNoiseFloor) | ||||
|             break; | ||||
|         if (v > peakmaxvalue) | ||||
|         { | ||||
|             peakmaxindex = peakhi; | ||||
|             peakmaxvalue = v; | ||||
|         } | ||||
|         peakhi++; | ||||
|     } | ||||
|  | ||||
|     /*  | ||||
|      * Okay, peakmaxindex 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; | ||||
| } | ||||
|  | ||||
| /* Decodes a fluxmap into a nice aligned array of bits. */ | ||||
| std::vector<bool> decodeFluxmapToBits(const Fluxmap& fluxmap, nanoseconds_t clockPeriod) | ||||
| { | ||||
|     int pulses = fluxmap.duration() / clockPeriod; | ||||
|     nanoseconds_t lowerThreshold = clockPeriod * clockDecodeThreshold; | ||||
|  | ||||
|     std::vector<bool> bitmap(pulses); | ||||
|     unsigned count = 0; | ||||
|     int cursor = 0; | ||||
|     nanoseconds_t timestamp = 0; | ||||
|     for (;;) | ||||
|     { | ||||
|         while (timestamp < lowerThreshold) | ||||
|         { | ||||
|             if (cursor >= fluxmap.bytes()) | ||||
|                 goto abort; | ||||
|             uint8_t interval = fluxmap[cursor++]; | ||||
|             timestamp += interval * NS_PER_TICK; | ||||
|         } | ||||
|  | ||||
|         int clocks = (timestamp + clockPeriod/2) / clockPeriod; | ||||
|         count += clocks; | ||||
|         if (count >= bitmap.size()) | ||||
|             goto abort; | ||||
|         bitmap[count] = true; | ||||
|         timestamp = 0; | ||||
|     } | ||||
| abort: | ||||
|  | ||||
|     return bitmap; | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								lib/decoders/decoders.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/decoders/decoders.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| #ifndef DECODERS_H | ||||
| #define DECODERS_H | ||||
|  | ||||
| /* IBM format (i.e. ordinary PC floppies). */ | ||||
|  | ||||
| #define IBM_IAM 0xFC   /* start-of-track record */ | ||||
| #define IBM_IAM_LEN    4 | ||||
| #define IBM_IDAM 0xFE  /* sector header */ | ||||
| #define IBM_IDAM_LEN   10 | ||||
| #define IBM_DAM1 0xF8  /* sector data (type 1) */ | ||||
| #define IBM_DAM2 0xFB  /* sector data (type 2) */ | ||||
| #define IBM_DAM_LEN    6 /* plus user data */ | ||||
| /* Length of a DAM record is determined by the previous sector header. */ | ||||
|  | ||||
| struct IbmIdam | ||||
| { | ||||
|     uint8_t marker[3]; | ||||
|     uint8_t id; | ||||
|     uint8_t cylinder; | ||||
|     uint8_t side; | ||||
|     uint8_t sector; | ||||
|     uint8_t sectorSize; | ||||
|     uint16_t crcBE; | ||||
| }; | ||||
|  | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| extern std::vector<bool> decodeFluxmapToBits(const Fluxmap& fluxmap, nanoseconds_t clock_period); | ||||
|  | ||||
| extern std::vector<std::vector<uint8_t>> decodeBitsToRecordsMfm(const std::vector<bool>& bitmap); | ||||
|  | ||||
| extern std::vector<std::unique_ptr<Sector>> decodeIbmRecordsToSectors(const std::vector<std::vector<uint8_t>>& records); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										59
									
								
								lib/decoders/ibm.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								lib/decoders/ibm.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders.h" | ||||
| #include "image.h" | ||||
| #include <string.h> | ||||
|  | ||||
| static_assert(std::is_trivially_copyable<IbmIdam>::value); | ||||
|  | ||||
| std::vector<std::unique_ptr<Sector>> decodeIbmRecordsToSectors(const std::vector<std::vector<uint8_t>>& records) | ||||
| { | ||||
|     bool idamValid = false; | ||||
|     IbmIdam idam; | ||||
|     std::vector<std::unique_ptr<Sector>> sectors; | ||||
|  | ||||
|     for (auto& record : records) | ||||
|     { | ||||
|         switch (record[3]) | ||||
|         { | ||||
|             case IBM_IAM: | ||||
|                 /* Track header. Ignore. */ | ||||
|                 break; | ||||
|  | ||||
|             case IBM_IDAM: | ||||
|             { | ||||
|                 if (record.size() < sizeof(idam)) | ||||
|                     goto garbage; | ||||
|                 memcpy(&idam, &record[0], sizeof(idam)); | ||||
|                 idamValid = true; | ||||
|                 /* TODO: check CRC! */ | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             case IBM_DAM1: | ||||
|             case IBM_DAM2: | ||||
|             { | ||||
|                 if (!idamValid) | ||||
|                     goto garbage; | ||||
|                  | ||||
|                 unsigned size = 1 << (idam.sectorSize + 7); | ||||
|                 if ((record.size()-IBM_DAM_LEN) < size) | ||||
|                     goto garbage; | ||||
|                 /* TODO: check CRC! */ | ||||
|  | ||||
|                 std::vector<uint8_t> sectordata(size); | ||||
|                 memcpy(§ordata[0], &record[4], size); | ||||
|  | ||||
|                 auto sector = std::unique_ptr<Sector>(new Sector(idam.cylinder, idam.side, idam.sector-1, sectordata)); | ||||
|                 sectors.push_back(std::move(sector)); | ||||
|                 idamValid = false; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             default: | ||||
|             garbage: | ||||
|                 Error() << "garbage record on disk (this diagnostic needs improving)"; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return sectors; | ||||
| } | ||||
							
								
								
									
										97
									
								
								lib/decoders/mfm.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								lib/decoders/mfm.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "protocol.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| #define CLOCK_LOCK_BOOST 6 /* arbitrary */ | ||||
| #define CLOCK_LOCK_DECAY 1 /* arbitrary */ | ||||
| #define CLOCK_DETECTOR_AMPLITUDE_THRESHOLD 60 /* arbi4rary */ | ||||
| #define CLOCK_ERROR_BOUNDS 0.25 | ||||
|  | ||||
| static unsigned cursor; | ||||
| static std::vector<uint8_t> outputbuffer; | ||||
| static uint8_t outputfifo = 0; | ||||
| static int bitcount = 0; | ||||
| static bool phase = false; | ||||
|  | ||||
| static void write_bit(bool bit) | ||||
| { | ||||
|     outputfifo = (outputfifo << 1) | bit; | ||||
|     bitcount++; | ||||
|     if (bitcount == 8) | ||||
|     { | ||||
|         outputbuffer.push_back(outputfifo); | ||||
|         bitcount = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::vector<uint8_t>> decodeBitsToRecordsMfm(const std::vector<bool>& bits) | ||||
| { | ||||
|     std::vector<std::vector<uint8_t>> records; | ||||
|  | ||||
|     cursor = 0; | ||||
|     uint64_t inputfifo = 0; | ||||
|     bool reading = false; | ||||
|  | ||||
|     while (cursor < bits.size()) | ||||
|     { | ||||
|         bool bit = bits[cursor++]; | ||||
|         inputfifo = (inputfifo << 1) | bit; | ||||
|  | ||||
|         /*  | ||||
|          * The IAM record, which is the first one on the disk (and is optional), uses | ||||
|          * a distorted 0xC2 0xC2 0xC2 marker to identify it. Unfortunately, if this is | ||||
|          * shifted out of phase, it becomes a legal encoding, so if we're looking at | ||||
|          * real data we can't honour this. | ||||
|          *  | ||||
|          * 0xC2 is: | ||||
|          * data:    1  1  0  0  0  0  1 0 | ||||
|          * mfm:     01 01 00 10 10 10 01 00 = 0x5254 | ||||
|          * special: 01 01 00 10 00 10 01 00 = 0x5224 | ||||
|          *                    ^^^^ | ||||
|          * shifted: 10 10 01 00 01 00 10 0. = legal, and might happen in real data | ||||
|          *  | ||||
|          * Therefore, when we've read the marker, the input fifo will contain | ||||
|          * 0xXXXX522252225222. | ||||
|          *  | ||||
|          * All other records use 0xA1 as a marker: | ||||
|          *  | ||||
|          * 0xA1  is: | ||||
|          * data:    1  0  1  0  0  0  0  1 | ||||
|          * mfm:     01 00 01 00 10 10 10 01 = 0x44A9 | ||||
|          * special: 01 00 01 00 10 00 10 01 = 0x4489 | ||||
|          *                       ^^^^^ | ||||
|          * shifted: 10 00 10 01 00 01 00 1 | ||||
|          *  | ||||
|          * When this is shifted out of phase, we get an illegal encoding (you | ||||
|          * can't do 10 00). So, if we ever see 0x448944894489 in the input | ||||
|          * fifo, we know we've landed at the beginning of a new record. | ||||
|          */ | ||||
|           | ||||
|         uint64_t masked = inputfifo & 0xFFFFFFFFFFFFLL; | ||||
|         if ((!reading && (masked == 0x522452245224LL)) || (masked == 0x448944894489LL)) | ||||
|         { | ||||
|             if (reading) | ||||
|                 records.push_back(outputbuffer); | ||||
|  | ||||
|             outputbuffer.resize(3); | ||||
|             std::fill(outputbuffer.begin(), outputbuffer.begin()+3, reading ? 0xA1 : 0xC2); | ||||
|  | ||||
|             reading = true; | ||||
|             bitcount = 0; | ||||
|             phase = 0; | ||||
|         } | ||||
|         else if (reading) | ||||
|         { | ||||
|             if (phase) | ||||
|                 write_bit(bit); | ||||
|             phase = !phase; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (reading) | ||||
|         records.push_back(outputbuffer); | ||||
|  | ||||
|     return records; | ||||
| } | ||||
							
								
								
									
										12
									
								
								lib/flags.h
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								lib/flags.h
									
									
									
									
									
								
							| @@ -101,4 +101,16 @@ public: | ||||
|     void set(const std::string value) { _value = std::stoi(value); } | ||||
| }; | ||||
|  | ||||
| class DoubleFlag : public ValueFlag<double> | ||||
| { | ||||
| public: | ||||
|     DoubleFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             double defaultValue = 1.0): | ||||
|         ValueFlag(names, helptext, defaultValue) | ||||
|     {} | ||||
|  | ||||
|     const std::string defaultValue() const { return std::to_string(_defaultValue); } | ||||
|     void set(const std::string value) { _value = std::stod(value); } | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -16,6 +16,8 @@ public: | ||||
|     Fluxmap& appendIntervals(std::vector<uint8_t>& intervals); | ||||
|     Fluxmap& appendIntervals(const uint8_t* ptr, size_t len); | ||||
|  | ||||
|     nanoseconds_t guessClock() const; | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _duration = 0; | ||||
|     int _ticks = 0; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "globals.h" | ||||
| #include <sys/time.h> | ||||
| #include <stdarg.h> | ||||
|  | ||||
| double getCurrentTime(void) | ||||
| { | ||||
| @@ -7,5 +8,4 @@ double getCurrentTime(void) | ||||
| 	gettimeofday(&tv, NULL); | ||||
|  | ||||
| 	return double(tv.tv_sec) + tv.tv_usec/1000000.0; | ||||
| } | ||||
|  | ||||
| } | ||||
							
								
								
									
										40
									
								
								lib/image.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/image.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| #include "globals.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| void writeSectorsToFile(const std::vector<std::unique_ptr<Sector>>& sectors, const std::string& filename) | ||||
| { | ||||
|     /* Count the tracks, sides and sectors. */ | ||||
|  | ||||
|     int trackCount = 0; | ||||
|     int sideCount = 0; | ||||
|     int sectorCount = 0; | ||||
|     size_t sectorSize = 0; | ||||
|     for (auto& sector : sectors) | ||||
|     { | ||||
|         trackCount = std::max(sector->track()+1, trackCount); | ||||
|         sideCount = std::max(sector->side()+1, sideCount); | ||||
|         sectorCount = std::max(sector->sector()+1, sectorCount); | ||||
|         sectorSize = std::max(sector->data().size(), sectorSize); | ||||
|     } | ||||
|  | ||||
|     size_t sideSize = sectorCount * sectorSize; | ||||
|     size_t trackSize = sideSize * sideCount; | ||||
|  | ||||
|     std::cout << fmt::format("{} tracks, {} sides, {} sectors, {} bytes per sector, {} kB total", | ||||
|         trackCount, sideCount, sectorCount, sectorSize, | ||||
|         trackCount * sideCount * sectorCount * sectorSize / 1024); | ||||
|  | ||||
|     std::ofstream outputFile(filename, std::ios::out | std::ios::binary); | ||||
|     if (!outputFile.is_open()) | ||||
|         Error() << "cannot open output file"; | ||||
|  | ||||
|     for (auto& sector : sectors) | ||||
|     { | ||||
|         outputFile.seekp(sector->track()*trackSize + sector->side()*sideSize + sector->sector()*sectorSize, std::ios::beg); | ||||
|         outputFile.write((const char*) §or->data().at(0), sector->data().size()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								lib/image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/image.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| #ifndef IMAGE_H | ||||
| #define IMAGE_H | ||||
|  | ||||
| /*  | ||||
|  * Note that sectors here used zero-based numbering throughout (to make the | ||||
|  * maths easier); traditionally floppy disk use 0-based track numbering and | ||||
|  * 1-based sector numbering, which makes no sense. | ||||
|  */ | ||||
| class Sector | ||||
| { | ||||
| public: | ||||
|     Sector(int track, int side, int sector, const std::vector<uint8_t>& data): | ||||
|         _track(track), | ||||
|         _side(side), | ||||
|         _sector(sector), | ||||
|         _data(data) | ||||
|     {} | ||||
|  | ||||
|     int track() const { return _track; } | ||||
|     int side() const { return _side; } | ||||
|     int sector() const { return _sector; } | ||||
|     const std::vector<uint8_t>& data() const { return _data; } | ||||
|  | ||||
| private: | ||||
|     const int _track; | ||||
|     const int _side; | ||||
|     const int _sector; | ||||
|     const std::vector<uint8_t> _data; | ||||
| }; | ||||
|  | ||||
| extern void writeSectorsToFile(const std::vector<std::unique_ptr<Sector>>& sectors, const std::string& filename); | ||||
|  | ||||
| #endif | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "reader.h" | ||||
| #include "fluxmap.h" | ||||
| #include "sql.h" | ||||
| #include "fmt/format.h" | ||||
| #include <regex> | ||||
|  | ||||
| static const std::regex SOURCE_REGEX("([^:]*)" | ||||
| @@ -40,7 +41,9 @@ Fluxmap& Track::read() | ||||
| { | ||||
|     if (!_read) | ||||
|     { | ||||
|         std::cout << fmt::format("{0:>3}.{1}: ", track, side) << std::flush; | ||||
|         reallyRead(); | ||||
|         std::cout << fmt::format("{0} ms in {1} bytes", int(_fluxmap->duration()/1e6), _fluxmap->bytes()) << std::endl; | ||||
|         _read = true; | ||||
|     } | ||||
|     return *_fluxmap.get(); | ||||
| @@ -53,22 +56,16 @@ void Track::forceReread() | ||||
|  | ||||
| void CapturedTrack::reallyRead() | ||||
| { | ||||
|     std::cout << "read track " << track << " side " << side << ": " << std::flush; | ||||
|  | ||||
|     usbSeek(track); | ||||
|     _fluxmap = usbRead(side, revolutions); | ||||
|     std::cout << int(_fluxmap->duration()/1e6) << "ms in " << _fluxmap->bytes() << " bytes" << std::endl; | ||||
| } | ||||
|  | ||||
| void FileTrack::reallyRead() | ||||
| { | ||||
|     std::cout << "read track " << track << " side " << side << ": " << std::flush; | ||||
|  | ||||
|     if (!db) | ||||
|         db = sqlOpen(basefilename, SQLITE_OPEN_READONLY); | ||||
|     _fluxmap = sqlReadFlux(db, track, side); | ||||
|  | ||||
|     std::cout << int(_fluxmap->duration()/1e6) << "ms in " << _fluxmap->bytes() << " bytes" << std::endl; | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<Track>> readTracks() | ||||
|   | ||||
							
								
								
									
										30
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								meson.build
									
									
									
									
									
								
							| @@ -24,23 +24,43 @@ executable('fluxclient', | ||||
|     dependencies: [libusb, sqlite] | ||||
| ) | ||||
|  | ||||
| fmtlib = shared_library('fmtlib', | ||||
|     [ | ||||
|         'dep/fmt/format.cc', | ||||
|         'dep/fmt/posix.cc' | ||||
|     ]) | ||||
| fmtinc = include_directories('dep/fmt') | ||||
|  | ||||
| felib = shared_library('felib', | ||||
|     [ | ||||
|         'lib/flags.cc', | ||||
|         'lib/fluxmap.cc', | ||||
|         'lib/globals.cc', | ||||
|         'lib/usb.cc', | ||||
|         'lib/image.cc', | ||||
|     ], | ||||
|     include_directories: [fmtinc], | ||||
|     link_with: [fmtlib], | ||||
|     dependencies: [libusb] | ||||
| ) | ||||
|  | ||||
| sqllib = shared_library('sqllib', ['lib/sql.cc'], link_with: [felib], dependencies: [sqlite]) | ||||
| readerlib = shared_library('readerlib', ['lib/reader.cc'], link_with: [felib, sqllib]) | ||||
|  | ||||
| feinc = include_directories('lib') | ||||
|  | ||||
| sqllib =     shared_library('sqllib',     ['lib/sql.cc'],                                     link_with: [felib], dependencies: [sqlite]) | ||||
| readerlib =  shared_library('readerlib',  ['lib/reader.cc'],   include_directories: [fmtinc], link_with: [felib, sqllib, fmtlib]) | ||||
|  | ||||
| decoderlib = shared_library('decoderlib', | ||||
|     [ | ||||
|         'lib/decoders/decoders.cc', | ||||
|         'lib/decoders/mfm.cc', | ||||
|         'lib/decoders/ibm.cc' | ||||
|     ], | ||||
|     include_directories: [feinc], | ||||
|     link_with: [felib] | ||||
| ) | ||||
| decoderinc = include_directories('lib/decoders') | ||||
|  | ||||
| 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]) | ||||
| executable('fe-readibm',           ['src/fe-readibm.cc'],           include_directories: [feinc], link_with: [felib, readerlib]) | ||||
| executable('fe-readibm',           ['src/fe-readibm.cc'],           include_directories: [feinc, fmtinc, decoderinc], link_with: [felib, readerlib, decoderlib, fmtlib]) | ||||
|  | ||||
|   | ||||
| @@ -2,16 +2,47 @@ | ||||
| #include "flags.h" | ||||
| #include "reader.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders.h" | ||||
| #include "image.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| static StringFlag outputFilename( | ||||
|     { "--output", "-o" }, | ||||
|     "The output image file to write to.", | ||||
|     "ibm.img"); | ||||
|  | ||||
| int main(int argc, const char* argv[]) | ||||
| { | ||||
|     Flag::parseFlags(argc, argv); | ||||
|  | ||||
|     std::vector<std::unique_ptr<Sector>> allSectors; | ||||
|     for (auto& track : readTracks()) | ||||
|     { | ||||
|         track->read(); | ||||
|         std::cout << "track " << track->track << " " << track->side << std::endl; | ||||
|         Fluxmap& fluxmap = track->read(); | ||||
|  | ||||
|         nanoseconds_t clockPeriod = fluxmap.guessClock(); | ||||
|         std::cout << fmt::format("       {:.1f} us clock; ", (double)clockPeriod/1000.0) << std::flush; | ||||
|  | ||||
|         /* For MFM, the bit clock is half the detected clock. */ | ||||
|         auto bitmap = decodeFluxmapToBits(fluxmap, clockPeriod/2); | ||||
|         std::cout << fmt::format("{} bytes encoded; ", bitmap.size()/8) << std::flush; | ||||
|  | ||||
|         auto records = decodeBitsToRecordsMfm(bitmap); | ||||
|         std::cout << records.size() << " records." << std::endl; | ||||
|  | ||||
|         auto sectors = decodeIbmRecordsToSectors(records); | ||||
|         std::cout << "       " << sectors.size() << " sectors; "; | ||||
|  | ||||
|         int size = 0; | ||||
|         for (auto& sector : sectors) | ||||
|         { | ||||
|             size += sector->data().size(); | ||||
|             allSectors.push_back(std::move(sector)); | ||||
|         } | ||||
|         std::cout << size << " bytes decoded." << std::endl; | ||||
|     } | ||||
|  | ||||
|     writeSectorsToFile(allSectors, outputFilename); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user