mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-31 11:17:01 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			193 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lib/globals.h"
 | |
| #include "arch/apple2/apple2.h"
 | |
| #include "lib/decoders/decoders.h"
 | |
| #include "lib/encoders/encoders.h"
 | |
| #include "lib/sector.h"
 | |
| #include "lib/readerwriter.h"
 | |
| #include "lib/image.h"
 | |
| #include "fmt/format.h"
 | |
| #include "lib/encoders/encoders.pb.h"
 | |
| #include <ctype.h>
 | |
| #include "lib/bytes.h"
 | |
| 
 | |
| static int encode_data_gcr(uint8_t data)
 | |
| {
 | |
|     switch (data)
 | |
|     {
 | |
| #define GCR_ENTRY(gcr, data) \
 | |
|     case data:               \
 | |
|         return gcr;
 | |
| #include "data_gcr.h"
 | |
| #undef GCR_ENTRY
 | |
|     }
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| class Apple2Encoder : public Encoder
 | |
| {
 | |
| public:
 | |
|     Apple2Encoder(const EncoderProto& config):
 | |
|         Encoder(config),
 | |
|         _config(config.apple2())
 | |
|     {
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     const Apple2EncoderProto& _config;
 | |
| 
 | |
| public:
 | |
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
 | |
|         const std::vector<std::shared_ptr<const Sector>>& sectors,
 | |
|         const Image& image) override
 | |
|     {
 | |
|         int bitsPerRevolution =
 | |
|             (_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
 | |
| 
 | |
|         std::vector<bool> bits(bitsPerRevolution);
 | |
|         unsigned cursor = 0;
 | |
| 
 | |
|         for (const auto& sector : sectors)
 | |
|             writeSector(bits, cursor, *sector);
 | |
| 
 | |
|         if (cursor >= bits.size())
 | |
|             error("track data overrun by {} bits", cursor - bits.size());
 | |
|         fillBitmapTo(bits, cursor, bits.size(), {true, false});
 | |
| 
 | |
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
 | |
|         fluxmap->appendBits(bits,
 | |
|             calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
 | |
|                 _config.rotational_period_ms() * 1e6));
 | |
|         return fluxmap;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     uint8_t volume_id = 254;
 | |
| 
 | |
|     /* This is extremely inspired by the MESS implementation, written by Nathan
 | |
|      * Woods and R. Belmont:
 | |
|      * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
 | |
|      * as well as Understanding the Apple II (1983) Chapter 9
 | |
|      * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
 | |
|      */
 | |
| 
 | |
|     void writeSector(
 | |
|         std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
 | |
|     {
 | |
|         if ((sector.status == Sector::OK) or
 | |
|             (sector.status == Sector::BAD_CHECKSUM))
 | |
|         {
 | |
|             auto write_bit = [&](bool val)
 | |
|             {
 | |
|                 if (cursor <= bits.size())
 | |
|                 {
 | |
|                     bits[cursor] = val;
 | |
|                 }
 | |
|                 cursor++;
 | |
|             };
 | |
| 
 | |
|             auto write_bits = [&](uint32_t bits, int width)
 | |
|             {
 | |
|                 for (int i = width; i--;)
 | |
|                 {
 | |
|                     write_bit(bits & (1u << i));
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             auto write_gcr44 = [&](uint8_t value)
 | |
|             {
 | |
|                 write_bits((value << 7) | value | 0xaaaa, 16);
 | |
|             };
 | |
| 
 | |
|             auto write_gcr6 = [&](uint8_t value)
 | |
|             {
 | |
|                 write_bits(encode_data_gcr(value), 8);
 | |
|             };
 | |
| 
 | |
|             // The special "FF40" sequence is used to synchronize the receiving
 | |
|             // shift register. It's written as "1111 1111 00"; FF indicates the
 | |
|             // 8 consecutive 1-bits, while "40" indicates the total number of
 | |
|             // microseconds.
 | |
|             auto write_ff40 = [&](int n = 1)
 | |
|             {
 | |
|                 for (; n--;)
 | |
|                 {
 | |
|                     write_bits(0xff << 2, 10);
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             // There is data to encode to disk.
 | |
|             if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
 | |
|                 error("unsupported sector size {} --- you must pick 256",
 | |
|                     sector.data.size());
 | |
| 
 | |
|             // Write address syncing leader : A sequence of "FF40"s; 5 of them
 | |
|             // are said to suffice to synchronize the decoder.
 | |
|             // "FF40" indicates that the actual data written is "1111
 | |
|             // 1111 00" i.e., 8 1s and a total of 40 microseconds
 | |
|             //
 | |
|             // In standard formatting, the first logical sector apparently gets
 | |
|             // extra padding.
 | |
|             write_ff40(sector.logicalSector == 0 ? 32 : 8);
 | |
| 
 | |
|             int track = sector.logicalTrack;
 | |
|             if (sector.logicalSide == 1)
 | |
|                 track += _config.side_one_track_offset();
 | |
| 
 | |
|             // Write address field: APPLE2_SECTOR_RECORD + sector identifier +
 | |
|             // DE AA EB
 | |
|             write_bits(APPLE2_SECTOR_RECORD, 24);
 | |
|             write_gcr44(volume_id);
 | |
|             write_gcr44(track);
 | |
|             write_gcr44(sector.logicalSector);
 | |
|             write_gcr44(volume_id ^ track ^ sector.logicalSector);
 | |
|             write_bits(0xDEAAEB, 24);
 | |
| 
 | |
|             // Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector
 | |
|             // data + sum + DE AA EB (+ mystery bits cut off of the scan?)
 | |
|             write_ff40(8);
 | |
|             write_bits(APPLE2_DATA_RECORD, 24);
 | |
| 
 | |
|             // Convert the sector data to GCR, append the checksum, and write it
 | |
|             // out
 | |
|             constexpr auto TWOBIT_COUNT =
 | |
|                 0x56; // Size of the 'twobit' area at the start of the GCR data
 | |
|             uint8_t checksum = 0;
 | |
|             for (int i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++)
 | |
|             {
 | |
|                 int value;
 | |
|                 if (i >= TWOBIT_COUNT)
 | |
|                 {
 | |
|                     value = sector.data[i - TWOBIT_COUNT] >> 2;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     uint8_t tmp = sector.data[i];
 | |
|                     value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
 | |
| 
 | |
|                     tmp = sector.data[i + TWOBIT_COUNT];
 | |
|                     value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
 | |
| 
 | |
|                     if (i + 2 * TWOBIT_COUNT < APPLE2_SECTOR_LENGTH)
 | |
|                     {
 | |
|                         tmp = sector.data[i + 2 * TWOBIT_COUNT];
 | |
|                         value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
 | |
|                     }
 | |
|                 }
 | |
|                 checksum ^= value;
 | |
|                 // assert(checksum & ~0x3f == 0);
 | |
|                 write_gcr6(checksum);
 | |
|                 checksum = value;
 | |
|             }
 | |
|             if (sector.status == Sector::BAD_CHECKSUM)
 | |
|                 checksum ^= 0x3f;
 | |
|             write_gcr6(checksum);
 | |
|             write_bits(0xDEAAEB, 24);
 | |
|         }
 | |
|     }
 | |
| };
 | |
| 
 | |
| std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config)
 | |
| {
 | |
|     return std::unique_ptr<Encoder>(new Apple2Encoder(config));
 | |
| }
 |