linked-rolls 0.56.0 → 0.58.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 -361
- package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
- package/lib/{constraints.js → analysis/constraints.js} +9 -8
- 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} +4 -4
- 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} +6 -6
- 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} +22 -51
- 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.d.ts → io/migrate.d.ts} +9 -0
- package/lib/{migrate.js → io/migrate.js} +97 -54
- package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
- package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
- package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +1 -2
- 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} +3 -1
- package/lib/{Feature.js → model/Feature.js} +2 -0
- 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} +5 -12
- 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} +11 -3
- 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 -4
- package/lib/schema.json +11 -151
- package/lib/spec/context.json +2 -12
- 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} +38 -48
- package/lib/{EditionView.js → view/EditionView.js} +90 -96
- 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 -1081
- package/lib/importJsonLd.js +0 -62
- /package/lib/{Agent.js → model/Agent.js} +0 -0
- /package/lib/{Assumption.js → model/Assumption.js} +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/{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,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pedalBrushing, pedalDefaults, ROWS_PER_MM, WELTE_T98_SPOOL, } from "welte-mignon-emulator";
|
|
2
2
|
import { aperturePorts, CHAIN_GAP_MM, DERIVED, GENUINE, instrumentT98Of, labelOf, nuanceOf, PUNCH_T98_MM, pneumaticT98Model, rewindAt, runPedals, sforzandoPianoLift, STARTING_VALUES, TRACKER_BORE_T98_MM, } from "welte-mignon-emulator/t98";
|
|
3
3
|
import { welteT98 } from "./bar.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { mm, seconds, track } from "../../model/Quantity.js";
|
|
5
|
+
import { defaultVelocityMap } from "../velocity.js";
|
|
6
|
+
import { performWelte, sameParameters } from "../welte.js";
|
|
7
|
+
export { secondsAt } from "../welte.js";
|
|
7
8
|
/**
|
|
8
9
|
* The instruments the emulator offers for the green Welte, in three groups that
|
|
9
10
|
* answer three different questions and must not be listed as one.
|
|
@@ -37,8 +38,6 @@ export const instrumentNames = [
|
|
|
37
38
|
...Object.keys(DERIVED).map(derived => ({ derived })),
|
|
38
39
|
{ unfitted: 'starting-values' }
|
|
39
40
|
];
|
|
40
|
-
const sameParameters = (a, b) => Object.keys(a).length === Object.keys(b).length
|
|
41
|
-
&& Object.entries(a).every(([name, value]) => b[name] === value);
|
|
42
41
|
/** The instrument a pair of nuancing constants belongs to, if it is one. */
|
|
43
42
|
export const instrumentNameOf = (nuance) => instrumentNames.find(name => {
|
|
44
43
|
const instrument = instrumentT98Of(name);
|
|
@@ -86,98 +85,10 @@ const CODES = {
|
|
|
86
85
|
SoftPedal: 'hammerRail'
|
|
87
86
|
};
|
|
88
87
|
const isWelteT98ExpressionType = (type) => Object.hasOwn(CODES, type);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const isExpression = (event) => event.type === 'expression';
|
|
94
|
-
const paperOf = (toOwnPaper) => ({
|
|
95
|
-
rowOf: place => place * toOwnPaper * ROWS_PER_MM,
|
|
96
|
-
placeOfRow: row => mm(row / (toOwnPaper * ROWS_PER_MM)),
|
|
97
|
-
paperOfRow: row => mm(row / ROWS_PER_MM)
|
|
98
|
-
});
|
|
99
|
-
/** When the spool brings a place on the roll to the tracker bar. */
|
|
100
|
-
export const secondsAt = (spool, place) => seconds(paperSeconds(spool, inCentimeters(place)));
|
|
101
|
-
const halfOf = (note, division) => note.vertical.from >= division ? 'treble' : 'bass';
|
|
102
|
-
const readingOf = (paper) => (event) => {
|
|
103
|
-
const control = codeOf(event.expressionType);
|
|
104
|
-
if (!control)
|
|
105
|
-
return undefined;
|
|
106
|
-
const half = event.scope;
|
|
107
|
-
return {
|
|
108
|
-
event,
|
|
109
|
-
punch: {
|
|
110
|
-
half,
|
|
111
|
-
control,
|
|
112
|
-
rowOn: paper.rowOf(event.horizontal.from),
|
|
113
|
-
rowOff: paper.rowOf(event.horizontal.to)
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
|
|
118
|
-
/**
|
|
119
|
-
* One sample per row of the scan the constants were fitted on, from the
|
|
120
|
-
* beginning of the roll to a little past the last hole.
|
|
121
|
-
*/
|
|
122
|
-
const gridOver = (events, spool, paper) => {
|
|
123
|
-
const last = mm(events.reduce((furthest, event) => Math.max(furthest, event.horizontal.to), 0));
|
|
124
|
-
const length = Math.ceil(paper.rowOf(last) + RUN_OUT * ROWS_PER_MM) + 1;
|
|
125
|
-
const times = new Float64Array(length).map((_, row) => secondsAt(spool, paper.paperOfRow(row)));
|
|
126
|
-
return new Grid(0, times);
|
|
127
|
-
};
|
|
128
|
-
const nuanceCurves = (grid, ports, samples, options) => {
|
|
129
|
-
const instrument = `Welte-Mignon T-98, ${labelOf(options.instrument)}`;
|
|
130
|
-
const curveOf = (half) => {
|
|
131
|
-
const params = options.nuance[half];
|
|
132
|
-
const output = pneumaticT98Model.run({ grid, half, ports }, params);
|
|
133
|
-
const travel = travelBetweenRails(output, params);
|
|
134
|
-
const hook = clamp(mezzoforteTravel(params), 0.01, 0.99);
|
|
135
|
-
return {
|
|
136
|
-
...samples,
|
|
137
|
-
name: half,
|
|
138
|
-
kind: 'dynamics',
|
|
139
|
-
instrument,
|
|
140
|
-
travel,
|
|
141
|
-
velocity: travel.map(value => velocityOf(value, hook, options.velocity))
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
return { bass: curveOf('bass'), treble: curveOf('treble') };
|
|
145
|
-
};
|
|
146
|
-
const pedalCurves = (grid, ports, samples, options) => {
|
|
147
|
-
const travel = runPedals({ grid, ports }, options.pedals);
|
|
148
|
-
return {
|
|
149
|
-
damper: { ...samples, name: 'damper', kind: 'pedal', travel: travel.damper },
|
|
150
|
-
hammerRail: { ...samples, name: 'hammerRail', kind: 'pedal', travel: travel.hammerRail }
|
|
151
|
-
};
|
|
152
|
-
};
|
|
153
|
-
const performNotes = (events, grid, nuance, options, paper) => events
|
|
154
|
-
.filter(isNote)
|
|
155
|
-
.flatMap((note) => {
|
|
156
|
-
const curve = nuance[halfOf(note, options.division)];
|
|
157
|
-
const velocity = curve.velocity[grid.indexOfRow(paper.rowOf(note.horizontal.from))];
|
|
158
|
-
return [
|
|
159
|
-
{ type: 'noteOn', performs: note, pitch: note.pitch, velocity, at: secondsAt(options.spool, note.horizontal.from) },
|
|
160
|
-
{ type: 'noteOff', performs: note, pitch: note.pitch, velocity: 127, at: secondsAt(options.spool, note.horizontal.to) }
|
|
161
|
-
];
|
|
162
|
-
});
|
|
163
|
-
/**
|
|
164
|
-
* The travel of one pedal as controller steps, each attributed to the last
|
|
165
|
-
* perforation of that pedal the tracker bar has reached.
|
|
166
|
-
*/
|
|
167
|
-
const performPedal = (type, curve, grid, readings, mode) => {
|
|
168
|
-
if (readings.length === 0)
|
|
169
|
-
return [];
|
|
170
|
-
const ordered = readings.toSorted((a, b) => a.punch.rowOn - b.punch.rowOn);
|
|
171
|
-
const causeOf = (row) => ordered[Math.max(partitionPoint(ordered, reading => reading.punch.rowOn <= row) - 1, 0)].event;
|
|
172
|
-
return levelChanges(curve.travel, { mode })
|
|
173
|
-
.filter(change => change.index > 0)
|
|
174
|
-
.map(change => ({
|
|
175
|
-
type,
|
|
176
|
-
performs: causeOf(grid.rowAt(change.index)),
|
|
177
|
-
value: change.value,
|
|
178
|
-
at: seconds(curve.seconds[change.index])
|
|
179
|
-
}));
|
|
180
|
-
};
|
|
88
|
+
/** The punch a T-98 expression is read as: its control, held for as long as it runs. */
|
|
89
|
+
const punchOf = (event, rows) => isWelteT98ExpressionType(event.expressionType)
|
|
90
|
+
? { half: event.scope, control: CODES[event.expressionType], ...rows }
|
|
91
|
+
: undefined;
|
|
181
92
|
/**
|
|
182
93
|
* The row at which the Abstellbalg trips and the roll goes back, or the end of
|
|
183
94
|
* the grid where it never does. The shut-off hangs on the bass sforzando-piano
|
|
@@ -199,36 +110,24 @@ const cutCurve = (curve, rows) => ({
|
|
|
199
110
|
...(curve.kind === 'dynamics' ? { velocity: curve.velocity.slice(0, rows) } : {})
|
|
200
111
|
});
|
|
201
112
|
/**
|
|
202
|
-
* The
|
|
203
|
-
*
|
|
113
|
+
* The shared Welte performance, cut off where the rewind takes hold unless
|
|
114
|
+
* the options ignore it.
|
|
204
115
|
*/
|
|
205
116
|
const perform = (events, options, roll) => {
|
|
206
|
-
const paper = paperOf(roll.toOwnPaper ?? 1);
|
|
207
|
-
const readings = events
|
|
208
|
-
.filter(isExpression)
|
|
209
|
-
.map(readingOf(paper))
|
|
210
|
-
.filter((reading) => reading !== undefined);
|
|
211
|
-
const grid = gridOver(events, options.spool, paper);
|
|
212
|
-
const geometry = geometryInMm(roll.punchDiameter ?? options.punchDiameter, options.trackerBore);
|
|
213
117
|
const gap = options.chainGap * ROWS_PER_MM;
|
|
214
|
-
const ports
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const readingsOf = (control) => readings.filter(reading => reading.punch.control === control);
|
|
118
|
+
const { grid, ports, events: performed, curves } = performWelte(events, options, roll, {
|
|
119
|
+
punchOf,
|
|
120
|
+
portsOf: (grid, punches, geometry) => aperturePorts(grid, punches, geometry, gap),
|
|
121
|
+
model: pneumaticT98Model,
|
|
122
|
+
runPedals,
|
|
123
|
+
instrument: `Welte-Mignon T-98, ${labelOf(options.instrument)}`
|
|
124
|
+
});
|
|
222
125
|
const stops = rewindRow(grid, ports, options);
|
|
223
126
|
const until = seconds(grid.seconds[Math.min(stops, grid.length - 1)]);
|
|
224
127
|
const rows = Math.min(stops + 1, grid.length);
|
|
225
128
|
return {
|
|
226
|
-
events: truncated(
|
|
227
|
-
|
|
228
|
-
...performPedal('damper', pedals.damper, grid, readingsOf('sustainPedal'), options.pedalMode),
|
|
229
|
-
...performPedal('hammerRail', pedals.hammerRail, grid, readingsOf('hammerRail'), options.pedalMode)
|
|
230
|
-
], until),
|
|
231
|
-
curves: [nuance.bass, nuance.treble, pedals.damper, pedals.hammerRail].map(curve => cutCurve(curve, rows))
|
|
129
|
+
events: truncated(performed, until),
|
|
130
|
+
curves: curves.map(curve => cutCurve(curve, rows))
|
|
232
131
|
};
|
|
233
132
|
};
|
|
234
133
|
/**
|
package/lib/validate.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ErrorObject } from "ajv";
|
|
2
|
-
import { Edition } from "./Edition.js";
|
|
2
|
+
import { Edition } from "./model/Edition.js";
|
|
3
3
|
/**
|
|
4
4
|
* Holds a document against the edition schema. `errors` says what the
|
|
5
5
|
* last call found wrong, and is empty where it found nothing.
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
import { Edition } from "
|
|
2
|
-
import { HorizontalSpan,
|
|
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 {
|
|
12
10
|
readonly edition: Edition;
|
|
13
|
-
/**
|
|
14
|
-
* Map from id to object
|
|
15
|
-
*/
|
|
16
|
-
private readonly byId;
|
|
17
11
|
/**
|
|
18
12
|
* Map from id to its path within the edition
|
|
19
13
|
*/
|
|
@@ -24,17 +18,46 @@ export declare class EditionView {
|
|
|
24
18
|
private readonly links;
|
|
25
19
|
/** Built when a copy is first asked for, the walk not recording it. */
|
|
26
20
|
private copiesByFeature?;
|
|
21
|
+
/**
|
|
22
|
+
* One index for each kind of entity, built from where the edition
|
|
23
|
+
* holds it. The index an entity is found in says what it is, so a
|
|
24
|
+
* lookup needs neither a type discriminator nor a cast.
|
|
25
|
+
*/
|
|
26
|
+
private readonly versionsById;
|
|
27
|
+
private readonly copiesById;
|
|
28
|
+
private readonly symbolsById;
|
|
29
|
+
private featuresById?;
|
|
27
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. */
|
|
28
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
|
+
*/
|
|
29
38
|
indexObjects(): void;
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
version(id: string): Readonly<Version> | undefined;
|
|
40
|
+
copy(id: string): Readonly<RollCopy> | undefined;
|
|
41
|
+
symbol(id: string): Readonly<AnySymbol> | undefined;
|
|
42
|
+
/** The symbols under the ids, leaving out an id that names none. */
|
|
43
|
+
symbols(ids: readonly string[]): Readonly<AnySymbol>[];
|
|
44
|
+
/** A feature or a patch on any copy, one a patch bears included. */
|
|
45
|
+
feature(id: string): Readonly<NestedFeature> | undefined;
|
|
46
|
+
/** The features and patches under the ids, leaving out an id that names none. */
|
|
47
|
+
features(ids: readonly string[]): Readonly<NestedFeature>[];
|
|
32
48
|
getPath(anyId: string): Path | undefined;
|
|
33
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
|
+
*/
|
|
34
55
|
travelUp(versionId: string, callback: (version: Readonly<Version>) => void): void;
|
|
35
56
|
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
36
57
|
lineageOf(versionId: string): Readonly<Version>[];
|
|
37
|
-
carriersOf(symbol: AnySymbol): Readonly<
|
|
58
|
+
carriersOf(symbol: AnySymbol): Readonly<NestedFeature>[];
|
|
59
|
+
/** The carriers that state a place of their own, which a feature on a patch does not. */
|
|
60
|
+
placedCarriersOf(symbol: AnySymbol): Readonly<FeatureOrPatch>[];
|
|
38
61
|
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
39
62
|
copyOf(featureId: string): Readonly<RollCopy> | undefined;
|
|
40
63
|
/**
|
|
@@ -46,29 +69,6 @@ export declare class EditionView {
|
|
|
46
69
|
* `mergeFeatures` asks before it reads them as one.
|
|
47
70
|
*/
|
|
48
71
|
actOf(featureId: string): Readonly<ProductionEvent | Modification> | undefined;
|
|
49
|
-
/**
|
|
50
|
-
* How a place on the edition's shared axis relates to the paper of
|
|
51
|
-
* this version: place × factor = millimetres of its own paper.
|
|
52
|
-
*
|
|
53
|
-
* Copies cut for different systems are scaled onto one axis so that
|
|
54
|
-
* they can be collated at all, which leaves a version of another
|
|
55
|
-
* system carrying places in the axis copy's millimetres. A
|
|
56
|
-
* performance needs the paper the roll actually ran on, and the
|
|
57
|
-
* factor is the inverse of the scale `alignCopy` recorded.
|
|
58
|
-
*
|
|
59
|
-
* It is read only from the copies of the version's own system, since
|
|
60
|
-
* under the shared axis a green version's notes are carried by red
|
|
61
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
62
|
-
* scale is put down to its own paper having stretched is left out as
|
|
63
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
64
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
65
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
66
|
-
*/
|
|
67
|
-
toOwnPaperOf(version: Readonly<Version>): number | undefined;
|
|
68
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
69
|
-
speedScalesIn(version: Readonly<Version>): number[];
|
|
70
|
-
/** The copies of the version's own system that carry any of its symbols. */
|
|
71
|
-
copiesOwning(version: Readonly<Version>): Readonly<RollCopy>[];
|
|
72
72
|
/** The version the given one's text is read against, by its principal derivation. */
|
|
73
73
|
predecessorOf(versionId: string): Readonly<Version> | undefined;
|
|
74
74
|
/**
|
|
@@ -100,14 +100,4 @@ export declare class EditionView {
|
|
|
100
100
|
withGenerations(): Array<Version & {
|
|
101
101
|
generation: number;
|
|
102
102
|
}>;
|
|
103
|
-
/**
|
|
104
|
-
* The symbol as a performance needs it: where it lies, and the
|
|
105
|
-
* position the performing bar reads it on.
|
|
106
|
-
*
|
|
107
|
-
* Nothing where that bar reads nothing of it, which is the case a
|
|
108
|
-
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
109
|
-
* green version still inherits cannot be performed on a green
|
|
110
|
-
* machine, and an edit has yet to say what took its place.
|
|
111
|
-
*/
|
|
112
|
-
simplifySymbol(symbol: Note | Expression, bar: TrackerBar): NegotiatedEvent | null;
|
|
113
103
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { 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) {
|
|
@@ -15,6 +14,7 @@ export const getAt = (path, obj) => {
|
|
|
15
14
|
};
|
|
16
15
|
/** Keys under which an object names others by id, singly or in a list. */
|
|
17
16
|
const referenceKeys = ['delete', 'comprehends', 'motivation', 'premises', 'used'];
|
|
17
|
+
const byIdIn = (entities) => new Map(entities.map(entity => [entity.id, entity]));
|
|
18
18
|
const isObject = (v) => v !== null && typeof v === "object";
|
|
19
19
|
/** What a reference may state besides the id: a belief about it, and the tolerance a derivation was collated at. */
|
|
20
20
|
const referenceOwnKeys = new Set(['id', '@annotation', 'collationTolerance']);
|
|
@@ -34,15 +34,6 @@ export class EditionView {
|
|
|
34
34
|
writable: true,
|
|
35
35
|
value: void 0
|
|
36
36
|
});
|
|
37
|
-
/**
|
|
38
|
-
* Map from id to object
|
|
39
|
-
*/
|
|
40
|
-
Object.defineProperty(this, "byId", {
|
|
41
|
-
enumerable: true,
|
|
42
|
-
configurable: true,
|
|
43
|
-
writable: true,
|
|
44
|
-
value: new Map()
|
|
45
|
-
});
|
|
46
37
|
/**
|
|
47
38
|
* Map from id to its path within the edition
|
|
48
39
|
*/
|
|
@@ -68,14 +59,53 @@ export class EditionView {
|
|
|
68
59
|
writable: true,
|
|
69
60
|
value: void 0
|
|
70
61
|
});
|
|
62
|
+
/**
|
|
63
|
+
* One index for each kind of entity, built from where the edition
|
|
64
|
+
* holds it. The index an entity is found in says what it is, so a
|
|
65
|
+
* lookup needs neither a type discriminator nor a cast.
|
|
66
|
+
*/
|
|
67
|
+
Object.defineProperty(this, "versionsById", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: void 0
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(this, "copiesById", {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
writable: true,
|
|
77
|
+
value: void 0
|
|
78
|
+
});
|
|
79
|
+
Object.defineProperty(this, "symbolsById", {
|
|
80
|
+
enumerable: true,
|
|
81
|
+
configurable: true,
|
|
82
|
+
writable: true,
|
|
83
|
+
value: void 0
|
|
84
|
+
});
|
|
85
|
+
Object.defineProperty(this, "featuresById", {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
configurable: true,
|
|
88
|
+
writable: true,
|
|
89
|
+
value: void 0
|
|
90
|
+
});
|
|
71
91
|
this.edition = edition;
|
|
92
|
+
this.versionsById = byIdIn(edition.versions);
|
|
93
|
+
this.copiesById = byIdIn(edition.copies);
|
|
94
|
+
this.symbolsById = byIdIn(edition.versions.flatMap(insertedBy));
|
|
72
95
|
this.indexObjects();
|
|
73
96
|
}
|
|
97
|
+
/** What stands at the path, or null where nothing does. A zero or an empty string stands there as well. */
|
|
74
98
|
atPath(path) {
|
|
75
|
-
|
|
76
|
-
return node || null;
|
|
99
|
+
return getAt(path, this.edition) ?? null;
|
|
77
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
|
+
*/
|
|
78
106
|
indexObjects() {
|
|
107
|
+
this.paths.clear();
|
|
108
|
+
this.links.clear();
|
|
79
109
|
const visited = new WeakSet();
|
|
80
110
|
const link = (id, trail) => {
|
|
81
111
|
const trails = this.links.get(id);
|
|
@@ -89,8 +119,7 @@ export class EditionView {
|
|
|
89
119
|
return;
|
|
90
120
|
if (isReferenceOnly(keys))
|
|
91
121
|
link(record.id, { key: 'id', up: trail });
|
|
92
|
-
else if (!this.
|
|
93
|
-
this.byId.set(record.id, record);
|
|
122
|
+
else if (!this.paths.has(record.id)) {
|
|
94
123
|
this.paths.set(record.id, trail);
|
|
95
124
|
}
|
|
96
125
|
};
|
|
@@ -120,11 +149,27 @@ export class EditionView {
|
|
|
120
149
|
};
|
|
121
150
|
traverse(this.edition, null);
|
|
122
151
|
}
|
|
123
|
-
|
|
124
|
-
return this.
|
|
152
|
+
version(id) {
|
|
153
|
+
return this.versionsById.get(id);
|
|
154
|
+
}
|
|
155
|
+
copy(id) {
|
|
156
|
+
return this.copiesById.get(id);
|
|
157
|
+
}
|
|
158
|
+
symbol(id) {
|
|
159
|
+
return this.symbolsById.get(id);
|
|
160
|
+
}
|
|
161
|
+
/** The symbols under the ids, leaving out an id that names none. */
|
|
162
|
+
symbols(ids) {
|
|
163
|
+
return ids.flatMap(id => this.symbol(id) ?? []);
|
|
125
164
|
}
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
/** A feature or a patch on any copy, one a patch bears included. */
|
|
166
|
+
feature(id) {
|
|
167
|
+
this.featuresById ?? (this.featuresById = byIdIn(this.edition.copies.flatMap(copy => featuresOf(copy).flatMap(withBorneFeatures))));
|
|
168
|
+
return this.featuresById.get(id);
|
|
169
|
+
}
|
|
170
|
+
/** The features and patches under the ids, leaving out an id that names none. */
|
|
171
|
+
features(ids) {
|
|
172
|
+
return ids.flatMap(id => this.feature(id) ?? []);
|
|
128
173
|
}
|
|
129
174
|
getPath(anyId) {
|
|
130
175
|
const trail = this.paths.get(anyId);
|
|
@@ -133,14 +178,19 @@ export class EditionView {
|
|
|
133
178
|
linksTo(anyId) {
|
|
134
179
|
return (this.links.get(anyId) ?? []).map(pathOf);
|
|
135
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
|
+
*/
|
|
136
186
|
travelUp(versionId, callback) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
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));
|
|
144
194
|
}
|
|
145
195
|
}
|
|
146
196
|
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
@@ -150,7 +200,11 @@ export class EditionView {
|
|
|
150
200
|
return lineage;
|
|
151
201
|
}
|
|
152
202
|
carriersOf(symbol) {
|
|
153
|
-
return this.
|
|
203
|
+
return this.features(idsOf(symbol.carriers));
|
|
204
|
+
}
|
|
205
|
+
/** The carriers that state a place of their own, which a feature on a patch does not. */
|
|
206
|
+
placedCarriersOf(symbol) {
|
|
207
|
+
return this.carriersOf(symbol).filter(isPlaced);
|
|
154
208
|
}
|
|
155
209
|
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
156
210
|
copyOf(featureId) {
|
|
@@ -178,51 +232,11 @@ export class EditionView {
|
|
|
178
232
|
return copy.production;
|
|
179
233
|
return copy.modifications.find(act => states(featuresMadeBy(act)));
|
|
180
234
|
}
|
|
181
|
-
/**
|
|
182
|
-
* How a place on the edition's shared axis relates to the paper of
|
|
183
|
-
* this version: place × factor = millimetres of its own paper.
|
|
184
|
-
*
|
|
185
|
-
* Copies cut for different systems are scaled onto one axis so that
|
|
186
|
-
* they can be collated at all, which leaves a version of another
|
|
187
|
-
* system carrying places in the axis copy's millimetres. A
|
|
188
|
-
* performance needs the paper the roll actually ran on, and the
|
|
189
|
-
* factor is the inverse of the scale `alignCopy` recorded.
|
|
190
|
-
*
|
|
191
|
-
* It is read only from the copies of the version's own system, since
|
|
192
|
-
* under the shared axis a green version's notes are carried by red
|
|
193
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
194
|
-
* scale is put down to its own paper having stretched is left out as
|
|
195
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
196
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
197
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
198
|
-
*/
|
|
199
|
-
toOwnPaperOf(version) {
|
|
200
|
-
const scales = this.speedScalesIn(version);
|
|
201
|
-
return scales.length === 1 ? 1 / scales[0] : undefined;
|
|
202
|
-
}
|
|
203
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
204
|
-
speedScalesIn(version) {
|
|
205
|
-
return [...new Set(this.copiesOwning(version)
|
|
206
|
-
.filter(copy => !copy.conditions.some(isPaperStretch))
|
|
207
|
-
.map(copy => copy.measurements.scale)
|
|
208
|
-
.filter((scale) => scale !== undefined && scale > 0))];
|
|
209
|
-
}
|
|
210
|
-
/** The copies of the version's own system that carry any of its symbols. */
|
|
211
|
-
copiesOwning(version) {
|
|
212
|
-
const system = systemIdOf(version.system);
|
|
213
|
-
const carrying = new Set(this.snapshot(version.id)
|
|
214
|
-
.flatMap(symbol => idsOf(symbol.carriers))
|
|
215
|
-
.flatMap(id => {
|
|
216
|
-
const copy = this.copyOf(id);
|
|
217
|
-
return copy ? [copy.id] : [];
|
|
218
|
-
}));
|
|
219
|
-
return this.edition.copies.filter(copy => carrying.has(copy.id) && systemIdOf(copy.production?.system) === system);
|
|
220
|
-
}
|
|
221
235
|
/** The version the given one's text is read against, by its principal derivation. */
|
|
222
236
|
predecessorOf(versionId) {
|
|
223
|
-
const v = this.
|
|
237
|
+
const v = this.version(versionId);
|
|
224
238
|
const principal = v && principalDerivationOf(v);
|
|
225
|
-
return principal && this.
|
|
239
|
+
return principal && this.version(idOf(principal));
|
|
226
240
|
}
|
|
227
241
|
/**
|
|
228
242
|
* Where along the roll the symbol lies, as its carriers put it, or
|
|
@@ -238,7 +252,7 @@ export class EditionView {
|
|
|
238
252
|
* position a semitone away on either bar.
|
|
239
253
|
*/
|
|
240
254
|
placeOf(symbol) {
|
|
241
|
-
const carriers = this.
|
|
255
|
+
const carriers = this.placedCarriersOf(symbol);
|
|
242
256
|
if (carriers.length === 0)
|
|
243
257
|
return;
|
|
244
258
|
return {
|
|
@@ -282,7 +296,7 @@ export class EditionView {
|
|
|
282
296
|
if (inStack.has(id)) {
|
|
283
297
|
throw new Error(`Cycle detected involving node '${id}'. Check parentId links.`);
|
|
284
298
|
}
|
|
285
|
-
const node = this.
|
|
299
|
+
const node = this.version(id);
|
|
286
300
|
if (!node) {
|
|
287
301
|
memo.set(id, 0);
|
|
288
302
|
return 0;
|
|
@@ -294,7 +308,7 @@ export class EditionView {
|
|
|
294
308
|
if (basedOn === undefined) {
|
|
295
309
|
gen = 0; // root
|
|
296
310
|
}
|
|
297
|
-
else if (!this.
|
|
311
|
+
else if (!this.version(basedOn)) {
|
|
298
312
|
// Orphaned parent reference — treat boundary as root
|
|
299
313
|
gen = 0;
|
|
300
314
|
}
|
|
@@ -312,24 +326,4 @@ export class EditionView {
|
|
|
312
326
|
}));
|
|
313
327
|
return withGen;
|
|
314
328
|
}
|
|
315
|
-
/**
|
|
316
|
-
* The symbol as a performance needs it: where it lies, and the
|
|
317
|
-
* position the performing bar reads it on.
|
|
318
|
-
*
|
|
319
|
-
* Nothing where that bar reads nothing of it, which is the case a
|
|
320
|
-
* transfer between systems leaves behind: a red `ForzandoOn` a
|
|
321
|
-
* green version still inherits cannot be performed on a green
|
|
322
|
-
* machine, and an edit has yet to say what took its place.
|
|
323
|
-
*/
|
|
324
|
-
simplifySymbol(symbol, bar) {
|
|
325
|
-
const horizontal = this.placeOf(symbol);
|
|
326
|
-
const position = bar.positionOf(symbol);
|
|
327
|
-
if (!horizontal || position === undefined)
|
|
328
|
-
return null;
|
|
329
|
-
return {
|
|
330
|
-
...symbol,
|
|
331
|
-
horizontal,
|
|
332
|
-
vertical: { unit: 'track', from: position }
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
329
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linked-rolls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.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
|
-
}
|