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.
Files changed (71) hide show
  1. package/README.md +26 -7
  2. package/lib/analysis/constraints.d.ts +2 -2
  3. package/lib/analysis/constraints.js +30 -21
  4. package/lib/analysis/ownPaper.d.ts +11 -16
  5. package/lib/analysis/ownPaper.js +13 -31
  6. package/lib/analysis/paper.d.ts +129 -0
  7. package/lib/analysis/paper.js +260 -0
  8. package/lib/analysis/reservations.d.ts +2 -2
  9. package/lib/analysis/reservations.js +6 -6
  10. package/lib/analysis/sigla.d.ts +4 -5
  11. package/lib/analysis/sigla.js +4 -4
  12. package/lib/analysis/stemma.d.ts +22 -0
  13. package/lib/analysis/stemma.js +38 -0
  14. package/lib/analysis/text.d.ts +31 -0
  15. package/lib/analysis/text.js +62 -0
  16. package/lib/analysis/witnesses.d.ts +5 -5
  17. package/lib/analysis/witnesses.js +22 -20
  18. package/lib/collation/alignment.d.ts +46 -8
  19. package/lib/collation/alignment.js +137 -47
  20. package/lib/collation/scatter.d.ts +4 -4
  21. package/lib/collation/scatter.js +15 -13
  22. package/lib/emulation/Emulation.d.ts +7 -7
  23. package/lib/emulation/Emulation.js +29 -27
  24. package/lib/emulation/negotiation.d.ts +2 -2
  25. package/lib/emulation/negotiation.js +3 -2
  26. package/lib/index.d.ts +6 -2
  27. package/lib/index.js +6 -2
  28. package/lib/io/asJsonLd.js +16 -1
  29. package/lib/io/importJsonLd.d.ts +5 -0
  30. package/lib/io/importJsonLd.js +18 -1
  31. package/lib/io/migrate.js +94 -2
  32. package/lib/lookup/lookup.d.ts +32 -0
  33. package/lib/lookup/lookup.js +40 -0
  34. package/lib/lookup/paths.d.ts +11 -0
  35. package/lib/lookup/paths.js +78 -0
  36. package/lib/lookup/perState.d.ts +32 -0
  37. package/lib/lookup/perState.js +63 -0
  38. package/lib/model/Assumption.d.ts +21 -9
  39. package/lib/model/Edit.d.ts +4 -0
  40. package/lib/model/Edition.d.ts +19 -0
  41. package/lib/model/Edition.js +9 -1
  42. package/lib/model/Quantity.d.ts +4 -1
  43. package/lib/model/Quantity.js +1 -0
  44. package/lib/model/RollCopy.d.ts +144 -48
  45. package/lib/model/RollCopy.js +9 -6
  46. package/lib/model/Version.d.ts +7 -1
  47. package/lib/ops/beliefs.d.ts +1 -1
  48. package/lib/ops/beliefs.js +1 -1
  49. package/lib/ops/collation.d.ts +3 -4
  50. package/lib/ops/collation.js +25 -23
  51. package/lib/ops/commands.d.ts +4 -5
  52. package/lib/ops/commands.js +10 -10
  53. package/lib/ops/copies.d.ts +47 -10
  54. package/lib/ops/copies.js +92 -26
  55. package/lib/ops/draft.d.ts +12 -12
  56. package/lib/ops/draft.js +11 -13
  57. package/lib/ops/edits.d.ts +1 -2
  58. package/lib/ops/edits.js +13 -10
  59. package/lib/ops/features.d.ts +2 -2
  60. package/lib/ops/features.js +3 -2
  61. package/lib/ops/index.d.ts +2 -2
  62. package/lib/ops/index.js +7 -1
  63. package/lib/ops/versions.d.ts +2 -3
  64. package/lib/ops/versions.js +6 -5
  65. package/lib/readers/stanfordAton.js +28 -1
  66. package/lib/schema.json +185 -26
  67. package/lib/spec/context.json +33 -1
  68. package/lib/systems/ReproducingSystem.d.ts +7 -6
  69. package/package.json +1 -1
  70. package/lib/view/EditionView.d.ts +0 -103
  71. 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, scale, subtract } from "../model/Quantity.js";
4
- /** Moves both ends of a span, the far end only where the span has one. */
5
- const move = (span, by) => {
6
- span.from = add(span.from, by);
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 = add(span.to, by);
14
+ span.to = through(span.to);
9
15
  };
