From a1bb8b9dd86adac733b131d075b472f5d075a429 Mon Sep 17 00:00:00 2001 From: Christopher Mosher Date: Tue, 1 Jan 2019 09:12:19 -0500 Subject: [PATCH] expand and reduce track image in memory --- src/steppermotor.cpp | 42 ++++-- src/wozfile.cpp | 306 +++++++++++++++++++++++++++++++++++-------- src/wozfile.h | 12 ++ 3 files changed, 289 insertions(+), 71 deletions(-) diff --git a/src/steppermotor.cpp b/src/steppermotor.cpp index e258076..621a755 100644 --- a/src/steppermotor.cpp +++ b/src/steppermotor.cpp @@ -83,7 +83,7 @@ void StepperMotor::setMagnet(const unsigned char magnet, const bool on) { this->mags &= ~mask; } -// const std::uint8_t oldQT = this->quarterTrack; + const std::uint8_t oldQT = this->quarterTrack; const char newPos = mapMagPos[this->mags]; char d = 0; @@ -91,6 +91,20 @@ void StepperMotor::setMagnet(const unsigned char magnet, const bool on) { d = calcDeltaPos(this->pos,newPos); this->pos = newPos; + // TODO: delay moving arm by a small amount + // For example, Locksmith, in order to write "quarter tracks" (i.e., T+.25 or T+.75), it positions + // to the correct track (by turning two adjacent magnets on), then turns them both off in rapid + // succession. In real life the arm doesn't move in such a case. In order to emulate that, we need + // to delay the arm move for a bit, to see if the magnets change in the meantime. + /* + * ARM: ph2 + [..*.] T$0D.00 +0.00 + * ARM: ph3 + [..**] T$0D.25 --> +0.25 + * switching from tmap[34] --> [35] + * + * ARM: ph3 - [..*.] T$0D.00 <-- -0.25 <-\ + * switching from tmap[35] --> [34] <--\-- this needs to get delayed + * ARM: ph2 - [....] T$0D.00 +0.00 + */ this->quarterTrack += d; if (this->quarterTrack < 0) this->quarterTrack = 0; @@ -98,18 +112,18 @@ void StepperMotor::setMagnet(const unsigned char magnet, const bool on) { this->quarterTrack = QTRACKS-1; } -// const std::uint8_t newQT = this->quarterTrack; -// const std::int8_t deltaQT = newQT - oldQT; + const std::uint8_t newQT = this->quarterTrack; + const std::int8_t deltaQT = newQT - oldQT; -// printf("ARM: ph%d %s [%c%c%c%c] T$%02X.%02d %s %+0.2f\n", -// (std::uint8_t)magnet, -// on ? "+" : "-", -// (mags&1)?'*':'.', -// (mags&2)?'*':'.', -// (mags&4)?'*':'.', -// (mags&8)?'*':'.', -// this->quarterTrack / 4, -// (this->quarterTrack % 4) * 25, -// deltaQT>0 ? "-->" : deltaQT<0 ? "<--" : " ", -// (deltaQT % 4) / 4.0); + printf("ARM: ph%d %s [%c%c%c%c] T$%02X.%02d %s %+0.2f\n", + (std::uint8_t)magnet, + on ? "+" : "-", + (mags&1)?'*':'.', + (mags&2)?'*':'.', + (mags&4)?'*':'.', + (mags&8)?'*':'.', + this->quarterTrack / 4, + (this->quarterTrack % 4) * 25, + deltaQT>0 ? "-->" : deltaQT<0 ? "<--" : " ", + (deltaQT % 4) / 4.0); } diff --git a/src/wozfile.cpp b/src/wozfile.cpp index caea0f3..7ab98b1 100644 --- a/src/wozfile.cpp +++ b/src/wozfile.cpp @@ -58,8 +58,61 @@ static void print_compat(std::uint16_t compat, std::uint16_t mask, const char *n } } +void WozFile::dumpTmap() { + printf("\x1b[31;47m-------------------------------------------------\x1b[0m\n"); + for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { + const std::uint16_t t(qt*25); + if (this->tmap[qt] == 0xFFu) { + printf("\x1b[31;47m"); + } + if (t % 100) { + printf("TMAP[0x%02X] track 0x%02X +.%02d: TRKS track index 0x%02X", qt, t/100, t%100, this->tmap[qt]); + } else { + printf("TMAP[0x%02X] track 0x%02X : TRKS track index 0x%02X", qt, t/100, this->tmap[qt]); + } + printf("\x1b[0m"); + if (qt == this->initialQtrack && qt == this->finalQtrack) { + printf(" <-- lone track"); + } else if (qt == this->initialQtrack) { + printf(" <-- initial track"); + } else if (qt == this->finalQtrack) { + printf(" <-- final track"); + } + printf("\n"); + } + printf("\x1b[31;47m-------------------------------------------------\x1b[0m\n"); +} + +void WozFile::dumpTracks() { + for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { + if (this->trk[qt]) { + printf("TRK index %02X: %08x bytes; %08x bits ", qt, this->trk_byts[qt], this->trk_bits[qt]); + printf("(" + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + BYTE_TO_BINARY_PATTERN + "...)\n", + BYTE_TO_BINARY(this->trk[qt][0]), + BYTE_TO_BINARY(this->trk[qt][1]), + BYTE_TO_BINARY(this->trk[qt][2]), + BYTE_TO_BINARY(this->trk[qt][3]), + BYTE_TO_BINARY(this->trk[qt][4]), + BYTE_TO_BINARY(this->trk[qt][5]), + BYTE_TO_BINARY(this->trk[qt][6]), + BYTE_TO_BINARY(this->trk[qt][7])); + } + } +} + bool WozFile::load(const std::string& filePath) { - std::ifstream in(filePath.c_str(),std::ios::binary|std::ios::in); + printf("Reading WOZ 2.0 file: %s\n", filePath.c_str()); + + std::ifstream in(filePath.c_str(), std::ios::binary|std::ios::in); if (!in.is_open()) { printf("Error opening file: %d\n", errno); return false; @@ -159,28 +212,7 @@ bool WozFile::load(const std::string& filePath) { printf("Could not find any final track (%02X).\n", this->finalQtrack); } - printf("\x1b[31;47m-------------------------------------------\x1b[0m\n"); - for (std::uint8_t qt(0); qt < chunk_size; ++qt) { - const std::uint16_t t(qt*25); - if (tmap[qt] == 0xFFu) { - printf("\x1b[31;47m"); - } - if (t % 100) { - printf("TMAP track 0x%02X +.%02d: TRKS track index 0x%02X", t/100, t%100, tmap[qt]); - } else { - printf("TMAP track 0x%02X : TRKS track index 0x%02X", t/100, tmap[qt]); - } - printf("\x1b[0m"); - if (qt == this->initialQtrack && qt == this->finalQtrack) { - printf(" <-- lone track"); - } else if (qt == this->initialQtrack) { - printf(" <-- initial track"); - } else if (qt == this->finalQtrack) { - printf(" <-- final track"); - } - printf("\n"); - } - printf("\x1b[31;47m-------------------------------------------\x1b[0m\n"); + dumpTmap(); } break; case 0x534B5254: { // TRKS @@ -264,21 +296,7 @@ bool WozFile::load(const std::string& filePath) { this->loaded = true; this->modified = false; - // TODO if the file is not write protected, then "fill out" our tracks: - // 1. create new TRKS entries and full copies of duplicate TMAP pointers - // 2. create new TRKS entries (and fill with zeroes) for FF entries in TMAP. (with length of avg of adjacent tracks?) - // - // ... - // 6.50: FF x 08 - 00 00 00 - // 6.75: 02 \ 02 - d5 aa 96 - // 7.00: 02 - d5 aa 96 03 - d5 aa 96 - // 7.25: 02 / 04 - d5 aa 96 - // 7.50: FF x 09 - 00 00 00 - // 7.75: 05 \ 05 - d5 aa 96 - // 8.00: 05 - d5 aa 96 06 - d5 aa 96 - // 8.25: 05 / 07 - d5 aa 96 - // 8.50: FF x 0A - 00 00 00 - // ... + expandTracks(); return true; } @@ -297,6 +315,11 @@ void WozFile::save() { if (isWriteProtected() || !isLoaded()) { return; } + + printf("Saving WOZ 2.0 file: %s\n", filePath.c_str()); + + reduceTracks(); + std::ofstream out(filePath.c_str(), std::ios::binary); std::uint32_t woz2(0x325A4F57u); @@ -340,6 +363,7 @@ void WozFile::save() { std::uint16_t ram(0); out.write((char*)&ram, sizeof(ram)); + std::uint16_t largest_track(0); for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { if (largest_track < (this->trk_byts[qt]>>9)) { @@ -376,10 +400,16 @@ void WozFile::save() { uint16_t block(3); for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { struct trk_t ts; - ts.blockFirst = block; - ts.blockCount = (this->trk_byts[qt]>>9); - block += ts.blockCount; - ts.bitCount = this->trk_bits[qt]; + if (this->trk_byts[qt]) { + ts.blockFirst = block; + ts.blockCount = (this->trk_byts[qt]>>9); + block += ts.blockCount; + ts.bitCount = this->trk_bits[qt]; + } else { + ts.blockFirst = 0; + ts.blockCount = 0; + ts.bitCount = 0; + } out.write((char*)&ts, sizeof(ts)); } @@ -393,8 +423,9 @@ void WozFile::save() { out.close(); this->modified = false; -} + expandTracks(); +} void WozFile::unload() { this->bit = 0x80u; this->byt = 0x00u; @@ -402,18 +433,13 @@ void WozFile::unload() { this->loaded = false; this->filePath = ""; this->modified = false; + for (int i(0); i < C_QTRACK; ++i) { + removeTrack(i); + } if (this->tmap) { delete [] this->tmap; this->tmap = 0; } - for (int i(0); i < C_QTRACK; ++i) { - if (this->trk[i]) { - delete [] this->trk[i]; - this->trk[i] = 0; - } - this->trk_bits[i] = 0; - this->trk_byts[i] = 0; - } } static std::uint8_t bc(std::uint8_t bit) { @@ -485,6 +511,7 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) { // previous, based on each track's length (tracks can be of // different lengths in the WOZ image). if (currentQuarterTrack != this->lastQuarterTrack) { + printf("switching from tmap[%02x] --> [%02x]\n", this->lastQuarterTrack, currentQuarterTrack); const double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]]; const double newLen = this->trk_bits[this->tmap[currentQuarterTrack]]; const double ratio = newLen/oldLen; @@ -521,6 +548,14 @@ bool WozFile::getBit(std::uint8_t currentQuarterTrack) { return this->trk[this->tmap[currentQuarterTrack]][this->byt] & this->bit; } +void WozFile::rawSet(std::uint8_t currentQuarterTrack, bool on) { + if (on) { + this->trk[this->tmap[currentQuarterTrack]][this->byt] |= this->bit; + } else { + this->trk[this->tmap[currentQuarterTrack]][this->byt] &= ~this->bit; + } +} + void WozFile::setBit(std::uint8_t currentQuarterTrack, bool on) { if (!isLoaded()) { return; // there's no disk to write data to @@ -531,13 +566,170 @@ void WozFile::setBit(std::uint8_t currentQuarterTrack, bool on) { } // printf("%c",(on?'1':'0')); fflush(stdout); - if (on) { - this->trk[this->tmap[currentQuarterTrack]][this->byt] |= this->bit; - } else { - this->trk[this->tmap[currentQuarterTrack]][this->byt] &= ~this->bit; + rawSet(currentQuarterTrack, on); + + + + // also write preceding and following quarter tracks (at relative position, and if they exist) + if (0 < currentQuarterTrack) { + rawSet(currentQuarterTrack-1, on); + } + if (currentQuarterTrack < C_QTRACK-1) { + rawSet(currentQuarterTrack+1, on); } this->modified = true; - - // TODO: also write preceding and following quarter tracks (at relative position, and if they exist) +} + +void WozFile::removeTrack(const int trackIndex) { + if (this->trk[trackIndex]) { + delete [] this->trk[trackIndex]; + this->trk[trackIndex] = 0; + } + this->trk_bits[trackIndex] = 0; + this->trk_byts[trackIndex] = 0; +} + +bool WozFile::trackIsZeroes(std::uint8_t qt) { + for (std::uint16_t byt = 0; byt < this->trk_byts[qt]; ++byt) { + if (this->trk[qt][byt]) { + return false; + } + } + return true; +} + +bool WozFile::tracksAreIdentical(std::uint8_t qt1, std::uint8_t qt2) { + if (this->trk_bits[qt1] != this->trk_bits[qt2]) { + return false; + } + for (std::uint16_t byt = 0; byt < this->trk_byts[qt1]; ++byt) { + if (this->trk[qt1][byt] != this->trk[qt2][byt]) { + return false; + } + } + return true; +} + +// opposite of expandTracks() +void WozFile::reduceTracks() { + for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { + if (trackIsZeroes(this->tmap[qt])) { + removeTrack(this->tmap[qt]); + this->tmap[qt] = 0xFFu; + } + } + for (std::uint8_t qt(0); qt < C_QTRACK-1; ++qt) { + for (std::uint8_t qto(qt+1); qto < C_QTRACK; ++qto) { + if (this->tmap[qt] != 0xFFu && this->tmap[qto] != 0xFFu && this->tmap[qt] != this->tmap[qto]) { + if (tracksAreIdentical(this->tmap[qt], this->tmap[qto])) { + removeTrack(this->tmap[qto]); + this->tmap[qto] = this->tmap[qt]; + } + } + } + } + // kludge? remove track $22.25 if standard disk + for (std::uint8_t qt(C_QTRACK-1); qt >= 0x89; --qt) { + if (this->tmap[qt] != 0xFFu) { + if (qt == 0x89) { + if (this->tmap[qt] == this->tmap[qt-1]) { + this->tmap[qt] = 0xFFu; + } + } + break; + } + } + dumpTmap(); + dumpTracks(); +} + +static std::uint16_t bytesForBits(const std::uint32_t c_bits) { + std::uint16_t c_bytes = (c_bits + 7) / 8; + return static_cast(c_bytes + 0x1FFu) / 0x200 * 0x200; +} + +void WozFile::expandTracks() { + for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) { + if (this->tmap[qt] != 0xFFu) { + for (std::uint8_t qto(qt-1); qto != 0xFFu; --qto) { + if (this->tmap[qt] == this->tmap[qto]) { + copyTrack(qt, qto); + } + } + } else { + createNewTrack(qt); + } + } +} + +void WozFile::copyTrack(std::uint8_t qt_dest, std::uint8_t qt_src) { + for (std::uint8_t t(0); t < C_QTRACK; ++t) { + if (!this->trk[t]) { + this->tmap[qt_dest] = t; + break; + } + } + this->trk_bits[this->tmap[qt_dest]] = this->trk_bits[this->tmap[qt_src]]; + this->trk_byts[this->tmap[qt_dest]] = this->trk_byts[this->tmap[qt_src]]; + this->trk[this->tmap[qt_dest]] = new std::uint8_t[this->trk_byts[this->tmap[qt_dest]]]; + memcpy(this->trk[this->tmap[qt_dest]], this->trk[this->tmap[qt_src]], this->trk_byts[this->tmap[qt_dest]]); +} + +void WozFile::createNewTrack(const std::uint8_t qt) { + if (this->tmap[qt] != 0xFFu) { // track already exists + return; + } + + for (std::uint8_t t(0); t < C_QTRACK; ++t) { + if (!this->trk[t]) { + this->tmap[qt] = t; + break; + } + } + if (this->tmap[qt] == 0xFFu) { + printf("Cannot create track %d\n", qt); + return; + } + + this->trk_bits[this->tmap[qt]] = calcNewTrackLengthBits(qt); + this->trk_byts[this->tmap[qt]] = bytesForBits(this->trk_bits[this->tmap[qt]]); + this->trk[this->tmap[qt]] = new std::uint8_t[this->trk_byts[this->tmap[qt]]]; + memset(this->trk[this->tmap[qt]], 0, this->trk_byts[this->tmap[qt]]); +} + +/* + * example: + * tmap[] track length in bits + * 17 = 06 --> 50000 + * 18 = FF -X + * 19 = 07 --> 50002 + * + * calcNewTrackLengthBits(18) returns 50001 + */ +std::uint32_t WozFile::calcNewTrackLengthBits(const std::uint8_t qt) { + uint32_t t1 = 0; + for (int t(qt-1); t >= 0; --t) { + if (this->tmap[t] != 0xFFu) { + t1 = this->trk_bits[this->tmap[t]]; + break; + } + } + uint32_t t2 = 0; + for (int t(qt+1); t < C_QTRACK; ++t) { + if (this->tmap[t] != 0xFFu) { + t2 = this->trk_bits[this->tmap[t]]; + break; + } + } + // corner case: no existing tracks at all + if (!t1 && !t2) { + return 0xC5C0u; + } + // nominal case: average flanking tracks + if (t1 && t2) { + return (t1+t2)/2; + } + // odd cases: first or last track + return t1 ? t1 : t2; } diff --git a/src/wozfile.h b/src/wozfile.h index 446a859..ff632fa 100644 --- a/src/wozfile.h +++ b/src/wozfile.h @@ -94,6 +94,17 @@ class WozFile { std::string creator; void checkForWriteProtection(); + void expandTracks(); + void reduceTracks(); + bool trackIsZeroes(std::uint8_t qt); + bool tracksAreIdentical(std::uint8_t qt1, std::uint8_t qt2); + void removeTrack(const int trackIndex); + std::uint32_t calcNewTrackLengthBits(std::uint8_t qt); + void copyTrack(std::uint8_t qt_dest, std::uint8_t qt_src); + void createNewTrack(const std::uint8_t qt); + void dumpTmap(); + void dumpTracks(); + void rawSet(std::uint8_t currentQuarterTrack, bool on); public: WozFile(); @@ -122,6 +133,7 @@ public: void rotateOneBit(std::uint8_t currentQuarterTrack); bool getBit(std::uint8_t currentQuarterTrack); void setBit(std::uint8_t currentQuarterTrack, bool on); + void rawSet(); }; #endif // WOZFILE_H