linked-rolls 0.62.0 → 0.64.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 +26 -7
- package/lib/analysis/constraints.d.ts +2 -2
- package/lib/analysis/constraints.js +30 -21
- package/lib/analysis/ownPaper.d.ts +11 -16
- package/lib/analysis/ownPaper.js +13 -31
- package/lib/analysis/paper.d.ts +129 -0
- package/lib/analysis/paper.js +260 -0
- package/lib/analysis/reservations.d.ts +2 -2
- package/lib/analysis/reservations.js +6 -6
- package/lib/analysis/sigla.d.ts +4 -5
- package/lib/analysis/sigla.js +4 -4
- package/lib/analysis/stemma.d.ts +22 -0
- package/lib/analysis/stemma.js +38 -0
- package/lib/analysis/text.d.ts +31 -0
- package/lib/analysis/text.js +62 -0
- package/lib/analysis/witnesses.d.ts +5 -5
- package/lib/analysis/witnesses.js +22 -20
- package/lib/collation/alignment.d.ts +46 -8
- package/lib/collation/alignment.js +137 -47
- package/lib/collation/scatter.d.ts +4 -4
- package/lib/collation/scatter.js +15 -13
- package/lib/emulation/Emulation.d.ts +7 -7
- package/lib/emulation/Emulation.js +29 -27
- package/lib/emulation/negotiation.d.ts +2 -2
- package/lib/emulation/negotiation.js +3 -2
- package/lib/index.d.ts +6 -2
- package/lib/index.js +6 -2
- package/lib/io/asJsonLd.js +16 -1
- package/lib/io/importJsonLd.d.ts +5 -0
- package/lib/io/importJsonLd.js +18 -1
- package/lib/io/migrate.js +94 -2
- package/lib/lookup/lookup.d.ts +32 -0
- package/lib/lookup/lookup.js +40 -0
- package/lib/lookup/paths.d.ts +11 -0
- package/lib/lookup/paths.js +78 -0
- package/lib/lookup/perState.d.ts +32 -0
- package/lib/lookup/perState.js +63 -0
- package/lib/model/Assumption.d.ts +21 -9
- package/lib/model/Edit.d.ts +4 -0
- package/lib/model/Edition.d.ts +19 -0
- package/lib/model/Edition.js +9 -1
- package/lib/model/Quantity.d.ts +4 -1
- package/lib/model/Quantity.js +1 -0
- package/lib/model/RollCopy.d.ts +144 -48
- package/lib/model/RollCopy.js +9 -6
- package/lib/model/Version.d.ts +7 -1
- package/lib/ops/beliefs.d.ts +1 -1
- package/lib/ops/beliefs.js +1 -1
- package/lib/ops/collation.d.ts +3 -4
- package/lib/ops/collation.js +25 -23
- package/lib/ops/commands.d.ts +4 -5
- package/lib/ops/commands.js +10 -10
- package/lib/ops/copies.d.ts +47 -10
- package/lib/ops/copies.js +92 -26
- package/lib/ops/draft.d.ts +12 -12
- package/lib/ops/draft.js +11 -13
- package/lib/ops/edits.d.ts +1 -2
- package/lib/ops/edits.js +13 -10
- package/lib/ops/features.d.ts +2 -2
- package/lib/ops/features.js +3 -2
- package/lib/ops/index.d.ts +2 -2
- package/lib/ops/index.js +7 -1
- package/lib/ops/versions.d.ts +2 -3
- package/lib/ops/versions.js +6 -5
- package/lib/readers/stanfordAton.js +28 -1
- package/lib/schema.json +185 -26
- package/lib/spec/context.json +33 -1
- package/lib/systems/ReproducingSystem.d.ts +7 -6
- package/package.json +1 -1
- package/lib/view/EditionView.d.ts +0 -103
- package/lib/view/EditionView.js +0 -329
|
@@ -1,56 +1,126 @@
|
|
|
1
|
-
import { featuresOf } from "../model/RollCopy.js";
|
|
1
|
+
import { featuresOf, isTear, tearsOf } from "../model/RollCopy.js";
|
|
2
2
|
import { defaultTrackerBar } from "../systems/index.js";
|
|
3
|
-
import { add, mm,
|
|
4
|
-
/**
|
|
5
|
-
const
|
|
6
|
-
|
|
3
|
+
import { add, mm, subtract, track } from "../model/Quantity.js";
|
|
4
|
+
/** A place on the copy's own paper, on the edition's axis. */
|
|
5
|
+
export const toAxis = (alignment) => (place) => alignment ? mm((place + alignment.shift.horizontal) * alignment.scale) : place;
|
|
6
|
+
/** A place on the edition's axis, on the copy's own paper. */
|
|
7
|
+
export const fromAxis = (alignment) => (place) => alignment ? mm(place / alignment.scale - alignment.shift.horizontal) : place;
|
|
8
|
+
const acrossToAxis = (alignment) => (position) => alignment ? track(position + alignment.shift.vertical) : position;
|
|
9
|
+
const acrossFromAxis = (alignment) => (position) => alignment ? track(position - alignment.shift.vertical) : position;
|
|
10
|
+
/** Both ends of a span taken through the mapping, the far end only where the span has one. */
|
|
11
|
+
const mapped = (span, through) => {
|
|
12
|
+
span.from = through(span.from);
|
|
7
13
|
if (span.to !== undefined)
|
|
8
|
-
span.to =
|
|
14
|
+
span.to = through(span.to);
|
|
9
15
|
};
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Where along the roll the copy states anything: its features, and its
|
|
18
|
+
* tears, which are measured as the features are. A tear states no track,
|
|
19
|
+
* so the shift across the roll leaves it where it is.
|
|
20
|
+
*/
|
|
21
|
+
const spansAlong = (copy) => [...featuresOf(copy), ...tearsOf(copy)].map(placed => placed.horizontal);
|
|
22
|
+
/** Puts the copy's own places onto the axis in place. */
|
|
23
|
+
const moveOnto = (alignment, copy) => {
|
|
24
|
+
spansAlong(copy).forEach(span => mapped(span, toAxis(alignment)));
|
|
25
|
+
featuresOf(copy).forEach(feature => mapped(feature.vertical, acrossToAxis(alignment)));
|
|
15
26
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
featuresOf(copy).forEach(feature => {
|
|
21
|
-
move(feature.horizontal, shift.horizontal);
|
|
22
|
-
move(feature.vertical, shift.vertical);
|
|
23
|
-
});
|
|
24
|
-
copy.measurements.shift = shift;
|
|
27
|
+
/** Takes the copy's places on the axis back to its own paper in place. */
|
|
28
|
+
const moveOff = (alignment, copy) => {
|
|
29
|
+
spansAlong(copy).forEach(span => mapped(span, fromAxis(alignment)));
|
|
30
|
+
featuresOf(copy).forEach(feature => mapped(feature.vertical, acrossFromAxis(alignment)));
|
|
25
31
|
};
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Carries the copy's features and tears from its own places onto the
|
|
34
|
+
* axis, and records how, in place of any alignment it had: that one is
|
|
35
|
+
* taken off first.
|
|
36
|
+
*/
|
|
37
|
+
export const applyAlignment = (alignment, copy) => {
|
|
38
|
+
revertAlignment(copy);
|
|
39
|
+
moveOnto(alignment, copy);
|
|
40
|
+
copy.measurements.alignment = alignment;
|
|
32
41
|
};
|
|
33
|
-
/** Takes the
|
|
34
|
-
export const
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
42
|
+
/** Takes the copy's features and tears back to its own places, as far as it was aligned. */
|
|
43
|
+
export const revertAlignment = (copy) => {
|
|
44
|
+
const alignment = copy.measurements.alignment;
|
|
45
|
+
if (!alignment)
|
|
37
46
|
return;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
move(feature.horizontal, reversed.horizontal);
|
|
41
|
-
move(feature.vertical, reversed.vertical);
|
|
42
|
-
});
|
|
43
|
-
delete copy.measurements.shift;
|
|
47
|
+
moveOff(alignment, copy);
|
|
48
|
+
delete copy.measurements.alignment;
|
|
44
49
|
};
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Places are written to the nanometre. Carried onto the axis and back,
|
|
52
|
+
* a place comes back a rounding error away from where it was, and a
|
|
53
|
+
* document saved again would change in its last digits every time.
|
|
54
|
+
* A scan resolves a tenth of a millimetre at best.
|
|
55
|
+
*/
|
|
56
|
+
const PLACES_PER_MM = 1e6;
|
|
57
|
+
const written = (place) => mm(Math.round(place * PLACES_PER_MM) / PLACES_PER_MM);
|
|
58
|
+
/**
|
|
59
|
+
* The copy with its features and tears at its own places, as a document
|
|
60
|
+
* holds them, and its alignment kept to say how they go onto the axis.
|
|
61
|
+
* The copy given is left as it is. A copy that is not aligned is the
|
|
62
|
+
* very same copy.
|
|
63
|
+
*/
|
|
64
|
+
export const atOwnPlaces = (copy) => {
|
|
65
|
+
const alignment = copy.measurements.alignment;
|
|
66
|
+
if (!alignment)
|
|
67
|
+
return copy;
|
|
68
|
+
const along = (place) => written(fromAxis(alignment)(place));
|
|
69
|
+
const across = acrossFromAxis(alignment);
|
|
70
|
+
const ownSpan = (span, through) => {
|
|
71
|
+
const own = { ...span };
|
|
72
|
+
mapped(own, through);
|
|
73
|
+
return own;
|
|
74
|
+
};
|
|
75
|
+
const ownFeature = (feature) => ({ ...feature, horizontal: ownSpan(feature.horizontal, along), vertical: ownSpan(feature.vertical, across) });
|
|
76
|
+
return {
|
|
77
|
+
...copy,
|
|
78
|
+
...(copy.production && {
|
|
79
|
+
production: {
|
|
80
|
+
...copy.production,
|
|
81
|
+
...(copy.production.produced && { produced: copy.production.produced.map(ownFeature) })
|
|
82
|
+
}
|
|
83
|
+
}),
|
|
84
|
+
modifications: copy.modifications.map(act => {
|
|
85
|
+
if (act.type === 'Alteration')
|
|
86
|
+
return { ...act, produced: act.produced.map(ownFeature) };
|
|
87
|
+
if (act.type === 'Attachment')
|
|
88
|
+
return { ...act, added: act.added.map(ownFeature) };
|
|
89
|
+
return act;
|
|
90
|
+
}),
|
|
91
|
+
conditions: copy.conditions.map(condition => isTear(condition)
|
|
92
|
+
? { ...condition, horizontal: ownSpan(condition.horizontal, along) }
|
|
93
|
+
: condition)
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Puts the features and tears of a copy read from a document, which
|
|
98
|
+
* holds them at the copy's own places, onto the axis. The copy given is
|
|
99
|
+
* changed.
|
|
100
|
+
*/
|
|
101
|
+
export const putOnAxis = (copy) => {
|
|
102
|
+
const alignment = copy.measurements.alignment;
|
|
103
|
+
if (alignment)
|
|
104
|
+
moveOnto(alignment, copy);
|
|
105
|
+
};
|
|
106
|
+
/** The copy's features at its own places, as it was read, whatever alignment it has. */
|
|
107
|
+
export const ownFeaturesOf = (copy) => {
|
|
108
|
+
const alignment = copy.measurements.alignment;
|
|
109
|
+
if (!alignment)
|
|
110
|
+
return featuresOf(copy);
|
|
111
|
+
return featuresOf(copy).map(feature => {
|
|
112
|
+
const own = { ...feature, horizontal: { ...feature.horizontal }, vertical: { ...feature.vertical } };
|
|
113
|
+
mapped(own.horizontal, fromAxis(alignment));
|
|
114
|
+
mapped(own.vertical, acrossFromAxis(alignment));
|
|
115
|
+
return own;
|
|
116
|
+
});
|
|
52
117
|
};
|
|
53
118
|
const chainsOf = (copy) => featuresOf(copy).filter(feature => feature.type === 'HoleChain');
|
|
119
|
+
/**
|
|
120
|
+
* The reader's extension as it lies on the axis. It is a length on the
|
|
121
|
+
* copy's own paper, which the alignment scales like any other.
|
|
122
|
+
*/
|
|
123
|
+
const extensionOnAxis = (extension, copy) => mm(extension * (copy.measurements.alignment?.scale ?? 1));
|
|
54
124
|
/**
|
|
55
125
|
* Takes the extension a pneumatic reader adds off the ends of the
|
|
56
126
|
* copy's chains of holes, and records how much was taken.
|
|
@@ -86,7 +156,10 @@ const chainsOf = (copy) => featuresOf(copy).filter(feature => feature.type === '
|
|
|
86
156
|
* on the feature, a reading of the chain, a smaller extension — and
|
|
87
157
|
* `shortenChains` throws rather than answer it.
|
|
88
158
|
*/
|
|
89
|
-
export const tooShortToShorten = (extension, copy, leaving = new Set()) =>
|
|
159
|
+
export const tooShortToShorten = (extension, copy, leaving = new Set()) => {
|
|
160
|
+
const onAxis = extensionOnAxis(extension, copy);
|
|
161
|
+
return chainsOf(copy).filter(chain => !leaving.has(chain.id) && chain.horizontal.to - chain.horizontal.from <= onAxis);
|
|
162
|
+
};
|
|
90
163
|
/**
|
|
91
164
|
* Takes the extension off, leaving the named chains as the reader gave
|
|
92
165
|
* them. Naming a chain is an editorial act and not a repair: it says
|
|
@@ -107,10 +180,11 @@ export const shortenChains = (extension, copy, leaving = new Set()) => {
|
|
|
107
180
|
+ `chain(s) of holes of copy ${copy.id}, which are no longer than it: `
|
|
108
181
|
+ `${tooShort.map(chain => chain.id).join(', ')}`);
|
|
109
182
|
}
|
|
183
|
+
const onAxis = extensionOnAxis(extension, copy);
|
|
110
184
|
const left = chainsOf(copy).filter(chain => leaving.has(chain.id)).map(chain => chain.id);
|
|
111
185
|
chainsOf(copy)
|
|
112
186
|
.filter(chain => !leaving.has(chain.id))
|
|
113
|
-
.forEach(chain => { chain.horizontal.to = subtract(chain.horizontal.to,
|
|
187
|
+
.forEach(chain => { chain.horizontal.to = subtract(chain.horizontal.to, onAxis); });
|
|
114
188
|
copy.measurements.readerExtension = { length: extension, ...(left.length > 0 && { leaving: left }) };
|
|
115
189
|
};
|
|
116
190
|
/** Puts the reader's extension back on the chains it was taken off, as far as one was taken off. */
|
|
@@ -118,12 +192,20 @@ export const revertShortening = (copy) => {
|
|
|
118
192
|
const taken = copy.measurements.readerExtension;
|
|
119
193
|
if (taken === undefined)
|
|
120
194
|
return;
|
|
195
|
+
const onAxis = extensionOnAxis(taken.length, copy);
|
|
121
196
|
const left = new Set(taken.leaving ?? []);
|
|
122
197
|
chainsOf(copy)
|
|
123
198
|
.filter(chain => !left.has(chain.id))
|
|
124
|
-
.forEach(chain => { chain.horizontal.to = add(chain.horizontal.to,
|
|
199
|
+
.forEach(chain => { chain.horizontal.to = add(chain.horizontal.to, onAxis); });
|
|
125
200
|
delete copy.measurements.readerExtension;
|
|
126
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* The method `alignFeatures` follows, as an alignment records it. Its
|
|
204
|
+
* revision changes when the method does and at no other release, so
|
|
205
|
+
* that an alignment found by an earlier method can be told from one
|
|
206
|
+
* that would be found again as it stands.
|
|
207
|
+
*/
|
|
208
|
+
export const ALIGNMENT_METHOD = { software: 'linked-rolls alignFeatures', version: '1' };
|
|
127
209
|
const byPlace = (x, y) => x.at - y.at || x.pitch - y.pitch;
|
|
128
210
|
/** The notes the bar reads off the features, in the order they pass it. */
|
|
129
211
|
const noteOnsets = (features, bar) => features
|
|
@@ -218,15 +300,23 @@ const settled = (a, b) => (fit, window) => {
|
|
|
218
300
|
const matches = nearestMatches(a, b, fit.line, window);
|
|
219
301
|
return { line: robustLine(matches) ?? fit.line, matches };
|
|
220
302
|
};
|
|
303
|
+
/** How far the places of the matches on the copy spread, as a standard deviation. */
|
|
304
|
+
const spreadOf = (matches) => {
|
|
305
|
+
const centre = matches.reduce((total, m) => total + m.a, 0) / matches.length;
|
|
306
|
+
return Math.sqrt(matches.reduce((total, m) => total + (m.a - centre) ** 2, 0) / matches.length);
|
|
307
|
+
};
|
|
221
308
|
const resultOf = ({ line, matches }) => {
|
|
222
309
|
if (matches.length < 2 || line.slope <= 0)
|
|
223
310
|
return undefined;
|
|
224
311
|
const squares = matches.reduce((total, m) => total + residualOf(line, m) ** 2, 0);
|
|
312
|
+
const residual = Math.sqrt(squares / matches.length);
|
|
313
|
+
const spread = spreadOf(matches);
|
|
225
314
|
return {
|
|
226
315
|
shift: mm(line.intercept / line.slope),
|
|
227
316
|
scale: line.slope,
|
|
228
317
|
matched: matches.length,
|
|
229
|
-
residual: mm(
|
|
318
|
+
residual: mm(residual),
|
|
319
|
+
scaleError: spread > 0 ? residual / (spread * Math.sqrt(matches.length)) : Infinity
|
|
230
320
|
};
|
|
231
321
|
};
|
|
232
322
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Belief } from "../model/Assumption.js";
|
|
2
2
|
import { BothEnds, CollationTolerance, Displacement } from "./Collation.js";
|
|
3
|
-
import { EditionView } from "../view/EditionView.js";
|
|
4
3
|
import { Millimeters } from "../model/Quantity.js";
|
|
5
4
|
import { Histogram, Spread, Tail } from "./statistics.js";
|
|
6
5
|
import { AnySymbol } from "../model/Symbol.js";
|
|
6
|
+
import { Edition } from "../model/Edition.js";
|
|
7
7
|
/**
|
|
8
8
|
* How far the copies of a roll disagree about where a symbol lies, and
|
|
9
9
|
* the collation tolerance that follows from it.
|
|
@@ -44,7 +44,7 @@ export interface Reading {
|
|
|
44
44
|
* A symbol only one side carries measures nothing and is passed over,
|
|
45
45
|
* which leaves out exactly the insertions and the deletions.
|
|
46
46
|
*/
|
|
47
|
-
export declare const readingsOf: (
|
|
47
|
+
export declare const readingsOf: (edition: Edition, symbols: readonly Readonly<AnySymbol>[], copies: ReadonlySet<string>) => Reading[];
|
|
48
48
|
/**
|
|
49
49
|
* What sample a reading belongs to. Notes and expressions are taken
|
|
50
50
|
* apart by default, since in the present material they scatter
|
|
@@ -166,7 +166,7 @@ export declare const departureThreshold: (n: number) => number;
|
|
|
166
166
|
*/
|
|
167
167
|
export declare const scatterOf: (readings: readonly Reading[], options?: ScatterOptions) => Scatter[];
|
|
168
168
|
/** How one copy's readings of a version's text scatter against the readings of the copies it is collated with. */
|
|
169
|
-
export declare const scatterOfCopy: (
|
|
169
|
+
export declare const scatterOfCopy: (edition: Edition, versionId: string, copyId: string, options?: ScatterOptions) => Scatter[];
|
|
170
170
|
/**
|
|
171
171
|
* Which side of a derivation a measurement was taken over: the version
|
|
172
172
|
* derived, or the version it is read against.
|
|
@@ -256,7 +256,7 @@ export interface Sides {
|
|
|
256
256
|
* neither side, which is the case where the edition has genuinely
|
|
257
257
|
* stopped saying and an editor has to.
|
|
258
258
|
*/
|
|
259
|
-
export declare const sidesOf: (
|
|
259
|
+
export declare const sidesOf: (edition: Edition, versionId: string) => Sides | undefined;
|
|
260
260
|
/**
|
|
261
261
|
* The belief a calculated tolerance rests on, to annotate the
|
|
262
262
|
* derivation's `collationTolerance` with: an inference from the scatter
|
package/lib/collation/scatter.js
CHANGED
|
@@ -4,12 +4,14 @@ import { mean, mm } from "../model/Quantity.js";
|
|
|
4
4
|
import { excessKurtosisOf, histogramOf, normalQuantile, spreadOf, standardise, tailOf } from "./statistics.js";
|
|
5
5
|
import { groupBy } from "../shared/utils.js";
|
|
6
6
|
import { deletedBy, insertedBy } from "../model/Version.js";
|
|
7
|
+
import { carriersOf, placedCarriersOf, snapshotOf } from "../analysis/text.js";
|
|
8
|
+
import { copyOfFeature, symbolsIn, versionIn } from "../lookup/lookup.js";
|
|
7
9
|
const placeOf = (carriers) => ({
|
|
8
10
|
from: mean(carriers.map(carrier => carrier.horizontal.from)),
|
|
9
11
|
to: mean(carriers.map(carrier => carrier.horizontal.to))
|
|
10
12
|
});
|
|
11
|
-
const sitsOn = (
|
|
12
|
-
const copy =
|
|
13
|
+
const sitsOn = (edition, copies) => (feature) => {
|
|
14
|
+
const copy = copyOfFeature(edition, feature.id);
|
|
13
15
|
return copy !== undefined && copies.has(copy.id);
|
|
14
16
|
};
|
|
15
17
|
/**
|
|
@@ -34,10 +36,10 @@ const sitsOn = (view, copies) => (feature) => {
|
|
|
34
36
|
* A symbol only one side carries measures nothing and is passed over,
|
|
35
37
|
* which leaves out exactly the insertions and the deletions.
|
|
36
38
|
*/
|
|
37
|
-
export const readingsOf = (
|
|
38
|
-
const tested = sitsOn(
|
|
39
|
+
export const readingsOf = (edition, symbols, copies) => {
|
|
40
|
+
const tested = sitsOn(edition, copies);
|
|
39
41
|
return symbols.flatMap((symbol) => {
|
|
40
|
-
const carriers =
|
|
42
|
+
const carriers = placedCarriersOf(edition, symbol);
|
|
41
43
|
const here = carriers.filter(tested);
|
|
42
44
|
const there = carriers.filter(carrier => !tested(carrier));
|
|
43
45
|
if (here.length === 0 || there.length === 0)
|
|
@@ -120,7 +122,7 @@ const scatterIn = (group, sample, { binWidth = DEFAULT_BIN_WIDTH, stated }) => {
|
|
|
120
122
|
export const scatterOf = (readings, options = {}) => [...groupBy(readings, reading => (options.groupOf ?? byWhatItIs)(reading.symbol))]
|
|
121
123
|
.flatMap(([group, sample]) => scatterIn(group, sample, options));
|
|
122
124
|
/** How one copy's readings of a version's text scatter against the readings of the copies it is collated with. */
|
|
123
|
-
export const scatterOfCopy = (
|
|
125
|
+
export const scatterOfCopy = (edition, versionId, copyId, options = {}) => scatterOf(readingsOf(edition, snapshotOf(edition, versionId), new Set([copyId])), options);
|
|
124
126
|
const covering = (windows) => {
|
|
125
127
|
const lowest = Math.min(...windows.map(window => window.offset - window.tolerance));
|
|
126
128
|
const highest = Math.max(...windows.map(window => window.offset + window.tolerance));
|
|
@@ -189,10 +191,10 @@ export const changesBetween = (readings, inForce, proposed) => ({
|
|
|
189
191
|
separated: readings.filter(({ displacement }) => admits(inForce, displacement) && !admits(proposed, displacement)),
|
|
190
192
|
merged: readings.filter(({ displacement }) => !admits(inForce, displacement) && admits(proposed, displacement))
|
|
191
193
|
});
|
|
192
|
-
const copiesBearing = (
|
|
194
|
+
const copiesBearing = (edition, symbols) => {
|
|
193
195
|
const tally = symbols.reduce((counts, symbol) => {
|
|
194
|
-
const bearers = new Set(
|
|
195
|
-
const copy =
|
|
196
|
+
const bearers = new Set(carriersOf(edition, symbol).flatMap(carrier => {
|
|
197
|
+
const copy = copyOfFeature(edition, carrier.id);
|
|
196
198
|
return copy ? [copy.id] : [];
|
|
197
199
|
}));
|
|
198
200
|
bearers.forEach(copy => counts.set(copy, (counts.get(copy) ?? 0) + 1));
|
|
@@ -219,13 +221,13 @@ const copiesBearing = (view, symbols) => {
|
|
|
219
221
|
* neither side, which is the case where the edition has genuinely
|
|
220
222
|
* stopped saying and an editor has to.
|
|
221
223
|
*/
|
|
222
|
-
export const sidesOf = (
|
|
223
|
-
const version =
|
|
224
|
+
export const sidesOf = (edition, versionId) => {
|
|
225
|
+
const version = versionIn(edition, versionId);
|
|
224
226
|
if (!version)
|
|
225
227
|
return undefined;
|
|
226
228
|
return {
|
|
227
|
-
child: copiesBearing(
|
|
228
|
-
parent: copiesBearing(
|
|
229
|
+
child: copiesBearing(edition, insertedBy(version)),
|
|
230
|
+
parent: copiesBearing(edition, symbolsIn(edition, deletedBy(version)))
|
|
229
231
|
};
|
|
230
232
|
};
|
|
231
233
|
const inMillimetres = (value) => `${value.toFixed(2)} mm`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { MidiFile } from "midifile-ts";
|
|
2
|
-
import { EditionView } from "../view/EditionView.js";
|
|
3
2
|
import type { TrackerBar } from "../systems/TrackerBar.js";
|
|
4
3
|
import { Version } from "../model/Version.js";
|
|
5
4
|
import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "../systems/ReproducingSystem.js";
|
|
6
5
|
import { Millimeters } from "../model/Quantity.js";
|
|
6
|
+
import { Edition } from "../model/Edition.js";
|
|
7
7
|
export type EmulationScope = {
|
|
8
8
|
/** Only notes whose onset lies within this span of the roll are played. */
|
|
9
9
|
range?: [Millimeters, Millimeters];
|
|
@@ -12,17 +12,17 @@ export type EmulationScope = {
|
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* The events moved to where their statements put them, the events given
|
|
15
|
-
* left as they were. The
|
|
15
|
+
* left as they were. The edition supplies the copies, whose measurements
|
|
16
16
|
* decide how far before or after its reference a command goes, and a
|
|
17
17
|
* punch diameter, or a millimetre, where no copy agrees with a statement.
|
|
18
18
|
*/
|
|
19
|
-
export declare const withPlacementsApplied: (
|
|
19
|
+
export declare const withPlacementsApplied: (edition: Edition, events: readonly NegotiatedEvent[]) => NegotiatedEvent[];
|
|
20
20
|
/**
|
|
21
21
|
* The commands of a version as the bar performs them, in order of place,
|
|
22
22
|
* within the scope and with the placements applied. A note plays only
|
|
23
23
|
* where its onset falls in the range; expressions play throughout.
|
|
24
24
|
*/
|
|
25
|
-
export declare const negotiatedEventsOf: (
|
|
25
|
+
export declare const negotiatedEventsOf: (edition: Edition, version: Readonly<Version>, bar: TrackerBar, { range }?: Pick<EmulationScope, 'range'>) => NegotiatedEvent[];
|
|
26
26
|
/** A version performed: what was negotiated, and what the system made of it. */
|
|
27
27
|
export interface Emulated {
|
|
28
28
|
/** The id of the version performed. */
|
|
@@ -36,7 +36,7 @@ export interface Emulated {
|
|
|
36
36
|
* Performs a version on a reproducing system: the symbols are negotiated
|
|
37
37
|
* into placed events and the system plays them. Nothing given is changed.
|
|
38
38
|
*/
|
|
39
|
-
export declare const emulate: <Options extends object>(system: ReproducingSystem<Options>, version: Readonly<Version>,
|
|
39
|
+
export declare const emulate: <Options extends object>(system: ReproducingSystem<Options>, version: Readonly<Version>, edition: Edition, options?: Options, { range, skipToFirstNote }?: EmulationScope) => Emulated;
|
|
40
40
|
/**
|
|
41
41
|
* The performed events as a MIDI file, in which every note and pedal step
|
|
42
42
|
* is labelled with the symbol it performs. The file names the system, the
|
|
@@ -57,8 +57,8 @@ export declare class Emulation<Options extends object> {
|
|
|
57
57
|
source?: string;
|
|
58
58
|
constructor(system: ReproducingSystem<Options>, options?: Options);
|
|
59
59
|
/** Moves the negotiated events to where their statements put them; see `withPlacementsApplied`. */
|
|
60
|
-
applyConstraints(
|
|
61
|
-
emulateVersion(version: Version,
|
|
60
|
+
applyConstraints(edition: Edition): void;
|
|
61
|
+
emulateVersion(version: Version, edition: Edition, scope?: EmulationScope): AnyPerformedRollFeature[];
|
|
62
62
|
findEventsPerforming(id: string): AnyPerformedRollFeature[];
|
|
63
63
|
asMIDI(): MidiFile;
|
|
64
64
|
}
|
|
@@ -5,25 +5,27 @@ import { add, mean, mm, seconds, subtract } from "../model/Quantity.js";
|
|
|
5
5
|
import { punchDiameterOf } from "../model/RollCopy.js";
|
|
6
6
|
import { toOwnPaperOf } from "../analysis/ownPaper.js";
|
|
7
7
|
import { negotiatedEventOf } from "./negotiation.js";
|
|
8
|
+
import { placedCarriersOf, snapshotOf } from "../analysis/text.js";
|
|
9
|
+
import { copyOfFeature, symbolIn } from "../lookup/lookup.js";
|
|
8
10
|
/** The punch diameter the edition's copies report, where any of them does. */
|
|
9
|
-
const meanPunchDiameterOf = (
|
|
10
|
-
const measured =
|
|
11
|
+
const meanPunchDiameterOf = (edition) => {
|
|
12
|
+
const measured = edition.copies
|
|
11
13
|
.map(punchDiameterOf)
|
|
12
14
|
.filter(value => value !== undefined);
|
|
13
15
|
return measured.length > 0 ? mean(measured) : undefined;
|
|
14
16
|
};
|
|
15
|
-
const propertiesOf = (
|
|
16
|
-
punchDiameter: meanPunchDiameterOf(
|
|
17
|
-
tempo:
|
|
18
|
-
toOwnPaper: toOwnPaperOf(
|
|
17
|
+
const propertiesOf = (edition, version) => ({
|
|
18
|
+
punchDiameter: meanPunchDiameterOf(edition),
|
|
19
|
+
tempo: edition.tempoAdjustment,
|
|
20
|
+
toOwnPaper: toOwnPaperOf(edition, version)
|
|
19
21
|
});
|
|
20
22
|
/** The onset a symbol has on each copy carrying it, by the copy's id, as the mean of its chains there. */
|
|
21
|
-
const onsetsByCopy = (
|
|
22
|
-
const symbol =
|
|
23
|
+
const onsetsByCopy = (edition, symbolId) => {
|
|
24
|
+
const symbol = symbolIn(edition, symbolId);
|
|
23
25
|
if (!symbol)
|
|
24
26
|
return new Map();
|
|
25
|
-
const onsets =
|
|
26
|
-
const copy =
|
|
27
|
+
const onsets = placedCarriersOf(edition, symbol).flatMap((carrier) => {
|
|
28
|
+
const copy = copyOfFeature(edition, carrier.id);
|
|
27
29
|
return copy ? [[copy.id, carrier.horizontal.from]] : [];
|
|
28
30
|
});
|
|
29
31
|
const copies = new Set(onsets.map(([copy]) => copy));
|
|
@@ -34,9 +36,9 @@ const onsetsByCopy = (view, symbolId) => {
|
|
|
34
36
|
* the follower on each copy carrying both, negative where the follower
|
|
35
37
|
* comes first there.
|
|
36
38
|
*/
|
|
37
|
-
const offsetsBetween = (
|
|
38
|
-
const references = onsetsByCopy(
|
|
39
|
-
return [...onsetsByCopy(
|
|
39
|
+
const offsetsBetween = (edition, followerId, referenceId) => {
|
|
40
|
+
const references = onsetsByCopy(edition, referenceId);
|
|
41
|
+
return [...onsetsByCopy(edition, followerId)].flatMap(([copy, onset]) => {
|
|
40
42
|
const reference = references.get(copy);
|
|
41
43
|
return reference === undefined ? [] : [subtract(onset, reference)];
|
|
42
44
|
});
|
|
@@ -91,13 +93,13 @@ const displacementsOf = (events, offsetsBetween, gap) => {
|
|
|
91
93
|
const earliestOf = (times) => times.length === 0 ? seconds(0) : times.reduce((soonest, at) => at < soonest ? at : soonest);
|
|
92
94
|
/**
|
|
93
95
|
* The events moved to where their statements put them, the events given
|
|
94
|
-
* left as they were. The
|
|
96
|
+
* left as they were. The edition supplies the copies, whose measurements
|
|
95
97
|
* decide how far before or after its reference a command goes, and a
|
|
96
98
|
* punch diameter, or a millimetre, where no copy agrees with a statement.
|
|
97
99
|
*/
|
|
98
|
-
export const withPlacementsApplied = (
|
|
99
|
-
const gap = meanPunchDiameterOf(
|
|
100
|
-
const offsets = (follower, reference) => offsetsBetween(
|
|
100
|
+
export const withPlacementsApplied = (edition, events) => {
|
|
101
|
+
const gap = meanPunchDiameterOf(edition) ?? mm(1);
|
|
102
|
+
const offsets = (follower, reference) => offsetsBetween(edition, follower, reference);
|
|
101
103
|
const displacements = displacementsOf(events, offsets, gap);
|
|
102
104
|
return events.map(event => {
|
|
103
105
|
const distance = displacements.get(event);
|
|
@@ -112,11 +114,11 @@ export const withPlacementsApplied = (view, events) => {
|
|
|
112
114
|
* within the scope and with the placements applied. A note plays only
|
|
113
115
|
* where its onset falls in the range; expressions play throughout.
|
|
114
116
|
*/
|
|
115
|
-
export const negotiatedEventsOf = (
|
|
117
|
+
export const negotiatedEventsOf = (edition, version, bar, { range } = {}) => {
|
|
116
118
|
const inScope = (event) => !range || event.type !== 'note' || (event.horizontal.from > range[0] && event.horizontal.from < range[1]);
|
|
117
|
-
return withPlacementsApplied(
|
|
119
|
+
return withPlacementsApplied(edition, snapshotOf(edition, version.id)
|
|
118
120
|
.filter(isCommand)
|
|
119
|
-
.map(symbol => negotiatedEventOf(
|
|
121
|
+
.map(symbol => negotiatedEventOf(edition, symbol, bar))
|
|
120
122
|
.filter(event => event !== null)
|
|
121
123
|
.filter(inScope));
|
|
122
124
|
};
|
|
@@ -124,11 +126,11 @@ export const negotiatedEventsOf = (view, version, bar, { range } = {}) => {
|
|
|
124
126
|
* Performs a version on a reproducing system: the symbols are negotiated
|
|
125
127
|
* into placed events and the system plays them. Nothing given is changed.
|
|
126
128
|
*/
|
|
127
|
-
export const emulate = (system, version,
|
|
128
|
-
const negotiated = negotiatedEventsOf(
|
|
129
|
+
export const emulate = (system, version, edition, options = system.defaultOptions, { range, skipToFirstNote = false } = {}) => {
|
|
130
|
+
const negotiated = negotiatedEventsOf(edition, version, system.trackerBar, { range });
|
|
129
131
|
if (negotiated.length === 0)
|
|
130
132
|
return { source: version.id, negotiated, events: [], curves: [] };
|
|
131
|
-
const performance = system.perform(negotiated, options, propertiesOf(
|
|
133
|
+
const performance = system.perform(negotiated, options, propertiesOf(edition, version));
|
|
132
134
|
const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
|
|
133
135
|
const origin = skipToFirstNote ? earliestOf(onsets) : seconds(0);
|
|
134
136
|
return {
|
|
@@ -262,11 +264,11 @@ export class Emulation {
|
|
|
262
264
|
this.options = options;
|
|
263
265
|
}
|
|
264
266
|
/** Moves the negotiated events to where their statements put them; see `withPlacementsApplied`. */
|
|
265
|
-
applyConstraints(
|
|
266
|
-
this.negotiatedEvents = withPlacementsApplied(
|
|
267
|
+
applyConstraints(edition) {
|
|
268
|
+
this.negotiatedEvents = withPlacementsApplied(edition, this.negotiatedEvents);
|
|
267
269
|
}
|
|
268
|
-
emulateVersion(version,
|
|
269
|
-
const emulated = emulate(this.system, version,
|
|
270
|
+
emulateVersion(version, edition, scope = {}) {
|
|
271
|
+
const emulated = emulate(this.system, version, edition, this.options, scope);
|
|
270
272
|
this.source = emulated.source;
|
|
271
273
|
this.negotiatedEvents = [...emulated.negotiated];
|
|
272
274
|
this.curves = emulated.curves;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { EditionView } from "../view/EditionView.js";
|
|
2
1
|
import type { Expression, Note } from "../model/Symbol.js";
|
|
3
2
|
import type { TrackerBar } from "../systems/TrackerBar.js";
|
|
4
3
|
import type { NegotiatedEvent } from "../systems/ReproducingSystem.js";
|
|
4
|
+
import { Edition } from "../model/Edition.js";
|
|
5
5
|
/**
|
|
6
6
|
* The symbol as a performance needs it: where it lies, and the
|
|
7
7
|
* position the performing bar reads it on.
|
|
@@ -11,4 +11,4 @@ import type { NegotiatedEvent } from "../systems/ReproducingSystem.js";
|
|
|
11
11
|
* green version still inherits cannot be performed on a green
|
|
12
12
|
* machine, and an edit has yet to say what took its place.
|
|
13
13
|
*/
|
|
14
|
-
export declare const negotiatedEventOf: (
|
|
14
|
+
export declare const negotiatedEventOf: (edition: Edition, symbol: Note | Expression, bar: TrackerBar) => NegotiatedEvent | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { placeOf } from "../analysis/text.js";
|
|
1
2
|
/**
|
|
2
3
|
* The symbol as a performance needs it: where it lies, and the
|
|
3
4
|
* position the performing bar reads it on.
|
|
@@ -7,8 +8,8 @@
|
|
|
7
8
|
* green version still inherits cannot be performed on a green
|
|
8
9
|
* machine, and an edit has yet to say what took its place.
|
|
9
10
|
*/
|
|
10
|
-
export const negotiatedEventOf = (
|
|
11
|
-
const horizontal =
|
|
11
|
+
export const negotiatedEventOf = (edition, symbol, bar) => {
|
|
12
|
+
const horizontal = placeOf(edition, symbol);
|
|
12
13
|
const position = bar.positionOf(symbol);
|
|
13
14
|
if (!horizontal || position === undefined)
|
|
14
15
|
return null;
|
package/lib/index.d.ts
CHANGED
|
@@ -28,11 +28,15 @@ export * from './model/procedures.js';
|
|
|
28
28
|
export * from './model/vocabulary.js';
|
|
29
29
|
export * from './analysis/reservations.js';
|
|
30
30
|
export * from './analysis/witnesses.js';
|
|
31
|
-
export { alignFeatures, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
|
|
31
|
+
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
|
|
32
32
|
export * from './collation/statistics.js';
|
|
33
33
|
export * from './model/Edition.js';
|
|
34
|
-
export * from './
|
|
34
|
+
export * from './lookup/lookup.js';
|
|
35
|
+
export * from './lookup/paths.js';
|
|
36
|
+
export * from './analysis/stemma.js';
|
|
37
|
+
export * from './analysis/text.js';
|
|
35
38
|
export * from './analysis/ownPaper.js';
|
|
39
|
+
export * from './analysis/paper.js';
|
|
36
40
|
export * from './emulation/negotiation.js';
|
|
37
41
|
export * from './collation/scatter.js';
|
|
38
42
|
export * from './ops/index.js';
|
package/lib/index.js
CHANGED
|
@@ -21,11 +21,15 @@ export * from './model/procedures.js';
|
|
|
21
21
|
export * from './model/vocabulary.js';
|
|
22
22
|
export * from './analysis/reservations.js';
|
|
23
23
|
export * from './analysis/witnesses.js';
|
|
24
|
-
export { alignFeatures, tooShortToShorten } from './collation/alignment.js';
|
|
24
|
+
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten } from './collation/alignment.js';
|
|
25
25
|
export * from './collation/statistics.js';
|
|
26
26
|
export * from './model/Edition.js';
|
|
27
|
-
export * from './
|
|
27
|
+
export * from './lookup/lookup.js';
|
|
28
|
+
export * from './lookup/paths.js';
|
|
29
|
+
export * from './analysis/stemma.js';
|
|
30
|
+
export * from './analysis/text.js';
|
|
28
31
|
export * from './analysis/ownPaper.js';
|
|
32
|
+
export * from './analysis/paper.js';
|
|
29
33
|
export * from './emulation/negotiation.js';
|
|
30
34
|
export * from './collation/scatter.js';
|
|
31
35
|
export * from './ops/index.js';
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { systemIdIn } from "../systems/TrackerBar.js";
|
|
2
2
|
import { certaintyOf, isAsserted } from "../model/Assumption.js";
|
|
3
3
|
import context from "../spec/context.json" with { type: 'json' };
|
|
4
|
+
import { atOwnPlaces } from "../collation/alignment.js";
|
|
4
5
|
/**
|
|
5
6
|
* The keys an export derives from the tree, which no node of the
|
|
6
7
|
* edition states itself. An import reads them off again.
|
|
@@ -58,6 +59,16 @@ const withSystemContext = (version) => {
|
|
|
58
59
|
return system ? { '@context': `https://w3id.org/reo/${system}/context.jsonld`, ...version } : version;
|
|
59
60
|
};
|
|
60
61
|
const withSystemContexts = (edition) => Array.isArray(edition.versions) ? { ...edition, versions: edition.versions.map(withSystemContext) } : edition;
|
|
62
|
+
/**
|
|
63
|
+
* Gives every version the act that made it, stating nothing of it where
|
|
64
|
+
* nothing is known. The axioms of reo.ttl order that act in time against
|
|
65
|
+
* the recording, the copies and the act that made the version it derives
|
|
66
|
+
* from, and a reasoner cannot place a node the graph lacks. An import
|
|
67
|
+
* drops it again where it states nothing.
|
|
68
|
+
*/
|
|
69
|
+
const withCreations = (edition) => Array.isArray(edition.versions)
|
|
70
|
+
? { ...edition, versions: edition.versions.map((version) => version.creation ? version : { ...version, creation: {} }) }
|
|
71
|
+
: edition;
|
|
61
72
|
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
62
73
|
/** The act with what it changed named: an attachment augments the copy, a removal diminishes it. */
|
|
63
74
|
const changing = (copyId) => (act) => {
|
|
@@ -151,8 +162,12 @@ const withDoubtedReferencesQuoted = (value) => {
|
|
|
151
162
|
quoted: entries.flatMap(({ quoted }) => quoted)
|
|
152
163
|
};
|
|
153
164
|
};
|
|
165
|
+
/** The edition with each copy's features and tears at the copy's own places, as a document holds them. */
|
|
166
|
+
const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
|
|
167
|
+
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
168
|
+
: edition;
|
|
154
169
|
export const asJsonLd = (edition) => {
|
|
155
|
-
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withChanges(asJsonLdEntity(edition))));
|
|
170
|
+
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))));
|
|
156
171
|
// The context is the export's own; one carried in from an import must not override it.
|
|
157
172
|
const { base, '@context': carried, ...rest } = node;
|
|
158
173
|
return {
|
package/lib/io/importJsonLd.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Edition } from "../model/Edition.js";
|
|
2
2
|
export declare const importDate: (str: string) => Date;
|
|
3
3
|
type Json = any;
|
|
4
|
+
/**
|
|
5
|
+
* Reads an edition. A document holds each copy's features and tears at
|
|
6
|
+
* the copy's own places, as they were read; the edition holds them on
|
|
7
|
+
* its axis, which is where the copy's alignment carries them.
|
|
8
|
+
*/
|
|
4
9
|
export declare const importJsonLd: (json: Json) => Edition;
|
|
5
10
|
export {};
|