linked-rolls 0.64.0 → 0.65.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.
@@ -80,20 +80,19 @@ const typesNotOnTheBar = (version, snapshot) => {
80
80
  * out says 59. Collation across systems consults only the place, so
81
81
  * without this nothing checks the tracks at all.
82
82
  */
83
- const carriersOffTheirMeaning = (edition, version, commands) => {
84
- const misread = (carrier, symbol) => {
83
+ const carriedOffItsMeaning = (edition) => (symbol) => {
84
+ const key = keyOf(symbol);
85
+ return placedCarriersOf(edition, symbol).some(carrier => {
85
86
  const copy = copyOfFeature(edition, carrier.id);
86
- if (!copy)
87
- return false;
88
87
  // A carrier lying across several positions reads as several commands,
89
88
  // and carries the symbol as long as one of them is the symbol's.
90
- return !barOf(copy).meaningsOf(carrier.vertical)
91
- .some(meaning => keyOf(meaning) === keyOf(symbol));
92
- };
93
- return commands
94
- .filter(symbol => placedCarriersOf(edition, symbol).some(carrier => misread(carrier, symbol)))
95
- .map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
89
+ return copy !== undefined && !barOf(copy).meaningsOf(carrier.vertical).some(meaning => keyOf(meaning) === key);
90
+ });
96
91
  };
92
+ /** The commands the version shows among those carried off their meaning. */
93
+ const carriersOffTheirMeaning = (misread, version, commands) => commands
94
+ .filter(symbol => misread.has(symbol))
95
+ .map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
97
96
  /**
98
97
  * Where a copy of the version's own system strays from the others in
99
98
  * its paper by more than paper does.
@@ -104,13 +103,9 @@ const carriersOffTheirMeaning = (edition, version, commands) => {
104
103
  * speed, and averaging it in would hide both that and the fact that the
105
104
  * playback rests on a guess.
106
105
  */
107
- const paperDisagreed = (edition, version) => {
108
- const own = new Set(paperOfVersion(edition, version)?.copies ?? []);
109
- return alignmentProblems(edition)
110
- .some(({ copy, problem }) => problem === 'paper-beyond-its-spread' && own.has(copy))
111
- ? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
112
- : [];
113
- };
106
+ const paperDisagreed = (edition, strays, version) => paperOfVersion(edition, version)?.copies.some(copy => strays.has(copy))
107
+ ? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
108
+ : [];
114
109
  /**
115
110
  * A version's strikes that take nothing out of its text: each deleted
116
111
  * symbol has to be one the parent hands down, or one the version put
@@ -143,14 +138,22 @@ const strikesBitingNothing = (edition, version) => {
143
138
  * track that does not say what its symbol says, and a strike that takes
144
139
  * nothing out.
145
140
  */
146
- export const constraintProblems = (edition) => edition.versions.flatMap(version => {
147
- const snapshot = snapshotOf(edition, version.id);
148
- const commands = snapshot.filter(isCommand);
149
- return [
150
- ...problemsIn(version.id, commands),
151
- ...typesNotOnTheBar(version, snapshot),
152
- ...carriersOffTheirMeaning(edition, version.id, commands),
153
- ...paperDisagreed(edition, version),
154
- ...strikesBitingNothing(edition, version)
155
- ];
156
- });
141
+ export const constraintProblems = (edition) => {
142
+ // What a symbol's carriers and the copies' paper say holds whichever
143
+ // version shows them, so it is asked once for the edition.
144
+ const misread = new Set(edition.versions.flatMap(insertedBy).filter(isCommand).filter(carriedOffItsMeaning(edition)));
145
+ const strays = new Set(alignmentProblems(edition)
146
+ .filter(({ problem }) => problem === 'paper-beyond-its-spread')
147
+ .map(({ copy }) => copy));
148
+ return edition.versions.flatMap(version => {
149
+ const snapshot = snapshotOf(edition, version.id);
150
+ const commands = snapshot.filter(isCommand);
151
+ return [
152
+ ...problemsIn(version.id, commands),
153
+ ...typesNotOnTheBar(version, snapshot),
154
+ ...carriersOffTheirMeaning(misread, version.id, commands),
155
+ ...paperDisagreed(edition, strays, version),
156
+ ...strikesBitingNothing(edition, version)
157
+ ];
158
+ });
159
+ };
@@ -2,6 +2,7 @@ import { certaintyOf, idOf, idsOf } from "../model/Assumption.js";
2
2
  import { insertedBy } from "../model/Version.js";
3
3
  import { lineageOf } from "./stemma.js";
4
4
  import { copyOfFeature, versionIn } from "../lookup/lookup.js";
5
+ import { perState } from "../lookup/perState.js";
5
6
  /** The copies whose features carry any of the symbols. */
6
7
  const copiesCarrying = (edition, symbols) => new Set(symbols
7
8
  .flatMap(symbol => idsOf(symbol.carriers))
@@ -17,19 +18,21 @@ const carriagesIn = (edition) => edition.versions.flatMap(version => {
17
18
  .filter(copy => carrying.has(copy.id))
18
19
  .map((copy) => ({ copy: copy.id, version: version.id, depth }));
19
20
  });
20
- const carriersIn = (edition) => {
21
+ /** What the copies' features attest, worked out once for each state of the edition. */
22
+ const carriersIn = perState((edition) => {
21
23
  const carriages = carriagesIn(edition);
24
+ const byVersion = Map.groupBy(carriages, carriage => carriage.version);
22
25
  return {
23
26
  copiesOf: new Map(edition.versions.map(version => [
24
27
  version.id,
25
- carriages.filter(carriage => carriage.version === version.id).map(carriage => carriage.copy)
28
+ (byVersion.get(version.id) ?? []).map(carriage => carriage.copy)
26
29
  ])),
27
30
  // Written in order of depth, so a copy's latest carriage is set last and stands.
28
31
  latestOf: new Map([...carriages]
29
32
  .sort((one, other) => one.depth - other.depth)
30
33
  .map(carriage => [carriage.copy, carriage.version]))
31
34
  };
32
- };
35
+ });
33
36
  /**
34
37
  * The versions a copy's features carry at first hand: for every copy,
35
38
  * the latest state it bears. A version left out is reached only through
@@ -1,6 +1,7 @@
1
1
  import { featuresOf, isTear, tearsOf } from "../model/RollCopy.js";
2
2
  import { defaultTrackerBar } from "../systems/index.js";
3
3
  import { add, mm, subtract, track } from "../model/Quantity.js";
4
+ import { partitionPoint } from "../shared/sorted.js";
4
5
  /** A place on the copy's own paper, on the edition's axis. */
5
6
  export const toAxis = (alignment) => (place) => alignment ? mm((place + alignment.shift.horizontal) * alignment.scale) : place;
6
7
  /** A place on the edition's axis, on the copy's own paper. */
@@ -217,12 +218,41 @@ const noteOnsets = (features, bar) => features
217
218
  .map(meaning => ({ pitch: meaning.pitch, at: feature.horizontal.from }));
218
219
  })
219
220
  .sort(byPlace);
221
+ /**
222
+ * The middle one of the values, the upper of the two where their number
223
+ * is even. It is selected rather than the values sorted (Hoare's FIND,
224
+ * as Wirth writes it): a long roll gives over a million slopes, and only
225
+ * the one in the middle is wanted. The values are reordered where they
226
+ * lie.
227
+ */
220
228
  const median = (values) => {
221
- const sorted = [...values].sort((x, y) => x - y);
222
- return sorted[Math.floor(sorted.length / 2)] ?? 0;
229
+ const middle = values.length >> 1;
230
+ let low = 0;
231
+ let high = values.length - 1;
232
+ while (low < high) {
233
+ const pivot = values[middle];
234
+ let i = low;
235
+ let j = high;
236
+ while (i <= j) {
237
+ while (values[i] < pivot)
238
+ i++;
239
+ while (pivot < values[j])
240
+ j--;
241
+ if (i <= j) {
242
+ const swapped = values[i];
243
+ values[i] = values[j];
244
+ values[j] = swapped;
245
+ i++;
246
+ j--;
247
+ }
248
+ }
249
+ if (j < middle)
250
+ low = i;
251
+ if (middle < i)
252
+ high = j;
253
+ }
254
+ return values[middle] ?? 0;
223
255
  };
224
- /** The slope of the line through two matches, or nothing where they share a place on the copy. */
225
- const slopeBetween = (m, n) => n.a === m.a ? [] : [(n.b - m.b) / (n.a - m.a)];
226
256
  /** Matches beyond this are thinned before the slopes are taken, whose number grows with the square. */
227
257
  const SLOPE_SAMPLE = 1500;
228
258
  /**
@@ -234,25 +264,27 @@ const SLOPE_SAMPLE = 1500;
234
264
  const robustLine = (matches) => {
235
265
  const step = Math.max(1, Math.ceil(matches.length / SLOPE_SAMPLE));
236
266
  const sample = matches.filter((_, i) => i % step === 0);
237
- const slopes = sample.flatMap((m, i) => sample.slice(i + 1).flatMap(n => slopeBetween(m, n)));
238
- if (slopes.length === 0)
267
+ const slopes = new Float64Array(sample.length * (sample.length - 1) / 2);
268
+ let count = 0;
269
+ sample.forEach((m, i) => {
270
+ for (let j = i + 1; j < sample.length; j++) {
271
+ const n = sample[j];
272
+ // Two matches at one place on the copy give no slope.
273
+ if (n.a !== m.a)
274
+ slopes[count++] = (n.b - m.b) / (n.a - m.a);
275
+ }
276
+ });
277
+ if (count === 0)
239
278
  return undefined;
240
- const slope = median(slopes);
241
- return { slope, intercept: median(matches.map(m => m.b - slope * m.a)) };
279
+ const slope = median(slopes.subarray(0, count));
280
+ return { slope, intercept: median(Float64Array.from(matches, m => m.b - slope * m.a)) };
242
281
  };
243
282
  const placed = (line, a) => line.slope * a + line.intercept;
244
283
  const residualOf = (line, match) => match.b - placed(line, match.a);
245
284
  /** Pitches in a row that pin down a place on a roll, provided the row occurs once. */
246
285
  const ANCHOR_LENGTH = 6;
247
286
  /** Every run of `length` pitches, keyed by the run, with the onsets it starts at. */
248
- const runsOf = (onsets, length) => {
249
- const runs = new Map();
250
- onsets.slice(0, Math.max(0, onsets.length - length + 1)).forEach((start, i) => {
251
- const key = onsets.slice(i, i + length).map(onset => onset.pitch).join(',');
252
- runs.set(key, [...(runs.get(key) ?? []), start]);
253
- });
254
- return runs;
255
- };
287
+ const runsOf = (onsets, length) => Map.groupBy(onsets.slice(0, Math.max(0, onsets.length - length + 1)), (_, i) => onsets.slice(i, i + length).map(onset => onset.pitch).join(','));
256
288
  /** The places a run of pitches found once on each roll ties together. */
257
289
  const anchors = (a, b) => {
258
290
  const runsB = runsOf(b, ANCHOR_LENGTH);
@@ -277,13 +309,16 @@ const pairedNearestFirst = (candidates) => {
277
309
  }
278
310
  return matches;
279
311
  };
312
+ /** Widens the window by a hair before it is searched, so that rounding in its bounds cannot leave out what the distance admits. */
313
+ const WINDOW_SLACK = 1e-9;
280
314
  /** Each onset of A paired with the nearest onset of B of its pitch within the window around where the line puts it. */
281
315
  const nearestMatches = (a, b, line, window) => {
282
- const bByPitch = new Map();
283
- b.forEach(onset => bByPitch.set(onset.pitch, [...(bByPitch.get(onset.pitch) ?? []), onset]));
284
316
  const candidates = a.flatMap(onset => {
285
317
  const expected = placed(line, onset.at);
286
- return (bByPitch.get(onset.pitch) ?? [])
318
+ const ofPitch = b.get(onset.pitch) ?? [];
319
+ const first = partitionPoint(ofPitch, other => other.at < expected - window - WINDOW_SLACK);
320
+ const end = partitionPoint(ofPitch, other => other.at <= expected + window + WINDOW_SLACK);
321
+ return ofPitch.slice(first, end)
287
322
  .map(other => ({ distance: Math.abs(other.at - expected), a: onset, b: other }))
288
323
  .filter(candidate => candidate.distance <= window);
289
324
  });
@@ -341,5 +376,5 @@ export function alignFeatures(rollA, rollB, barA = defaultTrackerBar, barB = bar
341
376
  const coarse = robustLine(anchors(a, b));
342
377
  if (!coarse)
343
378
  return undefined;
344
- return resultOf(WINDOWS.reduce(settled(a, b), { line: coarse, matches: [] }));
379
+ return resultOf(WINDOWS.reduce(settled(a, Map.groupBy(b, onset => onset.pitch)), { line: coarse, matches: [] }));
345
380
  }
@@ -66,7 +66,7 @@ export interface ScatterOptions {
66
66
  groupOf?: Grouping;
67
67
  /** The width of the histogram's bins. */
68
68
  binWidth?: Millimeters;
69
- /** The tolerance the edition states at present, against which each departure is reported as still admitted or not. */
69
+ /** The tolerance the derivation states at present, against which each departure is reported as still admitted or not. */
70
70
  stated?: CollationTolerance;
71
71
  }
72
72
  /** A reading the calculated tolerance does not admit. */
@@ -90,7 +90,7 @@ export interface Departure {
90
90
  */
91
91
  separatedBy: BothEnds<boolean>;
92
92
  /**
93
- * Whether the tolerance the edition states at present still admits
93
+ * Whether the tolerance the derivation states at present still admits
94
94
  * it. Absent where none was given to compare against.
95
95
  *
96
96
  * This sees one direction only. A departure is by definition a
@@ -80,23 +80,19 @@ const changing = (copyId) => (act) => {
80
80
  return { ...act, diminished: { '@id': copyId } };
81
81
  return act;
82
82
  };
83
+ /** The copy with what each of its acts changed stated. */
84
+ const withChangesOf = (copy) => isRecord(copy) && Array.isArray(copy.modifications)
85
+ ? { ...copy, modifications: copy.modifications.map(changing(copy['@id'])) }
86
+ : copy;
83
87
  /**
84
88
  * The document with what each act changed stated. Which of P110
85
89
  * augmented and P112 diminished applies depends on the class of the
86
90
  * act, which no property chain in OWL 2 RL can test, so the export
87
91
  * states it. What a copy or a patch bears is left to a reasoner (see
88
- * the entailments in reo.ttl).
92
+ * the entailments in reo.ttl). The copies are the only nodes that are
93
+ * modified, and the edition holds them in its list of copies alone.
89
94
  */
90
- const withChanges = (value) => {
91
- if (Array.isArray(value))
92
- return value.map(withChanges);
93
- if (!isRecord(value))
94
- return value;
95
- const node = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, withChanges(child)]));
96
- return node['@type'] === 'RollCopy' && Array.isArray(node.modifications)
97
- ? { ...node, modifications: node.modifications.map(changing(node['@id'])) }
98
- : node;
99
- };
95
+ const withChanges = (edition) => Array.isArray(edition.copies) ? { ...edition, copies: edition.copies.map(withChangesOf) } : edition;
100
96
  /** Terms the context sets to null, which say nothing when the edition is read as RDF. */
