linked-rolls 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/lib/Collation.d.ts +4 -0
- package/lib/Collation.js +1 -0
- package/lib/Condition.d.ts +10 -0
- package/lib/Condition.js +1 -0
- package/lib/ConditionState.d.ts +9 -0
- package/lib/ConditionState.js +6 -0
- package/lib/Edit.d.ts +21 -0
- package/lib/Edit.js +112 -0
- package/lib/Edition.d.ts +54 -0
- package/lib/Edition.js +1 -0
- package/lib/EditorialAssumption.d.ts +67 -0
- package/lib/EditorialAssumption.js +26 -0
- package/lib/Emulation.d.ts +76 -0
- package/lib/Emulation.js +474 -0
- package/lib/Feature.d.ts +37 -0
- package/lib/Feature.js +3 -0
- package/lib/Measurement.d.ts +9 -0
- package/lib/Measurement.js +1 -0
- package/lib/PlaceTimeConversion.d.ts +65 -0
- package/lib/PlaceTimeConversion.js +175 -0
- package/lib/RollCopy.d.ts +85 -0
- package/lib/RollCopy.js +273 -0
- package/lib/RollEvent.d.ts +76 -0
- package/lib/RollEvent.js +3 -0
- package/lib/Stage.d.ts +37 -0
- package/lib/Stage.js +165 -0
- package/lib/Symbol.d.ts +62 -0
- package/lib/Symbol.js +29 -0
- package/lib/TrackerBar.d.ts +8 -0
- package/lib/TrackerBar.js +53 -0
- package/lib/Transcription.d.ts +7 -0
- package/lib/Transcription.js +9 -0
- package/lib/Version.d.ts +42 -0
- package/lib/Version.js +217 -0
- package/lib/WithId.d.ts +3 -0
- package/lib/WithId.js +1 -0
- package/lib/alignFeatures.d.ts +11 -0
- package/lib/alignFeatures.js +54 -0
- package/lib/alignRolls.d.ts +7 -0
- package/lib/alignRolls.js +49 -0
- package/lib/alignSymbols.d.ts +7 -0
- package/lib/alignSymbols.js +49 -0
- package/lib/asJsonLd.d.ts +3 -0
- package/lib/asJsonLd.js +67 -0
- package/lib/asMIDISpans.d.ts +23 -0
- package/lib/asMIDISpans.js +111 -0
- package/lib/aton/AtonParser.d.ts +48 -0
- package/lib/aton/AtonParser.js +245 -0
- package/lib/aton/AtonParser.test.d.ts +1 -0
- package/lib/aton/AtonParser.test.js +16 -0
- package/lib/build-schema.cjs +113 -0
- package/lib/context.d.ts +1 -0
- package/lib/context.js +1 -0
- package/lib/importJsonLd.d.ts +4 -0
- package/lib/importJsonLd.js +121 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +17 -0
- package/lib/spec/context.json +192 -0
- package/package.json +27 -0
package/lib/Emulation.js
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { MIDIControlEvents } from "midifile-ts";
|
|
2
|
+
import { KinematicConversion } from "./PlaceTimeConversion";
|
|
3
|
+
import { getSnapshot } from "./Version";
|
|
4
|
+
import { dimensionOf } from "./Symbol";
|
|
5
|
+
function resize(arr, newSize, defaultValue) {
|
|
6
|
+
while (newSize > arr.length)
|
|
7
|
+
arr.push(defaultValue);
|
|
8
|
+
}
|
|
9
|
+
const simplifySymbol = (symbol) => {
|
|
10
|
+
if (!symbol.carriers || !symbol.carriers.length)
|
|
11
|
+
return null;
|
|
12
|
+
const mean = dimensionOf(symbol);
|
|
13
|
+
return {
|
|
14
|
+
...symbol,
|
|
15
|
+
...mean
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export class Emulation {
|
|
19
|
+
constructor(options = {
|
|
20
|
+
welte_p: 35,
|
|
21
|
+
welte_f: 90,
|
|
22
|
+
welte_mf: 60,
|
|
23
|
+
welte_loud: 75,
|
|
24
|
+
trackerBarDiameter: 1.413,
|
|
25
|
+
punchExtensionFraction: 0.75,
|
|
26
|
+
slow_decay_rate: 2380,
|
|
27
|
+
fastC_decay_rate: 300,
|
|
28
|
+
fastD_decay_rate: 400,
|
|
29
|
+
division: 54
|
|
30
|
+
}) {
|
|
31
|
+
Object.defineProperty(this, "placeTimeConversion", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: new KinematicConversion()
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "midiEvents", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: []
|
|
42
|
+
});
|
|
43
|
+
// sorted list of events with the negotiated assumptions already applied
|
|
44
|
+
Object.defineProperty(this, "negotiatedEvents", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: []
|
|
49
|
+
});
|
|
50
|
+
// stores a velocity for every millisecond
|
|
51
|
+
Object.defineProperty(this, "trebleVelocities", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
value: []
|
|
56
|
+
});
|
|
57
|
+
Object.defineProperty(this, "bassVelocities", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
value: []
|
|
62
|
+
});
|
|
63
|
+
Object.defineProperty(this, "startTempo", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
value: void 0
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(this, "endTempo", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
75
|
+
Object.defineProperty(this, "source", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
value: void 0
|
|
80
|
+
});
|
|
81
|
+
Object.defineProperty(this, "options", {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
configurable: true,
|
|
84
|
+
writable: true,
|
|
85
|
+
value: void 0
|
|
86
|
+
});
|
|
87
|
+
options.slow_step = (options.welte_mf - options.welte_p) / options.slow_decay_rate;
|
|
88
|
+
options.fastC_step = (options.welte_mf - options.welte_p) / options.fastC_decay_rate;
|
|
89
|
+
options.fastD_step = -(options.welte_f - options.welte_p) / options.fastD_decay_rate;
|
|
90
|
+
this.options = options;
|
|
91
|
+
}
|
|
92
|
+
findRollTempo(tempo) {
|
|
93
|
+
if (!tempo) {
|
|
94
|
+
this.startTempo = 104.331;
|
|
95
|
+
this.endTempo = 104.331;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.startTempo = tempo.startsWith;
|
|
99
|
+
this.endTempo = tempo.endsWith;
|
|
100
|
+
}
|
|
101
|
+
assignPhysicalTime(skipToFirstNote = false) {
|
|
102
|
+
if (this.negotiatedEvents.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
const first = skipToFirstNote ? this.negotiatedEvents[0].horizontal.from : 0;
|
|
105
|
+
for (const event of this.negotiatedEvents) {
|
|
106
|
+
if (!event.assumedPhysicalTime) {
|
|
107
|
+
// convert from mm to cm and then to time
|
|
108
|
+
event.assumedPhysicalTime = [
|
|
109
|
+
this.placeTimeConversion.placeToTime((event.horizontal.from - first) / 10),
|
|
110
|
+
this.placeTimeConversion.placeToTime((event.horizontal.to - first) / 10)
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
applyTrackerBarExtension() {
|
|
116
|
+
const correction = this.options.trackerBarDiameter * this.options.punchExtensionFraction + 0.5;
|
|
117
|
+
for (const event of this.negotiatedEvents) {
|
|
118
|
+
if (event.horizontal.to) {
|
|
119
|
+
event.horizontal.to += correction;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
convertEventsToMIDI() {
|
|
124
|
+
for (const event of this.negotiatedEvents) {
|
|
125
|
+
if (event.type === 'expression') {
|
|
126
|
+
const expression = event;
|
|
127
|
+
const map = new Map([
|
|
128
|
+
['SustainPedalOn', 'sustainPedalOn'],
|
|
129
|
+
['SustainPedalOff', 'sustainPedalOff'],
|
|
130
|
+
['SoftPedalOn', 'softPedalOn'],
|
|
131
|
+
['SoftPedalOff', 'softPedalOff']
|
|
132
|
+
]);
|
|
133
|
+
if (map.has(expression.expressionType)) {
|
|
134
|
+
this.midiEvents.push({
|
|
135
|
+
type: map.get(expression.expressionType),
|
|
136
|
+
performs: event,
|
|
137
|
+
at: event.assumedPhysicalTime[0],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (event.type === 'note') {
|
|
142
|
+
const note = event;
|
|
143
|
+
// take velocity from the calculated velocity list
|
|
144
|
+
const pitch = note.pitch;
|
|
145
|
+
if (event.vertical.from >= this.options.division) {
|
|
146
|
+
this.insertNote(event, pitch, this.trebleVelocities[+(event.assumedPhysicalTime[0] * 1000).toFixed()]);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.insertNote(event, pitch, this.bassVelocities[+(event.assumedPhysicalTime[0] * 1000).toFixed()]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.midiEvents.sort((a, b) => a.at - b.at);
|
|
154
|
+
}
|
|
155
|
+
emulateFromRoll(events) {
|
|
156
|
+
this.startTempo = 104.331;
|
|
157
|
+
this.endTempo = 104.331;
|
|
158
|
+
this.negotiatedEvents = events
|
|
159
|
+
.map(simplifySymbol)
|
|
160
|
+
.filter(s => s !== null);
|
|
161
|
+
this.applyTrackerBarExtension();
|
|
162
|
+
this.assignPhysicalTime();
|
|
163
|
+
this.applyTrackerBarExtension();
|
|
164
|
+
this.calculateVelocities('treble');
|
|
165
|
+
this.calculateVelocities('bass');
|
|
166
|
+
this.convertEventsToMIDI();
|
|
167
|
+
return this.midiEvents;
|
|
168
|
+
}
|
|
169
|
+
emulateVersion(version, rollTempo, skipToFirstNote = false) {
|
|
170
|
+
this.source = version.id;
|
|
171
|
+
this.negotiatedEvents =
|
|
172
|
+
getSnapshot(version)
|
|
173
|
+
.filter(s => s.type === 'note' || s.type === 'expression')
|
|
174
|
+
.map(simplifySymbol)
|
|
175
|
+
.filter(s => s !== null);
|
|
176
|
+
this.findRollTempo(rollTempo);
|
|
177
|
+
this.applyTrackerBarExtension();
|
|
178
|
+
this.assignPhysicalTime(skipToFirstNote);
|
|
179
|
+
this.calculateVelocities('treble');
|
|
180
|
+
this.calculateVelocities('bass');
|
|
181
|
+
this.convertEventsToMIDI();
|
|
182
|
+
return this.midiEvents;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The original code of the calculateVelocities() method,
|
|
186
|
+
* written by Kitty Shi and Craig Stuart Sapp,
|
|
187
|
+
* was taken from the midi2exp project (https://github.com/pianoroll/midi2exp)
|
|
188
|
+
* and adapted to the different data representation.
|
|
189
|
+
*/
|
|
190
|
+
calculateVelocities(scope) {
|
|
191
|
+
if (!this.negotiatedEvents.length)
|
|
192
|
+
return;
|
|
193
|
+
const velocities = scope === 'treble' ? this.trebleVelocities : this.bassVelocities;
|
|
194
|
+
const isMF = []; // is MF hook on?
|
|
195
|
+
const isSlowC = []; // is slow crescendo on?
|
|
196
|
+
const isFastC = []; // is fast crescendo on?
|
|
197
|
+
const isFastD = []; // is fast decrescendo on?
|
|
198
|
+
let lastOnsetMs = this.negotiatedEvents[this.negotiatedEvents.length - 1].assumedPhysicalTime[0];
|
|
199
|
+
if (!lastOnsetMs) {
|
|
200
|
+
console.log('Failed calculating the last onset.');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
lastOnsetMs *= 1000;
|
|
204
|
+
// set all of the times to piano by default
|
|
205
|
+
resize(velocities, lastOnsetMs, this.options.welte_p);
|
|
206
|
+
// fill all hook maps with false
|
|
207
|
+
for (const target of [isMF, isSlowC, isFastC, isFastD]) {
|
|
208
|
+
resize(target, lastOnsetMs, false);
|
|
209
|
+
}
|
|
210
|
+
// Lock and Cancel
|
|
211
|
+
let valve_mf_on = false;
|
|
212
|
+
let valve_slowc_on = false;
|
|
213
|
+
let valve_mf_starttime = 0; // 0 for off
|
|
214
|
+
let valve_slowc_starttime = 0;
|
|
215
|
+
// First pass: For each time section calculate the current boolean
|
|
216
|
+
// state of each expression.
|
|
217
|
+
for (const negotiatedEvent of this.negotiatedEvents) {
|
|
218
|
+
if (negotiatedEvent.type !== 'expression')
|
|
219
|
+
continue;
|
|
220
|
+
if (scope === 'treble' && negotiatedEvent.vertical.from < this.options.division)
|
|
221
|
+
continue;
|
|
222
|
+
else if (scope === 'bass' && negotiatedEvent.vertical.from >= this.options.division)
|
|
223
|
+
continue;
|
|
224
|
+
const event = negotiatedEvent;
|
|
225
|
+
const startMs = event.assumedPhysicalTime[0] * 1000;
|
|
226
|
+
const endMs = event.assumedPhysicalTime[1] * 1000;
|
|
227
|
+
// console.log('encoutering expression', event.expressionType["@id"], 'from', startMs, 'to', endMs)
|
|
228
|
+
if (event.expressionType === 'MezzoforteOn') {
|
|
229
|
+
// update the mezzoforte start time
|
|
230
|
+
// only if the mf valve is not on already
|
|
231
|
+
if (!valve_mf_on) {
|
|
232
|
+
valve_mf_on = true;
|
|
233
|
+
valve_mf_starttime = startMs;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (event.expressionType === 'MezzoforteOff') {
|
|
237
|
+
if (valve_mf_on) {
|
|
238
|
+
// fill from the mezzoforte start time to here ...
|
|
239
|
+
isMF.fill(true, valve_mf_starttime, startMs);
|
|
240
|
+
}
|
|
241
|
+
valve_mf_on = false;
|
|
242
|
+
}
|
|
243
|
+
else if (event.expressionType === 'SlowCrescendoOn') {
|
|
244
|
+
// update the slow crescendo start time
|
|
245
|
+
// only if slow crescendo is not on already
|
|
246
|
+
if (!valve_slowc_on) {
|
|
247
|
+
valve_slowc_on = true;
|
|
248
|
+
valve_slowc_starttime = startMs;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (event.expressionType === 'SlowCrescendoOff') {
|
|
252
|
+
if (valve_slowc_on) {
|
|
253
|
+
// fill from the mezzoforte start time to here ...
|
|
254
|
+
isSlowC.fill(true, valve_slowc_starttime, startMs);
|
|
255
|
+
}
|
|
256
|
+
valve_slowc_on = false;
|
|
257
|
+
}
|
|
258
|
+
else if (event.expressionType === 'ForzandoOn') {
|
|
259
|
+
// Forzando On/Off are a direct operations (length of perforation matters)
|
|
260
|
+
isFastC.fill(true, startMs, endMs);
|
|
261
|
+
}
|
|
262
|
+
else if (event.expressionType === 'ForzandoOff') {
|
|
263
|
+
// Forzando On/Off are a direct operations (length of perforation matters)
|
|
264
|
+
isFastD.fill(true, startMs, endMs);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// TODO: deal with the last case (if crescendo OFF is missing)
|
|
268
|
+
// Second pass, update the current velocity according to the previous one
|
|
269
|
+
let amount = 0.0;
|
|
270
|
+
let eps = 0.0001;
|
|
271
|
+
for (let i = 1; i < lastOnsetMs; i++) {
|
|
272
|
+
if (!isSlowC[i] && !isFastC[i] && !isFastD[i]) {
|
|
273
|
+
// slow decrescendo is always on
|
|
274
|
+
amount = -this.options.slow_step;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
amount =
|
|
278
|
+
(isSlowC[i] ? this.options.slow_step : 0) +
|
|
279
|
+
(isFastC[i] ? this.options.fastC_step : 0) +
|
|
280
|
+
(isFastD[i] ? this.options.fastD_step : 0);
|
|
281
|
+
}
|
|
282
|
+
velocities[i] = velocities[i - 1] + amount;
|
|
283
|
+
if (isMF[i]) {
|
|
284
|
+
if (velocities[i - 1] > this.options.welte_mf) {
|
|
285
|
+
if (amount < 0) {
|
|
286
|
+
velocities[i] = Math.max(this.options.welte_mf + eps, velocities[i]);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
velocities[i] = Math.min(this.options.welte_f, velocities[i]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (velocities[i - 1] < this.options.welte_mf) {
|
|
293
|
+
if (amount > 0) {
|
|
294
|
+
velocities[i] = Math.min(this.options.welte_mf - eps, velocities[i]);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
velocities[i] = Math.max(this.options.welte_p, velocities[i]);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
// slow crescendo will only reach welte_loud
|
|
303
|
+
if (isSlowC[i] && !isFastC[i] && velocities[i - 1] < this.options.welte_loud) {
|
|
304
|
+
velocities[i] = Math.min(velocities[i], this.options.welte_loud - eps);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// regulating max and min
|
|
308
|
+
velocities[i] = Math.max(this.options.welte_p, velocities[i]);
|
|
309
|
+
velocities[i] = Math.min(this.options.welte_f, velocities[i]);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
insertNote(event, pitch, velocity) {
|
|
313
|
+
this.midiEvents.push({
|
|
314
|
+
type: 'noteOn',
|
|
315
|
+
performs: event,
|
|
316
|
+
velocity,
|
|
317
|
+
at: event.assumedPhysicalTime[0],
|
|
318
|
+
pitch
|
|
319
|
+
});
|
|
320
|
+
this.midiEvents.push({
|
|
321
|
+
type: 'noteOff',
|
|
322
|
+
performs: event,
|
|
323
|
+
velocity: 127,
|
|
324
|
+
at: event.assumedPhysicalTime[1],
|
|
325
|
+
pitch
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
findEventsPerforming(id) {
|
|
329
|
+
return this.midiEvents.filter(event => event.performs.id === id);
|
|
330
|
+
}
|
|
331
|
+
asMIDI() {
|
|
332
|
+
const events = [];
|
|
333
|
+
this.midiEvents.sort((a, b) => a.at - b.at);
|
|
334
|
+
let currentTime = 0;
|
|
335
|
+
// insert metadata first
|
|
336
|
+
events.push({
|
|
337
|
+
type: 'meta',
|
|
338
|
+
subtype: 'text',
|
|
339
|
+
text: 'linked-rolls (based on midi2exp)',
|
|
340
|
+
deltaTime: 0
|
|
341
|
+
});
|
|
342
|
+
if (this.source) {
|
|
343
|
+
events.push({
|
|
344
|
+
type: 'meta',
|
|
345
|
+
subtype: 'text',
|
|
346
|
+
text: this.source,
|
|
347
|
+
deltaTime: 0
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
events.push({
|
|
351
|
+
type: 'meta',
|
|
352
|
+
subtype: 'text',
|
|
353
|
+
text: `place-time-conversion: ${this.placeTimeConversion.summary}`,
|
|
354
|
+
deltaTime: 0
|
|
355
|
+
});
|
|
356
|
+
for (const [key, value] of Object.entries(this.options)) {
|
|
357
|
+
events.push({
|
|
358
|
+
type: 'meta',
|
|
359
|
+
subtype: 'text',
|
|
360
|
+
text: `${key}=${value}`,
|
|
361
|
+
deltaTime: 0
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
events.push({
|
|
365
|
+
type: 'meta',
|
|
366
|
+
subtype: 'setTempo',
|
|
367
|
+
microsecondsPerBeat: 1000000,
|
|
368
|
+
deltaTime: 0
|
|
369
|
+
});
|
|
370
|
+
for (const event of this.midiEvents) {
|
|
371
|
+
const deltaTimeMs = (event.at - currentTime) * 1000;
|
|
372
|
+
if (event.type === 'noteOn') {
|
|
373
|
+
console.log('dealing with note on', event.pitch, event.at, deltaTimeMs);
|
|
374
|
+
events.push({
|
|
375
|
+
type: 'meta',
|
|
376
|
+
subtype: 'text',
|
|
377
|
+
deltaTime: deltaTimeMs,
|
|
378
|
+
text: event.performs.id
|
|
379
|
+
});
|
|
380
|
+
events.push({
|
|
381
|
+
type: 'channel',
|
|
382
|
+
subtype: 'noteOn',
|
|
383
|
+
noteNumber: event.pitch,
|
|
384
|
+
velocity: +event.velocity.toFixed(0),
|
|
385
|
+
deltaTime: 0,
|
|
386
|
+
channel: 0
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
else if (event.type === 'noteOff') {
|
|
390
|
+
events.push({
|
|
391
|
+
type: 'channel',
|
|
392
|
+
subtype: 'noteOff',
|
|
393
|
+
noteNumber: event.pitch,
|
|
394
|
+
velocity: 127,
|
|
395
|
+
deltaTime: deltaTimeMs,
|
|
396
|
+
channel: 0
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
else if (event.type === 'sustainPedalOn') {
|
|
400
|
+
events.push({
|
|
401
|
+
type: 'meta',
|
|
402
|
+
subtype: 'text',
|
|
403
|
+
deltaTime: deltaTimeMs,
|
|
404
|
+
text: event.performs.id
|
|
405
|
+
});
|
|
406
|
+
events.push({
|
|
407
|
+
type: 'channel',
|
|
408
|
+
subtype: 'controller',
|
|
409
|
+
controllerType: MIDIControlEvents.SUSTAIN,
|
|
410
|
+
deltaTime: 0,
|
|
411
|
+
channel: 0,
|
|
412
|
+
value: 127
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
else if (event.type === 'sustainPedalOff') {
|
|
416
|
+
events.push({
|
|
417
|
+
type: 'meta',
|
|
418
|
+
subtype: 'text',
|
|
419
|
+
deltaTime: deltaTimeMs,
|
|
420
|
+
text: event.performs.id
|
|
421
|
+
});
|
|
422
|
+
events.push({
|
|
423
|
+
type: 'channel',
|
|
424
|
+
subtype: 'controller',
|
|
425
|
+
controllerType: MIDIControlEvents.SUSTAIN,
|
|
426
|
+
deltaTime: 0,
|
|
427
|
+
channel: 0,
|
|
428
|
+
value: 0
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
else if (event.type === 'softPedalOn') {
|
|
432
|
+
events.push({
|
|
433
|
+
type: 'meta',
|
|
434
|
+
subtype: 'text',
|
|
435
|
+
deltaTime: deltaTimeMs,
|
|
436
|
+
text: event.performs.id
|
|
437
|
+
});
|
|
438
|
+
events.push({
|
|
439
|
+
type: 'channel',
|
|
440
|
+
subtype: 'controller',
|
|
441
|
+
controllerType: MIDIControlEvents.SOFT_PEDAL,
|
|
442
|
+
deltaTime: 0,
|
|
443
|
+
channel: 0,
|
|
444
|
+
value: 127
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
else if (event.type === 'softPedalOff') {
|
|
448
|
+
events.push({
|
|
449
|
+
type: 'meta',
|
|
450
|
+
subtype: 'text',
|
|
451
|
+
deltaTime: deltaTimeMs,
|
|
452
|
+
text: event.performs.id
|
|
453
|
+
});
|
|
454
|
+
events.push({
|
|
455
|
+
type: 'channel',
|
|
456
|
+
subtype: 'controller',
|
|
457
|
+
controllerType: MIDIControlEvents.SOFT_PEDAL,
|
|
458
|
+
deltaTime: 0,
|
|
459
|
+
channel: 0,
|
|
460
|
+
value: 0
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
currentTime = event.at;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
header: {
|
|
467
|
+
ticksPerBeat: 1000,
|
|
468
|
+
formatType: 0,
|
|
469
|
+
trackCount: 1
|
|
470
|
+
},
|
|
471
|
+
tracks: [events]
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
package/lib/Feature.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ConditionState } from "./ConditionState";
|
|
2
|
+
import { EditorialAssumption } from "./EditorialAssumption";
|
|
3
|
+
import { WithId } from "./WithId";
|
|
4
|
+
export interface FeatureCondition extends ConditionState<'missing-perforation' | 'damaged-perforation' | 'illegible'> {
|
|
5
|
+
}
|
|
6
|
+
export type FeatureConditionAssignment = EditorialAssumption<'conditionAssignment', FeatureCondition>;
|
|
7
|
+
export interface HorizontalSpan {
|
|
8
|
+
unit: 'mm';
|
|
9
|
+
from: number;
|
|
10
|
+
to: number;
|
|
11
|
+
}
|
|
12
|
+
export interface VerticalSpan {
|
|
13
|
+
unit: 'track';
|
|
14
|
+
from: number;
|
|
15
|
+
to?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RollFeature extends WithId {
|
|
18
|
+
/**
|
|
19
|
+
* IIIF region in string form.
|
|
20
|
+
*/
|
|
21
|
+
annotates?: string;
|
|
22
|
+
horizontal: HorizontalSpan;
|
|
23
|
+
vertical: VerticalSpan;
|
|
24
|
+
/**
|
|
25
|
+
* Describes whether the event takes place
|
|
26
|
+
* on the verso or recto side of the roll.
|
|
27
|
+
* Since the same perforation is present on both
|
|
28
|
+
* sides, this property is left optional for now.
|
|
29
|
+
*/
|
|
30
|
+
side?: 'verso' | 'recto';
|
|
31
|
+
/**
|
|
32
|
+
* This can be used e.g. to indicate a perforation
|
|
33
|
+
* which is torn out or in any other way damaged.
|
|
34
|
+
*/
|
|
35
|
+
condition?: FeatureConditionAssignment;
|
|
36
|
+
}
|
|
37
|
+
export declare const isRollFeature: (obj: object) => obj is RollFeature;
|
package/lib/Feature.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export declare abstract class PlaceTimeConversion {
|
|
2
|
+
abstract get summary(): string;
|
|
3
|
+
abstract placeToTime(x1: number): number;
|
|
4
|
+
abstract timeToPlace(t1: number): number;
|
|
5
|
+
}
|
|
6
|
+
export declare class NoAccelerationConversion implements PlaceTimeConversion {
|
|
7
|
+
get summary(): string;
|
|
8
|
+
metersPerMinute: number;
|
|
9
|
+
/**
|
|
10
|
+
* Converts physical place (in cm) to time (in seconds).
|
|
11
|
+
* @param x1 - The distance in mm.
|
|
12
|
+
* @returns Time in seconds.
|
|
13
|
+
*/
|
|
14
|
+
placeToTime(cm: number): number;
|
|
15
|
+
/**
|
|
16
|
+
* Converts time (in seconds) to physical place (in cm).
|
|
17
|
+
* @param t1 - The time in seconds.
|
|
18
|
+
* @returns Distance in cm.
|
|
19
|
+
*/
|
|
20
|
+
timeToPlace(t1: number): number;
|
|
21
|
+
}
|
|
22
|
+
declare enum SpeedUnit {
|
|
23
|
+
MetersPerMinute = 0,
|
|
24
|
+
FeetPerMinute = 1
|
|
25
|
+
}
|
|
26
|
+
declare enum AccelerationUnit {
|
|
27
|
+
MillimetersPerSecondSquared = 0,
|
|
28
|
+
FeetPerMinuteSquared = 1
|
|
29
|
+
}
|
|
30
|
+
export declare class KinematicConversion implements PlaceTimeConversion {
|
|
31
|
+
get summary(): string;
|
|
32
|
+
static readonly slowSpeed = 9.46;
|
|
33
|
+
static readonly normalSpeed = 9.85;
|
|
34
|
+
static readonly stanfordAcceleration = 0.3147;
|
|
35
|
+
static readonly baertschAcceleration = 0.1772;
|
|
36
|
+
private feetPerMinute;
|
|
37
|
+
private acceleration;
|
|
38
|
+
constructor(feetPerMinute?: number, acceleration?: number);
|
|
39
|
+
setSpeed(speed: number, unit: SpeedUnit): void;
|
|
40
|
+
setAcceleration(acceleration: number, unit: AccelerationUnit): void;
|
|
41
|
+
/**
|
|
42
|
+
* Converts physical place (in cm) to time (in seconds).
|
|
43
|
+
* @param x1 - The distance in mm.
|
|
44
|
+
* @returns Time in seconds.
|
|
45
|
+
*/
|
|
46
|
+
placeToTime(cm: number): number;
|
|
47
|
+
/**
|
|
48
|
+
* Converts time (in seconds) to physical place (in cm).
|
|
49
|
+
* @param t1 - The time in seconds.
|
|
50
|
+
* @returns Distance in cm.
|
|
51
|
+
*/
|
|
52
|
+
timeToPlace(t1: number): number;
|
|
53
|
+
}
|
|
54
|
+
export declare class GottschewskiConversion implements PlaceTimeConversion {
|
|
55
|
+
get summary(): string;
|
|
56
|
+
paperThickness: number;
|
|
57
|
+
initialCircumference: number;
|
|
58
|
+
secondsPerTurn: number;
|
|
59
|
+
/**
|
|
60
|
+
* @param x1 in centimeters
|
|
61
|
+
*/
|
|
62
|
+
placeToTime(x1: number): number;
|
|
63
|
+
timeToPlace(t1: number): number;
|
|
64
|
+
}
|
|
65
|
+
export {};
|