linked-rolls 0.0.1 → 0.1.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.
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 +259 -24
  20. package/lib/RollCopy.js +232 -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
package/lib/Plan.js ADDED
@@ -0,0 +1,555 @@
1
+ import { getAt } from "./EditionView";
2
+ import { v4 } from "uuid";
3
+ import { asSymbols } from "./RollCopy";
4
+ import { assignReference, idOf } from "./Assumption";
5
+ export const isPlan = (obj) => {
6
+ return obj && typeof obj.build === 'function' && typeof obj.setView === 'function';
7
+ };
8
+ export class BasePlan {
9
+ constructor() {
10
+ Object.defineProperty(this, "view", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: null
15
+ });
16
+ Object.defineProperty(this, "setView", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: (view) => {
21
+ this.view = view;
22
+ }
23
+ });
24
+ }
25
+ }
26
+ /**
27
+ * Walks through the versions. If it finds a symbol that is still
28
+ * part of the tradition (i.e. it is included in the current snapshot)
29
+ * and is equivalent with the given symbol, it will add the feature
30
+ * carrying the symbol to the collated symbol. Otherwise, the
31
+ * given symbol will be added to the current version's insertions.
32
+ *
33
+ * All symbols of the tradition that are not included in the given
34
+ * symbols are considered to be deleted.
35
+ *
36
+ * @param creation
37
+ * @param symbols
38
+ * @returns
39
+ */
40
+ export class ConnectVersions extends BasePlan {
41
+ constructor(childId, parentId, tolerance = {
42
+ toleranceEnd: 5,
43
+ toleranceStart: 5
44
+ }) {
45
+ super();
46
+ Object.defineProperty(this, "childId", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: childId
51
+ });
52
+ Object.defineProperty(this, "parentId", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: parentId
57
+ });
58
+ Object.defineProperty(this, "tolerance", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: tolerance
63
+ });
64
+ }
65
+ build() {
66
+ if (!this.view)
67
+ return [];
68
+ const view = this.view;
69
+ const result = [];
70
+ const parentSnapshot = view.snapshot(this.parentId);
71
+ const childSnapshot = view.snapshot(this.childId);
72
+ const treatedSymbols = [];
73
+ // can the symbol be collated with any of the
74
+ // symbols of the tradition, i.e. the ones
75
+ // included in the current snapshot?
76
+ const insertions = [...childSnapshot];
77
+ for (const symbol of childSnapshot) {
78
+ parentSnapshot
79
+ .filter(toCompare => view.isCollatable(symbol, toCompare, this.tolerance))
80
+ .forEach(parentSymbol => {
81
+ result.push(draft => {
82
+ const symbolPath = view.getPath(parentSymbol.id);
83
+ if (!symbolPath)
84
+ return;
85
+ const correspSymbol = getAt(symbolPath, draft);
86
+ if (!correspSymbol)
87
+ return;
88
+ correspSymbol.carriers.push(...symbol.carriers);
89
+ });
90
+ insertions.splice(insertions.indexOf(symbol), 1);
91
+ treatedSymbols.push(parentSymbol);
92
+ });
93
+ }
94
+ const edits = insertions.map((symbol) => {
95
+ return {
96
+ type: 'edit',
97
+ id: v4(),
98
+ insert: [symbol],
99
+ delete: [],
100
+ };
101
+ });
102
+ const deletions = parentSnapshot.filter(sym => {
103
+ return !treatedSymbols.includes(sym);
104
+ });
105
+ for (const symbol of deletions) {
106
+ edits.push({
107
+ type: 'edit',
108
+ id: v4(),
109
+ insert: [],
110
+ delete: [symbol.id],
111
+ });
112
+ }
113
+ result.push(draft => {
114
+ const v = draft.versions.find(v => v.id === this.childId);
115
+ if (!v)
116
+ return;
117
+ v.edits = edits;
118
+ v.basedOn = assignReference(this.parentId);
119
+ });
120
+ return result;
121
+ }
122
+ }
123
+ /**
124
+ * Walks through the versions. If it finds a symbol that is still
125
+ * part of the tradition (i.e. it is included in the current snapshot)
126
+ * and is equivalent with the given symbol, it will add the feature
127
+ * carrying the symbol to the collated symbol. Otherwise, the
128
+ * given symbol will be added to the current version's insertions.
129
+ *
130
+ * All symbols of the tradition that are not included in the given
131
+ * symbols are considered to be deleted.
132
+ *
133
+ * @param creation
134
+ * @param symbols
135
+ * @returns
136
+ */
137
+ export class CreateVersion extends BasePlan {
138
+ constructor(siglum, copy) {
139
+ super();
140
+ Object.defineProperty(this, "siglum", {
141
+ enumerable: true,
142
+ configurable: true,
143
+ writable: true,
144
+ value: siglum
145
+ });
146
+ Object.defineProperty(this, "copy", {
147
+ enumerable: true,
148
+ configurable: true,
149
+ writable: true,
150
+ value: copy
151
+ });
152
+ }
153
+ build() {
154
+ const view = this.view;
155
+ if (!view)
156
+ return [];
157
+ const result = [];
158
+ result.push(draft => {
159
+ draft.copies.push(this.copy);
160
+ const newVersion = {
161
+ siglum: this.siglum,
162
+ id: v4(),
163
+ edits: asSymbols(this.copy.features).map((symbol) => {
164
+ return {
165
+ type: 'edit',
166
+ id: v4(),
167
+ insert: [symbol],
168
+ delete: [],
169
+ };
170
+ }),
171
+ type: 'edition',
172
+ motivations: []
173
+ };
174
+ draft.versions.push(newVersion);
175
+ });
176
+ return result;
177
+ }
178
+ }
179
+ /**
180
+ * Creates a new version based on the given version
181
+ * calculating the effect of a cover on existing symbols.
182
+
183
+ export class CoverPerforation extends BasePlan {
184
+ constructor(
185
+ private copyId: string,
186
+ private versionId: string,
187
+ private coverDimension: { horizontal: HorizontalSpan, vertical: VerticalSpan },
188
+ private material: string | undefined = undefined,
189
+ ) {
190
+ super()
191
+ }
192
+
193
+ build(): EditionOp[] {
194
+ const view = this.view
195
+ if (!view) return []
196
+
197
+ return [
198
+ (draft: Draft<Edition>): void => {
199
+ const version = draft.versions.find(v => v.id === this.versionId)
200
+ const copy = draft.copies.find(c => c.id === this.copyId)
201
+ if (!version || !copy) return
202
+
203
+ // find perforations in the snapshot that
204
+ // overlap with the cover
205
+ const overlappingSymbols =
206
+ view.snapshot(this.versionId)
207
+ .filter(symbol => {
208
+ const dimension = view.dimensionOf(symbol)
209
+ if (!dimension) return false
210
+
211
+ return (
212
+ (symbol.type === 'note' || symbol.type === 'expression') &&
213
+ overlaps(dimension, this.coverDimension)
214
+ )
215
+ })
216
+
217
+ if (!overlappingSymbols.length) return undefined
218
+
219
+ const deepClone = JSON.parse(JSON.stringify(overlappingSymbols)) as AnySymbol[]
220
+
221
+ for (const symbol of deepClone) {
222
+ //symbol.id = v4();
223
+ const dimension = structuredClone(view.dimensionOf(symbol))
224
+ if (!dimension) continue
225
+
226
+ // check if the cover partially covers the beginning
227
+ if (dimension.horizontal.from >= this.coverDimension.horizontal.from &&
228
+ dimension.horizontal.from <= this.coverDimension.horizontal.to
229
+ ) {
230
+ dimension.horizontal.from = this.coverDimension.horizontal.to
231
+ }
232
+
233
+ // check if the cover partially covers the ending
234
+ if (dimension.horizontal.to >= this.coverDimension.horizontal.from &&
235
+ dimension.horizontal.to <= this.coverDimension.horizontal.to
236
+ ) {
237
+ dimension.horizontal.to = this.coverDimension.horizontal.from
238
+ }
239
+
240
+ const newFeature: RollFeature = {
241
+ ...dimension,
242
+ id: v4(),
243
+ }
244
+
245
+ copy.features.push(newFeature)
246
+ symbol.id = `symbol-${v4().slice(0, 8)}`
247
+ symbol.carriers = [assignReference(newFeature.id)]
248
+ }
249
+
250
+ const coverId = `cover-${v4().slice(0, 8)}`
251
+ copy.features.push({
252
+ ...this.coverDimension,
253
+ id: coverId,
254
+ })
255
+
256
+ const edit: Edit = {
257
+ type: 'edit',
258
+ id: v4(),
259
+ delete: overlappingSymbols.map(s => s.id),
260
+ insert: deepClone,
261
+ intentionOf: [{
262
+ type: 'cover',
263
+ id: v4(),
264
+ note: this.material,
265
+ carriers: [assignReference(coverId)],
266
+ }]
267
+ }
268
+
269
+ draft.versions.push({
270
+ edits: [edit],
271
+ id: v4(),
272
+ basedOn: assignReference(version.id),
273
+ siglum: version.siglum + ' rev',
274
+ type: 'authorised-revision',
275
+ motivations: [
276
+ {
277
+ type: 'motivation',
278
+ note: 'Stanzfehler korrigiert',
279
+ id: v4(),
280
+ }
281
+ ],
282
+ })
283
+ }]
284
+ }
285
+ }
286
+ */
287
+ export class DetachVersion extends BasePlan {
288
+ constructor(versionId) {
289
+ super();
290
+ Object.defineProperty(this, "versionId", {
291
+ enumerable: true,
292
+ configurable: true,
293
+ writable: true,
294
+ value: versionId
295
+ });
296
+ }
297
+ build() {
298
+ if (!this.view)
299
+ return [];
300
+ const snapshot = this.view.snapshot(this.versionId);
301
+ const newEdits = snapshot.map((symbol) => ({
302
+ type: 'edit',
303
+ id: v4(),
304
+ insert: [symbol],
305
+ delete: [],
306
+ }));
307
+ return [draft => {
308
+ const v = draft.versions.find(ver => ver.id === this.versionId);
309
+ if (!v)
310
+ return;
311
+ v.edits = newEdits;
312
+ delete v.basedOn;
313
+ v.motivations = [];
314
+ }];
315
+ }
316
+ }
317
+ export class RemoveFeature extends BasePlan {
318
+ constructor(copyID, featureIDs) {
319
+ super();
320
+ Object.defineProperty(this, "copyID", {
321
+ enumerable: true,
322
+ configurable: true,
323
+ writable: true,
324
+ value: copyID
325
+ });
326
+ Object.defineProperty(this, "featureIDs", {
327
+ enumerable: true,
328
+ configurable: true,
329
+ writable: true,
330
+ value: featureIDs
331
+ });
332
+ }
333
+ build() {
334
+ const view = this.view;
335
+ if (!view)
336
+ return [];
337
+ return [
338
+ (draft) => {
339
+ const copy = draft.copies.find(c => c.id === this.copyID);
340
+ if (!copy)
341
+ return;
342
+ copy.features = copy.features.filter(f => !this.featureIDs.includes(f.id));
343
+ this.featureIDs.forEach(featureId => {
344
+ // expect a path of format:
345
+ // ['versions', index, 'edits', index, 'insert', index, 'carriers', index, 'id']
346
+ const carrierPath = view.linksTo(featureId).at(0);
347
+ if (!carrierPath)
348
+ return;
349
+ // expect to find the parent symbol at:
350
+ // ['versions', index, 'edits', index, 'insert', index]
351
+ const symbol = getAt(carrierPath.slice(0, 6), draft);
352
+ if (!symbol)
353
+ return;
354
+ symbol.carriers = symbol.carriers.filter(c => idOf(c) !== featureId);
355
+ // if the symbol has no more carriers, remove it
356
+ if (symbol.carriers.length === 0) {
357
+ // expect to find the parent edit at:
358
+ // ['versions', index, 'edits', index]
359
+ const inserts = getAt(carrierPath.slice(0, 5), draft);
360
+ if (!inserts)
361
+ return;
362
+ inserts.splice(inserts.findIndex(s => s.id === symbol.id), 1);
363
+ if (inserts.length === 0) {
364
+ const edit = getAt(carrierPath.slice(0, 4), draft);
365
+ if (!edit)
366
+ return;
367
+ if (!edit.insert?.length && !edit.delete?.length) {
368
+ const edits = getAt(carrierPath.slice(0, 3), draft);
369
+ if (!edits)
370
+ return;
371
+ edits.splice(edits.findIndex(e => e.id === edit.id), 1);
372
+ }
373
+ }
374
+ }
375
+ });
376
+ }
377
+ ];
378
+ }
379
+ }
380
+ export class MergeEdits extends BasePlan {
381
+ constructor(versionId, toMerge) {
382
+ super();
383
+ Object.defineProperty(this, "versionId", {
384
+ enumerable: true,
385
+ configurable: true,
386
+ writable: true,
387
+ value: versionId
388
+ });
389
+ Object.defineProperty(this, "toMerge", {
390
+ enumerable: true,
391
+ configurable: true,
392
+ writable: true,
393
+ value: toMerge
394
+ });
395
+ }
396
+ guessMotivation(edit) {
397
+ const view = this.view;
398
+ if (!view)
399
+ return 'correct-error';
400
+ const inserts = (edit.insert || []);
401
+ const deletes = view.getAll(edit.delete ?? []);
402
+ const types = [
403
+ inserts.filter(e => e.type === 'expression').map(e => e.expressionType),
404
+ deletes.filter(e => e.type === 'expression').map(e => e.expressionType)
405
+ ];
406
+ if (arraysIdentical(types, [['SlowCrescendoOn', 'SlowCrescendoOff'], []])) {
407
+ return 'additional-accent';
408
+ }
409
+ else if (arraysIdentical(types, [['ForzandoOn', 'ForzandoOff'], []])) {
410
+ // TODO: check if the inserts are very close
411
+ // and return 'short-dynamic-differentation'
412
+ return 'additional-accent';
413
+ }
414
+ else if (types.every(t => t.length > 1) && arraysIdentical(types[0], types[1])) {
415
+ return 'shift';
416
+ }
417
+ else if (types[0].length === 0 && types[1].length === 1) {
418
+ return 'remove-redundancy';
419
+ }
420
+ if (inserts.length === 1 && deletes.length === 1) {
421
+ const insertDim = view.dimensionOf(inserts[0])?.horizontal;
422
+ const deleteDim = view.dimensionOf(deletes[0])?.horizontal;
423
+ if (!insertDim || !deleteDim) {
424
+ return 'correct-error';
425
+ }
426
+ if (Math.abs(insertDim.from - deleteDim.from) < 5) {
427
+ const insertLength = Math.abs(insertDim.to - insertDim.from);
428
+ const deleteLength = Math.abs(deleteDim.to - deleteDim.from);
429
+ if (insertLength < deleteLength) {
430
+ return 'shorten';
431
+ }
432
+ else {
433
+ return 'prolong';
434
+ }
435
+ }
436
+ }
437
+ return 'correct-error';
438
+ }
439
+ build() {
440
+ const view = this.view;
441
+ if (!view)
442
+ return [];
443
+ const result = structuredClone(this.toMerge[0]);
444
+ this.toMerge
445
+ .slice(1)
446
+ .forEach(edit => {
447
+ if (result.insert) {
448
+ result.insert.push(...(edit.insert || []));
449
+ }
450
+ else {
451
+ result.insert = edit.insert;
452
+ }
453
+ if (result.delete) {
454
+ result.delete.push(...(edit.delete || []));
455
+ }
456
+ else {
457
+ result.delete = edit.delete;
458
+ }
459
+ });
460
+ const edit = {
461
+ ...result,
462
+ id: v4(),
463
+ motivation: this.guessMotivation(result),
464
+ };
465
+ return ([draft => {
466
+ const version = draft.versions.find(v => v.id === this.versionId);
467
+ if (!version)
468
+ return;
469
+ // remove all edits that were merged
470
+ for (const e of this.toMerge) {
471
+ const index = version.edits.findIndex(ve => ve.id === e.id);
472
+ if (index !== -1)
473
+ version.edits.splice(index, 1);
474
+ }
475
+ version.edits.push(edit);
476
+ }]);
477
+ }
478
+ }
479
+ export class SplitEdit extends BasePlan {
480
+ constructor(versionId, toSplit) {
481
+ super();
482
+ Object.defineProperty(this, "versionId", {
483
+ enumerable: true,
484
+ configurable: true,
485
+ writable: true,
486
+ value: versionId
487
+ });
488
+ Object.defineProperty(this, "toSplit", {
489
+ enumerable: true,
490
+ configurable: true,
491
+ writable: true,
492
+ value: toSplit
493
+ });
494
+ }
495
+ build() {
496
+ const view = this.view;
497
+ if (!view)
498
+ return [];
499
+ const result = [];
500
+ for (const insert of this.toSplit.insert ?? []) {
501
+ result.push({
502
+ type: 'edit',
503
+ id: v4(),
504
+ insert: [insert]
505
+ });
506
+ }
507
+ for (const remove of this.toSplit.delete ?? []) {
508
+ result.push({
509
+ type: 'edit',
510
+ id: v4(),
511
+ delete: [remove]
512
+ });
513
+ }
514
+ return ([draft => {
515
+ const version = draft.versions.find(v => v.id === this.versionId);
516
+ if (!version)
517
+ return;
518
+ // remove the edit that is split
519
+ const index = version.edits.findIndex(ve => ve.id === this.toSplit.id);
520
+ if (index !== -1)
521
+ version.edits.splice(index, 1);
522
+ version.edits.push(...result);
523
+ }]);
524
+ }
525
+ }
526
+ /*
527
+ type LazyDimension = Partial<{ from: number, to: number }>;
528
+
529
+ type LazyArea = {
530
+ horizontal: LazyDimension;
531
+ vertical: LazyDimension;
532
+ }
533
+
534
+ const overlaps = (a: LazyArea, b: LazyArea): boolean => {
535
+ const overlapsDimension = (a: LazyDimension, b: LazyDimension): boolean =>
536
+ (a.from ?? 0) < (b.to ?? Infinity) && (b.from ?? 0) < (a.to ?? Infinity);
537
+
538
+ // console.log('do the dimensions overlap?', a, b)
539
+
540
+ return overlapsDimension(a.horizontal, b.horizontal);
541
+ }
542
+ */
543
+ const arraysIdentical = (a, b) => {
544
+ let i = a.length;
545
+ if (i != b.length)
546
+ return false;
547
+ while (i--) {
548
+ if (Array.isArray(a[i]) && Array.isArray(b[i])) {
549
+ return arraysIdentical(a[i], b[i]);
550
+ }
551
+ if (a[i] !== b[i])
552
+ return false;
553
+ }
554
+ return true;
555
+ };
@@ -0,0 +1,80 @@
1
+ import { RollTempo } from "./Edition";
2
+ import { Hole } from "./Feature";
3
+ import { Expression, Note } from "./Symbol";
4
+ import { TrackerBar } from "./TrackerBar";
5
+ /**
6
+ * A note or expression of a version with the dimensions of its carriers
7
+ * averaged in and the editorial assumptions applied, which is all a
8
+ * performance needs to know of a symbol.
9
+ */
10
+ export type NegotiatedEvent = Omit<Note | Expression, 'carriers'> & Pick<Hole, 'horizontal' | 'vertical'>;
11
+ interface PerformedRollFeature<T> {
12
+ type: T;
13
+ performs: NegotiatedEvent;
14
+ /** Seconds from the beginning of the roll. */
15
+ at: number;
16
+ }
17
+ interface PerformedNoteEvent<T> extends PerformedRollFeature<T> {
18
+ pitch: number;
19
+ velocity: number;
20
+ }
21
+ export interface PerformedNoteOnEvent extends PerformedNoteEvent<'noteOn'> {
22
+ }
23
+ export interface PerformedNoteOffEvent extends PerformedNoteEvent<'noteOff'> {
24
+ }
25
+ /**
26
+ * One step of a pedal. A pedal driven by a bellows takes time to travel,
27
+ * so a single perforation results in a run of these; `performs` is the
28
+ * perforation whose reading the step follows from.
29
+ */
30
+ export interface PerformedPedalEvent extends PerformedRollFeature<'damper' | 'hammerRail'> {
31
+ /** 0 with the pedal up and 127 with it fully down. */
32
+ value: number;
33
+ }
34
+ export type AnyPerformedRollFeature = PerformedNoteOnEvent | PerformedNoteOffEvent | PerformedPedalEvent;
35
+ interface CurveSamples {
36
+ readonly name: string;
37
+ /** Paper position of each sample, in mm from the beginning of the roll. */
38
+ readonly place: Float64Array;
39
+ readonly seconds: Float64Array;
40
+ /**
41
+ * Position between the two ends of a travel: 0 with a bellows open and
42
+ * 1 with it closed, 0 with a pedal up and 1 with it down.
43
+ */
44
+ readonly travel: Float64Array;
45
+ }
46
+ /** The dynamics of one part of the keyboard, with the velocity the travel maps onto. */
47
+ export type DynamicsCurve = CurveSamples & {
48
+ readonly kind: 'dynamics';
49
+ readonly velocity: Float64Array;
50
+ };
51
+ export type PedalCurve = CurveSamples & {
52
+ readonly kind: 'pedal';
53
+ };
54
+ export type EmulatedCurve = DynamicsCurve | PedalCurve;
55
+ /** What the edition records about the roll that a mechanism may want to know. */
56
+ export type RollProperties = {
57
+ /** Diameter of the punches in mm, where the copies record it. */
58
+ punchDiameter?: number;
59
+ /** The tempo the edition adjusts the roll to, where it states one. */
60
+ tempo?: RollTempo;
61
+ };
62
+ export type Performance = {
63
+ readonly events: readonly AnyPerformedRollFeature[];
64
+ readonly curves: readonly EmulatedCurve[];
65
+ };
66
+ /**
67
+ * A reproducing piano as the edition sees it, from two sides: the tracker
68
+ * bar reads the holes into symbols, and the mechanism plays the symbols.
69
+ * Implementations live outside the core of this library, so that it does
70
+ * not depend on any one instrument's model; `linked-rolls/welte-t100` is
71
+ * the first.
72
+ */
73
+ export interface ReproducingSystem<Options extends object> {
74
+ readonly name: string;
75
+ readonly trackerBar: TrackerBar;
76
+ readonly defaultOptions: Options;
77
+ /** Performs the events of a version, given in order of place. */
78
+ perform(events: readonly NegotiatedEvent[], options: Options, roll: RollProperties): Performance;
79
+ }
80
+ export {};