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.
- package/README.md +8 -0
- package/lib/analysis/constraints.js +31 -28
- package/lib/analysis/paper.d.ts +17 -0
- package/lib/analysis/paper.js +38 -1
- package/lib/analysis/reservations.js +2 -2
- package/lib/analysis/witnesses.js +6 -3
- package/lib/collation/alignment.js +55 -20
- package/lib/collation/scatter.d.ts +2 -2
- package/lib/io/asJsonLd.js +33 -38
- package/lib/io/migrate.js +73 -26
- package/lib/model/Agent.d.ts +17 -0
- package/lib/model/Assumption.d.ts +53 -8
- package/lib/model/Edition.d.ts +0 -13
- package/lib/model/FeatureSource.d.ts +2 -17
- package/lib/model/RollCopy.d.ts +36 -47
- package/lib/model/RollCopy.js +10 -0
- package/lib/model/Version.d.ts +30 -0
- package/lib/ops/collation.js +13 -9
- package/lib/ops/copies.js +11 -3
- package/lib/ops/immutable.d.ts +7 -1
- package/lib/ops/immutable.js +22 -8
- package/lib/readers/phillipsEroll.js +1 -8
- package/lib/readers/stanfordAton.js +20 -19
- package/lib/schema.json +297 -141
- package/lib/spec/context.json +12 -8
- package/lib/systems/welte.d.ts +21 -1
- package/lib/systems/welte.js +22 -4
- package/lib/systems/welteT100/system.d.ts +1 -16
- package/lib/systems/welteT100/system.js +3 -17
- package/lib/systems/welteT98/system.d.ts +1 -12
- package/lib/systems/welteT98/system.js +3 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,14 @@ between the two. What the alignments say about the paper of the copies,
|
|
|
55
55
|
how far each has stretched and how the papers of the systems relate, is
|
|
56
56
|
worked out by `paperOf`.
|
|
57
57
|
|
|
58
|
+
What is worked out is not stated. Where the edition holds to the ratio a
|
|
59
|
+
re-cut gave the paper of its version, the creation of the version states
|
|
60
|
+
it (`lengthRatio`, as `lengthRatioOf` gives it), so that others can take
|
|
61
|
+
it as a premise. A measured value, such as that ratio, a copy's
|
|
62
|
+
dimensions and hole separation, or the setting of its perforator, is
|
|
63
|
+
held on the strength of a measurement, which names what it was taken
|
|
64
|
+
on, the program and its version, and the day.
|
|
65
|
+
|
|
58
66
|
Documents written by earlier releases are brought up to date on import.
|
|
59
67
|
To check a document against the schema:
|
|
60
68
|
|
|
@@ -80,20 +80,19 @@ const typesNotOnTheBar = (version, snapshot) => {
|
|
|
80
80
|
* out says 59. Collation across systems consults only the place, so
|
|
81
81
|
* without this nothing checks the tracks at all.
|
|
82
82
|
*/
|
|
83
|
-
const
|
|
84
|
-
const
|
|
83
|
+
const carriedOffItsMeaning = (edition) => (symbol) => {
|
|
84
|
+
const key = keyOf(symbol);
|
|
85
|
+
return placedCarriersOf(edition, symbol).some(carrier => {
|
|
85
86
|
const copy = copyOfFeature(edition, carrier.id);
|
|
86
|
-
if (!copy)
|
|
87
|
-
return false;
|
|
88
87
|
// A carrier lying across several positions reads as several commands,
|
|
89
88
|
// and carries the symbol as long as one of them is the symbol's.
|
|
90
|
-
return !barOf(copy).meaningsOf(carrier.vertical)
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
return commands
|
|
94
|
-
.filter(symbol => placedCarriersOf(edition, symbol).some(carrier => misread(carrier, symbol)))
|
|
95
|
-
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
89
|
+
return copy !== undefined && !barOf(copy).meaningsOf(carrier.vertical).some(meaning => keyOf(meaning) === key);
|
|
90
|
+
});
|
|
96
91
|
};
|
|
92
|
+
/** The commands the version shows among those carried off their meaning. */
|
|
93
|
+
const carriersOffTheirMeaning = (misread, version, commands) => commands
|
|
94
|
+
.filter(symbol => misread.has(symbol))
|
|
95
|
+
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
97
96
|
/**
|
|
98
97
|
* Where a copy of the version's own system strays from the others in
|
|
99
98
|
* its paper by more than paper does.
|
|
@@ -104,13 +103,9 @@ const carriersOffTheirMeaning = (edition, version, commands) => {
|
|
|
104
103
|
* speed, and averaging it in would hide both that and the fact that the
|
|
105
104
|
* playback rests on a guess.
|
|
106
105
|
*/
|
|
107
|
-
const paperDisagreed = (edition, version) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.some(({ copy, problem }) => problem === 'paper-beyond-its-spread' && own.has(copy))
|
|
111
|
-
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
112
|
-
: [];
|
|
113
|
-
};
|
|
106
|
+
const paperDisagreed = (edition, strays, version) => paperOfVersion(edition, version)?.copies.some(copy => strays.has(copy))
|
|
107
|
+
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
108
|
+
: [];
|
|
114
109
|
/**
|
|
115
110
|
* A version's strikes that take nothing out of its text: each deleted
|
|
116
111
|
* symbol has to be one the parent hands down, or one the version put
|
|
@@ -143,14 +138,22 @@ const strikesBitingNothing = (edition, version) => {
|
|
|
143
138
|
* track that does not say what its symbol says, and a strike that takes
|
|
144
139
|
* nothing out.
|
|
145
140
|
*/
|
|
146
|
-
export const constraintProblems = (edition) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
141
|
+
export const constraintProblems = (edition) => {
|
|
142
|
+
// What a symbol's carriers and the copies' paper say holds whichever
|
|
143
|
+
// version shows them, so it is asked once for the edition.
|
|
144
|
+
const misread = new Set(edition.versions.flatMap(insertedBy).filter(isCommand).filter(carriedOffItsMeaning(edition)));
|
|
145
|
+
const strays = new Set(alignmentProblems(edition)
|
|
146
|
+
.filter(({ problem }) => problem === 'paper-beyond-its-spread')
|
|
147
|
+
.map(({ copy }) => copy));
|
|
148
|
+
return edition.versions.flatMap(version => {
|
|
149
|
+
const snapshot = snapshotOf(edition, version.id);
|
|
150
|
+
const commands = snapshot.filter(isCommand);
|
|
151
|
+
return [
|
|
152
|
+
...problemsIn(version.id, commands),
|
|
153
|
+
...typesNotOnTheBar(version, snapshot),
|
|
154
|
+
...carriersOffTheirMeaning(misread, version.id, commands),
|
|
155
|
+
...paperDisagreed(edition, strays, version),
|
|
156
|
+
...strikesBitingNothing(edition, version)
|
|
157
|
+
];
|
|
158
|
+
});
|
|
159
|
+
};
|
package/lib/analysis/paper.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Edition } from "../model/Edition.js";
|
|
2
2
|
import { Strain } from "../model/RollCopy.js";
|
|
3
|
+
import { LengthRatio } from "../model/Version.js";
|
|
3
4
|
import { Percent } from "../model/Quantity.js";
|
|
4
5
|
/**
|
|
5
6
|
* How far the paper of one copy is taken to have stretched or shrunk
|
|
@@ -127,3 +128,19 @@ export type AlignmentProblem = {
|
|
|
127
128
|
* is `alignmentFor`'s to say.
|
|
128
129
|
*/
|
|
129
130
|
export declare const alignmentProblems: (edition: Pick<Edition, 'copies' | 'referenceCopy'>) => AlignmentProblem[];
|
|
131
|
+
/**
|
|
132
|
+
* How long the version's paper runs for a length of the paper of the
|
|
133
|
+
* version it derives from, as the alignments give it: the ratio of the
|
|
134
|
+
* two versions' systems' papers, with its uncertainty. It is what the
|
|
135
|
+
* creation of the version states where the edition holds to it
|
|
136
|
+
* (`VersionCreation.lengthRatio`).
|
|
137
|
+
*
|
|
138
|
+
* The reading pools the copies of a system, so the ratio is only given
|
|
139
|
+
* for a version that one of those copies bears witness to: a second
|
|
140
|
+
* re-cut for the same system, whose copies measure no paper, is not
|
|
141
|
+
* credited with the first one's ratio. Where the reading has copies of
|
|
142
|
+
* two versions re-cut for one system, the ratio is theirs together.
|
|
143
|
+
* Nothing is given for a version that derives from none, or from one on
|
|
144
|
+
* its own system, whose paper it keeps.
|
|
145
|
+
*/
|
|
146
|
+
export declare const lengthRatioOf: (edition: Edition, versionId: string) => LengthRatio | undefined;
|
package/lib/analysis/paper.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { referenceCopyOf } from "../model/Edition.js";
|
|
2
2
|
import { barOf, featuresOf, paperStretchOf } from "../model/RollCopy.js";
|
|
3
|
-
import { certaintyOf, isAsserted } from "../model/Assumption.js";
|
|
3
|
+
import { certaintyOf, idOf, isAsserted } from "../model/Assumption.js";
|
|
4
|
+
import { principalDerivationOf } from "../model/Version.js";
|
|
5
|
+
import { versionIn } from "../lookup/lookup.js";
|
|
6
|
+
import { witnessesOf } from "./witnesses.js";
|
|
4
7
|
import { inMetersPerMinute, percent } from "../model/Quantity.js";
|
|
8
|
+
import { trackerBarOf } from "../systems/index.js";
|
|
5
9
|
/**
|
|
6
10
|
* How far the paper of one copy is taken to have stretched or shrunk
|
|
7
11
|
* along the roll, as a standard deviation, where the edition has too few
|
|
@@ -258,3 +262,36 @@ export const alignmentProblems = (edition) => {
|
|
|
258
262
|
});
|
|
259
263
|
return [...unaligned, ...elsewhere, ...beyond, ...speeds];
|
|
260
264
|
};
|
|
265
|
+
/**
|
|
266
|
+
* How long the version's paper runs for a length of the paper of the
|
|
267
|
+
* version it derives from, as the alignments give it: the ratio of the
|
|
268
|
+
* two versions' systems' papers, with its uncertainty. It is what the
|
|
269
|
+
* creation of the version states where the edition holds to it
|
|
270
|
+
* (`VersionCreation.lengthRatio`).
|
|
271
|
+
*
|
|
272
|
+
* The reading pools the copies of a system, so the ratio is only given
|
|
273
|
+
* for a version that one of those copies bears witness to: a second
|
|
274
|
+
* re-cut for the same system, whose copies measure no paper, is not
|
|
275
|
+
* credited with the first one's ratio. Where the reading has copies of
|
|
276
|
+
* two versions re-cut for one system, the ratio is theirs together.
|
|
277
|
+
* Nothing is given for a version that derives from none, or from one on
|
|
278
|
+
* its own system, whose paper it keeps.
|
|
279
|
+
*/
|
|
280
|
+
export const lengthRatioOf = (edition, versionId) => {
|
|
281
|
+
const version = versionIn(edition, versionId);
|
|
282
|
+
const derivation = version && principalDerivationOf(version);
|
|
283
|
+
const base = derivation && versionIn(edition, idOf(derivation));
|
|
284
|
+
const reading = paperOf(edition);
|
|
285
|
+
if (!version || !base || !reading)
|
|
286
|
+
return undefined;
|
|
287
|
+
const systemOf = (of) => reading.systems.find(paper => paper.system === trackerBarOf(of.system)?.id);
|
|
288
|
+
const own = systemOf(version);
|
|
289
|
+
const theirs = systemOf(base);
|
|
290
|
+
if (!own || !theirs || own.system === theirs.system)
|
|
291
|
+
return undefined;
|
|
292
|
+
const witnesses = new Set(witnessesOf(edition, versionId).map(witness => witness.copy));
|
|
293
|
+
if (!own.copies.some(copy => witnesses.has(copy)))
|
|
294
|
+
return undefined;
|
|
295
|
+
const value = own.ratio / theirs.ratio;
|
|
296
|
+
return { value, uncertainty: value * Math.hypot(own.ratioError / own.ratio, theirs.ratioError / theirs.ratio) };
|
|
297
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bearsPhysicalEvidence, isMeasured, sourceLabels } from "../model/FeatureSource.js";
|
|
2
|
-
import { calibrationOf, featuresOf } from "../model/RollCopy.js";
|
|
2
|
+
import { calibrationOf, featuresOf, measuringSoftwareOf } from "../model/RollCopy.js";
|
|
3
3
|
import { trackerBarOf } from "../systems/index.js";
|
|
4
4
|
import { witnessesOf } from "./witnesses.js";
|
|
5
5
|
export const reservationTypes = [
|
|
@@ -49,7 +49,7 @@ const physicalEvidence = copy => copy.readFrom && !bearsPhysicalEvidence(copy.re
|
|
|
49
49
|
type: 'no-physical-evidence',
|
|
50
50
|
note: 'Punch diameter, hole separation, punching pattern, marks and writings cannot be observed on this source.'
|
|
51
51
|
} : undefined;
|
|
52
|
-
const measurementDocumented = copy =>
|
|
52
|
+
const measurementDocumented = copy => measuringSoftwareOf(copy).length === 0 && copy.readFrom?.kind !== 'roll' ? {
|
|
53
53
|
type: 'measurement-undocumented',
|
|
54
54
|
note: 'No measuring software is recorded for this copy.'
|
|
55
55
|
} : undefined;
|
|
@@ -2,6 +2,7 @@ import { certaintyOf, idOf, idsOf } from "../model/Assumption.js";
|
|
|
2
2
|
import { insertedBy } from "../model/Version.js";
|
|
3
3
|
import { lineageOf } from "./stemma.js";
|
|
4
4
|
import { copyOfFeature, versionIn } from "../lookup/lookup.js";
|
|
5
|
+
import { perState } from "../lookup/perState.js";
|
|
5
6
|
/** The copies whose features carry any of the symbols. */
|
|
6
7
|
const copiesCarrying = (edition, symbols) => new Set(symbols
|
|
7
8
|
.flatMap(symbol => idsOf(symbol.carriers))
|
|
@@ -17,19 +18,21 @@ const carriagesIn = (edition) => edition.versions.flatMap(version => {
|
|
|
17
18
|
.filter(copy => carrying.has(copy.id))
|
|
18
19
|
.map((copy) => ({ copy: copy.id, version: version.id, depth }));
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
+
/** What the copies' features attest, worked out once for each state of the edition. */
|
|
22
|
+
const carriersIn = perState((edition) => {
|
|
21
23
|
const carriages = carriagesIn(edition);
|
|
24
|
+
const byVersion = Map.groupBy(carriages, carriage => carriage.version);
|
|
22
25
|
return {
|
|
23
26
|
copiesOf: new Map(edition.versions.map(version => [
|
|
24
27
|
version.id,
|
|
25
|
-
|
|
28
|
+
(byVersion.get(version.id) ?? []).map(carriage => carriage.copy)
|
|
26
29
|
])),
|
|
27
30
|
// Written in order of depth, so a copy's latest carriage is set last and stands.
|
|
28
31
|
latestOf: new Map([...carriages]
|
|
29
32
|
.sort((one, other) => one.depth - other.depth)
|
|
30
33
|
.map(carriage => [carriage.copy, carriage.version]))
|
|
31
34
|
};
|
|
32
|
-
};
|
|
35
|
+
});
|
|
33
36
|
/**
|
|
34
37
|
* The versions a copy's features carry at first hand: for every copy,
|
|
35
38
|
* the latest state it bears. A version left out is reached only through
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { featuresOf, isTear, tearsOf } from "../model/RollCopy.js";
|
|
2
2
|
import { defaultTrackerBar } from "../systems/index.js";
|
|
3
3
|
import { add, mm, subtract, track } from "../model/Quantity.js";
|
|
4
|
+
import { partitionPoint } from "../shared/sorted.js";
|
|
4
5
|
/** A place on the copy's own paper, on the edition's axis. */
|
|
5
6
|
export const toAxis = (alignment) => (place) => alignment ? mm((place + alignment.shift.horizontal) * alignment.scale) : place;
|
|
6
7
|
/** A place on the edition's axis, on the copy's own paper. */
|
|
@@ -217,12 +218,41 @@ const noteOnsets = (features, bar) => features
|
|
|
217
218
|
.map(meaning => ({ pitch: meaning.pitch, at: feature.horizontal.from }));
|
|
218
219
|
})
|
|
219
220
|
.sort(byPlace);
|
|
221
|
+
/**
|
|
222
|
+
* The middle one of the values, the upper of the two where their number
|
|
223
|
+
* is even. It is selected rather than the values sorted (Hoare's FIND,
|
|
224
|
+
* as Wirth writes it): a long roll gives over a million slopes, and only
|
|
225
|
+
* the one in the middle is wanted. The values are reordered where they
|
|
226
|
+
* lie.
|
|
227
|
+
*/
|
|
220
228
|
const median = (values) => {
|
|
221
|
-
const
|
|
222
|
-
|
|
229
|
+
const middle = values.length >> 1;
|
|
230
|
+
let low = 0;
|
|
231
|
+
let high = values.length - 1;
|
|
232
|
+
while (low < high) {
|
|
233
|
+
const pivot = values[middle];
|
|
234
|
+
let i = low;
|
|
235
|
+
let j = high;
|
|
236
|
+
while (i <= j) {
|
|
237
|
+
while (values[i] < pivot)
|
|
238
|
+
i++;
|
|
239
|
+
while (pivot < values[j])
|
|
240
|
+
j--;
|
|
241
|
+
if (i <= j) {
|
|
242
|
+
const swapped = values[i];
|
|
243
|
+
values[i] = values[j];
|
|
244
|
+
values[j] = swapped;
|
|
245
|
+
i++;
|
|
246
|
+
j--;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (j < middle)
|
|
250
|
+
low = i;
|
|
251
|
+
if (middle < i)
|
|
252
|
+
high = j;
|
|
253
|
+
}
|
|
254
|
+
return values[middle] ?? 0;
|
|
223
255
|
};
|
|
224
|
-
/** The slope of the line through two matches, or nothing where they share a place on the copy. */
|
|
225
|
-
const slopeBetween = (m, n) => n.a === m.a ? [] : [(n.b - m.b) / (n.a - m.a)];
|
|
226
256
|
/** Matches beyond this are thinned before the slopes are taken, whose number grows with the square. */
|
|
227
257
|
const SLOPE_SAMPLE = 1500;
|
|
228
258
|
/**
|
|
@@ -234,25 +264,27 @@ const SLOPE_SAMPLE = 1500;
|
|
|
234
264
|
const robustLine = (matches) => {
|
|
235
265
|
const step = Math.max(1, Math.ceil(matches.length / SLOPE_SAMPLE));
|
|
236
266
|
const sample = matches.filter((_, i) => i % step === 0);
|
|
237
|
-
const slopes = sample.
|
|
238
|
-
|
|
267
|
+
const slopes = new Float64Array(sample.length * (sample.length - 1) / 2);
|
|
268
|
+
let count = 0;
|
|
269
|
+
sample.forEach((m, i) => {
|
|
270
|
+
for (let j = i + 1; j < sample.length; j++) {
|
|
271
|
+
const n = sample[j];
|
|
272
|
+
// Two matches at one place on the copy give no slope.
|
|
273
|
+
if (n.a !== m.a)
|
|
274
|
+
slopes[count++] = (n.b - m.b) / (n.a - m.a);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
if (count === 0)
|
|
239
278
|
return undefined;
|
|
240
|
-
const slope = median(slopes);
|
|
241
|
-
return { slope, intercept: median(
|
|
279
|
+
const slope = median(slopes.subarray(0, count));
|
|
280
|
+
return { slope, intercept: median(Float64Array.from(matches, m => m.b - slope * m.a)) };
|
|
242
281
|
};
|
|
243
282
|
const placed = (line, a) => line.slope * a + line.intercept;
|
|
244
283
|
const residualOf = (line, match) => match.b - placed(line, match.a);
|
|
245
284
|
/** Pitches in a row that pin down a place on a roll, provided the row occurs once. */
|
|
246
285
|
const ANCHOR_LENGTH = 6;
|
|
247
286
|
/** Every run of `length` pitches, keyed by the run, with the onsets it starts at. */
|
|
248
|
-
const runsOf = (onsets, length) =>
|
|
249
|
-
const runs = new Map();
|
|
250
|
-
onsets.slice(0, Math.max(0, onsets.length - length + 1)).forEach((start, i) => {
|
|
251
|
-
const key = onsets.slice(i, i + length).map(onset => onset.pitch).join(',');
|
|
252
|
-
runs.set(key, [...(runs.get(key) ?? []), start]);
|
|
253
|
-
});
|
|
254
|
-
return runs;
|
|
255
|
-
};
|
|
287
|
+
const runsOf = (onsets, length) => Map.groupBy(onsets.slice(0, Math.max(0, onsets.length - length + 1)), (_, i) => onsets.slice(i, i + length).map(onset => onset.pitch).join(','));
|
|
256
288
|
/** The places a run of pitches found once on each roll ties together. */
|
|
257
289
|
const anchors = (a, b) => {
|
|
258
290
|
const runsB = runsOf(b, ANCHOR_LENGTH);
|
|
@@ -277,13 +309,16 @@ const pairedNearestFirst = (candidates) => {
|
|
|
277
309
|
}
|
|
278
310
|
return matches;
|
|
279
311
|
};
|
|
312
|
+
/** Widens the window by a hair before it is searched, so that rounding in its bounds cannot leave out what the distance admits. */
|
|
313
|
+
const WINDOW_SLACK = 1e-9;
|
|
280
314
|
/** Each onset of A paired with the nearest onset of B of its pitch within the window around where the line puts it. */
|
|
281
315
|
const nearestMatches = (a, b, line, window) => {
|
|
282
|
-
const bByPitch = new Map();
|
|
283
|
-
b.forEach(onset => bByPitch.set(onset.pitch, [...(bByPitch.get(onset.pitch) ?? []), onset]));
|
|
284
316
|
const candidates = a.flatMap(onset => {
|
|
285
317
|
const expected = placed(line, onset.at);
|
|
286
|
-
|
|
318
|
+
const ofPitch = b.get(onset.pitch) ?? [];
|
|
319
|
+
const first = partitionPoint(ofPitch, other => other.at < expected - window - WINDOW_SLACK);
|
|
320
|
+
const end = partitionPoint(ofPitch, other => other.at <= expected + window + WINDOW_SLACK);
|
|
321
|
+
return ofPitch.slice(first, end)
|
|
287
322
|
.map(other => ({ distance: Math.abs(other.at - expected), a: onset, b: other }))
|
|
288
323
|
.filter(candidate => candidate.distance <= window);
|
|
289
324
|
});
|
|
@@ -341,5 +376,5 @@ export function alignFeatures(rollA, rollB, barA = defaultTrackerBar, barB = bar
|
|
|
341
376
|
const coarse = robustLine(anchors(a, b));
|
|
342
377
|
if (!coarse)
|
|
343
378
|
return undefined;
|
|
344
|
-
return resultOf(WINDOWS.reduce(settled(a, b), { line: coarse, matches: [] }));
|
|
379
|
+
return resultOf(WINDOWS.reduce(settled(a, Map.groupBy(b, onset => onset.pitch)), { line: coarse, matches: [] }));
|
|
345
380
|
}
|
|
@@ -66,7 +66,7 @@ export interface ScatterOptions {
|
|
|
66
66
|
groupOf?: Grouping;
|
|
67
67
|
/** The width of the histogram's bins. */
|
|
68
68
|
binWidth?: Millimeters;
|
|
69
|
-
/** The tolerance the
|
|
69
|
+
/** The tolerance the derivation states at present, against which each departure is reported as still admitted or not. */
|
|
70
70
|
stated?: CollationTolerance;
|
|
71
71
|
}
|
|
72
72
|
/** A reading the calculated tolerance does not admit. */
|
|
@@ -90,7 +90,7 @@ export interface Departure {
|
|
|
90
90
|
*/
|
|
91
91
|
separatedBy: BothEnds<boolean>;
|
|
92
92
|
/**
|
|
93
|
-
* Whether the tolerance the
|
|
93
|
+
* Whether the tolerance the derivation states at present still admits
|
|
94
94
|
* it. Absent where none was given to compare against.
|
|
95
95
|
*
|
|
96
96
|
* This sees one direction only. A departure is by definition a
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -80,23 +80,19 @@ const changing = (copyId) => (act) => {
|
|
|
80
80
|
return { ...act, diminished: { '@id': copyId } };
|
|
81
81
|
return act;
|
|
82
82
|
};
|
|
83
|
+
/** The copy with what each of its acts changed stated. */
|
|
84
|
+
const withChangesOf = (copy) => isRecord(copy) && Array.isArray(copy.modifications)
|
|
85
|
+
? { ...copy, modifications: copy.modifications.map(changing(copy['@id'])) }
|
|
86
|
+
: copy;
|
|
83
87
|
/**
|
|
84
88
|
* The document with what each act changed stated. Which of P110
|
|
85
89
|
* augmented and P112 diminished applies depends on the class of the
|
|
86
90
|
* act, which no property chain in OWL 2 RL can test, so the export
|
|
87
91
|
* states it. What a copy or a patch bears is left to a reasoner (see
|
|
88
|
-
* the entailments in reo.ttl).
|
|
92
|
+
* the entailments in reo.ttl). The copies are the only nodes that are
|
|
93
|
+
* modified, and the edition holds them in its list of copies alone.
|
|
89
94
|
*/
|
|
90
|
-
const withChanges = (
|
|
91
|
-
if (Array.isArray(value))
|
|
92
|
-
return value.map(withChanges);
|
|
93
|
-
if (!isRecord(value))
|
|
94
|
-
return value;
|
|
95
|
-
const node = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, withChanges(child)]));
|
|
96
|
-
return node['@type'] === 'RollCopy' && Array.isArray(node.modifications)
|
|
97
|
-
? { ...node, modifications: node.modifications.map(changing(node['@id'])) }
|
|
98
|
-
: node;
|
|
99
|
-
};
|
|
95
|
+
const withChanges = (edition) => Array.isArray(edition.copies) ? { ...edition, copies: edition.copies.map(withChangesOf) } : edition;
|
|
100
96
|
/** Terms the context sets to null, which say nothing when the edition is read as RDF. */
|
|
101
97
|
const silentTerms = new Set(Object.entries(context['@context'])
|
|
102
98
|
.filter(([, definition]) => definition === null)
|
|
@@ -120,21 +116,24 @@ const quote = (subject, key, reference, listed) => {
|
|
|
120
116
|
const { '@annotation': { '@id': annotation, ...about }, ...object } = reference;
|
|
121
117
|
return { '@id': { '@id': subject, [key]: listed ? [object] : object }, annotation, ...about };
|
|
122
118
|
};
|
|
123
|
-
/** The value a node states under a key, less the doubted references,
|
|
124
|
-
const
|
|
119
|
+
/** The value a node states under a key, less the doubted references, which go to the quoted. */
|
|
120
|
+
const unquoted = (subject, key, value, quoted) => {
|
|
125
121
|
if (Array.isArray(value)) {
|
|
126
|
-
return {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
return value.filter(item => {
|
|
123
|
+
if (!isDoubtedReference(item))
|
|
124
|
+
return true;
|
|
125
|
+
quoted.push(quote(subject, key, item, true));
|
|
126
|
+
return false;
|
|
127
|
+
});
|
|
130
128
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
if (!isDoubtedReference(value))
|
|
130
|
+
return value;
|
|
131
|
+
quoted.push(quote(subject, key, value, false));
|
|
132
|
+
return undefined;
|
|
134
133
|
};
|
|
135
134
|
/**
|
|
136
135
|
* The document with every doubted reference taken off the node that
|
|
137
|
-
* states it, and quoted instead.
|
|
136
|
+
* states it, and quoted instead, in the order the document states them.
|
|
138
137
|
*
|
|
139
138
|
* An `@annotation` in JSON-LD-star states the triple it annotates and
|
|
140
139
|
* then says something about it, so a statement the edition holds
|
|
@@ -142,32 +141,28 @@ const quotingValue = (subject, key, value) => {
|
|
|
142
141
|
* between nodes are quoted, since only they can be put back where they
|
|
143
142
|
* stood; a doubted date or attribution stays annotated in place.
|
|
144
143
|
*/
|
|
145
|
-
const withDoubtedReferencesQuoted = (value) => {
|
|
146
|
-
if (Array.isArray(value))
|
|
147
|
-
|
|
148
|
-
return { node: quotings.map(({ node }) => node), quoted: quotings.flatMap(({ quoted }) => quoted) };
|
|
149
|
-
}
|
|
144
|
+
const withDoubtedReferencesQuoted = (value, quoted) => {
|
|
145
|
+
if (Array.isArray(value))
|
|
146
|
+
return value.map(item => withDoubtedReferencesQuoted(item, quoted));
|
|
150
147
|
if (!isRecord(value))
|
|
151
|
-
return
|
|
148
|
+
return value;
|
|
152
149
|
const subject = value['@id'];
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
const node = {};
|
|
151
|
+
Object.entries(value).forEach(([key, child]) => {
|
|
152
|
+
const stated = typeof subject === 'string' && !key.startsWith('@') ? unquoted(subject, key, child, quoted) : child;
|
|
153
|
+
const below = withDoubtedReferencesQuoted(stated, quoted);
|
|
154
|
+
if (below !== undefined)
|
|
155
|
+
node[key] = below;
|
|
159
156
|
});
|
|
160
|
-
return
|
|
161
|
-
node: Object.fromEntries(entries.filter(({ node }) => node !== undefined).map(({ key, node }) => [key, node])),
|
|
162
|
-
quoted: entries.flatMap(({ quoted }) => quoted)
|
|
163
|
-
};
|
|
157
|
+
return node;
|
|
164
158
|
};
|
|
165
159
|
/** The edition with each copy's features and tears at the copy's own places, as a document holds them. */
|
|
166
160
|
const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
|
|
167
161
|
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
168
162
|
: edition;
|
|
169
163
|
export const asJsonLd = (edition) => {
|
|
170
|
-
const
|
|
164
|
+
const quoted = [];
|
|
165
|
+
const node = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))), quoted);
|
|
171
166
|
// The context is the export's own; one carried in from an import must not override it.
|
|
172
167
|
const { base, '@context': carried, ...rest } = node;
|
|
173
168
|
return {
|
package/lib/io/migrate.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
1
2
|
import { systemIdIn, systemOf, translationBetween } from "../systems/TrackerBar.js";
|
|
2
3
|
import { trackerBars } from "../systems/index.js";
|
|
3
4
|
import { welteT100 } from "../systems/welteT100/bar.js";
|
|
4
5
|
import { isDateString } from "../shared/utils.js";
|
|
6
|
+
import { mapped, withValues } from "../ops/immutable.js";
|
|
5
7
|
const retiredVersionTypes = new Set(['edition', 'unicum']);
|
|
6
8
|
/**
|
|
7
9
|
* The vocabulary the steps below were written against, as it stood in
|
|
@@ -37,10 +39,10 @@ const referenceKeys = ['alignedWith', 'pairedWith', 'basedOn'];
|
|
|
37
39
|
* The five type terms that were capitalised while the rest of the
|
|
38
40
|
* vocabulary was not, under the keys that carried them then.
|
|
39
41
|
*/
|
|
40
|
-
const lowerCasedTerms = {
|
|
42
|
+
const lowerCasedTerms = Object.entries({
|
|
41
43
|
method: { Print: 'print', Handwriting: 'handwriting', Stamp: 'stamp' },
|
|
42
44
|
material: { Paper: 'paper', Tape: 'tape' }
|
|
43
|
-
};
|
|
45
|
+
});
|
|
44
46
|
const named = (name) => ({ name, sameAs: [] });
|
|
45
47
|
const withRenamedKeys = (node) => Object.keys(node).some(key => Object.hasOwn(renamedKeys, key))
|
|
46
48
|
? Object.fromEntries(Object.entries(node).map(([key, value]) => [renamedKeys[key] ?? key, value]))
|
|
@@ -82,7 +84,7 @@ const withoutVersionSiglum = (node) => {
|
|
|
82
84
|
const { siglum: _labelled, ...rest } = node;
|
|
83
85
|
return rest;
|
|
84
86
|
};
|
|
85
|
-
const withLowerCaseTerms = (node) =>
|
|
87
|
+
const withLowerCaseTerms = (node) => lowerCasedTerms.reduce((result, [key, terms]) => {
|
|
86
88
|
const lowered = terms[result[key]];
|
|
87
89
|
return lowered ? { ...result, [key]: lowered } : result;
|
|
88
90
|
}, node);
|
|
@@ -450,26 +452,68 @@ const withTimeSpanDates = (node) => {
|
|
|
450
452
|
const { '@value': within, '@type': _typed, ...rest } = node;
|
|
451
453
|
return { ...rest, within };
|
|
452
454
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
455
|
+
/** The values a copy's one `measuredBy` stood for, of those the edition exports: what the analysis it named had given. */
|
|
456
|
+
const measuredByCovered = ['dimensions', 'holeSeparation'];
|
|
457
|
+
/** A day written as a date, of a date written with or without its time. */
|
|
458
|
+
const dayOf = (date) => typeof date === 'string' && /^\d{4}-\d{2}-\d{2}/.test(date) ? date.slice(0, 10) : undefined;
|
|
459
|
+
/**
|
|
460
|
+
* A copy once named one program for all its measurements, beside values
|
|
461
|
+
* that could not say what took them. Each value the edition exports now
|
|
462
|
+
* carries the measurement it rests on, so the program, its version and
|
|
463
|
+
* the day go into the reasons of the dimensions and the hole separation,
|
|
464
|
+
* which the analysis it named had given. The scan resolution is read
|
|
465
|
+
* off the file rather than measured, and the margins are not exported;
|
|
466
|
+
* nothing is said of them. The ids are the copy's, so that migrating a
|
|
467
|
+
* file twice writes the same document.
|
|
468
|
+
*/
|
|
469
|
+
const withMeasuredByInReasons = (node) => {
|
|
470
|
+
const measurements = node?.measurements;
|
|
471
|
+
if (!measurements || typeof measurements !== 'object' || !Object.hasOwn(measurements, 'measuredBy'))
|
|
472
|
+
return node;
|
|
473
|
+
const { measuredBy, ...rest } = measurements;
|
|
474
|
+
const day = dayOf(measuredBy?.date);
|
|
475
|
+
const reason = measuredBy?.software && {
|
|
476
|
+
'@type': 'measurement',
|
|
477
|
+
software: [{ name: measuredBy.software, ...(measuredBy.version && { version: measuredBy.version }) }],
|
|
478
|
+
...(day && { date: { within: day } })
|
|
479
|
+
};
|
|
480
|
+
const owner = typeof node['@id'] === 'string' ? plainCopyId(node['@id']) : v4();
|
|
481
|
+
const withReason = (key, value) => {
|
|
482
|
+
if (!reason || !value || typeof value !== 'object')
|
|
483
|
+
return value;
|
|
484
|
+
const annotation = value['@annotation'];
|
|
485
|
+
if (annotation?.belief) {
|
|
486
|
+
return { ...value, '@annotation': { ...annotation, belief: { ...annotation.belief, reasons: [...(annotation.belief.reasons ?? []), reason] } } };
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
...value,
|
|
490
|
+
'@annotation': {
|
|
491
|
+
'@id': `${owner}-${key}-annotation`,
|
|
492
|
+
belief: { '@type': 'belief', '@id': `${owner}-${key}-belief`, certainty: 'true', reasons: [reason] }
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
return {
|
|
497
|
+
...node,
|
|
498
|
+
measurements: {
|
|
499
|
+
...rest,
|
|
500
|
+
...Object.fromEntries(measuredByCovered
|
|
501
|
+
.filter(key => Object.hasOwn(rest, key))
|
|
502
|
+
.map(key => [key, withReason(key, rest[key])]))
|
|
503
|
+
}
|
|
504
|
+
};
|
|
467
505
|
};
|
|
506
|
+
const nodeSteps = [withRenamedKeys, withRenamedEditType, withTypology, withRenamedType, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
|
|
507
|
+
withPerforator, withProductionNodes, withTypedPerforator, withScale, withoutOps, withDerivationList, withReadingKind, withTimeSpanDates,
|
|
508
|
+
withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withOwnPlaces, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType,
|
|
509
|
+
withMeasuredByInReasons];
|
|
510
|
+
const migrateNode = (node) => nodeSteps.reduce((result, step) => step(result), node);
|
|
511
|
+
/** The value with every node in it migrated, a node before its children; the very same value where nothing changed. */
|
|
468
512
|
const walk = (value) => {
|
|
469
513
|
if (Array.isArray(value))
|
|
470
|
-
return
|
|
514
|
+
return mapped(value, walk);
|
|
471
515
|
if (value && typeof value === 'object')
|
|
472
|
-
return
|
|
516
|
+
return withValues(migrateNode(value), walk);
|
|
473
517
|
return value;
|
|
474
518
|
};
|
|
475
519
|
/** The production of a copy, under whichever of its two names it carries. */
|
|
@@ -576,18 +620,21 @@ const statesNoTolerance = (version) => isSingleDerivation(version.basedOn) && !v
|
|
|
576
620
|
* The collation tolerance was the edition's before it was stated on
|
|
577
621
|
* each derivation. An edition written then collated every version at
|
|
578
622
|
* that one value, so it is written onto every derivation that gives
|
|
579
|
-
* none of its own.
|
|
623
|
+
* none of its own, and the edition states it no longer.
|
|
580
624
|
*/
|
|
581
625
|
const withDerivationTolerance = (edition) => {
|
|
582
|
-
|
|
583
|
-
const versions = Array.isArray(edition.versions) ? edition.versions : [];
|
|
584
|
-
if (!collationTolerance || !versions.some(statesNoTolerance))
|
|
626
|
+
if (!edition.creation || !Object.hasOwn(edition.creation, 'collationTolerance'))
|
|
585
627
|
return edition;
|
|
628
|
+
const { collationTolerance, ...creation } = edition.creation;
|
|
629
|
+
const versions = listAt(edition, 'versions');
|
|
586
630
|
return {
|
|
587
631
|
...edition,
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
: version)
|
|
632
|
+
creation,
|
|
633
|
+
...(collationTolerance && versions && {
|
|
634
|
+
versions: versions.map(version => statesNoTolerance(version)
|
|
635
|
+
? { ...version, basedOn: { ...version.basedOn, collationTolerance } }
|
|
636
|
+
: version)
|
|
637
|
+
})
|
|
591
638
|
};
|
|
592
639
|
};
|
|
593
640
|
/** Release 0.58.0 stated a revision of the format, which the format does not state. */
|
package/lib/model/Agent.d.ts
CHANGED
|
@@ -78,4 +78,21 @@ export interface Place extends Named {
|
|
|
78
78
|
* @see crm:E55 Type
|
|
79
79
|
*/
|
|
80
80
|
export type Concept = (WithId & Partial<Named>) | (Named & Partial<WithId>);
|
|
81
|
+
/**
|
|
82
|
+
* Software that captured a copy or measured it: the transcriber that
|
|
83
|
+
* read a recording into notes, the emulator that wrote a MIDI file, the
|
|
84
|
+
* program that found the holes on a scan or aligned two copies.
|
|
85
|
+
* @see crmdig:D14 Software
|
|
86
|
+
*/
|
|
87
|
+
export interface Software {
|
|
88
|
+
/**
|
|
89
|
+
* @see rdfs:label
|
|
90
|
+
*/
|
|
91
|
+
name: string;
|
|
92
|
+
/**
|
|
93
|
+
* The release, the commit, or the model checkpoint of a transcriber.
|
|
94
|
+
* @see owl:versionInfo
|
|
95
|
+
*/
|
|
96
|
+
version?: string;
|
|
97
|
+
}
|
|
81
98
|
export {};
|