linked-rolls 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +48 -1
  2. package/lib/Assumption.d.ts +124 -0
  3. package/lib/Assumption.js +34 -0
  4. package/lib/Collation.d.ts +11 -0
  5. package/lib/ConditionState.d.ts +8 -3
  6. package/lib/ConditionState.js +0 -5
  7. package/lib/Edit.d.ts +44 -14
  8. package/lib/Edit.js +2 -95
  9. package/lib/Edition.d.ts +144 -4
  10. package/lib/EditionView.d.ts +51 -0
  11. package/lib/EditionView.js +294 -0
  12. package/lib/Emulation.d.ts +21 -68
  13. package/lib/Emulation.js +94 -386
  14. package/lib/Feature.d.ts +137 -16
  15. package/lib/Feature.js +9 -1
  16. package/lib/Plan.d.ts +190 -0
  17. package/lib/Plan.js +555 -0
  18. package/lib/ReproducingSystem.d.ts +80 -0
  19. package/lib/RollCopy.d.ts +265 -24
  20. package/lib/RollCopy.js +276 -215
  21. package/lib/Symbol.d.ts +70 -37
  22. package/lib/Symbol.js +2 -27
  23. package/lib/TrackCalibration.d.ts +33 -0
  24. package/lib/TrackCalibration.js +14 -0
  25. package/lib/TrackerBar.d.ts +52 -5
  26. package/lib/TrackerBar.js +70 -49
  27. package/lib/Version.d.ts +32 -33
  28. package/lib/Version.js +3 -209
  29. package/lib/alignFeatures.d.ts +3 -2
  30. package/lib/alignFeatures.js +5 -4
  31. package/lib/asJsonLd.d.ts +1 -1
  32. package/lib/asJsonLd.js +13 -26
  33. package/lib/importJsonLd.d.ts +0 -1
  34. package/lib/importJsonLd.js +9 -72
  35. package/lib/index.d.ts +7 -3
  36. package/lib/index.js +7 -3
  37. package/lib/schema.json +1739 -0
  38. package/lib/spec/context.json +125 -144
  39. package/lib/systems/welteT100.d.ts +55 -0
  40. package/lib/systems/welteT100.js +191 -0
  41. package/lib/utils.d.ts +29 -0
  42. package/lib/validate.d.ts +3 -0
  43. package/lib/validate.js +10 -0
  44. package/package.json +41 -8
  45. package/lib/Condition.d.ts +0 -10
  46. package/lib/EditorialAssumption.d.ts +0 -67
  47. package/lib/EditorialAssumption.js +0 -26
  48. package/lib/Measurement.d.ts +0 -9
  49. package/lib/PlaceTimeConversion.d.ts +0 -65
  50. package/lib/PlaceTimeConversion.js +0 -175
  51. package/lib/RollEvent.d.ts +0 -76
  52. package/lib/RollEvent.js +0 -3
  53. package/lib/Stage.d.ts +0 -37
  54. package/lib/Stage.js +0 -165
  55. package/lib/Transcription.d.ts +0 -7
  56. package/lib/Transcription.js +0 -9
  57. package/lib/WithId.d.ts +0 -3
  58. package/lib/WithId.js +0 -1
  59. package/lib/alignRolls.d.ts +0 -7
  60. package/lib/alignRolls.js +0 -49
  61. package/lib/alignSymbols.d.ts +0 -7
  62. package/lib/alignSymbols.js +0 -49
  63. package/lib/aton/AtonParser.test.d.ts +0 -1
  64. package/lib/aton/AtonParser.test.js +0 -16
  65. package/lib/build-schema.cjs +0 -113
  66. /package/lib/{Condition.js → ReproducingSystem.js} +0 -0
  67. /package/lib/{Measurement.js → utils.js} +0 -0