10
- /** Stretches both ends of a span away from the beginning of the roll. */
11
- const stretch = (span, factor) => {
12
- span.from = scale(span.from, factor);
13
- if (span.to !== undefined)
14
- span.to = scale(span.to, factor);
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
- const back = (shift) => ({ horizontal: scale(shift.horizontal, -1), vertical: scale(shift.vertical, -1) });
17
- export const applyShift = (shift, copy) => {
18
- if (copy.measurements.shift)
19
- return;
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
- /** Scales the copy's features away from the beginning of the roll, and records the factor. */
27
- export const applyScale = (factor, copy) => {
28
- if (copy.measurements.scale !== undefined)
29
- return;
30
- featuresOf(copy).forEach(feature => stretch(feature.horizontal, factor));
31
- copy.measurements.scale = factor;
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 shift off the copy's features again, as far as one was applied. */
34
- export const revertShift = (copy) => {
35
- const shift = copy.measurements.shift;
36
- if (!shift)
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
- const reversed = back(shift);
39
- featuresOf(copy).forEach(feature => {
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
- /** Takes the scale off the copy's features again, as far as one was applied. */
46
- export const revertScale = (copy) => {
47
- const factor = copy.measurements.scale;
48
- if (factor === undefined)
49
- return;
50
- featuresOf(copy).forEach(feature => stretch(feature.horizontal, 1 / factor));
51
- delete copy.measurements.scale;
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()) => chainsOf(copy).filter(chain => !leaving.has(chain.id) && chain.horizontal.to - chain.horizontal.from <= extension);
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, extension); });
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, taken.length); });
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(Math.sqrt(squares / matches.length))
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: (view: EditionView, symbols: readonly Readonly<AnySymbol>[], copies: ReadonlySet<string>) => Reading[];
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: (view: EditionView, versionId: string, copyId: string, options?: ScatterOptions) => Scatter[];
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: (view: EditionView, versionId: string) => Sides | undefined;
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
@@ -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 = (view, copies) => (feature) => {
12
- const copy = view.copyOf(feature.id);
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 = (view, symbols, copies) => {
38
- const tested = sitsOn(view, copies);
39
+ export const readingsOf = (edition, symbols, copies) => {
40
+ const tested = sitsOn(edition, copies);
39
41
  return symbols.flatMap((symbol) => {
40
- const carriers = view.placedCarriersOf(symbol);
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 = (view, versionId, copyId, options = {}) => scatterOf(readingsOf(view, view.snapshot(versionId), new Set([copyId])), options);
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 = (view, symbols) => {
194
+ const copiesBearing = (edition, symbols) => {
193
195
  const tally = symbols.reduce((counts, symbol) => {
194
- const bearers = new Set(view.carriersOf(symbol).flatMap(carrier => {
195
- const copy = view.copyOf(carrier.id);
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 = (view, versionId) => {
223
- const version = view.version(versionId);
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(view, insertedBy(version)),
228
- parent: copiesBearing(view, view.symbols(deletedBy(version)))
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 view supplies the copies, whose measurements
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: (view: EditionView, events: readonly NegotiatedEvent[]) => NegotiatedEvent[];
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: (view: EditionView, version: Readonly<Version>, bar: TrackerBar, { range }?: Pick<EmulationScope, 'range'>) => NegotiatedEvent[];
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>, view: EditionView, options?: Options, { range, skipToFirstNote }?: EmulationScope) => Emulated;
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(view: EditionView): void;
61
- emulateVersion(version: Version, view: EditionView, scope?: EmulationScope): AnyPerformedRollFeature[];
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 = (view) => {
10
- const measured = view.edition.copies
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 = (view, version) => ({
16
- punchDiameter: meanPunchDiameterOf(view),
17
- tempo: view.edition.tempoAdjustment,
18
- toOwnPaper: toOwnPaperOf(view, version)
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 = (view, symbolId) => {
22
- const symbol = view.symbol(symbolId);
23
+ const onsetsByCopy = (edition, symbolId) => {
24
+ const symbol = symbolIn(edition, symbolId);
23
25
  if (!symbol)
24
26
  return new Map();
25
- const onsets = view.placedCarriersOf(symbol).flatMap((carrier) => {
26
- const copy = view.copyOf(carrier.id);
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 = (view, followerId, referenceId) => {
38
- const references = onsetsByCopy(view, referenceId);
39
- return [...onsetsByCopy(view, followerId)].flatMap(([copy, onset]) => {
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 view supplies the copies, whose measurements
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 = (view, events) => {
99
- const gap = meanPunchDiameterOf(view) ?? mm(1);
100
- const offsets = (follower, reference) => offsetsBetween(view, follower, reference);
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 = (view, version, bar, { range } = {}) => {
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(view, view.snapshot(version.id)
119
+ return withPlacementsApplied(edition, snapshotOf(edition, version.id)
118
120
  .filter(isCommand)
119
- .map(symbol => negotiatedEventOf(view, symbol, bar))
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, view, options = system.defaultOptions, { range, skipToFirstNote = false } = {}) => {
128
- const negotiated = negotiatedEventsOf(view, version, system.trackerBar, { range });
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(view, version));
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(view) {
266
- this.negotiatedEvents = withPlacementsApplied(view, this.negotiatedEvents);
267
+ applyConstraints(edition) {
268
+ this.negotiatedEvents = withPlacementsApplied(edition, this.negotiatedEvents);
267
269
  }
268
- emulateVersion(version, view, scope = {}) {
269
- const emulated = emulate(this.system, version, view, this.options, scope);
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: (view: EditionView, symbol: Note | Expression, bar: TrackerBar) => NegotiatedEvent | null;
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 = (view, symbol, bar) => {
11
- const horizontal = view.placeOf(symbol);
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 './view/EditionView.js';
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 './view/EditionView.js';
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';
@@ -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 {
@@ -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 {};