linked-rolls 0.15.0 → 0.17.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/EditionView.js +4 -2
- package/lib/Feature.d.ts +20 -2
- package/lib/Feature.js +5 -0
- package/lib/Quantity.d.ts +6 -1
- package/lib/Quantity.js +3 -0
- package/lib/RollCopy.d.ts +10 -0
- package/lib/Version.d.ts +19 -1
- package/lib/Version.js +3 -0
- package/lib/editionOps.d.ts +35 -8
- package/lib/editionOps.js +191 -28
- package/lib/migrate.js +21 -1
- package/lib/readers/stanfordAton.js +4 -1
- package/lib/schema.json +111 -3
- package/lib/spec/context.json +1 -0
- package/lib/validate.d.ts +10 -1
- package/lib/validate.js +12 -7
- package/package.json +2 -1
package/lib/EditionView.js
CHANGED
|
@@ -13,8 +13,10 @@ export const getAt = (path, obj) => {
|
|
|
13
13
|
/** Keys under which an object names others by id, singly or in a list. */
|
|
14
14
|
const referenceKeys = ['delete', 'comprehends', 'motivation'];
|
|
15
15
|
const isObject = (v) => v !== null && typeof v === "object";
|
|
16
|
-
/**
|
|
17
|
-
const
|
|
16
|
+
/** What a reference may state besides the id: a belief about it, and the tolerance a derivation was collated at. */
|
|
17
|
+
const referenceOwnKeys = new Set(['id', '@annotation', 'collationTolerance']);
|
|
18
|
+
/** An object that names another by its id and says nothing of its own beyond that. */
|
|
19
|
+
const isReferenceOnly = (keys) => keys.every(key => referenceOwnKeys.has(key));
|
|
18
20
|
const pathOf = (trail) => {
|
|
19
21
|
const path = [];
|
|
20
22
|
for (let link = trail; link !== null; link = link.up)
|
package/lib/Feature.d.ts
CHANGED
|
@@ -89,6 +89,14 @@ export declare const conditions: {
|
|
|
89
89
|
readonly Mark: readonly ["faded"];
|
|
90
90
|
readonly GluedOn: readonly ["detaching", "ripped"];
|
|
91
91
|
};
|
|
92
|
+
/** The kinds of condition a feature may be in, whichever kind of feature it is. */
|
|
93
|
+
export type FeatureConditionType = typeof conditions[FeatureType][number];
|
|
94
|
+
/**
|
|
95
|
+
* A condition assigned to a feature, annotatable with a belief about
|
|
96
|
+
* its certainty. Which of the kinds a feature may be in depends on its
|
|
97
|
+
* own kind, which `conditions` states.
|
|
98
|
+
*/
|
|
99
|
+
export type FeatureConditionAssignment = ObjectAssumption<ConditionState<FeatureConditionType>>;
|
|
92
100
|
/**
|
|
93
101
|
* A hole (perforation) in the roll paper. Holes are the primary
|
|
94
102
|
* carriers of musical information on piano rolls, as they trigger
|
|
@@ -109,7 +117,7 @@ export interface Hole extends RollFeature<'Hole', typeof conditions.Hole[number]
|
|
|
109
117
|
* A trace is a visible mark or writing on the roll surface.
|
|
110
118
|
* Traces may fade over time.
|
|
111
119
|
*/
|
|
112
|
-
export interface Trace<T extends FeatureType> extends RollFeature<T, typeof conditions
|
|
120
|
+
export interface Trace<T extends FeatureType> extends RollFeature<T, typeof conditions[T][number]> {
|
|
113
121
|
}
|
|
114
122
|
export declare const writingMethods: readonly ['Print', 'Handwriting', 'Stamp'];
|
|
115
123
|
/**
|
|
@@ -165,7 +173,7 @@ export interface GluedOn extends RollFeature<'GluedOn', typeof conditions.GluedO
|
|
|
165
173
|
* Nested features do not need to be positioned explicitly.
|
|
166
174
|
* @see crm:P56 bears feature
|
|
167
175
|
*/
|
|
168
|
-
features?:
|
|
176
|
+
features?: NestedFeature[];
|
|
169
177
|
}
|
|
170
178
|
/**
|
|
171
179
|
* The union of all physical feature types that can appear on a roll:
|
|
@@ -173,4 +181,14 @@ export interface GluedOn extends RollFeature<'GluedOn', typeof conditions.GluedO
|
|
|
173
181
|
* and glued-on patches (paper, tape).
|
|
174
182
|
*/
|
|
175
183
|
export type AnyFeature = Hole | Writing | Mark | GluedOn;
|
|
184
|
+
/**
|
|
185
|
+
* A feature borne by another feature. It states no place of its own,
|
|
186
|
+
* the feature bearing it standing in one.
|
|
187
|
+
*/
|
|
188
|
+
export type NestedFeature = PartialBy<AnyFeature, 'horizontal' | 'vertical'>;
|
|
176
189
|
export declare const isRollFeature: (obj: object) => obj is AnyFeature;
|
|
190
|
+
export declare const isGluedOn: <T extends NestedFeature>(feature: T) => feature is T & GluedOn;
|
|
191
|
+
/** The features a feature bears: a patch those stated as parts of it, any other feature none. */
|
|
192
|
+
export declare const featuresBorneBy: (feature: NestedFeature) => NestedFeature[];
|
|
193
|
+
/** The feature together with everything it bears, as deep as a patch on a patch goes. */
|
|
194
|
+
export declare const withBorneFeatures: (feature: NestedFeature) => NestedFeature[];
|
package/lib/Feature.js
CHANGED
|
@@ -9,3 +9,8 @@ export const writingMethods = ['Print', 'Handwriting', 'Stamp'];
|
|
|
9
9
|
export const isRollFeature = (obj) => {
|
|
10
10
|
return 'type' in obj && featureTypes.includes(obj.type);
|
|
11
11
|
};
|
|
12
|
+
export const isGluedOn = (feature) => feature.type === 'GluedOn';
|
|
13
|
+
/** The features a feature bears: a patch those stated as parts of it, any other feature none. */
|
|
14
|
+
export const featuresBorneBy = (feature) => isGluedOn(feature) ? feature.features ?? [] : [];
|
|
15
|
+
/** The feature together with everything it bears, as deep as a patch on a patch goes. */
|
|
16
|
+
export const withBorneFeatures = (feature) => [feature, ...featuresBorneBy(feature).flatMap(withBorneFeatures)];
|
package/lib/Quantity.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* The unit names are the ones the records state in their `unit` field.
|
|
8
8
|
*/
|
|
9
9
|
declare const unit: unique symbol;
|
|
10
|
-
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min';
|
|
10
|
+
export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in';
|
|
11
11
|
export type Quantity<U extends Unit> = number & {
|
|
12
12
|
readonly [unit]: U;
|
|
13
13
|
};
|
|
@@ -22,6 +22,8 @@ export type Seconds = Quantity<'s'>;
|
|
|
22
22
|
export type Milliseconds = Quantity<'ms'>;
|
|
23
23
|
export type FeetPerMinute = Quantity<'ft/min'>;
|
|
24
24
|
export type MetersPerMinute = Quantity<'m/min'>;
|
|
25
|
+
/** How finely a scan was read: pixels of the image per inch of paper. */
|
|
26
|
+
export type Resolution = Quantity<'px/in'>;
|
|
25
27
|
export declare const mm: (value: number) => Quantity<"mm">;
|
|
26
28
|
export declare const cm: (value: number) => Quantity<"cm">;
|
|
27
29
|
export declare const px: (value: number) => Quantity<"px">;
|
|
@@ -30,6 +32,7 @@ export declare const seconds: (value: number) => Quantity<"s">;
|
|
|
30
32
|
export declare const milliseconds: (value: number) => Quantity<"ms">;
|
|
31
33
|
export declare const feetPerMinute: (value: number) => Quantity<"ft/min">;
|
|
32
34
|
export declare const metersPerMinute: (value: number) => Quantity<"m/min">;
|
|
35
|
+
export declare const pixelsPerInch: (value: number) => Quantity<"px/in">;
|
|
33
36
|
/**
|
|
34
37
|
* A value together with the unit it was measured in, as a record
|
|
35
38
|
* states it.
|
|
@@ -56,6 +59,8 @@ export declare const sum: <U extends Unit>(values: readonly Quantity<U>[]) => Qu
|
|
|
56
59
|
export declare const mean: <U extends Unit>(values: readonly Quantity<U>[]) => Quantity<U>;
|
|
57
60
|
/** A place in a scan taken at `dpi` dots per inch, on the paper. */
|
|
58
61
|
export declare const inMillimeters: (place: Pixels, dpi: number) => Millimeters;
|
|
62
|
+
/** A place on the paper, in a scan taken at `dpi` dots per inch. */
|
|
63
|
+
export declare const inPixels: (place: Millimeters, dpi: number) => Pixels;
|
|
59
64
|
export declare const inCentimeters: (length: Millimeters) => Centimeters;
|
|
60
65
|
export declare const inSeconds: (time: Milliseconds) => Seconds;
|
|
61
66
|
/** A speed as a record states it, in feet or metres per minute. */
|
package/lib/Quantity.js
CHANGED
|
@@ -7,6 +7,7 @@ export const seconds = quantity;
|
|
|
7
7
|
export const milliseconds = quantity;
|
|
8
8
|
export const feetPerMinute = quantity;
|
|
9
9
|
export const metersPerMinute = quantity;
|
|
10
|
+
export const pixelsPerInch = quantity;
|
|
10
11
|
// The second operand takes its unit from the first. Inferred from both,
|
|
11
12
|
// TypeScript would unite two units rather than reject them.
|
|
12
13
|
export const add = (a, b) => quantity(a + b);
|
|
@@ -21,6 +22,8 @@ export const mean = (values) => quantity(sum(values) / values.length);
|
|
|
21
22
|
const MM_PER_INCH = 25.4;
|
|
22
23
|
/** A place in a scan taken at `dpi` dots per inch, on the paper. */
|
|
23
24
|
export const inMillimeters = (place, dpi) => mm(place / dpi * MM_PER_INCH);
|
|
25
|
+
/** A place on the paper, in a scan taken at `dpi` dots per inch. */
|
|
26
|
+
export const inPixels = (place, dpi) => px(place / MM_PER_INCH * dpi);
|
|
24
27
|
export const inCentimeters = (length) => cm(length / 10);
|
|
25
28
|
export const inSeconds = (time) => seconds(time / 1000);
|
|
26
29
|
const METERS_PER_FOOT = 0.3048;
|
package/lib/RollCopy.d.ts
CHANGED
|
@@ -222,6 +222,16 @@ export interface RollCopy extends WithType<'RollCopy'>, WithId {
|
|
|
222
222
|
* Not exported to RDF.
|
|
223
223
|
*/
|
|
224
224
|
scale: number;
|
|
225
|
+
/**
|
|
226
|
+
* The resolution this copy's scan was read at, along the roll.
|
|
227
|
+
* It is what the measurements given in pixels are to be read
|
|
228
|
+
* against, and what every conversion between a place in the
|
|
229
|
+
* scan and one on the paper goes through. A scanner may read
|
|
230
|
+
* across the roll at another resolution, which the analysis
|
|
231
|
+
* files do not state.
|
|
232
|
+
* @see reo:scanResolution
|
|
233
|
+
*/
|
|
234
|
+
scanResolution: Measure<'px/in'>;
|
|
225
235
|
/**
|
|
226
236
|
* Relates this copy's scan to the tracker bar: how the scanning
|
|
227
237
|
* software's hole numbering was shifted onto the bar, and where
|
package/lib/Version.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Edit } from "./Edit";
|
|
2
2
|
import { ReferenceAssumption } from "./Assumption";
|
|
3
|
+
import { CollationTolerance } from "./Collation";
|
|
3
4
|
import { AnySymbol } from "./Symbol";
|
|
4
5
|
import { WithId, WithNote, WithType } from "./utils";
|
|
5
6
|
export declare const versionTypes: readonly [
|
|
@@ -24,6 +25,23 @@ export type VersionType = typeof versionTypes[number];
|
|
|
24
25
|
* @see crm:E33 Linguistic Object
|
|
25
26
|
*/
|
|
26
27
|
export type Motivation = WithType<'motivation'> & WithId & WithNote;
|
|
28
|
+
/**
|
|
29
|
+
* A derivation names the version another one was derived from, together
|
|
30
|
+
* with the tolerance the two were collated at. How precisely the copies
|
|
31
|
+
* put a symbol depends on what they are and on how their features were
|
|
32
|
+
* obtained, so the tolerance can differ from derivation to derivation.
|
|
33
|
+
* @see lrmoo:R76 is derivative of
|
|
34
|
+
*/
|
|
35
|
+
export type Derivation = ReferenceAssumption & {
|
|
36
|
+
/**
|
|
37
|
+
* The tolerance at which the derived version was collated against
|
|
38
|
+
* the one it is based on. A derivation written before the tolerance
|
|
39
|
+
* was held here states none. Not exported to RDF.
|
|
40
|
+
*/
|
|
41
|
+
collationTolerance?: CollationTolerance;
|
|
42
|
+
};
|
|
43
|
+
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
44
|
+
export declare const collationToleranceOf: (derivation: Readonly<Derivation>) => CollationTolerance;
|
|
27
45
|
/**
|
|
28
46
|
* A version is defined by the sum of edits applied
|
|
29
47
|
* to the version it is based on. For simple identification,
|
|
@@ -46,7 +64,7 @@ export interface Version extends WithId, WithType<'Version'> {
|
|
|
46
64
|
* If no derivation is defined, it is assumed that this version represents the mother roll.
|
|
47
65
|
* @see lrmoo:R76 is derivative of
|
|
48
66
|
*/
|
|
49
|
-
basedOn?:
|
|
67
|
+
basedOn?: Derivation;
|
|
50
68
|
/**
|
|
51
69
|
* The list of edits that, applied to the base version, produce this version.
|
|
52
70
|
* @see reo:involvedEdit
|
package/lib/Version.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultCollationTolerance } from "./Collation";
|
|
1
2
|
export const versionTypes = [
|
|
2
3
|
/**
|
|
3
4
|
* The roll is in a state where it is (possibly) used as
|
|
@@ -9,6 +10,8 @@ export const versionTypes = [
|
|
|
9
10
|
*/
|
|
10
11
|
'unicum'
|
|
11
12
|
];
|
|
13
|
+
/** The tolerance the derivation was collated at, or the default where it states none. */
|
|
14
|
+
export const collationToleranceOf = (derivation) => derivation.collationTolerance ?? defaultCollationTolerance;
|
|
12
15
|
/** The symbols the version's edits insert. */
|
|
13
16
|
export const insertedBy = (version) => version.edits.flatMap(edit => edit.insert ?? []);
|
|
14
17
|
/** The ids of the symbols the version's edits delete. */
|
package/lib/editionOps.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ import { Edition } from "./Edition";
|
|
|
4
4
|
import { AnySymbol, PlacementRelation } from "./Symbol";
|
|
5
5
|
import { CollationTolerance } from "./Collation";
|
|
6
6
|
import { Edit } from "./Edit";
|
|
7
|
-
import { RollCopy, ScaleReading, Shift } from "./RollCopy";
|
|
7
|
+
import { GeneralRollCondition, RollCopy, ScaleReading, Shift } from "./RollCopy";
|
|
8
8
|
import { FeatureSource } from "./FeatureSource";
|
|
9
|
-
import { AnyArgumentation, Certainty } from "./Assumption";
|
|
10
|
-
import { AnyFeature } from "./Feature";
|
|
9
|
+
import { AnyArgumentation, Certainty, ObjectAssumption } from "./Assumption";
|
|
10
|
+
import { AnyFeature, FeatureConditionAssignment } from "./Feature";
|
|
11
11
|
/**
|
|
12
12
|
* A change to an edition, written onto an immer draft of it. One
|
|
13
13
|
* operation is one undo step, so an operation that has to read the
|
|
@@ -36,13 +36,31 @@ export declare const unalignCopy: (copyId: string) => EditionOp;
|
|
|
36
36
|
export declare const stateSource: (copyId: string, source: FeatureSource) => EditionOp;
|
|
37
37
|
/** Takes back the statement, leaving the copy silent about its source again. */
|
|
38
38
|
export declare const clearSource: (copyId: string) => EditionOp;
|
|
39
|
+
/**
|
|
40
|
+
* Adds a general condition to the copy, beside whatever is stated of it
|
|
41
|
+
* already. The other condition a copy may be in, a paper stretch, is
|
|
42
|
+
* read off an alignment and stated by `alignCopy`.
|
|
43
|
+
*/
|
|
44
|
+
export declare const addGeneralCondition: (copyId: string, condition: ObjectAssumption<GeneralRollCondition>) => EditionOp;
|
|
45
|
+
/**
|
|
46
|
+
* States the condition of the feature, in place of any earlier
|
|
47
|
+
* statement. Throws where the kind of feature is in no such condition;
|
|
48
|
+
* `conditions` says which conditions each kind of feature may be in.
|
|
49
|
+
*/
|
|
50
|
+
export declare const stateFeatureCondition: (copyId: string, featureId: string, condition: FeatureConditionAssignment) => EditionOp;
|
|
39
51
|
/** The symbols of the versions that no other copy carries. */
|
|
40
52
|
export declare const symbolsCarriedOnlyBy: (edition: Edition, copyId: string) => AnySymbol[];
|
|
41
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Takes the features off the copy, and out of the versions with what
|
|
55
|
+
* only they carried. A feature may be named wherever the copy bears it:
|
|
56
|
+
* a patch goes with everything glued onto it, and a feature of a patch
|
|
57
|
+
* may be taken back on its own, the patch staying where it is.
|
|
58
|
+
*/
|
|
42
59
|
export declare const removeFeatures: (copyId: string, featureIds: readonly string[]) => EditionOp;
|
|
43
60
|
/**
|
|
44
61
|
* Takes the copy out of the edition together with the symbols only it
|
|
45
|
-
* carries, and with every reference the versions
|
|
62
|
+
* carries, and with every reference the versions and the argumentations
|
|
63
|
+
* made to those symbols.
|
|
46
64
|
*/
|
|
47
65
|
export declare const removeCopy: (copyId: string) => EditionOp;
|
|
48
66
|
/** Why several features cannot be replaced by one. */
|
|
@@ -61,7 +79,14 @@ export declare const mergeObstacle: (features: readonly AnyFeature[]) => MergeOb
|
|
|
61
79
|
* all, where a scan has split what the editor reads as one feature.
|
|
62
80
|
* The merged feature takes the place of the first it replaces and
|
|
63
81
|
* spans from the first to the last, any gap between them included.
|
|
64
|
-
* What the replaced features carried is carried by the merged one
|
|
82
|
+
* What the replaced features carried is carried by the merged one, and
|
|
83
|
+
* whatever else named them, a modification or a comprehension, names
|
|
84
|
+
* the merged one in their stead.
|
|
85
|
+
*
|
|
86
|
+
* Only the features the copy bears itself are merged. A feature of a
|
|
87
|
+
* patch states no place along the roll of its own, so there is nothing
|
|
88
|
+
* for a merge to span, and an id naming one is passed over as an id the
|
|
89
|
+
* copy does not bear is.
|
|
65
90
|
*
|
|
66
91
|
* Throws where the features cannot stand for one; `mergeObstacle`
|
|
67
92
|
* says beforehand whether they can.
|
|
@@ -71,12 +96,14 @@ export declare const mergeFeatures: (copyId: string, featureIds: readonly string
|
|
|
71
96
|
* Bases the child on the parent. A symbol of the child that collates
|
|
72
97
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
73
98
|
* rest become the child's insertions, and what the parent hands down
|
|
74
|
-
* and the child lacks becomes its deletions.
|
|
99
|
+
* and the child lacks becomes its deletions. The derivation states the
|
|
100
|
+
* tolerance it was collated at.
|
|
75
101
|
*/
|
|
76
102
|
export declare const connectVersions: (view: EditionView, childId: string, parentId: string, tolerance?: CollationTolerance) => EditionOp;
|
|
77
103
|
/**
|
|
78
104
|
* Folds the version's own symbols into those it inherits and collates
|
|
79
|
-
* with: the carriers pass over, and the insertions go.
|
|
105
|
+
* with: the carriers pass over, and the insertions go. Collates at the
|
|
106
|
+
* tolerance of the derivation, where the caller names none.
|
|
80
107
|
*/
|
|
81
108
|
export declare const collateSymbols: (view: EditionView, versionId: string, symbolIds: readonly string[], tolerance?: CollationTolerance) => EditionOp;
|
|
82
109
|
/**
|
package/lib/editionOps.js
CHANGED
|
@@ -3,10 +3,11 @@ import { v4 } from "uuid";
|
|
|
3
3
|
import { getAt } from "./EditionView";
|
|
4
4
|
import { isPerforation, placementRelations } from "./Symbol";
|
|
5
5
|
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
6
|
-
import { insertedBy } from "./Version";
|
|
6
|
+
import { collationToleranceOf, insertedBy } from "./Version";
|
|
7
7
|
import { asSymbols } from "./RollCopy";
|
|
8
8
|
import { applyShift, applyScale, revertShift, revertScale } from "./alignment";
|
|
9
9
|
import { assignReference, idOf } from "./Assumption";
|
|
10
|
+
import { conditions as conditionsAllowed, featuresBorneBy, isGluedOn, withBorneFeatures } from "./Feature";
|
|
10
11
|
import { distance, mm, subtract } from "./Quantity";
|
|
11
12
|
const noChange = () => undefined;
|
|
12
13
|
const onCopy = (copyId, op) => draft => {
|
|
@@ -19,6 +20,11 @@ const onVersion = (versionId, op) => draft => {
|
|
|
19
20
|
if (version)
|
|
20
21
|
op(version, draft);
|
|
21
22
|
};
|
|
23
|
+
const onFeature = (copyId, featureId, op) => onCopy(copyId, copy => {
|
|
24
|
+
const feature = copy.features.find(f => f.id === featureId);
|
|
25
|
+
if (feature)
|
|
26
|
+
op(feature);
|
|
27
|
+
});
|
|
22
28
|
/**
|
|
23
29
|
* The state a draft stands at, as plain data. Reading a draft proxies
|
|
24
30
|
* everything it touches, so what is only read is read from this.
|
|
@@ -31,15 +37,24 @@ const mapped = (items, change) => {
|
|
|
31
37
|
const changed = items.map(change);
|
|
32
38
|
return changed.every((item, i) => item === items[i]) ? items : changed;
|
|
33
39
|
};
|
|
40
|
+
/** The items each changed, less those the change emptied; the very same array where it changed none. */
|
|
41
|
+
const pruned = (items, change, emptied) => {
|
|
42
|
+
const changed = mapped(items, change);
|
|
43
|
+
return changed === items ? items : changed.filter((item, i) => item === items[i] || !emptied(item));
|
|
44
|
+
};
|
|
45
|
+
/** The record with the field replaced, or the very same record where that is what stood there. */
|
|
46
|
+
const replacing = (record, key, value) => record[key] === value ? record : { ...record, [key]: value };
|
|
47
|
+
/** The ids of the items the rewriting left out. */
|
|
48
|
+
const droppedIds = (before, after) => {
|
|
49
|
+
const kept = new Set(after.map(item => item.id));
|
|
50
|
+
return before.flatMap(item => kept.has(item.id) ? [] : [item.id]);
|
|
51
|
+
};
|
|
34
52
|
const insertion = (symbol) => ({ type: 'edit', id: v4(), insert: [symbol] });
|
|
35
53
|
const deletion = (symbolId) => ({ type: 'edit', id: v4(), delete: [symbolId] });
|
|
36
54
|
const isEmpty = (edit) => !edit.insert?.length && !edit.delete?.length;
|
|
37
55
|
const insertedIn = (versions) => versions.flatMap(insertedBy);
|
|
38
56
|
/** The edits with the change applied, less those it emptied; the very same array where it changed none. */
|
|
39
|
-
const edited = (edits, change) =>
|
|
40
|
-
const changed = mapped(edits, change);
|
|
41
|
-
return changed === edits ? edits : changed.filter((edit, i) => edit === edits[i] || !isEmpty(edit));
|
|
42
|
-
};
|
|
57
|
+
const edited = (edits, change) => pruned(edits, change, isEmpty);
|
|
43
58
|
/** The edit without the symbols among its insertions, or the very same edit where it inserts none of them. */
|
|
44
59
|
const droppingInsertions = (symbolIds) => (edit) => {
|
|
45
60
|
const insert = edit.insert && without(edit.insert, symbol => symbolIds.has(symbol.id));
|
|
@@ -104,7 +119,36 @@ export const stateSource = (copyId, source) => onCopy(copyId, copy => {
|
|
|
104
119
|
export const clearSource = (copyId) => onCopy(copyId, copy => {
|
|
105
120
|
copy.readFrom = undefined;
|
|
106
121
|
});
|
|
107
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Adds a general condition to the copy, beside whatever is stated of it
|
|
124
|
+
* already. The other condition a copy may be in, a paper stretch, is
|
|
125
|
+
* read off an alignment and stated by `alignCopy`.
|
|
126
|
+
*/
|
|
127
|
+
export const addGeneralCondition = (copyId, condition) => onCopy(copyId, copy => {
|
|
128
|
+
copy.conditions.push(condition);
|
|
129
|
+
});
|
|
130
|
+
/**
|
|
131
|
+
* Whether the feature's own kind allows a condition of this kind. The
|
|
132
|
+
* type narrowed to is the weaker statement, holding of a condition any
|
|
133
|
+
* kind of feature may be in.
|
|
134
|
+
*/
|
|
135
|
+
const allows = (feature, condition) => {
|
|
136
|
+
const allowed = conditionsAllowed[feature.type];
|
|
137
|
+
return allowed.includes(condition.conditionType);
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* States the condition of the feature, in place of any earlier
|
|
141
|
+
* statement. Throws where the kind of feature is in no such condition;
|
|
142
|
+
* `conditions` says which conditions each kind of feature may be in.
|
|
143
|
+
*/
|
|
144
|
+
export const stateFeatureCondition = (copyId, featureId, condition) => onFeature(copyId, featureId, feature => {
|
|
145
|
+
if (!allows(feature, condition)) {
|
|
146
|
+
throw new Error(`A ${feature.type} is in no '${condition.conditionType}' condition`);
|
|
147
|
+
}
|
|
148
|
+
feature.condition = condition;
|
|
149
|
+
});
|
|
150
|
+
/** The ids of every feature the copy bears, those a patch bears among them. */
|
|
151
|
+
const featureIdsOf = (copy) => new Set(copy.features.flatMap(withBorneFeatures).map(feature => feature.id));
|
|
108
152
|
/**
|
|
109
153
|
* A symbol every carrier of which lies among the features loses its
|
|
110
154
|
* evidence with them. A symbol without carriers, such as a label,
|
|
@@ -146,34 +190,136 @@ const forgettingFeatures = (features, dropped) => {
|
|
|
146
190
|
return { ...edit, ...(insert && { insert }), ...(deleted && { delete: deleted }) };
|
|
147
191
|
};
|
|
148
192
|
};
|
|
193
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
194
|
+
/** The record's values each changed, or the very same record where the change left every one as it was. */
|
|
195
|
+
const withValues = (record, change) => {
|
|
196
|
+
const entries = Object.entries(record);
|
|
197
|
+
const changed = entries.map(([key, value]) => [key, change(value)]);
|
|
198
|
+
return changed.every(([, value], i) => value === entries[i][1]) ? record : Object.fromEntries(changed);
|
|
199
|
+
};
|
|
200
|
+
/** The value with the change applied to every record within it and then to itself, innermost first. */
|
|
201
|
+
const deeply = (change) => {
|
|
202
|
+
const changed = (value) => Array.isArray(value) ? mapped(value, changed)
|
|
203
|
+
: isRecord(value) ? change(withValues(value, changed))
|
|
204
|
+
: value;
|
|
205
|
+
return changed;
|
|
206
|
+
};
|
|
207
|
+
/** Whether the two stand alike, so that what differs within them can be written where it lies. */
|
|
208
|
+
const alike = (before, after) => {
|
|
209
|
+
if (!isRecord(before) || !isRecord(after))
|
|
210
|
+
return false;
|
|
211
|
+
if (Array.isArray(before))
|
|
212
|
+
return Array.isArray(after) && before.length === after.length;
|
|
213
|
+
return !Array.isArray(after);
|
|
214
|
+
};
|
|
215
|
+
/** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
|
|
216
|
+
const writeInto = (draft, before, after) => Object.entries(after).forEach(([key, value]) => {
|
|
217
|
+
if (value === before[key])
|
|
218
|
+
return;
|
|
219
|
+
if (alike(before[key], value))
|
|
220
|
+
writeInto(draft[key], before[key], value);
|
|
221
|
+
else
|
|
222
|
+
draft[key] = value;
|
|
223
|
+
});
|
|
224
|
+
const isCopy = (record) => 'type' in record && record.type === 'RollCopy';
|
|
225
|
+
const isBelief = (record) => 'type' in record && record.type === 'belief';
|
|
226
|
+
const isComprehension = (reason) => reason.type === 'meaningComprehension';
|
|
227
|
+
/** What the modification names, be it as added or as removed. */
|
|
228
|
+
const membersOf = (modification) => modification.type === 'Addition' ? modification.added : modification.removed;
|
|
229
|
+
const namesNothing = (modification) => membersOf(modification).length === 0;
|
|
230
|
+
const comprehendsNothing = (reason) => isComprehension(reason) && reason.comprehends.length === 0;
|
|
231
|
+
/** The modification with what it names rewritten, or the very same one where that leaves it as it was. */
|
|
232
|
+
const renamingMembers = (rename) => (modification) => modification.type === 'Addition'
|
|
233
|
+
? replacing(modification, 'added', rename(modification.added))
|
|
234
|
+
: replacing(modification, 'removed', rename(modification.removed));
|
|
235
|
+
/** The reason with what a comprehension comprehends rewritten; any other reason names nothing of the kind. */
|
|
236
|
+
const renamingComprehended = (rename) => (reason) => isComprehension(reason) ? replacing(reason, 'comprehends', rename(reason.comprehends)) : reason;
|
|
237
|
+
/**
|
|
238
|
+
* The record with the ids it names rewritten: what a copy's
|
|
239
|
+
* modifications added or removed, and what the comprehensions among a
|
|
240
|
+
* belief's reasons comprehend. A modification or a comprehension the
|
|
241
|
+
* rewriting emptied goes with what it named; one that named nothing
|
|
242
|
+
* before stays, as an edit that was empty before it does.
|
|
243
|
+
*/
|
|
244
|
+
const renaming = (rename) => (record) => {
|
|
245
|
+
if (isCopy(record)) {
|
|
246
|
+
return replacing(record, 'modifications', pruned(record.modifications, renamingMembers(rename), namesNothing));
|
|
247
|
+
}
|
|
248
|
+
if (isBelief(record)) {
|
|
249
|
+
return replacing(record, 'reasons', pruned(record.reasons, renamingComprehended(rename), comprehendsNothing));
|
|
250
|
+
}
|
|
251
|
+
return record;
|
|
252
|
+
};
|
|
149
253
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
254
|
+
* Rewrites the ids by which the edition names features and symbols
|
|
255
|
+
* outside the versions. A belief is annotatable anywhere, so the whole
|
|
256
|
+
* edition is read; what the rewriting leaves alone is left the very
|
|
257
|
+
* object it was.
|
|
258
|
+
*/
|
|
259
|
+
const renameReferences = (draft, rename) => {
|
|
260
|
+
const before = stateOf(draft);
|
|
261
|
+
const after = deeply(renaming(rename))(before);
|
|
262
|
+
if (after !== before)
|
|
263
|
+
writeInto(draft, before, after);
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Strikes the features from the edition: their carriers go, a symbol
|
|
267
|
+
* that had no other carrier goes with them, an edit left exchanging
|
|
268
|
+
* nothing goes as well, and so does every reference the versions, the
|
|
269
|
+
* modifications of the copies and the comprehensions made to what went.
|
|
153
270
|
*/
|
|
154
271
|
const forgetFeatures = (draft, features) => {
|
|
155
272
|
const versions = stateOf(draft.versions);
|
|
156
273
|
const dropped = new Set(insertedIn(versions).filter(carriedOnlyOn(features)).map(symbol => symbol.id));
|
|
157
274
|
const forget = forgettingFeatures(features, dropped);
|
|
275
|
+
const edits = versions.map(version => edited(version.edits, forget));
|
|
276
|
+
const gone = new Set([
|
|
277
|
+
...features,
|
|
278
|
+
...dropped,
|
|
279
|
+
...versions.flatMap((version, i) => droppedIds(version.edits, edits[i]))
|
|
280
|
+
]);
|
|
281
|
+
renameReferences(draft, ids => without(ids, id => gone.has(id)));
|
|
158
282
|
draft.versions.forEach((version, i) => {
|
|
159
|
-
version.edits =
|
|
283
|
+
version.edits = edits[i];
|
|
160
284
|
});
|
|
161
285
|
};
|
|
162
|
-
/**
|
|
286
|
+
/**
|
|
287
|
+
* The ids of the features named and of everything they bear: a feature
|
|
288
|
+
* of a patch stands nowhere once the patch is gone.
|
|
289
|
+
*/
|
|
290
|
+
const goneWith = (features, named) => features.flatMap(feature => named.has(feature.id)
|
|
291
|
+
? withBorneFeatures(feature).map(borne => borne.id)
|
|
292
|
+
: goneWith(featuresBorneBy(feature), named));
|
|
293
|
+
/** The feature with the named ones gone from what it bears, or the very same one where it bears none of them. */
|
|
294
|
+
const withoutBorne = (feature, named) => {
|
|
295
|
+
if (!isGluedOn(feature) || !feature.features)
|
|
296
|
+
return feature;
|
|
297
|
+
const borne = withoutFeatures(feature.features, named);
|
|
298
|
+
return borne === feature.features ? feature : { ...feature, features: borne };
|
|
299
|
+
};
|
|
300
|
+
/** The features without those named, wherever they lie, and without whatever those bore. */
|
|
301
|
+
const withoutFeatures = (features, named) => mapped(without(features, feature => named.has(feature.id)), feature => withoutBorne(feature, named));
|
|
302
|
+
/**
|
|
303
|
+
* Takes the features off the copy, and out of the versions with what
|
|
304
|
+
* only they carried. A feature may be named wherever the copy bears it:
|
|
305
|
+
* a patch goes with everything glued onto it, and a feature of a patch
|
|
306
|
+
* may be taken back on its own, the patch staying where it is.
|
|
307
|
+
*/
|
|
163
308
|
export const removeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
309
|
+
const named = new Set(featureIds);
|
|
310
|
+
const features = stateOf(copy.features);
|
|
311
|
+
copy.features = withoutFeatures(features, named);
|
|
312
|
+
forgetFeatures(draft, new Set(goneWith(features, named)));
|
|
167
313
|
});
|
|
168
314
|
/**
|
|
169
315
|
* Takes the copy out of the edition together with the symbols only it
|
|
170
|
-
* carries, and with every reference the versions
|
|
316
|
+
* carries, and with every reference the versions and the argumentations
|
|
317
|
+
* made to those symbols.
|
|
171
318
|
*/
|
|
172
319
|
export const removeCopy = (copyId) => onCopy(copyId, (copy, draft) => {
|
|
173
320
|
forgetFeatures(draft, featureIdsOf(copy));
|
|
174
321
|
draft.copies = draft.copies.filter(c => c.id !== copyId);
|
|
175
322
|
});
|
|
176
|
-
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
177
323
|
/**
|
|
178
324
|
* Whether two records of the edition state the same, the identity of a
|
|
179
325
|
* statement left out: two transcriptions reading the same word say the
|
|
@@ -188,10 +334,17 @@ const sayTheSame = (a, b) => {
|
|
|
188
334
|
keys.delete('id');
|
|
189
335
|
return [...keys].every(key => sayTheSame(a[key], b[key]));
|
|
190
336
|
};
|
|
191
|
-
/**
|
|
337
|
+
/**
|
|
338
|
+
* What a feature states beyond its identity, its place along the roll,
|
|
339
|
+
* its depiction and its condition. What a patch bears counts by
|
|
340
|
+
* identity: two patches bear the same only where they bear the very
|
|
341
|
+
* same features, so that no merge takes a feature of a patch away.
|
|
342
|
+
*/
|
|
192
343
|
const nature = (feature) => {
|
|
193
344
|
const { id, horizontal, depiction, condition, ...rest } = feature;
|
|
194
|
-
return
|
|
345
|
+
return isGluedOn(feature) && feature.features
|
|
346
|
+
? { ...rest, features: feature.features.map(borne => borne.id) }
|
|
347
|
+
: rest;
|
|
195
348
|
};
|
|
196
349
|
const conditionsOf = (features) => features.flatMap(feature => feature.condition ? [feature.condition] : []);
|
|
197
350
|
/**
|
|
@@ -266,23 +419,31 @@ const carryOver = (draft, replaced, mergedId) => {
|
|
|
266
419
|
* all, where a scan has split what the editor reads as one feature.
|
|
267
420
|
* The merged feature takes the place of the first it replaces and
|
|
268
421
|
* spans from the first to the last, any gap between them included.
|
|
269
|
-
* What the replaced features carried is carried by the merged one
|
|
422
|
+
* What the replaced features carried is carried by the merged one, and
|
|
423
|
+
* whatever else named them, a modification or a comprehension, names
|
|
424
|
+
* the merged one in their stead.
|
|
425
|
+
*
|
|
426
|
+
* Only the features the copy bears itself are merged. A feature of a
|
|
427
|
+
* patch states no place along the roll of its own, so there is nothing
|
|
428
|
+
* for a merge to span, and an id naming one is passed over as an id the
|
|
429
|
+
* copy does not bear is.
|
|
270
430
|
*
|
|
271
431
|
* Throws where the features cannot stand for one; `mergeObstacle`
|
|
272
432
|
* says beforehand whether they can.
|
|
273
433
|
*/
|
|
274
434
|
export const mergeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
275
|
-
const
|
|
276
|
-
const isReplaced = (feature) => replaced.has(feature.id);
|
|
435
|
+
const named = new Set(featureIds);
|
|
277
436
|
const features = stateOf(copy.features);
|
|
278
|
-
const toMerge = features.filter(
|
|
437
|
+
const toMerge = features.filter(feature => named.has(feature.id));
|
|
279
438
|
const obstacle = mergeObstacle(toMerge);
|
|
280
439
|
if (obstacle) {
|
|
281
440
|
throw new Error(`The features of copy ${copyId} cannot be merged: ${obstacle}`);
|
|
282
441
|
}
|
|
442
|
+
const replaced = new Set(toMerge.map(feature => feature.id));
|
|
283
443
|
const merged = mergedFrom(toMerge);
|
|
284
|
-
copy.features = standingFor(features,
|
|
444
|
+
copy.features = standingFor(features, feature => replaced.has(feature.id), merged);
|
|
285
445
|
carryOver(draft, replaced, merged.id);
|
|
446
|
+
renameReferences(draft, ids => standingFor(ids, id => replaced.has(id), merged.id));
|
|
286
447
|
});
|
|
287
448
|
/** The carriers of each collated symbol pass to its counterpart. */
|
|
288
449
|
const handOverCarriers = (view, draft, collations) => collations.forEach(({ symbol, counterpart }) => {
|
|
@@ -294,7 +455,8 @@ const handOverCarriers = (view, draft, collations) => collations.forEach(({ symb
|
|
|
294
455
|
* Bases the child on the parent. A symbol of the child that collates
|
|
295
456
|
* with one the parent hands down adds its carriers to that symbol; the
|
|
296
457
|
* rest become the child's insertions, and what the parent hands down
|
|
297
|
-
* and the child lacks becomes its deletions.
|
|
458
|
+
* and the child lacks becomes its deletions. The derivation states the
|
|
459
|
+
* tolerance it was collated at.
|
|
298
460
|
*/
|
|
299
461
|
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
300
462
|
const inherited = view.snapshot(parentId);
|
|
@@ -309,20 +471,21 @@ export const connectVersions = (view, childId, parentId, tolerance = defaultColl
|
|
|
309
471
|
return onVersion(childId, (child, draft) => {
|
|
310
472
|
handOverCarriers(view, draft, collations);
|
|
311
473
|
child.edits = edits;
|
|
312
|
-
child.basedOn = assignReference(parentId);
|
|
474
|
+
child.basedOn = { ...assignReference(parentId), collationTolerance: tolerance };
|
|
313
475
|
});
|
|
314
476
|
};
|
|
315
477
|
/**
|
|
316
478
|
* Folds the version's own symbols into those it inherits and collates
|
|
317
|
-
* with: the carriers pass over, and the insertions go.
|
|
479
|
+
* with: the carriers pass over, and the insertions go. Collates at the
|
|
480
|
+
* tolerance of the derivation, where the caller names none.
|
|
318
481
|
*/
|
|
319
|
-
export const collateSymbols = (view, versionId, symbolIds, tolerance
|
|
482
|
+
export const collateSymbols = (view, versionId, symbolIds, tolerance) => {
|
|
320
483
|
const version = view.get(versionId);
|
|
321
484
|
if (!version?.basedOn)
|
|
322
485
|
return noChange;
|
|
323
486
|
const chosen = new Set(symbolIds);
|
|
324
487
|
const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
|
|
325
|
-
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance);
|
|
488
|
+
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance ?? collationToleranceOf(version.basedOn));
|
|
326
489
|
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
327
490
|
return onVersion(versionId, (version, draft) => {
|
|
328
491
|
handOverCarriers(view, draft, collations);
|
package/lib/migrate.js
CHANGED
|
@@ -107,4 +107,24 @@ const withRollSystem = (edition) => {
|
|
|
107
107
|
const withEditors = (edition) => !edition.creation || edition.creation.editors
|
|
108
108
|
? edition
|
|
109
109
|
: { ...edition, creation: { ...edition.creation, editors: [] } };
|
|
110
|
-
|
|
110
|
+
const statesNoTolerance = (version) => version.basedOn && !version.basedOn.collationTolerance;
|
|
111
|
+
/**
|
|
112
|
+
* The collation tolerance was the edition's before it was stated on
|
|
113
|
+
* each derivation. An edition written then collated every version at
|
|
114
|
+
* that one value, so it is written onto every derivation that gives
|
|
115
|
+
* none of its own.
|
|
116
|
+
*/
|
|
117
|
+
const withDerivationTolerance = (edition) => {
|
|
118
|
+
const collationTolerance = edition.creation?.collationTolerance;
|
|
119
|
+
const versions = Array.isArray(edition.versions) ? edition.versions : [];
|
|
120
|
+
if (!collationTolerance || !versions.some(statesNoTolerance))
|
|
121
|
+
return edition;
|
|
122
|
+
return {
|
|
123
|
+
...edition,
|
|
124
|
+
versions: versions.map(version => statesNoTolerance(version)
|
|
125
|
+
? { ...version, basedOn: { ...version.basedOn, collationTolerance } }
|
|
126
|
+
: version)
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const editionSteps = [withRollSystem, withEditors, withDerivationTolerance];
|
|
130
|
+
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|
|
@@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
|
|
2
2
|
import { AtonParser } from "./AtonParser";
|
|
3
3
|
import { systemOf, translationBetween } from "../TrackerBar";
|
|
4
4
|
import { welteT100 } from "../systems/welteT100/bar";
|
|
5
|
-
import { inMillimeters, mean, mm, px, subtract, track } from "../Quantity";
|
|
5
|
+
import { inMillimeters, mean, mm, pixelsPerInch, px, subtract, track } from "../Quantity";
|
|
6
6
|
/** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
|
|
7
7
|
const readPx = (value) => px(parseFloat(value));
|
|
8
8
|
/** The parser writes one record as an object and several as an array. */
|
|
@@ -155,6 +155,9 @@ export function readFromStanfordAton(atonString, { trackShift, bar = welteT100,
|
|
|
155
155
|
bass: readPx(json.ROLLINFO.HARD_MARGIN_BASS),
|
|
156
156
|
unit: 'px'
|
|
157
157
|
},
|
|
158
|
+
...(Number.isFinite(dpi) && {
|
|
159
|
+
scanResolution: { value: pixelsPerInch(dpi), unit: 'px/in' }
|
|
160
|
+
}),
|
|
158
161
|
trackCalibration: calibration,
|
|
159
162
|
...(measuredBy && { measuredBy })
|
|
160
163
|
},
|
package/lib/schema.json
CHANGED
|
@@ -277,6 +277,41 @@
|
|
|
277
277
|
],
|
|
278
278
|
"type": "object"
|
|
279
279
|
},
|
|
280
|
+
"Derivation": {
|
|
281
|
+
"description": "A derivation names the version another one was derived from, together with the tolerance the two were collated at. How precisely the copies put a symbol depends on what they are and on how their features were obtained, so the tolerance can differ from derivation to derivation. [ontology: lrmoo:R76 is derivative of]",
|
|
282
|
+
"properties": {
|
|
283
|
+
"@annotation": {
|
|
284
|
+
"description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
|
|
285
|
+
"properties": {
|
|
286
|
+
"belief": {
|
|
287
|
+
"$ref": "#/definitions/Belief",
|
|
288
|
+
"description": "The belief held about the annotated statement. [ontology: crminf:J4i is subject of]"
|
|
289
|
+
},
|
|
290
|
+
"@id": {
|
|
291
|
+
"description": "A unique identifier for this object.",
|
|
292
|
+
"type": "string"
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
"required": [
|
|
296
|
+
"belief",
|
|
297
|
+
"@id"
|
|
298
|
+
],
|
|
299
|
+
"type": "object"
|
|
300
|
+
},
|
|
301
|
+
"collationTolerance": {
|
|
302
|
+
"$ref": "#/definitions/CollationTolerance",
|
|
303
|
+
"description": "The tolerance at which the derived version was collated against the one it is based on. A derivation written before the tolerance was held here states none. Not exported to RDF."
|
|
304
|
+
},
|
|
305
|
+
"@id": {
|
|
306
|
+
"description": "A unique identifier for this object.",
|
|
307
|
+
"type": "string"
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"required": [
|
|
311
|
+
"@id"
|
|
312
|
+
],
|
|
313
|
+
"type": "object"
|
|
314
|
+
},
|
|
280
315
|
"Edit": {
|
|
281
316
|
"description": "A set of edits transforms a version of a roll into another version. Edits insert or delete symbols, or both (= replace). Edits may be motivated by a given set of reasons, e.g. to add an additional accent or to correct an error. If an edit is the interpretation of a metamark, such as a pencil mark, this should be made explicit using a meaning comprehension on the `@annotation` field. [ontology: reo:Edit]",
|
|
282
317
|
"properties": {
|
|
@@ -591,7 +626,7 @@
|
|
|
591
626
|
"features": {
|
|
592
627
|
"description": "A glued-on feature itself may carry other features. Nested features do not need to be positioned explicitly. [ontology: crm:P56 bears feature]",
|
|
593
628
|
"items": {
|
|
594
|
-
"$ref": "#/definitions/
|
|
629
|
+
"$ref": "#/definitions/NestedFeature"
|
|
595
630
|
},
|
|
596
631
|
"type": "array"
|
|
597
632
|
},
|
|
@@ -882,6 +917,25 @@
|
|
|
882
917
|
],
|
|
883
918
|
"type": "object"
|
|
884
919
|
},
|
|
920
|
+
"Measure<\"px/in\">": {
|
|
921
|
+
"description": "A value together with the unit it was measured in, as a record states it. [ontology: crm:E54 Dimension]",
|
|
922
|
+
"properties": {
|
|
923
|
+
"unit": {
|
|
924
|
+
"const": "px/in",
|
|
925
|
+
"description": "The unit of measurement. [ontology: crm:P91 has unit]",
|
|
926
|
+
"type": "string"
|
|
927
|
+
},
|
|
928
|
+
"value": {
|
|
929
|
+
"$ref": "#/definitions/Quantity%3C%22px%2Fin%22%3E",
|
|
930
|
+
"description": "The measured value. [ontology: crm:P90 has value]"
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
"required": [
|
|
934
|
+
"value",
|
|
935
|
+
"unit"
|
|
936
|
+
],
|
|
937
|
+
"type": "object"
|
|
938
|
+
},
|
|
885
939
|
"Millimeters": {
|
|
886
940
|
"$ref": "#/definitions/Quantity%3C%22mm%22%3E",
|
|
887
941
|
"description": "A place along the roll, or any length on the paper."
|
|
@@ -995,6 +1049,10 @@
|
|
|
995
1049
|
],
|
|
996
1050
|
"type": "object"
|
|
997
1051
|
},
|
|
1052
|
+
"NestedFeature": {
|
|
1053
|
+
"$ref": "#/definitions/PartialBy%3CAnyFeature%2C(%22horizontal%22%7C%22vertical%22)%3E",
|
|
1054
|
+
"description": "A feature borne by another feature. It states no place of its own, the feature bearing it standing in one."
|
|
1055
|
+
},
|
|
998
1056
|
"Note": {
|
|
999
1057
|
"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]",
|
|
1000
1058
|
"properties": {
|
|
@@ -1227,6 +1285,49 @@
|
|
|
1227
1285
|
],
|
|
1228
1286
|
"type": "object"
|
|
1229
1287
|
},
|
|
1288
|
+
"ObjectAssumption<ConditionState<(\"illegible\")>>": {
|
|
1289
|
+
"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.",
|
|
1290
|
+
"properties": {
|
|
1291
|
+
"@annotation": {
|
|
1292
|
+
"description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
|
|
1293
|
+
"properties": {
|
|
1294
|
+
"belief": {
|
|
1295
|
+
"$ref": "#/definitions/Belief",
|
|
1296
|
+
"description": "The belief held about the annotated statement. [ontology: crminf:J4i is subject of]"
|
|
1297
|
+
},
|
|
1298
|
+
"@id": {
|
|
1299
|
+
"description": "A unique identifier for this object.",
|
|
1300
|
+
"type": "string"
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
"required": [
|
|
1304
|
+
"belief",
|
|
1305
|
+
"@id"
|
|
1306
|
+
],
|
|
1307
|
+
"type": "object"
|
|
1308
|
+
},
|
|
1309
|
+
"conditionType": {
|
|
1310
|
+
"const": "illegible",
|
|
1311
|
+
"description": "The kind of condition, from the list the roll or the kind of feature allows. [ontology: crm:P2 has type]",
|
|
1312
|
+
"type": "string"
|
|
1313
|
+
},
|
|
1314
|
+
"description": {
|
|
1315
|
+
"description": "A free-text description of the condition, providing details beyond the type classification. [ontology: crm:P3 has note]",
|
|
1316
|
+
"type": "string"
|
|
1317
|
+
},
|
|
1318
|
+
"@type": {
|
|
1319
|
+
"const": "ConditionState",
|
|
1320
|
+
"description": "The type discriminator for this object. [ontology: rdf:type]",
|
|
1321
|
+
"type": "string",
|
|
1322
|
+
"ontology": "rdf:type"
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
"required": [
|
|
1326
|
+
"conditionType",
|
|
1327
|
+
"@type"
|
|
1328
|
+
],
|
|
1329
|
+
"type": "object"
|
|
1330
|
+
},
|
|
1230
1331
|
"ObjectAssumption<ConditionState<(\"partially-torn\"|\"missing-perforation\")>>": {
|
|
1231
1332
|
"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.",
|
|
1232
1333
|
"properties": {
|
|
@@ -1610,6 +1711,9 @@
|
|
|
1610
1711
|
"Quantity<\"px\">": {
|
|
1611
1712
|
"type": "number"
|
|
1612
1713
|
},
|
|
1714
|
+
"Quantity<\"px/in\">": {
|
|
1715
|
+
"type": "number"
|
|
1716
|
+
},
|
|
1613
1717
|
"Quantity<\"track\">": {
|
|
1614
1718
|
"type": "number"
|
|
1615
1719
|
},
|
|
@@ -1817,6 +1921,10 @@
|
|
|
1817
1921
|
"description": "The factor this copy's features were scaled by to align them with the others. What it is put down to is stated apart: a paper-stretch condition, or the speed the copy was cut for. Not exported to RDF.",
|
|
1818
1922
|
"type": "number"
|
|
1819
1923
|
},
|
|
1924
|
+
"scanResolution": {
|
|
1925
|
+
"$ref": "#/definitions/Measure%3C%22px%2Fin%22%3E",
|
|
1926
|
+
"description": "The resolution this copy's scan was read at, along the roll. It is what the measurements given in pixels are to be read against, and what every conversion between a place in the scan and one on the paper goes through. A scanner may read across the roll at another resolution, which the analysis files do not state. [ontology: reo:scanResolution]"
|
|
1927
|
+
},
|
|
1820
1928
|
"shift": {
|
|
1821
1929
|
"$ref": "#/definitions/Shift",
|
|
1822
1930
|
"description": "The shift applied to align this copy with the others. Not exported to RDF."
|
|
@@ -1978,7 +2086,7 @@
|
|
|
1978
2086
|
"description": "A version is defined by the sum of edits applied to the version it is based on. For simple identification, a siglum is given to each version. [ontology: lrmoo:F2 Expression]",
|
|
1979
2087
|
"properties": {
|
|
1980
2088
|
"basedOn": {
|
|
1981
|
-
"$ref": "#/definitions/
|
|
2089
|
+
"$ref": "#/definitions/Derivation",
|
|
1982
2090
|
"description": "If no derivation is defined, it is assumed that this version represents the mother roll. [ontology: lrmoo:R76 is derivative of]"
|
|
1983
2091
|
},
|
|
1984
2092
|
"edits": {
|
|
@@ -2059,7 +2167,7 @@
|
|
|
2059
2167
|
"description": "A piece of writing found on the roll, such as a label, catalogue number, or annotation. Writings have a method of production and a transcription of their content. [ontology: crm:E25 Human-Made Feature]",
|
|
2060
2168
|
"properties": {
|
|
2061
2169
|
"condition": {
|
|
2062
|
-
"$ref": "#/definitions/ObjectAssumption%3CConditionState%3C(%
|
|
2170
|
+
"$ref": "#/definitions/ObjectAssumption%3CConditionState%3C(%22illegible%22)%3E%3E",
|
|
2063
2171
|
"description": "This can be used e.g. to indicate a perforation which is torn out or in any other way damaged. [ontology: crm:P44 has condition]"
|
|
2064
2172
|
},
|
|
2065
2173
|
"depiction": {
|
package/lib/spec/context.json
CHANGED
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"height": "reo:height",
|
|
132
132
|
"punchDiameter": "reo:punchDiameter",
|
|
133
133
|
"holeSeparation": "reo:holeSeparation",
|
|
134
|
+
"scanResolution": "reo:scanResolution",
|
|
134
135
|
"value": "crm:P90_has_value",
|
|
135
136
|
"measuredBy": "crmdig:L23_used_software_or_firmware",
|
|
136
137
|
"software": "rdfs:label",
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { type ErrorObject } from "ajv";
|
|
1
2
|
import { Edition } from "./Edition";
|
|
2
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Holds a document against the edition schema. `errors` says what the
|
|
5
|
+
* last call found wrong, and is empty where it found nothing.
|
|
6
|
+
*/
|
|
7
|
+
export interface ValidateEdition {
|
|
8
|
+
(document: unknown): document is Edition;
|
|
9
|
+
errors?: ErrorObject[] | null;
|
|
10
|
+
}
|
|
11
|
+
declare const validate: ValidateEdition;
|
|
3
12
|
export { validate };
|
package/lib/validate.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import * as schema from "./schema.json";
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const validate =
|
|
3
|
+
const compileEditionSchema = () => new Ajv({ strict: false, formats: { "date": true } }).compile(schema);
|
|
4
|
+
/**
|
|
5
|
+
* Compiling costs enough that a consumer which never validates should not
|
|
6
|
+
* pay for it, so the compiled form is made on first use and kept.
|
|
7
|
+
*/
|
|
8
|
+
let compiled;
|
|
9
|
+
const validate = (document) => {
|
|
10
|
+
compiled ?? (compiled = compileEditionSchema());
|
|
11
|
+
const valid = compiled(document);
|
|
12
|
+
validate.errors = compiled.errors;
|
|
13
|
+
return valid;
|
|
14
|
+
};
|
|
10
15
|
export { validate };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Digital editions of piano rolls: import, collation, editorial assumptions, JSON-LD export, and emulation through a reproducing system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"url": "git+https://github.com/pfefferniels/linked-rolls.git"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
11
12
|
"main": "lib/index.js",
|
|
12
13
|
"types": "lib/index.d.ts",
|
|
13
14
|
"exports": {
|