#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 #include #include #include 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 _metadata; }; } // namespace std::unique_ptr FluxSink::createA2RFluxSink( const A2RFluxSinkProto& config) { return std::unique_ptr(new A2RFluxSink(config)); }