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,76 @@
|
|
|
1
|
+
import { Draft } from "immer";
|
|
2
|
+
import { EditionView, Path } from "./EditionView";
|
|
3
|
+
import { Edition } from "./Edition";
|
|
4
|
+
import { AnySymbol, PlacementRelation } from "./Symbol";
|
|
5
|
+
import { CollationTolerance } from "./Collation";
|
|
6
|
+
import { Edit } from "./Edit";
|
|
7
|
+
import { PaperStretch, RollCopy, Shift } from "./RollCopy";
|
|
8
|
+
import { AnyArgumentation, Certainty, ObjectAssumption } from "./Assumption";
|
|
9
|
+
/**
|
|
10
|
+
* A change to an edition, written onto an immer draft of it. One
|
|
11
|
+
* operation is one undo step, so an operation that has to read the
|
|
12
|
+
* edition first takes an `EditionView` of the state it will be
|
|
13
|
+
* applied to and does all its writing in the one function it returns.
|
|
14
|
+
*/
|
|
15
|
+
export type EditionOp = (draft: Draft<Edition>) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Puts the copy into the edition with a version of its own, which
|
|
18
|
+
* inserts every symbol the tracker bar reads on the copy.
|
|
19
|
+
*/
|
|
20
|
+
export declare const createVersion: (siglum: string, copy: RollCopy) => EditionOp;
|
|
21
|
+
/** Shifts and then stretches the copy's features into line with another copy's. */
|
|
22
|
+
export declare const alignCopy: (copyId: string, shift: Shift, stretch: ObjectAssumption<PaperStretch>) => EditionOp;
|
|
23
|
+
/** Puts the copy's features back where they were measured. */
|
|
24
|
+
export declare const unalignCopy: (copyId: string) => EditionOp;
|
|
25
|
+
/** The symbols of the versions that no other copy carries. */
|
|
26
|
+
export declare const symbolsCarriedOnlyBy: (edition: Edition, copyId: string) => AnySymbol[];
|
|
27
|
+
/** Takes the features off the copy, and out of the versions with what only they carried. */
|
|
28
|
+
export declare const removeFeatures: (copyId: string, featureIds: readonly string[]) => EditionOp;
|
|
29
|
+
/**
|
|
30
|
+
* Takes the copy out of the edition together with the symbols only it
|
|
31
|
+
* carries, and with every reference the versions made to those symbols.
|
|
32
|
+
*/
|
|
33
|
+
export declare const removeCopy: (copyId: string) => EditionOp;
|
|
34
|
+
/**
|
|
35
|
+
* Bases the child on the parent. A symbol of the child that collates
|
|
36
|
+
* with one the parent hands down adds its carriers to that symbol; the
|
|
37
|
+
* rest become the child's insertions, and what the parent hands down
|
|
38
|
+
* and the child lacks becomes its deletions.
|
|
39
|
+
*/
|
|
40
|
+
export declare const connectVersions: (view: EditionView, childId: string, parentId: string, tolerance?: CollationTolerance) => EditionOp;
|
|
41
|
+
/**
|
|
42
|
+
* Folds the version's own symbols into those it inherits and collates
|
|
43
|
+
* with: the carriers pass over, and the insertions go.
|
|
44
|
+
*/
|
|
45
|
+
export declare const collateSymbols: (view: EditionView, versionId: string, symbolIds: readonly string[], tolerance?: CollationTolerance) => EditionOp;
|
|
46
|
+
/**
|
|
47
|
+
* Makes the version stand on its own: what it inherited becomes its
|
|
48
|
+
* own insertions, and the link to the version it was based on goes,
|
|
49
|
+
* with the motivations that belonged to that derivation.
|
|
50
|
+
*/
|
|
51
|
+
export declare const detachVersion: (view: EditionView, versionId: string) => EditionOp;
|
|
52
|
+
/** Takes the version out; whatever was based on it comes to stand on its own. */
|
|
53
|
+
export declare const removeVersion: (view: EditionView, versionId: string) => EditionOp;
|
|
54
|
+
/** Takes the symbols out of the version's own insertions, and the edits that had nothing else. */
|
|
55
|
+
export declare const removeSymbols: (versionId: string, symbolIds: readonly string[]) => EditionOp;
|
|
56
|
+
/** Moves the edits into a new version based on this one. */
|
|
57
|
+
export declare const deriveVersion: (versionId: string, editIds: readonly string[]) => EditionOp;
|
|
58
|
+
/**
|
|
59
|
+
* Replaces the edits with a single one carrying all their insertions
|
|
60
|
+
* and deletions, classified by a guess at what the exchange does.
|
|
61
|
+
*/
|
|
62
|
+
export declare const mergeEdits: (view: EditionView, versionId: string, toMerge: readonly Edit[]) => EditionOp;
|
|
63
|
+
/** Replaces the edit with one edit per inserted and one per deleted symbol. */
|
|
64
|
+
export declare const splitEdit: (versionId: string, toSplit: Edit) => EditionOp;
|
|
65
|
+
/** States how the follower is placed relative to the reference, in place of any earlier statement. */
|
|
66
|
+
export declare const placePerforation: (view: EditionView, followerId: string, referenceId: string, relation: PlacementRelation) => EditionOp;
|
|
67
|
+
export declare const unplacePerforation: (view: EditionView, followerId: string) => EditionOp;
|
|
68
|
+
/** The pair is stated on `statingId` only, as the format asks. */
|
|
69
|
+
export declare const pairPerforations: (view: EditionView, statingId: string, partnerId: string) => EditionOp;
|
|
70
|
+
export declare const unpairPerforation: (view: EditionView, statingId: string) => EditionOp;
|
|
71
|
+
/** Annotates the assumption at the path with a belief held true, for reasons to be added. */
|
|
72
|
+
export declare const createBelief: (path: Path) => EditionOp;
|
|
73
|
+
export declare const clearBelief: (path: Path) => EditionOp;
|
|
74
|
+
export declare const setCertainty: (path: Path, certainty: Certainty) => EditionOp;
|
|
75
|
+
export declare const addReason: (path: Path, reason: AnyArgumentation) => EditionOp;
|
|
76
|
+
export declare const removeReason: (path: Path, index: number) => EditionOp;
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { getAt } from "./EditionView";
|
|
3
|
+
import { isPerforation, placementRelations } from "./Symbol";
|
|
4
|
+
import { collationsOf, defaultCollationTolerance } from "./Collation";
|
|
5
|
+
import { asSymbols } from "./RollCopy";
|
|
6
|
+
import { applyShift, applyStretch, revertShift, revertStretch } from "./alignment";
|
|
7
|
+
import { assignReference, idOf } from "./Assumption";
|
|
8
|
+
const noChange = () => undefined;
|
|
9
|
+
const onCopy = (copyId, op) => draft => {
|
|
10
|
+
const copy = draft.copies.find(c => c.id === copyId);
|
|
11
|
+
if (copy)
|
|
12
|
+
op(copy, draft);
|
|
13
|
+
};
|
|
14
|
+
const onVersion = (versionId, op) => draft => {
|
|
15
|
+
const version = draft.versions.find(v => v.id === versionId);
|
|
16
|
+
if (version)
|
|
17
|
+
op(version, draft);
|
|
18
|
+
};
|
|
19
|
+
/** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
|
|
20
|
+
const without = (items, matches) => items.some(matches) ? items.filter(item => !matches(item)) : items;
|
|
21
|
+
const insertion = (symbol) => ({ type: 'edit', id: v4(), insert: [symbol] });
|
|
22
|
+
const deletion = (symbolId) => ({ type: 'edit', id: v4(), delete: [symbolId] });
|
|
23
|
+
const isEmpty = (edit) => !edit.insert?.length && !edit.delete?.length;
|
|
24
|
+
const insertedIn = (versions) => versions.flatMap(version => version.edits).flatMap(edit => edit.insert ?? []);
|
|
25
|
+
/** Runs the change over the version's edits and drops those it emptied, leaving edits that were empty before alone. */
|
|
26
|
+
const editing = (version, change) => {
|
|
27
|
+
const emptyBefore = new Set(version.edits.filter(isEmpty).map(edit => edit.id));
|
|
28
|
+
version.edits.forEach(change);
|
|
29
|
+
version.edits = without(version.edits, edit => isEmpty(edit) && !emptyBefore.has(edit.id));
|
|
30
|
+
};
|
|
31
|
+
/** Takes the symbols out of the version's own insertions. */
|
|
32
|
+
const dropInsertions = (version, symbolIds) => editing(version, edit => {
|
|
33
|
+
if (edit.insert)
|
|
34
|
+
edit.insert = without(edit.insert, symbol => symbolIds.has(symbol.id));
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Puts the copy into the edition with a version of its own, which
|
|
38
|
+
* inserts every symbol the tracker bar reads on the copy.
|
|
39
|
+
*/
|
|
40
|
+
export const createVersion = (siglum, copy) => draft => {
|
|
41
|
+
draft.copies.push(copy);
|
|
42
|
+
draft.versions.push({
|
|
43
|
+
type: 'Version',
|
|
44
|
+
id: v4(),
|
|
45
|
+
siglum,
|
|
46
|
+
versionType: 'edition',
|
|
47
|
+
edits: asSymbols(copy.features).map(insertion),
|
|
48
|
+
motivations: []
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
/** Shifts and then stretches the copy's features into line with another copy's. */
|
|
52
|
+
export const alignCopy = (copyId, shift, stretch) => onCopy(copyId, copy => {
|
|
53
|
+
applyShift(shift, copy);
|
|
54
|
+
applyStretch(stretch, copy);
|
|
55
|
+
});
|
|
56
|
+
/** Puts the copy's features back where they were measured. */
|
|
57
|
+
export const unalignCopy = (copyId) => onCopy(copyId, copy => {
|
|
58
|
+
revertStretch(copy);
|
|
59
|
+
revertShift(copy);
|
|
60
|
+
});
|
|
61
|
+
const featureIdsOf = (copy) => new Set(copy.features.map(feature => feature.id));
|
|
62
|
+
/**
|
|
63
|
+
* A symbol every carrier of which lies among the features loses its
|
|
64
|
+
* evidence with them. A symbol without carriers, such as a label,
|
|
65
|
+
* stands on its own.
|
|
66
|
+
*/
|
|
67
|
+
const carriedOnlyOn = (features) => (symbol) => symbol.carriers.length > 0 && symbol.carriers.every(carrier => features.has(idOf(carrier)));
|
|
68
|
+
/** The symbols of the versions that no other copy carries. */
|
|
69
|
+
export const symbolsCarriedOnlyBy = (edition, copyId) => {
|
|
70
|
+
const copy = edition.copies.find(c => c.id === copyId);
|
|
71
|
+
return copy ? insertedIn(edition.versions).filter(carriedOnlyOn(featureIdsOf(copy))) : [];
|
|
72
|
+
};
|
|
73
|
+
const references = [...placementRelations, 'pairedWith'];
|
|
74
|
+
const forgetPerforations = (perforation, dropped) => references
|
|
75
|
+
.filter(relation => {
|
|
76
|
+
const reference = perforation[relation];
|
|
77
|
+
return reference && dropped.has(idOf(reference));
|
|
78
|
+
})
|
|
79
|
+
.forEach(relation => { delete perforation[relation]; });
|
|
80
|
+
const forgetCarriers = (symbol, features, dropped) => {
|
|
81
|
+
symbol.carriers = without(symbol.carriers, carrier => features.has(idOf(carrier)));
|
|
82
|
+
if (isPerforation(symbol))
|
|
83
|
+
forgetPerforations(symbol, dropped);
|
|
84
|
+
};
|
|
85
|
+
const forgetFeaturesInEdit = (edit, features, dropped) => {
|
|
86
|
+
if (edit.insert) {
|
|
87
|
+
edit.insert = without(edit.insert, symbol => dropped.has(symbol.id));
|
|
88
|
+
edit.insert.forEach(symbol => forgetCarriers(symbol, features, dropped));
|
|
89
|
+
}
|
|
90
|
+
if (edit.delete) {
|
|
91
|
+
edit.delete = without(edit.delete, id => dropped.has(id));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Strikes the features from the versions: their carriers go, a symbol
|
|
96
|
+
* that had no other carrier goes with them, and so does every
|
|
97
|
+
* reference the versions made to such a symbol.
|
|
98
|
+
*/
|
|
99
|
+
const forgetFeatures = (draft, features) => {
|
|
100
|
+
const dropped = new Set(insertedIn(draft.versions).filter(carriedOnlyOn(features)).map(symbol => symbol.id));
|
|
101
|
+
draft.versions.forEach(version => editing(version, edit => forgetFeaturesInEdit(edit, features, dropped)));
|
|
102
|
+
};
|
|
103
|
+
/** Takes the features off the copy, and out of the versions with what only they carried. */
|
|
104
|
+
export const removeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
105
|
+
const features = new Set(featureIds);
|
|
106
|
+
copy.features = without(copy.features, feature => features.has(feature.id));
|
|
107
|
+
forgetFeatures(draft, features);
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Takes the copy out of the edition together with the symbols only it
|
|
111
|
+
* carries, and with every reference the versions made to those symbols.
|
|
112
|
+
*/
|
|
113
|
+
export const removeCopy = (copyId) => onCopy(copyId, (copy, draft) => {
|
|
114
|
+
forgetFeatures(draft, featureIdsOf(copy));
|
|
115
|
+
draft.copies = draft.copies.filter(c => c.id !== copyId);
|
|
116
|
+
});
|
|
117
|
+
/** The carriers of each collated symbol pass to its counterpart. */
|
|
118
|
+
const handOverCarriers = (view, draft, collations) => collations.forEach(({ symbol, counterpart }) => {
|
|
119
|
+
const path = view.getPath(counterpart.id);
|
|
120
|
+
const target = path && getAt(path, draft);
|
|
121
|
+
target?.carriers.push(...symbol.carriers);
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* Bases the child on the parent. A symbol of the child that collates
|
|
125
|
+
* with one the parent hands down adds its carriers to that symbol; the
|
|
126
|
+
* rest become the child's insertions, and what the parent hands down
|
|
127
|
+
* and the child lacks becomes its deletions.
|
|
128
|
+
*/
|
|
129
|
+
export const connectVersions = (view, childId, parentId, tolerance = defaultCollationTolerance) => {
|
|
130
|
+
const inherited = view.snapshot(parentId);
|
|
131
|
+
const own = view.snapshot(childId);
|
|
132
|
+
const collations = collationsOf(own, inherited, symbol => view.dimensionOf(symbol), tolerance);
|
|
133
|
+
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
134
|
+
const matched = new Set(collations.map(({ counterpart }) => counterpart.id));
|
|
135
|
+
const edits = [
|
|
136
|
+
...own.filter(symbol => !collated.has(symbol.id)).map(insertion),
|
|
137
|
+
...inherited.filter(symbol => !matched.has(symbol.id)).map(symbol => deletion(symbol.id))
|
|
138
|
+
];
|
|
139
|
+
return onVersion(childId, (child, draft) => {
|
|
140
|
+
handOverCarriers(view, draft, collations);
|
|
141
|
+
child.edits = edits;
|
|
142
|
+
child.basedOn = assignReference(parentId);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Folds the version's own symbols into those it inherits and collates
|
|
147
|
+
* with: the carriers pass over, and the insertions go.
|
|
148
|
+
*/
|
|
149
|
+
export const collateSymbols = (view, versionId, symbolIds, tolerance = defaultCollationTolerance) => {
|
|
150
|
+
const version = view.get(versionId);
|
|
151
|
+
if (!version?.basedOn)
|
|
152
|
+
return noChange;
|
|
153
|
+
const chosen = new Set(symbolIds);
|
|
154
|
+
const own = insertedIn([version]).filter(symbol => chosen.has(symbol.id));
|
|
155
|
+
const collations = collationsOf(own, view.snapshot(idOf(version.basedOn)), symbol => view.dimensionOf(symbol), tolerance);
|
|
156
|
+
const collated = new Set(collations.map(({ symbol }) => symbol.id));
|
|
157
|
+
return onVersion(versionId, (version, draft) => {
|
|
158
|
+
handOverCarriers(view, draft, collations);
|
|
159
|
+
dropInsertions(version, collated);
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Makes the version stand on its own: what it inherited becomes its
|
|
164
|
+
* own insertions, and the link to the version it was based on goes,
|
|
165
|
+
* with the motivations that belonged to that derivation.
|
|
166
|
+
*/
|
|
167
|
+
export const detachVersion = (view, versionId) => {
|
|
168
|
+
const edits = view.snapshot(versionId).map(insertion);
|
|
169
|
+
return onVersion(versionId, version => {
|
|
170
|
+
version.edits = edits;
|
|
171
|
+
delete version.basedOn;
|
|
172
|
+
version.motivations = [];
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
/** Takes the version out; whatever was based on it comes to stand on its own. */
|
|
176
|
+
export const removeVersion = (view, versionId) => {
|
|
177
|
+
const detachments = view.edition.versions
|
|
178
|
+
.filter(version => version.basedOn && idOf(version.basedOn) === versionId)
|
|
179
|
+
.map(version => detachVersion(view, version.id));
|
|
180
|
+
return draft => {
|
|
181
|
+
detachments.forEach(detach => detach(draft));
|
|
182
|
+
draft.versions = without(draft.versions, version => version.id === versionId);
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
/** Takes the symbols out of the version's own insertions, and the edits that had nothing else. */
|
|
186
|
+
export const removeSymbols = (versionId, symbolIds) => onVersion(versionId, version => dropInsertions(version, new Set(symbolIds)));
|
|
187
|
+
/** Moves the edits into a new version based on this one. */
|
|
188
|
+
export const deriveVersion = (versionId, editIds) => onVersion(versionId, (version, draft) => {
|
|
189
|
+
const chosen = new Set(editIds);
|
|
190
|
+
const moved = version.edits.filter(edit => chosen.has(edit.id));
|
|
191
|
+
version.edits = without(version.edits, edit => chosen.has(edit.id));
|
|
192
|
+
draft.versions.push({
|
|
193
|
+
type: 'Version',
|
|
194
|
+
id: v4(),
|
|
195
|
+
siglum: `${version.siglum}_derived`,
|
|
196
|
+
versionType: 'unicum',
|
|
197
|
+
basedOn: assignReference(versionId),
|
|
198
|
+
edits: moved,
|
|
199
|
+
motivations: []
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
const sameSequence = (a, b) => a.length === b.length && a.every((value, i) => value === b[i]);
|
|
203
|
+
const expressionTypesOf = (symbols) => symbols.filter((symbol) => symbol.type === 'expression').map(symbol => symbol.expressionType);
|
|
204
|
+
const accents = [['SlowCrescendoOn', 'SlowCrescendoOff'], ['ForzandoOn', 'ForzandoOff']];
|
|
205
|
+
const lengthOf = (span) => span.to - span.from;
|
|
206
|
+
/** Shorten or prolong, where the inserted symbol starts about where the deleted one did. */
|
|
207
|
+
const replacementType = (view, inserted, deleted) => {
|
|
208
|
+
const after = view.dimensionOf(inserted)?.horizontal;
|
|
209
|
+
const before = view.dimensionOf(deleted)?.horizontal;
|
|
210
|
+
if (!after || !before || Math.abs(after.from - before.from) >= 5)
|
|
211
|
+
return undefined;
|
|
212
|
+
return lengthOf(after) < lengthOf(before) ? 'shorten' : 'prolong';
|
|
213
|
+
};
|
|
214
|
+
/** A guess at what an edit does, from the symbols it exchanges. */
|
|
215
|
+
const guessEditType = (view, edit) => {
|
|
216
|
+
const inserts = edit.insert ?? [];
|
|
217
|
+
const deletes = view.getAll(edit.delete ?? []);
|
|
218
|
+
const inserted = expressionTypesOf(inserts);
|
|
219
|
+
const deleted = expressionTypesOf(deletes);
|
|
220
|
+
if (deleted.length === 0 && accents.some(accent => sameSequence(inserted, accent)))
|
|
221
|
+
return 'additional-accent';
|
|
222
|
+
if (inserted.length > 1 && sameSequence(inserted, deleted))
|
|
223
|
+
return 'shift';
|
|
224
|
+
if (inserted.length === 0 && deleted.length === 1)
|
|
225
|
+
return 'remove-redundancy';
|
|
226
|
+
if (inserts.length === 1 && deletes.length === 1)
|
|
227
|
+
return replacementType(view, inserts[0], deletes[0]) ?? 'correct-error';
|
|
228
|
+
return 'correct-error';
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Replaces the edits with a single one carrying all their insertions
|
|
232
|
+
* and deletions, classified by a guess at what the exchange does.
|
|
233
|
+
*/
|
|
234
|
+
export const mergeEdits = (view, versionId, toMerge) => {
|
|
235
|
+
if (toMerge.length === 0)
|
|
236
|
+
return noChange;
|
|
237
|
+
const merged = {
|
|
238
|
+
...toMerge[0],
|
|
239
|
+
id: v4(),
|
|
240
|
+
insert: toMerge.flatMap(edit => edit.insert ?? []),
|
|
241
|
+
delete: toMerge.flatMap(edit => edit.delete ?? [])
|
|
242
|
+
};
|
|
243
|
+
merged.editType = guessEditType(view, merged);
|
|
244
|
+
const mergedIds = new Set(toMerge.map(edit => edit.id));
|
|
245
|
+
return onVersion(versionId, version => {
|
|
246
|
+
version.edits = [...version.edits.filter(edit => !mergedIds.has(edit.id)), merged];
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
/** Replaces the edit with one edit per inserted and one per deleted symbol. */
|
|
250
|
+
export const splitEdit = (versionId, toSplit) => {
|
|
251
|
+
const parts = [
|
|
252
|
+
...(toSplit.insert ?? []).map(insertion),
|
|
253
|
+
...(toSplit.delete ?? []).map(deletion)
|
|
254
|
+
];
|
|
255
|
+
return onVersion(versionId, version => {
|
|
256
|
+
version.edits = [...version.edits.filter(edit => edit.id !== toSplit.id), ...parts];
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* Runs the change on the perforation the view locates by id, in whichever
|
|
261
|
+
* version inserted it. A statement made there holds in every version
|
|
262
|
+
* that carries the perforation.
|
|
263
|
+
*/
|
|
264
|
+
const onPerforation = (view, id, op) => draft => {
|
|
265
|
+
const path = view.getPath(id);
|
|
266
|
+
const symbol = path && getAt(path, draft);
|
|
267
|
+
if (isPerforation(symbol))
|
|
268
|
+
op(symbol);
|
|
269
|
+
};
|
|
270
|
+
const clearPlacement = (perforation) => placementRelations.forEach(relation => { delete perforation[relation]; });
|
|
271
|
+
/** States how the follower is placed relative to the reference, in place of any earlier statement. */
|
|
272
|
+
export const placePerforation = (view, followerId, referenceId, relation) => onPerforation(view, followerId, perforation => {
|
|
273
|
+
clearPlacement(perforation);
|
|
274
|
+
perforation[relation] = assignReference(referenceId);
|
|
275
|
+
});
|
|
276
|
+
export const unplacePerforation = (view, followerId) => onPerforation(view, followerId, clearPlacement);
|
|
277
|
+
/** The pair is stated on `statingId` only, as the format asks. */
|
|
278
|
+
export const pairPerforations = (view, statingId, partnerId) => onPerforation(view, statingId, perforation => {
|
|
279
|
+
perforation.pairedWith = assignReference(partnerId);
|
|
280
|
+
});
|
|
281
|
+
export const unpairPerforation = (view, statingId) => onPerforation(view, statingId, perforation => {
|
|
282
|
+
delete perforation.pairedWith;
|
|
283
|
+
});
|
|
284
|
+
const onAssumptionAt = (path, op) => draft => {
|
|
285
|
+
const assumption = getAt(path, draft);
|
|
286
|
+
if (assumption)
|
|
287
|
+
op(assumption);
|
|
288
|
+
};
|
|
289
|
+
const onBeliefAt = (path, op) => onAssumptionAt(path, assumption => {
|
|
290
|
+
const belief = assumption['@annotation']?.belief;
|
|
291
|
+
if (belief)
|
|
292
|
+
op(belief);
|
|
293
|
+
});
|
|
294
|
+
/** Annotates the assumption at the path with a belief held true, for reasons to be added. */
|
|
295
|
+
export const createBelief = (path) => onAssumptionAt(path, assumption => {
|
|
296
|
+
assumption['@annotation'] = {
|
|
297
|
+
id: v4(),
|
|
298
|
+
belief: { type: 'belief', id: v4(), certainty: 'true', reasons: [] }
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
export const clearBelief = (path) => onAssumptionAt(path, assumption => {
|
|
302
|
+
delete assumption['@annotation'];
|
|
303
|
+
});
|
|
304
|
+
export const setCertainty = (path, certainty) => onBeliefAt(path, belief => {
|
|
305
|
+
belief.certainty = certainty;
|
|
306
|
+
});
|
|
307
|
+
export const addReason = (path, reason) => onBeliefAt(path, belief => {
|
|
308
|
+
belief.reasons.push(reason);
|
|
309
|
+
});
|
|
310
|
+
export const removeReason = (path, index) => onBeliefAt(path, belief => {
|
|
311
|
+
belief.reasons.splice(index, 1);
|
|
312
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
export * from './utils';
|
|
2
|
+
export * from './Agent';
|
|
1
3
|
export * from './Assumption';
|
|
2
|
-
export * from './asJsonLd';
|
|
3
|
-
export * from './Collation';
|
|
4
4
|
export * from './ConditionState';
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './Edit';
|
|
7
|
-
export * from './Edition';
|
|
8
|
-
export * from './Emulation';
|
|
9
|
-
export * from './ReproducingSystem';
|
|
5
|
+
export * from './Symbol';
|
|
10
6
|
export * from './Feature';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './RollCopy';
|
|
7
|
+
export * from './Edit';
|
|
13
8
|
export * from './Version';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './TrackerBar';
|
|
9
|
+
export * from './Collation';
|
|
16
10
|
export * from './TrackCalibration';
|
|
17
|
-
export * from './
|
|
11
|
+
export * from './TrackerBar';
|
|
12
|
+
export * from './systems/welteT100/bar';
|
|
13
|
+
export * from './ReproducingSystem';
|
|
14
|
+
export * from './RollCopy';
|
|
15
|
+
export * from './alignment';
|
|
16
|
+
export * from './Edition';
|
|
18
17
|
export * from './EditionView';
|
|
19
|
-
export * from './
|
|
18
|
+
export * from './editionOps';
|
|
19
|
+
export * from './Emulation';
|
|
20
20
|
export * from './validate';
|
|
21
|
-
export * from './
|
|
21
|
+
export * from './constraints';
|
|
22
|
+
export * from './context';
|
|
23
|
+
export * from './asJsonLd';
|
|
24
|
+
export * from './importJsonLd';
|
|
25
|
+
export { readFromStanfordAton, type StanfordAtonOptions } from './readers/stanfordAton';
|
|
26
|
+
export { readFromSpencerMIDI, atConstantSpeed, spencerTrackOf, SPENCER_FEET_PER_MINUTE } from './readers/spencerMidi';
|
package/lib/index.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
export * from './utils';
|
|
2
|
+
export * from './Agent';
|
|
1
3
|
export * from './Assumption';
|
|
2
|
-
export * from './asJsonLd';
|
|
3
|
-
export * from './Collation';
|
|
4
4
|
export * from './ConditionState';
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './Edit';
|
|
7
|
-
export * from './Edition';
|
|
8
|
-
export * from './Emulation';
|
|
9
|
-
export * from './ReproducingSystem';
|
|
5
|
+
export * from './Symbol';
|
|
10
6
|
export * from './Feature';
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './RollCopy';
|
|
7
|
+
export * from './Edit';
|
|
13
8
|
export * from './Version';
|
|
14
|
-
export * from './
|
|
15
|
-
export * from './TrackerBar';
|
|
9
|
+
export * from './Collation';
|
|
16
10
|
export * from './TrackCalibration';
|
|
17
|
-
export * from './
|
|
11
|
+
export * from './TrackerBar';
|
|
12
|
+
export * from './systems/welteT100/bar';
|
|
13
|
+
export * from './ReproducingSystem';
|
|
14
|
+
export * from './RollCopy';
|
|
15
|
+
export * from './alignment';
|
|
16
|
+
export * from './Edition';
|
|
18
17
|
export * from './EditionView';
|
|
19
|
-
export * from './
|
|
18
|
+
export * from './editionOps';
|
|
19
|
+
export * from './Emulation';
|
|
20
20
|
export * from './validate';
|
|
21
|
-
export * from './
|
|
21
|
+
export * from './constraints';
|
|
22
|
+
export * from './context';
|
|
23
|
+
export * from './asJsonLd';
|
|
24
|
+
export * from './importJsonLd';
|
|
25
|
+
export { readFromStanfordAton } from './readers/stanfordAton';
|
|
26
|
+
export { readFromSpencerMIDI, atConstantSpeed, spencerTrackOf, SPENCER_FEET_PER_MINUTE } from './readers/spencerMidi';
|
package/lib/migrate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { conditions } from "./Feature";
|
|
2
2
|
import { rollConditions } from "./RollCopy";
|
|
3
|
-
import { systemOf
|
|
3
|
+
import { systemOf } from "./TrackerBar";
|
|
4
|
+
import { welteT100 } from "./systems/welteT100/bar";
|
|
4
5
|
import { versionTypes } from "./Version";
|
|
5
6
|
const versionTypeValues = new Set(versionTypes);
|
|
6
7
|
const conditionTypeValues = new Set([...rollConditions, ...Object.values(conditions).flat()]);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RollCopy } from "../RollCopy";
|
|
2
|
+
/**
|
|
3
|
+
* How a MIDI key number in one of Spencer Chase's roll files names a
|
|
4
|
+
* tracker bar track.
|
|
5
|
+
*
|
|
6
|
+
* The note block follows the obvious rule, `pitch - 13`, which puts
|
|
7
|
+
* track 11 on MIDI 24 as the T100 compass requires. The bass expression
|
|
8
|
+
* block does not: it reads two tracks high, and subtracting two is what
|
|
9
|
+
* has made these files come out right so far.
|
|
10
|
+
*
|
|
11
|
+
* The boundary between the two rules is unresolved. Taken literally the
|
|
12
|
+
* rules leave tracks 8 and 9 unreachable and jump from track 7 to track 10,
|
|
13
|
+
* which no lateral offset can produce, so at least one of them is
|
|
14
|
+
* approximate. Settling it needs a Spencer file whose expression holes
|
|
15
|
+
* can be checked against the roll, hence the option to override.
|
|
16
|
+
*/
|
|
17
|
+
export declare const spencerTrackOf: (pitch: number) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
20
|
+
* 83 (=8.3 feet per minute). A scanner feeds the paper at one
|
|
21
|
+
* speed, so time in his files is proportional to place.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SPENCER_FEET_PER_MINUTE = 8.3;
|
|
24
|
+
/** Place on the roll in mm after `seconds` at a constant `feetPerMinute`. */
|
|
25
|
+
export declare const atConstantSpeed: (feetPerMinute: number) => (seconds: number) => number;
|
|
26
|
+
export declare function readFromSpencerMIDI(midiBuffer: ArrayBuffer, placeAt?: (seconds: number) => number, trackOf?: (pitch: number) => number): RollCopy;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { read } from "midifile-ts";
|
|
3
|
+
import { asSpans } from "./midiSpans";
|
|
4
|
+
/**
|
|
5
|
+
* How a MIDI key number in one of Spencer Chase's roll files names a
|
|
6
|
+
* tracker bar track.
|
|
7
|
+
*
|
|
8
|
+
* The note block follows the obvious rule, `pitch - 13`, which puts
|
|
9
|
+
* track 11 on MIDI 24 as the T100 compass requires. The bass expression
|
|
10
|
+
* block does not: it reads two tracks high, and subtracting two is what
|
|
11
|
+
* has made these files come out right so far.
|
|
12
|
+
*
|
|
13
|
+
* The boundary between the two rules is unresolved. Taken literally the
|
|
14
|
+
* rules leave tracks 8 and 9 unreachable and jump from track 7 to track 10,
|
|
15
|
+
* which no lateral offset can produce, so at least one of them is
|
|
16
|
+
* approximate. Settling it needs a Spencer file whose expression holes
|
|
17
|
+
* can be checked against the roll, hence the option to override.
|
|
18
|
+
*/
|
|
19
|
+
export const spencerTrackOf = (pitch) => {
|
|
20
|
+
const track = pitch - 13;
|
|
21
|
+
return track < 10 ? track - 2 : track;
|
|
22
|
+
};
|
|
23
|
+
const MM_PER_FOOT = 304.8;
|
|
24
|
+
/**
|
|
25
|
+
* Spencer Chase's rolls seem to be scanned at a roll speed of
|
|
26
|
+
* 83 (=8.3 feet per minute). A scanner feeds the paper at one
|
|
27
|
+
* speed, so time in his files is proportional to place.
|
|
28
|
+
*/
|
|
29
|
+
export const SPENCER_FEET_PER_MINUTE = 8.3;
|
|
30
|
+
/** Place on the roll in mm after `seconds` at a constant `feetPerMinute`. */
|
|
31
|
+
export const atConstantSpeed = (feetPerMinute) => (seconds) => feetPerMinute * MM_PER_FOOT / 60 * seconds;
|
|
32
|
+
export function readFromSpencerMIDI(midiBuffer, placeAt = atConstantSpeed(SPENCER_FEET_PER_MINUTE), trackOf = spencerTrackOf) {
|
|
33
|
+
const features = asSpans(read(midiBuffer))
|
|
34
|
+
.filter(span => span.type === 'note')
|
|
35
|
+
.map((span) => ({
|
|
36
|
+
type: 'Hole',
|
|
37
|
+
id: v4(),
|
|
38
|
+
vertical: {
|
|
39
|
+
from: trackOf(span.pitch),
|
|
40
|
+
unit: 'track'
|
|
41
|
+
},
|
|
42
|
+
horizontal: {
|
|
43
|
+
from: placeAt(span.onsetMs / 1000),
|
|
44
|
+
to: placeAt(span.offsetMs / 1000),
|
|
45
|
+
unit: 'mm'
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
return {
|
|
49
|
+
type: 'RollCopy',
|
|
50
|
+
id: v4(),
|
|
51
|
+
ops: [],
|
|
52
|
+
conditions: [],
|
|
53
|
+
keeper: { name: '', sameAs: [] },
|
|
54
|
+
measurements: {},
|
|
55
|
+
modifications: [],
|
|
56
|
+
features
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RollCopy } from "../RollCopy";
|
|
2
|
+
import { TrackerBar } from "../TrackerBar";
|
|
3
|
+
export interface StanfordAtonOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Added to the scanning software's hole numbering to reach the
|
|
6
|
+
* tracker bar. Left out, it is inferred by putting the rewind
|
|
7
|
+
* perforation on the bar's rewind track.
|
|
8
|
+
*/
|
|
9
|
+
trackShift?: number;
|
|
10
|
+
bar?: TrackerBar;
|
|
11
|
+
/**
|
|
12
|
+
* Where the scan the analysis was made from can be seen. Stanford's
|
|
13
|
+
* files name their scan by DRUID, so this is only needed for an
|
|
14
|
+
* analysis of a scan hosted elsewhere.
|
|
15
|
+
*/
|
|
16
|
+
scan?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function readFromStanfordAton(atonString: string, { trackShift, bar, scan }?: StanfordAtonOptions): RollCopy;
|