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));
|
|
}
|