101
97
  const silentTerms = new Set(Object.entries(context['@context'])
102
98
  .filter(([, definition]) => definition === null)
@@ -120,21 +116,24 @@ const quote = (subject, key, reference, listed) => {
120
116
  const { '@annotation': { '@id': annotation, ...about }, ...object } = reference;
121
117
  return { '@id': { '@id': subject, [key]: listed ? [object] : object }, annotation, ...about };
122
118
  };
123
- /** The value a node states under a key, less the doubted references, and those references quoted. */
124
- const quotingValue = (subject, key, value) => {
119
+ /** The value a node states under a key, less the doubted references, which go to the quoted. */
120
+ const unquoted = (subject, key, value, quoted) => {
125
121
  if (Array.isArray(value)) {
126
- return {
127
- node: value.filter(item => !isDoubtedReference(item)),
128
- quoted: value.filter(isDoubtedReference).map(item => quote(subject, key, item, true))
129
- };
122
+ return value.filter(item => {
123
+ if (!isDoubtedReference(item))
124
+ return true;
125
+ quoted.push(quote(subject, key, item, true));
126
+ return false;
127
+ });
130
128
  }
131
- return isDoubtedReference(value)
132
- ? { node: undefined, quoted: [quote(subject, key, value, false)] }
133
- : { node: value, quoted: [] };
129
+ if (!isDoubtedReference(value))
130
+ return value;
131
+ quoted.push(quote(subject, key, value, false));
132
+ return undefined;
134
133
  };
135
134
  /**
136
135
  * The document with every doubted reference taken off the node that
137
- * states it, and quoted instead.
136
+ * states it, and quoted instead, in the order the document states them.
138
137
  *
139
138
  * An `@annotation` in JSON-LD-star states the triple it annotates and
140
139
  * then says something about it, so a statement the edition holds
@@ -142,32 +141,28 @@ const quotingValue = (subject, key, value) => {
142
141
  * between nodes are quoted, since only they can be put back where they
143
142
  * stood; a doubted date or attribution stays annotated in place.
144
143
  */
145
- const withDoubtedReferencesQuoted = (value) => {
146
- if (Array.isArray(value)) {
147
- const quotings = value.map(withDoubtedReferencesQuoted);
148
- return { node: quotings.map(({ node }) => node), quoted: quotings.flatMap(({ quoted }) => quoted) };
149
- }
144
+ const withDoubtedReferencesQuoted = (value, quoted) => {
145
+ if (Array.isArray(value))
146
+ return value.map(item => withDoubtedReferencesQuoted(item, quoted));
150
147
  if (!isRecord(value))
151
- return { node: value, quoted: [] };
148
+ return value;
152
149
  const subject = value['@id'];
153
- const entries = Object.entries(value).map(([key, child]) => {
154
- const own = typeof subject === 'string' && !key.startsWith('@')
155
- ? quotingValue(subject, key, child)
156
- : { node: child, quoted: [] };
157
- const below = withDoubtedReferencesQuoted(own.node);
158
- return { key, node: below.node, quoted: [...own.quoted, ...below.quoted] };
150
+ const node = {};
151
+ Object.entries(value).forEach(([key, child]) => {
152
+ const stated = typeof subject === 'string' && !key.startsWith('@') ? unquoted(subject, key, child, quoted) : child;
153
+ const below = withDoubtedReferencesQuoted(stated, quoted);
154
+ if (below !== undefined)
155
+ node[key] = below;
159
156
  });
160
- return {
161
- node: Object.fromEntries(entries.filter(({ node }) => node !== undefined).map(({ key, node }) => [key, node])),
162
- quoted: entries.flatMap(({ quoted }) => quoted)
163
- };
157
+ return node;
164
158
  };
165
159
  /** The edition with each copy's features and tears at the copy's own places, as a document holds them. */
166
160
  const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
167
161
  ? { ...edition, copies: edition.copies.map(atOwnPlaces) }
168
162
  : edition;
169
163
  export const asJsonLd = (edition) => {
170
- const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))));
164
+ const quoted = [];
165
+ const node = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))), quoted);
171
166
  // The context is the export's own; one carried in from an import must not override it.
