edfcore 0.2.66 → 0.2.67
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/CHANGELOG.md +20 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/format-header.d.ts.map +1 -1
- package/dist/format-header.js +39 -10
- package/dist/format-header.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +1 -1
- package/src/format-header.ts +42 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@ alone does not tell you whether you were affected.
|
|
|
6
6
|
edfcore is pre-1.0. Patch releases have carried behaviour changes where the old behaviour was a
|
|
7
7
|
defect; those are called out below.
|
|
8
8
|
|
|
9
|
+
## 0.2.67
|
|
10
|
+
|
|
11
|
+
Three defects in `formatHeader`, found by an adversarial sweep of the modules no earlier pass had
|
|
12
|
+
covered.
|
|
13
|
+
|
|
14
|
+
- **Fixed** the duration line losing a whole second. It computed `recordCount *
|
|
15
|
+
recordDurationSeconds` in float64 and truncated, and a record duration with no exact binary form
|
|
16
|
+
makes that product land just under the true value: 100 records of 0.29 s is exactly 29 s,
|
|
17
|
+
computes as 28.999999999999996, and printed `00:00:28`. It is now computed from
|
|
18
|
+
`recordDurationTicks`, which is exact. A genuine fraction still truncates rather than rounding —
|
|
19
|
+
7 × 0.7 s is 4.9 and prints `00:00:04`, because rounding would name a time the file never reaches.
|
|
20
|
+
- **Fixed** control characters in a signal label being printed verbatim. EDF pads labels with
|
|
21
|
+
spaces and says nothing about what else may be in them, so a writer can put a newline there and
|
|
22
|
+
the label renders as TWO rows — forging a signal the file does not contain — while a tab shifts
|
|
23
|
+
every column after it. They are replaced with a dot in the rendering only; `signal.raw.label`
|
|
24
|
+
still holds the bytes.
|
|
25
|
+
- **Fixed** the diagnostic severity summary being ordered by arrival, so two files with the same
|
|
26
|
+
diagnostics could summarise them differently. Now error-warning-info, matching
|
|
27
|
+
`formatValidationReport` since 0.2.15 and sharing its counting.
|
|
28
|
+
|
|
9
29
|
## 0.2.66
|
|
10
30
|
|
|
11
31
|
- **Fixed** the changelog numbering, which had drifted a second time, and **fixed the cause** so it
|
package/dist/constants.d.ts
CHANGED
|
@@ -109,5 +109,5 @@ export declare const SIGNAL_FIELD_BLOCK_OFFSETS: {
|
|
|
109
109
|
readonly reserved: 224;
|
|
110
110
|
};
|
|
111
111
|
/** Published package version. Kept in sync with package.json by a test. */
|
|
112
|
-
export declare const VERSION = "0.2.
|
|
112
|
+
export declare const VERSION = "0.2.67";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-header.d.ts","sourceRoot":"","sources":["../src/format-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"format-header.d.ts","sourceRoot":"","sources":["../src/format-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAmB,SAAS,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA0ClF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAsErF"}
|
package/dist/format-header.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* identification unless asked — a header carries a name and a birth date, and a summary that
|
|
12
12
|
* lands in a chat log or an issue tracker should not carry them by default.
|
|
13
13
|
*/
|
|
14
|
+
import { TICKS_PER_SECOND } from './constants.js';
|
|
15
|
+
import { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
14
16
|
function formatDate(date) {
|
|
15
17
|
if (date === undefined)
|
|
16
18
|
return 'unknown';
|
|
@@ -18,16 +20,33 @@ function formatDate(date) {
|
|
|
18
20
|
const day = String(date.day).padStart(2, '0');
|
|
19
21
|
return `${date.year}-${month}-${day}`;
|
|
20
22
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* `hh:mm:ss` from an exact tick count.
|
|
25
|
+
*
|
|
26
|
+
* Ticks, not `recordCount * recordDurationSeconds`. That product is float64, and a record duration
|
|
27
|
+
* with no exact binary representation makes it land just under the true value: 100 records of
|
|
28
|
+
* 0.29 s is exactly 29 s and computes as 28.999999999999996, which floors to 28. The header line
|
|
29
|
+
* then reports a recording a whole second shorter than it is (fixed in 0.2.67).
|
|
30
|
+
*/
|
|
31
|
+
function formatDurationTicks(ticks) {
|
|
32
|
+
if (ticks < 0n)
|
|
23
33
|
return 'unknown';
|
|
24
|
-
const whole =
|
|
34
|
+
const whole = Number(ticks / TICKS_PER_SECOND);
|
|
25
35
|
const hours = Math.floor(whole / 3600);
|
|
26
36
|
const minutes = Math.floor((whole % 3600) / 60);
|
|
27
37
|
const rest = whole % 60;
|
|
28
38
|
const pad = (n) => String(n).padStart(2, '0');
|
|
29
39
|
return `${pad(hours)}:${pad(minutes)}:${pad(rest)}`;
|
|
30
40
|
}
|
|
41
|
+
/** Control characters become a dot, so one field can never become two rows or shift a column. */
|
|
42
|
+
function printable(text) {
|
|
43
|
+
let out = '';
|
|
44
|
+
for (const character of text) {
|
|
45
|
+
const code = character.codePointAt(0) ?? 0;
|
|
46
|
+
out += code < 0x20 || code === 0x7f ? '.' : character;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
31
50
|
function formatRate(signal) {
|
|
32
51
|
// undefined is the honest answer for a zero record duration, which is legal EDF.
|
|
33
52
|
return signal.sampleRateHz === undefined ? '—' : `${signal.sampleRateHz} Hz`;
|
|
@@ -48,7 +67,7 @@ export function formatHeader(header, options) {
|
|
|
48
67
|
` (local, no timezone)`);
|
|
49
68
|
lines.push(`record ${header.recordDurationSeconds} s · ${header.recordByteLength} bytes · ` +
|
|
50
69
|
`${header.bytesPerSample} bytes/sample`);
|
|
51
|
-
lines.push(`duration ${
|
|
70
|
+
lines.push(`duration ${formatDurationTicks(header.recordDurationTicks * BigInt(header.recordCount))} ` +
|
|
52
71
|
`(${header.recordCount} × ${header.recordDurationSeconds} s)`);
|
|
53
72
|
if (header.recordCountSource === 'sourceByteLength') {
|
|
54
73
|
// Worth saying out loud: the count came from the file size, not from the header field.
|
|
@@ -62,7 +81,11 @@ export function formatHeader(header, options) {
|
|
|
62
81
|
lines.push(' # label kind rate range');
|
|
63
82
|
for (const signal of header.signals) {
|
|
64
83
|
const index = String(signal.index).padStart(3);
|
|
65
|
-
|
|
84
|
+
// Control characters are replaced, not printed. A label holding a newline would otherwise
|
|
85
|
+
// render as two rows and forge a signal the file does not contain; a tab would shift every
|
|
86
|
+
// column after it. EDF pads labels with spaces and says nothing about what else may be in
|
|
87
|
+
// them, so a writer can put anything there and a reader must not be steered by it.
|
|
88
|
+
const label = printable(signal.label).slice(0, 20).padEnd(21);
|
|
66
89
|
const kind = signal.kind.padEnd(12);
|
|
67
90
|
const rate = formatRate(signal).padEnd(9);
|
|
68
91
|
const range = signal.kind === 'annotations'
|
|
@@ -74,11 +97,17 @@ export function formatHeader(header, options) {
|
|
|
74
97
|
}
|
|
75
98
|
if (header.diagnostics.length > 0) {
|
|
76
99
|
lines.push('');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
// Fixed error-warning-info order, matching `formatValidationReport` since 0.2.15. Ordering by
|
|
101
|
+
// arrival meant two files with the same diagnostics could summarise them differently.
|
|
102
|
+
const counted = summarizeDiagnostics(header.diagnostics);
|
|
103
|
+
const summary = [
|
|
104
|
+
[counted.errors, 'error'],
|
|
105
|
+
[counted.warnings, 'warning'],
|
|
106
|
+
[counted.infos, 'info'],
|
|
107
|
+
]
|
|
108
|
+
.filter(([count]) => count > 0)
|
|
109
|
+
.map(([count, severity]) => `${count} ${severity}`)
|
|
110
|
+
.join(', ');
|
|
82
111
|
lines.push(`${header.diagnostics.length} diagnostic(s): ${summary}`);
|
|
83
112
|
lines.push('Call formatDiagnostics(header.diagnostics) for the detail.');
|
|
84
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-header.js","sourceRoot":"","sources":["../src/format-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"format-header.js","sourceRoot":"","sources":["../src/format-header.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
package/src/format-header.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* lands in a chat log or an issue tracker should not carry them by default.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { TICKS_PER_SECOND } from './constants.js';
|
|
16
|
+
import { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
15
17
|
import type { EdfCalendarDate, EdfHeader, FormatHeaderOptions } from './types.js';
|
|
16
18
|
|
|
17
19
|
function formatDate(date: EdfCalendarDate | undefined): string {
|
|
@@ -21,9 +23,17 @@ function formatDate(date: EdfCalendarDate | undefined): string {
|
|
|
21
23
|
return `${date.year}-${month}-${day}`;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* `hh:mm:ss` from an exact tick count.
|
|
28
|
+
*
|
|
29
|
+
* Ticks, not `recordCount * recordDurationSeconds`. That product is float64, and a record duration
|
|
30
|
+
* with no exact binary representation makes it land just under the true value: 100 records of
|
|
31
|
+
* 0.29 s is exactly 29 s and computes as 28.999999999999996, which floors to 28. The header line
|
|
32
|
+
* then reports a recording a whole second shorter than it is (fixed in 0.2.67).
|
|
33
|
+
*/
|
|
34
|
+
function formatDurationTicks(ticks: bigint): string {
|
|
35
|
+
if (ticks < 0n) return 'unknown';
|
|
36
|
+
const whole = Number(ticks / TICKS_PER_SECOND);
|
|
27
37
|
const hours = Math.floor(whole / 3600);
|
|
28
38
|
const minutes = Math.floor((whole % 3600) / 60);
|
|
29
39
|
const rest = whole % 60;
|
|
@@ -31,6 +41,16 @@ function formatDuration(seconds: number): string {
|
|
|
31
41
|
return `${pad(hours)}:${pad(minutes)}:${pad(rest)}`;
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
/** Control characters become a dot, so one field can never become two rows or shift a column. */
|
|
45
|
+
function printable(text: string): string {
|
|
46
|
+
let out = '';
|
|
47
|
+
for (const character of text) {
|
|
48
|
+
const code = character.codePointAt(0) ?? 0;
|
|
49
|
+
out += code < 0x20 || code === 0x7f ? '.' : character;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
function formatRate(signal: EdfHeader['signals'][number]): string {
|
|
35
55
|
// undefined is the honest answer for a zero record duration, which is legal EDF.
|
|
36
56
|
return signal.sampleRateHz === undefined ? '—' : `${signal.sampleRateHz} Hz`;
|
|
@@ -60,7 +80,7 @@ export function formatHeader(header: EdfHeader, options?: FormatHeaderOptions):
|
|
|
60
80
|
`${header.bytesPerSample} bytes/sample`,
|
|
61
81
|
);
|
|
62
82
|
lines.push(
|
|
63
|
-
`duration ${
|
|
83
|
+
`duration ${formatDurationTicks(header.recordDurationTicks * BigInt(header.recordCount))} ` +
|
|
64
84
|
`(${header.recordCount} × ${header.recordDurationSeconds} s)`,
|
|
65
85
|
);
|
|
66
86
|
if (header.recordCountSource === 'sourceByteLength') {
|
|
@@ -77,7 +97,11 @@ export function formatHeader(header: EdfHeader, options?: FormatHeaderOptions):
|
|
|
77
97
|
lines.push(' # label kind rate range');
|
|
78
98
|
for (const signal of header.signals) {
|
|
79
99
|
const index = String(signal.index).padStart(3);
|
|
80
|
-
|
|
100
|
+
// Control characters are replaced, not printed. A label holding a newline would otherwise
|
|
101
|
+
// render as two rows and forge a signal the file does not contain; a tab would shift every
|
|
102
|
+
// column after it. EDF pads labels with spaces and says nothing about what else may be in
|
|
103
|
+
// them, so a writer can put anything there and a reader must not be steered by it.
|
|
104
|
+
const label = printable(signal.label).slice(0, 20).padEnd(21);
|
|
81
105
|
const kind = signal.kind.padEnd(12);
|
|
82
106
|
const rate = formatRate(signal).padEnd(9);
|
|
83
107
|
const range =
|
|
@@ -91,11 +115,19 @@ export function formatHeader(header: EdfHeader, options?: FormatHeaderOptions):
|
|
|
91
115
|
|
|
92
116
|
if (header.diagnostics.length > 0) {
|
|
93
117
|
lines.push('');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
// Fixed error-warning-info order, matching `formatValidationReport` since 0.2.15. Ordering by
|
|
119
|
+
// arrival meant two files with the same diagnostics could summarise them differently.
|
|
120
|
+
const counted = summarizeDiagnostics(header.diagnostics);
|
|
121
|
+
const summary = (
|
|
122
|
+
[
|
|
123
|
+
[counted.errors, 'error'],
|
|
124
|
+
[counted.warnings, 'warning'],
|
|
125
|
+
[counted.infos, 'info'],
|
|
126
|
+
] as const
|
|
127
|
+
)
|
|
128
|
+
.filter(([count]) => count > 0)
|
|
129
|
+
.map(([count, severity]) => `${count} ${severity}`)
|
|
130
|
+
.join(', ');
|
|
99
131
|
lines.push(`${header.diagnostics.length} diagnostic(s): ${summary}`);
|
|
100
132
|
lines.push('Call formatDiagnostics(header.diagnostics) for the detail.');
|
|
101
133
|
}
|