linked-rolls 0.64.0 → 0.66.0

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.
@@ -1,5 +1,5 @@
1
1
  import { WithNote, WithId } from "../shared/utils.js";
2
- import { Person, WithActor } from "./Agent.js";
2
+ import { Concept, Person, Software, WithActor } from "./Agent.js";
3
3
  export declare const certainties: readonly ['true', 'likely', 'possible', 'unlikely', 'false'];
4
4
  /**
5
5
  * Certainty levels for beliefs, ranging from 'true' to 'false'
@@ -102,6 +102,11 @@ export interface BeliefAdoption extends Argumentation<'beliefAdoption'> {
102
102
  * premises. CRMinf counts observations, and a measurement is one, among
103
103
  * the ways a belief comes about, so the value does not have to be
104
104
  * passed off as an inference.
105
+ *
106
+ * A measurement a program took names the program and its version, so
107
+ * that the value can be taken again and a value taken by a program
108
+ * later found wanting can be told from the rest. Naming software makes
109
+ * it a digital measurement event in the sense of CRMdig.
105
110
  * @see crmsci:S21 Measurement
106
111
  */
107
112
  export interface Measurement extends Argumentation<'measurement'> {
@@ -111,6 +116,24 @@ export interface Measurement extends Argumentation<'measurement'> {
111
116
  * @see crm:P16 used specific object
112
117
  */
113
118
  used?: string[];
119
+ /**
120
+ * The method followed, where it has a name apart from the program
121
+ * that carries it out, such as a line of Theil and Sen through the
122
+ * matched notes of two copies.
123
+ * @see crm:P33 used specific technique
124
+ */
125
+ procedure?: Concept;
126
+ /**
127
+ * The programs that took the measurement, each with its version.
128
+ * @see crmdig:L23 used software or firmware
129
+ */
130
+ software?: Software[];
131
+ /**
132
+ * When the measurement was taken. It is part of the reason and
133
+ * carries no belief of its own.
134
+ * @see crm:P4 has time-span
135
+ */
136
+ date?: TimeSpan;
114
137
  }
115
138
  /**
116
139
  * An argumentation can be either a plain argumentation, a
@@ -185,7 +208,6 @@ export type ObjectAssumption<O extends object> = Assumption & O;
185
208
  * annotated with a belief about its certainty.
186
209
  */
187
210
  export type ActorAssignment = ObjectAssumption<Person>;
188
- /** The day an event falls within. */
189
211
  interface DateWithin {
190
212
  /**
191
213
  * The day the event falls within.
@@ -194,8 +216,7 @@ interface DateWithin {
194
216
  */
195
217
  within: Date;
196
218
  }
197
- /** The bounds an event lies between. */
198
- interface DateBounds {
219
+ interface DateAfter {
199
220
  /**
200
221
  * The earliest day the event can have happened on: it did not
201
222
  * happen before it.
@@ -203,6 +224,22 @@ interface DateBounds {
203
224
  * @see crm:P82a begin of the begin
204
225
  */
205
226
  after: Date;
227
+ /**
228
+ * The latest day the event can have happened on: it did not
229
+ * happen after it.
230
+ * @format date
231
+ * @see crm:P82b end of the end
232
+ */
233
+ before?: Date;
234
+ }
235
+ interface DateBefore {
236
+ /**
237
+ * The earliest day the event can have happened on: it did not
238
+ * happen before it.
239
+ * @format date
240
+ * @see crm:P82a begin of the begin
241
+ */
242
+ after?: Date;
206
243
  /**
207
244
  * The latest day the event can have happened on: it did not
208
245
  * happen after it.
@@ -212,12 +249,20 @@ interface DateBounds {
212
249
  before: Date;
213
250
  }
214
251
  /**
215
- * When something happened, as far as the edition can state it: the day
216
- * it falls `within`, or the bounds it lies between. A bound nobody can
217
- * give is left out, so `after` alone says "not before".
252
+ * When something happened: the day it falls `within`, or the bounds it
253
+ * lies between. A bound nobody can give is left out, so `after` alone
254
+ * says "not before".
255
+ * @see crm:E52 Time-Span
256
+ */
257
+ export type TimeSpan = DateWithin | DateAfter | DateBefore;
258
+ /**
259
+ * When something happened, as far as the edition can state it, and as
260
+ * an assumption, so that a dating can carry the belief it rests on: the
261
+ * day it falls `within`, or the bounds it lies between. A bound nobody
262
+ * can give is left out, so `after` alone says "not before".
218
263
  * @see crm:E52 Time-Span
219
264
  */
220
- export type DateAssignment = Assumption & (DateWithin | (Pick<DateBounds, 'after'> & Partial<Pick<DateBounds, 'before'>>) | (Pick<DateBounds, 'before'> & Partial<Pick<DateBounds, 'after'>>));
265
+ export type DateAssignment = Assumption & TimeSpan;
221
266
  export declare function valueOf<ValueT>(assumption: ValueAssumption<ValueT>): ValueT;
222
267
  export declare function valuesOf<ValueT>(assumptions: ValueAssumption<ValueT>[]): ValueT[];
223
268
  export declare function idOf(assumption: ReferenceAssumption): string;
@@ -1,6 +1,5 @@
1
1
  import { RollCopy } from "./RollCopy.js";
2
2
  import { Version } from "./Version.js";
3
- import { CollationTolerance } from "../collation/Collation.js";
4
3
  import { DateAssignment, ObjectAssumption } from "./Assumption.js";
5
4
  import { Editor, Person, Place } from "./Agent.js";
6
5
  import { RollTempo } from "../systems/ReproducingSystem.js";
@@ -28,18 +27,6 @@ export interface EditionCreation {
28
27
  * @see dcterms:date
29
28
  */
30
29
  publicationDate: Date;
31
- /**
32
- * The tolerance every version of the edition was collated at,
33
- * before each derivation stated its own. How precisely two copies
34
- * place a symbol depends on which two they are, so the tolerance
35
- * belongs to the derivation, `Derivation.collationTolerance`, and
36
- * migration writes this one onto every derivation that gives none.
37
- * Nothing else reads it. A window's offset is the displacement
38
- * between two particular copies and so can never be stated here.
39
- * Not exported to RDF.
40
- * @deprecated
41
- */
42
- collationTolerance?: CollationTolerance;
43
30
  }
44
31
  /**
45
32
  * Describes the event of recording and documents
@@ -1,5 +1,5 @@
1
1
  import { ActorAssignment, DateAssignment, ObjectAssumption } from "./Assumption.js";
2
- import { Concept, Named } from "./Agent.js";
2
+ import { Concept, Named, Software } from "./Agent.js";
3
3
  import { ConditionState } from "./ConditionState.js";
4
4
  import { WithNote } from "../shared/utils.js";
5
5
  /**
@@ -25,22 +25,7 @@ export declare const isMeasured: (kind: SourceKind) => boolean;
25
25
  * be observed on the roll and on an image of it, and on nothing else.
26
26
  */
27
27
  export declare const bearsPhysicalEvidence: (kind: SourceKind) => boolean;
28
- /**
29
- * Software a capture ran, such as the transcriber that read a recording
30
- * into notes or the emulator that wrote a MIDI file.
31
- * @see crmdig:D14 Software
32
- */
33
- export interface Software {
34
- /**
35
- * @see rdfs:label
36
- */
37
- name: string;
38
- /**
39
- * The release, or the model checkpoint of a transcriber.
40
- * @see owl:versionInfo
41
- */
42
- version?: string;
43
- }
28
+ export type { Software } from "./Agent.js";
44
29
  /**
45
30
  * An instrument a copy was played on to be recorded. How it was
46
31
  * regulated, where that is known, is its condition.
@@ -5,7 +5,7 @@ import { TrackCalibration } from "./TrackCalibration.js";
5
5
  import { AnyFeature, FeatureOrPatch, HorizontalSpan, Patch } from "./Feature.js";
6
6
  import { ActorAssignment, DateAssignment, ObjectAssumption, ReferenceAssumption } from "./Assumption.js";
7
7
  import { WithId, WithType } from "../shared/utils.js";
8
- import { Agent, Concept } from "./Agent.js";
8
+ import { Agent, Concept, Software } from "./Agent.js";
9
9
  import { FeatureSource } from "./FeatureSource.js";
10
10
  import { Measure, Millimeters, Percent, Quantity, Track } from "./Quantity.js";
11
11
  import { Perforator } from "./Perforator.js";
@@ -161,6 +161,28 @@ export interface Alignment {
161
161
  date: Date;
162
162
  };
163
163
  }
164
+ /**
165
+ * The width and length of a roll copy's paper, as they were measured on
166
+ * it or on its scan.
167
+ * @see crm:E54 Dimension
168
+ */
169
+ export interface RollDimensions {
170
+ /**
171
+ * The width of the roll.
172
+ * @see reo:width
173
+ */
174
+ width: Millimeters;
175
+ /**
176
+ * The total height (length) of the roll.
177
+ * @see reo:height
178
+ */
179
+ height: Millimeters;
180
+ /**
181
+ * The unit of measurement.
182
+ * @see crm:P91 has unit
183
+ */
184
+ unit: 'mm';
185
+ }
164
186
  /** The margins on the treble and bass sides of the roll, in the unit the scan was measured in. */
165
187
  export interface Margins<U extends 'px' | 'mm'> {
166
188
  treble: Quantity<U>;
@@ -307,11 +329,14 @@ export type Modification = Partial<{
307
329
  */
308
330
  export interface RollCopy extends WithType<'RollCopy'>, WithId {
309
331
  /**
310
- * Physical measurements of this roll copy, including
311
- * dimensions, hole separation, margins, the alignment onto the
312
- * edition's axis, and information about the measuring software. What the
313
- * perforations tell about the machine that cut them is stated
314
- * with the production.
332
+ * Physical measurements of this roll copy: its dimensions, hole
333
+ * separation and margins, the resolution of its scan, and how it
334
+ * lies on the edition's axis. What the perforations tell about the
335
+ * machine that cut them is stated with the production.
336
+ *
337
+ * A value the edition exports is an object assumption, so that it
338
+ * can say what took it: a measurement naming the program and its
339
+ * version, the scan or analysis it was taken on, and the day.
315
340
  * @see crm:P39i was measured by
316
341
  */
317
342
  measurements: Partial<{
@@ -319,23 +344,7 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
319
344
  * The physical dimensions of the roll.
320
345
  * @see reo:dimensions
321
346
  */
322
- dimensions: {
323
- /**
324
- * The width of the roll.
325
- * @see reo:width
326
- */
327
- width: Millimeters;
328
- /**
329
- * The total height (length) of the roll.
330
- * @see reo:height
331
- */
332
- height: Millimeters;
333
- /**
334
- * The unit of measurement.
335
- * @see crm:P91 has unit
336
- */
337
- unit: 'mm';
338
- };
347
+ dimensions: ObjectAssumption<RollDimensions>;
339
348
  /**
340
349
  * The distance across the roll from the centre of one track to
341
350
  * the next, the track pitch or Teilung, in the unit the scan was
@@ -344,7 +353,7 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
344
353
  * along the roll.
345
354
  * @see reo:holeSeparation
346
355
  */
347
- holeSeparation: Measure<'px'> | Measure<'mm'>;
356
+ holeSeparation: ObjectAssumption<Measure<'px'> | Measure<'mm'>>;
348
357
  /**
349
358
  * The margins on the treble and bass sides of the roll.
350
359
  * Not exported to RDF.
@@ -389,35 +398,13 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
389
398
  * files do not state.
390
399
  * @see reo:scanResolution
391
400
  */
392
- scanResolution: Measure<'px/in'>;
401
+ scanResolution: ObjectAssumption<Measure<'px/in'>>;
393
402
  /**
394
403
  * Relates this copy's scan to the tracker bar: how the scanning
395
404
  * software's hole numbering was shifted onto the bar, and where
396
405
  * the track grid sits in the image. Not exported to RDF.
397
406
  */
398
407
  trackCalibration: TrackCalibration;
399
- /**
400
- * Information about the software used to take the measurements.
401
- * @see crmdig:L23 used software or firmware
402
- */
403
- measuredBy: {
404
- /**
405
- * The name of the measurement software.
406
- * @see rdfs:label
407
- */
408
- software: string;
409
- /**
410
- * The version of the measurement software.
411
- * @see owl:versionInfo
412
- */
413
- version: string;
414
- /**
415
- * The date on which the measurements were taken.
416
- * @format date
417
- * @see dcterms:date
418
- */
419
- date: Date;
420
- };
421
408
  }>;
422
409
  /**
423
410
  * The production event that created this roll copy.
@@ -496,6 +483,8 @@ export declare const statesNothing: (modification: Modification) => boolean;
496
483
  export declare const featuresByAct: (copy: Pick<RollCopy, 'production' | 'modifications'>) => FeatureOrPatch[][];
497
484
  /** Every feature the copy states at a place of its own, whichever act made it. */
498
485
  export declare const featuresOf: (copy: Pick<RollCopy, 'production' | 'modifications'>) => FeatureOrPatch[];
486
+ /** The programs the measurements of a copy name as having taken them, each once, as far as they name any. */
487
+ export declare const measuringSoftwareOf: (copy: Pick<RollCopy, 'measurements'>) => Software[];
499
488
  /** Whether the condition is the stretch or shrinkage of the paper, as measured on the copy. */
500
489
  export declare const isPaperStretch: (condition: RollConditionAssignment) => condition is ObjectAssumption<PaperStretch>;
501
490
  /** What the copy's paper was measured to have done, where anything was measured. */
@@ -35,6 +35,16 @@ export const statesNothing = (modification) => modification.type === 'Removal' ?
35
35
  export const featuresByAct = (copy) => [copy.production?.produced ?? [], ...copy.modifications.map(featuresMadeBy)];
36
36
  /** Every feature the copy states at a place of its own, whichever act made it. */
37
37
  export const featuresOf = (copy) => featuresByAct(copy).flat();
38
+ /** The measurements of a copy the edition exports, each of which can say what took it. */
39
+ const exportedMeasurementsOf = (copy) => [copy.measurements.dimensions, copy.measurements.holeSeparation, copy.measurements.scanResolution]
40
+ .filter(value => value !== undefined);
41
+ /** The programs the measurements of a copy name as having taken them, each once, as far as they name any. */
42
+ export const measuringSoftwareOf = (copy) => {
43
+ const named = exportedMeasurementsOf(copy)
44
+ .flatMap(value => value['@annotation']?.belief.reasons ?? [])
45
+ .flatMap(reason => reason.type === 'measurement' ? reason.software ?? [] : []);
46
+ return [...new Map(named.map(software => [`${software.name}\u0000${software.version ?? ''}`, software])).values()];
47
+ };
38
48
  /** Whether the condition is the stretch or shrinkage of the paper, as measured on the copy. */
39
49
  export const isPaperStretch = (condition) => condition.conditionType === 'paper-stretch';
40
50
  /** What the copy's paper was measured to have done, where anything was measured. */
@@ -33,6 +33,23 @@ export type Derivation = ReferenceAssumption & {
33
33
  };
34
34
  /** The tolerance the derivation was collated at, or the default where it states none. */
35
35
  export declare const collationToleranceOf: (derivation: Readonly<Derivation>) => CollationTolerance;
36
+ /**
37
+ * How long a version's paper runs for a length of the paper of the
38
+ * version it derives from: about 0.77 for a Licensee or green re-cut of
39
+ * a red roll, cut for a slower paper speed.
40
+ * @see crm:E54 Dimension
41
+ */
42
+ export interface LengthRatio {
43
+ /**
44
+ * @see crm:P90 has value
45
+ */
46
+ value: number;
47
+ /**
48
+ * The standard uncertainty of the value, one standard deviation.
49
+ * @see reo:uncertainty
50
+ */
51
+ uncertainty?: number;
52
+ }
36
53
  /**
37
54
  * How a version was made, where that is known and worth stating.
38
55
  *
@@ -63,6 +80,19 @@ export interface VersionCreation {
63
80
  * @see crm:P33 used specific technique
64
81
  */
65
82
  procedure?: Concept;
83
+ /**
84
+ * How long the act made the version's paper for a length of the
85
+ * paper of the version it derives from. A re-cut for a system whose
86
+ * paper runs slower comes out shorter by the ratio of the speeds,
87
+ * and the alignments of the copies measure that ratio more closely
88
+ * than any label states a speed (`lengthRatioOf`). It is stated
89
+ * where the edition holds to it, as a measurement naming the copies
90
+ * it used and the program that found it, so that it can serve as a
91
+ * premise. A speed follows from it only together with a premise
92
+ * about the speed of the other version.
93
+ * @see reo:lengthRatio
94
+ */
95
+ lengthRatio?: ObjectAssumption<LengthRatio>;
66
96
  }
67
97
  /**
68
98
  * A version is defined by the sum of edits applied to the version it is
@@ -5,7 +5,7 @@ import { collationToleranceOf, derivesFrom, editsOf, insertedBy, principalDeriva
5
5
  import { substitutionsBetween } from "../collation/substitution.js";
6
6
  import { trackerBarOf } from "../systems/index.js";
7
7
  import { assignReference, idOf } from "../model/Assumption.js";
8
- import { noChange, onVersion, stateOf, insertion, deletion, asUnchecked, declaring, insertedIn, dropInsertions, reading } from "./draft.js";
8
+ import { noChange, onVersion, insertion, deletion, asUnchecked, declaring, dropInsertions, reading } from "./draft.js";
9
9
  import { hypothesesBeside } from "./versions.js";
10
10
  import { placeOf, snapshotOf } from "../analysis/text.js";
11
11
  import { copyOfFeature, versionIn } from "../lookup/lookup.js";
@@ -114,14 +114,18 @@ export const connectVersions = (childId, parentId, tolerance = defaultCollationT
114
114
  .filter(symbol => !matched.has(symbol.id) && !paired.has(symbol.id))
115
115
  .map(symbol => asUnchecked(deletion(symbol.id)))
116
116
  ];
117
- return onVersion(childId, (child, draft) => {
117
+ // Read off the child as it stands, since once its edits are written a
118
+ // reading of it would copy them all.
119
+ const motivations = declaring(child?.motivations ?? [], edits);
120
+ const basedOn = [
121
+ { ...assignReference(parentId), collationTolerance: tolerance },
122
+ ...(child ? hypothesesBeside(child, parentId) : [])
123
+ ];
124
+ return onVersion(childId, (version, draft) => {
118
125
  handOverCarriers(edition, draft, collations);
119
- child.edits = edits;
120
- child.motivations = declaring(stateOf(child).motivations, edits);
121
- child.basedOn = [
122
- { ...assignReference(parentId), collationTolerance: tolerance },
123
- ...hypothesesBeside(stateOf(child), parentId)
124
- ];
126
+ version.edits = edits;
127
+ version.motivations = motivations;
128
+ version.basedOn = basedOn;
125
129
  });
126
130
  });
127
131
  /**
@@ -135,7 +139,7 @@ export const collateSymbols = (versionId, symbolIds, tolerance) => reading(editi
135
139
  if (!version || !principal)
136
140
  return noChange;
137
141
  const chosen = new Set(symbolIds);
138
- const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
142
+ const own = insertedBy(version).filter(symbol => chosen.has(symbol.id));
139
143
  const collations = collationsOf(own, snapshotOf(edition, idOf(principal)), symbol => placeOf(edition, symbol), tolerance ?? collationToleranceOf(principal));
140
144
  const collated = new Set(collations.map(({ symbol }) => symbol.id));
141
145
  return onVersion(versionId, (version, draft) => {
package/lib/ops/copies.js CHANGED
@@ -83,9 +83,17 @@ export const alignCopies = (date = new Date()) => draft => {
83
83
  const own = draft.copies.find(c => c.id === reference.id);
84
84
  if (own)
85
85
  revertAlignment(own);
86
- stateOf(draft).copies
87
- .filter(copy => copy.id !== reference.id && featuresOf(copy).length > 0)
88
- .forEach(copy => alignCopy(copy.id, date)(draft));
86
+ // Aligning a copy moves neither the reference copy nor any other,
87
+ // so every copy is aligned from the one state. Read afresh after
88
+ // each, the draft would be copied with every copy aligned so far.
89
+ const state = stateOf(draft);
90
+ state.copies.forEach((copy, i) => {
91
+ if (copy.id === reference.id || featuresOf(copy).length === 0)
92
+ return;
93
+ const alignment = alignmentFor(state, copy, date);
94
+ if (alignment)
95
+ applyAlignment(alignment, draft.copies[i]);
96
+ });
89
97
  };
90
98
  /**
91
99
  * Makes the copy the one whose millimetres are the edition's axis, and
@@ -1,7 +1,11 @@
1
1
  /** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
2
2
  /** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
3
3
  export declare const without: <T>(items: T[], matches: (item: T) => boolean) => T[];
4
- /** The items each changed, or the very same array when the change left every one as it was. */
4
+ /**
5
+ * The items each changed, or the very same array when the change left
6
+ * every one as it was. The array is copied at the first item that
7
+ * changes, so that one left as it was costs no copy.
8
+ */
5
9
  export declare const mapped: <T>(items: T[], change: (item: T) => T) => T[];
6
10
  /** The items each changed, less those the change emptied; the very same array where it changed none. */
7
11
  export declare const pruned: <T>(items: T[], change: (item: T) => T, emptied: (item: T) => boolean) => T[];
@@ -10,6 +14,8 @@ export declare const replacing: <T extends object, K extends keyof T>(record: T,
10
14
  /** A record of the edition, as the walk over it sees one. */
11
15
  export type Node = Record<string, unknown>;
12
16
  export declare const isRecord: (value: unknown) => value is Node;
17
+ /** The record's values each changed, or the very same record where the change left every one as it was, copied as `mapped` copies. */
18
+ export declare const withValues: (record: Node, change: (value: unknown) => unknown) => Node;
13
19
  /** The value with the change applied to every record within it and then to itself, innermost first. */
14
20
  export declare const deeply: (change: (record: object) => object) => (value: unknown) => unknown;
15
21
  /** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
@@ -1,10 +1,19 @@
1
1
  /** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
2
2
  /** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
3
3
  export const without = (items, matches) => items.some(matches) ? items.filter(item => !matches(item)) : items;
4
- /** The items each changed, or the very same array when the change left every one as it was. */
4
+ /**
5
+ * The items each changed, or the very same array when the change left
6
+ * every one as it was. The array is copied at the first item that
7
+ * changes, so that one left as it was costs no copy.
8
+ */
5
9
  export const mapped = (items, change) => {
6
- const changed = items.map(change);
7
- return changed.every((item, i) => item === items[i]) ? items : changed;
10
+ let changed;
11
+ items.forEach((item, i) => {
12
+ const after = change(item);
13
+ if (after !== item)
14
+ (changed ?? (changed = items.slice()))[i] = after;
15
+ });
16
+ return changed ?? items;
8
17
  };
9
18
  /** The items each changed, less those the change emptied; the very same array where it changed none. */
10
19
  export const pruned = (items, change, emptied) => {
@@ -14,11 +23,16 @@ export const pruned = (items, change, emptied) => {
14
23
  /** The record with the field replaced, or the very same record where that is what stood there. */
15
24
  export const replacing = (record, key, value) => record[key] === value ? record : { ...record, [key]: value };
16
25
  export const isRecord = (value) => typeof value === 'object' && value !== null;
17
- /** The record's values each changed, or the very same record where the change left every one as it was. */
18
- const withValues = (record, change) => {
19
- const entries = Object.entries(record);
20
- const changed = entries.map(([key, value]) => [key, change(value)]);
21
- return changed.every(([, value], i) => value === entries[i][1]) ? record : Object.fromEntries(changed);
26
+ /** The record's values each changed, or the very same record where the change left every one as it was, copied as `mapped` copies. */
27
+ export const withValues = (record, change) => {
28
+ let changed;
29
+ Object.keys(record).forEach(key => {
30
+ const value = record[key];
31
+ const after = change(value);
32
+ if (after !== value)
33
+ (changed ?? (changed = { ...record }))[key] = after;
34
+ });
35
+ return changed ?? record;
22
36
  };
23
37
  /** The value with the change applied to every record within it and then to itself, innermost first. */
24
38
  export const deeply = (change) => {
@@ -129,12 +129,6 @@ const controlOffsetOf = (system, given) => {
129
129
  }
130
130
  return measured;
131
131
  };
132
- /** The position each note of the bar sounds from. */
133
- const positionsByPitch = (bar) => new Map(Array.from({ length: bar.trackCount }, (_, index) => track(index + 1))
134
- .flatMap((position) => {
135
- const meaning = bar.meaningOf(position);
136
- return meaning?.type === 'note' ? [[meaning.pitch, position]] : [];
137
- }));
138
132
  /**
139
133
  * Reads one of his e-roll files as a copy of the roll, in millimetres
140
134
  * of paper, on the bar it was cut for and in that bar's numbering. A
@@ -146,8 +140,7 @@ export function readFromPhillipsEroll(buffer, { system = welteT100, controlOffse
146
140
  const elapsedAt = clockOf(temposIn(timed), file.header.ticksPerBeat);
147
141
  const place = placeAt ?? atStatedSpeed(system);
148
142
  const offset = controlOffsetOf(system, controlOffset);
149
- const notePositions = positionsByPitch(system);
150
- const positionOf = (number) => notePositions.get(number) ?? track(number - offset);
143
+ const positionOf = (number) => system.positionOf({ type: 'note', pitch: number }) ?? track(number - offset);
151
144
  const features = notesIn(timed).flatMap((note) => {
152
145
  const position = positionOf(note.pitch);
153
146
  if (!system.meaningOf(position))
@@ -1,8 +1,10 @@
1
1
  import { v4 } from "uuid";
2
2
  import { AtonParser } from "./AtonParser.js";
3
+ import { assignDate } from "../model/Assumption.js";
3
4
  import { systemOf } from "../systems/TrackerBar.js";
4
5
  import { welteT100 } from "../systems/welteT100/bar.js";
5
6
  import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../model/Quantity.js";
7
+ import { medianOf } from "../collation/statistics.js";
6
8
  /** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
7
9
  const readPx = (value) => px(parseFloat(value));
8
10
  /** The parser writes one record as an object and several as an array. */
@@ -20,11 +22,6 @@ const chainedBadHoles = (holes, calibration) => holes
20
22
  ...hole,
21
23
  TRACKER_HOLE: `${Math.round((readPx(hole.CENTROID_COL) - calibration.offset) / calibration.separation)}`
22
24
  }));
23
- const median = (values) => {
24
- const sorted = [...values].sort((a, b) => a - b);
25
- const middle = Math.floor(sorted.length / 2);
26
- return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
27
- };
28
25
  const mostFrequent = (values) => {
29
26
  const counts = values.reduce((acc, value) => acc.set(value, (acc.get(value) || 0) + 1), new Map());
30
27
  return [...counts].sort(([, a], [, b]) => b - a)[0]?.[0];
@@ -76,7 +73,7 @@ const calibrationShiftIn = (holes, bar) => {
76
73
  const gridOffsetOf = (holes, separation, stated) => {
77
74
  if (stated !== undefined)
78
75
  return readPx(stated);
79
- return px(median(holes.map(hole => readPx(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation)));
76
+ return medianOf(holes.map(hole => px(readPx(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation))) ?? px(0);
80
77
  };
81
78
  const chainsAmong = (holes) => holes
82
79
  .filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
@@ -124,19 +121,24 @@ const tearsIn = (tears, dpi, depictionOf) => {
124
121
  .sort((a, b) => a.horizontal.from - b.horizontal.from);
125
122
  };
126
123
  /**
127
- * The software behind an analysis and when it was run, as the
128
- * analysis file states them.
124
+ * The software behind an analysis and the day it was run, as the
125
+ * analysis file states them: the measurement the dimensions and the
126
+ * hole separation it gives rest on.
129
127
  */
130
- const measuredByOf = (rollinfo) => {
128
+ const measurementOf = (rollinfo) => {
131
129
  const date = new Date(rollinfo.ANALYSIS_DATE);
132
130
  if (!rollinfo.HOLE_SOFTWARE || isNaN(date.getTime()))
133
131
  return undefined;
134
132
  return {
135
- software: rollinfo.HOLE_SOFTWARE,
136
- version: rollinfo.SOFTWARE_DATE ?? '',
137
- date
133
+ type: 'measurement',
134
+ software: [{ name: rollinfo.HOLE_SOFTWARE, ...(rollinfo.SOFTWARE_DATE && { version: rollinfo.SOFTWARE_DATE }) }],
135
+ date: assignDate(new Date(date.getFullYear(), date.getMonth(), date.getDate()))
138
136
  };
139
137
  };
138
+ /** The value held on the strength of the measurement, where the file names one, and plainly where it does not. */
139
+ const takenBy = (measurement) => (value) => measurement
140
+ ? { ...value, '@annotation': { id: v4(), belief: { type: 'belief', id: v4(), certainty: 'true', reasons: [measurement] } } }
141
+ : value;
140
142
  export function readFromStanfordAton(atonString, { trackShift, system = welteT100, scan } = {}) {
141
143
  const parser = new AtonParser();
142
144
  const json = parser.parse(atonString);
@@ -145,7 +147,7 @@ export function readFromStanfordAton(atonString, { trackShift, system = welteT10
145
147
  const stanford = druid ? stanfordScan(druid) : undefined;
146
148
  const separation = readPx(json.ROLLINFO.HOLE_SEPARATION);
147
149
  const dpi = parseFloat(json.ROLLINFO.LENGTH_DPI);
148
- const measuredBy = measuredByOf(json.ROLLINFO);
150
+ const measured = takenBy(measurementOf(json.ROLLINFO));
149
151
  const shift = trackShift ?? calibrationShiftIn(holes, system);
150
152
  const calibration = {
151
153
  unit: 'px',
@@ -200,15 +202,15 @@ export function readFromStanfordAton(atonString, { trackShift, system = welteT10
200
202
  modifications: [],
201
203
  ...((scan ?? stanford) && { scan: scan ?? stanford?.scan }),
202
204
  measurements: {
203
- dimensions: {
205
+ dimensions: measured({
204
206
  width: inMillimeters(readPx(json.ROLLINFO.ROLL_WIDTH), dpi),
205
207
  height: inMillimeters(readPx(json.ROLLINFO.IMAGE_LENGTH), dpi),
206
208
  unit: 'mm'
207
- },
208
- holeSeparation: {
209
+ }),
210
+ holeSeparation: measured({
209
211
  value: separation,
210
212
  unit: 'px'
211
- },
213
+ }),
212
214
  margins: {
213
215
  treble: readPx(json.ROLLINFO.HARD_MARGIN_TREBLE),
214
216
  bass: readPx(json.ROLLINFO.HARD_MARGIN_BASS),
@@ -217,8 +219,7 @@ export function readFromStanfordAton(atonString, { trackShift, system = welteT10
217
219
  ...(Number.isFinite(dpi) && {
218
220
  scanResolution: { value: pixelsPerInch(dpi), unit: 'px/in' }
219
221
  }),
220
- trackCalibration: calibration,
221
- ...(measuredBy && { measuredBy })
222
+ trackCalibration: calibration
222
223
  }
223
224
  };
224
225
  }