@@ -0,0 +1,294 @@
1
+ import { idOf, idsOf } from "./Assumption";
2
+ export const getAt = (path, obj) => {
3
+ let node = obj;
4
+ for (const key of path) {
5
+ if (node == null)
6
+ return undefined;
7
+ node = node[key];
8
+ }
9
+ return node;
10
+ };
11
+ const referenceKeys = new Set([
12
+ 'delete',
13
+ 'comprehends',
14
+ 'motivation'
15
+ ]);
16
+ export class EditionView {
17
+ /**
18
+ * Dimensions cache: one frequent operation is to find the average
19
+ * dimensions of a symbol based on its carriers. This cache stores
20
+ * the computed dimensions for reuse.
21
+ */
22
+ //private readonly dimensionsCache: Map<string, { horizontal: HorizontalSpan, vertical: VerticalSpan }> = new Map()
23
+ constructor(edition) {
24
+ Object.defineProperty(this, "edition", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ /**
31
+ * Map from id to object
32
+ */
33
+ Object.defineProperty(this, "byId", {
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true,
37
+ value: new Map()
38
+ });
39
+ /**
40
+ * Map from id to its path within the edition
41
+ */
42
+ Object.defineProperty(this, "paths", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: new Map()
47
+ });
48
+ /**
49
+ * Map from id to paths where it is referenced
50
+ */
51
+ Object.defineProperty(this, "links", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: new Map()
56
+ });
57
+ this.edition = edition;
58
+ this.indexObjects();
59
+ //this.buildDimensionsCache()
60
+ }
61
+ // private buildDimensionsCache() {
62
+ // for (const version of this.edition.versions) {
63
+ // for (const symbol of version.edits.flatMap(edit => edit.insert || [])) {
64
+ // const dim = this.dimensionOf(symbol)
65
+ // if (dim) {
66
+ // this.dimensionsCache.set(symbol.id, dim)
67
+ // }
68
+ // }
69
+ // }
70
+ // }
71
+ atPath(path) {
72
+ const node = getAt(path, this.edition);
73
+ return node || null;
74
+ }
75
+ indexObjects() {
76
+ const visited = new WeakSet();
77
+ const isObject = (v) => v !== null && typeof v === "object";
78
+ const traverse = (node, path) => {
79
+ if (!isObject(node))
80
+ return;
81
+ if (visited.has(node))
82
+ return;
83
+ visited.add(node);
84
+ const anyNode = node;
85
+ if (typeof anyNode.id === "string") {
86
+ if (Object.keys(anyNode).filter(k => k !== '@annotation').length === 1) {
87
+ // this is a reference-only object, store link
88
+ if (!this.links.has(anyNode.id)) {
89
+ this.links.set(anyNode.id, new Set());
90
+ }
91
+ this.links.get(anyNode.id).add([...path, 'id']);
92
+ }
93
+ else {
94
+ if (!this.byId.has(anyNode.id)) {
95
+ this.byId.set(anyNode.id, node);
96
+ this.paths.set(anyNode.id, [...path]);
97
+ }
98
+ }
99
+ }
100
+ Object
101
+ .keys(anyNode)
102
+ .filter(k => referenceKeys.has(k))
103
+ .forEach(key => {
104
+ const ref = anyNode[key];
105
+ if (typeof ref === "string") {
106
+ if (!this.links.has(ref)) {
107
+ this.links.set(ref, new Set());
108
+ }
109
+ this.links.get(ref).add([...path, key]);
110
+ }
111
+ else if (Array.isArray(ref)) {
112
+ for (const r of ref) {
113
+ if (typeof r === "string") {
114
+ if (!this.links.has(anyNode.id)) {
115
+ this.links.set(anyNode.id, new Set());
116
+ }
117
+ this.links.get(anyNode.id).add([...path, key]);
118
+ }
119
+ }
120
+ }
121
+ });
122
+ if (Array.isArray(node)) {
123
+ for (let i = 0; i < node.length; i++) {
124
+ traverse(node[i], [...path, i]);
125
+ }
126
+ }
127
+ else {
128
+ for (const key of Object.keys(node)) {
129
+ traverse(anyNode[key], [...path, key]);
130
+ }
131
+ }
132
+ };
133
+ traverse(this.edition, []);
134
+ }
135
+ get(anyId) {
136
+ return this.byId.get(anyId);
137
+ }
138
+ getAll(anyIds) {
139
+ return anyIds.map(id => this.get(id)).filter((v) => !!v);
140
+ }
141
+ getPath(anyId) {
142
+ return this.paths.get(anyId);
143
+ }
144
+ linksTo(anyId) {
145
+ return Array.from(this.links.get(anyId) || []);
146
+ }
147
+ travelUp(versionId, callback) {
148
+ const v = this.get(versionId);
149
+ if (!v)
150
+ return;
151
+ callback(v);
152
+ if (v.basedOn) {
153
+ this.travelUp(idOf(v.basedOn), callback);
154
+ }
155
+ }
156
+ carriersOf(symbol) {
157
+ return this.getAll(idsOf(symbol.carriers));
158
+ }
159
+ predecessorOf(versionId) {
160
+ const v = this.get(versionId);
161
+ if (!v?.basedOn)
162
+ return;
163
+ return this.get(idOf(v.basedOn));
164
+ }
165
+ dimensionOf(symbol) {
166
+ // if (this.dimensionsCache.has(symbol.id)) {
167
+ // return this.dimensionsCache.get(symbol.id);
168
+ // }
169
+ const carriers = this.getAll(idsOf(symbol.carriers));
170
+ if (carriers.length === 0) {
171
+ return;
172
+ }
173
+ const horizontalFrom = carriers.reduce((hAcc, h) => hAcc + h.horizontal.from, 0) / carriers.length;
174
+ const horizontalTo = carriers.reduce((hAcc, h) => hAcc + h.horizontal.to, 0) / carriers.length;
175
+ const verticalFrom = carriers.reduce((vAcc, v) => vAcc + v.vertical.from, 0) / carriers.length;
176
+ const definedVerticalTo = carriers.filter(v => v.vertical.to !== undefined);
177
+ let verticalTo = undefined;
178
+ if (definedVerticalTo.length > 0) {
179
+ verticalTo = definedVerticalTo
180
+ .reduce((vAcc, v) => vAcc + v.vertical.to, 0) / definedVerticalTo.length;
181
+ }
182
+ return {
183
+ horizontal: { unit: 'mm', from: horizontalFrom, to: horizontalTo },
184
+ vertical: { unit: 'track', from: verticalFrom, to: verticalTo }
185
+ };
186
+ }
187
+ snapshot(versionId) {
188
+ const snapshot = [];
189
+ const toDelete = [];
190
+ this.travelUp(versionId, s => {
191
+ snapshot.push(...s.edits.flatMap(edit => edit.insert || []));
192
+ // as we travel further up, remove symbols that are
193
+ // deleted in the versions further down
194
+ const deleted = [];
195
+ for (const toRemove of toDelete) {
196
+ const index = snapshot.findIndex(s => s.id === toRemove);
197
+ if (index !== -1) {
198
+ snapshot.splice(index, 1);
199
+ deleted.push(toRemove);
200
+ }
201
+ }
202
+ for (const del of deleted) {
203
+ toDelete.splice(toDelete.indexOf(del), 1);
204
+ }
205
+ // collect symbols that are deleted in the current version
206
+ toDelete.push(...s.edits.flatMap(edit => edit.delete || []));
207
+ });
208
+ return snapshot.sort((a, b) => {
209
+ const aDimension = this.dimensionOf(a);
210
+ const bDimension = this.dimensionOf(b);
211
+ return (aDimension?.horizontal.from || 0) - (bDimension?.horizontal.from || 0);
212
+ });
213
+ }
214
+ isCollatable(symbolA, symbolB, tolerance = {
215
+ toleranceEnd: 5,
216
+ toleranceStart: 5
217
+ }) {
218
+ // two symbols are collatible if they share the same
219
+ // symbol characteristics (pitch, expression type etc.)
220
+ // and occur in the same horizontal position.
221
+ if (symbolA.type === 'note' && symbolB.type === 'note') {
222
+ if (symbolA.pitch !== symbolB.pitch)
223
+ return false;
224
+ }
225
+ else if (symbolA.type === 'expression' && symbolB.type === 'expression') {
226
+ if (symbolA.expressionType !== symbolB.expressionType)
227
+ return false;
228
+ if (symbolA.scope !== symbolB.scope)
229
+ return false;
230
+ }
231
+ const dimensionA = this.dimensionOf(symbolA);
232
+ const dimensionB = this.dimensionOf(symbolB);
233
+ if (!dimensionA || !dimensionB)
234
+ return false;
235
+ const distanceStart = Math.abs(dimensionA.horizontal.from - dimensionB.horizontal.from);
236
+ const distanceEnd = Math.abs(dimensionA.horizontal.to - dimensionB.horizontal.to);
237
+ if (distanceStart > tolerance.toleranceStart
238
+ || distanceEnd > tolerance.toleranceEnd) {
239
+ // the symbols are too far apart to be collated
240
+ return false;
241
+ }
242
+ return true;
243
+ }
244
+ /**
245
+ * Assigns a generation (depth) to every node.
246
+ */
247
+ withGenerations() {
248
+ const memo = new Map();
249
+ const inStack = new Set();
250
+ const computeGeneration = (id) => {
251
+ if (memo.has(id))
252
+ return memo.get(id);
253
+ if (inStack.has(id)) {
254
+ throw new Error(`Cycle detected involving node '${id}'. Check parentId links.`);
255
+ }
256
+ const node = this.get(id);
257
+ if (!node) {
258
+ memo.set(id, 0);
259
+ return 0;
260
+ }
261
+ inStack.add(id);
262
+ let gen;
263
+ const basedOn = node.basedOn && idOf(node.basedOn);
264
+ if (basedOn === undefined) {
265
+ gen = 0; // root
266
+ }
267
+ else if (!this.get(basedOn)) {
268
+ // Orphaned parent reference — treat boundary as root
269
+ gen = 0;
270
+ }
271
+ else {
272
+ gen = 1 + computeGeneration(basedOn);
273
+ }
274
+ inStack.delete(id);
275
+ memo.set(id, gen);
276
+ return gen;
277
+ };
278
+ // Compute for all nodes (order doesn’t matter)
279
+ const withGen = this.edition.versions.map(n => ({
280
+ ...n,
281
+ generation: computeGeneration(n.id),
282
+ }));
283
+ return withGen;
284
+ }
285
+ simplifySymbol(symbol) {
286
+ const dim = this.dimensionOf(symbol);
287
+ if (!symbol.carriers.length || !dim)
288
+ return null;
289
+ return {
290
+ ...symbol,
291
+ ...dim
292
+ };
293
+ }
294
+ }
@@ -1,76 +1,29 @@
1
1
  import { MidiFile } from "midifile-ts";
