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.
- package/lib/analysis/constraints.js +31 -28
- package/lib/analysis/witnesses.js +6 -3
- package/lib/collation/alignment.js +55 -20
- package/lib/collation/scatter.d.ts +2 -2
- package/lib/io/asJsonLd.js +33 -38
- package/lib/io/migrate.js +20 -26
- package/lib/model/Edition.d.ts +0 -13
- package/lib/ops/collation.js +13 -9
- package/lib/ops/copies.js +11 -3
- package/lib/ops/immutable.d.ts +7 -1
- package/lib/ops/immutable.js +22 -8
- package/lib/readers/phillipsEroll.js +1 -8
- package/lib/readers/stanfordAton.js +2 -6
- package/lib/schema.json +0 -31
- package/lib/systems/welte.d.ts +17 -0
- package/lib/systems/welte.js +17 -1
- package/lib/systems/welteT100/system.d.ts +1 -16
- package/lib/systems/welteT100/system.js +3 -17
- package/lib/systems/welteT98/system.d.ts +1 -12
- package/lib/systems/welteT98/system.js +3 -13
- package/package.json +1 -1
|
@@ -80,20 +80,19 @@ const typesNotOnTheBar = (version, snapshot) => {
|
|
|
80
80
|
* out says 59. Collation across systems consults only the place, so
|
|
81
81
|
* without this nothing checks the tracks at all.
|
|
82
82
|
*/
|
|
83
|
-
const
|
|
84
|
-
const
|
|
83
|
+
const carriedOffItsMeaning = (edition) => (symbol) => {
|
|
84
|
+
const key = keyOf(symbol);
|
|
85
|
+
return placedCarriersOf(edition, symbol).some(carrier => {
|
|
85
86
|
const copy = copyOfFeature(edition, carrier.id);
|
|
86
|
-
if (!copy)
|
|
87
|
-
return false;
|
|
88
87
|
// A carrier lying across several positions reads as several commands,
|
|
89
88
|
// and carries the symbol as long as one of them is the symbol's.
|
|
90
|
-
return !barOf(copy).meaningsOf(carrier.vertical)
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
return commands
|
|
94
|
-
.filter(symbol => placedCarriersOf(edition, symbol).some(carrier => misread(carrier, symbol)))
|
|
95
|
-
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
89
|
+
return copy !== undefined && !barOf(copy).meaningsOf(carrier.vertical).some(meaning => keyOf(meaning) === key);
|
|
90
|
+
});
|
|
96
91
|
};
|
|
92
|
+
/** The commands the version shows among those carried off their meaning. */
|
|
93
|
+
const carriersOffTheirMeaning = (misread, version, commands) => commands
|
|
94
|
+
.filter(symbol => misread.has(symbol))
|
|
95
|
+
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
97
96
|
/**
|
|
98
97
|
* Where a copy of the version's own system strays from the others in
|
|
99
98
|
* its paper by more than paper does.
|
|
@@ -104,13 +103,9 @@ const carriersOffTheirMeaning = (edition, version, commands) => {
|
|
|
104
103
|
* speed, and averaging it in would hide both that and the fact that the
|
|
105
104
|
* playback rests on a guess.
|
|
106
105
|
*/
|
|
107
|
-
const paperDisagreed = (edition, version) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.some(({ copy, problem }) => problem === 'paper-beyond-its-spread' && own.has(copy))
|
|
111
|
-
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
112
|
-
: [];
|
|
113
|
-
};
|
|
106
|
+
const paperDisagreed = (edition, strays, version) => paperOfVersion(edition, version)?.copies.some(copy => strays.has(copy))
|
|
107
|
+
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
108
|
+
: [];
|
|
114
109
|
/**
|
|
115
110
|
* A version's strikes that take nothing out of its text: each deleted
|
|
116
111
|
* symbol has to be one the parent hands down, or one the version put
|
|
@@ -143,14 +138,22 @@ const strikesBitingNothing = (edition, version) => {
|
|
|
143
138
|
* track that does not say what its symbol says, and a strike that takes
|
|
144
139
|
* nothing out.
|
|
145
140
|
*/
|
|
146
|
-
export const constraintProblems = (edition) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
141
|
+
export const constraintProblems = (edition) => {
|
|
142
|
+
// What a symbol's carriers and the copies' paper say holds whichever
|
|
143
|
+
// version shows them, so it is asked once for the edition.
|
|
144
|
+
const misread = new Set(edition.versions.flatMap(insertedBy).filter(isCommand).filter(carriedOffItsMeaning(edition)));
|
|
145
|
+
const strays = new Set(alignmentProblems(edition)
|
|
146
|
+
.filter(({ problem }) => problem === 'paper-beyond-its-spread')
|
|
147
|
+
.map(({ copy }) => copy));
|
|
148
|
+
return edition.versions.flatMap(version => {
|
|
149
|
+
const snapshot = snapshotOf(edition, version.id);
|
|
150
|
+
const commands = snapshot.filter(isCommand);
|
|
151
|
+
return [
|
|
152
|
+
...problemsIn(version.id, commands),
|
|
153
|
+
...typesNotOnTheBar(version, snapshot),
|
|
154
|
+
...carriersOffTheirMeaning(misread, version.id, commands),
|
|
155
|
+
...paperDisagreed(edition, strays, version),
|
|
156
|
+
...strikesBitingNothing(edition, version)
|
|
157
|
+
];
|
|
158
|
+
});
|
|
159
|
+
};
|
|
@@ -2,6 +2,7 @@ import { certaintyOf, idOf, idsOf } from "../model/Assumption.js";
|
|
|
2
2
|
import { insertedBy } from "../model/Version.js";
|
|
3
3
|
import { lineageOf } from "./stemma.js";
|
|
4
4
|
import { copyOfFeature, versionIn } from "../lookup/lookup.js";
|
|
5
|
+
import { perState } from "../lookup/perState.js";
|
|
5
6
|
/** The copies whose features carry any of the symbols. */
|
|
6
7
|
const copiesCarrying = (edition, symbols) => new Set(symbols
|
|
7
8
|
.flatMap(symbol => idsOf(symbol.carriers))
|
|
@@ -17,19 +18,21 @@ const carriagesIn = (edition) => edition.versions.flatMap(version => {
|
|
|
17
18
|
.filter(copy => carrying.has(copy.id))
|
|
18
19
|
.map((copy) => ({ copy: copy.id, version: version.id, depth }));
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
+
/** What the copies' features attest, worked out once for each state of the edition. */
|
|
22
|
+
const carriersIn = perState((edition) => {
|
|
21
23
|
const carriages = carriagesIn(edition);
|
|
24
|
+
const byVersion = Map.groupBy(carriages, carriage => carriage.version);
|
|
22
25
|
return {
|
|
23
26
|
copiesOf: new Map(edition.versions.map(version => [
|
|
24
27
|
version.id,
|
|
25
|
-
|
|
28
|
+
(byVersion.get(version.id) ?? []).map(carriage => carriage.copy)
|
|
26
29
|
])),
|
|
27
30
|
// Written in order of depth, so a copy's latest carriage is set last and stands.
|
|
28
31
|
latestOf: new Map([...carriages]
|
|
29
32
|
.sort((one, other) => one.depth - other.depth)
|
|
30
33
|
.map(carriage => [carriage.copy, carriage.version]))
|
|
31
34
|
};
|
|
32
|
-
};
|
|
35
|
+
});
|
|
33
36
|
/**
|
|
34
37
|
* The versions a copy's features carry at first hand: for every copy,
|
|
35
38
|
* the latest state it bears. A version left out is reached only through
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { featuresOf, isTear, tearsOf } from "../model/RollCopy.js";
|
|
2
2
|
import { defaultTrackerBar } from "../systems/index.js";
|
|
3
3
|
import { add, mm, subtract, track } from "../model/Quantity.js";
|
|
4
|
+
import { partitionPoint } from "../shared/sorted.js";
|
|
4
5
|
/** A place on the copy's own paper, on the edition's axis. */
|
|
5
6
|
export const toAxis = (alignment) => (place) => alignment ? mm((place + alignment.shift.horizontal) * alignment.scale) : place;
|
|
6
7
|
/** A place on the edition's axis, on the copy's own paper. */
|
|
@@ -217,12 +218,41 @@ const noteOnsets = (features, bar) => features
|
|
|
217
218
|
.map(meaning => ({ pitch: meaning.pitch, at: feature.horizontal.from }));
|
|
218
219
|
})
|
|
219
220
|
.sort(byPlace);
|
|
221
|
+
/**
|
|
222
|
+
* The middle one of the values, the upper of the two where their number
|
|
223
|
+
* is even. It is selected rather than the values sorted (Hoare's FIND,
|
|
224
|
+
* as Wirth writes it): a long roll gives over a million slopes, and only
|
|
225
|
+
* the one in the middle is wanted. The values are reordered where they
|
|
226
|
+
* lie.
|
|
227
|
+
*/
|
|
220
228
|
const median = (values) => {
|
|
221
|
-
const
|
|
222
|
-
|
|
229
|
+
const middle = values.length >> 1;
|
|
230
|
+
let low = 0;
|
|
231
|
+
let high = values.length - 1;
|
|
232
|
+
while (low < high) {
|
|
233
|
+
const pivot = values[middle];
|
|
234
|
+
let i = low;
|
|
235
|
+
let j = high;
|
|
236
|
+
while (i <= j) {
|
|
237
|
+
while (values[i] < pivot)
|
|
238
|
+
i++;
|
|
239
|
+
while (pivot < values[j])
|
|
240
|
+
j--;
|
|
241
|
+
if (i <= j) {
|
|
242
|
+
const swapped = values[i];
|
|
243
|
+
values[i] = values[j];
|
|
244
|
+
values[j] = swapped;
|
|
245
|
+
i++;
|
|
246
|
+
j--;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (j < middle)
|
|
250
|
+
low = i;
|
|
251
|
+
if (middle < i)
|
|
252
|
+
high = j;
|
|
253
|
+
}
|
|
254
|
+
return values[middle] ?? 0;
|
|
223
255
|
};
|
|
224
|
-
/** The slope of the line through two matches, or nothing where they share a place on the copy. */
|
|
225
|
-
const slopeBetween = (m, n) => n.a === m.a ? [] : [(n.b - m.b) / (n.a - m.a)];
|
|
226
256
|
/** Matches beyond this are thinned before the slopes are taken, whose number grows with the square. */
|
|
227
257
|
const SLOPE_SAMPLE = 1500;
|
|
228
258
|
/**
|
|
@@ -234,25 +264,27 @@ const SLOPE_SAMPLE = 1500;
|
|
|
234
264
|
const robustLine = (matches) => {
|
|
235
265
|
const step = Math.max(1, Math.ceil(matches.length / SLOPE_SAMPLE));
|
|
236
266
|
const sample = matches.filter((_, i) => i % step === 0);
|
|
237
|
-
const slopes = sample.
|
|
238
|
-
|
|
267
|
+
const slopes = new Float64Array(sample.length * (sample.length - 1) / 2);
|
|
268
|
+
let count = 0;
|
|
269
|
+
sample.forEach((m, i) => {
|
|
270
|
+
for (let j = i + 1; j < sample.length; j++) {
|
|
271
|
+
const n = sample[j];
|
|
272
|
+
// Two matches at one place on the copy give no slope.
|
|
273
|
+
if (n.a !== m.a)
|
|
274
|
+
slopes[count++] = (n.b - m.b) / (n.a - m.a);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
if (count === 0)
|
|
239
278
|
return undefined;
|
|
240
|
-
const slope = median(slopes);
|
|
241
|
-
return { slope, intercept: median(
|
|
279
|
+
const slope = median(slopes.subarray(0, count));
|
|
280
|
+
return { slope, intercept: median(Float64Array.from(matches, m => m.b - slope * m.a)) };
|
|
242
281
|
};
|
|
243
282
|
const placed = (line, a) => line.slope * a + line.intercept;
|
|
244
283
|
const residualOf = (line, match) => match.b - placed(line, match.a);
|
|
245
284
|
/** Pitches in a row that pin down a place on a roll, provided the row occurs once. */
|
|
246
285
|
const ANCHOR_LENGTH = 6;
|
|
247
286
|
/** Every run of `length` pitches, keyed by the run, with the onsets it starts at. */
|
|
248
|
-
const runsOf = (onsets, length) =>
|
|
249
|
-
const runs = new Map();
|
|
250
|
-
onsets.slice(0, Math.max(0, onsets.length - length + 1)).forEach((start, i) => {
|
|
251
|
-
const key = onsets.slice(i, i + length).map(onset => onset.pitch).join(',');
|
|
252
|
-
runs.set(key, [...(runs.get(key) ?? []), start]);
|
|
253
|
-
});
|
|
254
|
-
return runs;
|
|
255
|
-
};
|
|
287
|
+
const runsOf = (onsets, length) => Map.groupBy(onsets.slice(0, Math.max(0, onsets.length - length + 1)), (_, i) => onsets.slice(i, i + length).map(onset => onset.pitch).join(','));
|
|
256
288
|
/** The places a run of pitches found once on each roll ties together. */
|
|
257
289
|
const anchors = (a, b) => {
|
|
258
290
|
const runsB = runsOf(b, ANCHOR_LENGTH);
|
|
@@ -277,13 +309,16 @@ const pairedNearestFirst = (candidates) => {
|
|
|
277
309
|
}
|
|
278
310
|
return matches;
|
|
279
311
|
};
|
|
312
|
+
/** Widens the window by a hair before it is searched, so that rounding in its bounds cannot leave out what the distance admits. */
|
|
313
|
+
const WINDOW_SLACK = 1e-9;
|
|
280
314
|
/** Each onset of A paired with the nearest onset of B of its pitch within the window around where the line puts it. */
|
|
281
315
|
const nearestMatches = (a, b, line, window) => {
|
|
282
|
-
const bByPitch = new Map();
|
|
283
|
-
b.forEach(onset => bByPitch.set(onset.pitch, [...(bByPitch.get(onset.pitch) ?? []), onset]));
|
|
284
316
|
const candidates = a.flatMap(onset => {
|
|
285
317
|
const expected = placed(line, onset.at);
|
|
286
|
-
|
|
318
|
+
const ofPitch = b.get(onset.pitch) ?? [];
|
|
319
|
+
const first = partitionPoint(ofPitch, other => other.at < expected - window - WINDOW_SLACK);
|
|
320
|
+
const end = partitionPoint(ofPitch, other => other.at <= expected + window + WINDOW_SLACK);
|
|
321
|
+
return ofPitch.slice(first, end)
|
|
287
322
|
.map(other => ({ distance: Math.abs(other.at - expected), a: onset, b: other }))
|
|
288
323
|
.filter(candidate => candidate.distance <= window);
|
|
289
324
|
});
|
|
@@ -341,5 +376,5 @@ export function alignFeatures(rollA, rollB, barA = defaultTrackerBar, barB = bar
|
|
|
341
376
|
const coarse = robustLine(anchors(a, b));
|
|
342
377
|
if (!coarse)
|
|
343
378
|
return undefined;
|
|
344
|
-
return resultOf(WINDOWS.reduce(settled(a, b), { line: coarse, matches: [] }));
|
|
379
|
+
return resultOf(WINDOWS.reduce(settled(a, Map.groupBy(b, onset => onset.pitch)), { line: coarse, matches: [] }));
|
|
345
380
|
}
|
|
@@ -66,7 +66,7 @@ export interface ScatterOptions {
|
|
|
66
66
|
groupOf?: Grouping;
|
|
67
67
|
/** The width of the histogram's bins. */
|
|
68
68
|
binWidth?: Millimeters;
|
|
69
|
-
/** The tolerance the
|
|
69
|
+
/** The tolerance the derivation states at present, against which each departure is reported as still admitted or not. */
|
|
70
70
|
stated?: CollationTolerance;
|
|
71
71
|
}
|
|
72
72
|
/** A reading the calculated tolerance does not admit. */
|
|
@@ -90,7 +90,7 @@ export interface Departure {
|
|
|
90
90
|
*/
|
|
91
91
|
separatedBy: BothEnds<boolean>;
|
|
92
92
|
/**
|
|
93
|
-
* Whether the tolerance the
|
|
93
|
+
* Whether the tolerance the derivation states at present still admits
|
|
94
94
|
* it. Absent where none was given to compare against.
|
|
95
95
|
*
|
|
96
96
|
* This sees one direction only. A departure is by definition a
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -80,23 +80,19 @@ const changing = (copyId) => (act) => {
|
|
|
80
80
|
return { ...act, diminished: { '@id': copyId } };
|
|
81
81
|
return act;
|
|
82
82
|
};
|
|
83
|
+
/** The copy with what each of its acts changed stated. */
|
|
84
|
+
const withChangesOf = (copy) => isRecord(copy) && Array.isArray(copy.modifications)
|
|
85
|
+
? { ...copy, modifications: copy.modifications.map(changing(copy['@id'])) }
|
|
86
|
+
: copy;
|
|
83
87
|
/**
|
|
84
88
|
* The document with what each act changed stated. Which of P110
|
|
85
89
|
* augmented and P112 diminished applies depends on the class of the
|
|
86
90
|
* act, which no property chain in OWL 2 RL can test, so the export
|
|
87
91
|
* states it. What a copy or a patch bears is left to a reasoner (see
|
|
88
|
-
* the entailments in reo.ttl).
|
|
92
|
+
* the entailments in reo.ttl). The copies are the only nodes that are
|
|
93
|
+
* modified, and the edition holds them in its list of copies alone.
|
|
89
94
|
*/
|
|
90
|
-
const withChanges = (
|
|
91
|
-
if (Array.isArray(value))
|
|
92
|
-
return value.map(withChanges);
|
|
93
|
-
if (!isRecord(value))
|
|
94
|
-
return value;
|
|
95
|
-
const node = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, withChanges(child)]));
|
|
96
|
-
return node['@type'] === 'RollCopy' && Array.isArray(node.modifications)
|
|
97
|
-
? { ...node, modifications: node.modifications.map(changing(node['@id'])) }
|
|
98
|
-
: node;
|
|
99
|
-
};
|
|
95
|
+
const withChanges = (edition) => Array.isArray(edition.copies) ? { ...edition, copies: edition.copies.map(withChangesOf) } : edition;
|
|
100
96
|
/** Terms the context sets to null, which say nothing when the edition is read as RDF. */
|
|
101
97
|
const silentTerms = new Set(Object.entries(context['@context'])
|
|
102
98
|
.filter(([, definition]) => definition === null)
|
|
@@ -120,21 +116,24 @@ const quote = (subject, key, reference, listed) => {
|
|
|
120
116
|
const { '@annotation': { '@id': annotation, ...about }, ...object } = reference;
|
|
121
117
|
return { '@id': { '@id': subject, [key]: listed ? [object] : object }, annotation, ...about };
|
|
122
118
|
};
|
|
123
|
-
/** The value a node states under a key, less the doubted references,
|
|
124
|
-
const
|
|
119
|
+
/** The value a node states under a key, less the doubted references, which go to the quoted. */
|
|
120
|
+
const unquoted = (subject, key, value, quoted) => {
|
|
125
121
|
if (Array.isArray(value)) {
|
|
126
|
-
return {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
return value.filter(item => {
|
|
123
|
+
if (!isDoubtedReference(item))
|
|
124
|
+
return true;
|
|
125
|
+
quoted.push(quote(subject, key, item, true));
|
|
126
|
+
return false;
|
|
127
|
+
});
|
|
130
128
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
if (!isDoubtedReference(value))
|
|
130
|
+
return value;
|
|
131
|
+
quoted.push(quote(subject, key, value, false));
|
|
132
|
+
return undefined;
|
|
134
133
|
};
|
|
135
134
|
/**
|
|
136
135
|
* The document with every doubted reference taken off the node that
|
|
137
|
-
* states it, and quoted instead.
|
|
136
|
+
* states it, and quoted instead, in the order the document states them.
|
|
138
137
|
*
|
|
139
138
|
* An `@annotation` in JSON-LD-star states the triple it annotates and
|
|
140
139
|
* then says something about it, so a statement the edition holds
|
|
@@ -142,32 +141,28 @@ const quotingValue = (subject, key, value) => {
|
|
|
142
141
|
* between nodes are quoted, since only they can be put back where they
|
|
143
142
|
* stood; a doubted date or attribution stays annotated in place.
|
|
144
143
|
*/
|
|
145
|
-
const withDoubtedReferencesQuoted = (value) => {
|
|
146
|
-
if (Array.isArray(value))
|
|
147
|
-
|
|
148
|
-
return { node: quotings.map(({ node }) => node), quoted: quotings.flatMap(({ quoted }) => quoted) };
|
|
149
|
-
}
|
|
144
|
+
const withDoubtedReferencesQuoted = (value, quoted) => {
|
|
145
|
+
if (Array.isArray(value))
|
|
146
|
+
return value.map(item => withDoubtedReferencesQuoted(item, quoted));
|
|
150
147
|
if (!isRecord(value))
|
|
151
|
-
return
|
|
148
|
+
return value;
|
|
152
149
|
const subject = value['@id'];
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
const node = {};
|
|
151
|
+
Object.entries(value).forEach(([key, child]) => {
|
|
152
|
+
const stated = typeof subject === 'string' && !key.startsWith('@') ? unquoted(subject, key, child, quoted) : child;
|
|
153
|
+
const below = withDoubtedReferencesQuoted(stated, quoted);
|
|
154
|
+
if (below !== undefined)
|
|
155
|
+
node[key] = below;
|
|
159
156
|
});
|
|
160
|
-
return
|
|
161
|
-
node: Object.fromEntries(entries.filter(({ node }) => node !== undefined).map(({ key, node }) => [key, node])),
|
|
162
|
-
quoted: entries.flatMap(({ quoted }) => quoted)
|
|
163
|
-
};
|
|
157
|
+
return node;
|
|
164
158
|
};
|
|
165
159
|
/** The edition with each copy's features and tears at the copy's own places, as a document holds them. */
|
|
166
160
|
const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
|
|
167
161
|
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
168
162
|
: edition;
|
|
169
163
|
export const asJsonLd = (edition) => {
|
|
170
|
-
const
|
|
164
|
+
const quoted = [];
|
|
165
|
+
const node = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))), quoted);
|
|
171
166
|
// The context is the export's own; one carried in from an import must not override it.
|
|
172
167
|
const { base, '@context': carried, ...rest } = node;
|
|
173
168
|
return {
|
package/lib/io/migrate.js
CHANGED
|
@@ -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) =>
|
|
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
|
|
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
|
-
|
|
457
|
-
/** The
|
|
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
|
|
461
|
+
return mapped(value, walk);
|
|
471
462
|
if (value && typeof value === 'object')
|
|
472
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
589
|
-
|
|
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. */
|
package/lib/model/Edition.d.ts
CHANGED
|
@@ -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
|
package/lib/ops/collation.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 =
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
package/lib/ops/immutable.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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. */
|
package/lib/ops/immutable.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
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
|
|
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": {
|
package/lib/systems/welte.d.ts
CHANGED
|
@@ -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
|
package/lib/systems/welte.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
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