linked-rolls 0.3.0 → 0.4.1
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 +18 -10
- package/lib/Emulation.d.ts +7 -1
- package/lib/Emulation.js +71 -17
- package/lib/Plan.d.ts +8 -0
- package/lib/Plan.js +55 -0
- package/lib/Symbol.d.ts +33 -0
- package/lib/Symbol.js +11 -0
- package/lib/schema.json +18 -2
- package/lib/spec/context.json +2 -0
- package/lib/validate.d.ts +6 -5
- package/lib/validate.js +27 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,20 +69,28 @@ reads editions does not need it. For development on both at once, a
|
|
|
69
69
|
checkout of [welte-t100](https://github.com/pfefferniels/welte-t100)
|
|
70
70
|
beside this repository, declared as `file:../welte-t100`, works too.
|
|
71
71
|
|
|
72
|
-
### Alignment and pairing
|
|
72
|
+
### Alignment, order and pairing
|
|
73
73
|
|
|
74
|
-
A perforation takes its place from the holes that carry it.
|
|
74
|
+
A perforation takes its place from the holes that carry it. Four fields
|
|
75
75
|
on a perforation let an editor state where the measurement should give
|
|
76
76
|
way. `alignedWith` names another perforation whose onset this one takes
|
|
77
77
|
in the performance, as a "crescendo off" is meant to fall on the note
|
|
78
|
-
it belongs to. `
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
it belongs to. `before` and `after` name a perforation whose onset this
|
|
79
|
+
one precedes or follows, without saying by how much: where the copies
|
|
80
|
+
disagree on which side of a note an expression falls, the statement
|
|
81
|
+
settles the order. A perforation the measurement already has on the
|
|
82
|
+
stated side keeps its place. One it does not is put on that side, as
|
|
83
|
+
far from the reference as the copies that agree with the statement put
|
|
84
|
+
it, and a punch diameter away where none does. A perforation makes one
|
|
85
|
+
of these three statements at most. `pairedWith` names a partner whose
|
|
86
|
+
distance to this one is fixed, as a "forzando on" belongs with its
|
|
87
|
+
"forzando off": whatever displaces the one displaces the other. Any two
|
|
88
|
+
perforations may be paired, the relation is symmetric, and it is stated
|
|
89
|
+
on one side only. All are applied when a version is emulated.
|
|
90
|
+
`constraintProblems` lists, version by version, the cases in which the
|
|
91
|
+
statements cannot hold: a reference or partner that is absent, a
|
|
92
|
+
perforation placed relative to itself or in several ways at once, one
|
|
93
|
+
claimed by several pairs, or a pair whose members are both placed.
|
|
86
94
|
|
|
87
95
|
## Building
|
|
88
96
|
```
|
package/lib/Emulation.d.ts
CHANGED
|
@@ -22,7 +22,13 @@ export declare class Emulation<Options extends object> {
|
|
|
22
22
|
curves: readonly EmulatedCurve[];
|
|
23
23
|
source?: string;
|
|
24
24
|
constructor(system: ReproducingSystem<Options>, options?: Options);
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Moves the negotiated events to where their statements put them.
|
|
27
|
+
* The view supplies the copies, whose measurements decide how far
|
|
28
|
+
* before or after its reference a perforation goes, and a punch
|
|
29
|
+
* diameter, or a millimetre, where no copy agrees with a statement.
|
|
30
|
+
*/
|
|
31
|
+
applyConstraints(view: EditionView): void;
|
|
26
32
|
emulateVersion(version: Version, view: EditionView, { range, skipToFirstNote }?: EmulationScope): AnyPerformedRollFeature[];
|
|
27
33
|
findEventsPerforming(id: string): AnyPerformedRollFeature[];
|
|
28
34
|
asMIDI(): MidiFile;
|
package/lib/Emulation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MIDIControlEvents } from "midifile-ts";
|
|
2
2
|
import { idOf } from "./Assumption";
|
|
3
|
-
import { pairsAmong } from "./Symbol";
|
|
3
|
+
import { pairsAmong, placementsOf } from "./Symbol";
|
|
4
4
|
/** The punch diameter the edition's copies report, where any of them does. */
|
|
5
5
|
const punchDiameterOf = (view) => {
|
|
6
6
|
const measured = view.edition.copies
|
|
@@ -14,24 +14,70 @@ const propertiesOf = (view) => ({
|
|
|
14
14
|
punchDiameter: punchDiameterOf(view),
|
|
15
15
|
tempo: view.edition.tempoAdjustment
|
|
16
16
|
});
|
|
17
|
+
const mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
18
|
+
/** The onset a symbol has on each copy carrying it, by the copy's index, as the mean of its holes there. */
|
|
19
|
+
const onsetsByCopy = (view, symbolId) => {
|
|
20
|
+
const symbol = view.get(symbolId);
|
|
21
|
+
if (!symbol)
|
|
22
|
+
return new Map();
|
|
23
|
+
const onsets = view.carriersOf(symbol).flatMap((carrier) => {
|
|
24
|
+
const copy = view.getPath(carrier.id)?.[1];
|
|
25
|
+
return typeof copy === 'number' ? [[copy, carrier.horizontal.from]] : [];
|
|
26
|
+
});
|
|
27
|
+
const copies = new Set(onsets.map(([copy]) => copy));
|
|
28
|
+
return new Map([...copies].map(copy => [copy, mean(onsets.filter(([at]) => at === copy).map(([, onset]) => onset))]));
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The measured distance from the onset of the reference to the onset of
|
|
32
|
+
* the follower on each copy carrying both, negative where the follower
|
|
33
|
+
* comes first there.
|
|
34
|
+
*/
|
|
35
|
+
const offsetsBetween = (view, followerId, referenceId) => {
|
|
36
|
+
const references = onsetsByCopy(view, referenceId);
|
|
37
|
+
return [...onsetsByCopy(view, followerId)].flatMap(([copy, onset]) => {
|
|
38
|
+
const reference = references.get(copy);
|
|
39
|
+
return reference === undefined ? [] : [onset - reference];
|
|
40
|
+
});
|
|
41
|
+
};
|
|
17
42
|
/**
|
|
18
|
-
* How far
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* is kept. Events that stay where they are do not appear.
|
|
43
|
+
* How far before or after its reference a follower is put when the
|
|
44
|
+
* measurement does not already have it there: as far as the copies
|
|
45
|
+
* that agree with the statement put it, and one gap where none does.
|
|
22
46
|
*/
|
|
23
|
-
const
|
|
47
|
+
const distanceOnSide = (side, offsets, gap) => {
|
|
48
|
+
const agreeing = offsets
|
|
49
|
+
.filter(offset => side === 'before' ? offset < 0 : offset > 0)
|
|
50
|
+
.map(Math.abs);
|
|
51
|
+
return agreeing.length > 0 ? mean(agreeing) : gap;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* How far each event has to move, in mm, for the placements to hold:
|
|
55
|
+
* an aligned event takes the onset of its reference, one placed before
|
|
56
|
+
* or after its reference keeps its place where the measurement has it
|
|
57
|
+
* on that side and is moved there where it does not, and a paired
|
|
58
|
+
* event follows its partner so that the distance between the two is
|
|
59
|
+
* kept. Events that stay where they are do not appear.
|
|
60
|
+
*/
|
|
61
|
+
const displacementsOf = (events, offsetsBetween, gap) => {
|
|
24
62
|
const byId = new Map(events.map(event => [event.id, event]));
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
/** Where an event comes to lie once its statement and those of its references hold. */
|
|
64
|
+
const placedOnsetOf = (event, visited = new Set()) => {
|
|
65
|
+
const measured = event.horizontal.from;
|
|
66
|
+
const placement = placementsOf(event)[0];
|
|
67
|
+
const reference = placement && byId.get(idOf(placement.reference));
|
|
68
|
+
if (!placement || !reference || reference.id === event.id || visited.has(event.id))
|
|
69
|
+
return measured;
|
|
70
|
+
const referenceOnset = placedOnsetOf(reference, new Set([...visited, event.id]));
|
|
71
|
+
const distance = (side) => distanceOnSide(side, offsetsBetween(event.id, reference.id), gap);
|
|
72
|
+
switch (placement.relation) {
|
|
73
|
+
case 'alignedWith': return referenceOnset;
|
|
74
|
+
case 'before': return measured < referenceOnset ? measured : referenceOnset - distance('before');
|
|
75
|
+
case 'after': return measured > referenceOnset ? measured : referenceOnset + distance('after');
|
|
76
|
+
}
|
|
31
77
|
};
|
|
32
78
|
const displacements = new Map(events
|
|
33
|
-
.
|
|
34
|
-
.
|
|
79
|
+
.map((event) => [event, placedOnsetOf(event) - event.horizontal.from])
|
|
80
|
+
.filter(([, distance]) => distance !== 0));
|
|
35
81
|
pairsAmong(events).forEach(([one, other]) => {
|
|
36
82
|
const shared = displacements.get(one) ?? displacements.get(other);
|
|
37
83
|
if (shared === undefined)
|
|
@@ -89,8 +135,16 @@ export class Emulation {
|
|
|
89
135
|
this.system = system;
|
|
90
136
|
this.options = options;
|
|
91
137
|
}
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Moves the negotiated events to where their statements put them.
|
|
140
|
+
* The view supplies the copies, whose measurements decide how far
|
|
141
|
+
* before or after its reference a perforation goes, and a punch
|
|
142
|
+
* diameter, or a millimetre, where no copy agrees with a statement.
|
|
143
|
+
*/
|
|
144
|
+
applyConstraints(view) {
|
|
145
|
+
const gap = punchDiameterOf(view) ?? 1;
|
|
146
|
+
const offsets = (follower, reference) => offsetsBetween(view, follower, reference);
|
|
147
|
+
displacementsOf(this.negotiatedEvents, offsets, gap).forEach((distance, event) => {
|
|
94
148
|
event.horizontal.from += distance;
|
|
95
149
|
event.horizontal.to += distance;
|
|
96
150
|
});
|
|
@@ -118,7 +172,7 @@ export class Emulation {
|
|
|
118
172
|
this.curves = [];
|
|
119
173
|
return this.midiEvents;
|
|
120
174
|
}
|
|
121
|
-
this.applyConstraints();
|
|
175
|
+
this.applyConstraints(view);
|
|
122
176
|
const performance = this.system.perform(this.negotiatedEvents, this.options, propertiesOf(view));
|
|
123
177
|
this.curves = performance.curves;
|
|
124
178
|
const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
|
package/lib/Plan.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Draft } from "immer";
|
|
2
2
|
import { EditionView } from "./EditionView";
|
|
3
3
|
import { Edition } from "./Edition";
|
|
4
|
+
import { AnySymbol } from "./Symbol";
|
|
4
5
|
import { CollationTolerance } from "./Collation";
|
|
5
6
|
import { Edit } from "./Edit";
|
|
6
7
|
import { RollCopy } from "./RollCopy";
|
|
@@ -175,6 +176,13 @@ export declare class RemoveFeature extends BasePlan {
|
|
|
175
176
|
constructor(copyID: string, featureIDs: string[]);
|
|
176
177
|
build(): EditionOp[];
|
|
177
178
|
}
|
|
179
|
+
/** The symbols of the versions that no other copy carries. */
|
|
180
|
+
export declare const symbolsCarriedOnlyBy: (edition: Edition, copyId: string) => AnySymbol[];
|
|
181
|
+
/**
|
|
182
|
+
* Takes the copy out of the edition together with the symbols only it
|
|
183
|
+
* carries, and with every reference the versions made to those symbols.
|
|
184
|
+
*/
|
|
185
|
+
export declare const removeCopy: (copyId: string) => EditionOp;
|
|
178
186
|
export declare class MergeEdits extends BasePlan {
|
|
179
187
|
private versionId;
|
|
180
188
|
private toMerge;
|
package/lib/Plan.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAt } from "./EditionView";
|
|
2
|
+
import { isPerforation, placementRelations } from "./Symbol";
|
|
2
3
|
import { v4 } from "uuid";
|
|
3
4
|
import { asSymbols } from "./RollCopy";
|
|
4
5
|
import { assignReference, idOf } from "./Assumption";
|
|
@@ -378,6 +379,60 @@ export class RemoveFeature extends BasePlan {
|
|
|
378
379
|
];
|
|
379
380
|
}
|
|
380
381
|
}
|
|
382
|
+
const featureIdsOf = (copy) => new Set(copy.features.map(feature => feature.id));
|
|
383
|
+
const insertedIn = (versions) => versions.flatMap(version => version.edits).flatMap(edit => edit.insert ?? []);
|
|
384
|
+
/**
|
|
385
|
+
* A symbol every carrier of which lies on the copy loses its evidence
|
|
386
|
+
* with the copy. A symbol without carriers, such as a label, stands
|
|
387
|
+
* on its own.
|
|
388
|
+
*/
|
|
389
|
+
const carriedOnlyOn = (features) => (symbol) => symbol.carriers.length > 0 && symbol.carriers.every(carrier => features.has(idOf(carrier)));
|
|
390
|
+
/** The symbols of the versions that no other copy carries. */
|
|
391
|
+
export const symbolsCarriedOnlyBy = (edition, copyId) => {
|
|
392
|
+
const copy = edition.copies.find(c => c.id === copyId);
|
|
393
|
+
return copy ? insertedIn(edition.versions).filter(carriedOnlyOn(featureIdsOf(copy))) : [];
|
|
394
|
+
};
|
|
395
|
+
const references = [...placementRelations, 'pairedWith'];
|
|
396
|
+
const forgetPerforations = (perforation, dropped) => references
|
|
397
|
+
.filter(relation => {
|
|
398
|
+
const reference = perforation[relation];
|
|
399
|
+
return reference && dropped.has(idOf(reference));
|
|
400
|
+
})
|
|
401
|
+
.forEach(relation => { delete perforation[relation]; });
|
|
402
|
+
const forgetFeatures = (symbol, features, dropped) => {
|
|
403
|
+
symbol.carriers = symbol.carriers.filter(carrier => !features.has(idOf(carrier)));
|
|
404
|
+
if (isPerforation(symbol))
|
|
405
|
+
forgetPerforations(symbol, dropped);
|
|
406
|
+
};
|
|
407
|
+
const forgetCopy = (edit, features, dropped) => {
|
|
408
|
+
if (edit.insert) {
|
|
409
|
+
edit.insert = edit.insert.filter(symbol => !dropped.has(symbol.id));
|
|
410
|
+
edit.insert.forEach(symbol => forgetFeatures(symbol, features, dropped));
|
|
411
|
+
}
|
|
412
|
+
if (edit.delete) {
|
|
413
|
+
edit.delete = edit.delete.filter(id => !dropped.has(id));
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const isEmpty = (edit) => !edit.insert?.length && !edit.delete?.length;
|
|
417
|
+
/** Drops the edits the removal has emptied and leaves those that were empty before alone. */
|
|
418
|
+
const forgetCopyIn = (version, features, dropped) => {
|
|
419
|
+
const emptyBefore = new Set(version.edits.filter(isEmpty).map(edit => edit.id));
|
|
420
|
+
version.edits.forEach(edit => forgetCopy(edit, features, dropped));
|
|
421
|
+
version.edits = version.edits.filter(edit => !isEmpty(edit) || emptyBefore.has(edit.id));
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* Takes the copy out of the edition together with the symbols only it
|
|
425
|
+
* carries, and with every reference the versions made to those symbols.
|
|
426
|
+
*/
|
|
427
|
+
export const removeCopy = (copyId) => draft => {
|
|
428
|
+
const copy = draft.copies.find(c => c.id === copyId);
|
|
429
|
+
if (!copy)
|
|
430
|
+
return;
|
|
431
|
+
const features = featureIdsOf(copy);
|
|
432
|
+
const dropped = new Set(insertedIn(draft.versions).filter(carriedOnlyOn(features)).map(symbol => symbol.id));
|
|
433
|
+
draft.copies = draft.copies.filter(c => c.id !== copyId);
|
|
434
|
+
draft.versions.forEach(version => forgetCopyIn(version, features, dropped));
|
|
435
|
+
};
|
|
381
436
|
export class MergeEdits extends BasePlan {
|
|
382
437
|
constructor(versionId, toMerge) {
|
|
383
438
|
super();
|
package/lib/Symbol.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface Symbol<T extends string> extends WithId {
|
|
|
19
19
|
carriers: ReferenceAssumption[];
|
|
20
20
|
}
|
|
21
21
|
export declare const isSymbol: (object: any) => object is AnySymbol;
|
|
22
|
+
export declare const isPerforation: (symbol: AnySymbol) => symbol is Note | Expression;
|
|
22
23
|
/**
|
|
23
24
|
* A perforation is a symbol that is typically encoded as a single punched
|
|
24
25
|
* hole or a group of punched holes in the physical carrier.
|
|
@@ -34,6 +35,24 @@ export interface Perforation<T extends string> extends Symbol<T> {
|
|
|
34
35
|
* @see reo:alignedWith
|
|
35
36
|
*/
|
|
36
37
|
alignedWith?: ReferenceAssumption;
|
|
38
|
+
/**
|
|
39
|
+
* A perforation whose onset this one precedes when the roll is
|
|
40
|
+
* performed, without saying by how much: a "crescendo off" ends
|
|
41
|
+
* before the note it leads to begins, even where the copies
|
|
42
|
+
* disagree on it. Where the measurement has it so, nothing moves;
|
|
43
|
+
* where it does not, this one is placed before the reference as
|
|
44
|
+
* far as the copies that agree put it. This points to the
|
|
45
|
+
* perforation by its `@id`.
|
|
46
|
+
* @see reo:before
|
|
47
|
+
*/
|
|
48
|
+
before?: ReferenceAssumption;
|
|
49
|
+
/**
|
|
50
|
+
* A perforation whose onset this one follows when the roll is
|
|
51
|
+
* performed, the counterpart of `before`. This points to the
|
|
52
|
+
* perforation by its `@id`.
|
|
53
|
+
* @see reo:after
|
|
54
|
+
*/
|
|
55
|
+
after?: ReferenceAssumption;
|
|
37
56
|
/**
|
|
38
57
|
* The perforation this one forms a pair with, e.g. a "forzando on"
|
|
39
58
|
* with its "forzando off". Any two perforations may be paired.
|
|
@@ -44,6 +63,20 @@ export interface Perforation<T extends string> extends Symbol<T> {
|
|
|
44
63
|
*/
|
|
45
64
|
pairedWith?: ReferenceAssumption;
|
|
46
65
|
}
|
|
66
|
+
export declare const placementRelations: readonly ["alignedWith", "before", "after"];
|
|
67
|
+
/** The ways a perforation may be placed relative to another. */
|
|
68
|
+
export type PlacementRelation = typeof placementRelations[number];
|
|
69
|
+
type Placeable = Partial<Record<PlacementRelation, ReferenceAssumption>>;
|
|
70
|
+
export type Placement = {
|
|
71
|
+
relation: PlacementRelation;
|
|
72
|
+
reference: ReferenceAssumption;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The statements placing a perforation relative to others, alignment
|
|
76
|
+
* first. A perforation is meant to make one at most; the first is the
|
|
77
|
+
* one a performance applies.
|
|
78
|
+
*/
|
|
79
|
+
export declare const placementsOf: (perforation: Placeable) => Placement[];
|
|
47
80
|
type Pairable = WithId & {
|
|
48
81
|
pairedWith?: ReferenceAssumption;
|
|
49
82
|
};
|
package/lib/Symbol.js
CHANGED
|
@@ -3,6 +3,17 @@ export const isSymbol = (object) => {
|
|
|
3
3
|
return ('type' in object
|
|
4
4
|
&& (object.type === 'note' || object.type === 'expression' || object.type === 'text'));
|
|
5
5
|
};
|
|
6
|
+
export const isPerforation = (symbol) => symbol.type !== 'text';
|
|
7
|
+
export const placementRelations = ['alignedWith', 'before', 'after'];
|
|
8
|
+
/**
|
|
9
|
+
* The statements placing a perforation relative to others, alignment
|
|
10
|
+
* first. A perforation is meant to make one at most; the first is the
|
|
11
|
+
* one a performance applies.
|
|
12
|
+
*/
|
|
13
|
+
export const placementsOf = (perforation) => placementRelations.flatMap(relation => {
|
|
14
|
+
const reference = perforation[relation];
|
|
15
|
+
return reference ? [{ relation, reference }] : [];
|
|
16
|
+
});
|
|
6
17
|
/**
|
|
7
18
|
* The pairs among the given perforations, each once and in the order
|
|
8
19
|
* the pairing is stated. A pair whose partner is absent is left out.
|
package/lib/schema.json
CHANGED
|
@@ -425,10 +425,18 @@
|
|
|
425
425
|
"Expression": {
|
|
426
426
|
"description": "An expression symbol, representing a perforation on the roll that governs dynamics, pedaling, or mechanical functions of the reproducing piano. Each expression has a scope (bass or treble) and a specific expression type. [ontology: reo:Expression]",
|
|
427
427
|
"properties": {
|
|
428
|
+
"after": {
|
|
429
|
+
"$ref": "#/definitions/ReferenceAssumption",
|
|
430
|
+
"description": "A perforation whose onset this one follows when the roll is performed, the counterpart of `before`. This points to the perforation by its `@id`. [ontology: reo:after]"
|
|
431
|
+
},
|
|
428
432
|
"alignedWith": {
|
|
429
433
|
"$ref": "#/definitions/ReferenceAssumption",
|
|
430
434
|
"description": "In piano rolls, perforations are often aligned with other perforations, e.g. a \"crescendo off\" might be logically aligned to the start of a note perforation. This points to the perforation by its `@id`. [ontology: reo:alignedWith]"
|
|
431
435
|
},
|
|
436
|
+
"before": {
|
|
437
|
+
"$ref": "#/definitions/ReferenceAssumption",
|
|
438
|
+
"description": "A perforation whose onset this one precedes when the roll is performed, without saying by how much: a \"crescendo off\" ends before the note it leads to begins, even where the copies disagree on it. Where the measurement has it so, nothing moves; where it does not, this one is placed before the reference as far as the copies that agree put it. This points to the perforation by its `@id`. [ontology: reo:before]"
|
|
439
|
+
},
|
|
432
440
|
"carriers": {
|
|
433
441
|
"description": "References to the physical features (e.g. holes) on the roll copies that carry this symbol. Since the association might be debatable, it can be annotated. There might be e.g. doubts about the meaning and correct transcription of a feature on a physical roll. This points to a feature by its `@id`. [ontology: crm:P128i is carried by]",
|
|
434
442
|
"items": {
|
|
@@ -813,10 +821,18 @@
|
|
|
813
821
|
"Note": {
|
|
814
822
|
"description": "A note symbol, representing a single pitched musical event on the roll. The pitch is encoded via the tracker bar position (track number). [ontology: reo:Note]",
|
|
815
823
|
"properties": {
|
|
824
|
+
"after": {
|
|
825
|
+
"$ref": "#/definitions/ReferenceAssumption",
|
|
826
|
+
"description": "A perforation whose onset this one follows when the roll is performed, the counterpart of `before`. This points to the perforation by its `@id`. [ontology: reo:after]"
|
|
827
|
+
},
|
|
816
828
|
"alignedWith": {
|
|
817
829
|
"$ref": "#/definitions/ReferenceAssumption",
|
|
818
830
|
"description": "In piano rolls, perforations are often aligned with other perforations, e.g. a \"crescendo off\" might be logically aligned to the start of a note perforation. This points to the perforation by its `@id`. [ontology: reo:alignedWith]"
|
|
819
831
|
},
|
|
832
|
+
"before": {
|
|
833
|
+
"$ref": "#/definitions/ReferenceAssumption",
|
|
834
|
+
"description": "A perforation whose onset this one precedes when the roll is performed, without saying by how much: a \"crescendo off\" ends before the note it leads to begins, even where the copies disagree on it. Where the measurement has it so, nothing moves; where it does not, this one is placed before the reference as far as the copies that agree put it. This points to the perforation by its `@id`. [ontology: reo:before]"
|
|
835
|
+
},
|
|
820
836
|
"carriers": {
|
|
821
837
|
"description": "References to the physical features (e.g. holes) on the roll copies that carry this symbol. Since the association might be debatable, it can be annotated. There might be e.g. doubts about the meaning and correct transcription of a feature on a physical roll. This points to a feature by its `@id`. [ontology: crm:P128i is carried by]",
|
|
822
838
|
"items": {
|
|
@@ -1173,7 +1189,7 @@
|
|
|
1173
1189
|
],
|
|
1174
1190
|
"type": "object"
|
|
1175
1191
|
},
|
|
1176
|
-
"ObjectAssumption<alias-731470504-73739-73887-731470504-0-217694<def-interface-src_Symbol.ts-
|
|
1192
|
+
"ObjectAssumption<alias-731470504-73739-73887-731470504-0-217694<def-interface-src_Symbol.ts-5670-6034-src_Symbol.ts-0-6242400334313,\"carriers\">>": {
|
|
1177
1193
|
"description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
|
|
1178
1194
|
"properties": {
|
|
1179
1195
|
"@annotation": {
|
|
@@ -1744,7 +1760,7 @@
|
|
|
1744
1760
|
"description": "The method by which this writing was produced (e.g. through print, handwriting, or stamping). [ontology: crm:P2 has type]"
|
|
1745
1761
|
},
|
|
1746
1762
|
"transcription": {
|
|
1747
|
-
"$ref": "#/definitions/ObjectAssumption%3Calias-731470504-73739-73887-731470504-0-217694%3Cdef-interface-src_Symbol.ts-
|
|
1763
|
+
"$ref": "#/definitions/ObjectAssumption%3Calias-731470504-73739-73887-731470504-0-217694%3Cdef-interface-src_Symbol.ts-5670-6034-src_Symbol.ts-0-6242400334313%2C%22carriers%22%3E%3E",
|
|
1748
1764
|
"description": "A transcription of the text content of the writing. This is an object assumption so that the transcription can be annotated with a belief about its correctness. [ontology: crm:P128 carries]"
|
|
1749
1765
|
},
|
|
1750
1766
|
"vertical": {
|
package/lib/spec/context.json
CHANGED
package/lib/validate.d.ts
CHANGED
|
@@ -6,14 +6,15 @@ export { validate };
|
|
|
6
6
|
export type ConstraintProblem = {
|
|
7
7
|
version: string;
|
|
8
8
|
symbol: string;
|
|
9
|
-
problem: 'alignment-reference-missing' | 'partner-missing' | 'paired-with-itself' | 'in-several-pairs' | 'pair-
|
|
9
|
+
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';
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
|
-
* Where the
|
|
12
|
+
* Where the placements and pairings of the edition cannot hold as
|
|
13
13
|
* stated, version by version: a reference or partner absent from the
|
|
14
|
-
* version, a perforation
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* version, a perforation placed relative to itself or in several ways
|
|
15
|
+
* at once, one claimed by several pairs, or a pair whose members are
|
|
16
|
+
* both placed and so cannot keep their distance and follow their
|
|
17
|
+
* references at once.
|
|
17
18
|
*/
|
|
18
19
|
export declare const constraintProblems: (view: EditionView) => ConstraintProblem[];
|
|
19
20
|
export type UnknownExpressionType = {
|
package/lib/validate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import * as schema from "./schema.json";
|
|
3
3
|
import { idOf } from "./Assumption";
|
|
4
|
-
import { pairsAmong } from "./Symbol";
|
|
4
|
+
import { isPerforation, pairsAmong, placementsOf } from "./Symbol";
|
|
5
5
|
const ajv = new Ajv({
|
|
6
6
|
strict: false,
|
|
7
7
|
formats: {
|
|
@@ -10,13 +10,24 @@ const ajv = new Ajv({
|
|
|
10
10
|
});
|
|
11
11
|
const validate = ajv.compile(schema);
|
|
12
12
|
export { validate };
|
|
13
|
-
const
|
|
13
|
+
const missingReference = {
|
|
14
|
+
alignedWith: 'alignment-reference-missing',
|
|
15
|
+
before: 'before-reference-missing',
|
|
16
|
+
after: 'after-reference-missing'
|
|
17
|
+
};
|
|
14
18
|
const problemsIn = (version, perforations) => {
|
|
15
19
|
const ids = new Set(perforations.map(p => p.id));
|
|
16
20
|
const report = (symbol, problem) => ({ version, symbol, problem });
|
|
17
21
|
const missingReferences = perforations
|
|
18
|
-
.
|
|
19
|
-
.
|
|
22
|
+
.flatMap(p => placementsOf(p)
|
|
23
|
+
.filter(({ reference }) => !ids.has(idOf(reference)))
|
|
24
|
+
.map(({ relation }) => report(p.id, missingReference[relation])));
|
|
25
|
+
const selfPlaced = perforations
|
|
26
|
+
.filter(p => placementsOf(p).some(({ reference }) => idOf(reference) === p.id))
|
|
27
|
+
.map(p => report(p.id, 'placed-relative-to-itself'));
|
|
28
|
+
const placedSeveralWays = perforations
|
|
29
|
+
.filter(p => placementsOf(p).length > 1)
|
|
30
|
+
.map(p => report(p.id, 'placed-several-ways'));
|
|
20
31
|
const missingPartners = perforations
|
|
21
32
|
.filter(p => p.pairedWith && !ids.has(idOf(p.pairedWith)))
|
|
22
33
|
.map(p => report(p.id, 'partner-missing'));
|
|
@@ -28,17 +39,21 @@ const problemsIn = (version, perforations) => {
|
|
|
28
39
|
const inSeveralPairs = perforations
|
|
29
40
|
.filter(p => pairsOf(p).length > 1)
|
|
30
41
|
.map(p => report(p.id, 'in-several-pairs'));
|
|
31
|
-
const
|
|
32
|
-
.filter(([one, other]) => one.
|
|
33
|
-
.flatMap(pair => pair.map(p => report(p.id, 'pair-
|
|
34
|
-
return [
|
|
42
|
+
const placedOnBothSides = pairs
|
|
43
|
+
.filter(([one, other]) => placementsOf(one).length > 0 && placementsOf(other).length > 0)
|
|
44
|
+
.flatMap(pair => pair.map(p => report(p.id, 'pair-placed-on-both-sides')));
|
|
45
|
+
return [
|
|
46
|
+
...missingReferences, ...selfPlaced, ...placedSeveralWays,
|
|
47
|
+
...missingPartners, ...selfPaired, ...inSeveralPairs, ...placedOnBothSides
|
|
48
|
+
];
|
|
35
49
|
};
|
|
36
50
|
/**
|
|
37
|
-
* Where the
|
|
51
|
+
* Where the placements and pairings of the edition cannot hold as
|
|
38
52
|
* stated, version by version: a reference or partner absent from the
|
|
39
|
-
* version, a perforation
|
|
40
|
-
*
|
|
41
|
-
*
|
|
53
|
+
* version, a perforation placed relative to itself or in several ways
|
|
54
|
+
* at once, one claimed by several pairs, or a pair whose members are
|
|
55
|
+
* both placed and so cannot keep their distance and follow their
|
|
56
|
+
* references at once.
|
|
42
57
|
*/
|
|
43
58
|
export const constraintProblems = (view) => view.edition.versions.flatMap(version => problemsIn(version.id, view.snapshot(version.id).filter(isPerforation)));
|
|
44
59
|
const isExpression = (symbol) => symbol.type === 'expression';
|
package/package.json
CHANGED