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.
Files changed (47) hide show
  1. package/lib/Agent.d.ts +59 -0
  2. package/lib/Agent.js +1 -0
  3. package/lib/Assumption.d.ts +19 -1
  4. package/lib/Collation.d.ts +19 -0
  5. package/lib/Collation.js +25 -1
  6. package/lib/Edit.d.ts +1 -8
  7. package/lib/Edition.d.ts +4 -77
  8. package/lib/Edition.js +1 -1
  9. package/lib/EditionView.d.ts +0 -7
  10. package/lib/EditionView.js +0 -50
  11. package/lib/ReproducingSystem.d.ts +23 -1
  12. package/lib/RollCopy.d.ts +2 -64
  13. package/lib/RollCopy.js +2 -270
  14. package/lib/Symbol.d.ts +2 -1
  15. package/lib/Symbol.js +1 -1
  16. package/lib/TrackerBar.d.ts +16 -14
  17. package/lib/TrackerBar.js +1 -56
  18. package/lib/alignment.d.ts +21 -0
  19. package/lib/alignment.js +117 -0
  20. package/lib/constraints.d.ts +27 -0
  21. package/lib/constraints.js +61 -0
  22. package/lib/editionOps.d.ts +76 -0
  23. package/lib/editionOps.js +312 -0
  24. package/lib/index.d.ts +19 -14
  25. package/lib/index.js +19 -14
  26. package/lib/migrate.js +2 -1
  27. package/lib/readers/spencerMidi.d.ts +26 -0
  28. package/lib/readers/spencerMidi.js +58 -0
  29. package/lib/readers/stanfordAton.d.ts +18 -0
  30. package/lib/readers/stanfordAton.js +158 -0
  31. package/lib/schema.json +2 -2
  32. package/lib/systems/welteT100/bar.d.ts +14 -0
  33. package/lib/systems/welteT100/bar.js +56 -0
  34. package/lib/systems/{welteT100.d.ts → welteT100/system.d.ts} +1 -1
  35. package/lib/systems/{welteT100.js → welteT100/system.js} +1 -1
  36. package/lib/utils.d.ts +0 -8
  37. package/lib/validate.d.ts +0 -27
  38. package/lib/validate.js +0 -61
  39. package/package.json +3 -3
  40. package/lib/Plan.d.ts +0 -198
  41. package/lib/Plan.js +0 -611
  42. package/lib/alignFeatures.d.ts +0 -12
  43. package/lib/alignFeatures.js +0 -55
  44. /package/lib/{aton → readers}/AtonParser.d.ts +0 -0
  45. /package/lib/{aton → readers}/AtonParser.js +0 -0
  46. /package/lib/{asMIDISpans.d.ts → readers/midiSpans.d.ts} +0 -0
  47. /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-5670-6034-src_Symbol.ts-0-6242400334313,\"carriers\">>": {
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-5670-6034-src_Symbol.ts-0-6242400334313%2C%22carriers%22%3E%3E",
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 "../ReproducingSystem";
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 "../TrackerBar";
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.4.1",
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
- }