linked-rolls 0.4.1 → 0.5.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/lib/Agent.d.ts +59 -0
- package/lib/Agent.js +1 -0
- package/lib/Assumption.d.ts +19 -1
- package/lib/Collation.d.ts +19 -0
- package/lib/Collation.js +25 -1
- package/lib/Edit.d.ts +1 -8
- package/lib/Edition.d.ts +4 -77
- package/lib/Edition.js +1 -1
- package/lib/EditionView.d.ts +0 -7
- package/lib/EditionView.js +0 -50
- package/lib/ReproducingSystem.d.ts +23 -1
- package/lib/RollCopy.d.ts +2 -64
- package/lib/RollCopy.js +2 -270
- package/lib/Symbol.d.ts +2 -1
- package/lib/Symbol.js +1 -1
- package/lib/TrackerBar.d.ts +16 -14
- package/lib/TrackerBar.js +1 -56
- package/lib/alignment.d.ts +21 -0
- package/lib/alignment.js +117 -0
- package/lib/constraints.d.ts +27 -0
- package/lib/constraints.js +61 -0
- package/lib/editionOps.d.ts +76 -0
- package/lib/editionOps.js +312 -0
- package/lib/index.d.ts +19 -14
- package/lib/index.js +19 -14
- package/lib/migrate.js +2 -1
- package/lib/readers/spencerMidi.d.ts +26 -0
- package/lib/readers/spencerMidi.js +58 -0
- package/lib/readers/stanfordAton.d.ts +18 -0
- package/lib/readers/stanfordAton.js +158 -0
- package/lib/schema.json +2 -2
- package/lib/systems/welteT100/bar.d.ts +14 -0
- package/lib/systems/welteT100/bar.js +56 -0
- package/lib/systems/{welteT100.d.ts → welteT100/system.d.ts} +1 -1
- package/lib/systems/{welteT100.js → welteT100/system.js} +1 -1
- package/lib/utils.d.ts +0 -8
- package/lib/validate.d.ts +0 -27
- package/lib/validate.js +0 -61
- package/package.json +3 -3
- package/lib/Plan.d.ts +0 -198
- package/lib/Plan.js +0 -611
- package/lib/alignFeatures.d.ts +0 -12
- package/lib/alignFeatures.js +0 -55
- /package/lib/{aton → readers}/AtonParser.d.ts +0 -0
- /package/lib/{aton → readers}/AtonParser.js +0 -0
- /package/lib/{asMIDISpans.d.ts → readers/midiSpans.d.ts} +0 -0
- /package/lib/{asMIDISpans.js → readers/midiSpans.js} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { AtonParser } from "./AtonParser";
|
|
3
|
+
import { welteT100 } from "../systems/welteT100/bar";
|
|
4
|
+
/** Values in these files carry their unit as a suffix, e.g. "37.7646px". */
|
|
5
|
+
const px = (value) => parseFloat(value);
|
|
6
|
+
/** The parser writes one record as an object and several as an array. */
|
|
7
|
+
const listOf = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
8
|
+
/**
|
|
9
|
+
* Holes the parser set aside as suspicious after it had already chained
|
|
10
|
+
* them into a note: the head of such a chain is usually a punch that
|
|
11
|
+
* came out a little short, and leaving it out would lose the whole
|
|
12
|
+
* chain. They carry no track number of their own, so the column says
|
|
13
|
+
* which track they sit on.
|
|
14
|
+
*/
|
|
15
|
+
const chainedBadHoles = (holes, calibration) => holes
|
|
16
|
+
.filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
|
|
17
|
+
.map(hole => ({
|
|
18
|
+
...hole,
|
|
19
|
+
TRACKER_HOLE: `${Math.round((px(hole.CENTROID_COL) - calibration.offset) / calibration.separation)}`
|
|
20
|
+
}));
|
|
21
|
+
const millimeters = (pixels, dpi) => pixels / dpi * 25.4;
|
|
22
|
+
const median = (values) => {
|
|
23
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
24
|
+
const middle = Math.floor(sorted.length / 2);
|
|
25
|
+
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
26
|
+
};
|
|
27
|
+
const mostFrequent = (values) => {
|
|
28
|
+
const counts = values.reduce((acc, value) => acc.set(value, (acc.get(value) || 0) + 1), new Map());
|
|
29
|
+
return [...counts].sort(([, a], [, b]) => b - a)[0]?.[0];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Only the first hole of a chain carries an attack and an off time;
|
|
33
|
+
* the rest continue it. The rewind perforation carries neither, and it
|
|
34
|
+
* is the last thing punched on the roll, so whatever the holes past the
|
|
35
|
+
* final musical attack sit on is the rewind track.
|
|
36
|
+
*/
|
|
37
|
+
const rewindTrackIn = (holes) => {
|
|
38
|
+
const lastMusical = holes.findLastIndex(hole => hole.NOTE_ATTACK);
|
|
39
|
+
const trailing = holes.slice(lastMusical + 1).map(hole => +hole.TRACKER_HOLE);
|
|
40
|
+
return mostFrequent(trailing);
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The phase of the tracker grid within the image. The analysis file
|
|
44
|
+
* usually states it; where it does not, the holes themselves give it away,
|
|
45
|
+
* since each sits close to the centre of its column.
|
|
46
|
+
*/
|
|
47
|
+
const gridOffsetOf = (holes, separation, stated) => {
|
|
48
|
+
if (stated !== undefined)
|
|
49
|
+
return px(stated);
|
|
50
|
+
return median(holes.map(hole => px(hole.CENTROID_COL) - +hole.TRACKER_HOLE * separation));
|
|
51
|
+
};
|
|
52
|
+
const punchDiameterOf = (holes, dpi) => {
|
|
53
|
+
const circular = holes
|
|
54
|
+
.filter(hole => px(hole.CIRCULARITY) > 0.95)
|
|
55
|
+
.map(hole => millimeters(px(hole.PERIMETER), dpi) / Math.PI);
|
|
56
|
+
if (!circular.length)
|
|
57
|
+
return undefined;
|
|
58
|
+
return circular.reduce((sum, diameter) => sum + diameter, 0) / circular.length;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The image service Stanford keeps for a scan, and a crop of a
|
|
62
|
+
* feature from it.
|
|
63
|
+
*/
|
|
64
|
+
const stanfordScan = (druid) => ({
|
|
65
|
+
scan: `https://stacks.stanford.edu/image/iiif/${druid}%2F${druid}_0001/`,
|
|
66
|
+
depictionOf: (column, row, width, height) => `https://stacks.stanford.edu/image/iiif/${druid}/${druid}_0001/${column},${row},${width},${height}/128,/270/default.jpg`
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* The software behind an analysis and when it was run, as the
|
|
70
|
+
* analysis file states them.
|
|
71
|
+
*/
|
|
72
|
+
const measuredByOf = (rollinfo) => {
|
|
73
|
+
const date = new Date(rollinfo.ANALYSIS_DATE);
|
|
74
|
+
if (!rollinfo.HOLE_SOFTWARE || isNaN(date.getTime()))
|
|
75
|
+
return undefined;
|
|
76
|
+
return {
|
|
77
|
+
software: rollinfo.HOLE_SOFTWARE,
|
|
78
|
+
version: rollinfo.SOFTWARE_DATE ?? '',
|
|
79
|
+
date
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export function readFromStanfordAton(atonString, { trackShift, bar = welteT100, scan } = {}) {
|
|
83
|
+
const parser = new AtonParser();
|
|
84
|
+
const json = parser.parse(atonString);
|
|
85
|
+
const holes = json.ROLLINFO.HOLES.HOLE;
|
|
86
|
+
const druid = json.ROLLINFO.DRUID;
|
|
87
|
+
const stanford = druid ? stanfordScan(druid) : undefined;
|
|
88
|
+
const separation = px(json.ROLLINFO.HOLE_SEPARATION);
|
|
89
|
+
const dpi = parseFloat(json.ROLLINFO.LENGTH_DPI);
|
|
90
|
+
const measuredBy = measuredByOf(json.ROLLINFO);
|
|
91
|
+
const rewindTrack = rewindTrackIn(holes);
|
|
92
|
+
const shift = trackShift
|
|
93
|
+
?? (rewindTrack === undefined ? 0 : bar.rewindTrack - rewindTrack);
|
|
94
|
+
const calibration = {
|
|
95
|
+
unit: 'px',
|
|
96
|
+
offset: gridOffsetOf(holes, separation, json.ROLLINFO.HOLE_OFFSET),
|
|
97
|
+
separation,
|
|
98
|
+
shift
|
|
99
|
+
};
|
|
100
|
+
const punchDiameter = punchDiameterOf(holes, dpi);
|
|
101
|
+
const chains = [...holes, ...chainedBadHoles(listOf(json.ROLLINFO.BADHOLES?.HOLE), calibration)]
|
|
102
|
+
.filter(hole => hole.NOTE_ATTACK && hole.OFF_TIME)
|
|
103
|
+
.sort((a, b) => px(a.NOTE_ATTACK) - px(b.NOTE_ATTACK));
|
|
104
|
+
const features = chains
|
|
105
|
+
.map((hole) => {
|
|
106
|
+
const attack = px(hole.NOTE_ATTACK);
|
|
107
|
+
const release = px(hole.OFF_TIME);
|
|
108
|
+
const column = px(hole.ORIGIN_COL);
|
|
109
|
+
const columnWidth = px(hole.WIDTH_COL);
|
|
110
|
+
return {
|
|
111
|
+
type: 'Hole',
|
|
112
|
+
id: v4(),
|
|
113
|
+
...(stanford && {
|
|
114
|
+
depiction: stanford.depictionOf(column, attack, columnWidth, release - attack)
|
|
115
|
+
}),
|
|
116
|
+
vertical: {
|
|
117
|
+
from: +hole.TRACKER_HOLE + shift,
|
|
118
|
+
unit: 'track'
|
|
119
|
+
},
|
|
120
|
+
horizontal: {
|
|
121
|
+
unit: 'mm',
|
|
122
|
+
from: millimeters(attack, dpi),
|
|
123
|
+
to: millimeters(release, dpi)
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
type: 'RollCopy',
|
|
129
|
+
id: v4(),
|
|
130
|
+
ops: [],
|
|
131
|
+
conditions: [],
|
|
132
|
+
keeper: { name: '', sameAs: [] },
|
|
133
|
+
modifications: [],
|
|
134
|
+
...((scan ?? stanford) && { scan: scan ?? stanford?.scan }),
|
|
135
|
+
measurements: {
|
|
136
|
+
dimensions: {
|
|
137
|
+
width: millimeters(px(json.ROLLINFO.ROLL_WIDTH), dpi),
|
|
138
|
+
height: millimeters(px(json.ROLLINFO.IMAGE_LENGTH), dpi),
|
|
139
|
+
unit: 'mm'
|
|
140
|
+
},
|
|
141
|
+
...(punchDiameter !== undefined && {
|
|
142
|
+
punchDiameter: { value: punchDiameter, unit: 'mm' }
|
|
143
|
+
}),
|
|
144
|
+
holeSeparation: {
|
|
145
|
+
value: separation,
|
|
146
|
+
unit: 'px'
|
|
147
|
+
},
|
|
148
|
+
margins: {
|
|
149
|
+
treble: px(json.ROLLINFO.HARD_MARGIN_TREBLE),
|
|
150
|
+
bass: px(json.ROLLINFO.HARD_MARGIN_BASS),
|
|
151
|
+
unit: 'px'
|
|
152
|
+
},
|
|
153
|
+
trackCalibration: calibration,
|
|
154
|
+
...(measuredBy && { measuredBy })
|
|
155
|
+
},
|
|
156
|
+
features
|
|
157
|
+
};
|
|
158
|
+
}
|
package/lib/schema.json
CHANGED
|
@@ -1189,7 +1189,7 @@
|
|
|
1189
1189
|
],
|
|
1190
1190
|
"type": "object"
|
|
1191
1191
|
},
|
|
1192
|
-
"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-5758-6122-src_Symbol.ts-0-6378400334313,\"carriers\">>": {
|
|
1193
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.",
|
|
1194
1194
|
"properties": {
|
|
1195
1195
|
"@annotation": {
|
|
@@ -1760,7 +1760,7 @@
|
|
|
1760
1760
|
"description": "The method by which this writing was produced (e.g. through print, handwriting, or stamping). [ontology: crm:P2 has type]"
|
|
1761
1761
|
},
|
|
1762
1762
|
"transcription": {
|
|
1763
|
-
"$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-5758-6122-src_Symbol.ts-0-6378400334313%2C%22carriers%22%3E%3E",
|
|
1764
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]"
|
|
1765
1765
|
},
|
|
1766
1766
|
"vertical": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TrackerBar } from "../../TrackerBar";
|
|
2
|
+
/**
|
|
3
|
+
* The commands of the Welte-Mignon T-100, as its tracker bar reads them.
|
|
4
|
+
*/
|
|
5
|
+
export declare const welteT100ExpressionTypes: readonly ["SustainPedalOn", "SustainPedalOff", "SoftPedalOn", "SoftPedalOff", "MezzoforteOff", "MezzoforteOn", "SlowCrescendoOn", "SlowCrescendoOff", "ForzandoOn", "ForzandoOff", "MotorOff", "MotorOn", "Rewind", "ElectricCutOff"];
|
|
6
|
+
export type WelteT100ExpressionType = typeof welteT100ExpressionTypes[number];
|
|
7
|
+
/**
|
|
8
|
+
* Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
|
|
9
|
+
*
|
|
10
|
+
* The note block spans 80 positions from C1 to g⁴, i.e. MIDI 24 to 103.
|
|
11
|
+
* The expression valves are duplicated, bass below the note block and
|
|
12
|
+
* treble above it, in mirrored order.
|
|
13
|
+
*/
|
|
14
|
+
export declare const welteT100: TrackerBar;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describeTrackerBar } from "../../TrackerBar";
|
|
2
|
+
/**
|
|
3
|
+
* The commands of the Welte-Mignon T-100, as its tracker bar reads them.
|
|
4
|
+
*/
|
|
5
|
+
export const welteT100ExpressionTypes = [
|
|
6
|
+
'SustainPedalOn',
|
|
7
|
+
'SustainPedalOff',
|
|
8
|
+
'SoftPedalOn',
|
|
9
|
+
'SoftPedalOff',
|
|
10
|
+
'MezzoforteOff',
|
|
11
|
+
'MezzoforteOn',
|
|
12
|
+
'SlowCrescendoOn',
|
|
13
|
+
'SlowCrescendoOff',
|
|
14
|
+
'ForzandoOn',
|
|
15
|
+
'ForzandoOff',
|
|
16
|
+
'MotorOff',
|
|
17
|
+
'MotorOn',
|
|
18
|
+
'Rewind',
|
|
19
|
+
'ElectricCutOff'
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Welte-Mignon T-100 ("red Welte"), cf. Hagmann, pp. 75 and 178.
|
|
23
|
+
*
|
|
24
|
+
* The note block spans 80 positions from C1 to g⁴, i.e. MIDI 24 to 103.
|
|
25
|
+
* The expression valves are duplicated, bass below the note block and
|
|
26
|
+
* treble above it, in mirrored order.
|
|
27
|
+
*/
|
|
28
|
+
export const welteT100 = describeTrackerBar({
|
|
29
|
+
id: 'welte-t100',
|
|
30
|
+
name: 'Welte-Mignon T100',
|
|
31
|
+
width: 328,
|
|
32
|
+
trackCount: 100,
|
|
33
|
+
notes: { from: 11, to: 90, lowestPitch: 24 },
|
|
34
|
+
expressions: new Map([
|
|
35
|
+
[1, 'MezzoforteOff'],
|
|
36
|
+
[2, 'MezzoforteOn'],
|
|
37
|
+
[3, 'SlowCrescendoOff'],
|
|
38
|
+
[4, 'SlowCrescendoOn'],
|
|
39
|
+
[5, 'ForzandoOff'],
|
|
40
|
+
[6, 'ForzandoOn'],
|
|
41
|
+
[7, 'SoftPedalOff'],
|
|
42
|
+
[8, 'SoftPedalOn'],
|
|
43
|
+
[9, 'MotorOff'],
|
|
44
|
+
[10, 'MotorOn'],
|
|
45
|
+
[91, 'Rewind'],
|
|
46
|
+
[92, 'ElectricCutOff'],
|
|
47
|
+
[93, 'SustainPedalOn'],
|
|
48
|
+
[94, 'SustainPedalOff'],
|
|
49
|
+
[95, 'ForzandoOn'],
|
|
50
|
+
[96, 'ForzandoOff'],
|
|
51
|
+
[97, 'SlowCrescendoOn'],
|
|
52
|
+
[98, 'SlowCrescendoOff'],
|
|
53
|
+
[99, 'MezzoforteOn'],
|
|
54
|
+
[100, 'MezzoforteOff']
|
|
55
|
+
])
|
|
56
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Half, Parameters, PedalMode, Spool } from "welte-t100-emulator";
|
|
2
|
-
import { ReproducingSystem } from "
|
|
2
|
+
import { ReproducingSystem } from "../../ReproducingSystem";
|
|
3
3
|
/**
|
|
4
4
|
* MIDI velocity at the open rail of the Nuancierbalg, at the Mezzoforte pin,
|
|
5
5
|
* and at the closed rail, joined linearly in between. Nothing in the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { aperturePorts, DEFAULT_PUNCH_MM, geometryInMm, Grid, levelChanges, mezzoforteTravel, paperSeconds, pedalDefaults, playbackParameters, pneumaticModel, ROWS_PER_MM, runPedals, TRACKER_BORE_MM, travelBetweenRails, WELTE_SPOOL, } from "welte-t100-emulator";
|
|
2
|
-
import { welteT100 } from "
|
|
2
|
+
import { welteT100 } from "./bar";
|
|
3
3
|
export const defaultWelteT100Options = {
|
|
4
4
|
spool: WELTE_SPOOL,
|
|
5
5
|
nuance: {
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Person } from "./Edition";
|
|
2
1
|
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
3
2
|
export type WithType<T extends string> = {
|
|
4
3
|
/**
|
|
@@ -13,13 +12,6 @@ export type WithId = {
|
|
|
13
12
|
*/
|
|
14
13
|
readonly id: string;
|
|
15
14
|
};
|
|
16
|
-
export type WithActor = {
|
|
17
|
-
/**
|
|
18
|
-
* The person who carried out this activity.
|
|
19
|
-
* @see crm:P14 carried out by
|
|
20
|
-
*/
|
|
21
|
-
actor?: Person;
|
|
22
|
-
};
|
|
23
15
|
export type WithNote = {
|
|
24
16
|
/**
|
|
25
17
|
* A free-text note providing additional context.
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,30 +1,3 @@
|
|
|
1
1
|
import { Edition } from "./Edition";
|
|
2
|
-
import { EditionView } from "./EditionView";
|
|
3
|
-
import { TrackerBar } from "./TrackerBar";
|
|
4
2
|
declare const validate: import("ajv").ValidateFunction<Edition>;
|
|
5
3
|
export { validate };
|
|
6
|
-
export type ConstraintProblem = {
|
|
7
|
-
version: string;
|
|
8
|
-
symbol: string;
|
|
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
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Where the placements and pairings of the edition cannot hold as
|
|
13
|
-
* stated, version by version: a reference or partner absent from the
|
|
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.
|
|
18
|
-
*/
|
|
19
|
-
export declare const constraintProblems: (view: EditionView) => ConstraintProblem[];
|
|
20
|
-
export type UnknownExpressionType = {
|
|
21
|
-
version: string;
|
|
22
|
-
symbol: string;
|
|
23
|
-
expressionType: string;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* The expressions of the edition whose type the tracker bar does not
|
|
27
|
-
* read, version by version. The roll names its system; a type from
|
|
28
|
-
* another system, or a misspelt one, means nothing on it.
|
|
29
|
-
*/
|
|
30
|
-
export declare const unknownExpressionTypes: (view: EditionView, bar: TrackerBar) => UnknownExpressionType[];
|
package/lib/validate.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import Ajv from "ajv";
|
|
2
2
|
import * as schema from "./schema.json";
|
|
3
|
-
import { idOf } from "./Assumption";
|
|
4
|
-
import { isPerforation, pairsAmong, placementsOf } from "./Symbol";
|
|
5
3
|
const ajv = new Ajv({
|
|
6
4
|
strict: false,
|
|
7
5
|
formats: {
|
|
@@ -10,62 +8,3 @@ const ajv = new Ajv({
|
|
|
10
8
|
});
|
|
11
9
|
const validate = ajv.compile(schema);
|
|
12
10
|
export { validate };
|
|
13
|
-
const missingReference = {
|
|
14
|
-
alignedWith: 'alignment-reference-missing',
|
|
15
|
-
before: 'before-reference-missing',
|
|
16
|
-
after: 'after-reference-missing'
|
|
17
|
-
};
|
|
18
|
-
const problemsIn = (version, perforations) => {
|
|
19
|
-
const ids = new Set(perforations.map(p => p.id));
|
|
20
|
-
const report = (symbol, problem) => ({ version, symbol, problem });
|
|
21
|
-
const missingReferences = perforations
|
|
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'));
|
|
31
|
-
const missingPartners = perforations
|
|
32
|
-
.filter(p => p.pairedWith && !ids.has(idOf(p.pairedWith)))
|
|
33
|
-
.map(p => report(p.id, 'partner-missing'));
|
|
34
|
-
const selfPaired = perforations
|
|
35
|
-
.filter(p => p.pairedWith && idOf(p.pairedWith) === p.id)
|
|
36
|
-
.map(p => report(p.id, 'paired-with-itself'));
|
|
37
|
-
const pairs = pairsAmong(perforations);
|
|
38
|
-
const pairsOf = (p) => pairs.filter(pair => pair.includes(p));
|
|
39
|
-
const inSeveralPairs = perforations
|
|
40
|
-
.filter(p => pairsOf(p).length > 1)
|
|
41
|
-
.map(p => report(p.id, 'in-several-pairs'));
|
|
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
|
-
];
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Where the placements and pairings of the edition cannot hold as
|
|
52
|
-
* stated, version by version: a reference or partner absent from the
|
|
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.
|
|
57
|
-
*/
|
|
58
|
-
export const constraintProblems = (view) => view.edition.versions.flatMap(version => problemsIn(version.id, view.snapshot(version.id).filter(isPerforation)));
|
|
59
|
-
const isExpression = (symbol) => symbol.type === 'expression';
|
|
60
|
-
/**
|
|
61
|
-
* The expressions of the edition whose type the tracker bar does not
|
|
62
|
-
* read, version by version. The roll names its system; a type from
|
|
63
|
-
* another system, or a misspelt one, means nothing on it.
|
|
64
|
-
*/
|
|
65
|
-
export const unknownExpressionTypes = (view, bar) => {
|
|
66
|
-
const known = new Set(bar.expressionTypes);
|
|
67
|
-
return view.edition.versions.flatMap(version => view.snapshot(version.id)
|
|
68
|
-
.filter(isExpression)
|
|
69
|
-
.filter(symbol => !known.has(symbol.expressionType))
|
|
70
|
-
.map(symbol => ({ version: version.id, symbol: symbol.id, expressionType: symbol.expressionType })));
|
|
71
|
-
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"default": "./lib/index.js"
|
|
17
17
|
},
|
|
18
18
|
"./welte-t100": {
|
|
19
|
-
"types": "./lib/systems/welteT100.d.ts",
|
|
20
|
-
"default": "./lib/systems/welteT100.js"
|
|
19
|
+
"types": "./lib/systems/welteT100/system.d.ts",
|
|
20
|
+
"default": "./lib/systems/welteT100/system.js"
|
|
21
21
|
},
|
|
22
22
|
"./lib/*": "./lib/*",
|
|
23
23
|
"./package.json": "./package.json"
|
package/lib/Plan.d.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { Draft } from "immer";
|
|
2
|
-
import { EditionView } from "./EditionView";
|
|
3
|
-
import { Edition } from "./Edition";
|
|
4
|
-
import { AnySymbol } from "./Symbol";
|
|
5
|
-
import { CollationTolerance } from "./Collation";
|
|
6
|
-
import { Edit } from "./Edit";
|
|
7
|
-
import { RollCopy } from "./RollCopy";
|
|
8
|
-
export type EditionOp = (d: Draft<Edition>) => void;
|
|
9
|
-
export interface Plan {
|
|
10
|
-
build(): EditionOp[];
|
|
11
|
-
setView: (view: EditionView) => void;
|
|
12
|
-
}
|
|
13
|
-
export declare const isPlan: (obj: any) => obj is Plan;
|
|
14
|
-
export declare abstract class BasePlan implements Plan {
|
|
15
|
-
protected view: EditionView | null;
|
|
16
|
-
setView: (view: EditionView) => void;
|
|
17
|
-
abstract build(): EditionOp[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Walks through the versions. If it finds a symbol that is still
|
|
21
|
-
* part of the tradition (i.e. it is included in the current snapshot)
|
|
22
|
-
* and is equivalent with the given symbol, it will add the feature
|
|
23
|
-
* carrying the symbol to the collated symbol. Otherwise, the
|
|
24
|
-
* given symbol will be added to the current version's insertions.
|
|
25
|
-
*
|
|
26
|
-
* All symbols of the tradition that are not included in the given
|
|
27
|
-
* symbols are considered to be deleted.
|
|
28
|
-
*
|
|
29
|
-
* @param creation
|
|
30
|
-
* @param symbols
|
|
31
|
-
* @returns
|
|
32
|
-
*/
|
|
33
|
-
export declare class ConnectVersions extends BasePlan {
|
|
34
|
-
private childId;
|
|
35
|
-
private parentId;
|
|
36
|
-
private tolerance;
|
|
37
|
-
constructor(childId: string, parentId: string, tolerance?: CollationTolerance);
|
|
38
|
-
build(): EditionOp[];
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Walks through the versions. If it finds a symbol that is still
|
|
42
|
-
* part of the tradition (i.e. it is included in the current snapshot)
|
|
43
|
-
* and is equivalent with the given symbol, it will add the feature
|
|
44
|
-
* carrying the symbol to the collated symbol. Otherwise, the
|
|
45
|
-
* given symbol will be added to the current version's insertions.
|
|
46
|
-
*
|
|
47
|
-
* All symbols of the tradition that are not included in the given
|
|
48
|
-
* symbols are considered to be deleted.
|
|
49
|
-
*
|
|
50
|
-
* @param creation
|
|
51
|
-
* @param symbols
|
|
52
|
-
* @returns
|
|
53
|
-
*/
|
|
54
|
-
export declare class CreateVersion extends BasePlan {
|
|
55
|
-
private siglum;
|
|
56
|
-
private copy;
|
|
57
|
-
constructor(siglum: string, copy: RollCopy);
|
|
58
|
-
build(): EditionOp[];
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Creates a new version based on the given version
|
|
62
|
-
* calculating the effect of a cover on existing symbols.
|
|
63
|
-
|
|
64
|
-
export class CoverPerforation extends BasePlan {
|
|
65
|
-
constructor(
|
|
66
|
-
private copyId: string,
|
|
67
|
-
private versionId: string,
|
|
68
|
-
private coverDimension: { horizontal: HorizontalSpan, vertical: VerticalSpan },
|
|
69
|
-
private material: string | undefined = undefined,
|
|
70
|
-
) {
|
|
71
|
-
super()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
build(): EditionOp[] {
|
|
75
|
-
const view = this.view
|
|
76
|
-
if (!view) return []
|
|
77
|
-
|
|
78
|
-
return [
|
|
79
|
-
(draft: Draft<Edition>): void => {
|
|
80
|
-
const version = draft.versions.find(v => v.id === this.versionId)
|
|
81
|
-
const copy = draft.copies.find(c => c.id === this.copyId)
|
|
82
|
-
if (!version || !copy) return
|
|
83
|
-
|
|
84
|
-
// find perforations in the snapshot that
|
|
85
|
-
// overlap with the cover
|
|
86
|
-
const overlappingSymbols =
|
|
87
|
-
view.snapshot(this.versionId)
|
|
88
|
-
.filter(symbol => {
|
|
89
|
-
const dimension = view.dimensionOf(symbol)
|
|
90
|
-
if (!dimension) return false
|
|
91
|
-
|
|
92
|
-
return (
|
|
93
|
-
(symbol.type === 'note' || symbol.type === 'expression') &&
|
|
94
|
-
overlaps(dimension, this.coverDimension)
|
|
95
|
-
)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
if (!overlappingSymbols.length) return undefined
|
|
99
|
-
|
|
100
|
-
const deepClone = JSON.parse(JSON.stringify(overlappingSymbols)) as AnySymbol[]
|
|
101
|
-
|
|
102
|
-
for (const symbol of deepClone) {
|
|
103
|
-
//symbol.id = v4();
|
|
104
|
-
const dimension = structuredClone(view.dimensionOf(symbol))
|
|
105
|
-
if (!dimension) continue
|
|
106
|
-
|
|
107
|
-
// check if the cover partially covers the beginning
|
|
108
|
-
if (dimension.horizontal.from >= this.coverDimension.horizontal.from &&
|
|
109
|
-
dimension.horizontal.from <= this.coverDimension.horizontal.to
|
|
110
|
-
) {
|
|
111
|
-
dimension.horizontal.from = this.coverDimension.horizontal.to
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// check if the cover partially covers the ending
|
|
115
|
-
if (dimension.horizontal.to >= this.coverDimension.horizontal.from &&
|
|
116
|
-
dimension.horizontal.to <= this.coverDimension.horizontal.to
|
|
117
|
-
) {
|
|
118
|
-
dimension.horizontal.to = this.coverDimension.horizontal.from
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const newFeature: RollFeature = {
|
|
122
|
-
...dimension,
|
|
123
|
-
id: v4(),
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
copy.features.push(newFeature)
|
|
127
|
-
symbol.id = `symbol-${v4().slice(0, 8)}`
|
|
128
|
-
symbol.carriers = [assignReference(newFeature.id)]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const coverId = `cover-${v4().slice(0, 8)}`
|
|
132
|
-
copy.features.push({
|
|
133
|
-
...this.coverDimension,
|
|
134
|
-
id: coverId,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const edit: Edit = {
|
|
138
|
-
type: 'edit',
|
|
139
|
-
id: v4(),
|
|
140
|
-
delete: overlappingSymbols.map(s => s.id),
|
|
141
|
-
insert: deepClone,
|
|
142
|
-
intentionOf: [{
|
|
143
|
-
type: 'cover',
|
|
144
|
-
id: v4(),
|
|
145
|
-
note: this.material,
|
|
146
|
-
carriers: [assignReference(coverId)],
|
|
147
|
-
}]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
draft.versions.push({
|
|
151
|
-
edits: [edit],
|
|
152
|
-
id: v4(),
|
|
153
|
-
basedOn: assignReference(version.id),
|
|
154
|
-
siglum: version.siglum + ' rev',
|
|
155
|
-
type: 'authorised-revision',
|
|
156
|
-
motivations: [
|
|
157
|
-
{
|
|
158
|
-
type: 'motivation',
|
|
159
|
-
note: 'Stanzfehler korrigiert',
|
|
160
|
-
id: v4(),
|
|
161
|
-
}
|
|
162
|
-
],
|
|
163
|
-
})
|
|
164
|
-
}]
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
*/
|
|
168
|
-
export declare class DetachVersion extends BasePlan {
|
|
169
|
-
private versionId;
|
|
170
|
-
constructor(versionId: string);
|
|
171
|
-
build(): EditionOp[];
|
|
172
|
-
}
|
|
173
|
-
export declare class RemoveFeature extends BasePlan {
|
|
174
|
-
private copyID;
|
|
175
|
-
private featureIDs;
|
|
176
|
-
constructor(copyID: string, featureIDs: string[]);
|
|
177
|
-
build(): EditionOp[];
|
|
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;
|
|
186
|
-
export declare class MergeEdits extends BasePlan {
|
|
187
|
-
private versionId;
|
|
188
|
-
private toMerge;
|
|
189
|
-
constructor(versionId: string, toMerge: Edit[]);
|
|
190
|
-
private guessMotivation;
|
|
191
|
-
build(): EditionOp[];
|
|
192
|
-
}
|
|
193
|
-
export declare class SplitEdit extends BasePlan {
|
|
194
|
-
private versionId;
|
|
195
|
-
private toSplit;
|
|
196
|
-
constructor(versionId: string, toSplit: Edit);
|
|
197
|
-
build(): EditionOp[];
|
|
198
|
-
}
|