linked-rolls 0.70.0 → 0.72.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 (143) hide show
  1. package/README.md +106 -5
  2. package/lib/analysis/constraints.d.ts +7 -0
  3. package/lib/analysis/constraints.js +1 -0
  4. package/lib/analysis/ownPaper.d.ts +5 -1
  5. package/lib/analysis/ownPaper.js +5 -1
  6. package/lib/analysis/paper.d.ts +22 -4
  7. package/lib/analysis/paper.js +6 -0
  8. package/lib/analysis/reservations.d.ts +24 -1
  9. package/lib/analysis/reservations.js +15 -1
  10. package/lib/analysis/sigla.d.ts +6 -1
  11. package/lib/analysis/sigla.js +5 -1
  12. package/lib/analysis/stemma.d.ts +6 -1
  13. package/lib/analysis/stemma.js +6 -1
  14. package/lib/analysis/text.d.ts +16 -2
  15. package/lib/analysis/text.js +16 -2
  16. package/lib/analysis/witnesses.d.ts +20 -1
  17. package/lib/analysis/witnesses.js +4 -0
  18. package/lib/collation/Collation.d.ts +47 -8
  19. package/lib/collation/Collation.js +29 -5
  20. package/lib/collation/alignment.d.ts +16 -3
  21. package/lib/collation/alignment.js +15 -3
  22. package/lib/collation/scatter.d.ts +50 -9
  23. package/lib/collation/scatter.js +11 -1
  24. package/lib/collation/statistics.d.ts +39 -9
  25. package/lib/collation/statistics.js +27 -6
  26. package/lib/collation/substitution.d.ts +5 -0
  27. package/lib/collation/substitution.js +4 -0
  28. package/lib/emulation/Emulation.d.ts +16 -1
  29. package/lib/emulation/Emulation.js +10 -0
  30. package/lib/emulation/negotiation.d.ts +1 -0
  31. package/lib/emulation/negotiation.js +1 -0
  32. package/lib/filiation/adoption.d.ts +47 -0
  33. package/lib/filiation/adoption.js +190 -0
  34. package/lib/filiation/changes.d.ts +33 -0
  35. package/lib/filiation/changes.js +38 -0
  36. package/lib/filiation/index.d.ts +12 -0
  37. package/lib/filiation/index.js +12 -0
  38. package/lib/filiation/presence.d.ts +50 -0
  39. package/lib/filiation/presence.js +122 -0
  40. package/lib/filiation/proposal.d.ts +132 -0
  41. package/lib/filiation/proposal.js +376 -0
  42. package/lib/filiation/readings.d.ts +108 -0
  43. package/lib/filiation/readings.js +281 -0
  44. package/lib/filiation/search.d.ts +113 -0
  45. package/lib/filiation/search.js +268 -0
  46. package/lib/filiation/systems.d.ts +21 -0
  47. package/lib/filiation/systems.js +39 -0
  48. package/lib/filiation/trees.d.ts +20 -0
  49. package/lib/filiation/trees.js +69 -0
  50. package/lib/index.d.ts +34 -2
  51. package/lib/index.js +3 -1
  52. package/lib/io/asJsonLd.d.ts +10 -0
  53. package/lib/io/asJsonLd.js +10 -0
  54. package/lib/io/context.d.ts +16 -2
  55. package/lib/io/context.js +16 -2
  56. package/lib/io/importJsonLd.d.ts +1 -0
  57. package/lib/io/importJsonLd.js +1 -0
  58. package/lib/io/migrate.d.ts +8 -0
  59. package/lib/io/migrate.js +8 -0
  60. package/lib/lookup/lookup.d.ts +29 -5
  61. package/lib/lookup/lookup.js +29 -5
  62. package/lib/lookup/paths.d.ts +23 -3
  63. package/lib/lookup/paths.js +17 -3
  64. package/lib/model/Agent.d.ts +28 -2
  65. package/lib/model/Agent.js +15 -1
  66. package/lib/model/Assumption.d.ts +96 -7
  67. package/lib/model/Assumption.js +65 -7
  68. package/lib/model/ConditionState.d.ts +1 -0
  69. package/lib/model/Edit.d.ts +10 -0
  70. package/lib/model/Edit.js +8 -0
  71. package/lib/model/Edition.d.ts +11 -0
  72. package/lib/model/Edition.js +1 -0
  73. package/lib/model/Feature.d.ts +74 -7
  74. package/lib/model/Feature.js +41 -3
  75. package/lib/model/FeatureSource.d.ts +14 -1
  76. package/lib/model/FeatureSource.js +7 -1
  77. package/lib/model/Perforator.d.ts +10 -0
  78. package/lib/model/Perforator.js +1 -0
  79. package/lib/model/Quantity.d.ts +160 -13
  80. package/lib/model/Quantity.js +110 -6
  81. package/lib/model/RollCopy.d.ts +74 -12
  82. package/lib/model/RollCopy.js +53 -10
  83. package/lib/model/Symbol.d.ts +38 -1
  84. package/lib/model/Symbol.js +16 -0
  85. package/lib/model/TrackCalibration.d.ts +10 -2
  86. package/lib/model/TrackCalibration.js +9 -2
  87. package/lib/model/Version.d.ts +24 -4
  88. package/lib/model/Version.js +19 -4
  89. package/lib/model/notes.d.ts +15 -2
  90. package/lib/model/notes.js +9 -2
  91. package/lib/model/procedures.d.ts +5 -1
  92. package/lib/model/procedures.js +5 -1
  93. package/lib/model/vocabulary.d.ts +6 -1
  94. package/lib/model/vocabulary.js +6 -1
  95. package/lib/ops/beliefs.d.ts +23 -1
  96. package/lib/ops/beliefs.js +23 -1
  97. package/lib/ops/collation.d.ts +3 -0
  98. package/lib/ops/collation.js +3 -0
  99. package/lib/ops/commands.d.ts +17 -2
  100. package/lib/ops/commands.js +17 -2
  101. package/lib/ops/copies.d.ts +37 -6
  102. package/lib/ops/copies.js +37 -6
  103. package/lib/ops/draft.d.ts +2 -0
  104. package/lib/ops/draft.js +1 -0
  105. package/lib/ops/edits.d.ts +5 -1
  106. package/lib/ops/edits.js +5 -1
  107. package/lib/ops/features.d.ts +12 -1
  108. package/lib/ops/features.js +7 -0
  109. package/lib/ops/versions.d.ts +15 -3
  110. package/lib/ops/versions.js +15 -3
  111. package/lib/readers/phillipsEroll.d.ts +12 -1
  112. package/lib/readers/phillipsEroll.js +6 -1
  113. package/lib/readers/spencerBar.d.ts +12 -1
  114. package/lib/readers/spencerBar.js +7 -1
  115. package/lib/readers/stanfordAton.d.ts +15 -0
  116. package/lib/readers/stanfordAton.js +9 -0
  117. package/lib/schema.json +10 -0
  118. package/lib/shared/utils.d.ts +12 -0
  119. package/lib/spec/context.json +4 -0
  120. package/lib/systems/ReproducingSystem.d.ts +46 -3
  121. package/lib/systems/TrackerBar.d.ts +45 -4
  122. package/lib/systems/TrackerBar.js +20 -3
  123. package/lib/systems/index.d.ts +9 -2
  124. package/lib/systems/index.js +9 -2
  125. package/lib/systems/velocity.d.ts +1 -0
  126. package/lib/systems/velocity.js +1 -0
  127. package/lib/systems/welte.d.ts +18 -3
  128. package/lib/systems/welte.js +9 -2
  129. package/lib/systems/welteLicensee/bar.d.ts +1 -0
  130. package/lib/systems/welteLicensee/bar.js +1 -0
  131. package/lib/systems/welteLicensee/system.d.ts +18 -0
  132. package/lib/systems/welteLicensee/system.js +17 -0
  133. package/lib/systems/welteT100/bar.d.ts +8 -0
  134. package/lib/systems/welteT100/bar.js +4 -0
  135. package/lib/systems/welteT100/system.d.ts +46 -5
  136. package/lib/systems/welteT100/system.js +29 -3
  137. package/lib/systems/welteT98/bar.d.ts +6 -0
  138. package/lib/systems/welteT98/bar.js +2 -0
  139. package/lib/systems/welteT98/system.d.ts +58 -5
  140. package/lib/systems/welteT98/system.js +42 -3
  141. package/lib/validate.d.ts +16 -0
  142. package/lib/validate.js +15 -0
  143. package/package.json +2 -1