172
167
  const { base, '@context': carried, ...rest } = node;
173
168
  return {
package/lib/io/migrate.js CHANGED
@@ -2,6 +2,7 @@ import { systemIdIn, systemOf, translationBetween } from "../systems/TrackerBar.
2
2
  import { trackerBars } from "../systems/index.js";
3
3
  import { welteT100 } from "../systems/welteT100/bar.js";
4
4
  import { isDateString } from "../shared/utils.js";
5
+ import { mapped, withValues } from "../ops/immutable.js";
5
6
  const retiredVersionTypes = new Set(['edition', 'unicum']);
6
7
  /**
7
8
  * The vocabulary the steps below were written against, as it stood in
@@ -37,10 +38,10 @@ const referenceKeys = ['alignedWith', 'pairedWith', 'basedOn'];
37
38
  * The five type terms that were capitalised while the rest of the
38
39
  * vocabulary was not, under the keys that carried them then.
39
40
  */
40
- const lowerCasedTerms = {
41
+ const lowerCasedTerms = Object.entries({
41
42
  method: { Print: 'print', Handwriting: 'handwriting', Stamp: 'stamp' },
42
43
  material: { Paper: 'paper', Tape: 'tape' }
43
- };
44
+ });
44
45
  const named = (name) => ({ name, sameAs: [] });
45
46
  const withRenamedKeys = (node) => Object.keys(node).some(key => Object.hasOwn(renamedKeys, key))
46
47
  ? Object.fromEntries(Object.entries(node).map(([key, value]) => [renamedKeys[key] ?? key, value]))
@@ -82,7 +83,7 @@ const withoutVersionSiglum = (node) => {
82
83
  const { siglum: _labelled, ...rest } = node;
83
84
  return rest;
84
85
  };
85
- const withLowerCaseTerms = (node) => Object.entries(lowerCasedTerms).reduce((result, [key, terms]) => {
86
+ const withLowerCaseTerms = (node) => lowerCasedTerms.reduce((result, [key, terms]) => {
86
87
  const lowered = terms[result[key]];
87
88
  return lowered ? { ...result, [key]: lowered } : result;
88
89
  }, node);
@@ -450,26 +451,16 @@ const withTimeSpanDates = (node) => {
450
451
  const { '@value': within, '@type': _typed, ...rest } = node;
451
452
  return { ...rest, within };
452
453
  };
453
- const migrateNode = (node) => [withRenamedKeys, withRenamedEditType, withTypology, withRenamedType, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
454
+ const nodeSteps = [withRenamedKeys, withRenamedEditType, withTypology, withRenamedType, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
454
455
  withPerforator, withProductionNodes, withTypedPerforator, withScale, withoutOps, withDerivationList, withReadingKind, withTimeSpanDates,
455
- withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withOwnPlaces, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType]
456
- .reduce((result, step) => step(result), node);
457
- /** The items each walked, or the very same list where the walk changed none. */
458
- const walked = (items) => {
459
- const result = items.map(walk);
460
- return result.every((item, i) => item === items[i]) ? items : result;
461
- };
462
- /** The node with each child walked, or the very same node where the walk changed none. */
463
- const withWalkedChildren = (node) => {
464
- const entries = Object.entries(node);
465
- const result = entries.map(([key, child]) => [key, walk(child)]);
466
- return result.every(([, child], i) => child === entries[i][1]) ? node : Object.fromEntries(result);
467
- };
456
+ withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withOwnPlaces, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType];
457
+ const migrateNode = (node) => nodeSteps.reduce((result, step) => step(result), node);
458
+ /** The value with every node in it migrated, a node before its children; the very same value where nothing changed. */
468
459
  const walk = (value) => {
469
460
  if (Array.isArray(value))
470
- return walked(value);
461
+ return mapped(value, walk);
471
462
  if (value && typeof value === 'object')
472
- return withWalkedChildren(migrateNode(value));
463
+ return withValues(migrateNode(value), walk);
473
464
  return value;
474
465
  };
475
466
  /** The production of a copy, under whichever of its two names it carries. */
@@ -576,18 +567,21 @@ const statesNoTolerance = (version) => isSingleDerivation(version.basedOn) && !v
576
567
  * The collation tolerance was the edition's before it was stated on
577
568
  * each derivation. An edition written then collated every version at
578
569
  * that one value, so it is written onto every derivation that gives
579
- * none of its own.
570
+ * none of its own, and the edition states it no longer.
580
571
  */
581
572
  const withDerivationTolerance = (edition) => {
582
- const collationTolerance = edition.creation?.collationTolerance;
583
- const versions = Array.isArray(edition.versions) ? edition.versions : [];
584
- if (!collationTolerance || !versions.some(statesNoTolerance))
573
+ if (!edition.creation || !Object.hasOwn(edition.creation, 'collationTolerance'))
585
574
  return edition;
575
+ const { collationTolerance, ...creation } = edition.creation;
576
+ const versions = listAt(edition, 'versions');
586
577
  return {
587
578
  ...edition,
588
- versions: versions.map(version => statesNoTolerance(version)
589
- ? { ...version, basedOn: { ...version.basedOn, collationTolerance } }
590
- : version)
579
+ creation,
580
+ ...(collationTolerance && versions && {
581
+ versions: versions.map(version => statesNoTolerance(version)
582
+ ? { ...version, basedOn: { ...version.basedOn, collationTolerance } }
583
+ : version)
584
+ })
591
585
  };
592
586
  };
593
587
  /** Release 0.58.0 stated a revision of the format, which the format does not state. */
@@ -1,6 +1,5 @@
1
1
  import { RollCopy } from "./RollCopy.js";
2
2
  import { Version } from "./Version.js";
3
- import { CollationTolerance } from "../collation/Collation.js";
4
3
  import { DateAssignment, ObjectAssumption } from "./Assumption.js";
5
4
  import { Editor, Person, Place } from "./Agent.js";
6
5
  import { RollTempo } from "../systems/ReproducingSystem.js";
@@ -28,18 +27,6 @@ export interface EditionCreation {
28
27
  * @see dcterms:date
29
28
  */
30
29
  publicationDate: Date;
31
- /**
32
- * The tolerance every version of the edition was collated at,
33
- * before each derivation stated its own. How precisely two copies
34
- * place a symbol depends on which two they are, so the tolerance
35
- * belongs to the derivation, `Derivation.collationTolerance`, and
36
- * migration writes this one onto every derivation that gives none.
37
- * Nothing else reads it. A window's offset is the displacement
38
- * between two particular copies and so can never be stated here.
39
- * Not exported to RDF.
40
- * @deprecated
41
- */
42
- collationTolerance?: CollationTolerance;
43
30
  }
44
31
  /**
45
32
  * Describes the event of recording and documents
@@ -5,7 +5,7 @@ import { collationToleranceOf, derivesFrom, editsOf, insertedBy, principalDeriva
5
5
  import { substitutionsBetween } from "../collation/substitution.js";
6
6
  import { trackerBarOf } from "../systems/index.js";
7
7
  import { assignReference, idOf } from "../model/Assumption.js";
8
- import { noChange, onVersion, stateOf, insertion, deletion, asUnchecked, declaring, insertedIn, dropInsertions, reading } from "./draft.js";
8
+ import { noChange, onVersion, insertion, deletion, asUnchecked, declaring, dropInsertions, reading } from "./draft.js";
9
9
  import { hypothesesBeside } from "./versions.js";
10
10
  import { placeOf, snapshotOf } from "../analysis/text.js";
11
11
  import { copyOfFeature, versionIn } from "../lookup/lookup.js";
@@ -114,14 +114,18 @@ export const connectVersions = (childId, parentId, tolerance = defaultCollationT
114
114
  .filter(symbol => !matched.has(symbol.id) && !paired.has(symbol.id))
115
115
  .map(symbol => asUnchecked(deletion(symbol.id)))
116
116
  ];
117
- return onVersion(childId, (child, draft) => {
117
+ // Read off the child as it stands, since once its edits are written a
118
+ // reading of it would copy them all.
119
+ const motivations = declaring(child?.motivations ?? [], edits);
120
+ const basedOn = [
121
+ { ...assignReference(parentId), collationTolerance: tolerance },
122
+ ...(child ? hypothesesBeside(child, parentId) : [])
123
+ ];
124
+ return onVersion(childId, (version, draft) => {
118
125
  handOverCarriers(edition, draft, collations);
119
- child.edits = edits;
120
- child.motivations = declaring(stateOf(child).motivations, edits);
121
- child.basedOn = [
122
- { ...assignReference(parentId), collationTolerance: tolerance },
123
- ...hypothesesBeside(stateOf(child), parentId)
124
- ];
126
+ version.edits = edits;
127
+ version.motivations = motivations;
128
+ version.basedOn = basedOn;
125
129
  });
126
130
  });
127
131
  /**
@@ -135,7 +139,7 @@ export const collateSymbols = (versionId, symbolIds, tolerance) => reading(editi
135
139
  if (!version || !principal)
136
140
  return noChange;
137
141
  const chosen = new Set(symbolIds);
138
- const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
142
+ const own = insertedBy(version).filter(symbol => chosen.has(symbol.id));
139
143
  const collations = collationsOf(own, snapshotOf(edition, idOf(principal)), symbol => placeOf(edition, symbol), tolerance ?? collationToleranceOf(principal));
140
144
  const collated = new Set(collations.map(({ symbol }) => symbol.id));
141
145
  return onVersion(versionId, (version, draft) => {
package/lib/ops/copies.js CHANGED
@@ -83,9 +83,17 @@ export const alignCopies = (date = new Date()) => draft => {
83
83
  const own = draft.copies.find(c => c.id === reference.id);
84
84
  if (own)
85
85
  revertAlignment(own);
86
- stateOf(draft).copies
87
- .filter(copy => copy.id !== reference.id && featuresOf(copy).length > 0)
88
- .forEach(copy => alignCopy(copy.id, date)(draft));
86
+ // Aligning a copy moves neither the reference copy nor any other,
87
+ // so every copy is aligned from the one state. Read afresh after
88
+ // each, the draft would be copied with every copy aligned so far.
89
+ const state = stateOf(draft);
90
+ state.copies.forEach((copy, i) => {
91
+ if (copy.id === reference.id || featuresOf(copy).length === 0)
92
+ return;
93
+ const alignment = alignmentFor(state, copy, date);
94
+ if (alignment)
95
+ applyAlignment(alignment, draft.copies[i]);
96
+ });
89
97
  };
90
98
  /**
91
99
  * Makes the copy the one whose millimetres are the edition's axis, and
@@ -1,7 +1,11 @@
1
1
  /** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
2
2
  /** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
3
3
  export declare const without: <T>(items: T[], matches: (item: T) => boolean) => T[];
4
- /** The items each changed, or the very same array when the change left every one as it was. */
4
+ /**
5
+ * The items each changed, or the very same array when the change left
6
+ * every one as it was. The array is copied at the first item that
7
+ * changes, so that one left as it was costs no copy.
8
+ */
5
9
  export declare const mapped: <T>(items: T[], change: (item: T) => T) => T[];
6
10
  /** The items each changed, less those the change emptied; the very same array where it changed none. */
7
11
  export declare const pruned: <T>(items: T[], change: (item: T) => T, emptied: (item: T) => boolean) => T[];
@@ -10,6 +14,8 @@ export declare const replacing: <T extends object, K extends keyof T>(record: T,
10
14
  /** A record of the edition, as the walk over it sees one. */
11
15
  export type Node = Record<string, unknown>;
12
16
  export declare const isRecord: (value: unknown) => value is Node;
17
+ /** The record's values each changed, or the very same record where the change left every one as it was, copied as `mapped` copies. */
18
+ export declare const withValues: (record: Node, change: (value: unknown) => unknown) => Node;
13
19
  /** The value with the change applied to every record within it and then to itself, innermost first. */
14
20
  export declare const deeply: (change: (record: object) => object) => (value: unknown) => unknown;
15
21
  /** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
@@ -1,10 +1,19 @@
1
1
  /** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
2
2
  /** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
3
3
  export const without = (items, matches) => items.some(matches) ? items.filter(item => !matches(item)) : items;
4
- /** The items each changed, or the very same array when the change left every one as it was. */
4
+ /**
5
+ * The items each changed, or the very same array when the change left
6
+ * every one as it was. The array is copied at the first item that
7
+ * changes, so that one left as it was costs no copy.
8
+ */
5
9
  export const mapped = (items, change) => {
6
- const changed = items.map(change);
7
- return changed.every((item, i) => item === items[i]) ? items : changed;
10
+ let changed;
11
+ items.forEach((item, i) => {
12
+ const after = change(item);
13
+ if (after !== item)
14
+ (changed ?? (changed = items.slice()))[i] = after;
15
+ });
16
+ return changed ?? items;
8
17
  };
9
18
  /** The items each changed, less those the change emptied; the very same array where it changed none. */
10
19
  export const pruned = (items, change, emptied) => {
@@ -14,11 +23,16 @@ export const pruned = (items, change, emptied) => {
14
23
  /** The record with the field replaced, or the very same record where that is what stood there. */
15
24
  export const replacing = (record, key, value) => record[key] === value ? record : { ...record, [key]: value };
16
25
  export const isRecord = (value) => typeof value === 'object' && value !== null;
17
- /** The record's values each changed, or the very same record where the change left every one as it was. */
18
- const withValues = (record, change) => {
19
- const entries = Object.entries(record);
20
- const changed = entries.map(([key, value]) => [key, change(value)]);
21
- return changed.every(([, value], i) => value === entries[i][1]) ? record : Object.fromEntries(changed);
26
+ /** The record's values each changed, or the very same record where the change left every one as it was, copied as `mapped` copies. */
27
+ export const withValues = (record, change) => {
28
+ let changed;
29
+ Object.keys(record).forEach(key => {
30
+ const value = record[key];
31
+ const after = change(value);
32
+ if (after !== value)
33
+ (changed ?? (changed = { ...record }))[key] = after;
34
+ });
35
+ return changed ?? record;
22
36
  };
23
37
  /** The value with the change applied to every record within it and then to itself, innermost first. */
24
38
  export const deeply = (change) => {
@@ -129,12 +129,6 @@ const controlOffsetOf = (system, given) => {
129
129
  }
130
130
  return measured;
131
131
  };
132
- /** The position each note of the bar sounds from. */
133
- const positionsByPitch = (bar) => new Map(Array.from({ length: bar.trackCount }, (_, index) => track(index + 1))
134
- .flatMap((position) => {
135
- const meaning = bar.meaningOf(position);
136
- return meaning?.type === 'note' ? [[meaning.pitch, position]] : [];
137
- }));
138
132
  /**
139
133
  * Reads one of his e-roll files as a copy of the roll, in millimetres
140
134
  * of paper, on the bar it was cut for and in that bar's numbering. A
@@ -146,8 +140,7 @@ export function readFromPhillipsEroll(buffer, { system = welteT100, controlOffse
146
140
  const elapsedAt = clockOf(temposIn(timed), file.header.ticksPerBeat);
147
141
  const place = placeAt ?? atStatedSpeed(system);
148
142
  const offset = controlOffsetOf(system, controlOffset);
149
- const notePositions = positionsByPitch(system);
150
- const positionOf = (number) => notePositions.get(number) ?? track(number - offset);
143
+ const positionOf = (number) => system.positionOf({ type: 'note', pitch: number }) ?? track(number - offset);
151
144
  const features = notesIn(timed).flatMap((note) => {
152
145
  const position = positionOf(note.pitch);
153
146
  if (!system.meaningOf(position))
@@ -3,6 +3,7 @@ import { AtonParser } from "./AtonParser.js";
3
3
  import { systemOf } from "../systems/TrackerBar.js";
4
4
  import { welteT100 } from "../systems/welteT100/bar.js";
5
5
  import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../model/Quantity.js";
6
+ import { medianOf } from "../collation/statistics.js";
6
7
  /** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
7
8
  const readPx = (value) => px(parseFloat(value));
8
9
  /** The parser writes one record as an object and several as an array. */
@@ -20,11 +21,6 @@ const chainedBadHoles = (holes, calibration) => holes
20
21
  ...hole,
21
22
  TRACKER_HOLE: `${Math.round((readPx(hole.CENTROID_COL) - calibration.offset) / calibration.separation)}`
22
23
  }));
23
- const median = (values) => {
24
- const sorted = [...values].sort((a, b) => a - b);
25
- const middle = Math.floor(sorted.length / 2);
26
- return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
27
- };
28
24
  const mostFrequent = (values) => {
29
25
  const counts = values.reduce((acc, value) => acc.set(value, (acc.get(value) || 0) + 1), new Map());
30
26
  return [...counts].sort(([, a], [, b]) => b - a)[0]?.[0];
@@ -76,7 +72,7 @@ const calibrationShiftIn = (holes, bar) => {
76
72
  const gridOffsetOf = (holes, separation, stated) => {
77
73
  if (stated !== undefined)
78
74
  return readPx(stated);
79
- return px(median(holes.map(hole => readPx(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation)));
75
+ return medianOf(holes.map(hole => px(readPx(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation))) ?? px(0);
80
76
  };
81
77
  const chainsAmong = (holes) => holes
82
78
  .filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
package/lib/schema.json CHANGED
@@ -272,32 +272,6 @@
272
272
  ],
273
273
  "type": "string"
274
274
  },
275
- "CollationTolerance": {
276
- "description": "The window two readings of one feature must fall in to be collated: how far apart they may lie at either end, and where that window is centred.\n\nThe centre is worth stating. Two copies of one roll differ by a systematic offset as well as by scatter, and a window centred on nothing has to be widened by the whole of that offset before it admits what the offset alone displaces. Naming the offset lets the tolerance stand for the scatter only. A window that names none is centred on nothing, which is what every window written before the offset was held here means.",
277
- "properties": {
278
- "offsetEnd": {
279
- "$ref": "#/definitions/Millimeters",
280
- "description": "The same at the end of a feature, in the same direction."
281
- },
282
- "offsetStart": {
283
- "$ref": "#/definitions/Millimeters",
284
- "description": "How much later the derived version puts the start of a feature than the version it is read against, where the two differ systematically. Nothing where they do not.\n\nThe direction is the one a collation measures in, the child less the parent, and it is not symmetric: stored the other way round the window sits on the wrong side of the readings and merges what it should separate. A measurement taken over the parent's copies gives the negative of this, which is what `toleranceAcross` asks the side for."
285
- },
286
- "toleranceEnd": {
287
- "$ref": "#/definitions/Millimeters",
288
- "description": "How far they may lie apart at the end, measured from `offsetEnd`."
289
- },
290
- "toleranceStart": {
291
- "$ref": "#/definitions/Millimeters",
292
- "description": "How far the two readings may lie apart at the start of a feature, measured from `offsetStart`."
293
- }
294
- },
295
- "required": [
296
- "toleranceStart",
297
- "toleranceEnd"
298
- ],
299
- "type": "object"
300
- },
301
275
  "Concept": {
302
276
  "anyOf": [
303
277
  {
@@ -671,11 +645,6 @@
671
645
  "EditionCreation": {
672
646
  "description": "This type describes the creation of an edition, i.e. the editor, publisher, and publication date.",
673
647
  "properties": {
674
- "collationTolerance": {
675
- "$ref": "#/definitions/CollationTolerance",
676
- "deprecated": true,
677
- "description": "The tolerance every version of the edition was collated at, before each derivation stated its own. How precisely two copies place a symbol depends on which two they are, so the tolerance belongs to the derivation, `Derivation.collationTolerance`, and migration writes this one onto every derivation that gives none. Nothing else reads it. A window's offset is the displacement between two particular copies and so can never be stated here. Not exported to RDF."
678
- },
679
648
  "editors": {
680
649
  "description": "The persons who prepared the edition, each with the part they took in the editorial work. An edition written before this field existed carries none.",
681
650
  "items": {
@@ -42,6 +42,23 @@ export type WelteOptions = {
42
42
  };
43
43
  /** Whether two sets of constants are the same, value for value. */
44
44
  export declare const sameParameters: (a: Parameters, b: Parameters) => boolean;
45
+ /**
46
+ * The two readings of the pedal mechanism the emulator offers, by name,
47
+ * which the systems share: Hagmann has the valves and bellows that carry
48
+ * out the movements "in beiden Systemen dieselbe" (p. 106), and only the
49
+ * Vorpneumatik differs. Under `damping` the dampers reach the strings
50
+ * within the shortest lift the rolls punch, so every lift damps. Under
51
+ * `brushing` their fall is slowed until the quick runs of latch changes
52
+ * in the SUPRA corpus dip without damping, at the price that lifts
53
+ * shorter than about 265 ms brush as well.
54
+ */
55
+ export declare const pedalPresets: {
56
+ damping: Readonly<Record<string, number>>;
57
+ brushing: Readonly<Record<string, number>>;
58
+ };
59
+ export type PedalPreset = keyof typeof pedalPresets;
60
+ /** The preset a set of pedal constants is, if it is one. */
61
+ export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
45
62
  /**
46
63
  * Between the edition's shared place axis and this version's own paper.
47
64
  * `toOwnPaper` is 1 for a version on the axis it was measured on, and about
@@ -1,10 +1,26 @@
1
- import { clamp, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, ROWS_PER_MM, travelBetweenRails, } from "welte-mignon-emulator";
1
+ import { clamp, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, pedalBrushing, pedalDefaults, ROWS_PER_MM, travelBetweenRails, } from "welte-mignon-emulator";
2
2
  import { inCentimeters, mm, seconds } from "../model/Quantity.js";
3
3
  import { partitionPoint } from "../shared/sorted.js";
4
4
  import { velocityOf } from "./velocity.js";
5
5
  /** Whether two sets of constants are the same, value for value. */
6
6
  export const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
7
7
  && Object.entries(a).every(([name, value]) => b[name] === value);
8
+ /**
9
+ * The two readings of the pedal mechanism the emulator offers, by name,
10
+ * which the systems share: Hagmann has the valves and bellows that carry
11
+ * out the movements "in beiden Systemen dieselbe" (p. 106), and only the
12
+ * Vorpneumatik differs. Under `damping` the dampers reach the strings
13
+ * within the shortest lift the rolls punch, so every lift damps. Under
14
+ * `brushing` their fall is slowed until the quick runs of latch changes
15
+ * in the SUPRA corpus dip without damping, at the price that lifts
16
+ * shorter than about 265 ms brush as well.
17
+ */
18
+ export const pedalPresets = {
19
+ damping: pedalDefaults,
20
+ brushing: pedalBrushing
21
+ };
22
+ /** The preset a set of pedal constants is, if it is one. */
23
+ export const pedalPresetOf = (pedals) => Object.keys(pedalPresets).find(name => sameParameters(pedalPresets[name], pedals));
8
24
  /** Paper the grid runs on past the last hole, so that a final pedal release completes. */
9
25
  const RUN_OUT = mm(100);
10
26
  const isNote = (event) => event.type === 'note';
@@ -3,7 +3,7 @@ import { Instrument, RollNumber } from "welte-mignon-emulator/t100";
3
3
  import { NegotiatedEvent, Performance, ReproducingSystem, RollProperties } from "../ReproducingSystem.js";
4
4
  import { type WelteOptions } from "../welte.js";
5
5
  export type { VelocityMap } from "../velocity.js";
6
- export { secondsAt } from "../welte.js";
6
+ export { pedalPresetOf, pedalPresets, secondsAt, type PedalPreset } from "../welte.js";
7
7
  export type { Instrument } from "welte-mignon-emulator/t100";
8
8
  export type InstrumentName = 'consensus' | RollNumber;
9
9
  /**
@@ -19,21 +19,6 @@ export declare const instrumentNames: readonly InstrumentName[];
19
19
  export declare const nuanceOf: (instrument: Instrument) => Record<Half, Parameters>;
20
20
  /** The instrument a pair of nuancing constants belongs to, if it is one. */
21
21
  export declare const instrumentNameOf: (nuance: Record<Half, Parameters>) => InstrumentName | undefined;
22
- /**
23
- * The two readings of the pedal mechanism the emulator offers, by name.
24
- * Under `damping` the dampers reach the strings within the shortest lift
25
- * the rolls punch, so every lift damps. Under `brushing` their fall is
26
- * slowed until the quick runs of latch changes in the SUPRA corpus dip
27
- * without damping, at the price that lifts shorter than about 265 ms brush
28
- * as well.
29
- */
30
- export declare const pedalPresets: {
31
- damping: Readonly<Record<string, number>>;
32
- brushing: Readonly<Record<string, number>>;
33
- };
34
- export type PedalPreset = keyof typeof pedalPresets;
35
- /** The preset a set of pedal constants is, if it is one. */
36
- export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
37
22
  /** The T-100 takes the options every Welte system does; see `WelteOptions`. */
38
23
  export type WelteT100Options = WelteOptions;
39
24
  export declare const defaultWelteT100Options: WelteT100Options;
@@ -1,10 +1,10 @@
1
- import { DEFAULT_PUNCH_MM, pedalBrushing, pedalDefaults, TRACKER_BORE_MM, WELTE_SPOOL, } from "welte-mignon-emulator";
1
+ import { DEFAULT_PUNCH_MM, TRACKER_BORE_MM, WELTE_SPOOL, } from "welte-mignon-emulator";
2
2
  import { aperturePorts, CONSENSUS, pneumaticModel, PRESETS, runPedals, } from "welte-mignon-emulator/t100";
3
3
  import { welteT100 } from "./bar.js";
4
4
  import { mm, track } from "../../model/Quantity.js";
5
5
  import { defaultVelocityMap } from "../velocity.js";
6
- import { performWelte, sameParameters } from "../welte.js";
7
- export { secondsAt } from "../welte.js";
6
+ import { pedalPresets, performWelte, sameParameters } from "../welte.js";
7
+ export { pedalPresetOf, pedalPresets, secondsAt } from "../welte.js";
8
8
  /**
9
9
  * The instruments the emulator was fitted as: the consensus over the six
10
10
  * rolls with drawn nuance lines, and the setting that drew each of them,
@@ -18,20 +18,6 @@ export const instrumentNames = ['consensus', ...Object.keys(PRESETS)];
18
18
  export const nuanceOf = (instrument) => ({ bass: instrument.bass, treble: instrument.treble });
19
19
  /** The instrument a pair of nuancing constants belongs to, if it is one. */
20
20
  export const instrumentNameOf = (nuance) => instrumentNames.find(name => sameParameters(instruments[name].bass, nuance.bass) && sameParameters(instruments[name].treble, nuance.treble));
21
- /**
22
- * The two readings of the pedal mechanism the emulator offers, by name.
23
- * Under `damping` the dampers reach the strings within the shortest lift
24
- * the rolls punch, so every lift damps. Under `brushing` their fall is
25
- * slowed until the quick runs of latch changes in the SUPRA corpus dip
26
- * without damping, at the price that lifts shorter than about 265 ms brush
27
- * as well.
28
- */
29
- export const pedalPresets = {
30
- damping: pedalDefaults,
31
- brushing: pedalBrushing
32
- };
33
- /** The preset a set of pedal constants is, if it is one. */
34
- export const pedalPresetOf = (pedals) => Object.keys(pedalPresets).find(name => sameParameters(pedalPresets[name], pedals));
35
21
  export const defaultWelteT100Options = {
36
22
  spool: WELTE_SPOOL,
37
23
  nuance: nuanceOf(instruments.consensus),
@@ -4,7 +4,7 @@ import { ReproducingSystem } from "../ReproducingSystem.js";
4
4
  import { Millimeters } from "../../model/Quantity.js";
5
5
  import { type WelteOptions } from "../welte.js";
6
6
  export type { VelocityMap } from "../velocity.js";
7
- export { secondsAt } from "../welte.js";
7
+ export { pedalPresetOf, pedalPresets, secondsAt, type PedalPreset } from "../welte.js";
8
8
  export type { WelteT98Instrument, WelteT98InstrumentName } from "welte-mignon-emulator/t98";
9
9
  /**
10
10
  * The instruments the emulator offers for the green Welte, in three groups that
@@ -44,17 +44,6 @@ export declare const instrumentNames: readonly WelteT98InstrumentName[];
44
44
  /** The instrument a pair of nuancing constants belongs to, if it is one. */
45
45
  export declare const instrumentNameOf: (nuance: Record<Half, Parameters>) => WelteT98InstrumentName | undefined;
46
46
  export { instrumentT98Of, labelOf, nuanceOf };
47
- /**
48
- * The two readings of the pedal mechanism, which is the T-100's below the
49
- * command: Hagmann has the valves and bellows that carry out the movements "in
50
- * beiden Systemen dieselbe" (p. 106), and only the Vorpneumatik differs.
51
- */
52
- export declare const pedalPresets: {
53
- damping: Readonly<Record<string, number>>;
54
- brushing: Readonly<Record<string, number>>;
55
- };
56
- export type PedalPreset = keyof typeof pedalPresets;
57
- export declare const pedalPresetOf: (pedals: Parameters) => PedalPreset | undefined;
58
47
  /**
59
48
  * The options every Welte system takes (see `WelteOptions`), with what the
60
49
  * T-98 adds. No source states a T-98 spool geometry, so the default keeps the
@@ -1,10 +1,10 @@
1
- import { pedalBrushing, pedalDefaults, ROWS_PER_MM, WELTE_T98_SPOOL, } from "welte-mignon-emulator";
1
+ import { ROWS_PER_MM, WELTE_T98_SPOOL, } from "welte-mignon-emulator";
2
2
  import { aperturePorts, CHAIN_GAP_MM, DERIVED, GENUINE, instrumentT98Of, labelOf, nuanceOf, PUNCH_T98_MM, pneumaticT98Model, rewindAt, runPedals, sforzandoPianoLift, STARTING_VALUES, TRACKER_BORE_T98_MM, } from "welte-mignon-emulator/t98";
3
3
  import { welteT98 } from "./bar.js";
4
4
  import { mm, seconds, track } from "../../model/Quantity.js";
5
5
  import { defaultVelocityMap } from "../velocity.js";
6
- import { performWelte, sameParameters } from "../welte.js";
7
- export { secondsAt } from "../welte.js";
6
+ import { pedalPresets, performWelte, sameParameters } from "../welte.js";
7
+ export { pedalPresetOf, pedalPresets, secondsAt } from "../welte.js";
8
8
  /**
9
9
  * The instruments the emulator offers for the green Welte, in three groups that
10
10
  * answer three different questions and must not be listed as one.
@@ -46,16 +46,6 @@ export const instrumentNameOf = (nuance) => instrumentNames.find(name => {
46
46
  && sameParameters(instrument.treble, nuance.treble);
47
47
  });
48
48
  export { instrumentT98Of, labelOf, nuanceOf };
49
- /**
50
- * The two readings of the pedal mechanism, which is the T-100's below the
51
- * command: Hagmann has the valves and bellows that carry out the movements "in
52
- * beiden Systemen dieselbe" (p. 106), and only the Vorpneumatik differs.
53
- */
54
- export const pedalPresets = {
55
- damping: pedalDefaults,
56
- brushing: pedalBrushing
57
- };
58
- export const pedalPresetOf = (pedals) => Object.keys(pedalPresets).find(name => sameParameters(pedalPresets[name], pedals));
59
49
  export const defaultWelteT98Options = {
60
50
  spool: WELTE_T98_SPOOL,
61
51
  nuance: nuanceOf(GENUINE.consensus),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linked-rolls",
3
- "version": "0.64.0",
3
+ "version": "0.65.0",
4
4
  "description": "Digital editions of piano rolls: import, collation, editorial assumptions, JSON-LD export, and emulation through a reproducing system",
5
5
  "license": "MIT",
6
6
  "repository": {