linked-rolls 0.19.1 → 0.20.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/Collation.d.ts +1 -3
- package/lib/Collation.js +9 -11
- package/lib/Edition.d.ts +1 -9
- package/lib/EditionView.d.ts +54 -6
- package/lib/EditionView.js +93 -20
- package/lib/Emulation.js +1 -1
- package/lib/RollCopy.d.ts +17 -2
- package/lib/RollCopy.js +18 -2
- package/lib/TrackerBar.d.ts +17 -2
- package/lib/TrackerBar.js +22 -16
- package/lib/Version.d.ts +49 -1
- package/lib/asJsonLd.js +23 -8
- package/lib/constraints.d.ts +8 -19
- package/lib/constraints.js +71 -15
- package/lib/context.d.ts +8 -1
- package/lib/context.js +8 -1
- package/lib/editionOps.d.ts +3 -1
- package/lib/editionOps.js +77 -15
- package/lib/importJsonLd.js +4 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/migrate.js +85 -11
- package/lib/readers/phillipsEroll.d.ts +3 -5
- package/lib/readers/phillipsEroll.js +6 -7
- package/lib/readers/spencerBar.d.ts +4 -5
- package/lib/readers/spencerBar.js +7 -8
- package/lib/readers/stanfordAton.d.ts +4 -7
- package/lib/readers/stanfordAton.js +4 -5
- package/lib/reservations.d.ts +1 -1
- package/lib/reservations.js +24 -2
- package/lib/schema.json +27 -5
- package/lib/spec/context.json +1 -0
- package/lib/spec/welte-licensee.context.json +10 -0
- package/lib/substitution.d.ts +73 -0
- package/lib/substitution.js +175 -0
- package/package.json +1 -1
package/lib/constraints.d.ts
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import { EditionView } from "./EditionView";
|
|
2
|
-
import { TrackerBar } from "./TrackerBar";
|
|
3
2
|
export type ConstraintProblem = {
|
|
4
3
|
version: string;
|
|
5
4
|
symbol: string;
|
|
6
|
-
problem: 'alignment-reference-missing' | 'before-reference-missing' | 'after-reference-missing' | 'placed-relative-to-itself' | 'placed-several-ways' | 'partner-missing' | 'paired-with-itself' | 'in-several-pairs' | 'pair-placed-on-both-sides';
|
|
5
|
+
problem: 'alignment-reference-missing' | 'before-reference-missing' | 'after-reference-missing' | 'placed-relative-to-itself' | 'placed-several-ways' | 'partner-missing' | 'paired-with-itself' | 'in-several-pairs' | 'pair-placed-on-both-sides' | 'type-not-on-the-bar' | 'carrier-on-another-track' | 'copies-disagree-on-the-paper';
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
9
|
-
* Where the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Where the edition cannot hold as stated, version by version: a
|
|
9
|
+
* placement or pairing reference absent from the version, a perforation
|
|
10
|
+
* placed relative to itself or in several ways at once, one claimed by
|
|
11
|
+
* several pairs, a pair whose members are both placed and so cannot
|
|
12
|
+
* keep their distance and follow their references at once, an
|
|
13
|
+
* expression the version's own bar cannot read, and a carrier sitting
|
|
14
|
+
* on a track that does not say what its symbol says.
|
|
15
15
|
*/
|
|
16
16
|
export declare const constraintProblems: (view: EditionView) => ConstraintProblem[];
|
|
17
|
-
export type UnknownExpressionType = {
|
|
18
|
-
version: string;
|
|
19
|
-
symbol: string;
|
|
20
|
-
expressionType: string;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* The expressions of the edition whose type the tracker bar does not
|
|
24
|
-
* read, version by version. The roll names its system; a type from
|
|
25
|
-
* another system, or a misspelt one, means nothing on it.
|
|
26
|
-
*/
|
|
27
|
-
export declare const unknownExpressionTypes: (view: EditionView, bar: TrackerBar) => UnknownExpressionType[];
|
package/lib/constraints.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { idOf } from "./Assumption";
|
|
2
2
|
import { isPerforation, pairsAmong, placementsOf } from "./Symbol";
|
|
3
|
+
import { keyOf } from "./TrackerBar";
|
|
4
|
+
import { trackerBarOf } from "./systems";
|
|
5
|
+
import { barOf } from "./RollCopy";
|
|
3
6
|
const missingReference = {
|
|
4
7
|
alignedWith: 'alignment-reference-missing',
|
|
5
8
|
before: 'before-reference-missing',
|
|
@@ -39,25 +42,78 @@ const problemsIn = (version, perforations) => {
|
|
|
39
42
|
...missingPartners, ...selfPaired, ...inSeveralPairs, ...placedOnBothSides
|
|
40
43
|
];
|
|
41
44
|
};
|
|
42
|
-
/**
|
|
43
|
-
* Where the placements and pairings of the edition cannot hold as
|
|
44
|
-
* stated, version by version: a reference or partner absent from the
|
|
45
|
-
* version, a perforation placed relative to itself or in several ways
|
|
46
|
-
* at once, one claimed by several pairs, or a pair whose members are
|
|
47
|
-
* both placed and so cannot keep their distance and follow their
|
|
48
|
-
* references at once.
|
|
49
|
-
*/
|
|
50
|
-
export const constraintProblems = (view) => view.edition.versions.flatMap(version => problemsIn(version.id, view.snapshot(version.id).filter(isPerforation)));
|
|
51
45
|
const isExpression = (symbol) => symbol.type === 'expression';
|
|
52
46
|
/**
|
|
53
|
-
* The expressions
|
|
54
|
-
*
|
|
55
|
-
*
|
|
47
|
+
* The expressions the version's own bar cannot read, its type belonging
|
|
48
|
+
* to another system or misspelt.
|
|
49
|
+
*
|
|
50
|
+
* This is what says a transfer between systems is unfinished. A green
|
|
51
|
+
* version connected to a red one inherits every red expression, and
|
|
52
|
+
* none of them means anything on a green machine, so each is reported
|
|
53
|
+
* until an edit deletes it and says what took its place. The list
|
|
54
|
+
* emptying is the proof that the transfer is complete.
|
|
56
55
|
*/
|
|
57
|
-
|
|
56
|
+
const typesNotOnTheBar = (version, snapshot) => {
|
|
57
|
+
const bar = trackerBarOf(version.system);
|
|
58
|
+
if (!bar)
|
|
59
|
+
return [];
|
|
58
60
|
const known = new Set(bar.expressionTypes);
|
|
59
|
-
return
|
|
61
|
+
return snapshot
|
|
60
62
|
.filter(isExpression)
|
|
61
63
|
.filter(symbol => !known.has(symbol.expressionType))
|
|
62
|
-
.map(symbol => ({ version: version.id, symbol: symbol.id,
|
|
64
|
+
.map(symbol => ({ version: version.id, symbol: symbol.id, problem: 'type-not-on-the-bar' }));
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Carriers whose track does not say what the symbol they carry says.
|
|
68
|
+
*
|
|
69
|
+
* A carrier stands as evidence for its symbol, so the bar of the copy
|
|
70
|
+
* it sits on must read its track as the symbol's own meaning. Since a
|
|
71
|
+
* copy cut for another system numbers its tracks differently, this is
|
|
72
|
+
* what tells a transfer apart from a miscalibration: a red hole on 47
|
|
73
|
+
* and a green one on 45 both say pitch 60, while a green copy one track
|
|
74
|
+
* out says 59. Collation across systems consults only the place, so
|
|
75
|
+
* without this nothing checks the tracks at all.
|
|
76
|
+
*/
|
|
77
|
+
const carriersOffTheirMeaning = (view, version, perforations) => {
|
|
78
|
+
const misread = (carrier, symbol) => {
|
|
79
|
+
const copy = view.copyOf(carrier.id);
|
|
80
|
+
if (!copy)
|
|
81
|
+
return false;
|
|
82
|
+
const meaning = barOf(copy).meaningOf(carrier.vertical.from);
|
|
83
|
+
return !meaning || keyOf(meaning) !== keyOf(symbol);
|
|
84
|
+
};
|
|
85
|
+
return perforations
|
|
86
|
+
.filter(symbol => view.carriersOf(symbol).some(carrier => misread(carrier, symbol)))
|
|
87
|
+
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
63
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Where the copies of the version's own system disagree about the scale
|
|
91
|
+
* that put them on the edition's shared axis.
|
|
92
|
+
*
|
|
93
|
+
* That scale is what takes a place back to the paper the version's roll
|
|
94
|
+
* ran on, so a performance needs one number. Copies disagreeing about it
|
|
95
|
+
* is evidence about the copies, and averaging it away would hide both
|
|
96
|
+
* the disagreement and the fact that the playback rests on a guess.
|
|
97
|
+
*/
|
|
98
|
+
const paperDisagreed = (view, version) => view.speedScalesIn(version).length > 1
|
|
99
|
+
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
100
|
+
: [];
|
|
101
|
+
/**
|
|
102
|
+
* Where the edition cannot hold as stated, version by version: a
|
|
103
|
+
* placement or pairing reference absent from the version, a perforation
|
|
104
|
+
* placed relative to itself or in several ways at once, one claimed by
|
|
105
|
+
* several pairs, a pair whose members are both placed and so cannot
|
|
106
|
+
* keep their distance and follow their references at once, an
|
|
107
|
+
* expression the version's own bar cannot read, and a carrier sitting
|
|
108
|
+
* on a track that does not say what its symbol says.
|
|
109
|
+
*/
|
|
110
|
+
export const constraintProblems = (view) => view.edition.versions.flatMap(version => {
|
|
111
|
+
const snapshot = view.snapshot(version.id);
|
|
112
|
+
const perforations = snapshot.filter(isPerforation);
|
|
113
|
+
return [
|
|
114
|
+
...problemsIn(version.id, perforations),
|
|
115
|
+
...typesNotOnTheBar(version, snapshot),
|
|
116
|
+
...carriersOffTheirMeaning(view, version.id, perforations),
|
|
117
|
+
...paperDisagreed(view, version)
|
|
118
|
+
];
|
|
119
|
+
});
|
package/lib/context.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { default as jsonLdContext } from './spec/context.json';
|
|
2
|
-
/** The context of the Welte-Mignon T-100, added to
|
|
2
|
+
/** The context of the Welte-Mignon T-100, added to a version coded for it. */
|
|
3
3
|
export { default as welteT100JsonLdContext } from './spec/welte-t100.context.json';
|
|
4
|
+
/**
|
|
5
|
+
* The context of the Welte-Mignon Licensee. Its expression terms are
|
|
6
|
+
* the T-100's, the two systems differing in the tracker scale rather
|
|
7
|
+
* than in the coding, so it reads them out of the T-100's vocabulary
|
|
8
|
+
* rather than minting a second set of IRIs for the same commands.
|
|
9
|
+
*/
|
|
10
|
+
export { default as welteLicenseeJsonLdContext } from './spec/welte-licensee.context.json';
|
package/lib/context.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { default as jsonLdContext } from './spec/context.json';
|
|
2
|
-
/** The context of the Welte-Mignon T-100, added to
|
|
2
|
+
/** The context of the Welte-Mignon T-100, added to a version coded for it. */
|
|
3
3
|
export { default as welteT100JsonLdContext } from './spec/welte-t100.context.json';
|
|
4
|
+
/**
|
|
5
|
+
* The context of the Welte-Mignon Licensee. Its expression terms are
|
|
6
|
+
* the T-100's, the two systems differing in the tracker scale rather
|
|
7
|
+
* than in the coding, so it reads them out of the T-100's vocabulary
|
|
8
|
+
* rather than minting a second set of IRIs for the same commands.
|
|
9
|
+
*/
|
|
10
|
+
export { default as welteLicenseeJsonLdContext } from './spec/welte-licensee.context.json';
|
package/lib/editionOps.d.ts
CHANGED
|
@@ -17,7 +17,9 @@ import { AnyFeature, FeatureConditionAssignment } from "./Feature";
|
|
|
17
17
|
export type EditionOp = (draft: Draft<Edition>) => void;
|
|
18
18
|
/**
|
|
19
19
|
* Puts the copy into the edition with a version of its own, which
|
|
20
|
-
* inserts every symbol the tracker bar reads on
|
|
20
|
+
* inserts every symbol the copy's own tracker bar reads on it. The
|
|
21
|
+
* version is a reading in that system's words, so it is coded for the
|
|
22
|
+
* system the copy was cut for.
|
|
21
23
|
*/
|
|
22
24
|
export declare const createVersion: (siglum: string, copy: RollCopy) => EditionOp;
|
|
23
25
|
/**
|
package/lib/editionOps.js
CHANGED
|
@@ -4,7 +4,10 @@ import { getAt } from "./EditionView";
|
|
|
4
4
|
import { isPerforation, placementRelations } from "./Symbol";
|
|
5
5
|
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
6
6
|
import { collationToleranceOf, insertedBy } from "./Version";
|
|
7
|
-
import { asSymbols } from "./RollCopy";
|
|
7
|
+
import { asSymbols, barOf, isPaperStretch } from "./RollCopy";
|
|
8
|
+
import { systemOf } from "./TrackerBar";
|
|
9
|
+
import { substitutionsBetween } from "./substitution";
|
|
10
|
+
import { trackerBarOf } from "./systems";
|
|
8
11
|
import { applyShift, applyScale, revertShift, revertScale } from "./alignment";
|
|
9
12
|
import { assignReference, idOf } from "./Assumption";
|
|
10
13
|
import { conditions as conditionsAllowed, featuresBorneBy, isGluedOn, withBorneFeatures } from "./Feature";
|
|
@@ -66,20 +69,23 @@ const dropInsertions = (version, symbolIds) => {
|
|
|
66
69
|
};
|
|
67
70
|
/**
|
|
68
71
|
* Puts the copy into the edition with a version of its own, which
|
|
69
|
-
* inserts every symbol the tracker bar reads on
|
|
72
|
+
* inserts every symbol the copy's own tracker bar reads on it. The
|
|
73
|
+
* version is a reading in that system's words, so it is coded for the
|
|
74
|
+
* system the copy was cut for.
|
|
70
75
|
*/
|
|
71
76
|
export const createVersion = (siglum, copy) => draft => {
|
|
77
|
+
const bar = barOf(copy);
|
|
72
78
|
draft.copies.push(copy);
|
|
73
79
|
draft.versions.push({
|
|
74
80
|
type: 'Version',
|
|
75
81
|
id: v4(),
|
|
76
82
|
siglum,
|
|
83
|
+
system: systemOf(bar),
|
|
77
84
|
versionType: 'edition',
|
|
78
|
-
edits: asSymbols(copy.features).map(insertion),
|
|
85
|
+
edits: asSymbols(copy.features, bar).map(insertion),
|
|
79
86
|
motivations: []
|
|
80
87
|
});
|
|
81
88
|
};
|
|
82
|
-
const isPaperStretch = (condition) => condition.conditionType === 'paper-stretch';
|
|
83
89
|
/** States what the scale is put down to, in place of an earlier reading. */
|
|
84
90
|
const readScale = (copy, reading) => {
|
|
85
91
|
if (reading.cause === 'paper') {
|
|
@@ -451,6 +457,12 @@ const handOverCarriers = (view, draft, collations) => collations.forEach(({ symb
|
|
|
451
457
|
const target = path && getAt(path, draft);
|
|
452
458
|
target?.carriers.push(...symbol.carriers);
|
|
453
459
|
});
|
|
460
|
+
/** Whether the two versions are coded for different reproducing systems. */
|
|
461
|
+
const differ = (child, parent) => {
|
|
462
|
+
const one = trackerBarOf(child?.system);
|
|
463
|
+
const other = trackerBarOf(parent?.system);
|
|
464
|
+
return one !== undefined && other !== undefined && one.id !== other.id;
|
|
465
|
+
};
|
|
454
466
|
/**
|
|
455
467
|
* Bases the child on the parent. A symbol of the child that collates
|
|
456
468
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
@@ -461,12 +473,33 @@ const handOverCarriers = (view, draft, collations) => collations.forEach(({ symb
|
|
|
461
473
|
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
462
474
|
const inherited = view.snapshot(parentId);
|
|
463
475
|
const own = view.snapshot(childId);
|
|
464
|
-
const
|
|
476
|
+
const locate = (symbol) => view.placeOf(symbol);
|
|
477
|
+
const collations = collationsOf(own, inherited, locate, tolerance);
|
|
465
478
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
466
479
|
const matched = new Set(collations.map(({ counterpart }) => counterpart.id));
|
|
480
|
+
/**
|
|
481
|
+
* Where the child is coded for another system, a held perforation of
|
|
482
|
+
* its own often stands for a latched pair of the parent's. Saying so
|
|
483
|
+
* as one edit is the transfer being carried out, and leaving the two
|
|
484
|
+
* apart would make the apparatus a list of unexplained losses beside
|
|
485
|
+
* a list of unexplained gains.
|
|
486
|
+
*/
|
|
487
|
+
const substituted = differ(view.get(childId), view.get(parentId))
|
|
488
|
+
? substitutionsBetween(own.filter(symbol => !collated.has(symbol.id)), inherited.filter(symbol => !matched.has(symbol.id)), locate, tolerance)
|
|
489
|
+
: [];
|
|
490
|
+
const paired = new Set(substituted.flatMap(({ replaced, by }) => [...by, ...replaced].map(symbol => symbol.id)));
|
|
467
491
|
const edits = [
|
|
468
|
-
...
|
|
469
|
-
|
|
492
|
+
...substituted.map(({ replaced, by }) => ({
|
|
493
|
+
type: 'edit',
|
|
494
|
+
id: v4(),
|
|
495
|
+
editType: 'replace-with-equivalent',
|
|
496
|
+
insert: [...by],
|
|
497
|
+
delete: replaced.map(symbol => symbol.id)
|
|
498
|
+
})),
|
|
499
|
+
...own.filter(symbol => !collated.has(symbol.id) && !paired.has(symbol.id)).map(insertion),
|
|
500
|
+
...inherited
|
|
501
|
+
.filter(symbol => !matched.has(symbol.id) && !paired.has(symbol.id))
|
|
502
|
+
.map(symbol => deletion(symbol.id))
|
|
470
503
|
];
|
|
471
504
|
return onVersion(childId, (child, draft) => {
|
|
472
505
|
handOverCarriers(view, draft, collations);
|
|
@@ -485,7 +518,7 @@ export const collateSymbols = (view, versionId, symbolIds, tolerance) => {
|
|
|
485
518
|
return noChange;
|
|
486
519
|
const chosen = new Set(symbolIds);
|
|
487
520
|
const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
|
|
488
|
-
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.
|
|
521
|
+
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.placeOf(symbol), tolerance ?? collationToleranceOf(version.basedOn));
|
|
489
522
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
490
523
|
return onVersion(versionId, (version, draft) => {
|
|
491
524
|
handOverCarriers(view, draft, collations);
|
|
@@ -526,6 +559,7 @@ export const deriveVersion = (versionId, editIds) => onVersion(versionId, (versi
|
|
|
526
559
|
type: 'Version',
|
|
527
560
|
id: v4(),
|
|
528
561
|
siglum: `${version.siglum}_derived`,
|
|
562
|
+
system: stateOf(version).system,
|
|
529
563
|
versionType: 'unicum',
|
|
530
564
|
basedOn: assignReference(versionId),
|
|
531
565
|
edits: moved,
|
|
@@ -534,25 +568,53 @@ export const deriveVersion = (versionId, editIds) => onVersion(versionId, (versi
|
|
|
534
568
|
});
|
|
535
569
|
const sameSequence = (a, b) => a.length === b.length && a.every((value, i) => value === b[i]);
|
|
536
570
|
const expressionTypesOf = (symbols) => symbols.filter((symbol) => symbol.type === 'expression').map(symbol => symbol.expressionType);
|
|
537
|
-
|
|
571
|
+
/**
|
|
572
|
+
* How a single added accent is spelled. The red Welte latches a valve
|
|
573
|
+
* on and off again, the green holds one perforation for as long as the
|
|
574
|
+
* accent lasts, so the spelling is the system's and not the music's.
|
|
575
|
+
* Which of them a bar can spell decides which ones it is offered.
|
|
576
|
+
*/
|
|
577
|
+
const ACCENTS = [
|
|
578
|
+
['SlowCrescendoOn', 'SlowCrescendoOff'],
|
|
579
|
+
['ForzandoOn', 'ForzandoOff'],
|
|
580
|
+
['Crescendo'],
|
|
581
|
+
['SforzandoForte']
|
|
582
|
+
];
|
|
583
|
+
const accentsOn = (bar) => bar ? ACCENTS.filter(accent => accent.every(type => bar.expressionTypes.includes(type))) : [];
|
|
538
584
|
const lengthOf = (span) => subtract(span.to, span.from);
|
|
539
585
|
/** How far apart two onsets may lie for the one symbol to count as a replacement of the other. */
|
|
540
586
|
const REPLACEMENT_TOLERANCE = mm(5);
|
|
541
587
|
/** Shorten or prolong, where the inserted symbol starts about where the deleted one did. */
|
|
542
588
|
const replacementType = (view, inserted, deleted) => {
|
|
543
|
-
const after = view.
|
|
544
|
-
const before = view.
|
|
589
|
+
const after = view.placeOf(inserted);
|
|
590
|
+
const before = view.placeOf(deleted);
|
|
545
591
|
if (!after || !before || distance(after.from, before.from) >= REPLACEMENT_TOLERANCE)
|
|
546
592
|
return undefined;
|
|
547
593
|
return lengthOf(after) < lengthOf(before) ? 'shorten' : 'prolong';
|
|
548
594
|
};
|
|
549
|
-
/**
|
|
550
|
-
|
|
595
|
+
/**
|
|
596
|
+
* A guess at what an edit does, from the symbols it exchanges and from
|
|
597
|
+
* the systems the version and the one it is based on are coded for.
|
|
598
|
+
*/
|
|
599
|
+
const guessEditType = (view, versionId, edit) => {
|
|
551
600
|
const inserts = edit.insert ?? [];
|
|
552
601
|
const deletes = view.getAll(edit.delete ?? []);
|
|
553
602
|
const inserted = expressionTypesOf(inserts);
|
|
554
603
|
const deleted = expressionTypesOf(deletes);
|
|
555
|
-
|
|
604
|
+
const bar = trackerBarOf(view.get(versionId)?.system);
|
|
605
|
+
const parentBar = trackerBarOf(view.predecessorOf(versionId)?.system);
|
|
606
|
+
/**
|
|
607
|
+
* Where the version is coded for another system than its parent, an
|
|
608
|
+
* exchange of expression matter is the transfer being carried out:
|
|
609
|
+
* a red ForzandoOn and ForzandoOff pair giving way to one held green
|
|
610
|
+
* SforzandoForte says the same thing in the other system's words,
|
|
611
|
+
* which is what 'replace-with-equivalent' is for. Calling it a
|
|
612
|
+
* corrected error would say the editor made a mistake.
|
|
613
|
+
*/
|
|
614
|
+
if (bar && parentBar && bar.id !== parentBar.id && inserted.length > 0 && deleted.length > 0) {
|
|
615
|
+
return 'replace-with-equivalent';
|
|
616
|
+
}
|
|
617
|
+
if (deleted.length === 0 && accentsOn(bar).some(accent => sameSequence(inserted, accent)))
|
|
556
618
|
return 'additional-accent';
|
|
557
619
|
if (inserted.length > 1 && sameSequence(inserted, deleted))
|
|
558
620
|
return 'shift';
|
|
@@ -575,7 +637,7 @@ export const mergeEdits = (view, versionId, toMerge) => {
|
|
|
575
637
|
insert: toMerge.flatMap(edit => edit.insert ?? []),
|
|
576
638
|
delete: toMerge.flatMap(edit => edit.delete ?? [])
|
|
577
639
|
};
|
|
578
|
-
merged.editType = guessEditType(view, merged);
|
|
640
|
+
merged.editType = guessEditType(view, versionId, merged);
|
|
579
641
|
const mergedIds = new Set(toMerge.map(edit => edit.id));
|
|
580
642
|
return onVersion(versionId, version => {
|
|
581
643
|
version.edits = [...version.edits.filter(edit => !mergedIds.has(edit.id)), merged];
|
package/lib/importJsonLd.js
CHANGED
|
@@ -23,10 +23,12 @@ const fromJsonLdValue = (value) => {
|
|
|
23
23
|
/**
|
|
24
24
|
* An entity with its keywords read as plain keys. The input is left as
|
|
25
25
|
* it is. The `@type` of a value object names the datatype of the value,
|
|
26
|
-
* not a class, and is dropped.
|
|
26
|
+
* not a class, and is dropped. So is a context: a version carries one
|
|
27
|
+
* naming its system's vocabulary, and it belongs to the serialisation
|
|
28
|
+
* rather than to the edition, which states the system as data.
|
|
27
29
|
*/
|
|
28
30
|
const fromJsonLdEntity = (json) => {
|
|
29
|
-
const { '@type': type, '@id': id, ...rest } = json;
|
|
31
|
+
const { '@type': type, '@id': id, '@context': context, ...rest } = json;
|
|
30
32
|
const entity = Object.fromEntries(Object.entries(rest).map(([key, value]) => [key, fromJsonLdValue(value)]));
|
|
31
33
|
if (type !== undefined && !('@value' in json))
|
|
32
34
|
entity.type = type;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/migrate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { conditions } from "./Feature";
|
|
2
2
|
import { rollConditions } from "./RollCopy";
|
|
3
|
-
import { systemOf } from "./TrackerBar";
|
|
3
|
+
import { systemIdIn, systemOf, translationBetween } from "./TrackerBar";
|
|
4
|
+
import { trackerBars } from "./systems";
|
|
4
5
|
import { welteT100 } from "./systems/welteT100/bar";
|
|
5
6
|
import { versionTypes } from "./Version";
|
|
6
7
|
const versionTypeValues = new Set(versionTypes);
|
|
@@ -88,20 +89,93 @@ const walk = (value) => {
|
|
|
88
89
|
return withWalkedChildren(migrateNode(value));
|
|
89
90
|
return value;
|
|
90
91
|
};
|
|
92
|
+
/** The production of a copy, under whichever of its two names it carries. */
|
|
93
|
+
const productionKeyOf = (copy) => copy?.productionEvent && !copy?.production ? 'productionEvent' : 'production';
|
|
94
|
+
/** The list under the key, or nothing where the document holds something else there. */
|
|
95
|
+
const listAt = (edition, key) => Array.isArray(edition[key]) ? edition[key] : undefined;
|
|
96
|
+
/** A file written while the system belonged to the roll rather than to each version. */
|
|
97
|
+
const namesSystemOnTheRoll = (edition) => edition.roll?.system !== undefined
|
|
98
|
+
|| (listAt(edition, 'versions') ?? []).some((version) => !version?.system);
|
|
99
|
+
const barNamed = (system) => trackerBars.find(bar => bar.id === systemIdIn(system?.['@id']));
|
|
100
|
+
const withSystem = (node, system) => node.system && typeof node.system === 'object' ? node : { ...node, system };
|
|
101
|
+
/** A span on the other bar, or nothing where that bar does not read one of its ends. */
|
|
102
|
+
const spanOnBar = (span, at) => {
|
|
103
|
+
const from = at(span.from);
|
|
104
|
+
if (from === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
if (span.to === undefined)
|
|
107
|
+
return { ...span, from };
|
|
108
|
+
const to = at(span.to);
|
|
109
|
+
return to === undefined ? undefined : { ...span, from, to };
|
|
110
|
+
};
|
|
111
|
+
/** The features on the copy's own bar, those it does not read left out, patches and all. */
|
|
112
|
+
const featuresOnBar = (features, at) => features.flatMap((feature) => {
|
|
113
|
+
const vertical = feature.vertical?.unit === 'track'
|
|
114
|
+
? spanOnBar(feature.vertical, at)
|
|
115
|
+
: feature.vertical;
|
|
116
|
+
if (!vertical)
|
|
117
|
+
return [];
|
|
118
|
+
return [{
|
|
119
|
+
...feature,
|
|
120
|
+
vertical,
|
|
121
|
+
...(Array.isArray(feature.features) && { features: featuresOnBar(feature.features, at) })
|
|
122
|
+
}];
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* A copy cut for another system had its holes put onto the roll's bar
|
|
126
|
+
* as it was read, so they are numbered in the roll's system and not in
|
|
127
|
+
* its own. They go back onto the bar the copy names, which is the only
|
|
128
|
+
* numbering that means anything once each copy is read by its own bar.
|
|
129
|
+
* Between two Welte scales the difference is two tracks, which is a
|
|
130
|
+
* legal position a whole tone away rather than a visible error.
|
|
131
|
+
*/
|
|
132
|
+
const onOwnBar = (copy, rollSystem) => {
|
|
133
|
+
if (!copy || typeof copy !== 'object')
|
|
134
|
+
return copy;
|
|
135
|
+
const key = productionKeyOf(copy);
|
|
136
|
+
const own = copy[key]?.system;
|
|
137
|
+
const from = barNamed(rollSystem);
|
|
138
|
+
const to = own && typeof own === 'object' ? barNamed(own) : undefined;
|
|
139
|
+
if (!from || !to || from.id === to.id || !Array.isArray(copy.features))
|
|
140
|
+
return copy;
|
|
141
|
+
return { ...copy, features: featuresOnBar(copy.features, translationBetween(from, to)) };
|
|
142
|
+
};
|
|
91
143
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
144
|
+
* Systems belonged to the roll before they belonged to the versions
|
|
145
|
+
* and the copies. Every 0.1 edition was read with the T-100 bar, so a
|
|
146
|
+
* roll that named no system was a T-100 roll, and the text a copy's
|
|
147
|
+
* production gave for it is kept as the system's name.
|
|
95
148
|
*/
|
|
96
|
-
const
|
|
97
|
-
if (!edition.roll || edition
|
|
149
|
+
const withSystems = (edition) => {
|
|
150
|
+
if (!edition.roll || !namesSystemOnTheRoll(edition))
|
|
98
151
|
return edition;
|
|
99
|
-
const
|
|
100
|
-
|
|
152
|
+
const versions = listAt(edition, 'versions');
|
|
153
|
+
const copies = listAt(edition, 'copies');
|
|
154
|
+
const stated = (copies ?? [])
|
|
155
|
+
.map((copy) => copy?.[productionKeyOf(copy)]?.system)
|
|
101
156
|
.find((system) => typeof system === 'string' && system !== '');
|
|
102
157
|
const { id, ...concept } = systemOf(welteT100);
|
|
103
|
-
const system = { '@id': id, ...concept, ...(stated && { name: stated }) };
|
|
104
|
-
|
|
158
|
+
const system = edition.roll.system ?? { '@id': id, ...concept, ...(stated && { name: stated }) };
|
|
159
|
+
const { system: _named, ...roll } = edition.roll;
|
|
160
|
+
const created = roll.recordingEvent?.created;
|
|
161
|
+
return {
|
|
162
|
+
...edition,
|
|
163
|
+
roll: created
|
|
164
|
+
? { ...roll, recordingEvent: { ...roll.recordingEvent, created: withSystem(created, system) } }
|
|
165
|
+
: roll,
|
|
166
|
+
...(versions && {
|
|
167
|
+
versions: versions.map((version) => withSystem(version, system))
|
|
168
|
+
}),
|
|
169
|
+
...(copies && {
|
|
170
|
+
copies: copies.map((copy) => {
|
|
171
|
+
if (!copy || typeof copy !== 'object')
|
|
172
|
+
return copy;
|
|
173
|
+
const key = productionKeyOf(copy);
|
|
174
|
+
const onOwn = onOwnBar(copy, system);
|
|
175
|
+
return { ...onOwn, [key]: withSystem(onOwn[key] ?? {}, system) };
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
};
|
|
105
179
|
};
|
|
106
180
|
/** An edition written before the editors were carried names none. */
|
|
107
181
|
const withEditors = (edition) => !edition.creation || edition.creation.editors
|
|
@@ -126,5 +200,5 @@ const withDerivationTolerance = (edition) => {
|
|
|
126
200
|
: version)
|
|
127
201
|
};
|
|
128
202
|
};
|
|
129
|
-
const editionSteps = [
|
|
203
|
+
const editionSteps = [withSystems, withEditors, withDerivationTolerance];
|
|
130
204
|
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|
|
@@ -37,8 +37,6 @@ export interface PhillipsErollOptions {
|
|
|
37
37
|
* the roll he read.
|
|
38
38
|
*/
|
|
39
39
|
system?: TrackerBar;
|
|
40
|
-
/** The edition's bar, onto which the positions are put. */
|
|
41
|
-
bar?: TrackerBar;
|
|
42
40
|
/**
|
|
43
41
|
* How far above its position an expression number sits. Defaults to
|
|
44
42
|
* what his samples show for that bar.
|
|
@@ -54,9 +52,9 @@ export interface PhillipsErollOptions {
|
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
56
54
|
* Reads one of his e-roll files as a copy of the roll, in millimetres
|
|
57
|
-
* of paper
|
|
58
|
-
* the bar would leave it.
|
|
55
|
+
* of paper, on the bar it was cut for and in that bar's numbering. A
|
|
56
|
+
* position the bar does not read is left out, as the bar would leave it.
|
|
59
57
|
*/
|
|
60
|
-
export declare function readFromPhillipsEroll(buffer: ArrayBuffer, { system,
|
|
58
|
+
export declare function readFromPhillipsEroll(buffer: ArrayBuffer, { system, controlOffset, placeAt }?: PhillipsErollOptions): RollCopy;
|
|
61
59
|
/** The bars his files are known to be numbered by. */
|
|
62
60
|
export declare const phillipsSystems: readonly TrackerBar[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read } from "midifile-ts";
|
|
2
2
|
import { v4 } from "uuid";
|
|
3
3
|
import { assignObject } from "../Assumption";
|
|
4
|
-
import { systemOf
|
|
4
|
+
import { systemOf } from "../TrackerBar";
|
|
5
5
|
import { welteT100 } from "../systems/welteT100/bar";
|
|
6
6
|
import { welteLicensee } from "../systems/welteLicensee/bar";
|
|
7
7
|
import { inMetersPerMinute, mm, seconds, track } from "../Quantity";
|
|
@@ -116,21 +116,20 @@ const positionsByPitch = (bar) => new Map(Array.from({ length: bar.trackCount },
|
|
|
116
116
|
}));
|
|
117
117
|
/**
|
|
118
118
|
* Reads one of his e-roll files as a copy of the roll, in millimetres
|
|
119
|
-
* of paper
|
|
120
|
-
* the bar would leave it.
|
|
119
|
+
* of paper, on the bar it was cut for and in that bar's numbering. A
|
|
120
|
+
* position the bar does not read is left out, as the bar would leave it.
|
|
121
121
|
*/
|
|
122
|
-
export function readFromPhillipsEroll(buffer, { system = welteT100,
|
|
122
|
+
export function readFromPhillipsEroll(buffer, { system = welteT100, controlOffset, placeAt } = {}) {
|
|
123
123
|
const file = read(new Uint8Array(buffer));
|
|
124
124
|
const timed = onOneClock(file.tracks);
|
|
125
125
|
const elapsedAt = clockOf(temposIn(timed), file.header.ticksPerBeat);
|
|
126
126
|
const place = placeAt ?? atStatedSpeed(system);
|
|
127
127
|
const offset = controlOffsetOf(system, controlOffset);
|
|
128
128
|
const notePositions = positionsByPitch(system);
|
|
129
|
-
const onBar = translationBetween(system, bar);
|
|
130
129
|
const positionOf = (number) => notePositions.get(number) ?? track(number - offset);
|
|
131
130
|
const features = notesIn(timed).flatMap((note) => {
|
|
132
|
-
const position =
|
|
133
|
-
if (position
|
|
131
|
+
const position = positionOf(note.pitch);
|
|
132
|
+
if (!system.meaningOf(position))
|
|
134
133
|
return [];
|
|
135
134
|
return [{
|
|
136
135
|
type: 'Hole',
|
|
@@ -16,14 +16,13 @@ export interface SpencerBarOptions {
|
|
|
16
16
|
* the roll it was scanned from. His Welte files are Licensee rolls.
|
|
17
17
|
*/
|
|
18
18
|
system?: TrackerBar;
|
|
19
|
-
/** The edition's bar, onto which the positions are put. */
|
|
20
|
-
bar?: TrackerBar;
|
|
21
19
|
}
|
|
22
20
|
/**
|
|
23
|
-
* Reads the copy
|
|
24
|
-
*
|
|
21
|
+
* Reads the copy on the bar it was cut for, whose numbering it keeps.
|
|
22
|
+
* A hole on a position that bar does not read is left out, as the bar
|
|
23
|
+
* would leave it.
|
|
25
24
|
*/
|
|
26
|
-
export declare function readFromSpencerBar(buffer: ArrayBuffer, { rowsPerInch, system
|
|
25
|
+
export declare function readFromSpencerBar(buffer: ArrayBuffer, { rowsPerInch, system }?: SpencerBarOptions): RollCopy;
|
|
27
26
|
/**
|
|
28
27
|
* The `.ann` file beside a `.bar` holds the player's settings for the
|
|
29
28
|
* roll as lines of "/key: value": title, composer, pianist, roll
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { v4 } from "uuid";
|
|
2
|
-
import { systemOf
|
|
3
|
-
import { welteT100 } from "../systems/welteT100/bar";
|
|
2
|
+
import { systemOf } from "../TrackerBar";
|
|
4
3
|
import { welteLicensee } from "../systems/welteLicensee/bar";
|
|
5
4
|
import { feetPerMinute, inMillimeters, px, track } from "../Quantity";
|
|
6
5
|
/**
|
|
@@ -80,20 +79,20 @@ const holesOf = (events) => {
|
|
|
80
79
|
return holes.sort((a, b) => a.from - b.from);
|
|
81
80
|
};
|
|
82
81
|
/**
|
|
83
|
-
* Reads the copy
|
|
84
|
-
*
|
|
82
|
+
* Reads the copy on the bar it was cut for, whose numbering it keeps.
|
|
83
|
+
* A hole on a position that bar does not read is left out, as the bar
|
|
84
|
+
* would leave it.
|
|
85
85
|
*/
|
|
86
|
-
export function readFromSpencerBar(buffer, { rowsPerInch = SPENCER_ROWS_PER_INCH, system = welteLicensee
|
|
86
|
+
export function readFromSpencerBar(buffer, { rowsPerInch = SPENCER_ROWS_PER_INCH, system = welteLicensee } = {}) {
|
|
87
87
|
const bytes = new Uint8Array(buffer);
|
|
88
88
|
if (byteAt(bytes, TEXT_AT) !== TEXT_TAG) {
|
|
89
89
|
throw new Error('Not a Spencer .bar file: no text after the header');
|
|
90
90
|
}
|
|
91
91
|
const placeOf = (row) => inMillimeters(px(row), rowsPerInch);
|
|
92
|
-
const onBar = translationBetween(system, bar);
|
|
93
92
|
const features = holesOf(eventsIn(bytes, endOfText(bytes, TEXT_AT + 1)))
|
|
94
93
|
.flatMap((hole) => {
|
|
95
|
-
const position =
|
|
96
|
-
if (position
|
|
94
|
+
const position = track(hole.position);
|
|
95
|
+
if (!system.meaningOf(position))
|
|
97
96
|
return [];
|
|
98
97
|
return [{
|
|
99
98
|
type: 'Hole',
|
|
@@ -8,13 +8,10 @@ export interface StanfordAtonOptions {
|
|
|
8
8
|
* perforation on the bar's rewind track.
|
|
9
9
|
*/
|
|
10
10
|
trackShift?: Track;
|
|
11
|
-
/** The edition's bar, onto which the holes are put. */
|
|
12
|
-
bar?: TrackerBar;
|
|
13
11
|
/**
|
|
14
|
-
* The bar the scanned roll was cut for
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* read is left out.
|
|
12
|
+
* The bar the scanned roll was cut for. The scan is calibrated on
|
|
13
|
+
* it and the holes keep its numbering, since a copy is read by the
|
|
14
|
+
* bar it was cut for and by no other.
|
|
18
15
|
*/
|
|
19
16
|
system?: TrackerBar;
|
|
20
17
|
/**
|
|
@@ -24,4 +21,4 @@ export interface StanfordAtonOptions {
|
|
|
24
21
|
*/
|
|
25
22
|
scan?: string;
|
|
26
23
|
}
|
|
27
|
-
export declare function readFromStanfordAton(atonString: string, { trackShift,
|
|
24
|
+
export declare function readFromStanfordAton(atonString: string, { trackShift, system, scan }?: StanfordAtonOptions): RollCopy;
|