@@ -9,7 +9,10 @@ import { quantity } from "../model/Quantity.js";
9
9
  * stood out; a median and a median absolute deviation leave them out of
10
10
  * account.
11
11
  */
12
- /** The middle of a sample, the mean of the two middle values where it has an even number of them. */
12
+ /**
13
+ * The middle of a sample, the mean of the two middle values where it has an even number of them.
14
+ * @internal
15
+ */
13
16
  export const medianOf = (values) => {
14
17
  if (values.length === 0)
15
18
  return undefined;
@@ -21,7 +24,10 @@ export const medianOf = (values) => {
21
24
  };
22
25
  /** What the median absolute deviation must be multiplied by to estimate the standard deviation of a normal sample. */
23
26
  const MAD_TO_SIGMA = 1.4826;
24
- /** Where the sample sits and how far it scatters, or nothing for an empty sample. */
27
+ /**
28
+ * Where the sample sits and how far it scatters, or nothing for an empty sample.
29
+ * @internal
30
+ */
25
31
  export const spreadOf = (values) => {
26
32
  const median = medianOf(values);
27
33
  if (median === undefined)
@@ -33,7 +39,10 @@ export const spreadOf = (values) => {
33
39
  sigma: quantity(MAD_TO_SIGMA * (medianOf(deviations) ?? 0))
34
40
  };
35
41
  };
36
- /** How far the value lies from the centre of the spread, in units of its scatter. */
42
+ /**
43
+ * How far the value lies from the centre of the spread, in units of its scatter.
44
+ * @internal
45
+ */
37
46
  export const standardise = (value, spread) => (value - spread.median) / spread.sigma;
38
47
  /** The complementary error function, after Press et al., whose fractional error stays below 1.2e-7. */
39
48
  const erfc = (x) => {
@@ -44,9 +53,15 @@ const erfc = (x) => {
44
53
  t * (-0.82215223 + t * 0.17087277)))))))));
45
54
  return x >= 0 ? tail : 2 - tail;
46
55
  };
47
- /** The share of a normal sample lying below `z` standard deviations. */
56
+ /**
57
+ * The share of a normal sample lying below `z` standard deviations.
58
+ * @internal
59
+ */
48
60
  export const normalBelow = (z) => erfc(-z / Math.SQRT2) / 2;
49
- /** The share of a normal sample lying further than `z` standard deviations from its centre, on either side. */
61
+ /**
62
+ * The share of a normal sample lying further than `z` standard deviations from its centre, on either side.
63
+ * @internal
64
+ */
50
65
  export const normalBeyond = (z) => erfc(z / Math.SQRT2);
51
66
  const horner = (coefficients, x) => coefficients.reduce((total, coefficient) => total * x + coefficient, 0);
