linked-rolls 0.57.0 → 0.59.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/README.md +54 -367
- package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
- package/lib/{constraints.js → analysis/constraints.js} +8 -7
- package/lib/analysis/ownPaper.d.ts +26 -0
- package/lib/analysis/ownPaper.js +41 -0
- package/lib/{reservations.d.ts → analysis/reservations.d.ts} +3 -3
- package/lib/{reservations.js → analysis/reservations.js} +3 -3
- package/lib/{sigla.d.ts → analysis/sigla.d.ts} +2 -2
- package/lib/{sigla.js → analysis/sigla.js} +4 -4
- package/lib/{witnesses.d.ts → analysis/witnesses.d.ts} +2 -2
- package/lib/{witnesses.js → analysis/witnesses.js} +2 -2
- package/lib/{Collation.d.ts → collation/Collation.d.ts} +4 -4
- package/lib/{Collation.js → collation/Collation.js} +4 -4
- package/lib/{alignment.d.ts → collation/alignment.d.ts} +4 -4
- package/lib/{alignment.js → collation/alignment.js} +10 -16
- package/lib/{scatter.d.ts → collation/scatter.d.ts} +4 -4
- package/lib/{scatter.js → collation/scatter.js} +3 -3
- package/lib/{statistics.d.ts → collation/statistics.d.ts} +1 -1
- package/lib/{statistics.js → collation/statistics.js} +2 -2
- package/lib/{substitution.d.ts → collation/substitution.d.ts} +9 -24
- package/lib/{substitution.js → collation/substitution.js} +13 -37
- package/lib/emulation/Emulation.d.ts +64 -0
- package/lib/emulation/Emulation.js +282 -0
- package/lib/emulation/negotiation.d.ts +14 -0
- package/lib/emulation/negotiation.js +20 -0
- package/lib/index.d.ts +43 -35
- package/lib/index.js +36 -35
- package/lib/{asJsonLd.d.ts → io/asJsonLd.d.ts} +1 -1
- package/lib/{asJsonLd.js → io/asJsonLd.js} +3 -3
- package/lib/{context.d.ts → io/context.d.ts} +4 -4
- package/lib/{context.js → io/context.js} +4 -4
- package/lib/{importJsonLd.d.ts → io/importJsonLd.d.ts} +1 -1
- package/lib/io/importJsonLd.js +128 -0
- package/lib/{migrate.js → io/migrate.js} +59 -55
- package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
- package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
- package/lib/{Edit.d.ts → model/Edit.d.ts} +1 -1
- package/lib/{Edition.d.ts → model/Edition.d.ts} +2 -2
- package/lib/{Feature.d.ts → model/Feature.d.ts} +1 -1
- package/lib/{FeatureSource.d.ts → model/FeatureSource.d.ts} +1 -1
- package/lib/{Perforator.d.ts → model/Perforator.d.ts} +1 -1
- package/lib/{RollCopy.d.ts → model/RollCopy.d.ts} +3 -10
- package/lib/{RollCopy.js → model/RollCopy.js} +3 -5
- package/lib/{Symbol.d.ts → model/Symbol.d.ts} +1 -1
- package/lib/{Version.d.ts → model/Version.d.ts} +10 -2
- package/lib/{Version.js → model/Version.js} +24 -1
- package/lib/{vocabulary.js → model/vocabulary.js} +2 -2
- package/lib/ops/beliefs.d.ts +9 -0
- package/lib/ops/beliefs.js +31 -0
- package/lib/ops/collation.d.ts +62 -0
- package/lib/ops/collation.js +228 -0
- package/lib/ops/commands.d.ts +9 -0
- package/lib/ops/commands.js +29 -0
- package/lib/ops/copies.d.ts +74 -0
- package/lib/ops/copies.js +135 -0
- package/lib/ops/draft.d.ts +59 -0
- package/lib/ops/draft.js +81 -0
- package/lib/ops/edits.d.ts +10 -0
- package/lib/ops/edits.js +82 -0
- package/lib/ops/features.d.ts +84 -0
- package/lib/ops/features.js +296 -0
- package/lib/ops/forget.d.ts +31 -0
- package/lib/ops/forget.js +110 -0
- package/lib/ops/immutable.d.ts +16 -0
- package/lib/ops/immutable.js +46 -0
- package/lib/ops/index.d.ts +13 -0
- package/lib/ops/index.js +7 -0
- package/lib/ops/versions.d.ts +33 -0
- package/lib/ops/versions.js +95 -0
- package/lib/readers/AtonParser.js +5 -7
- package/lib/readers/phillipsEroll.d.ts +3 -3
- package/lib/readers/phillipsEroll.js +4 -5
- package/lib/readers/spencerBar.d.ts +2 -2
- package/lib/readers/spencerBar.js +2 -3
- package/lib/readers/stanfordAton.d.ts +3 -3
- package/lib/readers/stanfordAton.js +2 -3
- package/lib/schema.json +0 -13
- package/lib/{ReproducingSystem.d.ts → systems/ReproducingSystem.d.ts} +3 -3
- package/lib/{TrackerBar.d.ts → systems/TrackerBar.d.ts} +48 -8
- package/lib/{TrackerBar.js → systems/TrackerBar.js} +11 -2
- package/lib/systems/index.d.ts +8 -2
- package/lib/systems/index.js +7 -1
- package/lib/systems/welte.d.ts +100 -0
- package/lib/systems/welte.js +119 -0
- package/lib/systems/welteLicensee/bar.d.ts +1 -1
- package/lib/systems/welteLicensee/bar.js +5 -2
- package/lib/systems/welteLicensee/system.d.ts +1 -1
- package/lib/systems/welteLicensee/system.js +1 -1
- package/lib/systems/welteT100/bar.d.ts +27 -1
- package/lib/systems/welteT100/bar.js +44 -2
- package/lib/systems/welteT100/system.d.ts +6 -39
- package/lib/systems/welteT100/system.js +19 -128
- package/lib/systems/welteT98/bar.d.ts +1 -1
- package/lib/systems/welteT98/bar.js +18 -2
- package/lib/systems/welteT98/system.d.ts +20 -38
- package/lib/systems/welteT98/system.js +20 -121
- package/lib/validate.d.ts +1 -1
- package/lib/{EditionView.d.ts → view/EditionView.d.ts} +17 -41
- package/lib/{EditionView.js → view/EditionView.js} +26 -75
- package/package.json +7 -2
- package/lib/Emulation.d.ts +0 -36
- package/lib/Emulation.js +0 -250
- package/lib/editionOps.d.ts +0 -260
- package/lib/editionOps.js +0 -1079
- package/lib/importJsonLd.js +0 -57
- /package/lib/{migrate.d.ts → io/migrate.d.ts} +0 -0
- /package/lib/{Agent.js → model/Agent.js} +0 -0
- /package/lib/{Assumption.js → model/Assumption.js} +0 -0
- /package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +0 -0
- /package/lib/{ConditionState.js → model/ConditionState.js} +0 -0
- /package/lib/{Edit.js → model/Edit.js} +0 -0
- /package/lib/{Edition.js → model/Edition.js} +0 -0
- /package/lib/{Feature.js → model/Feature.js} +0 -0
- /package/lib/{FeatureSource.js → model/FeatureSource.js} +0 -0
- /package/lib/{Perforator.js → model/Perforator.js} +0 -0
- /package/lib/{Quantity.d.ts → model/Quantity.d.ts} +0 -0
- /package/lib/{Quantity.js → model/Quantity.js} +0 -0
- /package/lib/{Symbol.js → model/Symbol.js} +0 -0
- /package/lib/{TrackCalibration.d.ts → model/TrackCalibration.d.ts} +0 -0
- /package/lib/{TrackCalibration.js → model/TrackCalibration.js} +0 -0
- /package/lib/{notes.d.ts → model/notes.d.ts} +0 -0
- /package/lib/{notes.js → model/notes.js} +0 -0
- /package/lib/{procedures.d.ts → model/procedures.d.ts} +0 -0
- /package/lib/{procedures.js → model/procedures.js} +0 -0
- /package/lib/{vocabulary.d.ts → model/vocabulary.d.ts} +0 -0
- /package/lib/{sorted.d.ts → shared/sorted.d.ts} +0 -0
- /package/lib/{sorted.js → shared/sorted.js} +0 -0
- /package/lib/{utils.d.ts → shared/utils.d.ts} +0 -0
- /package/lib/{utils.js → shared/utils.js} +0 -0
- /package/lib/{ReproducingSystem.js → systems/ReproducingSystem.js} +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { Edition } from "
|
|
2
|
-
import { FeatureOrPatch, HorizontalSpan, NestedFeature } from "
|
|
3
|
-
import { AnySymbol
|
|
4
|
-
import { Version } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { Modification, ProductionEvent, RollCopy } from "./RollCopy.js";
|
|
8
|
-
import { Millimeters } from "./Quantity.js";
|
|
1
|
+
import { Edition } from "../model/Edition.js";
|
|
2
|
+
import { FeatureOrPatch, HorizontalSpan, NestedFeature } from "../model/Feature.js";
|
|
3
|
+
import { AnySymbol } from "../model/Symbol.js";
|
|
4
|
+
import { Version } from "../model/Version.js";
|
|
5
|
+
import { Modification, ProductionEvent, RollCopy } from "../model/RollCopy.js";
|
|
6
|
+
import { Millimeters } from "../model/Quantity.js";
|
|
9
7
|
export type Path = (string | number)[];
|
|
10
8
|
export declare const getAt: <T>(path: Path, obj: unknown) => T | undefined;
|
|
11
9
|
export declare class EditionView {
|
|
@@ -30,7 +28,13 @@ export declare class EditionView {
|
|
|
30
28
|
private readonly symbolsById;
|
|
31
29
|
private featuresById?;
|
|
32
30
|
constructor(edition: Edition);
|
|
31
|
+
/** What stands at the path, or null where nothing does. A zero or an empty string stands there as well. */
|
|
33
32
|
atPath<T>(path: Path): T | null;
|
|
33
|
+
/**
|
|
34
|
+
* Walks the edition and records where each id stands and where it is
|
|
35
|
+
* referenced. The constructor does this; a second call starts afresh
|
|
36
|
+
* rather than recording every reference twice.
|
|
37
|
+
*/
|
|
34
38
|
indexObjects(): void;
|
|
35
39
|
version(id: string): Readonly<Version> | undefined;
|
|
36
40
|
copy(id: string): Readonly<RollCopy> | undefined;
|
|
@@ -43,6 +47,11 @@ export declare class EditionView {
|
|
|
43
47
|
features(ids: readonly string[]): Readonly<NestedFeature>[];
|
|
44
48
|
getPath(anyId: string): Path | undefined;
|
|
45
49
|
linksTo(anyId: string): Path[];
|
|
50
|
+
/**
|
|
51
|
+
* Calls back on the version and its ancestors along the principal
|
|
52
|
+
* line. A stemma that loops back on itself is walked once round, so
|
|
53
|
+
* that an edition in that state can still be read and repaired.
|
|
54
|
+
*/
|
|
46
55
|
travelUp(versionId: string, callback: (version: Readonly<Version>) => void): void;
|
|
47
56
|
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
48
57
|
lineageOf(versionId: string): Readonly<Version>[];
|
|
@@ -60,29 +69,6 @@ export declare class EditionView {
|
|
|
60
69
|
* `mergeFeatures` asks before it reads them as one.
|
|
61
70
|
*/
|
|
62
71
|
actOf(featureId: string): Readonly<ProductionEvent | Modification> | undefined;
|
|
63
|
-
/**
|
|
64
|
-
* How a place on the edition's shared axis relates to the paper of
|
|
65
|
-
* this version: place × factor = millimetres of its own paper.
|
|
66
|
-
*
|
|
67
|
-
* Copies cut for different systems are scaled onto one axis so that
|
|
68
|
-
* they can be collated at all, which leaves a version of another
|
|
69
|
-
* system carrying places in the axis copy's millimetres. A
|
|
70
|
-
* performance needs the paper the roll actually ran on, and the
|
|
71
|
-
* factor is the inverse of the scale `alignCopy` recorded.
|
|
72
|
-
*
|
|
73
|
-
* It is read only from the copies of the version's own system, since
|
|
74
|
-
* under the shared axis a green version's notes are carried by red
|
|
75
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
76
|
-
* scale is put down to its own paper having stretched is left out as
|
|
77
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
78
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
79
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
80
|
-
*/
|
|
81
|
-
toOwnPaperOf(version: Readonly<Version>): number | undefined;
|
|
82
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
83
|
-
speedScalesIn(version: Readonly<Version>): number[];
|
|
84
|
-
/** The copies of the version's own system that carry any of its symbols. */
|
|
85
|
-
copiesOwning(version: Readonly<Version>): Readonly<RollCopy>[];
|
|
86
72
|
/** The version the given one's text is read against, by its principal derivation. */
|
|
87
73
|
predecessorOf(versionId: string): Readonly<Version> | undefined;
|
|
88
74
|
/**
|
|
@@ -114,14 +100,4 @@ export declare class EditionView {
|
|
|
114
100
|
withGenerations(): Array<Version & {
|
|
115
101
|
generation: number;
|
|
116
102
|
}>;
|
|
117
|
-
/**
|
|
118
|
-
* The symbol as a performance needs it: where it lies, and the
|
|
119
|
-
* position the performing bar reads it on.
|
|
120
|
-
*
|
|
121
|
-
* Nothing where that bar reads nothing of it, which is the case a
|
|
122
|
-
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
123
|
-
* green version still inherits cannot be performed on a green
|
|
124
|
-
* machine, and an edit has yet to say what took its place.
|
|
125
|
-
*/
|
|
126
|
-
simplifySymbol(symbol: Note | Expression, bar: TrackerBar): NegotiatedEvent | null;
|
|
127
103
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { isPlaced, withBorneFeatures } from "
|
|
2
|
-
import { deletedBy, insertedBy, principalDerivationOf } from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { mean } from "./Quantity.js";
|
|
1
|
+
import { isPlaced, withBorneFeatures } from "../model/Feature.js";
|
|
2
|
+
import { deletedBy, insertedBy, principalDerivationOf } from "../model/Version.js";
|
|
3
|
+
import { featuresMadeBy, featuresOf } from "../model/RollCopy.js";
|
|
4
|
+
import { idOf, idsOf } from "../model/Assumption.js";
|
|
5
|
+
import { mean } from "../model/Quantity.js";
|
|
7
6
|
export const getAt = (path, obj) => {
|
|
8
7
|
let node = obj;
|
|
9
8
|
for (const key of path) {
|
|
@@ -95,11 +94,18 @@ export class EditionView {
|
|
|
95
94
|
this.symbolsById = byIdIn(edition.versions.flatMap(insertedBy));
|
|
96
95
|
this.indexObjects();
|
|
97
96
|
}
|
|
97
|
+
/** What stands at the path, or null where nothing does. A zero or an empty string stands there as well. */
|
|
98
98
|
atPath(path) {
|
|
99
|
-
|
|
100
|
-
return node || null;
|
|
99
|
+
return getAt(path, this.edition) ?? null;
|
|
101
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Walks the edition and records where each id stands and where it is
|
|
103
|
+
* referenced. The constructor does this; a second call starts afresh
|
|
104
|
+
* rather than recording every reference twice.
|
|
105
|
+
*/
|
|
102
106
|
indexObjects() {
|
|
107
|
+
this.paths.clear();
|
|
108
|
+
this.links.clear();
|
|
103
109
|
const visited = new WeakSet();
|
|
104
110
|
const link = (id, trail) => {
|
|
105
111
|
const trails = this.links.get(id);
|
|
@@ -172,14 +178,19 @@ export class EditionView {
|
|
|
172
178
|
linksTo(anyId) {
|
|
173
179
|
return (this.links.get(anyId) ?? []).map(pathOf);
|
|
174
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Calls back on the version and its ancestors along the principal
|
|
183
|
+
* line. A stemma that loops back on itself is walked once round, so
|
|
184
|
+
* that an edition in that state can still be read and repaired.
|
|
185
|
+
*/
|
|
175
186
|
travelUp(versionId, callback) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.
|
|
187
|
+
const visited = new Set();
|
|
188
|
+
let v = this.version(versionId);
|
|
189
|
+
while (v && !visited.has(v.id)) {
|
|
190
|
+
visited.add(v.id);
|
|
191
|
+
callback(v);
|
|
192
|
+
const principal = principalDerivationOf(v);
|
|
193
|
+
v = principal && this.version(idOf(principal));
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
@@ -221,46 +232,6 @@ export class EditionView {
|
|
|
221
232
|
return copy.production;
|
|
222
233
|
return copy.modifications.find(act => states(featuresMadeBy(act)));
|
|
223
234
|
}
|
|
224
|
-
/**
|
|
225
|
-
* How a place on the edition's shared axis relates to the paper of
|
|
226
|
-
* this version: place × factor = millimetres of its own paper.
|
|
227
|
-
*
|
|
228
|
-
* Copies cut for different systems are scaled onto one axis so that
|
|
229
|
-
* they can be collated at all, which leaves a version of another
|
|
230
|
-
* system carrying places in the axis copy's millimetres. A
|
|
231
|
-
* performance needs the paper the roll actually ran on, and the
|
|
232
|
-
* factor is the inverse of the scale `alignCopy` recorded.
|
|
233
|
-
*
|
|
234
|
-
* It is read only from the copies of the version's own system, since
|
|
235
|
-
* under the shared axis a green version's notes are carried by red
|
|
236
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
237
|
-
* scale is put down to its own paper having stretched is left out as
|
|
238
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
239
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
240
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
241
|
-
*/
|
|
242
|
-
toOwnPaperOf(version) {
|
|
243
|
-
const scales = this.speedScalesIn(version);
|
|
244
|
-
return scales.length === 1 ? 1 / scales[0] : undefined;
|
|
245
|
-
}
|
|
246
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
247
|
-
speedScalesIn(version) {
|
|
248
|
-
return [...new Set(this.copiesOwning(version)
|
|
249
|
-
.filter(copy => !copy.conditions.some(isPaperStretch))
|
|
250
|
-
.map(copy => copy.measurements.scale)
|
|
251
|
-
.filter((scale) => scale !== undefined && scale > 0))];
|
|
252
|
-
}
|
|
253
|
-
/** The copies of the version's own system that carry any of its symbols. */
|
|
254
|
-
copiesOwning(version) {
|
|
255
|
-
const system = systemIdOf(version.system);
|
|
256
|
-
const carrying = new Set(this.snapshot(version.id)
|
|
257
|
-
.flatMap(symbol => idsOf(symbol.carriers))
|
|
258
|
-
.flatMap(id => {
|
|
259
|
-
const copy = this.copyOf(id);
|
|
260
|
-
return copy ? [copy.id] : [];
|
|
261
|
-
}));
|
|
262
|
-
return this.edition.copies.filter(copy => carrying.has(copy.id) && systemIdOf(copy.production?.system) === system);
|
|
263
|
-
}
|
|
264
235
|
/** The version the given one's text is read against, by its principal derivation. */
|
|
265
236
|
predecessorOf(versionId) {
|
|
266
237
|
const v = this.version(versionId);
|
|
@@ -355,24 +326,4 @@ export class EditionView {
|
|
|
355
326
|
}));
|
|
356
327
|
return withGen;
|
|
357
328
|
}
|
|
358
|
-
/**
|
|
359
|
-
* The symbol as a performance needs it: where it lies, and the
|
|
360
|
-
* position the performing bar reads it on.
|
|
361
|
-
*
|
|
362
|
-
* Nothing where that bar reads nothing of it, which is the case a
|
|
363
|
-
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
364
|
-
* green version still inherits cannot be performed on a green
|
|
365
|
-
* machine, and an edit has yet to say what took its place.
|
|
366
|
-
*/
|
|
367
|
-
simplifySymbol(symbol, bar) {
|
|
368
|
-
const horizontal = this.placeOf(symbol);
|
|
369
|
-
const position = bar.positionOf(symbol);
|
|
370
|
-
if (!horizontal || position === undefined)
|
|
371
|
-
return null;
|
|
372
|
-
return {
|
|
373
|
-
...symbol,
|
|
374
|
-
horizontal,
|
|
375
|
-
vertical: { unit: 'track', from: position }
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
329
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.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": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./lib/index.d.ts",
|
|
17
17
|
"default": "./lib/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./validate": {
|
|
20
|
+
"types": "./lib/validate.d.ts",
|
|
21
|
+
"default": "./lib/validate.js"
|
|
22
|
+
},
|
|
19
23
|
"./welte-t100": {
|
|
20
24
|
"types": "./lib/systems/welteT100/system.d.ts",
|
|
21
25
|
"default": "./lib/systems/welteT100/system.js"
|
|
@@ -28,7 +32,6 @@
|
|
|
28
32
|
"types": "./lib/systems/welteLicensee/system.d.ts",
|
|
29
33
|
"default": "./lib/systems/welteLicensee/system.js"
|
|
30
34
|
},
|
|
31
|
-
"./lib/*": "./lib/*",
|
|
32
35
|
"./package.json": "./package.json"
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
"build": "npm run build:schema && rm -rf lib && tsc",
|
|
41
44
|
"prepublishOnly": "npm run build && vitest run && npm run smoke",
|
|
42
45
|
"test": "vitest",
|
|
46
|
+
"lint": "oxlint -c .oxlintrc.json src test scripts schema",
|
|
43
47
|
"build:schema": "npx ts-json-schema-generator --additional-properties --validation-keywords see --path 'src/**/*.ts' --type 'Edition' > schema_.json && node schema/postprocess.js && rm schema_.json && mv schema.json src/schema.json",
|
|
44
48
|
"build:docs": "node schema/docs/generate.ts src/schema.json docs/index.html",
|
|
45
49
|
"smoke": "node scripts/smoke.mjs"
|
|
@@ -50,6 +54,7 @@
|
|
|
50
54
|
"@types/node": "^24.13.3",
|
|
51
55
|
"jsonld": "^9.0.0",
|
|
52
56
|
"n3": "^2.7.12",
|
|
57
|
+
"oxlint": "^1.85.0",
|
|
53
58
|
"ts-json-schema-generator": "^2.9.0",
|
|
54
59
|
"typescript": "^7.0.2",
|
|
55
60
|
"vite": "^8.2.2",
|
package/lib/Emulation.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { MidiFile } from "midifile-ts";
|
|
2
|
-
import { EditionView } from "./EditionView.js";
|
|
3
|
-
import { Version } from "./Version.js";
|
|
4
|
-
import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "./ReproducingSystem.js";
|
|
5
|
-
import { Millimeters } from "./Quantity.js";
|
|
6
|
-
export type EmulationScope = {
|
|
7
|
-
/** Only notes whose onset lies within this span of the roll are played. */
|
|
8
|
-
range?: [Millimeters, Millimeters];
|
|
9
|
-
/** Start the clock at the first note rather than at the beginning of the roll. */
|
|
10
|
-
skipToFirstNote?: boolean;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* A version of the edition, performed: the symbols are negotiated into
|
|
14
|
-
* placed events, the reproducing system plays them, and the result goes
|
|
15
|
-
* out as MIDI in which every note and pedal step is labelled with the
|
|
16
|
-
* symbol it performs.
|
|
17
|
-
*/
|
|
18
|
-
export declare class Emulation<Options extends object> {
|
|
19
|
-
readonly system: ReproducingSystem<Options>;
|
|
20
|
-
options: Options;
|
|
21
|
-
midiEvents: AnyPerformedRollFeature[];
|
|
22
|
-
negotiatedEvents: NegotiatedEvent[];
|
|
23
|
-
curves: readonly EmulatedCurve[];
|
|
24
|
-
source?: string;
|
|
25
|
-
constructor(system: ReproducingSystem<Options>, options?: Options);
|
|
26
|
-
/**
|
|
27
|
-
* Moves the negotiated events to where their statements put them.
|
|
28
|
-
* The view supplies the copies, whose measurements decide how far
|
|
29
|
-
* before or after its reference a command goes, and a punch
|
|
30
|
-
* diameter, or a millimetre, where no copy agrees with a statement.
|
|
31
|
-
*/
|
|
32
|
-
applyConstraints(view: EditionView): void;
|
|
33
|
-
emulateVersion(version: Version, view: EditionView, { range, skipToFirstNote }?: EmulationScope): AnyPerformedRollFeature[];
|
|
34
|
-
findEventsPerforming(id: string): AnyPerformedRollFeature[];
|
|
35
|
-
asMIDI(): MidiFile;
|
|
36
|
-
}
|
package/lib/Emulation.js
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { MIDIControlEvents } from "midifile-ts";
|
|
2
|
-
import { idOf } from "./Assumption.js";
|
|
3
|
-
import { isCommand, pairsAmong, placementsOf } from "./Symbol.js";
|
|
4
|
-
import { add, mean, mm, seconds, subtract } from "./Quantity.js";
|
|
5
|
-
import { punchDiameterOf } from "./RollCopy.js";
|
|
6
|
-
/** The punch diameter the edition's copies report, where any of them does. */
|
|
7
|
-
const meanPunchDiameterOf = (view) => {
|
|
8
|
-
const measured = view.edition.copies
|
|
9
|
-
.map(punchDiameterOf)
|
|
10
|
-
.filter(value => value !== undefined);
|
|
11
|
-
return measured.length > 0 ? mean(measured) : undefined;
|
|
12
|
-
};
|
|
13
|
-
const propertiesOf = (view, version) => ({
|
|
14
|
-
punchDiameter: meanPunchDiameterOf(view),
|
|
15
|
-
tempo: view.edition.tempoAdjustment,
|
|
16
|
-
toOwnPaper: view.toOwnPaperOf(version)
|
|
17
|
-
});
|
|
18
|
-
/** The onset a symbol has on each copy carrying it, by the copy's index, as the mean of its chains there. */
|
|
19
|
-
const onsetsByCopy = (view, symbolId) => {
|
|
20
|
-
const symbol = view.symbol(symbolId);
|
|
21
|
-
if (!symbol)
|
|
22
|
-
return new Map();
|
|
23
|
-
const onsets = view.placedCarriersOf(symbol).flatMap((carrier) => {
|
|
24
|
-
const copy = view.getPath(carrier.id)?.[1];
|
|
25
|
-
return typeof copy === 'number' ? [[copy, carrier.horizontal.from]] : [];
|
|
26
|
-
});
|
|
27
|
-
const copies = new Set(onsets.map(([copy]) => copy));
|
|
28
|
-
return new Map([...copies].map(copy => [copy, mean(onsets.filter(([at]) => at === copy).map(([, onset]) => onset))]));
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* The measured distance from the onset of the reference to the onset of
|
|
32
|
-
* the follower on each copy carrying both, negative where the follower
|
|
33
|
-
* comes first there.
|
|
34
|
-
*/
|
|
35
|
-
const offsetsBetween = (view, followerId, referenceId) => {
|
|
36
|
-
const references = onsetsByCopy(view, referenceId);
|
|
37
|
-
return [...onsetsByCopy(view, followerId)].flatMap(([copy, onset]) => {
|
|
38
|
-
const reference = references.get(copy);
|
|
39
|
-
return reference === undefined ? [] : [subtract(onset, reference)];
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* How far before or after its reference a follower is put when the
|
|
44
|
-
* measurement does not already have it there: as far as the copies
|
|
45
|
-
* that agree with the statement put it, and one gap where none does.
|
|
46
|
-
*/
|
|
47
|
-
const distanceOnSide = (side, offsets, gap) => {
|
|
48
|
-
const agreeing = offsets.filter(offset => side === 'before' ? offset < 0 : offset > 0);
|
|
49
|
-
return agreeing.length > 0 ? mm(Math.abs(mean(agreeing))) : gap;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* How far each event has to move for the placements to hold:
|
|
53
|
-
* an aligned event takes the onset of its reference, one placed before
|
|
54
|
-
* or after its reference keeps its place where the measurement has it
|
|
55
|
-
* on that side and is moved there where it does not, and a paired
|
|
56
|
-
* event follows its partner so that the distance between the two is
|
|
57
|
-
* kept. Events that stay where they are do not appear.
|
|
58
|
-
*/
|
|
59
|
-
const displacementsOf = (events, offsetsBetween, gap) => {
|
|
60
|
-
const byId = new Map(events.map(event => [event.id, event]));
|
|
61
|
-
/** Where an event comes to lie once its statement and those of its references hold. */
|
|
62
|
-
const placedOnsetOf = (event, visited = new Set()) => {
|
|
63
|
-
const measured = event.horizontal.from;
|
|
64
|
-
const placement = placementsOf(event)[0];
|
|
65
|
-
const reference = placement && byId.get(idOf(placement.reference));
|
|
66
|
-
if (!placement || !reference || reference.id === event.id || visited.has(event.id))
|
|
67
|
-
return measured;
|
|
68
|
-
const referenceOnset = placedOnsetOf(reference, new Set([...visited, event.id]));
|
|
69
|
-
const distance = (side) => distanceOnSide(side, offsetsBetween(event.id, reference.id), gap);
|
|
70
|
-
switch (placement.relation) {
|
|
71
|
-
case 'alignedWith': return referenceOnset;
|
|
72
|
-
case 'before': return measured < referenceOnset ? measured : subtract(referenceOnset, distance('before'));
|
|
73
|
-
case 'after': return measured > referenceOnset ? measured : add(referenceOnset, distance('after'));
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
const displacements = new Map(events
|
|
77
|
-
.map((event) => [event, subtract(placedOnsetOf(event), event.horizontal.from)])
|
|
78
|
-
.filter(([, distance]) => distance !== 0));
|
|
79
|
-
pairsAmong(events).forEach(([one, other]) => {
|
|
80
|
-
const shared = displacements.get(one) ?? displacements.get(other);
|
|
81
|
-
if (shared === undefined)
|
|
82
|
-
return;
|
|
83
|
-
displacements.set(one, shared);
|
|
84
|
-
displacements.set(other, shared);
|
|
85
|
-
});
|
|
86
|
-
return displacements;
|
|
87
|
-
};
|
|
88
|
-
/** The earliest of the times, or the beginning of the roll where there are none. */
|
|
89
|
-
const earliestOf = (times) => times.length === 0 ? seconds(0) : times.reduce((soonest, at) => at < soonest ? at : soonest);
|
|
90
|
-
/**
|
|
91
|
-
* A version of the edition, performed: the symbols are negotiated into
|
|
92
|
-
* placed events, the reproducing system plays them, and the result goes
|
|
93
|
-
* out as MIDI in which every note and pedal step is labelled with the
|
|
94
|
-
* symbol it performs.
|
|
95
|
-
*/
|
|
96
|
-
export class Emulation {
|
|
97
|
-
constructor(system, options = system.defaultOptions) {
|
|
98
|
-
Object.defineProperty(this, "system", {
|
|
99
|
-
enumerable: true,
|
|
100
|
-
configurable: true,
|
|
101
|
-
writable: true,
|
|
102
|
-
value: void 0
|
|
103
|
-
});
|
|
104
|
-
Object.defineProperty(this, "options", {
|
|
105
|
-
enumerable: true,
|
|
106
|
-
configurable: true,
|
|
107
|
-
writable: true,
|
|
108
|
-
value: void 0
|
|
109
|
-
});
|
|
110
|
-
Object.defineProperty(this, "midiEvents", {
|
|
111
|
-
enumerable: true,
|
|
112
|
-
configurable: true,
|
|
113
|
-
writable: true,
|
|
114
|
-
value: []
|
|
115
|
-
});
|
|
116
|
-
// sorted list of events with the negotiated assumptions already applied
|
|
117
|
-
Object.defineProperty(this, "negotiatedEvents", {
|
|
118
|
-
enumerable: true,
|
|
119
|
-
configurable: true,
|
|
120
|
-
writable: true,
|
|
121
|
-
value: []
|
|
122
|
-
});
|
|
123
|
-
Object.defineProperty(this, "curves", {
|
|
124
|
-
enumerable: true,
|
|
125
|
-
configurable: true,
|
|
126
|
-
writable: true,
|
|
127
|
-
value: []
|
|
128
|
-
});
|
|
129
|
-
Object.defineProperty(this, "source", {
|
|
130
|
-
enumerable: true,
|
|
131
|
-
configurable: true,
|
|
132
|
-
writable: true,
|
|
133
|
-
value: void 0
|
|
134
|
-
});
|
|
135
|
-
this.system = system;
|
|
136
|
-
this.options = options;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Moves the negotiated events to where their statements put them.
|
|
140
|
-
* The view supplies the copies, whose measurements decide how far
|
|
141
|
-
* before or after its reference a command goes, and a punch
|
|
142
|
-
* diameter, or a millimetre, where no copy agrees with a statement.
|
|
143
|
-
*/
|
|
144
|
-
applyConstraints(view) {
|
|
145
|
-
const gap = meanPunchDiameterOf(view) ?? mm(1);
|
|
146
|
-
const offsets = (follower, reference) => offsetsBetween(view, follower, reference);
|
|
147
|
-
displacementsOf(this.negotiatedEvents, offsets, gap).forEach((distance, event) => {
|
|
148
|
-
event.horizontal.from = add(event.horizontal.from, distance);
|
|
149
|
-
event.horizontal.to = add(event.horizontal.to, distance);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
emulateVersion(version, view, { range, skipToFirstNote = false } = {}) {
|
|
153
|
-
this.source = version.id;
|
|
154
|
-
/** A note plays only where its onset falls in the range; expressions play throughout. */
|
|
155
|
-
const inScope = (event) => !range || event.type !== 'note' || (event.horizontal.from > range[0] && event.horizontal.from < range[1]);
|
|
156
|
-
this.negotiatedEvents =
|
|
157
|
-
view.snapshot(version.id)
|
|
158
|
-
.filter(isCommand)
|
|
159
|
-
.map(symbol => view.simplifySymbol(symbol, this.system.trackerBar))
|
|
160
|
-
.filter(event => event !== null)
|
|
161
|
-
.filter(inScope);
|
|
162
|
-
if (this.negotiatedEvents.length === 0) {
|
|
163
|
-
this.midiEvents = [];
|
|
164
|
-
this.curves = [];
|
|
165
|
-
return this.midiEvents;
|
|
166
|
-
}
|
|
167
|
-
this.applyConstraints(view);
|
|
168
|
-
const performance = this.system.perform(this.negotiatedEvents, this.options, propertiesOf(view, version));
|
|
169
|
-
this.curves = performance.curves;
|
|
170
|
-
const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
|
|
171
|
-
const origin = skipToFirstNote ? earliestOf(onsets) : seconds(0);
|
|
172
|
-
this.midiEvents = performance.events
|
|
173
|
-
.map(event => ({ ...event, at: subtract(event.at, origin) }))
|
|
174
|
-
.filter(event => event.at >= 0)
|
|
175
|
-
.sort((a, b) => a.at - b.at);
|
|
176
|
-
return this.midiEvents;
|
|
177
|
-
}
|
|
178
|
-
findEventsPerforming(id) {
|
|
179
|
-
return this.midiEvents.filter(event => event.performs.id === id);
|
|
180
|
-
}
|
|
181
|
-
asMIDI() {
|
|
182
|
-
const TICKS_PER_SECOND = 1000;
|
|
183
|
-
const events = [];
|
|
184
|
-
this.midiEvents.sort((a, b) => a.at - b.at);
|
|
185
|
-
const text = (text, deltaTime = 0) => ({ type: 'meta', subtype: 'text', text, deltaTime });
|
|
186
|
-
const controller = (controllerType, value, deltaTime = 0) => ({ type: 'channel', subtype: 'controller', controllerType, value, deltaTime, channel: 0 });
|
|
187
|
-
const controllerOf = (event) => event.type === 'damper' ? MIDIControlEvents.SUSTAIN : MIDIControlEvents.SOFT_PEDAL;
|
|
188
|
-
events.push(text(`linked-rolls (${this.system.name})`));
|
|
189
|
-
if (this.source) {
|
|
190
|
-
events.push(text(this.source));
|
|
191
|
-
}
|
|
192
|
-
for (const [key, value] of Object.entries(this.options)) {
|
|
193
|
-
events.push(text(`${key}=${typeof value === 'object' ? JSON.stringify(value) : value}`));
|
|
194
|
-
}
|
|
195
|
-
events.push({
|
|
196
|
-
type: 'meta',
|
|
197
|
-
subtype: 'setTempo',
|
|
198
|
-
microsecondsPerBeat: 1000000,
|
|
199
|
-
deltaTime: 0
|
|
200
|
-
});
|
|
201
|
-
// both pedals start at rest
|
|
202
|
-
events.push(controller(MIDIControlEvents.SUSTAIN, 0), controller(MIDIControlEvents.SOFT_PEDAL, 0));
|
|
203
|
-
// a pedal step is labelled with its command only where that
|
|
204
|
-
// command changes, so the file is not swamped with labels
|
|
205
|
-
const lastCause = {};
|
|
206
|
-
let currentTick = 0;
|
|
207
|
-
for (const event of this.midiEvents) {
|
|
208
|
-
const tick = Math.round(event.at * TICKS_PER_SECOND);
|
|
209
|
-
const deltaTime = tick - currentTick;
|
|
210
|
-
currentTick = tick;
|
|
211
|
-
if (event.type === 'noteOn') {
|
|
212
|
-
events.push(text(event.performs.id, deltaTime));
|
|
213
|
-
events.push({
|
|
214
|
-
type: 'channel',
|
|
215
|
-
subtype: 'noteOn',
|
|
216
|
-
noteNumber: event.pitch,
|
|
217
|
-
velocity: +event.velocity.toFixed(0),
|
|
218
|
-
deltaTime: 0,
|
|
219
|
-
channel: 0
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
else if (event.type === 'noteOff') {
|
|
223
|
-
events.push({
|
|
224
|
-
type: 'channel',
|
|
225
|
-
subtype: 'noteOff',
|
|
226
|
-
noteNumber: event.pitch,
|
|
227
|
-
velocity: 127,
|
|
228
|
-
deltaTime,
|
|
229
|
-
channel: 0
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
const labelled = lastCause[event.type] === event.performs.id;
|
|
234
|
-
lastCause[event.type] = event.performs.id;
|
|
235
|
-
if (!labelled) {
|
|
236
|
-
events.push(text(event.performs.id, deltaTime));
|
|
237
|
-
}
|
|
238
|
-
events.push(controller(controllerOf(event), event.value, labelled ? deltaTime : 0));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
header: {
|
|
243
|
-
ticksPerBeat: TICKS_PER_SECOND,
|
|
244
|
-
formatType: 0,
|
|
245
|
-
trackCount: 1
|
|
246
|
-
},
|
|
247
|
-
tracks: [events]
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|