2
- import { AnySymbol, Expression, Note } from "./Symbol";
3
- import { PlaceTimeConversion } from "./PlaceTimeConversion";
2
+ import { EditionView } from "./EditionView";
4
3
  import { Version } from "./Version";
5
- import { RollFeature } from "./Feature";
6
- import { RollTempo } from "./Edition";
7
- interface PerformedRollFeature<T> {
8
- type: T;
9
- performs: (AnySymbol | NegotiatedEvent);
10
- at: number;
11
- }
12
- interface PerformedNoteEvent<T> extends PerformedRollFeature<T> {
13
- pitch: number;
14
- velocity: number;
15
- }
16
- export interface PerformedNoteOnEvent extends PerformedNoteEvent<'noteOn'> {
17
- }
18
- export interface PerformedNoteOffEvent extends PerformedNoteEvent<'noteOff'> {
19
- }
20
- export interface PerformedSustainPedalOnEvent extends PerformedRollFeature<'sustainPedalOn'> {
21
- }
22
- export interface PerformedSustainPedalOffEvent extends PerformedRollFeature<'sustainPedalOff'> {
23
- }
24
- export interface PerformedSoftPedalOnEvent extends PerformedRollFeature<'softPedalOn'> {
25
- }
26
- export interface PerformedSoftPedalOffEvent extends PerformedRollFeature<'softPedalOff'> {
27
- }
28
- export type AnyPerformedRollFeature = PerformedNoteOnEvent | PerformedNoteOffEvent | PerformedSustainPedalOnEvent | PerformedSustainPedalOffEvent | PerformedSoftPedalOnEvent | PerformedSoftPedalOffEvent;
29
- type AssumedPhysicalTimeSpan = {
30
- assumedPhysicalTime?: [number, number];
31
- };
32
- export type NegotiatedEvent = Omit<Note | Expression, 'carriers'> & Pick<RollFeature, 'horizontal' | 'vertical'> & AssumedPhysicalTimeSpan;
33
- export type EmulationOptions = {
34
- welte_p: number;
35
- welte_f: number;
36
- welte_mf: number;
37
- welte_loud: number;
38
- slow_decay_rate: number;
39
- fastC_decay_rate: number;
40
- fastD_decay_rate: number;
41
- trackerBarDiameter: number;
42
- punchExtensionFraction: number;
43
- slow_step?: number;
44
- fastC_step?: number;
45
- fastD_step?: number;
46
- division: number;
4
+ import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "./ReproducingSystem";
5
+ export type EmulationScope = {
6
+ /** Only notes whose onset lies within this span of the roll, in mm, are played. */
7
+ range?: [number, number];
8
+ /** Start the clock at the first note rather than at the beginning of the roll. */
9
+ skipToFirstNote?: boolean;
47
10
  };
48
- export declare class Emulation {
49
- placeTimeConversion: PlaceTimeConversion;
50
- midiEvents: (AnyPerformedRollFeature)[];
11
+ /**
12
+ * A version of the edition, performed: the symbols are negotiated into
13
+ * placed events, the reproducing system plays them, and the result goes
14
+ * out as MIDI in which every note and pedal step is labelled with the
15
+ * symbol it performs.
16
+ */
17
+ export declare class Emulation<Options extends object> {
18
+ readonly system: ReproducingSystem<Options>;
19
+ options: Options;
20
+ midiEvents: AnyPerformedRollFeature[];
51
21
  negotiatedEvents: NegotiatedEvent[];
52
- trebleVelocities: number[];
53
- bassVelocities: number[];
54
- startTempo?: number;
55
- endTempo?: number;
22
+ curves: readonly EmulatedCurve[];
56
23
  source?: string;
57
- options: EmulationOptions;
58
- constructor(options?: EmulationOptions);
59
- private findRollTempo;
60
- private assignPhysicalTime;
61
- private applyTrackerBarExtension;
62
- private convertEventsToMIDI;
63
- emulateFromRoll(events: (Note | Expression)[]): AnyPerformedRollFeature[];
64
- emulateVersion(version: Version, rollTempo?: RollTempo, skipToFirstNote?: boolean): AnyPerformedRollFeature[];
65
- /**
66
- * The original code of the calculateVelocities() method,
67
- * written by Kitty Shi and Craig Stuart Sapp,
68
- * was taken from the midi2exp project (https://github.com/pianoroll/midi2exp)
69
- * and adapted to the different data representation.
70
- */
71
- calculateVelocities(scope: 'treble' | 'bass'): void;
72
- private insertNote;
24
+ constructor(system: ReproducingSystem<Options>, options?: Options);
25
+ applyConstraints(): void;
26
+ emulateVersion(version: Version, view: EditionView, { range, skipToFirstNote }?: EmulationScope): AnyPerformedRollFeature[];
73
27
  findEventsPerforming(id: string): AnyPerformedRollFeature[];
74
28
  asMIDI(): MidiFile;
75
29
  }
76
- export {};