52
67
  const CENTRAL_NUMERATOR = [
@@ -74,6 +89,7 @@ const tailQuantile = (share) => {
74
89
  * How many standard deviations out the given share of a normal sample
75
90
  * lies below, after Acklam's rational approximation, whose relative
76
91
  * error stays below 1.15e-9. The inverse of `normalBelow`.
92
+ * @internal
77
93
  */
78
94
  export const normalQuantile = (share) => {
79
95
  if (share <= 0)
@@ -92,9 +108,13 @@ export const normalQuantile = (share) => {
92
108
  * How much heavier the tails of a standardised sample are than a normal
93
109
  * sample's. Zero for a normal shape, positive where more of the sample
94
110
  * lies far out than the curve allows.
111
+ * @internal
95
112
  */
96
113
  export const excessKurtosisOf = (standardised) => standardised.reduce((total, z) => total + z ** 4, 0) / standardised.length - 3;
97
- /** What the sample puts beyond the given distance, against what a normal sample would. */
114
+ /**
115
+ * What the sample puts beyond the given distance, against what a normal sample would.
116
+ * @internal
117
+ */
98
118
  export const tailOf = (standardised, beyond) => ({
99
119
  beyond,
100
120
  observed: standardised.filter(z => Math.abs(z) > beyond).length,
@@ -104,6 +124,7 @@ export const tailOf = (standardised, beyond) => ({
104
124
  * The sample counted into bins of the given width, laid out on
105
125
  * multiples of that width so that two histograms of one width share
106
126
  * their bounds. Nothing for an empty sample or a width of nothing.
127
+ * @internal
107
128
  */
108
129
  export const histogramOf = (values, binWidth) => {
109
130
  if (values.length === 0 || binWidth <= 0)
@@ -8,11 +8,13 @@ export type { Operation, Spelling } from "../systems/TrackerBar.js";
8
8
  * nothing for a word no bar here knows or one without a counterpart in
9
9
  * another scale. The bars share their words where they share a scale,
10
10
  * as the Licensee does the T-100's, so the first that reads it answers.
11
+ * @category Systems and emulation
11
12
  */
12
13
  export declare const operationOf: (expressionType: string) => Operation | undefined;
13
14
  /**
14
15
  * A latched function of the older version and the held command of the
15
16
  * newer one that stands for it.
17
+ * @category Collation and alignment
16
18
  */
17
19
  export interface Substitution {
18
20
  /** The command that turned the function on, and the one that cancelled it. */
@@ -30,6 +32,7 @@ export interface Substitution {
30
32
  * command. The T-98 punches a hold as a chain of round holes on a
31
33
  * 2.66 mm grid with bridges of about a millimetre, so a scan read hole
32
34
  * by hole shows a run where the paper shows one command.
35
+ * @category Collation and alignment
33
36
  */
34
37
  export declare const defaultChainGap: import("../index.js").Quantity<"mm">;
35
38
  /**
@@ -42,6 +45,7 @@ export declare const defaultChainGap: import("../index.js").Quantity<"mm">;
42
45
  * the green bar, so drawing it as a perforation is out of the question,
43
46
  * but an edit still has to be shown somewhere, and the lane the green
44
47
  * scale gives the same function is where it belongs.
48
+ * @internal
45
49
  */
46
50
  export declare const positionOfSameFunction: (bar: TrackerBar, symbol: Expression) => Track | undefined;
47
51
  /**
@@ -54,5 +58,6 @@ export declare const positionOfSameFunction: (bar: TrackerBar, symbol: Expressio
54
58
  * reported and the editor is left to say what happened. Nothing is
55
59
  * invented either: both sides are perforations somebody punched, and all
56
60
  * this asserts is which stands for which.
61
+ * @category Collation and alignment
57
62
  */
58
63
  export declare const substitutionsBetween: (own: readonly AnySymbol[], inherited: readonly AnySymbol[], locate: Locate, tolerance?: CollationTolerance, chainGap?: Millimeters) => Substitution[];
@@ -6,6 +6,7 @@ import { trackerBars } from "../systems/index.js";
6
6
  * nothing for a word no bar here knows or one without a counterpart in
7
7
  * another scale. The bars share their words where they share a scale,
8
8
  * as the Licensee does the T-100's, so the first that reads it answers.
9
+ * @category Systems and emulation
9
10
  */
10
11
  export const operationOf = (expressionType) => {
11
12
  for (const bar of trackerBars) {
@@ -20,6 +21,7 @@ export const operationOf = (expressionType) => {
20
21
  * command. The T-98 punches a hold as a chain of round holes on a
21
22
  * 2.66 mm grid with bridges of about a millimetre, so a scan read hole
22
23
  * by hole shows a run where the paper shows one command.
24
+ * @category Collation and alignment
23
25
  */
24
26
  export const defaultChainGap = mm(3);
25
27
  const isExpression = (symbol) => symbol.type === 'expression';
@@ -35,6 +37,7 @@ const functionKey = (symbol, operation) => operation.sided ? `${operation.operat
35
37
  * the green bar, so drawing it as a perforation is out of the question,
36
38
  * but an edit still has to be shown somewhere, and the lane the green
37
39
  * scale gives the same function is where it belongs.
40
+ * @internal
38
41
  */
39
42
  export const positionOfSameFunction = (bar, symbol) => {
40
43
  const wanted = operationOf(symbol.expressionType);
@@ -132,6 +135,7 @@ const theOnly = (items) => items.length === 1 ? items[0] : undefined;
132
135
  * reported and the editor is left to say what happened. Nothing is
133
136
  * invented either: both sides are perforations somebody punched, and all
134
137
  * this asserts is which stands for which.
138
+ * @category Collation and alignment
135
139
  */
136
140
  export const substitutionsBetween = (own, inherited, locate, tolerance = defaultCollationTolerance, chainGap = defaultChainGap) => {
137
141
  const latched = latchedIn(inherited, locate);
@@ -4,6 +4,10 @@ import { Version } from "../model/Version.js";
4
4
  import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "../systems/ReproducingSystem.js";
5
5
  import { Millimeters } from "../model/Quantity.js";
6
6
  import { Edition } from "../model/Edition.js";
7
+ /**
8
+ * Which part of a version `emulate` plays, and where its clock starts.
9
+ * @category Systems and emulation
10
+ */
7
11
  export type EmulationScope = {
8
12
  /** Only notes whose onset lies within this span of the roll are played. */
9
13
  range?: [Millimeters, Millimeters];
@@ -15,15 +19,20 @@ export type EmulationScope = {
15
19
  * left as they were. The edition supplies the copies, whose measurements
16
20
  * decide how far before or after its reference a command goes, and a
17
21
  * punch diameter, or a millimetre, where no copy agrees with a statement.
22
+ * @category Systems and emulation
18
23
  */
19
24
  export declare const withPlacementsApplied: (edition: Edition, events: readonly NegotiatedEvent[]) => NegotiatedEvent[];
20
25
  /**
21
26
  * The commands of a version as the bar performs them, in order of place,
22
27
  * within the scope and with the placements applied. A note plays only
23
28
  * where its onset falls in the range; expressions play throughout.
29
+ * @category Systems and emulation
24
30
  */
25
31
  export declare const negotiatedEventsOf: (edition: Edition, version: Readonly<Version>, bar: TrackerBar, { range }?: Pick<EmulationScope, 'range'>) => NegotiatedEvent[];
26
- /** A version performed: what was negotiated, and what the system made of it. */
32
+ /**
33
+ * A version performed: what was negotiated, and what the system made of it.
34
+ * @category Systems and emulation
35
+ */
27
36
  export interface Emulated {
28
37
  /** The id of the version performed. */
29
38
  readonly source: string;
@@ -35,18 +44,21 @@ export interface Emulated {
35
44
  /**
36
45
  * Performs a version on a reproducing system: the symbols are negotiated
37
46
  * into placed events and the system plays them. Nothing given is changed.
47
+ * @category Systems and emulation
38
48
  */
39
49
  export declare const emulate: <Options extends object>(system: ReproducingSystem<Options>, version: Readonly<Version>, edition: Edition, options?: Options, { range, skipToFirstNote }?: EmulationScope) => Emulated;
40
50
  /**
41
51
  * The performed events as a MIDI file, in which every note and pedal step
42
52
  * is labelled with the symbol it performs. The file names the system, the
43
53
  * version and the options it was played with.
54
+ * @category Systems and emulation
44
55
  */
45
56
  export declare const midiOf: (events: readonly AnyPerformedRollFeature[], systemName: string, options: object, source?: string) => MidiFile;
46
57
  /**
47
58
  * A version of the edition, performed and kept: `emulate` and `midiOf`
48
59
  * with the result held on the object between the two calls. Prefer the
49
60
  * functions where nothing needs holding.
61
+ * @category Systems and emulation
50
62
  */
51
63
  export declare class Emulation<Options extends object> {
52
64
  readonly system: ReproducingSystem<Options>;
@@ -58,7 +70,10 @@ export declare class Emulation<Options extends object> {
58
70
  constructor(system: ReproducingSystem<Options>, options?: Options);
59
71
  /** Moves the negotiated events to where their statements put them; see `withPlacementsApplied`. */
60
72
  applyConstraints(edition: Edition): void;
73
+ /** Performs the version, keeps what came of it, and returns the performed events; see `emulate`. */
61
74
  emulateVersion(version: Version, edition: Edition, scope?: EmulationScope): AnyPerformedRollFeature[];
75
+ /** The performed events that sound or follow from the symbol with the id. */
62
76
  findEventsPerforming(id: string): AnyPerformedRollFeature[];
77
+ /** The performed events as a MIDI file; see `midiOf`. */
63
78
  asMIDI(): MidiFile;
64
79
  }
@@ -151,6 +151,7 @@ const earliestOf = (times) => times.length === 0 ? seconds(0) : times.reduce((so
151
151
  * left as they were. The edition supplies the copies, whose measurements
152
152
  * decide how far before or after its reference a command goes, and a
153
153
  * punch diameter, or a millimetre, where no copy agrees with a statement.
154
+ * @category Systems and emulation
154
155
  */
155
156
  export const withPlacementsApplied = (edition, events) => {
156
157
  const gap = meanPunchDiameterOf(edition) ?? mm(1);
@@ -168,6 +169,7 @@ export const withPlacementsApplied = (edition, events) => {
168
169
  * The commands of a version as the bar performs them, in order of place,
169
170
  * within the scope and with the placements applied. A note plays only
170
171
  * where its onset falls in the range; expressions play throughout.
172
+ * @category Systems and emulation
171
173
  */
172
174
  export const negotiatedEventsOf = (edition, version, bar, { range } = {}) => {
173
175
  const inScope = (event) => !range || event.type !== 'note' || (event.horizontal.from > range[0] && event.horizontal.from < range[1]);
@@ -180,6 +182,7 @@ export const negotiatedEventsOf = (edition, version, bar, { range } = {}) => {
180
182
  /**
181
183
  * Performs a version on a reproducing system: the symbols are negotiated
182
184
  * into placed events and the system plays them. Nothing given is changed.
185
+ * @category Systems and emulation
183
186
  */
184
187
  export const emulate = (system, version, edition, options = system.defaultOptions, { range, skipToFirstNote = false } = {}) => {
185
188
  const negotiated = negotiatedEventsOf(edition, version, system.trackerBar, { range });
@@ -202,6 +205,7 @@ export const emulate = (system, version, edition, options = system.defaultOption
202
205
  * The performed events as a MIDI file, in which every note and pedal step
203
206
  * is labelled with the symbol it performs. The file names the system, the
204
207
  * version and the options it was played with.
208
+ * @category Systems and emulation
205
209
  */
206
210
  export const midiOf = (events, systemName, options, source) => {
207
211
  const TICKS_PER_SECOND = 1000;
@@ -262,6 +266,8 @@ export const midiOf = (events, systemName, options, source) => {
262
266
  midi.push(controller(controllerOf(event), event.value, labelled ? deltaTime : 0));
263
267
  }
264
268
  }
269
+ // a standard MIDI file ends every track with this event
270
+ midi.push({ type: 'meta', subtype: 'endOfTrack', deltaTime: 0 });
265
271
  return {
266
272
  header: {
267
273
  ticksPerBeat: TICKS_PER_SECOND,
@@ -275,6 +281,7 @@ export const midiOf = (events, systemName, options, source) => {
275
281
  * A version of the edition, performed and kept: `emulate` and `midiOf`
276
282
  * with the result held on the object between the two calls. Prefer the
277
283
  * functions where nothing needs holding.
284
+ * @category Systems and emulation
278
285
  */
279
286
  export class Emulation {
280
287
  constructor(system, options = system.defaultOptions) {
@@ -322,6 +329,7 @@ export class Emulation {
322
329
  applyConstraints(edition) {
323
330
  this.negotiatedEvents = withPlacementsApplied(edition, this.negotiatedEvents);
324
331
  }
332
+ /** Performs the version, keeps what came of it, and returns the performed events; see `emulate`. */
325
333
  emulateVersion(version, edition, scope = {}) {
326
334
  const emulated = emulate(this.system, version, edition, this.options, scope);
327
335
  this.source = emulated.source;
@@ -330,9 +338,11 @@ export class Emulation {
330
338
  this.midiEvents = [...emulated.events];
331
339
  return this.midiEvents;
332
340
  }
341
+ /** The performed events that sound or follow from the symbol with the id. */
333
342
  findEventsPerforming(id) {
334
343
  return this.midiEvents.filter(event => event.performs.id === id);
335
344
  }
345
+ /** The performed events as a MIDI file; see `midiOf`. */
336
346
  asMIDI() {
337
347
  return midiOf(this.midiEvents, this.system.name, this.options, this.source);
338
348
  }
@@ -10,5 +10,6 @@ import { Edition } from "../model/Edition.js";
10
10
  * transfer between systems leaves behind: a red `ForzandoOn` a
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
+ * @category Systems and emulation
13
14
  */
14
15
  export declare const negotiatedEventOf: (edition: Edition, symbol: Note | Expression, bar: TrackerBar) => NegotiatedEvent | null;
@@ -7,6 +7,7 @@ import { placeOf } from "../analysis/text.js";
7
7
  * transfer between systems leaves behind: a red `ForzandoOn` a
8
8
  * green version still inherits cannot be performed on a green
9
9
  * machine, and an edit has yet to say what took its place.
10
+ * @category Systems and emulation
10
11
  */
11
12
  export const negotiatedEventOf = (edition, symbol, bar) => {
12
13
  const horizontal = placeOf(edition, symbol);
@@ -0,0 +1,47 @@
1
+ import { Edition } from "../model/Edition.js";
2
+ import { Version } from "../model/Version.js";
3
+ import { EditionOp } from "../ops/draft.js";
4
+ import { StemmaProposal, Unpinned } from "./proposal.js";
5
+ /**
6
+ * What adopting a proposal makes of the stemma: the versions it puts in
7
+ * place of the edition's, and what of the edition it leaves out.
8
+ * @category Filiation
9
+ */
10
+ export interface Adoption {
11
+ versions: Version[];
12
+ /** By version of the proposal, the id of the version it becomes. */
13
+ ids: Record<string, string>;
14
+ /** The versions of the edition the adoption leaves out. */
15
+ removed: string[];
16
+ /** The edits the editor wrote that the adoption leaves out, and why. */
17
+ dropped: Unpinned[];
18
+ }
19
+ /**
20
+ * What adopting the proposal makes of the edition's stemma, worked out
21
+ * without changing it, so that it can be looked at first.
22
+ *
23
+ * A version of the proposal that stands for a version of the edition
24
+ * keeps its id, system, creation and motivations, the derivation it
25
+ * states where it still derives from the same version, and its other
26
+ * derivations, the hypotheses, as long as they name a version that is
27
+ * still there and does not derive from it. Its edits are the proposal's:
28
+ * the edits the editor wrote that the proposal keeps, as they are, and
29
+ * one edit for each further reading it inserts or deletes, or for each
30
+ * recoding. An edit the version stated before over the very same
31
+ * readings is taken over with its id and what was written on it; the
32
+ * others are new and marked unchecked, as a collation marks its own.
33
+ *
34
+ * A reading that stands for a symbol of the edition keeps that symbol's
35
+ * id and whatever it states beside its carriers. A version of the edition
36
+ * nothing stands for goes, unless it states no edits and derives from a
37
+ * version that stays, as a version known only from a recording does.
38
+ * @category Filiation
39
+ */
40
+ export declare const adoptionOf: (edition: Edition, proposal: StemmaProposal) => Adoption;
41
+ /**
42
+ * Puts the proposed stemma in place of the edition's, as `adoptionOf`
43
+ * describes, and takes back a copy's statement that it carries a version
44
+ * that goes. One operation, and so one undo step.
45
+ * @category Operations
46
+ */
47
+ export declare const adoptStemma: (proposal: StemmaProposal) => EditionOp;
@@ -0,0 +1,190 @@
1
+ /** Taking a proposed stemma into the edition, as one operation. */
2
+ import { v5 } from "uuid";
3
+ import { assignReference, idOf } from "../model/Assumption.js";
4
+ import { derivesFrom, editsOf, insertedBy, principalDerivationOf } from "../model/Version.js";
5
+ import { systemOf } from "../systems/TrackerBar.js";
6
+ import { unchecked } from "../collation/Collation.js";
7
+ import { versionIn } from "../lookup/lookup.js";
8
+ import { declaring, reading } from "../ops/draft.js";
9
+ import { dropStatements } from "../ops/copies.js";
10
+ import { barNamed } from "./systems.js";
11
+ /** The namespace the names of adopted versions and edits are minted in, so that adopting again gives them again. */
12
+ const ADOPTED = '6f1d2c8e-93a4-4f0b-b8a1-2f4c5d7e9a31';
13
+ const exchangeKey = (inserted, deleted) => `${[...inserted].sort().join(',')}/${[...deleted].sort().join(',')}`;
14
+ /** The carriers of the reading, keeping what the edition states of those it held already. */
15
+ const carriersOf = (reading, stated) => {
16
+ const known = new Map(stated.map(carrier => [idOf(carrier), carrier]));
17
+ return reading.carriers.map(id => known.get(id) ?? assignReference(id));
18
+ };
19
+ /**
20
+ * What adopting the proposal makes of the edition's stemma, worked out
21
+ * without changing it, so that it can be looked at first.
22
+ *
23
+ * A version of the proposal that stands for a version of the edition
24
+ * keeps its id, system, creation and motivations, the derivation it
25
+ * states where it still derives from the same version, and its other
26
+ * derivations, the hypotheses, as long as they name a version that is
27
+ * still there and does not derive from it. Its edits are the proposal's:
28
+ * the edits the editor wrote that the proposal keeps, as they are, and
29
+ * one edit for each further reading it inserts or deletes, or for each
30
+ * recoding. An edit the version stated before over the very same
31
+ * readings is taken over with its id and what was written on it; the
32
+ * others are new and marked unchecked, as a collation marks its own.
33
+ *
34
+ * A reading that stands for a symbol of the edition keeps that symbol's
35
+ * id and whatever it states beside its carriers. A version of the edition
36
+ * nothing stands for goes, unless it states no edits and derives from a
37
+ * version that stays, as a version known only from a recording does.
38
+ * @category Filiation
39
+ */
40
+ export const adoptionOf = (edition, proposal) => {
41
+ const readingOf = new Map(proposal.collation.readings.map(reading => [reading.id, reading]));
42
+ const symbolIdOf = (reading) => proposal.symbols[reading] ?? reading;
43
+ const existing = new Map(edition.versions.flatMap(insertedBy).map(symbol => [symbol.id, symbol]));
44
+ // A reading the stemma lets arise more than once is one symbol for each time it arises, each
45
+ // carried by the copies below where it arose: the edition inserts a symbol in one version only.
46
+ const parentOf = new Map(proposal.versions.map(proposed => [proposed.id, proposed.parent]));
47
+ const copiesBelow = new Map(proposal.versions.map(proposed => [proposed.id, new Set()]));
48
+ for (const proposed of proposal.versions) {
49
+ for (let at = proposed.id; at !== undefined; at = parentOf.get(at)) {
50
+ if (proposed.copy)
51
+ copiesBelow.get(at).add(proposed.copy);
52
+ }
53
+ }
54
+ const arising = new Map();
55
+ for (const proposed of proposal.versions) {
56
+ for (const reading of proposed.inserts)
57
+ arising.set(reading, [...(arising.get(reading) ?? []), proposed.id]);
58
+ }
59
+ /** The symbol a reading is where it arose in the version given. */
60
+ const symbolIdAt = (reading, at) => {
61
+ const places = arising.get(reading) ?? [];
62
+ return places.indexOf(at) > 0 ? `symbol_${v5(`${reading}|${at}`, ADOPTED)}` : symbolIdOf(reading);
63
+ };
64
+ /** The symbol a version inherits of a reading: the one that arose nearest above it. */
65
+ const inheritedAt = (reading, at) => {
66
+ const places = arising.get(reading) ?? [];
67
+ for (let above = parentOf.get(at); above !== undefined; above = parentOf.get(above)) {
68
+ if (places.includes(above))
69
+ return symbolIdAt(reading, above);
70
+ }
71
+ return symbolIdOf(reading);
72
+ };
73
+ const symbolOf = (id, at) => {
74
+ const reading = readingOf.get(id);
75
+ const twice = (arising.get(id)?.length ?? 0) > 1;
76
+ const below = copiesBelow.get(at);
77
+ const own = twice
78
+ ? { ...reading, carriers: reading.carriers.filter((_, index) => below.has(reading.copies[index])) }
79
+ : reading;
80
+ const symbol = symbolIdAt(id, at);
81
+ const stated = symbol === proposal.symbols[id] ? existing.get(symbol) : undefined;
82
+ const carriers = carriersOf(own, stated?.carriers ?? []);
83
+ if (stated)
84
+ return { ...stated, carriers };
85
+ return reading.meaning.type === 'note'
86
+ ? { type: 'note', id: symbol, pitch: reading.meaning.pitch, carriers }
87
+ : { type: 'expression', id: symbol, expressionType: reading.meaning.expressionType, scope: reading.meaning.scope, carriers };
88
+ };
89
+ const standingFor = new Map(Object.entries(proposal.symbols).map(([reading, symbol]) => [symbol, reading]));
90
+ /** The symbol as an edit the editor wrote inserts it, with the carriers of the reading it stands for. */
91
+ const restated = (symbol) => {
92
+ const reading = standingFor.get(symbol.id);
93
+ return reading ? { ...symbol, carriers: carriersOf(readingOf.get(reading), symbol.carriers) } : symbol;
94
+ };
95
+ const idFor = (proposed) => proposed.version ?? v5(`version|${proposed.copy ?? ''}|${[...proposed.inserts].sort().join(',')}`, ADOPTED);
96
+ const ids = new Map(proposal.versions.map(proposed => [proposed.id, idFor(proposed)]));
97
+ const staying = new Set(ids.values());
98
+ const versions = proposal.versions.map((proposed) => {
99
+ const id = ids.get(proposed.id);
100
+ const before = proposed.version ? versionIn(edition, proposed.version) : undefined;
101
+ const pinned = new Set(proposed.pinned);
102
+ const stated = before ? editsOf(before) : [];
103
+ const kept = stated
104
+ .filter(edit => pinned.has(edit.id))
105
+ .map(edit => edit.insert ? { ...edit, insert: edit.insert.map(restated) } : edit);
106
+ const spoken = new Set(kept.flatMap(edit => [...(edit.insert ?? []).map(symbol => symbol.id), ...(edit.delete ?? [])]));
107
+ const statedBefore = new Map(stated.map(edit => [exchangeKey((edit.insert ?? []).map(symbol => symbol.id), edit.delete ?? []), edit]));
108
+ const made = (inserts, deletes, recoding) => {
109
+ const inserted = inserts.map(reading => symbolIdAt(reading, proposed.id));
110
+ const deleted = deletes.map(reading => inheritedAt(reading, proposed.id));
111
+ if ([...inserted, ...deleted].some(symbol => spoken.has(symbol)))
112
+ return undefined;
113
+ const key = exchangeKey(inserted, deleted);
114
+ const same = statedBefore.get(key);
115
+ const insert = inserts.map(reading => symbolOf(reading, proposed.id));
116
+ if (same)
117
+ return { ...same, ...(insert.length > 0 && { insert }), ...(deleted.length > 0 && { delete: deleted }) };
118
+ return {
119
+ type: 'edit',
120
+ id: v5(`edit|${id}|${key}`, ADOPTED),
121
+ ...(recoding && { editType: 'recoding' }),
122
+ motivation: unchecked,
123
+ ...(insert.length > 0 && { insert }),
124
+ ...(deleted.length > 0 && { delete: deleted })
125
+ };
126
+ };
127
+ // A recoding an edit of the editor's cuts into is stated reading by reading.
128
+ const whole = proposed.recodings.filter(recoding => ![...recoding.by.map(reading => symbolIdAt(reading, proposed.id)), ...recoding.replaced.map(reading => inheritedAt(reading, proposed.id))]
129
+ .some(symbol => spoken.has(symbol)));
130
+ const recoded = new Set(whole.flatMap(recoding => [...recoding.by, ...recoding.replaced]));
131
+ const edits = [
132
+ ...kept,
133
+ ...whole.map(recoding => made(recoding.by, recoding.replaced, true)),
134
+ ...proposed.inserts.filter(reading => !recoded.has(reading)).map(reading => made([reading], [], false)),
135
+ ...proposed.deletes.filter(reading => !recoded.has(reading)).map(reading => made([], [reading], false))
136
+ ].filter((edit) => edit !== undefined);
137
+ const parent = proposed.parent ? ids.get(proposed.parent) : undefined;
138
+ const principal = before && principalDerivationOf(before);
139
+ const derivation = parent === undefined
140
+ ? undefined
141
+ : principal && idOf(principal) === parent ? principal : assignReference(parent);
142
+ const hypotheses = (before?.basedOn ?? [])
143
+ .filter(other => other !== principal && staying.has(idOf(other)) && idOf(other) !== parent);
144
+ const basedOn = [...(derivation ? [derivation] : []), ...hypotheses];
145
+ const copy = proposed.copy ? edition.copies.find(copy => copy.id === proposed.copy) : undefined;
146
+ const bar = barNamed(proposed.system);
147
+ const system = copy?.production?.system ?? before?.system ?? (bar ? systemOf(bar) : { id: proposed.system, name: proposed.system, sameAs: [] });
148
+ const { basedOn: _, edits: __, ...rest } = before ?? {};
149
+ return {
150
+ ...rest,
151
+ id,
152
+ system,
153
+ ...(basedOn.length > 0 && { basedOn }),
154
+ edits,
155
+ motivations: declaring(before?.motivations ?? [], edits)
156
+ };
157
+ });
158
+ // A hypothesis may not name a version that now derives from the one stating it.
159
+ const pruned = versions.map(version => ({
160
+ ...version,
161
+ ...(version.basedOn && {
162
+ basedOn: version.basedOn.filter((derivation, index) => index === 0 || !derivesFrom(versions, idOf(derivation), version.id))
163
+ })
164
+ }));
165
+ const statedOnly = edition.versions.filter(version => {
166
+ if (staying.has(version.id) || editsOf(version).length > 0)
167
+ return false;
168
+ const principal = principalDerivationOf(version);
169
+ return principal !== undefined && staying.has(idOf(principal));
170
+ });
171
+ const removed = edition.versions.filter(version => !staying.has(version.id) && !statedOnly.includes(version)).map(version => version.id);
172
+ // The edition's order where a version stays, the new ones after it.
173
+ const rank = new Map(edition.versions.map((version, index) => [version.id, index]));
174
+ const ordered = [...pruned, ...statedOnly].sort((a, b) => (rank.get(a.id) ?? Infinity) - (rank.get(b.id) ?? Infinity));
175
+ return { versions: ordered, ids: Object.fromEntries(ids), removed, dropped: proposal.unpinned };
176
+ };
177
+ /**
178
+ * Puts the proposed stemma in place of the edition's, as `adoptionOf`
179
+ * describes, and takes back a copy's statement that it carries a version
180
+ * that goes. One operation, and so one undo step.
181
+ * @category Operations
182
+ */
183
+ export const adoptStemma = (proposal) => reading(edition => {
184
+ const { versions, removed } = adoptionOf(edition, proposal);
185
+ const gone = new Set(removed);
186
+ return draft => {
187
+ draft.versions = versions;
188
+ draft.copies.forEach(copy => dropStatements(copy, statement => gone.has(idOf(statement))));
189
+ };
190
+ });
@@ -0,0 +1,33 @@
1
+ /** What tells two states of a stemma apart, version by version. */
2
+ import { Edition } from "../model/Edition.js";
3
+ /**
4
+ * How a version differs between two states of the edition: whether it
5
+ * is in either, what it derives from in each, and which symbols it
6
+ * inserts and deletes in one and not the other.
7
+ * @category Filiation
8
+ */
9
+ export interface VersionChange {
10
+ version: string;
11
+ /** The version it derives from in the state before, where it is in that state. */
12
+ parentBefore?: string | null;
13
+ /** The version it derives from in the state after, where it is in that state. */
14
+ parentAfter?: string | null;
15
+ inserted: {
16
+ kept: number;
17
+ gone: string[];
18
+ added: string[];
19
+ };
20
+ deleted: {
21
+ kept: number;
22
+ gone: string[];
23
+ added: string[];
24
+ };
25
+ }
26
+ /**
27
+ * Version by version, how the stemma of one state of the edition differs
28
+ * from that of another, as when a proposed stemma is set against the one
29
+ * the edition holds. The versions of the state before come first, in
30
+ * their order, then those only the state after holds.
31
+ * @category Filiation
32
+ */
33
+ export declare const stemmaChanges: (before: Pick<Edition, 'versions'>, after: Pick<Edition, 'versions'>) => VersionChange[];
@@ -0,0 +1,38 @@
1
+ import { idOf } from "../model/Assumption.js";
2
+ import { deletedBy, insertedBy, principalDerivationOf } from "../model/Version.js";
3
+ const compared = (before, after) => {
4
+ const was = new Set(before);
5
+ const is = new Set(after);
6
+ return {
7
+ kept: after.filter(id => was.has(id)).length,
8
+ gone: before.filter(id => !is.has(id)),
9
+ added: after.filter(id => !was.has(id))
10
+ };
11
+ };
12
+ const parentOf = (version) => {
13
+ const principal = principalDerivationOf(version);
14
+ return principal ? idOf(principal) : null;
15
+ };
16
+ /**
17
+ * Version by version, how the stemma of one state of the edition differs
18
+ * from that of another, as when a proposed stemma is set against the one
19
+ * the edition holds. The versions of the state before come first, in
20
+ * their order, then those only the state after holds.
21
+ * @category Filiation
22
+ */
23
+ export const stemmaChanges = (before, after) => {
24
+ const was = new Map(before.versions.map(version => [version.id, version]));
25
+ const is = new Map(after.versions.map(version => [version.id, version]));
26
+ const ids = [...new Set([...was.keys(), ...is.keys()])];
27
+ return ids.map(id => {
28
+ const one = was.get(id);
29
+ const other = is.get(id);
30
+ return {
31
+ version: id,
32
+ ...(one && { parentBefore: parentOf(one) }),
33
+ ...(other && { parentAfter: parentOf(other) }),
34
+ inserted: compared(one ? insertedBy(one).map(symbol => symbol.id) : [], other ? insertedBy(other).map(symbol => symbol.id) : []),
35
+ deleted: compared(one ? deletedBy(one) : [], other ? deletedBy(other) : [])
36
+ };
37
+ });
38
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Filiation: proposing a stemma from the copies alone, by the editorial
3
+ * principles the rules of an edition state, and taking it into the
4
+ * edition.
5
+ */
6
+ export { collateCopies, readingsOfCopy, type CopyCollation, type CollatedReading, type CopyReading, type PairWindow, type Recoding } from "./readings.js";
7
+ export { presenceIn, ineffectiveAmong, defaultMargin, type Presence, type PresenceMatrix } from "./presence.js";
8
+ export { defaultCosts, type Costs, type Scored, type Support } from "./search.js";
9
+ export { transfersOf, systemAbove } from "./systems.js";
10
+ export { proposeStemma, defaultMovedWithin, type FiliationOptions, type ProposedVersion, type StemmaProposal, type Unpinned } from "./proposal.js";
11
+ export { adoptionOf, adoptStemma, type Adoption } from "./adoption.js";
12
+ export { stemmaChanges, type VersionChange } from "./changes.js";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Filiation: proposing a stemma from the copies alone, by the editorial
3
+ * principles the rules of an edition state, and taking it into the
4
+ * edition.
5
+ */
6
+ export { collateCopies, readingsOfCopy } from "./readings.js";
7
+ export { presenceIn, ineffectiveAmong, defaultMargin } from "./presence.js";
8
+ export { defaultCosts } from "./search.js";
9
+ export { transfersOf, systemAbove } from "./systems.js";
10
+ export { proposeStemma, defaultMovedWithin } from "./proposal.js";
11
+ export { adoptionOf, adoptStemma } from "./adoption.js";
12
+ export { stemmaChanges } from "./changes.js";