linked-rolls 0.19.1 → 0.21.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/README.md +31 -6
- 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 +5 -4
- package/lib/ReproducingSystem.d.ts +38 -2
- package/lib/RollCopy.d.ts +25 -2
- package/lib/RollCopy.js +30 -4
- package/lib/TrackerBar.d.ts +57 -2
- package/lib/TrackerBar.js +35 -17
- 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 +10 -1
- package/lib/context.js +10 -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 +4 -6
- 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/spec/welte-t98.context.json +10 -0
- package/lib/substitution.d.ts +73 -0
- package/lib/substitution.js +175 -0
- package/lib/systems/velocity.d.ts +34 -0
- package/lib/systems/velocity.js +36 -0
- package/lib/systems/welteLicensee/system.d.ts +46 -0
- package/lib/systems/welteLicensee/system.js +60 -0
- package/lib/systems/welteT100/system.d.ts +19 -16
- package/lib/systems/welteT100/system.js +42 -47
- package/lib/systems/welteT98/bar.d.ts +15 -2
- package/lib/systems/welteT98/bar.js +31 -3
- package/lib/systems/welteT98/system.d.ts +119 -0
- package/lib/systems/welteT98/system.js +245 -0
- package/package.json +12 -4
package/lib/TrackerBar.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import { track } from "./Quantity";
|
|
1
|
+
import { mm, track } from "./Quantity";
|
|
2
|
+
/**
|
|
3
|
+
* What a position says, as a key. Two bars read the same thing exactly
|
|
4
|
+
* where their keys agree, which is what lets a symbol cross from one
|
|
5
|
+
* system to another and what decides whether two symbols collate.
|
|
6
|
+
*/
|
|
7
|
+
export const keyOf = (meaning) => meaning.type === 'note'
|
|
8
|
+
? `note ${meaning.pitch}`
|
|
9
|
+
: `expression ${meaning.scope} ${meaning.expressionType}`;
|
|
2
10
|
const SYSTEM_IRI = 'https://w3id.org/reo/type/system/';
|
|
3
11
|
/** The roll system a tracker bar belongs to, as the roll metadata states it. */
|
|
4
12
|
export const systemOf = (bar) => ({ id: SYSTEM_IRI + bar.id, name: bar.name, sameAs: [] });
|
|
13
|
+
/** The identifier of a system the type vocabulary knows, from the IRI naming it. */
|
|
14
|
+
export const systemIdIn = (id) => id?.startsWith(SYSTEM_IRI) ? id.slice(SYSTEM_IRI.length) : undefined;
|
|
5
15
|
/** The identifier of a system the type vocabulary knows, from its concept. */
|
|
6
|
-
export const systemIdOf = (system) => system?.id
|
|
16
|
+
export const systemIdOf = (system) => systemIdIn(system?.id);
|
|
7
17
|
const areasOf = ({ notes, trackCount }) => [
|
|
8
18
|
{ role: 'bass-expression', from: track(1), to: track(notes.from - 1) },
|
|
9
19
|
{ role: 'note', from: track(notes.from), to: track(notes.to) },
|
|
@@ -28,41 +38,49 @@ export const describeTrackerBar = (spec) => {
|
|
|
28
38
|
return undefined;
|
|
29
39
|
return { type: 'expression', expressionType, scope: scopeOf(role) };
|
|
30
40
|
};
|
|
41
|
+
const positions = new Map(Array.from({ length: spec.trackCount }, (_, i) => track(i + 1))
|
|
42
|
+
.flatMap(position => {
|
|
43
|
+
const meaning = meaningOf(position);
|
|
44
|
+
return meaning ? [[keyOf(meaning), position]] : [];
|
|
45
|
+
}));
|
|
31
46
|
const rewind = spec.rewindTrack
|
|
32
47
|
?? [...spec.expressions].find(([, type]) => type === 'Rewind')?.[0];
|
|
33
48
|
if (rewind === undefined) {
|
|
34
49
|
throw new Error(`${spec.name} declares no rewind track`);
|
|
35
50
|
}
|
|
51
|
+
// A rewind on a line of its own is unambiguous; one sharing a line is only
|
|
52
|
+
// the rewind when it is far longer than that line's usual command.
|
|
53
|
+
const shared = spec.rewindTrack !== undefined;
|
|
54
|
+
const endsAt = (features) => {
|
|
55
|
+
const hold = spec.rewindHold ?? mm(0);
|
|
56
|
+
const candidates = features
|
|
57
|
+
.filter(feature => feature.vertical.from === rewind)
|
|
58
|
+
.filter(feature => !shared || feature.horizontal.to - feature.horizontal.from >= hold)
|
|
59
|
+
.map(feature => feature.horizontal.from);
|
|
60
|
+
return candidates.length ? { at: mm(Math.min(...candidates)), because: 'rewind' } : undefined;
|
|
61
|
+
};
|
|
36
62
|
return {
|
|
37
63
|
id: spec.id,
|
|
38
64
|
name: spec.name,
|
|
39
65
|
width: spec.width,
|
|
40
66
|
trackCount: spec.trackCount,
|
|
67
|
+
endsAt,
|
|
41
68
|
areas,
|
|
42
69
|
expressionTypes: [...new Set(spec.expressions.values())],
|
|
43
70
|
rewindTrack: track(rewind),
|
|
44
71
|
...(spec.paperSpeed && { paperSpeed: spec.paperSpeed }),
|
|
45
72
|
meaningOf,
|
|
73
|
+
positionOf: meaning => positions.get(keyOf(meaning)),
|
|
46
74
|
roleOf
|
|
47
75
|
};
|
|
48
76
|
};
|
|
49
|
-
const keyOf = (meaning) => meaning.type === 'note' ? `note ${meaning.pitch}` : `${meaning.scope} ${meaning.expressionType}`;
|
|
50
|
-
const positionsOf = (bar) => Array.from({ length: bar.trackCount }, (_, i) => track(i + 1));
|
|
51
77
|
/**
|
|
52
78
|
* Puts a position of one bar onto the position of another that reads
|
|
53
79
|
* the same thing, or nowhere when the other bar does not read it. This
|
|
54
|
-
* is how a copy
|
|
55
|
-
*
|
|
80
|
+
* is how a copy read in one system's numbering is put into another's,
|
|
81
|
+
* as the migration does for a Licensee copy stored on T-100 tracks.
|
|
56
82
|
*/
|
|
57
|
-
export const translationBetween = (from, to) => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const meaning = to.meaningOf(position);
|
|
61
|
-
if (meaning)
|
|
62
|
-
positions.set(keyOf(meaning), position);
|
|
63
|
-
});
|
|
64
|
-
return position => {
|
|
65
|
-
const meaning = from.meaningOf(position);
|
|
66
|
-
return meaning ? positions.get(keyOf(meaning)) : undefined;
|
|
67
|
-
};
|
|
83
|
+
export const translationBetween = (from, to) => (position) => {
|
|
84
|
+
const meaning = from.meaningOf(position);
|
|
85
|
+
return meaning && to.positionOf(meaning);
|
|
68
86
|
};
|
package/lib/Version.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Edit } from "./Edit";
|
|
2
|
-
import {
|
|
2
|
+
import { Concept } from "./Agent";
|
|
3
|
+
import { ActorAssignment, DateAssignment, ReferenceAssumption } from "./Assumption";
|
|
3
4
|
import { CollationTolerance } from "./Collation";
|
|
4
5
|
import { AnySymbol } from "./Symbol";
|
|
5
6
|
import { WithId, WithNote, WithType } from "./utils";
|
|
@@ -42,6 +43,37 @@ export type Derivation = ReferenceAssumption & {
|
|
|
42
43
|
};
|
|
43
44
|
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
44
45
|
export declare const collationToleranceOf: (derivation: Readonly<Derivation>) => CollationTolerance;
|
|
46
|
+
/**
|
|
47
|
+
* How a version was made, where that is known and worth stating.
|
|
48
|
+
*
|
|
49
|
+
* A roll issued for another system was re-punched by an editor of the
|
|
50
|
+
* publisher's, and that was an editorial act rather than a conversion:
|
|
51
|
+
* Lawson names Kähle as the man who corrected second masters for the
|
|
52
|
+
* green system. The rule he worked by is the procedure named here, and
|
|
53
|
+
* the version's edits carry it out, so the mechanical part of a
|
|
54
|
+
* transfer is stated once instead of being spelled out per note.
|
|
55
|
+
* @see lrmoo:F28 Expression Creation
|
|
56
|
+
*/
|
|
57
|
+
export interface VersionCreation {
|
|
58
|
+
/**
|
|
59
|
+
* Who carried the act out. An `ObjectAssumption`, so an attribution
|
|
60
|
+
* can carry the belief it rests on and the reasons for it.
|
|
61
|
+
* @see crm:P14 carried out by
|
|
62
|
+
*/
|
|
63
|
+
actor?: ActorAssignment;
|
|
64
|
+
/**
|
|
65
|
+
* When it took place.
|
|
66
|
+
* @see dcterms:date
|
|
67
|
+
*/
|
|
68
|
+
date?: DateAssignment;
|
|
69
|
+
/**
|
|
70
|
+
* The rule followed, as a term of the vocabulary: the notes stand
|
|
71
|
+
* at their pitch, the expression is re-spelled in the other
|
|
72
|
+
* system's words.
|
|
73
|
+
* @see crm:P33 used specific technique
|
|
74
|
+
*/
|
|
75
|
+
procedure?: Concept;
|
|
76
|
+
}
|
|
45
77
|
/**
|
|
46
78
|
* A version is defined by the sum of edits applied
|
|
47
79
|
* to the version it is based on. For simple identification,
|
|
@@ -54,6 +86,16 @@ export interface Version extends WithId, WithType<'Version'> {
|
|
|
54
86
|
* @see reo:siglum
|
|
55
87
|
*/
|
|
56
88
|
siglum: string;
|
|
89
|
+
/**
|
|
90
|
+
* The reproducing system this version is coded for. One roll was
|
|
91
|
+
* often issued for several of them, and a version is a reading in
|
|
92
|
+
* one system's words: its expression types are that system's
|
|
93
|
+
* vocabulary and its notes sit on that bar's positions. A system
|
|
94
|
+
* the type vocabulary knows carries the IRI of its concept as
|
|
95
|
+
* `id`, from which the export takes the system's own context.
|
|
96
|
+
* @see crm:P2 has type
|
|
97
|
+
*/
|
|
98
|
+
system: Concept;
|
|
57
99
|
/**
|
|
58
100
|
* Whether the version served as a master for reproductions
|
|
59
101
|
* or exists on one copy only.
|
|
@@ -65,6 +107,12 @@ export interface Version extends WithId, WithType<'Version'> {
|
|
|
65
107
|
* @see lrmoo:R76 is derivative of
|
|
66
108
|
*/
|
|
67
109
|
basedOn?: Derivation;
|
|
110
|
+
/**
|
|
111
|
+
* The act that made this version, where it is known: who carried it
|
|
112
|
+
* out, when, and by what rule.
|
|
113
|
+
* @see lrmoo:R17i was created by
|
|
114
|
+
*/
|
|
115
|
+
creation?: VersionCreation;
|
|
68
116
|
/**
|
|
69
117
|
* The list of edits that, applied to the base version, produce this version.
|
|
70
118
|
* @see reo:involvedEdit
|
package/lib/asJsonLd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { systemIdIn } from "./TrackerBar";
|
|
2
2
|
export const exportDate = (date) => {
|
|
3
3
|
const year = date.getFullYear();
|
|
4
4
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -39,20 +39,35 @@ const asJsonLdEntity = (obj) => {
|
|
|
39
39
|
return result;
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
* expression types as that system's terms.
|
|
42
|
+
* Gives every version the context of its own reproducing system, which
|
|
43
|
+
* reads its expression types as that system's terms.
|
|
44
|
+
*
|
|
45
|
+
* It sits on the version rather than on the edition because one edition
|
|
46
|
+
* may hold versions of several systems, and each system's context
|
|
47
|
+
* defines `expressionType` with its own `@vocab`. Two of them in one
|
|
48
|
+
* context array would leave every expression type in the document
|
|
49
|
+
* reading as whichever came last. An embedded context merges with the
|
|
50
|
+
* active one, so the shared prefixes survive.
|
|
44
51
|
*/
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const withSystemContexts = (node) => {
|
|
53
|
+
if (Array.isArray(node))
|
|
54
|
+
return node.map(withSystemContexts);
|
|
55
|
+
if (node === null || typeof node !== 'object')
|
|
56
|
+
return node;
|
|
57
|
+
const walked = Object.fromEntries(Object.entries(node).map(([key, value]) => [key, withSystemContexts(value)]));
|
|
58
|
+
const system = node['@type'] === 'Version'
|
|
59
|
+
? systemIdIn(node.system?.['@id'])
|
|
60
|
+
: undefined;
|
|
61
|
+
return system
|
|
62
|
+
? { '@context': `https://w3id.org/reo/${system}/context.jsonld`, ...walked }
|
|
63
|
+
: walked;
|
|
48
64
|
};
|
|
49
65
|
export const asJsonLd = (edition) => {
|
|
50
66
|
// The context is the export's own; one carried in from an import must not override it.
|
|
51
|
-
const { base, copies, '@context': carried, ...rest } = asJsonLdEntity(edition);
|
|
67
|
+
const { base, copies, '@context': carried, ...rest } = withSystemContexts(asJsonLdEntity(edition));
|
|
52
68
|
return {
|
|
53
69
|
'@context': [
|
|
54
70
|
'https://w3id.org/reo/context.jsonld',
|
|
55
|
-
...systemContextOf(edition),
|
|
56
71
|
{
|
|
57
72
|
'@base': edition.base
|
|
58
73
|
}
|
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,12 @@
|
|
|
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';
|
|
11
|
+
/** The context of the Welte-Mignon T-98, added to an edition of a green roll. */
|
|
12
|
+
export { default as welteT98JsonLdContext } from './spec/welte-t98.context.json';
|
package/lib/context.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
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';
|
|
11
|
+
/** The context of the Welte-Mignon T-98, added to an edition of a green roll. */
|
|
12
|
+
export { default as welteT98JsonLdContext } from './spec/welte-t98.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