feat: the export writes an MDF 3.30 file, so a window loads in ScryLab

A third format beside `.npy` and `.csv`, written by hand in `src/export.cpp` --
MF4 is HDF5 and would mean a library, MF3 is a handful of fixed blocks. The
layout is the asammdf 8.8.27 reader's own field list, byte-diffed against a
known-good 3.30 file it wrote; the shape is the one ScryLab's data-source
contract asks for: one channel group, record-interleaved, the master as the x
axis, and the unit in the channel's conversion block.

So in MDF the master is `abs_seconds`, named `time` -- the clock that is the
same in every display mode -- and the plotted axis goes out as an ordinary
channel, `time_relative`. `ExportTable::Column` gained the CONFIG's unit, which
only this writer spells out; the `.npy`/`.csv` bytes are unchanged, and
`write_export()` now picks the writer for both the dialog and the probe.
This commit is contained in:
Martino Ferrari committed 2026-10-14 15:56:27 +02:00
1 parent 761615c2fe
commit 908286ee89
10 files changed
+505 -28

No files matched your search

+14 -3
View File
@@ -2345,11 +2345,10 @@ gboolean export_probe(gpointer user) {
std::fprintf(stderr, "[export-probe] column '%s' n=%zu\n", col.name.c_str(), col.values.size());
}
for (const char* ext : {".npy", ".csv"}) {
for (const char* ext : {".npy", ".csv", ".mf3"}) {
const std::string path = prefix + ext;
std::string error;
const bool ok = std::strcmp(ext, ".csv") == 0 ? write_csv(path, table, error)
: write_npy(path, table, error);
const bool ok = write_export(path, table, error);
long bytes = -1;
if (ok) {
if (std::FILE* f = std::fopen(path.c_str(), "rb"); f != nullptr) {
@@ -2361,6 +2360,18 @@ gboolean export_probe(gpointer user) {
std::fprintf(stderr, "[export-probe] %s %s bytes=%ld%s%s\n", path.c_str(),
ok ? "written" : "FAILED", bytes, ok ? "" : " error=", ok ? "" : error.c_str());
}
// What the `.mf3` was handed, in the shape the file stores it: the master and the relative
// axis are channels too, and `record` is the bytes of one interleaved record. So the size the
// line above printed is `272 + comment + rows*record + 28 + channels*274 + 30`, and the units
// are the ones ScryLab reads each signal's unit from -- the CONFIG's, per column.
std::string units;
for (const char* u : {"s", "s"}) units += std::string(units.empty() ? "" : " ") + "'" + u + "'";
for (const ExportTable::Column& col : table.columns) {
units += std::string(units.empty() ? "" : " ") + "'" + col.unit + "'";
}
std::fprintf(stderr,
"[export-probe] mdf master=time master_unit=s channels=%zu record=%zu units=[%s]\n",
table.columns.size() + 2, 16 + 4 * table.columns.size(), units.c_str());
std::fflush(stderr);
return G_SOURCE_REMOVE;
}