edf2csv 0.7.206 → 0.7.208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/convert/run.d.ts
CHANGED
|
@@ -29,7 +29,11 @@ export declare class ConversionError extends Error {
|
|
|
29
29
|
constructor(code: ConversionErrorCode, message: string, hint?: string, options?: ErrorOptions);
|
|
30
30
|
}
|
|
31
31
|
export interface ConvertOptions extends PlanOptions {
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Destination directory. Defaults to `defaultOutputDir(inputPath)`: the input's name with
|
|
34
|
+
* its extension replaced by `_csv`, beside the input — `sleep-study.edf` gives
|
|
35
|
+
* `sleep-study_csv`, not `sleep-study`.
|
|
36
|
+
*/
|
|
33
37
|
outputDir?: string | undefined;
|
|
34
38
|
/** Overwrite an existing output directory. */
|
|
35
39
|
force?: boolean | undefined;
|
package/dist/convert/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/convert/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executing a conversion.\n *\n * Everything is written in a single pass over the data records. All rate groups are\n * open at once and fed from the same batch of bytes, so a file is read once no\n * matter how many output tables it produces, and memory stays flat.\n */\n\nimport { createWriteStream, fstatSync } from 'node:fs';\nimport { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport { finished } from 'node:stream/promises';\nimport type { Writable } from 'node:stream';\nimport { createGzip, gzipSync } from 'node:zlib';\nimport path from 'node:path';\n\nimport type { RecordBatch } from '../edf/reader.js';\nimport { EdfFile } from '../edf/reader.js';\nimport { describeFormat, formatRates, formatWallClock } from '../edf/header.js';\nimport type { Diagnostic } from '../edf/errors.js';\nimport { EdfError } from '../edf/errors.js';\nimport type { Annotation } from '../edf/annotations.js';\nimport {\n BufferedLineWriter,\n DEFAULT_FLUSH_THRESHOLD,\n UTF8_BOM,\n csvRow,\n escapeCsvField,\n} from '../format/csv.js';\nimport { listed } from '../format/list.js';\nimport {\n makeSampleFormatter,\n makeTimeFormatter,\n newOffsetBudget,\n newSampleCacheBudget,\n plain,\n} from '../format/number.js';\nimport type { SampleFormatter } from '../format/number.js';\nimport { TIME_COLUMN } from './channels.js';\nimport { assertInputPath } from './options.js';\nimport { buildPlan, withoutFileRateWarning } from './plan.js';\nimport type { ConversionPlan, PlanOptions, RateGroup } from './plan.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './timing.js';\nimport { sampleTimeIsInRange, toleranceFor } from './time-range.js';\nimport { VERSION as TOOL_VERSION } from '../version.js';\n\nexport { TOOL_VERSION };\n\nexport type ConversionErrorCode =\n | 'OUTPUT_EXISTS'\n | 'OUTPUT_UNWRITABLE'\n | 'INPUT_OUTPUT_COLLISION'\n | 'INPUT_UNREADABLE'\n | 'UNSUPPORTED_REQUEST'\n | 'CALLBACK_FAILED'\n | 'WRITE_FAILED';\n\n/**\n * Codes that mean the command cannot be carried out as written, rather than that something\n * about the file or the destination went wrong.\n *\n * The distinction is the one the exit codes draw: 1 is \"the file or the destination is the\n * problem\", 2 is \"the command line is the problem\". A caller with a `--stdout` conflict is\n * being told to change the flags — the hints say exactly that — so filing it under 1 sent\n * scripts looking at the disk. Exit 2 already covers checks that need the header first,\n * such as a `--channels` term matching nothing.\n */\nexport const USAGE_ERROR_CODES: ReadonlySet<ConversionErrorCode> = new Set(['UNSUPPORTED_REQUEST']);\n\nexport class ConversionError extends Error {\n readonly code: ConversionErrorCode;\n readonly hint: string | undefined;\n constructor(code: ConversionErrorCode, message: string, hint?: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConversionError';\n this.code = code;\n this.hint = hint;\n }\n}\n\nexport interface ConvertOptions extends PlanOptions {\n /** Destination directory. Defaults to the input file's name without its extension. */\n outputDir?: string | undefined;\n /** Overwrite an existing output directory. */\n force?: boolean | undefined;\n /** Record a SHA-256 of the input in metadata.json. Costs one extra read of the file. */\n checksum?: boolean | undefined;\n /**\n * Write the signal CSV to stdout instead of to a directory.\n *\n * Only valid when the conversion produces exactly one signal file. In the default wide\n * layout a mixed-rate recording becomes several tables, and merging them into one stream\n * would mean inventing the samples this tool exists not to invent; `layout: 'long'` gives\n * one table for any recording, so it lifts the restriction. No sidecar files are written.\n */\n toStdout?: boolean | undefined;\n onProgress?: ((progress: ConversionProgress) => void) | undefined;\n}\n\nexport interface ConversionProgress {\n recordsDone: number;\n recordsTotal: number;\n bytesWritten: number;\n}\n\nexport interface WrittenFile {\n name: string;\n rows: number;\n}\n\nexport interface ConvertResult {\n outputDir: string;\n files: WrittenFile[];\n /**\n * True when a `--stdout` reader closed the pipe before the conversion finished.\n *\n * `edf2csv rec.edf --stdout | head -1` is an ordinary thing to type and not a failure, but\n * it is also not a conversion: the row count is rows formatted before the close was\n * noticed, which is neither the recording's total nor what the reader received.\n */\n readerHungUp: boolean;\n annotationCount: number;\n diagnostics: Diagnostic[];\n plan: ConversionPlan;\n file: EdfFile;\n elapsedMs: number;\n}\n\ninterface OpenGroup {\n group: RateGroup;\n writer: BufferedLineWriter;\n formatters: SampleFormatter[];\n formatTime: (recordStart: number, sample: number) => string;\n rows: number;\n /** Resolves once a compressed stream's bytes have reached the file behind it. */\n settled: Promise<void>;\n /** Detaches the compressor's error forwarding from a stream this tool does not own. */\n release: () => void;\n}\n\nexport async function convert(inputPath: string, options: ConvertOptions = {}): Promise<ConvertResult> {\n assertInputPath(inputPath);\n const startedAt = Date.now();\n const file = await EdfFile.open(inputPath);\n\n try {\n /*\n Hashed before a record is read, and only published if the file held still.\n\n A checksum taken afterwards cannot be trusted whichever descriptor it goes through. A\n file overwritten in place keeps its inode, so the open handle sees the new bytes too,\n and the old ones are simply gone — there is nowhere left to read what was converted.\n Taking it first at least means the hash describes the file the header was read from.\n\n What makes it a guarantee rather than a hope is the check at the end: if size or\n modification time moved at any point, the hash is dropped and the run says why. So\n `sha256` present means the file demonstrably did not change while it was read, and a\n recording still being written gets a null and a warning instead of a plausible hash of\n the wrong bytes. (A change reverted within the same modification timestamp would slip\n through; nothing short of copying the input first can close that.)\n */\n const checksumAtOpen = options.checksum === true ? await file.sha256() : null;\n\n // One pass over the annotation channel supplies everything annotation-related:\n // where each record sits in time, and the full event list. It reads the whole\n // file even when a window was requested, because an annotation inside the window\n // may be stored in a record outside it.\n const annotationData =\n file.annotationSignals.length > 0\n ? await file.readAnnotations()\n : { annotations: [] as Annotation[], recordStarts: [] as (number | null)[], malformed: 0 };\n\n const timing = deriveRecordStarts(file, annotationData);\n\n const plan = buildPlan(\n {\n signals: file.header.signals,\n recordDuration: file.header.recordDuration,\n recordCount: file.recordCount,\n hasAnnotationChannel: file.annotationSignals.length > 0,\n recordStarts: timing.starts,\n },\n options,\n );\n plan.diagnostics.push(...timing.diagnostics);\n\n if (options.toStdout === true) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) throw refusal;\n let readerHungUp = false;\n const written = await writeSignalFiles(file, plan, null, timing.starts, options, (hungUp) => {\n readerHungUp = hungUp;\n });\n if (await file.changedSinceOpen()) plan.diagnostics.push(inputChanged(false));\n return {\n outputDir: '-',\n files: written,\n readerHungUp,\n annotationCount: 0,\n diagnostics: [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n }\n\n /*\n A destination whose last component is `.` or `..` does not name a directory of its own.\n\n `prepareOutputDir` claims the final component with a single non-recursive mkdir, having\n created its parents recursively — which is what makes two conversions racing for one\n directory safe. `path.dirname(\"out/.\")` is `\"out\"`, so for these the parent step creates\n the destination itself and the claim then asks the filesystem to make `.` inside it,\n which always exists. The result was a refusal naming a directory this same run had just\n made, one line after making it:\n\n edf2csv rec.edf --out ./fresh/.\n error: \"./fresh/.\" already exists.\n Pass --force to overwrite it, or --out to choose a different directory.\n\n Exit 1, nothing converted, and an empty `fresh/` left on disk. `--force` does not help:\n the claim fails the same way whatever it is told, so the path was unusable rather than\n occupied.\n\n Normalised only in that case, so `--out ./converted` keeps the spelling it was given —\n that one is already how the directory is found on disk, which is what `output_dir`\n promises, and rewriting it to `converted` would churn every example for no gain.\n */\n const asked = options.outputDir ?? defaultOutputDir(inputPath);\n const outputDir = /(?:^|[\\\\/])\\.\\.?$/u.test(asked) ? path.normalize(asked) : asked;\n await assertInputDoesNotOverlapOutputs(inputPath, outputDir, file, plan, options);\n await prepareOutputDir(outputDir, options.force === true);\n\n const written: WrittenFile[] = [];\n\n if (plan.writeSignals && plan.groups.length > 0) {\n const signals = await writeSignalFiles(file, plan, outputDir, timing.starts, options);\n written.push(...signals);\n } else {\n const missing = noSignalFile(file, plan);\n if (missing) plan.diagnostics.push(missing);\n }\n\n const noEvents = noAnnotations(file, options);\n if (noEvents) plan.diagnostics.push(noEvents);\n\n let annotationsWritten = 0;\n if (file.annotationSignals.length > 0) {\n const window = requestedAnnotationWindow(options, plan.range.recordingStartSeconds);\n // Reported against the rows that will be written, not against the file; see\n // durationDiagnostics.\n plan.diagnostics.push(...durationDiagnostics(annotationData.annotations, window));\n const result = await writeAnnotationsCsv(\n outputDir,\n annotationData.annotations,\n window,\n options.gzip === true,\n options.bom === true,\n );\n written.push(result);\n annotationsWritten = result.rows;\n }\n\n written.push(\n await writeChannelsCsv(outputDir, file, plan, options.gzip === true, options.bom === true),\n );\n\n /*\n The file moved under the conversion. Said out loud, because nothing else shows it.\n\n The CSVs are still correct for the records that were read, and metadata.json still\n describes the file as it was opened — so the record is consistent with the output\n whatever happens here. What stops being true is that the checksum describes the bytes\n that were converted, since an in-place overwrite leaves nowhere to read them from. It\n is dropped rather than guessed at.\n */\n const changed = await file.changedSinceOpen();\n if (changed) plan.diagnostics.push(inputChanged(options.checksum === true));\n\n await writeMetadata(\n outputDir,\n inputPath,\n file,\n plan,\n written,\n annotationsWritten,\n changed ? null : checksumAtOpen,\n timing.starts !== null,\n );\n\n const stale = await findStaleOutput(outputDir, written);\n\n return {\n outputDir,\n files: written,\n readerHungUp: false,\n annotationCount: annotationsWritten,\n diagnostics: [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics, ...stale],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n } finally {\n await file.close();\n }\n}\n\n/*\n Files this tool produces, used to spot leftovers from an earlier conversion.\n\n The rate part has to allow every shape a filename can now take, not just the plain ones:\n\n signals_256hz.csv an integer rate\n signals_12_5hz.csv a fractional rate, decimal point written as an underscore\n signals_1_000e-7hz.csv a rate small enough to need exponent form (0.2.2)\n signals_256hz.csv.gz any of the above, compressed (0.3.0)\n signals_0hz_2.csv a second group whose rate slug collided (0.2.1)\n\n The previous `[\\w.]+hz` matched neither of the last two — `-` and `+` are not word\n characters, and the collision suffix falls after the `hz`. Both were introduced by\n recent changes and both silently stopped being recognised as this tool's own output, so\n leftovers of exactly those kinds went unreported: the one situation the warning exists\n for. Requiring a digit after the underscore keeps a user's own `signals_notes.csv` out.\n*/\nconst OUTPUT_PATTERN =\n /^(signals(_\\d[\\w.+-]*hz(_\\d+)?)?\\.csv(\\.gz)?|annotations\\.csv(\\.gz)?|channels\\.csv(\\.gz)?|metadata\\.json)$/u;\n\n/**\n * Detect output from a previous run that this one did not replace.\n *\n * `--force` overwrites files but does not empty the directory, so converting a\n * mixed-rate recording and then a single-rate one into the same place leaves\n * `signals_256hz.csv` sitting next to a fresh `signals.csv`. Both look current.\n * Nothing is deleted here — the user is told, and decides.\n */\nasync function findStaleOutput(\n outputDir: string,\n written: readonly WrittenFile[],\n): Promise<Diagnostic[]> {\n const entries = await readdir(outputDir).catch(() => null);\n if (!entries) return [];\n\n // metadata.json is rewritten on every run but is not part of the reported file list.\n const fresh = new Set([...written.map((f) => f.name), 'metadata.json']);\n const stale = entries.filter((name) => OUTPUT_PATTERN.test(name) && !fresh.has(name)).sort();\n if (stale.length === 0) return [];\n\n return [\n {\n code: 'STALE_OUTPUT',\n severity: 'warning',\n /*\n Through `listed`, like every other message that enumerates something this run does not\n control. How many stale files a directory holds is up to the directory, and a\n mixed-rate recording converted into a reused one is exactly how it fills up: 120 old\n `signals_<rate>hz.csv` files produced a single 2,373-character warning line. That is\n the failure `listed` was written for — its own comment quotes the 1,600-character\n version of it — and this was the one message still joining its own list.\n\n The hint said \"Delete them\" whatever the count, so one stale file read \"signals_999hz\n .csv is left over ... Delete them.\"\n */\n message:\n `${listed(stale)} ${stale.length === 1 ? 'is' : 'are'} left over from an earlier ` +\n `conversion into this directory and ${stale.length === 1 ? 'was' : 'were'} not rewritten.`,\n hint:\n `Delete ${stale.length === 1 ? 'it' : 'them'}, or convert into a fresh directory, so ` +\n 'the two runs do not get mixed up.',\n },\n ];\n}\n\nexport function defaultOutputDir(inputPath: string): string {\n const base = path.basename(inputPath, path.extname(inputPath));\n return path.join(path.dirname(inputPath), `${base}_csv`);\n}\n\n/** Never let an output target resolve to the recording being read, even with --force. */\nasync function assertInputDoesNotOverlapOutputs(\n inputPath: string,\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n options: ConvertOptions,\n): Promise<void> {\n /*\n The names this run will actually write, compressed ones included.\n\n The rate files come from the plan and already carry `.csv.gz` under --gzip; the sidecars\n were spelled out here and did not. So a compressed run checked two names it would never\n write and missed the two it would: a recording sitting at <outdir>/channels.csv.gz was\n overwritten by its own conversion, with --force, reported as a success. The same file\n named signals.csv.gz was refused, which is what gives the oversight away.\n */\n const suffix = options.gzip === true ? '.csv.gz' : '.csv';\n const names = new Set(plan.groups.map((group) => group.fileName));\n if (file.annotationSignals.length > 0) names.add(`annotations${suffix}`);\n names.add(`channels${suffix}`);\n names.add('metadata.json');\n\n const inputResolved = path.resolve(inputPath);\n const inputInfo = await stat(inputPath);\n\n for (const name of names) {\n const target = path.join(outputDir, name);\n const targetResolved = path.resolve(target);\n const targetInfo = await stat(target).catch(() => null);\n const samePath = targetResolved === inputResolved;\n const sameFile =\n targetInfo !== null && targetInfo.dev === inputInfo.dev && targetInfo.ino === inputInfo.ino;\n if (!samePath && !sameFile) continue;\n\n throw new ConversionError(\n 'INPUT_OUTPUT_COLLISION',\n `Output file \"${target}\" is the same file as the input recording.`,\n 'Choose a separate directory with --out. The input was not modified.',\n );\n }\n}\n\nasync function prepareOutputDir(dir: string, force: boolean): Promise<void> {\n /*\n Claim the directory with a single atomic mkdir rather than asking whether it exists\n and then creating it.\n\n Checking first left a window between the two: two conversions started together both\n saw \"not there\", both proceeded, and both opened write streams on the same signals.csv.\n Neither reported anything — both exited 0, having half-written one file between them.\n A non-recursive mkdir cannot do that. Exactly one caller creates the directory; every\n other one gets EEXIST from the filesystem and takes the already-exists path below.\n\n Parents are still created recursively, since --out ./a/b/c should work. Only the final\n component is the claim.\n */\n const parent = path.dirname(dir);\n if (parent && parent !== dir) {\n try {\n await mkdir(parent, { recursive: true });\n } catch (cause: unknown) {\n // A parent that is a regular file surfaces as EEXIST naming the parent, which reads\n // as though the destination already exists rather than as \"you cannot put a\n // directory inside a file\". Name the real obstacle instead of the errno.\n const info = await stat(parent).catch(() => null);\n if (info && !info.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": \"${parent}\" is a file, not a directory.`,\n 'Choose a destination whose parent directories are directories, with --out.',\n );\n }\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n }\n\n let claimed = true;\n try {\n await mkdir(dir);\n } catch (cause: unknown) {\n if ((cause as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n claimed = false;\n }\n\n if (!claimed) {\n const existing = await stat(dir).catch(() => null);\n\n // --force means \"replace my previous output\", not \"write output files into\n // whatever this happens to be\". Pointing it at a regular file needs saying so.\n if (existing && !existing.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `\"${dir}\" is a file, but the converted data needs a directory.`,\n 'Choose a directory with --out.',\n );\n }\n if (!force) {\n throw new ConversionError(\n 'OUTPUT_EXISTS',\n `\"${dir}\" already exists.`,\n 'Pass --force to overwrite it, or --out to choose a different directory.',\n );\n }\n }\n}\n\n/** Turn a Node filesystem error into something a person can act on. */\nfunction describeFsError(cause: unknown): string {\n const code = (cause as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'EACCES' || code === 'EPERM') return 'permission denied';\n if (code === 'ENOSPC') return 'the disk is full';\n // The one that actually happens on a shared filesystem, and the one this list left out.\n if (code === 'EDQUOT') return 'you are over your disk quota on this filesystem';\n if (code === 'ENOTDIR') return 'part of the path is a file, not a directory';\n if (code === 'EROFS') return 'the filesystem is read-only';\n if (code === 'ENAMETOOLONG') return 'the path is too long';\n /*\n The one this list could not describe, and the recursive mkdir above is what makes it\n surprising: every parent is created on the way, so \"no such file or directory\" is not a\n parent that was missing. It is a component that exists and leads nowhere — a symbolic\n link with no target is the way to get one — or a directory removed by something else\n between the two calls.\n\n Missing it meant the fallback ran, which is Node's own text, complete with the internal\n call that raised it:\n\n error: Cannot create \"dangle/x\": ENOENT: no such file or directory, mkdir 'dangle'.\n Check the path exists and that you have permission to write there.\n\n Keeping that off the screen is the whole purpose of the six lines above it.\n */\n if (code === 'ENOENT') return 'part of the path does not exist';\n return cause instanceof Error ? cause.message : String(cause);\n}\n\nasync function writeSignalFiles(\n file: EdfFile,\n plan: ConversionPlan,\n outputDir: string | null,\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n onHangUp?: (hungUp: boolean) => void,\n): Promise<WrittenFile[]> {\n // One budget for every table in this conversion, not one per table: see OffsetBudget.\n const offsets = newOffsetBudget();\n // Likewise one for every channel, not one per channel: see SampleCacheBudget.\n const sampleCaches = newSampleCacheBudget();\n // Only the stdout path needs this; --out finds a full disk on its own, because it always\n // has another file to write afterwards. See auditStdout.\n const audit = outputDir === null ? auditStdout() : null;\n /*\n In the long layout every group writes into one table, so they share one stream. Opening\n a stream per group on the same path is what the rate-slug collision fix in groupByRate\n was about: two writers on one file interleave rows under a header naming one of them.\n */\n let shared: {\n stream: Writable;\n settled: Promise<void>;\n release: () => void;\n target: Writable;\n writer: BufferedLineWriter;\n } | null = null;\n\n /*\n One buffer's worth of memory for the conversion, not one per table.\n\n Every group had its own writer at the default 1 MiB threshold, so pending output was\n group count × 1 MiB before anything drained. A 6.5 MB recording with 40 sampling rates\n — the header allows thousands of channels, and a research montage really does mix a\n dozen rates — died with a raw V8 heap out-of-memory and exit 134 under a 96 MB cap,\n while the site advertises 48 MB. The recording is small; it is the fan-out that is not.\n\n Split evenly with a floor, so the single-rate case, which is nearly every recording,\n keeps exactly the buffer it always had, and forty tables cost one megabyte between them\n rather than forty. The floor is what the sum is bounded by rather than the split: past\n 128 groups every table takes the 8 KiB minimum, so the total grows again from there.\n The long layout shares one writer already and is unaffected either way.\n */\n const MIN_FLUSH_THRESHOLD = 8 * 1024;\n const flushThreshold = Math.max(\n MIN_FLUSH_THRESHOLD,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n /*\n The stream's own buffer is the other half of the same sum.\n\n 0.5.6 shared the line-buffer budget and left `createWriteStream` at its 64 KiB default,\n which is per stream: 200 rate groups meant 12.8 MB of stream buffer on top of 12.8 MB of\n line buffer, and an 855 KB recording still died at a 48 MB cap. Shared the same way, with\n a floor that keeps an ordinary conversion writing in useful-sized pieces.\n */\n const streamBuffer = Math.max(\n 16 * 1024,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n\n const open: OpenGroup[] = plan.groups.map((group, groupIndex) => {\n // A null directory means the single table goes to stdout. process.stdout is already a\n // writable stream, so the same buffered writer and backpressure handling apply.\n const target =\n shared?.target ??\n (outputDir === null\n ? process.stdout\n : createWriteStream(path.join(outputDir, group.fileName), {\n highWaterMark: streamBuffer,\n }));\n const { stream, settled, release } = shared ?? compressed(target, options.gzip === true);\n /*\n Under --gzip the writer feeds the compressor, so its byte count is the CSV before\n compression and says nothing about what reached the descriptor. The compressor's own\n output is what stdout is handed, so that is what is counted. `pipe` uses a 'data'\n listener of its own and a second one is delivered the same chunks.\n\n Once per stream, which in the long layout is once for all the groups. Attaching per\n group put N listeners on the one shared compressor, so every chunk was counted N\n times: `--stdout --layout long --gzip` on a 40-rate recording claimed 622,240 of\n 622,240 bytes where 15,556 had been written, failed with a disk-full error over a\n perfectly good file, and printed Node's MaxListenersExceededWarning to stderr on the\n way past ten. 0.5.4 fixed the same arithmetic in the uncompressed branch and left\n this one, because the uncompressed branch is where the count is a sum and this one is\n where it is a subscription.\n */\n if (audit && stream !== target && !shared) {\n stream.on('data', (chunk: Buffer) => audit.count(chunk.length));\n }\n /*\n One writer, not one per group, when the table is shared. Separate writers over one\n stream each hold their own buffer and flush on their own schedule, so the rows would\n reach the file in whatever order the buffers filled — which is not the order they\n were produced in, and the long layout's whole claim is that its rows are in time\n order.\n */\n const writer = shared?.writer ?? new BufferedLineWriter(stream, flushThreshold);\n if (plan.layout === 'long' && !shared) shared = { stream, settled, release, target, writer };\n\n // Only the first group writes the header of a shared table, and the mark before it.\n if (plan.layout !== 'long' || groupIndex === 0) {\n if (options.bom === true) writer.push(UTF8_BOM);\n writer.pushLine(\n plan.layout === 'long'\n ? csvRow([TIME_COLUMN, 'channel', 'value'])\n : csvRow([TIME_COLUMN, ...group.channels.map((c) => c.column)]),\n );\n }\n return {\n group,\n writer,\n formatters: group.channels.map((c) => makeSampleFormatter(c.signal, c.decimals, sampleCaches)),\n formatTime: makeTimeFormatter(\n group.samplesPerRecord,\n group.rate,\n group.timeDecimals,\n offsets,\n ),\n rows: 0,\n settled,\n release,\n };\n });\n\n try {\n const written = await streamSignalRows(file, plan, open, recordStarts, options);\n\n /*\n Checked once everything has been flushed and ended, and not when the reader hung up.\n\n `--stdout | head -1` closes the pipe on purpose, which is a shell idiom rather than a\n failure. A pipe is not a regular file, so auditStdout declines it anyway — the second\n guard is here because the cost of getting this one wrong is reporting a failure for a\n command that worked.\n */\n if (audit) {\n // Uncompressed, the writer hands its bytes straight to the descriptor; compressed,\n // they were counted on the compressor's way out.\n if (options.gzip !== true) {\n /*\n Once per writer, not once per group. The long layout gives every group the same\n writer, so counting per group multiplied its byte total by the number of rates:\n `--stdout --layout long` on a three-rate recording handed over 32,043 bytes, was\n credited with 96,129, and failed with a disk-full error for a file that was\n complete on disk. It only showed with stdout redirected to a regular file, since\n that is the one case the audit applies to — which is the command the --layout\n documentation gives.\n */\n for (const writer of new Set(open.map((entry) => entry.writer))) {\n audit.count(writer.bytesOut);\n }\n }\n if (!open.some((entry) => entry.writer.hungUp)) audit.verify();\n }\n onHangUp?.(open.some((entry) => entry.writer.hungUp));\n return written;\n } catch (cause) {\n for (const entry of open) entry.writer.destroy();\n\n /*\n Reading and writing both fail through here, and both were reported as writing.\n\n A recording that shrinks mid-conversion — still being written by the acquisition\n software, say — raises the reader's own error, which names the record and says the\n file changed size while it was being read. That precise diagnosis was then filed under\n `Writing to \"<dir>\" failed` and given the hint about freeing disk space, which sends\n someone to look at the one part of the system that was working.\n\n The reader's message and its advice are kept; only the note about partial output is\n added, since that much is true of either failure.\n */\n // A ConversionError arrived already saying what went wrong — a callback that threw, say.\n // Wrapping it again turned \"the onProgress callback threw\" into `Writing to \"out\"\n // failed: the onProgress callback threw`, under a hint about checking the destination.\n if (cause instanceof ConversionError) throw cause;\n\n if (cause instanceof EdfError) {\n const where = outputDir === null ? 'stdout' : `\"${outputDir}\"`;\n throw new ConversionError(\n 'INPUT_UNREADABLE',\n cause.message,\n `${cause.hint ? `${cause.hint} ` : ''}What was written to ${where} before it failed is ` +\n `incomplete and should not be used.`,\n );\n }\n\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to ${outputDir === null ? 'stdout' : `\"${outputDir}\"`} failed: ${detail}`,\n writeHint(cause, outputDir === null),\n );\n }\n}\n\n/**\n * One record's samples in the long layout: `time_s,channel,value`, in time order.\n *\n * The groups are merged rather than written one after another. Every sample in a record\n * falls inside that record's span, so taking the earliest next sample across the groups\n * each time leaves the whole file sorted by `time_s` — which is the only thing that makes a\n * mixed-rate long table useful, since sorting 3 million rows afterwards is the reader's\n * problem and a large one.\n *\n * Ties go to the group with the higher rate, which is the order the groups are already in.\n * Within a sample time the channels come out in the order the file declares them.\n */\n/**\n * Whether two sample times are close enough that only rounding could separate them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude. Well below any\n * real sample interval — the finest a recording can declare is bounded by its record\n * duration and samples-per-record fields — and well above the one-ULP disagreement that two\n * exact divisions of the same instant produce.\n */\nfunction nearlyEqual(a: number, b: number): boolean {\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/** The chosen instant as it will be written, for deciding which channels share it. */\nfunction formatAt(\n open: readonly OpenGroup[],\n cursors: Int32Array,\n recordStart: number,\n earliest: number,\n): string {\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n if (recordStart + sample / entry.group.rate === earliest) {\n return entry.formatTime(recordStart, sample);\n }\n }\n return '';\n}\n\nasync function writeLongRecord(\n file: EdfFile,\n open: readonly OpenGroup[],\n batch: RecordBatch,\n recordInBatch: number,\n recordStart: number,\n range: ConversionPlan['range'],\n): Promise<void> {\n const writer = open[0]?.writer;\n if (!writer) return;\n\n const cursors = new Int32Array(open.length);\n /* Reused across sample times so a three-million-row conversion allocates one of these. */\n const due: { entry: OpenGroup; groupIndex: number; sample: number; channel: number }[] = [];\n\n for (;;) {\n let earliest = Infinity;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time < earliest) earliest = time;\n }\n if (earliest === Infinity) return;\n\n /*\n Everything at this instant, in the order the file declares its channels.\n\n Groups are ordered by rate, largest first, because that is how the wide layout names\n its files. Emitting a tie group by group therefore ordered the channels by descending\n sampling rate — so a recording declaring `slow, medium, fast` wrote `fast, medium,\n slow`, while the documentation promised file order and channels.csv listed file order.\n Signal index is the file's own order, and the only one a reader can predict.\n\n \"At this instant\" is decided on the time as written, not on the double. `s / rate` for\n two channels at one moment need not give the same double: a 0.3 s record holding 12 and\n 4 samples makes 40 Hz and 13.333… Hz, and 9/40 is 0.22500000000000000555 while 3/13.333…\n is 0.22499999999999997780. Equality missed that, so those two rows fell out in numeric\n order — `slow` before `fast`, once, in the middle of a file that was otherwise right.\n\n Two rows are at one time exactly when they carry the same `time_s`, which is the only\n definition a reader of the CSV can apply. The numeric pre-filter keeps the common case\n to one comparison; formatting happens only for candidates already within a hair.\n */\n const earliestText = formatAt(open, cursors, recordStart, earliest);\n due.length = 0;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time !== earliest) {\n // Far away in the ordinary case; only a near-tie is worth formatting.\n if (!nearlyEqual(time, earliest)) continue;\n if (entry.formatTime(recordStart, sample) !== earliestText) continue;\n }\n cursors[g] = sample + 1;\n // Same window rule as the wide layout, with the same per-rate slack.\n if (\n !sampleTimeIsInRange(earliest, range.startSeconds, range.endSeconds, toleranceFor(entry.group.rate))\n ) {\n continue;\n }\n for (let c = 0; c < entry.group.channels.length; c++) {\n due.push({ entry, groupIndex: g, sample, channel: c });\n }\n }\n if (due.length === 0) continue;\n if (due.length > 1) {\n due.sort(\n (a, b) =>\n (a.entry.group.channels[a.channel]?.signal.index ?? 0) -\n (b.entry.group.channels[b.channel]?.signal.index ?? 0),\n );\n }\n\n for (const item of due) {\n const channel = item.entry.group.channels[item.channel];\n const format = item.entry.formatters[item.channel];\n if (!channel || !format) continue;\n if (writer.hungUp) return;\n writer.pushLine(\n `${item.entry.formatTime(recordStart, item.sample)},${escapeCsvField(channel.column)},` +\n `${format(file.sampleAt(batch, recordInBatch, channel.signal, item.sample))}`,\n );\n item.entry.rows++;\n // Flushed inside the record for the same reason the wide layout is; see there.\n if (writer.full) await writer.flush();\n }\n }\n}\n\nasync function streamSignalRows(\n file: EdfFile,\n plan: ConversionPlan,\n open: OpenGroup[],\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n): Promise<WrittenFile[]> {\n const { startSeconds, endSeconds, startRecord, endRecord } = plan.range;\n const { recordDuration } = file.header;\n let recordsDone = 0;\n\n /*\n Every destination has hung up, so nothing formatted from here on could reach anyone.\n `--stdout | head -1` is the usual way to arrive here: the reader takes one line and\n closes the pipe while the conversion is still near the start of the recording.\n\n Stopping also makes the reported row count mean what it says — rows that reached the\n consumer, rather than rows the loop went on formatting into a discarded buffer.\n */\n const allHungUp = (): boolean => open.length > 0 && open.every((entry) => entry.writer.hungUp);\n\n for await (const batch of file.readRecords({ startRecord, endRecord })) {\n if (allHungUp()) break;\n for (let r = 0; r < batch.recordCount; r++) {\n if (allHungUp()) break;\n const index = batch.firstRecordIndex + r;\n const recordStart = recordStarts ? (recordStarts[index] ?? index * recordDuration) : index * recordDuration;\n\n if (plan.layout === 'long') {\n await writeLongRecord(file, open, batch, r, recordStart, plan.range);\n recordsDone++;\n continue;\n }\n\n for (const entry of open) {\n const { group, writer, formatters, formatTime } = entry;\n const { channels, rate } = group;\n // Slack that never reaches the next sample; see toleranceFor.\n const slack = toleranceFor(rate);\n\n for (let sample = 0; sample < group.samplesPerRecord; sample++) {\n const time = recordStart + sample / rate;\n if (!sampleTimeIsInRange(time, startSeconds, endSeconds, slack)) continue;\n\n let row = formatTime(recordStart, sample);\n for (let c = 0; c < channels.length; c++) {\n const channel = channels[c];\n const format = formatters[c];\n if (!channel || !format) continue;\n row += ',' + format(file.sampleAt(batch, r, channel.signal, sample));\n }\n if (writer.hungUp) break;\n writer.pushLine(row);\n entry.rows++;\n\n /*\n Flushed inside the record, not only at the end of one.\n\n The buffer was drained once per record, so the rows of a single record piled up\n with nothing emptying them — memory followed samples-per-record rather than the\n batch size the writer exists to hold to. One record of 16,000,000 samples died\n with a heap out of memory under a 256 MB cap, while the same 32 MB of samples\n split into 16,000 records converted to the same 283 MB CSV without trouble. The\n format allows either layout and says nothing about which to expect.\n\n `full` is a synchronous read of the pending size, so the twenty million rows that\n are not at a boundary cost a comparison rather than a microtask each.\n */\n if (writer.full) await writer.flush();\n }\n await writer.maybeFlush();\n }\n\n recordsDone++;\n }\n\n if (options.onProgress) {\n /*\n A caller's callback is the caller's, and its failures are not the destination's.\n\n This ran inside the same try that turns a stream failure into WRITE_FAILED, so a\n progress callback that threw came back as `Writing to \"out\" failed: caller bug`,\n advising the reader to check a destination that was working perfectly. It is the\n same misattribution the write hints had until 0.4.36, one layer up.\n\n The original is kept as `cause`, so the stack that actually matters survives, and\n the conversion still stops — the callback threw, and carrying on writing into a\n directory whose owner has just failed is not an improvement.\n */\n try {\n options.onProgress({\n recordsDone,\n recordsTotal: endRecord - startRecord,\n // Once per writer: the long layout's groups share one, so summing per group\n // reported a figure larger than the file being written.\n bytesWritten: [...new Set(open.map((entry) => entry.writer))].reduce(\n (sum, entry) => sum + entry.charsWritten,\n 0,\n ),\n });\n } catch (cause) {\n throw new ConversionError(\n 'CALLBACK_FAILED',\n `The onProgress callback threw: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'This is the caller\\'s callback, not the recording or the destination. Whatever was ' +\n 'written before it threw is incomplete and should not be used.',\n { cause },\n );\n }\n }\n }\n\n const closed = new Set<BufferedLineWriter>();\n for (const entry of open) {\n if (closed.has(entry.writer)) continue;\n closed.add(entry.writer);\n try {\n await entry.writer.end();\n // With --gzip the writer's stream is the compressor, whose end callback fires when the\n // compressor is done rather than when the file behind it is. Awaiting only that would\n // report success with the tail of the file still in flight.\n await entry.settled;\n } finally {\n // In a finally for the reason 0.5.45 gives about the writer's own release: a\n // conversion that fails here still has to leave process.stdout as it found it, and a\n // failure is exactly when a caller goes on to convert something else.\n entry.release();\n }\n }\n\n // A shared table is one file, and its row count is every group's rows, not the first's.\n if (plan.layout === 'long') {\n const first = open[0];\n if (!first) return [];\n return [{ name: first.group.fileName, rows: open.reduce((sum, e) => sum + e.rows, 0) }];\n }\n return open.map((entry) => ({ name: entry.group.fileName, rows: entry.rows }));\n}\n\n/**\n * The stream rows are written to, plus a promise for the data reaching its destination.\n *\n * Compression sits between the writer and the file as a transform. Failures below it — a\n * full disk, an unwritable path — surface on the file stream, where nothing is listening,\n * so they are forwarded onto the compressor: that is the stream the writer watches, and\n * routing them there keeps one error path rather than two.\n */\nfunction compressed(\n target: Writable,\n gzip: boolean,\n): { stream: Writable; settled: Promise<void>; release: () => void } {\n if (!gzip) return { stream: target, settled: Promise.resolve(), release: (): void => {} };\n const compressor = createGzip();\n /*\n Removable, because one of the streams this can be handed outlives the conversion.\n\n 0.5.36 fixed exactly this leak for the writer's own listener: a library caller running\n twelve `toStdout` conversions left twelve 'error' listeners on `process.stdout` and got\n Node's MaxListenersExceededWarning on the eleventh. That fix is `BufferedLineWriter`'s\n `#release()`, and it cannot reach this one — under `gzip` the writer's stream is the\n compressor, and `process.stdout` is only ever the compressor's destination. So the same\n leak survived on the same stream, behind one extra flag, and the regression test written\n to catch it does not pass `gzip: true`.\n\n A file stream is a different matter and needs no release: it is created for this\n conversion and closed with it. The discipline is the writer's — release only from a\n stream this tool does not own.\n */\n const forward = (error: Error): void => {\n compressor.destroy(error);\n };\n target.on('error', forward);\n const release = (): void => {\n if (target === process.stdout || target === process.stderr) target.off('error', forward);\n };\n\n /*\n pipe() ends its destination when the source ends, and stdout must survive the\n conversion: the writer already refuses to close it, because a closed stdout breaks\n every later write in the process. Nothing waits on stdout either — it is not this\n tool's to finish.\n */\n const toStdout = target === process.stdout;\n compressor.pipe(target, { end: !toStdout });\n if (toStdout) {\n /*\n Waited on, even though stdout is not ours to end.\n\n This returned an already-resolved promise, so `await entry.settled` waited for nothing\n and the conversion declared itself finished while the compressor still held the tail of\n the stream. `--stdout --gzip` onto a destination that filled up therefore printed the\n ENOSPC *and then* \"Wrote 102,400 rows to stdout.\" — and exited 0, over a file 11,270\n bytes short whose gzip member has no trailer and will not decompress. Through `--out`,\n on the same volume with the same space, the identical failure is reported and exits 1.\n\n The byte audit could not see it either: it stats the descriptor as soon as the writers\n are done, which on this path is before the compressor has pushed its last chunks.\n\n `finished(compressor)` is the source side, not the destination — it resolves when the\n compressor has flushed everything into stdout, and it is stdout's own write that fails.\n That keeps `end: !toStdout` exactly as it was: nothing here closes stdout.\n */\n const flushed = finished(compressor).catch((error: unknown) => {\n /*\n EPIPE is not a failure here, and turning it into one is the trap this nearly fell\n into: `--stdout --gzip | head` is an ordinary thing to type, and the documented\n answer to it is \"Stopped: the reader closed the pipe after 52,507 of 102,400 rows\n had been written\", exit 0. Waiting on the compressor surfaced the EPIPE that the\n uncompressed path already routes through the writer's hang-up flag — the writer\n sees the same error, forwarded, and records it — so the wait has to let that one\n through and keep everything else.\n */\n if ((error as NodeJS.ErrnoException | null)?.code === 'EPIPE') return;\n throw error;\n });\n flushed.catch(() => {});\n return { stream: compressor, settled: flushed, release };\n }\n\n /*\n A failure under the compressor rejects both this promise and the writer's own end(),\n and end() is the one awaited first. Without a handler attached here that rejection\n belonged to nobody, and Node killed the process over it: `--gzip` into an unwritable\n path printed a raw EISDIR stack trace instead of the ordinary \"Writing to ... failed\"\n message and exit 1, which is what the same path does uncompressed.\n\n Attaching the handler is enough to make it handled. Awaiting `settled` downstream still\n reports the failure in the case where end() happened to succeed.\n */\n const settled = finished(target);\n settled.catch(() => {});\n return { stream: compressor, settled, release };\n}\n\n/**\n * Write one of the sidecar files, reporting a failure the way the signal writer does.\n *\n * These three used to call `writeFile` bare, so a failure escaped as whatever the\n * filesystem said — `EISDIR: illegal operation on a directory, open '...'` with no hint\n * and, more importantly, no mention that the signal files had already been written. The\n * conversion stopped half-done and the message gave no sign of it.\n */\nasync function writeOutputFile(\n outputDir: string,\n name: string,\n contents: string,\n gzip = false,\n bom = false,\n): Promise<void> {\n // metadata.json never gets one: JSON.parse rejects a leading U+FEFF, so a mark there\n // would break every reader of the file to help a spreadsheet that will not open it.\n if (bom) contents = UTF8_BOM + contents;\n try {\n // The sidecars are built in memory before being written, so compressing them in memory\n // costs nothing extra. Only the signal tables are large enough to need a stream.\n await writeFile(path.join(outputDir, name), gzip ? gzipSync(contents) : contents, gzip ? undefined : 'utf8');\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing \"${name}\" to \"${outputDir}\" failed: ${detail}`,\n writeHint(cause),\n );\n }\n}\n\nasync function writeChannelsCsv(\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const includedColumns = new Set(plan.groups.flatMap((g) => g.channels.map((c) => c.signal.index)));\n const fileFor = new Map<number, string>();\n for (const group of plan.groups) {\n for (const channel of group.channels) fileFor.set(channel.signal.index, group.fileName);\n }\n\n const lines = [\n csvRow([\n 'column',\n 'signal_index',\n 'label',\n 'unit',\n 'sampling_rate_hz',\n 'samples_per_record',\n 'physical_min',\n 'physical_max',\n 'digital_min',\n 'digital_max',\n 'transducer',\n 'prefiltering',\n 'output_file',\n 'converted',\n ]),\n ];\n\n for (const signal of file.header.signals) {\n if (signal.isAnnotations) continue;\n const column = plan.columnNames.get(signal.index) ?? `signal_${signal.index}`;\n lines.push(\n csvRow([\n column,\n String(signal.index),\n signal.label,\n signal.physicalDimension,\n String(signal.samplingRate),\n String(signal.samplesPerRecord),\n String(signal.physicalMin),\n String(signal.physicalMax),\n String(signal.digitalMin),\n String(signal.digitalMax),\n signal.transducer,\n signal.prefiltering,\n fileFor.get(signal.index) ?? '',\n includedColumns.has(signal.index) ? 'yes' : 'no',\n ]),\n );\n }\n\n const name = gzip ? 'channels.csv.gz' : 'channels.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: lines.length - 1 };\n}\n\n/*\n The bounds an annotation is filtered against: what the caller actually asked for, not the\n window that survived being clamped to the recording.\n\n Filtering by the resolved window meant an unbounded request still lost events. `--end 999h`\n on a three-second file clamps to 3, and `--start 0` gets its end from the recording, so\n both filtered to [0, 3) and dropped the markers at 3.0 and 3.5 that a bare invocation\n keeps — asking for more of a recording returned less of it.\n\n An end the caller did not give is unbounded, not \"the end of the data\": EDF+ lets an\n annotation sit past the last sample, which is exactly where an end-of-recording marker is.\n\n `--duration` is measured from wherever the conversion actually starts. Anchoring it at 0\n instead read the same absent `--start` two ways in two adjacent lines, and on a recording\n that does not begin at zero the two windows did not even overlap: an EDF+D file whose first\n record sits at 30 s converted its samples from [30, 35) while filtering annotations against\n (-inf, 5), so every event inside the converted window was dropped and `annotations.csv` came\n back empty. `resolveRange` has always defaulted the same missing start to the earliest\n record, which is where this now takes it from.\n*/\n/**\n * What the durations in the events that will actually be written look like.\n *\n * These two warnings were raised from the file-wide counts the decoder accumulates, while\n * `annotations.csv` is filtered to the requested window. A conversion of one second of a\n * recording therefore warned that \"1 annotation states a duration that is not a number, so\n * its duration_s cell is empty\" about an event two seconds outside it — naming a cell that is\n * not in the output — and `--strict` failed the run for it. There is no such value, no such\n * cell, and no such row.\n *\n * Taken from the events themselves, after the same filter the writer applies, so the count\n * and the sentence describe the same rows. An unreadable duration is carried on the event\n * because `duration: null` cannot say whether the file gave one; a negative duration needs no\n * flag, since the value is right there.\n */\nexport function durationDiagnostics(\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n): Diagnostic[] {\n const written = annotations.filter((a) => a.onset >= window.from && a.onset < window.to);\n const diagnostics: Diagnostic[] = [];\n\n const negative = written.filter((a) => a.duration !== null && a.duration < 0).length;\n if (negative > 0) {\n const one = negative === 1;\n diagnostics.push({\n code: 'ANNOTATION_DECODE_FAILED',\n severity: 'warning',\n message:\n `${negative} annotation${one ? '' : 's'} state${one ? 's' : ''} a duration below zero, ` +\n `which is not a length of time.`,\n hint:\n 'The value is written to annotations.csv as the file gave it. Adding it to onset_s ' +\n 'ends the event before it starts, so check these rows before using the durations.',\n });\n }\n\n const unreadable = written.filter((a) => a.durationUnreadable === true).length;\n if (unreadable > 0) {\n const one = unreadable === 1;\n diagnostics.push({\n code: 'ANNOTATION_DECODE_FAILED',\n severity: 'warning',\n message:\n `${unreadable} annotation${one ? '' : 's'} state${one ? 's' : ''} a duration that is ` +\n `not a number, so ${one ? 'its' : 'their'} duration_s cell is empty.`,\n hint:\n 'The onset and the description were read normally. An empty duration_s otherwise ' +\n 'means the file stated no duration, so these rows cannot be told apart from those.',\n });\n }\n\n return diagnostics;\n}\n\n/**\n * The window annotations are filtered by — the bounds as asked for, not as snapped to records.\n *\n * Exported so `--info` can count the events a conversion would write using the same predicate\n * that writes them, rather than a second copy of it.\n */\nexport function requestedAnnotationWindow(\n options: ConvertOptions,\n recordingStart: number,\n): { from: number; to: number } {\n const from = options.start ?? -Infinity;\n const to =\n options.end !== undefined\n ? options.end\n : options.duration !== undefined\n ? (options.start ?? recordingStart) + options.duration\n : Infinity;\n return { from, to };\n}\n\nasync function writeAnnotationsCsv(\n outputDir: string,\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const inWindow = annotations\n .filter((a) => a.onset >= window.from && a.onset < window.to)\n .sort((a, b) => a.onset - b.onset || a.recordIndex - b.recordIndex);\n\n const lines = [csvRow(['onset_s', 'duration_s', 'description', 'record_index'])];\n for (const annotation of inWindow) {\n lines.push(\n csvRow([\n plain(annotation.onset),\n annotation.duration === null ? '' : plain(annotation.duration),\n annotation.text,\n String(annotation.recordIndex),\n ]),\n );\n }\n\n const name = gzip ? 'annotations.csv.gz' : 'annotations.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: inWindow.length };\n}\n\n/**\n * What to try next, chosen from what actually went wrong.\n *\n * Every write failure carried the same advice — \"Free up space or choose another destination\n * with --out\" — which fits exactly one errno. A directory sitting where signals.csv belongs\n * came back telling the reader to free up disk space, and so did a read-only volume, a\n * permission denial and a path too long for the filesystem. Wrong advice is worse than none:\n * it sends someone to check `df` on a disk that is fine, and the thing that is actually\n * wrong stays unexamined.\n *\n * The errno is the one piece of the failure that names the cause, so it is what picks the\n * sentence. Anything unrecognised keeps the general form rather than guessing.\n */\nfunction writeHint(cause: unknown, toStdout = false): string {\n /*\n The stdout path writes no files, and --out is the flag its user chose not to pass.\n\n Both halves of this sentence were wrong there: \"the files written so far\" named files\n that do not exist, and \"choose another destination with --out\" is advice for a different\n command — the destination is whatever the shell redirected the stream to. Same class as\n the disk-space hint this function replaced, one flag over.\n */\n const preamble = toStdout\n ? 'What reached stdout before it failed is incomplete and should not be used. '\n : 'The files written so far are incomplete and should not be used. ';\n const elsewhere = toStdout ? 'redirect it somewhere else' : 'choose another with --out';\n const code = (cause as NodeJS.ErrnoException | null)?.code;\n switch (code) {\n case 'ENOSPC':\n return `${preamble}The destination is out of space; free some up or ${elsewhere}.`;\n case 'EDQUOT':\n return `${preamble}You are over your disk quota on this filesystem; ${elsewhere}.`;\n case 'EACCES':\n case 'EPERM':\n return `${preamble}You do not have permission to write there; ${elsewhere}.`;\n case 'EROFS':\n return `${preamble}That filesystem is mounted read-only; ${elsewhere}.`;\n case 'EISDIR':\n return `${preamble}A directory is sitting where that file belongs; remove or rename it, or ${elsewhere}.`;\n case 'ENOENT':\n return `${preamble}Part of that path no longer exists; make sure nothing is removing it while the conversion runs.`;\n case 'ENAMETOOLONG':\n return `${preamble}That path is longer than the filesystem allows; ${toStdout ? 'redirect it somewhere shorter' : 'choose a shorter destination with --out'}.`;\n case 'EMFILE':\n case 'ENFILE':\n return `${preamble}Too many files are open; a recording with many sampling rates opens one output file per rate, so --channels narrows it.`;\n case 'EPIPE':\n return `${preamble}Whatever was reading the output closed it before the conversion finished.`;\n default:\n return `${preamble}Check the destination and run the conversion again.`;\n }\n}\n\n/**\n * Confirms that everything handed to a file-backed stdout actually arrived.\n *\n * `edf2csv rec.edf --stdout > out.csv` onto a volume that the output very nearly fills lost\n * the tail in silence: 94,977 of 102,400 rows on disk, the file ending mid-row, stderr\n * announcing \"Wrote 102,400 rows to stdout.\" and the process exiting 0. The same recording\n * onto the same volume through `--out` fails correctly, which is what gives it away.\n *\n * POSIX `write` returns a short count rather than an error when the disk fills partway\n * through a single call, and only the NEXT write raises ENOSPC. `--out` always has a next\n * write — channels.csv and metadata.json come after — so it always finds out. `--stdout`\n * has nothing after it, and when fd 1 is a regular file Node's stdout is a SyncWriteStream\n * whose `_write` discards the byte count `writeSync` returns, so nothing is raised at all.\n * No error means no `#failure`, so checking that alone would not have caught this.\n *\n * What can be checked is the descriptor: how much it grew against how much it was given.\n * Only for a regular file — a pipe, a terminal or a socket has no size to compare, and on\n * those a short write cannot go unreported this way. Appending (`>>`) is fine, since the\n * starting size is taken before anything is written.\n */\n/**\n * Exported so `--info` can use the same audit a `--stdout` conversion does.\n *\n * `--info` wrote its description with `process.stdout.write` and looked at nothing: redirected\n * into a full filesystem it produced a zero-byte file and exited 0, so `edf2csv rec.edf --info\n * > desc.txt` reported success over nothing at all. A 900-channel recording's description is\n * 58 KB, which is not a size a destination is guaranteed to have.\n */\nexport function auditStdout(): { count: (bytes: number) => void; verify: () => void } | null {\n let startSize: number;\n try {\n const info = fstatSync(1);\n if (!info.isFile()) return null;\n startSize = info.size;\n } catch {\n // No usable descriptor to audit; the conversion is not the place to complain about it.\n return null;\n }\n\n let expected = 0;\n return {\n count: (bytes: number): void => {\n expected += bytes;\n },\n verify: (): void => {\n let landed: number;\n try {\n landed = fstatSync(1).size - startSize;\n } catch {\n return;\n }\n if (landed >= expected) return;\n\n /*\n This audit exists for the failure nothing else reports: a write that is accepted, and\n silently truncated. When the stream itself raised an error there is nothing left for\n it to add, and what it added was a second `error:` line denying the first —\n\n error: Writing to stdout failed: ENOSPC: no space left on device, write\n error: Writing to stdout failed: 58900 of 58900 bytes did not reach the destination,\n which stopped accepting them part way through.\n ... nothing after it raised an error because there was nothing after it.\n\n one failure, reported twice, the second of the two wrong about it.\n */\n if (process.stdout.errored) return;\n\n /*\n Nothing landing at all is not a short write, and was described as one.\n\n `--info > desc.txt` onto a full filesystem left a zero-byte file, and this said the\n destination \"stopped accepting them part way through\" and that \"what is there ends\n mid-row\" — of a file with nothing in it and no rows in it.\n */\n if (landed === 0) {\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: none of the ${expected} bytes reached the destination.`,\n 'The destination is almost certainly out of space. Nothing was written, so there ' +\n 'is nothing there to discard.',\n );\n }\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: ${expected - landed} of ${expected} bytes did not reach ` +\n `the destination, which stopped accepting them part way through.`,\n 'What is there ends mid-row and should not be used. The destination is almost ' +\n 'certainly out of space — a short write is how a filesystem reports filling up ' +\n 'mid-write, and nothing after it raised an error because there was nothing after it.',\n );\n },\n };\n}\n\n/**\n * Why `--stdout` cannot take this recording, or null when it can.\n *\n * Lifted out of the conversion so `--info` can ask the same question. It was not asking:\n * `--info --stdout` on a three-rate recording predicted \"Would write 1,155 rows, roughly\n * 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n * refuses to run, writes nothing, and names no files. `--info` exists to say what a\n * conversion will do, and this is one of the things it does.\n *\n * Reported by `--info` as a warning rather than a refusal, for the reason 0.5.51 gives about\n * the destination guards: `--info` writes nothing, so a rule about what the output would be\n * has no business stopping it from describing the recording — and being told the command\n * will not work is exactly what you asked.\n */\nexport function stdoutRefusal(file: EdfFile, plan: ConversionPlan): ConversionError | null {\n if (!plan.writeSignals) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n '--stdout has no signal data to write because --annotations-only was given.',\n 'Drop one of the two flags.',\n );\n }\n\n /*\n No table at all is its own answer, and neither layout gave it.\n\n A recording with no signal channels — one holding only EDF+ annotations — produced\n zero rate groups. The wide layout then said \"--stdout needs exactly one table, but\n this recording produces 0, one for each sampling rate its channels use ()\", with an\n empty parenthetical, advice to narrow to one of no rates, and advice to use\n `--layout long` — which wrote zero bytes to stdout, not even a header row, and exited\n 0 while warning that \"the signal files hold their headers and no data\". There were no\n files and there was no header. The one path that was right about this is\n `--annotations-only`, which refuses outright, and this is the same situation reached\n by a different route.\n */\n if (plan.groups.length === 0) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n file.dataSignals.length === 0\n ? '--stdout has no signal data to write: this recording has no signal channels, ' +\n 'only EDF+ annotations.'\n : '--stdout has no signal data to write: nothing was selected that carries samples.',\n file.dataSignals.length === 0\n ? 'Convert to a directory to get its annotations.csv, or drop --stdout.'\n : 'Check --channels and the requested window, or convert to a directory instead.',\n );\n }\n\n // The long layout is one table whatever the rates are, so it has nothing to refuse.\n if (plan.layout !== 'long' && plan.groups.length !== 1) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n // Naming the rates is the point: the hint says to narrow the selection, and this\n // is what there is to narrow it to. The parenthetical used to repeat the count\n // that had just been given — \"produces 3 (its channels use 3 different sampling\n // rates)\" — which told nobody anything they could act on.\n `--stdout needs exactly one table, but this recording produces ${plan.groups.length}, ` +\n `one for each sampling rate its channels use ` +\n `(${listed(formatRates(plan.groups.map((g) => g.rate)).map((r) => `${r} Hz`))}).`,\n 'Narrow it to one rate with --channels, write --layout long to get them all in ' +\n 'one table, or convert to a directory instead.',\n );\n }\n return null;\n}\n\n\n\n/**\n * Asked for signal data and given none to put in a file.\n *\n * Every channel selected carries zero samples per record, so there is no table to make —\n * `edf2csv rec.edf --channels unused` writes channels.csv and metadata.json and no signals.csv\n * at all. The NO_SAMPLES warning explains the channel; nothing explained the missing file, and\n * the documentation says signals.csv is written unless --annotations-only was passed. Someone\n * looking for it should be told where it went.\n *\n * There are two ways to arrive with no groups, and one wording is only true of one of them. A\n * recording that holds nothing but EDF+ annotations has no channel that could have been\n * selected, its channels.csv is a header row and nothing else, and no channel of it carries\n * samples — so \"every channel selected\", \"channels.csv still describes them\" and \"which\n * channels do carry samples\" were three false statements in one warning, printed under a\n * warning that had just said the file has no signal channels.\n *\n * Exported, and worded in the present tense, so `--info` can raise the same one. It was built\n * inline here, which meant the one mode whose purpose is to say what a conversion will do said\n * nothing about the file that conversion would not write: `--info --strict` on a recording of\n * nothing but annotations reported one warning where converting it reported two, and\n * `--info --json` carried the shorter list to whatever reads it. Everything the answer depends\n * on is in the file and the plan, both of which `--info` already has.\n */\nexport function noSignalFile(file: EdfFile, plan: ConversionPlan): Diagnostic | null {\n if (!plan.writeSignals || plan.groups.length > 0) return null;\n const noChannelsAtAll = file.dataSignals.length === 0;\n return {\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: noChannelsAtAll\n ? 'No signal file is written: there is no signal data in this recording to put in one.'\n : 'No signal file is written: every channel selected carries zero samples per data ' +\n 'record, so there is nothing to put in one.',\n hint: noChannelsAtAll\n ? 'annotations.csv holds whatever events it carries. channels.csv lists signal ' +\n 'channels, so it has none to list.'\n : 'channels.csv still describes them. Run with --info to see which channels do carry ' +\n 'samples.',\n };\n}\n\n/**\n * `--annotations-only` on a recording that has no annotations.\n *\n * Exported and present-tense for the same reason `noSignalFile` above is. `--info` prints an\n * accurate line about it in the report body — \"and no annotations.csv either, since this\n * recording has no annotation channel\" — and raised nothing, so the one mode whose purpose is\n * to say what a conversion will do carried a shorter warning list than the conversion did:\n * `--info --json --annotations-only` on a plain EDF file listed no warning where converting\n * lists one, and `--info --strict`, which cli-reference.md recommends for screening a folder\n * before converting it, exited 0 where the conversion exits 1. Same defect as the one 0.7.84\n * closed for `NO_SAMPLES`, one flag over; both halves of the answer are in the file and the\n * options, which `--info` already has.\n */\nexport function noAnnotations(file: EdfFile, options: ConvertOptions): Diagnostic | null {\n if (options.annotationsOnly !== true || file.annotationSignals.length > 0) return null;\n return {\n code: 'NO_ANNOTATIONS',\n severity: 'warning',\n message:\n '--annotations-only was requested but this recording has no annotation channel, ' +\n 'so there are no events to export.',\n hint: 'Plain EDF files carry no annotations. Convert without --annotations-only to get the signals.',\n };\n}\n\n/** Raised when the input moved while it was being read. See where it is pushed. */\nfunction inputChanged(hadChecksum: boolean): Diagnostic {\n return {\n code: 'INPUT_CHANGED',\n severity: 'warning',\n message:\n 'The input changed while it was being converted, so this output covers the file as ' +\n 'it was when the conversion started, not as it is now.',\n hint: hadChecksum\n ? 'No checksum was recorded: the bytes that were converted are no longer there to ' +\n 'hash. Convert again once the recording is finished.'\n : 'Convert again once the recording is finished to pick up the rest.',\n };\n}\n\nasync function writeMetadata(\n outputDir: string,\n inputPath: string,\n file: EdfFile,\n plan: ConversionPlan,\n written: readonly WrittenFile[],\n annotationCount: number,\n /** Hash of the bytes that were converted, or null when it could not be vouched for. */\n checksum: string | null,\n /** Whether the record start times could be read; see withTimingPromiseKept. */\n timedFromRecords: boolean,\n): Promise<void> {\n const { header } = file;\n\n /*\n The file as it was when it was opened, not as it is now.\n\n Both of these used to come from re-opening the path once the CSVs were written, which\n describes whatever answers to that name by then rather than what was converted. A\n recording still being written grew from 2,000 records to 3,000 mid-conversion and\n metadata.json recorded `data_records: 2000` — correct, the CSV holds 2,000 — beside the\n byte count and SHA-256 of the 3,000-record file. The two halves of one provenance record\n described two different files, and the checksum covered bytes nobody had converted.\n Replacing the file at that path did the same thing more thoroughly.\n\n `file.fileSize` is the number every record count and window in this output was derived\n from, and the hash is taken over exactly those bytes through the descriptor already open\n on them, so the record describes one file throughout.\n */\n const metadata = {\n tool: { name: 'edf2csv', version: TOOL_VERSION },\n source: {\n path: path.resolve(inputPath),\n bytes: file.fileSize,\n modified: new Date(file.modifiedAtOpenMs).toISOString(),\n sha256: checksum,\n },\n recording: {\n format: describeFormat(header),\n version: header.version,\n patient_id: header.patientId,\n recording_id: header.recordingId,\n // Zone-less on purpose: EDF records local wall-clock digits and no timezone.\n start_datetime_local: formatWallClock(header.startDateTime),\n start_date_raw: header.startDateRaw,\n start_time_raw: header.startTimeRaw,\n data_records: file.recordCount,\n data_records_declared: header.declaredRecordCount,\n record_duration_seconds: header.recordDuration,\n duration_seconds: file.durationSeconds,\n signal_count: header.signalCount,\n annotation_channels: file.annotationSignals.length,\n },\n conversion: {\n converted_at: new Date().toISOString(),\n start_seconds: plan.range.startSeconds,\n end_seconds: plan.range.endSeconds,\n whole_recording: plan.range.isWholeRecording,\n records_converted: [plan.range.startRecord, plan.range.endRecord],\n annotations_written: annotationCount,\n /*\n Which shape the signal table is in, which nothing recorded.\n\n A wide `signals.csv` and a long one are different files with different columns, and\n metadata.json described them identically — so a pipeline handed an output directory\n could not tell from the archive which it had. It matters most for `rate_groups` right\n below: in the wide layout those entries are one per file and their `channels` are that\n file's columns, and in the long layout every entry names the one shared table and its\n `channels` are values in that table's `channel` column. Same array, two readings, and\n no way to know which applied.\n */\n layout: plan.layout,\n files: written.map((f) => ({ name: f.name, rows: f.rows })),\n rate_groups: plan.groups.map((g) => ({\n file: g.fileName,\n sampling_rate_hz: g.rate,\n channels: g.channels.map((c) => c.column),\n decimals: g.channels.map((c) => c.decimals),\n })),\n },\n notes: [\n ...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timedFromRecords),\n ...plan.diagnostics,\n ].map((d) => ({\n code: d.code,\n severity: d.severity,\n message: d.message,\n })),\n };\n\n await writeOutputFile(outputDir, 'metadata.json', JSON.stringify(metadata, null, 2) + '\\n');\n}\n\n\n"]}
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/convert/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executing a conversion.\n *\n * Everything is written in a single pass over the data records. All rate groups are\n * open at once and fed from the same batch of bytes, so a file is read once no\n * matter how many output tables it produces, and memory stays flat.\n */\n\nimport { createWriteStream, fstatSync } from 'node:fs';\nimport { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport { finished } from 'node:stream/promises';\nimport type { Writable } from 'node:stream';\nimport { createGzip, gzipSync } from 'node:zlib';\nimport path from 'node:path';\n\nimport type { RecordBatch } from '../edf/reader.js';\nimport { EdfFile } from '../edf/reader.js';\nimport { describeFormat, formatRates, formatWallClock } from '../edf/header.js';\nimport type { Diagnostic } from '../edf/errors.js';\nimport { EdfError } from '../edf/errors.js';\nimport type { Annotation } from '../edf/annotations.js';\nimport {\n BufferedLineWriter,\n DEFAULT_FLUSH_THRESHOLD,\n UTF8_BOM,\n csvRow,\n escapeCsvField,\n} from '../format/csv.js';\nimport { listed } from '../format/list.js';\nimport {\n makeSampleFormatter,\n makeTimeFormatter,\n newOffsetBudget,\n newSampleCacheBudget,\n plain,\n} from '../format/number.js';\nimport type { SampleFormatter } from '../format/number.js';\nimport { TIME_COLUMN } from './channels.js';\nimport { assertInputPath } from './options.js';\nimport { buildPlan, withoutFileRateWarning } from './plan.js';\nimport type { ConversionPlan, PlanOptions, RateGroup } from './plan.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './timing.js';\nimport { sampleTimeIsInRange, toleranceFor } from './time-range.js';\nimport { VERSION as TOOL_VERSION } from '../version.js';\n\nexport { TOOL_VERSION };\n\nexport type ConversionErrorCode =\n | 'OUTPUT_EXISTS'\n | 'OUTPUT_UNWRITABLE'\n | 'INPUT_OUTPUT_COLLISION'\n | 'INPUT_UNREADABLE'\n | 'UNSUPPORTED_REQUEST'\n | 'CALLBACK_FAILED'\n | 'WRITE_FAILED';\n\n/**\n * Codes that mean the command cannot be carried out as written, rather than that something\n * about the file or the destination went wrong.\n *\n * The distinction is the one the exit codes draw: 1 is \"the file or the destination is the\n * problem\", 2 is \"the command line is the problem\". A caller with a `--stdout` conflict is\n * being told to change the flags — the hints say exactly that — so filing it under 1 sent\n * scripts looking at the disk. Exit 2 already covers checks that need the header first,\n * such as a `--channels` term matching nothing.\n */\nexport const USAGE_ERROR_CODES: ReadonlySet<ConversionErrorCode> = new Set(['UNSUPPORTED_REQUEST']);\n\nexport class ConversionError extends Error {\n readonly code: ConversionErrorCode;\n readonly hint: string | undefined;\n constructor(code: ConversionErrorCode, message: string, hint?: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConversionError';\n this.code = code;\n this.hint = hint;\n }\n}\n\nexport interface ConvertOptions extends PlanOptions {\n /**\n * Destination directory. Defaults to `defaultOutputDir(inputPath)`: the input's name with\n * its extension replaced by `_csv`, beside the input — `sleep-study.edf` gives\n * `sleep-study_csv`, not `sleep-study`.\n */\n outputDir?: string | undefined;\n /** Overwrite an existing output directory. */\n force?: boolean | undefined;\n /** Record a SHA-256 of the input in metadata.json. Costs one extra read of the file. */\n checksum?: boolean | undefined;\n /**\n * Write the signal CSV to stdout instead of to a directory.\n *\n * Only valid when the conversion produces exactly one signal file. In the default wide\n * layout a mixed-rate recording becomes several tables, and merging them into one stream\n * would mean inventing the samples this tool exists not to invent; `layout: 'long'` gives\n * one table for any recording, so it lifts the restriction. No sidecar files are written.\n */\n toStdout?: boolean | undefined;\n onProgress?: ((progress: ConversionProgress) => void) | undefined;\n}\n\nexport interface ConversionProgress {\n recordsDone: number;\n recordsTotal: number;\n bytesWritten: number;\n}\n\nexport interface WrittenFile {\n name: string;\n rows: number;\n}\n\nexport interface ConvertResult {\n outputDir: string;\n files: WrittenFile[];\n /**\n * True when a `--stdout` reader closed the pipe before the conversion finished.\n *\n * `edf2csv rec.edf --stdout | head -1` is an ordinary thing to type and not a failure, but\n * it is also not a conversion: the row count is rows formatted before the close was\n * noticed, which is neither the recording's total nor what the reader received.\n */\n readerHungUp: boolean;\n annotationCount: number;\n diagnostics: Diagnostic[];\n plan: ConversionPlan;\n file: EdfFile;\n elapsedMs: number;\n}\n\ninterface OpenGroup {\n group: RateGroup;\n writer: BufferedLineWriter;\n formatters: SampleFormatter[];\n formatTime: (recordStart: number, sample: number) => string;\n rows: number;\n /** Resolves once a compressed stream's bytes have reached the file behind it. */\n settled: Promise<void>;\n /** Detaches the compressor's error forwarding from a stream this tool does not own. */\n release: () => void;\n}\n\nexport async function convert(inputPath: string, options: ConvertOptions = {}): Promise<ConvertResult> {\n assertInputPath(inputPath);\n const startedAt = Date.now();\n const file = await EdfFile.open(inputPath);\n\n try {\n /*\n Hashed before a record is read, and only published if the file held still.\n\n A checksum taken afterwards cannot be trusted whichever descriptor it goes through. A\n file overwritten in place keeps its inode, so the open handle sees the new bytes too,\n and the old ones are simply gone — there is nowhere left to read what was converted.\n Taking it first at least means the hash describes the file the header was read from.\n\n What makes it a guarantee rather than a hope is the check at the end: if size or\n modification time moved at any point, the hash is dropped and the run says why. So\n `sha256` present means the file demonstrably did not change while it was read, and a\n recording still being written gets a null and a warning instead of a plausible hash of\n the wrong bytes. (A change reverted within the same modification timestamp would slip\n through; nothing short of copying the input first can close that.)\n */\n const checksumAtOpen = options.checksum === true ? await file.sha256() : null;\n\n // One pass over the annotation channel supplies everything annotation-related:\n // where each record sits in time, and the full event list. It reads the whole\n // file even when a window was requested, because an annotation inside the window\n // may be stored in a record outside it.\n const annotationData =\n file.annotationSignals.length > 0\n ? await file.readAnnotations()\n : { annotations: [] as Annotation[], recordStarts: [] as (number | null)[], malformed: 0 };\n\n const timing = deriveRecordStarts(file, annotationData);\n\n const plan = buildPlan(\n {\n signals: file.header.signals,\n recordDuration: file.header.recordDuration,\n recordCount: file.recordCount,\n hasAnnotationChannel: file.annotationSignals.length > 0,\n recordStarts: timing.starts,\n },\n options,\n );\n plan.diagnostics.push(...timing.diagnostics);\n\n if (options.toStdout === true) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) throw refusal;\n let readerHungUp = false;\n const written = await writeSignalFiles(file, plan, null, timing.starts, options, (hungUp) => {\n readerHungUp = hungUp;\n });\n if (await file.changedSinceOpen()) plan.diagnostics.push(inputChanged(false));\n return {\n outputDir: '-',\n files: written,\n readerHungUp,\n annotationCount: 0,\n diagnostics: [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n }\n\n /*\n A destination whose last component is `.` or `..` does not name a directory of its own.\n\n `prepareOutputDir` claims the final component with a single non-recursive mkdir, having\n created its parents recursively — which is what makes two conversions racing for one\n directory safe. `path.dirname(\"out/.\")` is `\"out\"`, so for these the parent step creates\n the destination itself and the claim then asks the filesystem to make `.` inside it,\n which always exists. The result was a refusal naming a directory this same run had just\n made, one line after making it:\n\n edf2csv rec.edf --out ./fresh/.\n error: \"./fresh/.\" already exists.\n Pass --force to overwrite it, or --out to choose a different directory.\n\n Exit 1, nothing converted, and an empty `fresh/` left on disk. `--force` does not help:\n the claim fails the same way whatever it is told, so the path was unusable rather than\n occupied.\n\n Normalised only in that case, so `--out ./converted` keeps the spelling it was given —\n that one is already how the directory is found on disk, which is what `output_dir`\n promises, and rewriting it to `converted` would churn every example for no gain.\n */\n const asked = options.outputDir ?? defaultOutputDir(inputPath);\n const outputDir = /(?:^|[\\\\/])\\.\\.?$/u.test(asked) ? path.normalize(asked) : asked;\n await assertInputDoesNotOverlapOutputs(inputPath, outputDir, file, plan, options);\n await prepareOutputDir(outputDir, options.force === true);\n\n const written: WrittenFile[] = [];\n\n if (plan.writeSignals && plan.groups.length > 0) {\n const signals = await writeSignalFiles(file, plan, outputDir, timing.starts, options);\n written.push(...signals);\n } else {\n const missing = noSignalFile(file, plan);\n if (missing) plan.diagnostics.push(missing);\n }\n\n const noEvents = noAnnotations(file, options);\n if (noEvents) plan.diagnostics.push(noEvents);\n\n let annotationsWritten = 0;\n if (file.annotationSignals.length > 0) {\n const window = requestedAnnotationWindow(options, plan.range.recordingStartSeconds);\n // Reported against the rows that will be written, not against the file; see\n // durationDiagnostics.\n plan.diagnostics.push(...durationDiagnostics(annotationData.annotations, window));\n const result = await writeAnnotationsCsv(\n outputDir,\n annotationData.annotations,\n window,\n options.gzip === true,\n options.bom === true,\n );\n written.push(result);\n annotationsWritten = result.rows;\n }\n\n written.push(\n await writeChannelsCsv(outputDir, file, plan, options.gzip === true, options.bom === true),\n );\n\n /*\n The file moved under the conversion. Said out loud, because nothing else shows it.\n\n The CSVs are still correct for the records that were read, and metadata.json still\n describes the file as it was opened — so the record is consistent with the output\n whatever happens here. What stops being true is that the checksum describes the bytes\n that were converted, since an in-place overwrite leaves nowhere to read them from. It\n is dropped rather than guessed at.\n */\n const changed = await file.changedSinceOpen();\n if (changed) plan.diagnostics.push(inputChanged(options.checksum === true));\n\n await writeMetadata(\n outputDir,\n inputPath,\n file,\n plan,\n written,\n annotationsWritten,\n changed ? null : checksumAtOpen,\n timing.starts !== null,\n );\n\n const stale = await findStaleOutput(outputDir, written);\n\n return {\n outputDir,\n files: written,\n readerHungUp: false,\n annotationCount: annotationsWritten,\n diagnostics: [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics, ...stale],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n } finally {\n await file.close();\n }\n}\n\n/*\n Files this tool produces, used to spot leftovers from an earlier conversion.\n\n The rate part has to allow every shape a filename can now take, not just the plain ones:\n\n signals_256hz.csv an integer rate\n signals_12_5hz.csv a fractional rate, decimal point written as an underscore\n signals_1_000e-7hz.csv a rate small enough to need exponent form (0.2.2)\n signals_256hz.csv.gz any of the above, compressed (0.3.0)\n signals_0hz_2.csv a second group whose rate slug collided (0.2.1)\n\n The previous `[\\w.]+hz` matched neither of the last two — `-` and `+` are not word\n characters, and the collision suffix falls after the `hz`. Both were introduced by\n recent changes and both silently stopped being recognised as this tool's own output, so\n leftovers of exactly those kinds went unreported: the one situation the warning exists\n for. Requiring a digit after the underscore keeps a user's own `signals_notes.csv` out.\n*/\nconst OUTPUT_PATTERN =\n /^(signals(_\\d[\\w.+-]*hz(_\\d+)?)?\\.csv(\\.gz)?|annotations\\.csv(\\.gz)?|channels\\.csv(\\.gz)?|metadata\\.json)$/u;\n\n/**\n * Detect output from a previous run that this one did not replace.\n *\n * `--force` overwrites files but does not empty the directory, so converting a\n * mixed-rate recording and then a single-rate one into the same place leaves\n * `signals_256hz.csv` sitting next to a fresh `signals.csv`. Both look current.\n * Nothing is deleted here — the user is told, and decides.\n */\nasync function findStaleOutput(\n outputDir: string,\n written: readonly WrittenFile[],\n): Promise<Diagnostic[]> {\n const entries = await readdir(outputDir).catch(() => null);\n if (!entries) return [];\n\n // metadata.json is rewritten on every run but is not part of the reported file list.\n const fresh = new Set([...written.map((f) => f.name), 'metadata.json']);\n const stale = entries.filter((name) => OUTPUT_PATTERN.test(name) && !fresh.has(name)).sort();\n if (stale.length === 0) return [];\n\n return [\n {\n code: 'STALE_OUTPUT',\n severity: 'warning',\n /*\n Through `listed`, like every other message that enumerates something this run does not\n control. How many stale files a directory holds is up to the directory, and a\n mixed-rate recording converted into a reused one is exactly how it fills up: 120 old\n `signals_<rate>hz.csv` files produced a single 2,373-character warning line. That is\n the failure `listed` was written for — its own comment quotes the 1,600-character\n version of it — and this was the one message still joining its own list.\n\n The hint said \"Delete them\" whatever the count, so one stale file read \"signals_999hz\n .csv is left over ... Delete them.\"\n */\n message:\n `${listed(stale)} ${stale.length === 1 ? 'is' : 'are'} left over from an earlier ` +\n `conversion into this directory and ${stale.length === 1 ? 'was' : 'were'} not rewritten.`,\n hint:\n `Delete ${stale.length === 1 ? 'it' : 'them'}, or convert into a fresh directory, so ` +\n 'the two runs do not get mixed up.',\n },\n ];\n}\n\nexport function defaultOutputDir(inputPath: string): string {\n const base = path.basename(inputPath, path.extname(inputPath));\n return path.join(path.dirname(inputPath), `${base}_csv`);\n}\n\n/** Never let an output target resolve to the recording being read, even with --force. */\nasync function assertInputDoesNotOverlapOutputs(\n inputPath: string,\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n options: ConvertOptions,\n): Promise<void> {\n /*\n The names this run will actually write, compressed ones included.\n\n The rate files come from the plan and already carry `.csv.gz` under --gzip; the sidecars\n were spelled out here and did not. So a compressed run checked two names it would never\n write and missed the two it would: a recording sitting at <outdir>/channels.csv.gz was\n overwritten by its own conversion, with --force, reported as a success. The same file\n named signals.csv.gz was refused, which is what gives the oversight away.\n */\n const suffix = options.gzip === true ? '.csv.gz' : '.csv';\n const names = new Set(plan.groups.map((group) => group.fileName));\n if (file.annotationSignals.length > 0) names.add(`annotations${suffix}`);\n names.add(`channels${suffix}`);\n names.add('metadata.json');\n\n const inputResolved = path.resolve(inputPath);\n const inputInfo = await stat(inputPath);\n\n for (const name of names) {\n const target = path.join(outputDir, name);\n const targetResolved = path.resolve(target);\n const targetInfo = await stat(target).catch(() => null);\n const samePath = targetResolved === inputResolved;\n const sameFile =\n targetInfo !== null && targetInfo.dev === inputInfo.dev && targetInfo.ino === inputInfo.ino;\n if (!samePath && !sameFile) continue;\n\n throw new ConversionError(\n 'INPUT_OUTPUT_COLLISION',\n `Output file \"${target}\" is the same file as the input recording.`,\n 'Choose a separate directory with --out. The input was not modified.',\n );\n }\n}\n\nasync function prepareOutputDir(dir: string, force: boolean): Promise<void> {\n /*\n Claim the directory with a single atomic mkdir rather than asking whether it exists\n and then creating it.\n\n Checking first left a window between the two: two conversions started together both\n saw \"not there\", both proceeded, and both opened write streams on the same signals.csv.\n Neither reported anything — both exited 0, having half-written one file between them.\n A non-recursive mkdir cannot do that. Exactly one caller creates the directory; every\n other one gets EEXIST from the filesystem and takes the already-exists path below.\n\n Parents are still created recursively, since --out ./a/b/c should work. Only the final\n component is the claim.\n */\n const parent = path.dirname(dir);\n if (parent && parent !== dir) {\n try {\n await mkdir(parent, { recursive: true });\n } catch (cause: unknown) {\n // A parent that is a regular file surfaces as EEXIST naming the parent, which reads\n // as though the destination already exists rather than as \"you cannot put a\n // directory inside a file\". Name the real obstacle instead of the errno.\n const info = await stat(parent).catch(() => null);\n if (info && !info.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": \"${parent}\" is a file, not a directory.`,\n 'Choose a destination whose parent directories are directories, with --out.',\n );\n }\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n }\n\n let claimed = true;\n try {\n await mkdir(dir);\n } catch (cause: unknown) {\n if ((cause as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n claimed = false;\n }\n\n if (!claimed) {\n const existing = await stat(dir).catch(() => null);\n\n // --force means \"replace my previous output\", not \"write output files into\n // whatever this happens to be\". Pointing it at a regular file needs saying so.\n if (existing && !existing.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `\"${dir}\" is a file, but the converted data needs a directory.`,\n 'Choose a directory with --out.',\n );\n }\n if (!force) {\n throw new ConversionError(\n 'OUTPUT_EXISTS',\n `\"${dir}\" already exists.`,\n 'Pass --force to overwrite it, or --out to choose a different directory.',\n );\n }\n }\n}\n\n/** Turn a Node filesystem error into something a person can act on. */\nfunction describeFsError(cause: unknown): string {\n const code = (cause as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'EACCES' || code === 'EPERM') return 'permission denied';\n if (code === 'ENOSPC') return 'the disk is full';\n // The one that actually happens on a shared filesystem, and the one this list left out.\n if (code === 'EDQUOT') return 'you are over your disk quota on this filesystem';\n if (code === 'ENOTDIR') return 'part of the path is a file, not a directory';\n if (code === 'EROFS') return 'the filesystem is read-only';\n if (code === 'ENAMETOOLONG') return 'the path is too long';\n /*\n The one this list could not describe, and the recursive mkdir above is what makes it\n surprising: every parent is created on the way, so \"no such file or directory\" is not a\n parent that was missing. It is a component that exists and leads nowhere — a symbolic\n link with no target is the way to get one — or a directory removed by something else\n between the two calls.\n\n Missing it meant the fallback ran, which is Node's own text, complete with the internal\n call that raised it:\n\n error: Cannot create \"dangle/x\": ENOENT: no such file or directory, mkdir 'dangle'.\n Check the path exists and that you have permission to write there.\n\n Keeping that off the screen is the whole purpose of the six lines above it.\n */\n if (code === 'ENOENT') return 'part of the path does not exist';\n return cause instanceof Error ? cause.message : String(cause);\n}\n\nasync function writeSignalFiles(\n file: EdfFile,\n plan: ConversionPlan,\n outputDir: string | null,\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n onHangUp?: (hungUp: boolean) => void,\n): Promise<WrittenFile[]> {\n // One budget for every table in this conversion, not one per table: see OffsetBudget.\n const offsets = newOffsetBudget();\n // Likewise one for every channel, not one per channel: see SampleCacheBudget.\n const sampleCaches = newSampleCacheBudget();\n // Only the stdout path needs this; --out finds a full disk on its own, because it always\n // has another file to write afterwards. See auditStdout.\n const audit = outputDir === null ? auditStdout() : null;\n /*\n In the long layout every group writes into one table, so they share one stream. Opening\n a stream per group on the same path is what the rate-slug collision fix in groupByRate\n was about: two writers on one file interleave rows under a header naming one of them.\n */\n let shared: {\n stream: Writable;\n settled: Promise<void>;\n release: () => void;\n target: Writable;\n writer: BufferedLineWriter;\n } | null = null;\n\n /*\n One buffer's worth of memory for the conversion, not one per table.\n\n Every group had its own writer at the default 1 MiB threshold, so pending output was\n group count × 1 MiB before anything drained. A 6.5 MB recording with 40 sampling rates\n — the header allows thousands of channels, and a research montage really does mix a\n dozen rates — died with a raw V8 heap out-of-memory and exit 134 under a 96 MB cap,\n while the site advertises 48 MB. The recording is small; it is the fan-out that is not.\n\n Split evenly with a floor, so the single-rate case, which is nearly every recording,\n keeps exactly the buffer it always had, and forty tables cost one megabyte between them\n rather than forty. The floor is what the sum is bounded by rather than the split: past\n 128 groups every table takes the 8 KiB minimum, so the total grows again from there.\n The long layout shares one writer already and is unaffected either way.\n */\n const MIN_FLUSH_THRESHOLD = 8 * 1024;\n const flushThreshold = Math.max(\n MIN_FLUSH_THRESHOLD,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n /*\n The stream's own buffer is the other half of the same sum.\n\n 0.5.6 shared the line-buffer budget and left `createWriteStream` at its 64 KiB default,\n which is per stream: 200 rate groups meant 12.8 MB of stream buffer on top of 12.8 MB of\n line buffer, and an 855 KB recording still died at a 48 MB cap. Shared the same way, with\n a floor that keeps an ordinary conversion writing in useful-sized pieces.\n */\n const streamBuffer = Math.max(\n 16 * 1024,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n\n const open: OpenGroup[] = plan.groups.map((group, groupIndex) => {\n // A null directory means the single table goes to stdout. process.stdout is already a\n // writable stream, so the same buffered writer and backpressure handling apply.\n const target =\n shared?.target ??\n (outputDir === null\n ? process.stdout\n : createWriteStream(path.join(outputDir, group.fileName), {\n highWaterMark: streamBuffer,\n }));\n const { stream, settled, release } = shared ?? compressed(target, options.gzip === true);\n /*\n Under --gzip the writer feeds the compressor, so its byte count is the CSV before\n compression and says nothing about what reached the descriptor. The compressor's own\n output is what stdout is handed, so that is what is counted. `pipe` uses a 'data'\n listener of its own and a second one is delivered the same chunks.\n\n Once per stream, which in the long layout is once for all the groups. Attaching per\n group put N listeners on the one shared compressor, so every chunk was counted N\n times: `--stdout --layout long --gzip` on a 40-rate recording claimed 622,240 of\n 622,240 bytes where 15,556 had been written, failed with a disk-full error over a\n perfectly good file, and printed Node's MaxListenersExceededWarning to stderr on the\n way past ten. 0.5.4 fixed the same arithmetic in the uncompressed branch and left\n this one, because the uncompressed branch is where the count is a sum and this one is\n where it is a subscription.\n */\n if (audit && stream !== target && !shared) {\n stream.on('data', (chunk: Buffer) => audit.count(chunk.length));\n }\n /*\n One writer, not one per group, when the table is shared. Separate writers over one\n stream each hold their own buffer and flush on their own schedule, so the rows would\n reach the file in whatever order the buffers filled — which is not the order they\n were produced in, and the long layout's whole claim is that its rows are in time\n order.\n */\n const writer = shared?.writer ?? new BufferedLineWriter(stream, flushThreshold);\n if (plan.layout === 'long' && !shared) shared = { stream, settled, release, target, writer };\n\n // Only the first group writes the header of a shared table, and the mark before it.\n if (plan.layout !== 'long' || groupIndex === 0) {\n if (options.bom === true) writer.push(UTF8_BOM);\n writer.pushLine(\n plan.layout === 'long'\n ? csvRow([TIME_COLUMN, 'channel', 'value'])\n : csvRow([TIME_COLUMN, ...group.channels.map((c) => c.column)]),\n );\n }\n return {\n group,\n writer,\n formatters: group.channels.map((c) => makeSampleFormatter(c.signal, c.decimals, sampleCaches)),\n formatTime: makeTimeFormatter(\n group.samplesPerRecord,\n group.rate,\n group.timeDecimals,\n offsets,\n ),\n rows: 0,\n settled,\n release,\n };\n });\n\n try {\n const written = await streamSignalRows(file, plan, open, recordStarts, options);\n\n /*\n Checked once everything has been flushed and ended, and not when the reader hung up.\n\n `--stdout | head -1` closes the pipe on purpose, which is a shell idiom rather than a\n failure. A pipe is not a regular file, so auditStdout declines it anyway — the second\n guard is here because the cost of getting this one wrong is reporting a failure for a\n command that worked.\n */\n if (audit) {\n // Uncompressed, the writer hands its bytes straight to the descriptor; compressed,\n // they were counted on the compressor's way out.\n if (options.gzip !== true) {\n /*\n Once per writer, not once per group. The long layout gives every group the same\n writer, so counting per group multiplied its byte total by the number of rates:\n `--stdout --layout long` on a three-rate recording handed over 32,043 bytes, was\n credited with 96,129, and failed with a disk-full error for a file that was\n complete on disk. It only showed with stdout redirected to a regular file, since\n that is the one case the audit applies to — which is the command the --layout\n documentation gives.\n */\n for (const writer of new Set(open.map((entry) => entry.writer))) {\n audit.count(writer.bytesOut);\n }\n }\n if (!open.some((entry) => entry.writer.hungUp)) audit.verify();\n }\n onHangUp?.(open.some((entry) => entry.writer.hungUp));\n return written;\n } catch (cause) {\n for (const entry of open) entry.writer.destroy();\n\n /*\n Reading and writing both fail through here, and both were reported as writing.\n\n A recording that shrinks mid-conversion — still being written by the acquisition\n software, say — raises the reader's own error, which names the record and says the\n file changed size while it was being read. That precise diagnosis was then filed under\n `Writing to \"<dir>\" failed` and given the hint about freeing disk space, which sends\n someone to look at the one part of the system that was working.\n\n The reader's message and its advice are kept; only the note about partial output is\n added, since that much is true of either failure.\n */\n // A ConversionError arrived already saying what went wrong — a callback that threw, say.\n // Wrapping it again turned \"the onProgress callback threw\" into `Writing to \"out\"\n // failed: the onProgress callback threw`, under a hint about checking the destination.\n if (cause instanceof ConversionError) throw cause;\n\n if (cause instanceof EdfError) {\n const where = outputDir === null ? 'stdout' : `\"${outputDir}\"`;\n throw new ConversionError(\n 'INPUT_UNREADABLE',\n cause.message,\n `${cause.hint ? `${cause.hint} ` : ''}What was written to ${where} before it failed is ` +\n `incomplete and should not be used.`,\n );\n }\n\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to ${outputDir === null ? 'stdout' : `\"${outputDir}\"`} failed: ${detail}`,\n writeHint(cause, outputDir === null),\n );\n }\n}\n\n/**\n * One record's samples in the long layout: `time_s,channel,value`, in time order.\n *\n * The groups are merged rather than written one after another. Every sample in a record\n * falls inside that record's span, so taking the earliest next sample across the groups\n * each time leaves the whole file sorted by `time_s` — which is the only thing that makes a\n * mixed-rate long table useful, since sorting 3 million rows afterwards is the reader's\n * problem and a large one.\n *\n * Ties go to the group with the higher rate, which is the order the groups are already in.\n * Within a sample time the channels come out in the order the file declares them.\n */\n/**\n * Whether two sample times are close enough that only rounding could separate them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude. Well below any\n * real sample interval — the finest a recording can declare is bounded by its record\n * duration and samples-per-record fields — and well above the one-ULP disagreement that two\n * exact divisions of the same instant produce.\n */\nfunction nearlyEqual(a: number, b: number): boolean {\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/** The chosen instant as it will be written, for deciding which channels share it. */\nfunction formatAt(\n open: readonly OpenGroup[],\n cursors: Int32Array,\n recordStart: number,\n earliest: number,\n): string {\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n if (recordStart + sample / entry.group.rate === earliest) {\n return entry.formatTime(recordStart, sample);\n }\n }\n return '';\n}\n\nasync function writeLongRecord(\n file: EdfFile,\n open: readonly OpenGroup[],\n batch: RecordBatch,\n recordInBatch: number,\n recordStart: number,\n range: ConversionPlan['range'],\n): Promise<void> {\n const writer = open[0]?.writer;\n if (!writer) return;\n\n const cursors = new Int32Array(open.length);\n /* Reused across sample times so a three-million-row conversion allocates one of these. */\n const due: { entry: OpenGroup; groupIndex: number; sample: number; channel: number }[] = [];\n\n for (;;) {\n let earliest = Infinity;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time < earliest) earliest = time;\n }\n if (earliest === Infinity) return;\n\n /*\n Everything at this instant, in the order the file declares its channels.\n\n Groups are ordered by rate, largest first, because that is how the wide layout names\n its files. Emitting a tie group by group therefore ordered the channels by descending\n sampling rate — so a recording declaring `slow, medium, fast` wrote `fast, medium,\n slow`, while the documentation promised file order and channels.csv listed file order.\n Signal index is the file's own order, and the only one a reader can predict.\n\n \"At this instant\" is decided on the time as written, not on the double. `s / rate` for\n two channels at one moment need not give the same double: a 0.3 s record holding 12 and\n 4 samples makes 40 Hz and 13.333… Hz, and 9/40 is 0.22500000000000000555 while 3/13.333…\n is 0.22499999999999997780. Equality missed that, so those two rows fell out in numeric\n order — `slow` before `fast`, once, in the middle of a file that was otherwise right.\n\n Two rows are at one time exactly when they carry the same `time_s`, which is the only\n definition a reader of the CSV can apply. The numeric pre-filter keeps the common case\n to one comparison; formatting happens only for candidates already within a hair.\n */\n const earliestText = formatAt(open, cursors, recordStart, earliest);\n due.length = 0;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time !== earliest) {\n // Far away in the ordinary case; only a near-tie is worth formatting.\n if (!nearlyEqual(time, earliest)) continue;\n if (entry.formatTime(recordStart, sample) !== earliestText) continue;\n }\n cursors[g] = sample + 1;\n // Same window rule as the wide layout, with the same per-rate slack.\n if (\n !sampleTimeIsInRange(earliest, range.startSeconds, range.endSeconds, toleranceFor(entry.group.rate))\n ) {\n continue;\n }\n for (let c = 0; c < entry.group.channels.length; c++) {\n due.push({ entry, groupIndex: g, sample, channel: c });\n }\n }\n if (due.length === 0) continue;\n if (due.length > 1) {\n due.sort(\n (a, b) =>\n (a.entry.group.channels[a.channel]?.signal.index ?? 0) -\n (b.entry.group.channels[b.channel]?.signal.index ?? 0),\n );\n }\n\n for (const item of due) {\n const channel = item.entry.group.channels[item.channel];\n const format = item.entry.formatters[item.channel];\n if (!channel || !format) continue;\n if (writer.hungUp) return;\n writer.pushLine(\n `${item.entry.formatTime(recordStart, item.sample)},${escapeCsvField(channel.column)},` +\n `${format(file.sampleAt(batch, recordInBatch, channel.signal, item.sample))}`,\n );\n item.entry.rows++;\n // Flushed inside the record for the same reason the wide layout is; see there.\n if (writer.full) await writer.flush();\n }\n }\n}\n\nasync function streamSignalRows(\n file: EdfFile,\n plan: ConversionPlan,\n open: OpenGroup[],\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n): Promise<WrittenFile[]> {\n const { startSeconds, endSeconds, startRecord, endRecord } = plan.range;\n const { recordDuration } = file.header;\n let recordsDone = 0;\n\n /*\n Every destination has hung up, so nothing formatted from here on could reach anyone.\n `--stdout | head -1` is the usual way to arrive here: the reader takes one line and\n closes the pipe while the conversion is still near the start of the recording.\n\n Stopping also makes the reported row count mean what it says — rows that reached the\n consumer, rather than rows the loop went on formatting into a discarded buffer.\n */\n const allHungUp = (): boolean => open.length > 0 && open.every((entry) => entry.writer.hungUp);\n\n for await (const batch of file.readRecords({ startRecord, endRecord })) {\n if (allHungUp()) break;\n for (let r = 0; r < batch.recordCount; r++) {\n if (allHungUp()) break;\n const index = batch.firstRecordIndex + r;\n const recordStart = recordStarts ? (recordStarts[index] ?? index * recordDuration) : index * recordDuration;\n\n if (plan.layout === 'long') {\n await writeLongRecord(file, open, batch, r, recordStart, plan.range);\n recordsDone++;\n continue;\n }\n\n for (const entry of open) {\n const { group, writer, formatters, formatTime } = entry;\n const { channels, rate } = group;\n // Slack that never reaches the next sample; see toleranceFor.\n const slack = toleranceFor(rate);\n\n for (let sample = 0; sample < group.samplesPerRecord; sample++) {\n const time = recordStart + sample / rate;\n if (!sampleTimeIsInRange(time, startSeconds, endSeconds, slack)) continue;\n\n let row = formatTime(recordStart, sample);\n for (let c = 0; c < channels.length; c++) {\n const channel = channels[c];\n const format = formatters[c];\n if (!channel || !format) continue;\n row += ',' + format(file.sampleAt(batch, r, channel.signal, sample));\n }\n if (writer.hungUp) break;\n writer.pushLine(row);\n entry.rows++;\n\n /*\n Flushed inside the record, not only at the end of one.\n\n The buffer was drained once per record, so the rows of a single record piled up\n with nothing emptying them — memory followed samples-per-record rather than the\n batch size the writer exists to hold to. One record of 16,000,000 samples died\n with a heap out of memory under a 256 MB cap, while the same 32 MB of samples\n split into 16,000 records converted to the same 283 MB CSV without trouble. The\n format allows either layout and says nothing about which to expect.\n\n `full` is a synchronous read of the pending size, so the twenty million rows that\n are not at a boundary cost a comparison rather than a microtask each.\n */\n if (writer.full) await writer.flush();\n }\n await writer.maybeFlush();\n }\n\n recordsDone++;\n }\n\n if (options.onProgress) {\n /*\n A caller's callback is the caller's, and its failures are not the destination's.\n\n This ran inside the same try that turns a stream failure into WRITE_FAILED, so a\n progress callback that threw came back as `Writing to \"out\" failed: caller bug`,\n advising the reader to check a destination that was working perfectly. It is the\n same misattribution the write hints had until 0.4.36, one layer up.\n\n The original is kept as `cause`, so the stack that actually matters survives, and\n the conversion still stops — the callback threw, and carrying on writing into a\n directory whose owner has just failed is not an improvement.\n */\n try {\n options.onProgress({\n recordsDone,\n recordsTotal: endRecord - startRecord,\n // Once per writer: the long layout's groups share one, so summing per group\n // reported a figure larger than the file being written.\n bytesWritten: [...new Set(open.map((entry) => entry.writer))].reduce(\n (sum, entry) => sum + entry.charsWritten,\n 0,\n ),\n });\n } catch (cause) {\n throw new ConversionError(\n 'CALLBACK_FAILED',\n `The onProgress callback threw: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'This is the caller\\'s callback, not the recording or the destination. Whatever was ' +\n 'written before it threw is incomplete and should not be used.',\n { cause },\n );\n }\n }\n }\n\n const closed = new Set<BufferedLineWriter>();\n for (const entry of open) {\n if (closed.has(entry.writer)) continue;\n closed.add(entry.writer);\n try {\n await entry.writer.end();\n // With --gzip the writer's stream is the compressor, whose end callback fires when the\n // compressor is done rather than when the file behind it is. Awaiting only that would\n // report success with the tail of the file still in flight.\n await entry.settled;\n } finally {\n // In a finally for the reason 0.5.45 gives about the writer's own release: a\n // conversion that fails here still has to leave process.stdout as it found it, and a\n // failure is exactly when a caller goes on to convert something else.\n entry.release();\n }\n }\n\n // A shared table is one file, and its row count is every group's rows, not the first's.\n if (plan.layout === 'long') {\n const first = open[0];\n if (!first) return [];\n return [{ name: first.group.fileName, rows: open.reduce((sum, e) => sum + e.rows, 0) }];\n }\n return open.map((entry) => ({ name: entry.group.fileName, rows: entry.rows }));\n}\n\n/**\n * The stream rows are written to, plus a promise for the data reaching its destination.\n *\n * Compression sits between the writer and the file as a transform. Failures below it — a\n * full disk, an unwritable path — surface on the file stream, where nothing is listening,\n * so they are forwarded onto the compressor: that is the stream the writer watches, and\n * routing them there keeps one error path rather than two.\n */\nfunction compressed(\n target: Writable,\n gzip: boolean,\n): { stream: Writable; settled: Promise<void>; release: () => void } {\n if (!gzip) return { stream: target, settled: Promise.resolve(), release: (): void => {} };\n const compressor = createGzip();\n /*\n Removable, because one of the streams this can be handed outlives the conversion.\n\n 0.5.36 fixed exactly this leak for the writer's own listener: a library caller running\n twelve `toStdout` conversions left twelve 'error' listeners on `process.stdout` and got\n Node's MaxListenersExceededWarning on the eleventh. That fix is `BufferedLineWriter`'s\n `#release()`, and it cannot reach this one — under `gzip` the writer's stream is the\n compressor, and `process.stdout` is only ever the compressor's destination. So the same\n leak survived on the same stream, behind one extra flag, and the regression test written\n to catch it does not pass `gzip: true`.\n\n A file stream is a different matter and needs no release: it is created for this\n conversion and closed with it. The discipline is the writer's — release only from a\n stream this tool does not own.\n */\n const forward = (error: Error): void => {\n compressor.destroy(error);\n };\n target.on('error', forward);\n const release = (): void => {\n if (target === process.stdout || target === process.stderr) target.off('error', forward);\n };\n\n /*\n pipe() ends its destination when the source ends, and stdout must survive the\n conversion: the writer already refuses to close it, because a closed stdout breaks\n every later write in the process. Nothing waits on stdout either — it is not this\n tool's to finish.\n */\n const toStdout = target === process.stdout;\n compressor.pipe(target, { end: !toStdout });\n if (toStdout) {\n /*\n Waited on, even though stdout is not ours to end.\n\n This returned an already-resolved promise, so `await entry.settled` waited for nothing\n and the conversion declared itself finished while the compressor still held the tail of\n the stream. `--stdout --gzip` onto a destination that filled up therefore printed the\n ENOSPC *and then* \"Wrote 102,400 rows to stdout.\" — and exited 0, over a file 11,270\n bytes short whose gzip member has no trailer and will not decompress. Through `--out`,\n on the same volume with the same space, the identical failure is reported and exits 1.\n\n The byte audit could not see it either: it stats the descriptor as soon as the writers\n are done, which on this path is before the compressor has pushed its last chunks.\n\n `finished(compressor)` is the source side, not the destination — it resolves when the\n compressor has flushed everything into stdout, and it is stdout's own write that fails.\n That keeps `end: !toStdout` exactly as it was: nothing here closes stdout.\n */\n const flushed = finished(compressor).catch((error: unknown) => {\n /*\n EPIPE is not a failure here, and turning it into one is the trap this nearly fell\n into: `--stdout --gzip | head` is an ordinary thing to type, and the documented\n answer to it is \"Stopped: the reader closed the pipe after 52,507 of 102,400 rows\n had been written\", exit 0. Waiting on the compressor surfaced the EPIPE that the\n uncompressed path already routes through the writer's hang-up flag — the writer\n sees the same error, forwarded, and records it — so the wait has to let that one\n through and keep everything else.\n */\n if ((error as NodeJS.ErrnoException | null)?.code === 'EPIPE') return;\n throw error;\n });\n flushed.catch(() => {});\n return { stream: compressor, settled: flushed, release };\n }\n\n /*\n A failure under the compressor rejects both this promise and the writer's own end(),\n and end() is the one awaited first. Without a handler attached here that rejection\n belonged to nobody, and Node killed the process over it: `--gzip` into an unwritable\n path printed a raw EISDIR stack trace instead of the ordinary \"Writing to ... failed\"\n message and exit 1, which is what the same path does uncompressed.\n\n Attaching the handler is enough to make it handled. Awaiting `settled` downstream still\n reports the failure in the case where end() happened to succeed.\n */\n const settled = finished(target);\n settled.catch(() => {});\n return { stream: compressor, settled, release };\n}\n\n/**\n * Write one of the sidecar files, reporting a failure the way the signal writer does.\n *\n * These three used to call `writeFile` bare, so a failure escaped as whatever the\n * filesystem said — `EISDIR: illegal operation on a directory, open '...'` with no hint\n * and, more importantly, no mention that the signal files had already been written. The\n * conversion stopped half-done and the message gave no sign of it.\n */\nasync function writeOutputFile(\n outputDir: string,\n name: string,\n contents: string,\n gzip = false,\n bom = false,\n): Promise<void> {\n // metadata.json never gets one: JSON.parse rejects a leading U+FEFF, so a mark there\n // would break every reader of the file to help a spreadsheet that will not open it.\n if (bom) contents = UTF8_BOM + contents;\n try {\n // The sidecars are built in memory before being written, so compressing them in memory\n // costs nothing extra. Only the signal tables are large enough to need a stream.\n await writeFile(path.join(outputDir, name), gzip ? gzipSync(contents) : contents, gzip ? undefined : 'utf8');\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing \"${name}\" to \"${outputDir}\" failed: ${detail}`,\n writeHint(cause),\n );\n }\n}\n\nasync function writeChannelsCsv(\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const includedColumns = new Set(plan.groups.flatMap((g) => g.channels.map((c) => c.signal.index)));\n const fileFor = new Map<number, string>();\n for (const group of plan.groups) {\n for (const channel of group.channels) fileFor.set(channel.signal.index, group.fileName);\n }\n\n const lines = [\n csvRow([\n 'column',\n 'signal_index',\n 'label',\n 'unit',\n 'sampling_rate_hz',\n 'samples_per_record',\n 'physical_min',\n 'physical_max',\n 'digital_min',\n 'digital_max',\n 'transducer',\n 'prefiltering',\n 'output_file',\n 'converted',\n ]),\n ];\n\n for (const signal of file.header.signals) {\n if (signal.isAnnotations) continue;\n const column = plan.columnNames.get(signal.index) ?? `signal_${signal.index}`;\n lines.push(\n csvRow([\n column,\n String(signal.index),\n signal.label,\n signal.physicalDimension,\n String(signal.samplingRate),\n String(signal.samplesPerRecord),\n String(signal.physicalMin),\n String(signal.physicalMax),\n String(signal.digitalMin),\n String(signal.digitalMax),\n signal.transducer,\n signal.prefiltering,\n fileFor.get(signal.index) ?? '',\n includedColumns.has(signal.index) ? 'yes' : 'no',\n ]),\n );\n }\n\n const name = gzip ? 'channels.csv.gz' : 'channels.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: lines.length - 1 };\n}\n\n/*\n The bounds an annotation is filtered against: what the caller actually asked for, not the\n window that survived being clamped to the recording.\n\n Filtering by the resolved window meant an unbounded request still lost events. `--end 999h`\n on a three-second file clamps to 3, and `--start 0` gets its end from the recording, so\n both filtered to [0, 3) and dropped the markers at 3.0 and 3.5 that a bare invocation\n keeps — asking for more of a recording returned less of it.\n\n An end the caller did not give is unbounded, not \"the end of the data\": EDF+ lets an\n annotation sit past the last sample, which is exactly where an end-of-recording marker is.\n\n `--duration` is measured from wherever the conversion actually starts. Anchoring it at 0\n instead read the same absent `--start` two ways in two adjacent lines, and on a recording\n that does not begin at zero the two windows did not even overlap: an EDF+D file whose first\n record sits at 30 s converted its samples from [30, 35) while filtering annotations against\n (-inf, 5), so every event inside the converted window was dropped and `annotations.csv` came\n back empty. `resolveRange` has always defaulted the same missing start to the earliest\n record, which is where this now takes it from.\n*/\n/**\n * What the durations in the events that will actually be written look like.\n *\n * These two warnings were raised from the file-wide counts the decoder accumulates, while\n * `annotations.csv` is filtered to the requested window. A conversion of one second of a\n * recording therefore warned that \"1 annotation states a duration that is not a number, so\n * its duration_s cell is empty\" about an event two seconds outside it — naming a cell that is\n * not in the output — and `--strict` failed the run for it. There is no such value, no such\n * cell, and no such row.\n *\n * Taken from the events themselves, after the same filter the writer applies, so the count\n * and the sentence describe the same rows. An unreadable duration is carried on the event\n * because `duration: null` cannot say whether the file gave one; a negative duration needs no\n * flag, since the value is right there.\n */\nexport function durationDiagnostics(\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n): Diagnostic[] {\n const written = annotations.filter((a) => a.onset >= window.from && a.onset < window.to);\n const diagnostics: Diagnostic[] = [];\n\n const negative = written.filter((a) => a.duration !== null && a.duration < 0).length;\n if (negative > 0) {\n const one = negative === 1;\n diagnostics.push({\n code: 'ANNOTATION_DECODE_FAILED',\n severity: 'warning',\n message:\n `${negative} annotation${one ? '' : 's'} state${one ? 's' : ''} a duration below zero, ` +\n `which is not a length of time.`,\n hint:\n 'The value is written to annotations.csv as the file gave it. Adding it to onset_s ' +\n 'ends the event before it starts, so check these rows before using the durations.',\n });\n }\n\n const unreadable = written.filter((a) => a.durationUnreadable === true).length;\n if (unreadable > 0) {\n const one = unreadable === 1;\n diagnostics.push({\n code: 'ANNOTATION_DECODE_FAILED',\n severity: 'warning',\n message:\n `${unreadable} annotation${one ? '' : 's'} state${one ? 's' : ''} a duration that is ` +\n `not a number, so ${one ? 'its' : 'their'} duration_s cell is empty.`,\n hint:\n 'The onset and the description were read normally. An empty duration_s otherwise ' +\n 'means the file stated no duration, so these rows cannot be told apart from those.',\n });\n }\n\n return diagnostics;\n}\n\n/**\n * The window annotations are filtered by — the bounds as asked for, not as snapped to records.\n *\n * Exported so `--info` can count the events a conversion would write using the same predicate\n * that writes them, rather than a second copy of it.\n */\nexport function requestedAnnotationWindow(\n options: ConvertOptions,\n recordingStart: number,\n): { from: number; to: number } {\n const from = options.start ?? -Infinity;\n const to =\n options.end !== undefined\n ? options.end\n : options.duration !== undefined\n ? (options.start ?? recordingStart) + options.duration\n : Infinity;\n return { from, to };\n}\n\nasync function writeAnnotationsCsv(\n outputDir: string,\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const inWindow = annotations\n .filter((a) => a.onset >= window.from && a.onset < window.to)\n .sort((a, b) => a.onset - b.onset || a.recordIndex - b.recordIndex);\n\n const lines = [csvRow(['onset_s', 'duration_s', 'description', 'record_index'])];\n for (const annotation of inWindow) {\n lines.push(\n csvRow([\n plain(annotation.onset),\n annotation.duration === null ? '' : plain(annotation.duration),\n annotation.text,\n String(annotation.recordIndex),\n ]),\n );\n }\n\n const name = gzip ? 'annotations.csv.gz' : 'annotations.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: inWindow.length };\n}\n\n/**\n * What to try next, chosen from what actually went wrong.\n *\n * Every write failure carried the same advice — \"Free up space or choose another destination\n * with --out\" — which fits exactly one errno. A directory sitting where signals.csv belongs\n * came back telling the reader to free up disk space, and so did a read-only volume, a\n * permission denial and a path too long for the filesystem. Wrong advice is worse than none:\n * it sends someone to check `df` on a disk that is fine, and the thing that is actually\n * wrong stays unexamined.\n *\n * The errno is the one piece of the failure that names the cause, so it is what picks the\n * sentence. Anything unrecognised keeps the general form rather than guessing.\n */\nfunction writeHint(cause: unknown, toStdout = false): string {\n /*\n The stdout path writes no files, and --out is the flag its user chose not to pass.\n\n Both halves of this sentence were wrong there: \"the files written so far\" named files\n that do not exist, and \"choose another destination with --out\" is advice for a different\n command — the destination is whatever the shell redirected the stream to. Same class as\n the disk-space hint this function replaced, one flag over.\n */\n const preamble = toStdout\n ? 'What reached stdout before it failed is incomplete and should not be used. '\n : 'The files written so far are incomplete and should not be used. ';\n const elsewhere = toStdout ? 'redirect it somewhere else' : 'choose another with --out';\n const code = (cause as NodeJS.ErrnoException | null)?.code;\n switch (code) {\n case 'ENOSPC':\n return `${preamble}The destination is out of space; free some up or ${elsewhere}.`;\n case 'EDQUOT':\n return `${preamble}You are over your disk quota on this filesystem; ${elsewhere}.`;\n case 'EACCES':\n case 'EPERM':\n return `${preamble}You do not have permission to write there; ${elsewhere}.`;\n case 'EROFS':\n return `${preamble}That filesystem is mounted read-only; ${elsewhere}.`;\n case 'EISDIR':\n return `${preamble}A directory is sitting where that file belongs; remove or rename it, or ${elsewhere}.`;\n case 'ENOENT':\n return `${preamble}Part of that path no longer exists; make sure nothing is removing it while the conversion runs.`;\n case 'ENAMETOOLONG':\n return `${preamble}That path is longer than the filesystem allows; ${toStdout ? 'redirect it somewhere shorter' : 'choose a shorter destination with --out'}.`;\n case 'EMFILE':\n case 'ENFILE':\n return `${preamble}Too many files are open; a recording with many sampling rates opens one output file per rate, so --channels narrows it.`;\n case 'EPIPE':\n return `${preamble}Whatever was reading the output closed it before the conversion finished.`;\n default:\n return `${preamble}Check the destination and run the conversion again.`;\n }\n}\n\n/**\n * Confirms that everything handed to a file-backed stdout actually arrived.\n *\n * `edf2csv rec.edf --stdout > out.csv` onto a volume that the output very nearly fills lost\n * the tail in silence: 94,977 of 102,400 rows on disk, the file ending mid-row, stderr\n * announcing \"Wrote 102,400 rows to stdout.\" and the process exiting 0. The same recording\n * onto the same volume through `--out` fails correctly, which is what gives it away.\n *\n * POSIX `write` returns a short count rather than an error when the disk fills partway\n * through a single call, and only the NEXT write raises ENOSPC. `--out` always has a next\n * write — channels.csv and metadata.json come after — so it always finds out. `--stdout`\n * has nothing after it, and when fd 1 is a regular file Node's stdout is a SyncWriteStream\n * whose `_write` discards the byte count `writeSync` returns, so nothing is raised at all.\n * No error means no `#failure`, so checking that alone would not have caught this.\n *\n * What can be checked is the descriptor: how much it grew against how much it was given.\n * Only for a regular file — a pipe, a terminal or a socket has no size to compare, and on\n * those a short write cannot go unreported this way. Appending (`>>`) is fine, since the\n * starting size is taken before anything is written.\n */\n/**\n * Exported so `--info` can use the same audit a `--stdout` conversion does.\n *\n * `--info` wrote its description with `process.stdout.write` and looked at nothing: redirected\n * into a full filesystem it produced a zero-byte file and exited 0, so `edf2csv rec.edf --info\n * > desc.txt` reported success over nothing at all. A 900-channel recording's description is\n * 58 KB, which is not a size a destination is guaranteed to have.\n */\nexport function auditStdout(): { count: (bytes: number) => void; verify: () => void } | null {\n let startSize: number;\n try {\n const info = fstatSync(1);\n if (!info.isFile()) return null;\n startSize = info.size;\n } catch {\n // No usable descriptor to audit; the conversion is not the place to complain about it.\n return null;\n }\n\n let expected = 0;\n return {\n count: (bytes: number): void => {\n expected += bytes;\n },\n verify: (): void => {\n let landed: number;\n try {\n landed = fstatSync(1).size - startSize;\n } catch {\n return;\n }\n if (landed >= expected) return;\n\n /*\n This audit exists for the failure nothing else reports: a write that is accepted, and\n silently truncated. When the stream itself raised an error there is nothing left for\n it to add, and what it added was a second `error:` line denying the first —\n\n error: Writing to stdout failed: ENOSPC: no space left on device, write\n error: Writing to stdout failed: 58900 of 58900 bytes did not reach the destination,\n which stopped accepting them part way through.\n ... nothing after it raised an error because there was nothing after it.\n\n one failure, reported twice, the second of the two wrong about it.\n */\n if (process.stdout.errored) return;\n\n /*\n Nothing landing at all is not a short write, and was described as one.\n\n `--info > desc.txt` onto a full filesystem left a zero-byte file, and this said the\n destination \"stopped accepting them part way through\" and that \"what is there ends\n mid-row\" — of a file with nothing in it and no rows in it.\n */\n if (landed === 0) {\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: none of the ${expected} bytes reached the destination.`,\n 'The destination is almost certainly out of space. Nothing was written, so there ' +\n 'is nothing there to discard.',\n );\n }\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: ${expected - landed} of ${expected} bytes did not reach ` +\n `the destination, which stopped accepting them part way through.`,\n 'What is there ends mid-row and should not be used. The destination is almost ' +\n 'certainly out of space — a short write is how a filesystem reports filling up ' +\n 'mid-write, and nothing after it raised an error because there was nothing after it.',\n );\n },\n };\n}\n\n/**\n * Why `--stdout` cannot take this recording, or null when it can.\n *\n * Lifted out of the conversion so `--info` can ask the same question. It was not asking:\n * `--info --stdout` on a three-rate recording predicted \"Would write 1,155 rows, roughly\n * 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n * refuses to run, writes nothing, and names no files. `--info` exists to say what a\n * conversion will do, and this is one of the things it does.\n *\n * Reported by `--info` as a warning rather than a refusal, for the reason 0.5.51 gives about\n * the destination guards: `--info` writes nothing, so a rule about what the output would be\n * has no business stopping it from describing the recording — and being told the command\n * will not work is exactly what you asked.\n */\nexport function stdoutRefusal(file: EdfFile, plan: ConversionPlan): ConversionError | null {\n if (!plan.writeSignals) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n '--stdout has no signal data to write because --annotations-only was given.',\n 'Drop one of the two flags.',\n );\n }\n\n /*\n No table at all is its own answer, and neither layout gave it.\n\n A recording with no signal channels — one holding only EDF+ annotations — produced\n zero rate groups. The wide layout then said \"--stdout needs exactly one table, but\n this recording produces 0, one for each sampling rate its channels use ()\", with an\n empty parenthetical, advice to narrow to one of no rates, and advice to use\n `--layout long` — which wrote zero bytes to stdout, not even a header row, and exited\n 0 while warning that \"the signal files hold their headers and no data\". There were no\n files and there was no header. The one path that was right about this is\n `--annotations-only`, which refuses outright, and this is the same situation reached\n by a different route.\n */\n if (plan.groups.length === 0) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n file.dataSignals.length === 0\n ? '--stdout has no signal data to write: this recording has no signal channels, ' +\n 'only EDF+ annotations.'\n : '--stdout has no signal data to write: nothing was selected that carries samples.',\n file.dataSignals.length === 0\n ? 'Convert to a directory to get its annotations.csv, or drop --stdout.'\n : 'Check --channels and the requested window, or convert to a directory instead.',\n );\n }\n\n // The long layout is one table whatever the rates are, so it has nothing to refuse.\n if (plan.layout !== 'long' && plan.groups.length !== 1) {\n return new ConversionError(\n 'UNSUPPORTED_REQUEST',\n // Naming the rates is the point: the hint says to narrow the selection, and this\n // is what there is to narrow it to. The parenthetical used to repeat the count\n // that had just been given — \"produces 3 (its channels use 3 different sampling\n // rates)\" — which told nobody anything they could act on.\n `--stdout needs exactly one table, but this recording produces ${plan.groups.length}, ` +\n `one for each sampling rate its channels use ` +\n `(${listed(formatRates(plan.groups.map((g) => g.rate)).map((r) => `${r} Hz`))}).`,\n 'Narrow it to one rate with --channels, write --layout long to get them all in ' +\n 'one table, or convert to a directory instead.',\n );\n }\n return null;\n}\n\n\n\n/**\n * Asked for signal data and given none to put in a file.\n *\n * Every channel selected carries zero samples per record, so there is no table to make —\n * `edf2csv rec.edf --channels unused` writes channels.csv and metadata.json and no signals.csv\n * at all. The NO_SAMPLES warning explains the channel; nothing explained the missing file, and\n * the documentation says signals.csv is written unless --annotations-only was passed. Someone\n * looking for it should be told where it went.\n *\n * There are two ways to arrive with no groups, and one wording is only true of one of them. A\n * recording that holds nothing but EDF+ annotations has no channel that could have been\n * selected, its channels.csv is a header row and nothing else, and no channel of it carries\n * samples — so \"every channel selected\", \"channels.csv still describes them\" and \"which\n * channels do carry samples\" were three false statements in one warning, printed under a\n * warning that had just said the file has no signal channels.\n *\n * Exported, and worded in the present tense, so `--info` can raise the same one. It was built\n * inline here, which meant the one mode whose purpose is to say what a conversion will do said\n * nothing about the file that conversion would not write: `--info --strict` on a recording of\n * nothing but annotations reported one warning where converting it reported two, and\n * `--info --json` carried the shorter list to whatever reads it. Everything the answer depends\n * on is in the file and the plan, both of which `--info` already has.\n */\nexport function noSignalFile(file: EdfFile, plan: ConversionPlan): Diagnostic | null {\n if (!plan.writeSignals || plan.groups.length > 0) return null;\n const noChannelsAtAll = file.dataSignals.length === 0;\n return {\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: noChannelsAtAll\n ? 'No signal file is written: there is no signal data in this recording to put in one.'\n : 'No signal file is written: every channel selected carries zero samples per data ' +\n 'record, so there is nothing to put in one.',\n hint: noChannelsAtAll\n ? 'annotations.csv holds whatever events it carries. channels.csv lists signal ' +\n 'channels, so it has none to list.'\n : 'channels.csv still describes them. Run with --info to see which channels do carry ' +\n 'samples.',\n };\n}\n\n/**\n * `--annotations-only` on a recording that has no annotations.\n *\n * Exported and present-tense for the same reason `noSignalFile` above is. `--info` prints an\n * accurate line about it in the report body — \"and no annotations.csv either, since this\n * recording has no annotation channel\" — and raised nothing, so the one mode whose purpose is\n * to say what a conversion will do carried a shorter warning list than the conversion did:\n * `--info --json --annotations-only` on a plain EDF file listed no warning where converting\n * lists one, and `--info --strict`, which cli-reference.md recommends for screening a folder\n * before converting it, exited 0 where the conversion exits 1. Same defect as the one 0.7.84\n * closed for `NO_SAMPLES`, one flag over; both halves of the answer are in the file and the\n * options, which `--info` already has.\n */\nexport function noAnnotations(file: EdfFile, options: ConvertOptions): Diagnostic | null {\n if (options.annotationsOnly !== true || file.annotationSignals.length > 0) return null;\n return {\n code: 'NO_ANNOTATIONS',\n severity: 'warning',\n message:\n '--annotations-only was requested but this recording has no annotation channel, ' +\n 'so there are no events to export.',\n hint: 'Plain EDF files carry no annotations. Convert without --annotations-only to get the signals.',\n };\n}\n\n/** Raised when the input moved while it was being read. See where it is pushed. */\nfunction inputChanged(hadChecksum: boolean): Diagnostic {\n return {\n code: 'INPUT_CHANGED',\n severity: 'warning',\n message:\n 'The input changed while it was being converted, so this output covers the file as ' +\n 'it was when the conversion started, not as it is now.',\n hint: hadChecksum\n ? 'No checksum was recorded: the bytes that were converted are no longer there to ' +\n 'hash. Convert again once the recording is finished.'\n : 'Convert again once the recording is finished to pick up the rest.',\n };\n}\n\nasync function writeMetadata(\n outputDir: string,\n inputPath: string,\n file: EdfFile,\n plan: ConversionPlan,\n written: readonly WrittenFile[],\n annotationCount: number,\n /** Hash of the bytes that were converted, or null when it could not be vouched for. */\n checksum: string | null,\n /** Whether the record start times could be read; see withTimingPromiseKept. */\n timedFromRecords: boolean,\n): Promise<void> {\n const { header } = file;\n\n /*\n The file as it was when it was opened, not as it is now.\n\n Both of these used to come from re-opening the path once the CSVs were written, which\n describes whatever answers to that name by then rather than what was converted. A\n recording still being written grew from 2,000 records to 3,000 mid-conversion and\n metadata.json recorded `data_records: 2000` — correct, the CSV holds 2,000 — beside the\n byte count and SHA-256 of the 3,000-record file. The two halves of one provenance record\n described two different files, and the checksum covered bytes nobody had converted.\n Replacing the file at that path did the same thing more thoroughly.\n\n `file.fileSize` is the number every record count and window in this output was derived\n from, and the hash is taken over exactly those bytes through the descriptor already open\n on them, so the record describes one file throughout.\n */\n const metadata = {\n tool: { name: 'edf2csv', version: TOOL_VERSION },\n source: {\n path: path.resolve(inputPath),\n bytes: file.fileSize,\n modified: new Date(file.modifiedAtOpenMs).toISOString(),\n sha256: checksum,\n },\n recording: {\n format: describeFormat(header),\n version: header.version,\n patient_id: header.patientId,\n recording_id: header.recordingId,\n // Zone-less on purpose: EDF records local wall-clock digits and no timezone.\n start_datetime_local: formatWallClock(header.startDateTime),\n start_date_raw: header.startDateRaw,\n start_time_raw: header.startTimeRaw,\n data_records: file.recordCount,\n data_records_declared: header.declaredRecordCount,\n record_duration_seconds: header.recordDuration,\n duration_seconds: file.durationSeconds,\n signal_count: header.signalCount,\n annotation_channels: file.annotationSignals.length,\n },\n conversion: {\n converted_at: new Date().toISOString(),\n start_seconds: plan.range.startSeconds,\n end_seconds: plan.range.endSeconds,\n whole_recording: plan.range.isWholeRecording,\n records_converted: [plan.range.startRecord, plan.range.endRecord],\n annotations_written: annotationCount,\n /*\n Which shape the signal table is in, which nothing recorded.\n\n A wide `signals.csv` and a long one are different files with different columns, and\n metadata.json described them identically — so a pipeline handed an output directory\n could not tell from the archive which it had. It matters most for `rate_groups` right\n below: in the wide layout those entries are one per file and their `channels` are that\n file's columns, and in the long layout every entry names the one shared table and its\n `channels` are values in that table's `channel` column. Same array, two readings, and\n no way to know which applied.\n */\n layout: plan.layout,\n files: written.map((f) => ({ name: f.name, rows: f.rows })),\n rate_groups: plan.groups.map((g) => ({\n file: g.fileName,\n sampling_rate_hz: g.rate,\n channels: g.channels.map((c) => c.column),\n decimals: g.channels.map((c) => c.decimals),\n })),\n },\n notes: [\n ...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timedFromRecords),\n ...plan.diagnostics,\n ].map((d) => ({\n code: d.code,\n severity: d.severity,\n message: d.message,\n })),\n };\n\n await writeOutputFile(outputDir, 'metadata.json', JSON.stringify(metadata, null, 2) + '\\n');\n}\n\n\n"]}
|
|
@@ -108,10 +108,15 @@ export function parseTimeSpec(input, optionName, allowNegative = false) {
|
|
|
108
108
|
see is the problem. 0.7.102 made this argument about a space; this is the same sentence
|
|
109
109
|
about the other character that reaches it. `--decimals` and `--jobs` both quote the value
|
|
110
110
|
back and say what is wrong with it.
|
|
111
|
+
|
|
112
|
+
Every plus, not the first one, and the leftover space with it — the same rule the spaced
|
|
113
|
+
refusal below applies to spaces, for the same reason: a hint whose command this function
|
|
114
|
+
refuses is worse than no hint. `+1h+30m` was answered with `1h+30m`, which comes straight
|
|
115
|
+
back as "is not a time I understand", and `+ 5s` with a value that opened on a space.
|
|
111
116
|
*/
|
|
112
117
|
if (text.startsWith('+')) {
|
|
113
118
|
throw new TimeRangeError(`${optionName} "${input}" begins with a plus. Write the number on its own: ` +
|
|
114
|
-
`${input.trim().
|
|
119
|
+
`${input.trim().replaceAll('+', '').trim()}`);
|
|
115
120
|
}
|
|
116
121
|
const signed = (seconds) => assertFinite(negative ? -seconds : seconds, optionName, input, allowNegative);
|
|
117
122
|
// Bare number means seconds.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-range.js","sourceRoot":"","sources":["../../src/convert/time-range.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Parsing for the time-range options.\n *\n * Researchers write offsets in whatever form is natural for the recording in front\n * of them: seconds for a short ECG strip, `30m` into a sleep study, `01:23:45` when\n * reading off a clock. All three are accepted; anything ambiguous is rejected with\n * a message that shows the forms that work.\n */\n\nimport { fixed, formatDuration } from '../format/number.js';\nimport { assertOptions } from './options.js';\n\nexport class TimeRangeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TimeRangeError';\n }\n}\n\n/**\n * Every spelling of a unit this accepts, and what one of it is worth in seconds.\n *\n * Exported so the spellings can be enumerated by a test rather than listed a second time:\n * twelve of these sixteen are named nowhere in the tool or its documentation, and none of\n * their values was checked by anything. `hrs: 360` would have converted `--start 2hrs` from\n * twelve minutes in and said nothing, which is the one kind of mistake a window may not make.\n */\nexport const UNIT_SECONDS: Record<string, number> = {\n h: 3600,\n hr: 3600,\n hrs: 3600,\n hour: 3600,\n hours: 3600,\n m: 60,\n min: 60,\n mins: 60,\n minute: 60,\n minutes: 60,\n s: 1,\n sec: 1,\n secs: 1,\n second: 1,\n seconds: 1,\n ms: 0.001,\n};\n\nconst CLOCK = /^(?:(\\d+):)?(\\d{1,2}):(\\d{1,2}(?:\\.\\d+)?)$/u;\nconst UNIT_TOKEN = /(\\d+(?:\\.\\d+)?)\\s*([a-z]+)/giu;\n\n/** How the value reads in a refusal: numbers bare, everything else quoted so its type shows. */\nfunction describeValue(value: unknown): string {\n return typeof value === 'number' ? String(value) : JSON.stringify(value) ?? String(value);\n}\n\n/**\n * Parse a duration or offset into seconds.\n *\n * Accepted: `90`, `90s`, `5m`, `1h30m`, `1h 30m 15s`, `00:30:00`, `30:00`, `250ms`.\n *\n * `allowNegative` is for the two options that name a position rather than a length.\n *\n * A recording is timed from its first record's timekeeping annotation, and nothing obliges that\n * to sit at or after zero: a file whose records run from -100 s to -97 s is one this tool reads,\n * times from -100, and describes with\n *\n * Timed from -100.000s (first sample; --start and --end use this clock)\n *\n * That line says the number can be typed straight back in, and it could not be. Every offset\n * such a recording has came back as \"not a time I understand\", so its whole clock was\n * unreachable and no window of it could be converted at all — the one file shape where a\n * window is refused for naming a moment the recording actually contains.\n *\n * A length below zero is still a different thing, and `--duration` still refuses one.\n */\nexport function parseTimeSpec(input: string, optionName: string, allowNegative = false): number {\n /*\n Text, because this is the function the API page points other people's users at: \"Use\n `parseTimeSpec` if you want to accept those forms from your own users.\"\n\n A value arriving from a JSON config or a form field is a number as often as a string, and\n `{ \"start\": 30 }` came back as `TypeError: input.trim is not a function` — a variable name\n from inside this file, thrown past a caller who had done exactly what the page told them\n to. Every other refusal here is a TimeRangeError quoting the value and the option. The\n number is not coerced, for the reason `channels: 'ECG'` is not iterated: a shape that\n happens to work for 30 also swallows NaN, and the CLI already writes `String(raw)`.\n */\n if (typeof input !== 'string') {\n throw new TimeRangeError(\n `${optionName} must be given as text, not ${describeValue(input)}. ` +\n `Seconds are written \"30\", and the other forms are \"30s\", \"5m\", \"1h30m\", \"00:30:00\".`,\n );\n }\n const trimmed = input.trim().toLowerCase();\n if (trimmed === '') {\n throw new TimeRangeError(`${optionName} is empty. Try a value like 30s, 5m, or 00:30:00.`);\n }\n\n /*\n The sign is read off the front and applied to the whole value, so `-1h30m` is an hour and a\n half before the origin rather than an hour before it and half an hour after.\n\n Nothing may sit between the sign and the number, and a leading `+` is still refused: every\n other number this tool takes is refused when written in a form nobody types, and `+5` is\n exactly what `--decimals` and `--jobs` reject one flag over.\n */\n const negative = trimmed.startsWith('-');\n const text = negative ? trimmed.slice(1) : trimmed;\n\n /*\n A leading plus, named as such.\n\n Refusing it is deliberate — see the paragraph above — but it came back as \"is not a time I\n understand. Try 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds\", which is the\n message for input that could not be read at all. Every part of `+5s` was read: the number\n parsed, the unit was looked up and found. The one thing wrong with it is a character the\n reader put there on purpose, and they were sent to re-check their unit spellings with `5m`\n sitting in the list of suggestions, differing from what they typed by the sign they cannot\n see is the problem. 0.7.102 made this argument about a space; this is the same sentence\n about the other character that reaches it. `--decimals` and `--jobs` both quote the value\n back and say what is wrong with it.\n */\n if (text.startsWith('+')) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" begins with a plus. Write the number on its own: ` +\n `${input.trim().replace('+', '')}`,\n );\n }\n const signed = (seconds: number): number =>\n assertFinite(negative ? -seconds : seconds, optionName, input, allowNegative);\n\n // Bare number means seconds.\n if (/^\\d+(?:\\.\\d+)?$/u.test(text)) return signed(Number(text));\n\n // Clock form: hh:mm:ss or mm:ss.\n const clock = CLOCK.exec(text);\n if (clock) {\n const hours = clock[1] === undefined ? 0 : Number(clock[1]);\n const minutes = Number(clock[2]);\n const seconds = Number(clock[3]);\n if (minutes >= 60 || seconds >= 60) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" has a minutes or seconds field of 60 or more.`,\n );\n }\n return signed(hours * 3600 + minutes * 60 + seconds);\n }\n\n // Unit form: 1h30m, 1h30m 15s, 250ms. A number sits directly against its unit; see below.\n UNIT_TOKEN.lastIndex = 0;\n let total = 0;\n let matched = 0;\n let consumed = 0;\n /** Whether any token had a space between its number and its unit. See the refusal below. */\n let spaced = false;\n let match: RegExpExecArray | null;\n // Which units have already been seen, so a repeat can be rejected rather than added.\n const seen = new Set<number>();\n while ((match = UNIT_TOKEN.exec(text)) !== null) {\n const amount = Number(match[1]);\n const unit = match[2] ?? '';\n const scale = UNIT_SECONDS[unit];\n if (scale === undefined) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" uses an unknown unit \"${unit}\". Use h, m, s or ms, ` +\n `or their long forms: hours, minutes, seconds.`,\n );\n }\n\n // \"1h1h\" is a typo, not a request for two hours. Summing repeated units silently\n // turned a slip into a plausible window that was quietly the wrong length. Units are\n // keyed by their scale so the aliases collapse together: \"1h30min20m\" is caught too.\n if (seen.has(scale)) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" gives the same unit twice. ` +\n `Combine each unit once, as in 1h30m.`,\n );\n }\n seen.add(scale);\n\n total += amount * scale;\n matched++;\n // Counted with the whitespace taken out, so the comparison below is against the same\n // string on both sides and a space inside a token reaches the refusal written for it\n // rather than the one about input that could not be read at all.\n consumed += match[0].replace(/\\s+/gu, '').length;\n if (/\\s/u.test(match[0])) spaced = true;\n }\n\n // Reject partially-understood input like \"5x\" or \"1h banana\".\n if (matched === 0 || consumed !== text.replace(/\\s+/gu, '').length) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" is not a time I understand. ` +\n `Try 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds.`,\n );\n }\n\n /*\n A space between a number and its unit, named as such.\n\n `5 min` is refused on purpose — cli-reference sets the rule out, and it is what keeps\n `1 2h` from being read as anything — but it came back as \"is not a time I understand. Try\n 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds\", which is the message for input\n that could not be read at all. Every part of `5 min` was read: the number parsed, the unit\n was looked up in the table and found. The one thing wrong with it is a space, and the\n reader was sent to re-check their unit spellings instead — with `5m` sitting in the list of\n suggestions, differing from what they typed by a character they cannot see is the problem.\n\n Only reachable now that the count above ignores whitespace; before, the length check caught\n these first and there was nothing left to tell them apart by.\n */\n if (spaced) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" puts a space between a number and its unit. ` +\n `Write them together: ${input.trim().replace(/(\\d)\\s+([a-z])/giu, '$1$2')}`,\n );\n }\n\n return signed(total);\n}\n\nfunction assertFinite(\n value: number,\n optionName: string,\n input: string,\n allowNegative: boolean,\n): number {\n /*\n Overflow answers for itself, rather than borrowing the sentence about signs.\n\n One branch covered two rejections. `--start` with four hundred nines is a positive value\n that `Number` returns as `Infinity`, and the answer was `is not a valid non-negative\n time` — which names the one thing about it that is not the problem. Worse on the two\n options this is called with `allowNegative` for: `--start` and `--end` take a sign on\n purpose, since a recording timed from before zero has no other way to be addressed, so\n \"non-negative\" is not their rule at all and overflow is the only way they reach here.\n */\n if (!Number.isFinite(value)) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" is further from zero than a number of seconds can hold.`,\n );\n }\n if (!allowNegative && value < 0) {\n throw new TimeRangeError(`${optionName} \"${input}\" is not a valid non-negative time.`);\n }\n return value;\n}\n\nexport interface ResolvedRange {\n /** Inclusive start, in seconds from the beginning of the recording. */\n startSeconds: number;\n /** Exclusive end, in seconds. */\n endSeconds: number;\n /** First data record touching the window. */\n startRecord: number;\n /** One past the last data record touching the window. */\n endRecord: number;\n /** True when the window covers the whole recording. */\n isWholeRecording: boolean;\n /** Earliest record start, including EDF+D timing gaps. */\n recordingStartSeconds: number;\n /** End of the latest record, including EDF+D timing gaps. */\n recordingEndSeconds: number;\n}\n\n/**\n * Slack for comparisons at sample/window boundaries.\n *\n * A nanosecond is far below any real sampling interval — 20 kHz is 50 microseconds — so it\n * absorbs the arithmetic error in `recordStart + sample / rate` without reaching a\n * neighbouring sample.\n */\nexport const BOUNDARY_TOLERANCE = 1e-9;\n\n/**\n * The slack to use for a channel sampled this often.\n *\n * Never as much as half a sample interval, because slack that reaches the next sample stops\n * being slack. A fixed nanosecond was applied whatever the rate, and the format does not\n * oblige the interval to be larger than it: EDF's record duration is an 8-character field\n * that accepts `1e-9`. A recording of two 1 ns records holding ten samples each wrote ten of\n * its twenty rows — the window ends at 2e-9, the comparison asked for `time < 2e-9 - 1e-9`,\n * and the entire second record failed it. Exit 0, no warning, half the samples gone.\n */\nexport function toleranceFor(rate: number): number {\n if (!(rate > 0) || !Number.isFinite(rate)) return BOUNDARY_TOLERANCE;\n return Math.min(BOUNDARY_TOLERANCE, 1 / rate / 2);\n}\n\n/** Match the exact half-open boundary rules used while writing signal rows. */\nexport function sampleTimeIsInRange(\n time: number,\n startSeconds: number,\n endSeconds: number,\n tolerance: number = BOUNDARY_TOLERANCE,\n): boolean {\n return time >= startSeconds - tolerance && time < endSeconds - tolerance;\n}\n\n/** Count samples from one record that fall inside a half-open requested window. */\nexport function countSamplesInRange(options: {\n recordStart: number;\n rate: number;\n samplesPerRecord: number;\n startSeconds: number;\n endSeconds: number;\n}): number {\n /*\n A record with no place on the clock contributes no rows, which is what the conversion\n already does with one: its sample times are non-finite, so every one of them fails the\n range test and none is written. The arithmetic below reached `Infinity - Infinity` and\n answered `NaN`, which `--info` then printed — \"Would write NaN rows, roughly NaN B.\" on a\n recording that goes on to write eight. Reachable from a header: a record duration of 1e308\n is five characters in an eight-character field, and the third record of one is past what a\n double can hold.\n */\n if (!Number.isFinite(options.recordStart)) return 0;\n const slack = toleranceFor(options.rate);\n const lower = Math.ceil(\n (options.startSeconds - slack - options.recordStart) * options.rate,\n );\n const upper = Math.ceil(\n (options.endSeconds - slack - options.recordStart) * options.rate,\n );\n const first = Math.max(0, Math.min(options.samplesPerRecord, lower));\n const last = Math.max(0, Math.min(options.samplesPerRecord, upper));\n return Math.max(0, last - first);\n}\n\n/**\n * Whether two instants differ only by the arithmetic that produced them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude — the same shape\n * the long layout uses to decide two sample times are one instant. Well below any real sample\n * interval, and well above the rounding that two routes to one quantity produce: a recording's\n * length is `recordCount * recordDuration`, which for 6003 records of 0.1s is not the 600.3 it\n * prints as.\n */\nfunction sameInstant(a: number, b: number): boolean {\n /*\n A relative tolerance has nothing to be relative to at infinity.\n\n `Math.abs(a - Infinity)` is Infinity, and so is the allowance beside it, so\n `Infinity <= Infinity` made every instant the same instant as an infinite one. A\n recording whose end overflows a double — three records of 1e308 seconds, which an\n 8-character record-duration field can state and a timekeeping TAL can place — was\n therefore refused the window nobody asked for:\n\n $ edf2csv rec.edf --info\n error: --start 0s is at or past the end of this unknown recording.\n\n Exit 2 for a file, naming a flag the command did not carry, quoting a length that is\n `formatDuration`'s word for a number it will not print. `--info` is the one command that\n would have explained the file and it is the one that could not run.\n\n Finite or equal, which is the only reading that means anything: two infinities are the\n same non-instant, and a real offset is not one of them.\n */\n if (!Number.isFinite(a) || !Number.isFinite(b)) return a === b;\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/**\n * Turn a requested window into both an exact time span and the record range that\n * contains it. Records are the unit the file can be read in; the exact span is what\n * decides which samples inside those records are actually written.\n */\nexport function resolveRange(options: {\n start?: number | undefined;\n /** The `--start` value exactly as typed, quoted back in the past-the-end error. */\n startText?: string | undefined;\n duration?: number | undefined;\n end?: number | undefined;\n /** The `--end` value exactly as typed, quoted back in the window error. */\n endText?: string | undefined;\n recordDuration: number;\n recordCount: number;\n /**\n * True start time of each data record, for discontinuous files. When absent,\n * records are assumed to sit end to end.\n */\n recordStarts?: Float64Array | null | undefined;\n}): ResolvedRange {\n /*\n The same check `buildPlan` makes, made here too, because this is exported on its own.\n\n `assertOptions` sits at the top of `buildPlan`, so `convert` was covered and the function\n underneath it was not — and this one has its own signature block on the API page.\n `resolveRange({ start: NaN, ... })` resolved: no error, no warning, and a range read back\n as `startSeconds: null, startRecord: null`, which is the \"takes the whole recording\n without saying so\" that comment names. `{ end: '2' }` was coerced by the arithmetic and\n accepted; `{ start: '30' }` reached the past-the-end error and printed it with the value\n missing — `--start s is at or past the end of this 3s recording`.\n */\n assertOptions({ start: options.start, duration: options.duration, end: options.end });\n\n // For a continuous file the recording spans recordCount * recordDuration. A\n // discontinuous one does not: a 10-second recording with a 95-second gap in the\n // middle still ends at 105 seconds. Deriving the span from the records' real\n // positions is what stops a requested window from being clipped back to the\n // amount of *data* in the file and silently discarding everything past it.\n const { earliest, latest } = span(options.recordStarts, options.recordCount, options.recordDuration);\n\n if (options.duration !== undefined && options.end !== undefined) {\n throw new TimeRangeError('Use either --duration or --end, not both.');\n }\n\n const startSeconds = options.start ?? earliest;\n\n let endSeconds: number;\n if (options.duration !== undefined) endSeconds = startSeconds + options.duration;\n else if (options.end !== undefined) endSeconds = options.end;\n else endSeconds = latest;\n\n /*\n At the end, allowing for the arithmetic that produced the end.\n\n `latest` is `recordCount * recordDuration`, and with a fractional duration that is not the\n number it prints as: 6003 records of 0.1s is 600.3000000000001, not 600.3. So `--start\n 600.3` on a recording `--info` calls \"10m 0.3s\" was accepted by a hair, converted nothing,\n and exited 0 with a signals.csv holding its header — which is the empty conversion this\n error exists to prevent, and which the same command on a whole-second recording is refused\n for.\n\n A relative epsilon, the same shape the long layout uses to decide two sample times are one\n instant: well below any real interval, and well above the rounding that two different\n routes to the same quantity produce.\n */\n if (startSeconds >= latest || sameInstant(startSeconds, latest)) {\n /*\n Quote what was typed. Reporting the parsed seconds meant `--start 4h` came back as\n \"--start 14400s is at or past the end\", which reads as a value the user never gave.\n\n In quotation marks, which the parse errors above have always used and this did not.\n Without them the value ran into the sentence: `--start \" 5s \"` printed as\n `--start 5s is at or past the end`, where the value appears to be `5s is` and\n the surrounding spaces — the actual reason a shell-built argument went wrong — are\n invisible.\n */\n throw new TimeRangeError(\n /*\n The recording's length in the same words --info uses for it.\n\n `formatSeconds` renders a bare number of seconds, so this message and the Duration\n line disagreed about one file in one session: --info said \"6m 40s\" and the error said\n \"400s\". On an overnight recording it read \"7950s recording\", leaving the reader to\n divide by 3600 to find out whether their --start was reasonable — which is the one\n question this message exists to answer. cli-reference.md has always documented it\n humanised (\"2h 12m 30s\"), a form no input could produce.\n\n The typed value keeps `quoted`, since that is the user's own text and should come\n back exactly as they wrote it.\n */\n /*\n `latest` is where the recording ends on its own clock, which is its length only when\n it starts at zero. A file timed from its first record's timekeeping TAL need not: one\n whose records run 1000s to 1003s is three seconds long, and this called it \"this\n 16m 43s recording\" — while --info two lines away said \"Duration 3s\". Where the\n recording sits is the useful thing to say in that case, and it is the number --start\n has to be given.\n */\n earliest === 0\n ? `--start ${quoted(options.startText, startSeconds)} is at or past the end of this ` +\n `${formatDuration(latest)} recording.`\n : `--start ${quoted(options.startText, startSeconds)} is at or past the end of this ` +\n `${formatDuration(latest - earliest)} recording, which runs from ` +\n `${formatSeconds(earliest)} to ${formatSeconds(latest)}.`,\n );\n }\n if (endSeconds <= startSeconds) {\n // Quote whatever the caller actually gave, for the same reason as the error above. The\n // end is only echoed when --end was passed: with --duration the end is computed here,\n // so there is no typed value to quote and the arithmetic result is the honest thing.\n const endShown =\n options.end !== undefined && options.endText !== undefined\n ? quoted(options.endText, endSeconds)\n : formatSeconds(endSeconds);\n const startShown = quoted(options.startText, startSeconds);\n throw new TimeRangeError(\n `The requested window ends at ${endShown}, which is not after its start at ${startShown}.`,\n );\n }\n\n const clampedEnd = Math.min(endSeconds, latest);\n /*\n Both ends of the window, clamped the same way.\n\n The end has been clamped to the recording since these fields existed — `--end 999h` on a\n two-hour file is documented as converting to the end, silently — and the start was left as\n typed. So a window asked for before the recording begins was recorded as one:\n\n edf2csv two-second.edf --out csv --start=-500\n metadata.json \"start_seconds\": -500, \"end_seconds\": 2\n\n over a signals.csv whose first row is 0.000, with `records_converted` correctly `[0, 2]`.\n output-files calls the pair \"the resolved time window\", which the end is and the start was\n not, and a script taking `end_seconds - start_seconds` for the span converted got 502 for\n two seconds of data.\n\n `earliest` and not zero, because a recording is timed from its first record and need not\n begin there: negative-origin.edf runs from -100s, and -100 is where a start before the\n recording belongs. Nothing downstream moves — every sample sits at or after `earliest`, so\n the row filter and the record selection answer the same for either value, and the byte\n estimate takes a width from the smaller magnitude, which is the one the column actually\n holds.\n\n Not when the whole window sits before the recording, where there is no overlap to clamp\n to and raising the start past the end would describe the request backwards: `--start 0\n --duration 0.001` on a file beginning at 30s is an empty window, and EMPTY_WINDOW quotes\n the bounds — \"(30.000s to 0.001s)\" says nothing a reader can act on, where the bounds as\n asked for are what its hint is already explaining.\n */\n const clampedStart = clampedEnd <= earliest ? startSeconds : Math.max(startSeconds, earliest);\n const { startRecord, endRecord } = selectRecords(options, clampedStart, clampedEnd);\n\n return {\n startSeconds: clampedStart,\n endSeconds: clampedEnd,\n startRecord,\n endRecord,\n /*\n The same rounding, one field over.\n\n `latest` is `recordCount * recordDuration`, and 6003 records of 0.1s is\n 600.3000000000001. So `--end 600.3` on a recording of exactly that length wrote every\n sample it has — byte-identical to a bare conversion — and metadata.json recorded\n `whole_recording: false` for it, while the bare run recorded true. One conversion, two\n answers, on the field a pipeline reads to decide whether it has the lot.\n */\n isWholeRecording:\n (startSeconds <= earliest || sameInstant(startSeconds, earliest)) &&\n (clampedEnd >= latest || sameInstant(clampedEnd, latest)),\n recordingStartSeconds: earliest,\n recordingEndSeconds: latest,\n };\n}\n\nfunction span(\n recordStarts: Float64Array | null | undefined,\n recordCount: number,\n recordDuration: number,\n): { earliest: number; latest: number } {\n if (!recordStarts || recordStarts.length === 0) {\n return { earliest: 0, latest: recordCount * recordDuration };\n }\n let earliest = Infinity;\n let latest = -Infinity;\n for (const start of recordStarts) {\n if (start < earliest) earliest = start;\n if (start + recordDuration > latest) latest = start + recordDuration;\n }\n if (!Number.isFinite(earliest) || !Number.isFinite(latest)) {\n return { earliest: 0, latest: recordCount * recordDuration };\n }\n return { earliest, latest };\n}\n\n/** Every record whose own time span overlaps the requested window. */\nfunction selectRecords(\n options: { recordDuration: number; recordCount: number; recordStarts?: Float64Array | null | undefined },\n startSeconds: number,\n endSeconds: number,\n): { startRecord: number; endRecord: number } {\n const starts = options.recordStarts;\n if (!starts || starts.length === 0) {\n return {\n startRecord: Math.max(0, Math.floor(startSeconds / options.recordDuration)),\n endRecord: Math.min(options.recordCount, Math.ceil(endSeconds / options.recordDuration)),\n };\n }\n\n let first = options.recordCount;\n let last = 0;\n for (let i = 0; i < starts.length; i++) {\n const begin = starts[i] ?? 0;\n if (begin + options.recordDuration > startSeconds && begin < endSeconds) {\n if (i < first) first = i;\n if (i + 1 > last) last = i + 1;\n }\n }\n return first < last ? { startRecord: first, endRecord: last } : { startRecord: 0, endRecord: 0 };\n}\n\n/**\n * The value as the caller typed it, in quotation marks, or the parsed seconds if they gave\n * none. The marks show where the value begins and ends, which matters most for the values\n * that went wrong because of what surrounds them.\n */\nfunction quoted(text: string | undefined, seconds: number): string {\n return text === undefined ? formatSeconds(seconds) : `\"${text}\"`;\n}\n\nfunction formatSeconds(seconds: number): string {\n /*\n The same 1e21 cliff, in the bounds this message hands back.\n\n `--start \"9e21\"` was answered with \"is at or past the end of this 3e+21s recording, which\n runs from 1e+21s to 4e+21s\" — a sentence whose whole purpose is to say what window there\n is to ask for, ending in two tokens the parser refuses: `--start 1e+21s` is \"uses an\n unknown unit \\\"e\\\"\". `Number(...)` was here to drop the trailing zeros, and it also\n re-introduced the exponent form that `toFixed` had produced.\n */\n const text = fixed(seconds, 3);\n return `${text.includes('.') ? text.replace(/\\.?0+$/u, '') : text}s`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"time-range.js","sourceRoot":"","sources":["../../src/convert/time-range.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Parsing for the time-range options.\n *\n * Researchers write offsets in whatever form is natural for the recording in front\n * of them: seconds for a short ECG strip, `30m` into a sleep study, `01:23:45` when\n * reading off a clock. All three are accepted; anything ambiguous is rejected with\n * a message that shows the forms that work.\n */\n\nimport { fixed, formatDuration } from '../format/number.js';\nimport { assertOptions } from './options.js';\n\nexport class TimeRangeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TimeRangeError';\n }\n}\n\n/**\n * Every spelling of a unit this accepts, and what one of it is worth in seconds.\n *\n * Exported so the spellings can be enumerated by a test rather than listed a second time:\n * twelve of these sixteen are named nowhere in the tool or its documentation, and none of\n * their values was checked by anything. `hrs: 360` would have converted `--start 2hrs` from\n * twelve minutes in and said nothing, which is the one kind of mistake a window may not make.\n */\nexport const UNIT_SECONDS: Record<string, number> = {\n h: 3600,\n hr: 3600,\n hrs: 3600,\n hour: 3600,\n hours: 3600,\n m: 60,\n min: 60,\n mins: 60,\n minute: 60,\n minutes: 60,\n s: 1,\n sec: 1,\n secs: 1,\n second: 1,\n seconds: 1,\n ms: 0.001,\n};\n\nconst CLOCK = /^(?:(\\d+):)?(\\d{1,2}):(\\d{1,2}(?:\\.\\d+)?)$/u;\nconst UNIT_TOKEN = /(\\d+(?:\\.\\d+)?)\\s*([a-z]+)/giu;\n\n/** How the value reads in a refusal: numbers bare, everything else quoted so its type shows. */\nfunction describeValue(value: unknown): string {\n return typeof value === 'number' ? String(value) : JSON.stringify(value) ?? String(value);\n}\n\n/**\n * Parse a duration or offset into seconds.\n *\n * Accepted: `90`, `90s`, `5m`, `1h30m`, `1h 30m 15s`, `00:30:00`, `30:00`, `250ms`.\n *\n * `allowNegative` is for the two options that name a position rather than a length.\n *\n * A recording is timed from its first record's timekeeping annotation, and nothing obliges that\n * to sit at or after zero: a file whose records run from -100 s to -97 s is one this tool reads,\n * times from -100, and describes with\n *\n * Timed from -100.000s (first sample; --start and --end use this clock)\n *\n * That line says the number can be typed straight back in, and it could not be. Every offset\n * such a recording has came back as \"not a time I understand\", so its whole clock was\n * unreachable and no window of it could be converted at all — the one file shape where a\n * window is refused for naming a moment the recording actually contains.\n *\n * A length below zero is still a different thing, and `--duration` still refuses one.\n */\nexport function parseTimeSpec(input: string, optionName: string, allowNegative = false): number {\n /*\n Text, because this is the function the API page points other people's users at: \"Use\n `parseTimeSpec` if you want to accept those forms from your own users.\"\n\n A value arriving from a JSON config or a form field is a number as often as a string, and\n `{ \"start\": 30 }` came back as `TypeError: input.trim is not a function` — a variable name\n from inside this file, thrown past a caller who had done exactly what the page told them\n to. Every other refusal here is a TimeRangeError quoting the value and the option. The\n number is not coerced, for the reason `channels: 'ECG'` is not iterated: a shape that\n happens to work for 30 also swallows NaN, and the CLI already writes `String(raw)`.\n */\n if (typeof input !== 'string') {\n throw new TimeRangeError(\n `${optionName} must be given as text, not ${describeValue(input)}. ` +\n `Seconds are written \"30\", and the other forms are \"30s\", \"5m\", \"1h30m\", \"00:30:00\".`,\n );\n }\n const trimmed = input.trim().toLowerCase();\n if (trimmed === '') {\n throw new TimeRangeError(`${optionName} is empty. Try a value like 30s, 5m, or 00:30:00.`);\n }\n\n /*\n The sign is read off the front and applied to the whole value, so `-1h30m` is an hour and a\n half before the origin rather than an hour before it and half an hour after.\n\n Nothing may sit between the sign and the number, and a leading `+` is still refused: every\n other number this tool takes is refused when written in a form nobody types, and `+5` is\n exactly what `--decimals` and `--jobs` reject one flag over.\n */\n const negative = trimmed.startsWith('-');\n const text = negative ? trimmed.slice(1) : trimmed;\n\n /*\n A leading plus, named as such.\n\n Refusing it is deliberate — see the paragraph above — but it came back as \"is not a time I\n understand. Try 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds\", which is the\n message for input that could not be read at all. Every part of `+5s` was read: the number\n parsed, the unit was looked up and found. The one thing wrong with it is a character the\n reader put there on purpose, and they were sent to re-check their unit spellings with `5m`\n sitting in the list of suggestions, differing from what they typed by the sign they cannot\n see is the problem. 0.7.102 made this argument about a space; this is the same sentence\n about the other character that reaches it. `--decimals` and `--jobs` both quote the value\n back and say what is wrong with it.\n\n Every plus, not the first one, and the leftover space with it — the same rule the spaced\n refusal below applies to spaces, for the same reason: a hint whose command this function\n refuses is worse than no hint. `+1h+30m` was answered with `1h+30m`, which comes straight\n back as \"is not a time I understand\", and `+ 5s` with a value that opened on a space.\n */\n if (text.startsWith('+')) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" begins with a plus. Write the number on its own: ` +\n `${input.trim().replaceAll('+', '').trim()}`,\n );\n }\n const signed = (seconds: number): number =>\n assertFinite(negative ? -seconds : seconds, optionName, input, allowNegative);\n\n // Bare number means seconds.\n if (/^\\d+(?:\\.\\d+)?$/u.test(text)) return signed(Number(text));\n\n // Clock form: hh:mm:ss or mm:ss.\n const clock = CLOCK.exec(text);\n if (clock) {\n const hours = clock[1] === undefined ? 0 : Number(clock[1]);\n const minutes = Number(clock[2]);\n const seconds = Number(clock[3]);\n if (minutes >= 60 || seconds >= 60) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" has a minutes or seconds field of 60 or more.`,\n );\n }\n return signed(hours * 3600 + minutes * 60 + seconds);\n }\n\n // Unit form: 1h30m, 1h30m 15s, 250ms. A number sits directly against its unit; see below.\n UNIT_TOKEN.lastIndex = 0;\n let total = 0;\n let matched = 0;\n let consumed = 0;\n /** Whether any token had a space between its number and its unit. See the refusal below. */\n let spaced = false;\n let match: RegExpExecArray | null;\n // Which units have already been seen, so a repeat can be rejected rather than added.\n const seen = new Set<number>();\n while ((match = UNIT_TOKEN.exec(text)) !== null) {\n const amount = Number(match[1]);\n const unit = match[2] ?? '';\n const scale = UNIT_SECONDS[unit];\n if (scale === undefined) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" uses an unknown unit \"${unit}\". Use h, m, s or ms, ` +\n `or their long forms: hours, minutes, seconds.`,\n );\n }\n\n // \"1h1h\" is a typo, not a request for two hours. Summing repeated units silently\n // turned a slip into a plausible window that was quietly the wrong length. Units are\n // keyed by their scale so the aliases collapse together: \"1h30min20m\" is caught too.\n if (seen.has(scale)) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" gives the same unit twice. ` +\n `Combine each unit once, as in 1h30m.`,\n );\n }\n seen.add(scale);\n\n total += amount * scale;\n matched++;\n // Counted with the whitespace taken out, so the comparison below is against the same\n // string on both sides and a space inside a token reaches the refusal written for it\n // rather than the one about input that could not be read at all.\n consumed += match[0].replace(/\\s+/gu, '').length;\n if (/\\s/u.test(match[0])) spaced = true;\n }\n\n // Reject partially-understood input like \"5x\" or \"1h banana\".\n if (matched === 0 || consumed !== text.replace(/\\s+/gu, '').length) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" is not a time I understand. ` +\n `Try 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds.`,\n );\n }\n\n /*\n A space between a number and its unit, named as such.\n\n `5 min` is refused on purpose — cli-reference sets the rule out, and it is what keeps\n `1 2h` from being read as anything — but it came back as \"is not a time I understand. Try\n 30s, 5m, 1h30m, 00:30:00, or a plain number of seconds\", which is the message for input\n that could not be read at all. Every part of `5 min` was read: the number parsed, the unit\n was looked up in the table and found. The one thing wrong with it is a space, and the\n reader was sent to re-check their unit spellings instead — with `5m` sitting in the list of\n suggestions, differing from what they typed by a character they cannot see is the problem.\n\n Only reachable now that the count above ignores whitespace; before, the length check caught\n these first and there was nothing left to tell them apart by.\n */\n if (spaced) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" puts a space between a number and its unit. ` +\n `Write them together: ${input.trim().replace(/(\\d)\\s+([a-z])/giu, '$1$2')}`,\n );\n }\n\n return signed(total);\n}\n\nfunction assertFinite(\n value: number,\n optionName: string,\n input: string,\n allowNegative: boolean,\n): number {\n /*\n Overflow answers for itself, rather than borrowing the sentence about signs.\n\n One branch covered two rejections. `--start` with four hundred nines is a positive value\n that `Number` returns as `Infinity`, and the answer was `is not a valid non-negative\n time` — which names the one thing about it that is not the problem. Worse on the two\n options this is called with `allowNegative` for: `--start` and `--end` take a sign on\n purpose, since a recording timed from before zero has no other way to be addressed, so\n \"non-negative\" is not their rule at all and overflow is the only way they reach here.\n */\n if (!Number.isFinite(value)) {\n throw new TimeRangeError(\n `${optionName} \"${input}\" is further from zero than a number of seconds can hold.`,\n );\n }\n if (!allowNegative && value < 0) {\n throw new TimeRangeError(`${optionName} \"${input}\" is not a valid non-negative time.`);\n }\n return value;\n}\n\nexport interface ResolvedRange {\n /** Inclusive start, in seconds from the beginning of the recording. */\n startSeconds: number;\n /** Exclusive end, in seconds. */\n endSeconds: number;\n /** First data record touching the window. */\n startRecord: number;\n /** One past the last data record touching the window. */\n endRecord: number;\n /** True when the window covers the whole recording. */\n isWholeRecording: boolean;\n /** Earliest record start, including EDF+D timing gaps. */\n recordingStartSeconds: number;\n /** End of the latest record, including EDF+D timing gaps. */\n recordingEndSeconds: number;\n}\n\n/**\n * Slack for comparisons at sample/window boundaries.\n *\n * A nanosecond is far below any real sampling interval — 20 kHz is 50 microseconds — so it\n * absorbs the arithmetic error in `recordStart + sample / rate` without reaching a\n * neighbouring sample.\n */\nexport const BOUNDARY_TOLERANCE = 1e-9;\n\n/**\n * The slack to use for a channel sampled this often.\n *\n * Never as much as half a sample interval, because slack that reaches the next sample stops\n * being slack. A fixed nanosecond was applied whatever the rate, and the format does not\n * oblige the interval to be larger than it: EDF's record duration is an 8-character field\n * that accepts `1e-9`. A recording of two 1 ns records holding ten samples each wrote ten of\n * its twenty rows — the window ends at 2e-9, the comparison asked for `time < 2e-9 - 1e-9`,\n * and the entire second record failed it. Exit 0, no warning, half the samples gone.\n */\nexport function toleranceFor(rate: number): number {\n if (!(rate > 0) || !Number.isFinite(rate)) return BOUNDARY_TOLERANCE;\n return Math.min(BOUNDARY_TOLERANCE, 1 / rate / 2);\n}\n\n/** Match the exact half-open boundary rules used while writing signal rows. */\nexport function sampleTimeIsInRange(\n time: number,\n startSeconds: number,\n endSeconds: number,\n tolerance: number = BOUNDARY_TOLERANCE,\n): boolean {\n return time >= startSeconds - tolerance && time < endSeconds - tolerance;\n}\n\n/** Count samples from one record that fall inside a half-open requested window. */\nexport function countSamplesInRange(options: {\n recordStart: number;\n rate: number;\n samplesPerRecord: number;\n startSeconds: number;\n endSeconds: number;\n}): number {\n /*\n A record with no place on the clock contributes no rows, which is what the conversion\n already does with one: its sample times are non-finite, so every one of them fails the\n range test and none is written. The arithmetic below reached `Infinity - Infinity` and\n answered `NaN`, which `--info` then printed — \"Would write NaN rows, roughly NaN B.\" on a\n recording that goes on to write eight. Reachable from a header: a record duration of 1e308\n is five characters in an eight-character field, and the third record of one is past what a\n double can hold.\n */\n if (!Number.isFinite(options.recordStart)) return 0;\n const slack = toleranceFor(options.rate);\n const lower = Math.ceil(\n (options.startSeconds - slack - options.recordStart) * options.rate,\n );\n const upper = Math.ceil(\n (options.endSeconds - slack - options.recordStart) * options.rate,\n );\n const first = Math.max(0, Math.min(options.samplesPerRecord, lower));\n const last = Math.max(0, Math.min(options.samplesPerRecord, upper));\n return Math.max(0, last - first);\n}\n\n/**\n * Whether two instants differ only by the arithmetic that produced them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude — the same shape\n * the long layout uses to decide two sample times are one instant. Well below any real sample\n * interval, and well above the rounding that two routes to one quantity produce: a recording's\n * length is `recordCount * recordDuration`, which for 6003 records of 0.1s is not the 600.3 it\n * prints as.\n */\nfunction sameInstant(a: number, b: number): boolean {\n /*\n A relative tolerance has nothing to be relative to at infinity.\n\n `Math.abs(a - Infinity)` is Infinity, and so is the allowance beside it, so\n `Infinity <= Infinity` made every instant the same instant as an infinite one. A\n recording whose end overflows a double — three records of 1e308 seconds, which an\n 8-character record-duration field can state and a timekeeping TAL can place — was\n therefore refused the window nobody asked for:\n\n $ edf2csv rec.edf --info\n error: --start 0s is at or past the end of this unknown recording.\n\n Exit 2 for a file, naming a flag the command did not carry, quoting a length that is\n `formatDuration`'s word for a number it will not print. `--info` is the one command that\n would have explained the file and it is the one that could not run.\n\n Finite or equal, which is the only reading that means anything: two infinities are the\n same non-instant, and a real offset is not one of them.\n */\n if (!Number.isFinite(a) || !Number.isFinite(b)) return a === b;\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/**\n * Turn a requested window into both an exact time span and the record range that\n * contains it. Records are the unit the file can be read in; the exact span is what\n * decides which samples inside those records are actually written.\n */\nexport function resolveRange(options: {\n start?: number | undefined;\n /** The `--start` value exactly as typed, quoted back in the past-the-end error. */\n startText?: string | undefined;\n duration?: number | undefined;\n end?: number | undefined;\n /** The `--end` value exactly as typed, quoted back in the window error. */\n endText?: string | undefined;\n recordDuration: number;\n recordCount: number;\n /**\n * True start time of each data record, for discontinuous files. When absent,\n * records are assumed to sit end to end.\n */\n recordStarts?: Float64Array | null | undefined;\n}): ResolvedRange {\n /*\n The same check `buildPlan` makes, made here too, because this is exported on its own.\n\n `assertOptions` sits at the top of `buildPlan`, so `convert` was covered and the function\n underneath it was not — and this one has its own signature block on the API page.\n `resolveRange({ start: NaN, ... })` resolved: no error, no warning, and a range read back\n as `startSeconds: null, startRecord: null`, which is the \"takes the whole recording\n without saying so\" that comment names. `{ end: '2' }` was coerced by the arithmetic and\n accepted; `{ start: '30' }` reached the past-the-end error and printed it with the value\n missing — `--start s is at or past the end of this 3s recording`.\n */\n assertOptions({ start: options.start, duration: options.duration, end: options.end });\n\n // For a continuous file the recording spans recordCount * recordDuration. A\n // discontinuous one does not: a 10-second recording with a 95-second gap in the\n // middle still ends at 105 seconds. Deriving the span from the records' real\n // positions is what stops a requested window from being clipped back to the\n // amount of *data* in the file and silently discarding everything past it.\n const { earliest, latest } = span(options.recordStarts, options.recordCount, options.recordDuration);\n\n if (options.duration !== undefined && options.end !== undefined) {\n throw new TimeRangeError('Use either --duration or --end, not both.');\n }\n\n const startSeconds = options.start ?? earliest;\n\n let endSeconds: number;\n if (options.duration !== undefined) endSeconds = startSeconds + options.duration;\n else if (options.end !== undefined) endSeconds = options.end;\n else endSeconds = latest;\n\n /*\n At the end, allowing for the arithmetic that produced the end.\n\n `latest` is `recordCount * recordDuration`, and with a fractional duration that is not the\n number it prints as: 6003 records of 0.1s is 600.3000000000001, not 600.3. So `--start\n 600.3` on a recording `--info` calls \"10m 0.3s\" was accepted by a hair, converted nothing,\n and exited 0 with a signals.csv holding its header — which is the empty conversion this\n error exists to prevent, and which the same command on a whole-second recording is refused\n for.\n\n A relative epsilon, the same shape the long layout uses to decide two sample times are one\n instant: well below any real interval, and well above the rounding that two different\n routes to the same quantity produce.\n */\n if (startSeconds >= latest || sameInstant(startSeconds, latest)) {\n /*\n Quote what was typed. Reporting the parsed seconds meant `--start 4h` came back as\n \"--start 14400s is at or past the end\", which reads as a value the user never gave.\n\n In quotation marks, which the parse errors above have always used and this did not.\n Without them the value ran into the sentence: `--start \" 5s \"` printed as\n `--start 5s is at or past the end`, where the value appears to be `5s is` and\n the surrounding spaces — the actual reason a shell-built argument went wrong — are\n invisible.\n */\n throw new TimeRangeError(\n /*\n The recording's length in the same words --info uses for it.\n\n `formatSeconds` renders a bare number of seconds, so this message and the Duration\n line disagreed about one file in one session: --info said \"6m 40s\" and the error said\n \"400s\". On an overnight recording it read \"7950s recording\", leaving the reader to\n divide by 3600 to find out whether their --start was reasonable — which is the one\n question this message exists to answer. cli-reference.md has always documented it\n humanised (\"2h 12m 30s\"), a form no input could produce.\n\n The typed value keeps `quoted`, since that is the user's own text and should come\n back exactly as they wrote it.\n */\n /*\n `latest` is where the recording ends on its own clock, which is its length only when\n it starts at zero. A file timed from its first record's timekeeping TAL need not: one\n whose records run 1000s to 1003s is three seconds long, and this called it \"this\n 16m 43s recording\" — while --info two lines away said \"Duration 3s\". Where the\n recording sits is the useful thing to say in that case, and it is the number --start\n has to be given.\n */\n earliest === 0\n ? `--start ${quoted(options.startText, startSeconds)} is at or past the end of this ` +\n `${formatDuration(latest)} recording.`\n : `--start ${quoted(options.startText, startSeconds)} is at or past the end of this ` +\n `${formatDuration(latest - earliest)} recording, which runs from ` +\n `${formatSeconds(earliest)} to ${formatSeconds(latest)}.`,\n );\n }\n if (endSeconds <= startSeconds) {\n // Quote whatever the caller actually gave, for the same reason as the error above. The\n // end is only echoed when --end was passed: with --duration the end is computed here,\n // so there is no typed value to quote and the arithmetic result is the honest thing.\n const endShown =\n options.end !== undefined && options.endText !== undefined\n ? quoted(options.endText, endSeconds)\n : formatSeconds(endSeconds);\n const startShown = quoted(options.startText, startSeconds);\n throw new TimeRangeError(\n `The requested window ends at ${endShown}, which is not after its start at ${startShown}.`,\n );\n }\n\n const clampedEnd = Math.min(endSeconds, latest);\n /*\n Both ends of the window, clamped the same way.\n\n The end has been clamped to the recording since these fields existed — `--end 999h` on a\n two-hour file is documented as converting to the end, silently — and the start was left as\n typed. So a window asked for before the recording begins was recorded as one:\n\n edf2csv two-second.edf --out csv --start=-500\n metadata.json \"start_seconds\": -500, \"end_seconds\": 2\n\n over a signals.csv whose first row is 0.000, with `records_converted` correctly `[0, 2]`.\n output-files calls the pair \"the resolved time window\", which the end is and the start was\n not, and a script taking `end_seconds - start_seconds` for the span converted got 502 for\n two seconds of data.\n\n `earliest` and not zero, because a recording is timed from its first record and need not\n begin there: negative-origin.edf runs from -100s, and -100 is where a start before the\n recording belongs. Nothing downstream moves — every sample sits at or after `earliest`, so\n the row filter and the record selection answer the same for either value, and the byte\n estimate takes a width from the smaller magnitude, which is the one the column actually\n holds.\n\n Not when the whole window sits before the recording, where there is no overlap to clamp\n to and raising the start past the end would describe the request backwards: `--start 0\n --duration 0.001` on a file beginning at 30s is an empty window, and EMPTY_WINDOW quotes\n the bounds — \"(30.000s to 0.001s)\" says nothing a reader can act on, where the bounds as\n asked for are what its hint is already explaining.\n */\n const clampedStart = clampedEnd <= earliest ? startSeconds : Math.max(startSeconds, earliest);\n const { startRecord, endRecord } = selectRecords(options, clampedStart, clampedEnd);\n\n return {\n startSeconds: clampedStart,\n endSeconds: clampedEnd,\n startRecord,\n endRecord,\n /*\n The same rounding, one field over.\n\n `latest` is `recordCount * recordDuration`, and 6003 records of 0.1s is\n 600.3000000000001. So `--end 600.3` on a recording of exactly that length wrote every\n sample it has — byte-identical to a bare conversion — and metadata.json recorded\n `whole_recording: false` for it, while the bare run recorded true. One conversion, two\n answers, on the field a pipeline reads to decide whether it has the lot.\n */\n isWholeRecording:\n (startSeconds <= earliest || sameInstant(startSeconds, earliest)) &&\n (clampedEnd >= latest || sameInstant(clampedEnd, latest)),\n recordingStartSeconds: earliest,\n recordingEndSeconds: latest,\n };\n}\n\nfunction span(\n recordStarts: Float64Array | null | undefined,\n recordCount: number,\n recordDuration: number,\n): { earliest: number; latest: number } {\n if (!recordStarts || recordStarts.length === 0) {\n return { earliest: 0, latest: recordCount * recordDuration };\n }\n let earliest = Infinity;\n let latest = -Infinity;\n for (const start of recordStarts) {\n if (start < earliest) earliest = start;\n if (start + recordDuration > latest) latest = start + recordDuration;\n }\n if (!Number.isFinite(earliest) || !Number.isFinite(latest)) {\n return { earliest: 0, latest: recordCount * recordDuration };\n }\n return { earliest, latest };\n}\n\n/** Every record whose own time span overlaps the requested window. */\nfunction selectRecords(\n options: { recordDuration: number; recordCount: number; recordStarts?: Float64Array | null | undefined },\n startSeconds: number,\n endSeconds: number,\n): { startRecord: number; endRecord: number } {\n const starts = options.recordStarts;\n if (!starts || starts.length === 0) {\n return {\n startRecord: Math.max(0, Math.floor(startSeconds / options.recordDuration)),\n endRecord: Math.min(options.recordCount, Math.ceil(endSeconds / options.recordDuration)),\n };\n }\n\n let first = options.recordCount;\n let last = 0;\n for (let i = 0; i < starts.length; i++) {\n const begin = starts[i] ?? 0;\n if (begin + options.recordDuration > startSeconds && begin < endSeconds) {\n if (i < first) first = i;\n if (i + 1 > last) last = i + 1;\n }\n }\n return first < last ? { startRecord: first, endRecord: last } : { startRecord: 0, endRecord: 0 };\n}\n\n/**\n * The value as the caller typed it, in quotation marks, or the parsed seconds if they gave\n * none. The marks show where the value begins and ends, which matters most for the values\n * that went wrong because of what surrounds them.\n */\nfunction quoted(text: string | undefined, seconds: number): string {\n return text === undefined ? formatSeconds(seconds) : `\"${text}\"`;\n}\n\nfunction formatSeconds(seconds: number): string {\n /*\n The same 1e21 cliff, in the bounds this message hands back.\n\n `--start \"9e21\"` was answered with \"is at or past the end of this 3e+21s recording, which\n runs from 1e+21s to 4e+21s\" — a sentence whose whole purpose is to say what window there\n is to ask for, ending in two tokens the parser refuses: `--start 1e+21s` is \"uses an\n unknown unit \\\"e\\\"\". `Number(...)` was here to drop the trailing zeros, and it also\n re-introduced the exponent form that `toFixed` had produced.\n */\n const text = fixed(seconds, 3);\n return `${text.includes('.') ? text.replace(/\\.?0+$/u, '') : text}s`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edf2csv",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.208",
|
|
4
4
|
"description": "Convert EDF, EDF+ and BDF biosignal recordings (European Data Format) to CSV from the command line. Local, streaming, and never resamples or alters units.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"edf",
|