mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-31 11:17:01 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			229 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "globals.h"
 | 
						|
#include "flags.h"
 | 
						|
#include "fluxmap.h"
 | 
						|
#include "bytes.h"
 | 
						|
#include "protocol.h"
 | 
						|
#include "fluxsink/fluxsink.h"
 | 
						|
#include "decoders/fluxmapreader.h"
 | 
						|
#include "lib/fluxsink/fluxsink.pb.h"
 | 
						|
#include "lib/logger.h"
 | 
						|
#include "proto.h"
 | 
						|
#include "fluxmap.h"
 | 
						|
#include "a2r.h"
 | 
						|
#include <fstream>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <sys/types.h>
 | 
						|
#include <fmt/chrono.h>
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
    uint32_t ticks_to_a2r(uint32_t ticks)
 | 
						|
    {
 | 
						|
        return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
 | 
						|
    }
 | 
						|
 | 
						|
    bool singlesided(void)
 | 
						|
    {
 | 
						|
        return config.heads().start() == config.heads().end();
 | 
						|
    }
 | 
						|
 | 
						|
    class A2RFluxSink : public FluxSink
 | 
						|
    {
 | 
						|
    public:
 | 
						|
        A2RFluxSink(const A2RFluxSinkProto& lconfig):
 | 
						|
            _config(lconfig),
 | 
						|
            _bytes{},
 | 
						|
            _writer{_bytes.writer()}
 | 
						|
        {
 | 
						|
 | 
						|
            log("A2R: writing A2R {} file containing {} tracks\n",
 | 
						|
                singlesided() ? "single sided" : "double sided",
 | 
						|
                config.tracks().end() - config.tracks().start() + 1);
 | 
						|
 | 
						|
            time_t now{std::time(nullptr)};
 | 
						|
            auto t = gmtime(&now);
 | 
						|
            _metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
 | 
						|
        }
 | 
						|
 | 
						|
        ~A2RFluxSink()
 | 
						|
        {
 | 
						|
            writeHeader();
 | 
						|
            writeInfo();
 | 
						|
            writeStream();
 | 
						|
            writeMeta();
 | 
						|
 | 
						|
            log("A2R: writing output file...\n");
 | 
						|
            std::ofstream of(
 | 
						|
                _config.filename(), std::ios::out | std::ios::binary);
 | 
						|
            if (!of.is_open())
 | 
						|
                error("cannot open output file");
 | 
						|
            _bytes.writeTo(of);
 | 
						|
            of.close();
 | 
						|
        }
 | 
						|
 | 
						|
    private:
 | 
						|
        void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
 | 
						|
        {
 | 
						|
            _writer.write_le32(chunk_id);
 | 
						|
            _writer.write_le32(data.size());
 | 
						|
            _writer += data;
 | 
						|
        }
 | 
						|
 | 
						|
        void writeHeader()
 | 
						|
        {
 | 
						|
            static const uint8_t a2r2_fileheader[] = {
 | 
						|
                'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
 | 
						|
            _writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
 | 
						|
        }
 | 
						|
 | 
						|
        void writeInfo()
 | 
						|
        {
 | 
						|
            Bytes info;
 | 
						|
            auto writer = info.writer();
 | 
						|
            writer.write_8(A2R_INFO_CHUNK_VERSION);
 | 
						|
            auto version_str_padded = fmt::format("{: <32}", "Fluxengine");
 | 
						|
            assert(version_str_padded.size() == 32);
 | 
						|
            writer.append(version_str_padded);
 | 
						|
            writer.write_8(singlesided() ? A2R_DISK_525 : A2R_DISK_35);
 | 
						|
            writer.write_8(1); // write protected
 | 
						|
            writer.write_8(1); // synchronized
 | 
						|
            writeChunkAndData(A2R_CHUNK_INFO, info);
 | 
						|
        }
 | 
						|
 | 
						|
        void writeMeta()
 | 
						|
        {
 | 
						|
            Bytes meta;
 | 
						|
            auto writer = meta.writer();
 | 
						|
            for (auto& i : _metadata)
 | 
						|
            {
 | 
						|
                writer.append(i.first);
 | 
						|
                writer.write_8('\t');
 | 
						|
                writer.append(i.second);
 | 
						|
                writer.write_8('\n');
 | 
						|
            }
 | 
						|
            writeChunkAndData(A2R_CHUNK_META, meta);
 | 
						|
        }
 | 
						|
 | 
						|
        void writeStream()
 | 
						|
        {
 | 
						|
            // A STRM always ends with a 255, even though this could ALSO
 | 
						|
            // indicate the first byte of a multi-byte sequence
 | 
						|
            _strmWriter.write_8(255);
 | 
						|
 | 
						|
            writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
 | 
						|
        }
 | 
						|
 | 
						|
        void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
 | 
						|
        {
 | 
						|
            if (!fluxmap.bytes())
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            // Writing from an image (as opposed to from a floppy) will contain
 | 
						|
            // exactly one revolution and no index events.
 | 
						|
            auto is_image = [](auto& fluxmap)
 | 
						|
            {
 | 
						|
                FluxmapReader fmr(fluxmap);
 | 
						|
                fmr.skipToEvent(F_BIT_INDEX);
 | 
						|
                // but maybe there is no index, if we're writing from an image
 | 
						|
                // to an a2r
 | 
						|
                return fmr.eof();
 | 
						|
            };
 | 
						|
 | 
						|
            // Write the flux data into its own Bytes
 | 
						|
            Bytes trackBytes;
 | 
						|
            auto trackWriter = trackBytes.writer();
 | 
						|
 | 
						|
            auto write_one_flux = [&](unsigned ticks)
 | 
						|
            {
 | 
						|
                auto value = ticks_to_a2r(ticks);
 | 
						|
                while (value > 254)
 | 
						|
                {
 | 
						|
                    trackWriter.write_8(255);
 | 
						|
                    value -= 255;
 | 
						|
                }
 | 
						|
                trackWriter.write_8(value);
 | 
						|
            };
 | 
						|
 | 
						|
            int revolution = 0;
 | 
						|
            uint32_t loopPoint = 0;
 | 
						|
            uint32_t totalTicks = 0;
 | 
						|
            FluxmapReader fmr(fluxmap);
 | 
						|
 | 
						|
            auto write_flux = [&](unsigned maxTicks = ~0u)
 | 
						|
            {
 | 
						|
                unsigned ticksSinceLastPulse = 0;
 | 
						|
 | 
						|
                while (!fmr.eof() && totalTicks < maxTicks)
 | 
						|
                {
 | 
						|
                    unsigned ticks;
 | 
						|
                    int event;
 | 
						|
                    fmr.getNextEvent(event, ticks);
 | 
						|
 | 
						|
                    ticksSinceLastPulse += ticks;
 | 
						|
                    totalTicks += ticks;
 | 
						|
 | 
						|
                    if (event & F_BIT_PULSE)
 | 
						|
                    {
 | 
						|
                        write_one_flux(ticksSinceLastPulse);
 | 
						|
                        ticksSinceLastPulse = 0;
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (event & F_BIT_INDEX && revolution == 0)
 | 
						|
                    {
 | 
						|
                        loopPoint = totalTicks;
 | 
						|
                        revolution += 1;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            };
 | 
						|
 | 
						|
            if (is_image(fluxmap))
 | 
						|
            {
 | 
						|
                // A timing stream with no index represents exactly one
 | 
						|
                // revolution with no index. However, a2r nominally contains 450
 | 
						|
                // degress of rotation, 250ms at 300rpm.
 | 
						|
                write_flux();
 | 
						|
                loopPoint = totalTicks;
 | 
						|
                fmr.rewind();
 | 
						|
                revolution += 1;
 | 
						|
                write_flux(totalTicks * 5 / 4);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                // We have an index, so this is real from a floppy and should be
 | 
						|
                // "one revolution plus a bit"
 | 
						|
                fmr.skipToEvent(F_BIT_INDEX);
 | 
						|
                write_flux();
 | 
						|
            }
 | 
						|
 | 
						|
            uint32_t chunk_size = 10 + trackBytes.size();
 | 
						|
 | 
						|
            _strmWriter.write_8(cylinder);
 | 
						|
            _strmWriter.write_8(A2R_TIMING);
 | 
						|
            _strmWriter.write_le32(trackBytes.size());
 | 
						|
            _strmWriter.write_le32(ticks_to_a2r(loopPoint));
 | 
						|
            _strmWriter += trackBytes;
 | 
						|
        }
 | 
						|
 | 
						|
        operator std::string() const override
 | 
						|
        {
 | 
						|
            return fmt::format("a2r({})", _config.filename());
 | 
						|
        }
 | 
						|
 | 
						|
    private:
 | 
						|
        const A2RFluxSinkProto& _config;
 | 
						|
        Bytes _bytes;
 | 
						|
        ByteWriter _writer;
 | 
						|
        Bytes _strmBytes;
 | 
						|
        ByteWriter _strmWriter{_strmBytes.writer()};
 | 
						|
        std::map<std::string, std::string> _metadata;
 | 
						|
    };
 | 
						|
} // namespace
 | 
						|
 | 
						|
std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
 | 
						|
    const A2RFluxSinkProto& config)
 | 
						|
{
 | 
						|
    return std::unique_ptr<FluxSink>(new A2RFluxSink(config));
 | 
						|
}
 |