vibe-editor 0.0.0 → 0.0.1-dev
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/LICENSE +1 -1
- package/README.md +12 -9
- package/package.json +11 -5
- package/src/scripts/js/Core.js +212 -186
- package/src/scripts/js/MusicProcessor.js +286 -128
- package/src/scripts/js/{VerovioScoreEditor.js → VIBE.js} +62 -28
- package/src/scripts/js/assets/mei_template.js +5 -1
- package/src/scripts/js/datastructures/MeasureMatrix.js +6 -85
- package/src/scripts/js/datastructures/ScoreGraph.js +1 -1
- package/src/scripts/js/entry.js +3 -2
- package/src/scripts/js/gui/Annotations.js +188 -111
- package/src/scripts/js/gui/HarmonyLabel.js +26 -2
- package/src/scripts/js/gui/ScoreManipulator.js +61 -31
- package/src/scripts/js/gui/Tabbar.js +41 -21
- package/src/scripts/js/gui/Toolbar.js +4 -4
- package/src/scripts/js/handlers/AnnotationChangeHandler.js +131 -60
- package/src/scripts/js/handlers/ClickModeHandler.js +406 -143
- package/src/scripts/js/handlers/CustomToolbarHandler.js +26 -24
- package/src/scripts/js/handlers/GlobalKeyboardHandler.js +12 -7
- package/src/scripts/js/handlers/InsertModeHandler.js +26 -32
- package/src/scripts/js/handlers/KeyModeHandler.js +12 -86
- package/src/scripts/js/handlers/LabelHandler.js +3 -2
- package/src/scripts/js/handlers/PhantomElementHandler.js +1 -1
- package/src/scripts/js/handlers/ScoreManipulatorHandler.js +101 -14
- package/src/scripts/js/handlers/SelectionHandler.js +80 -36
- package/src/scripts/js/handlers/SideBarHandler.js +10 -3
- package/src/scripts/js/handlers/WindowHandler.js +25 -4
- package/src/scripts/js/utils/DOMCreator.js +1 -1
- package/src/scripts/js/utils/MEIConverter.js +13 -1
- package/src/scripts/js/utils/MEIOperations.js +180 -187
- package/src/scripts/js/utils/Mouse2SVG.js +200 -43
- package/src/scripts/js/utils/ReactWrapper.js +46 -0
- package/src/scripts/js/utils/RectWrapper.js +10 -0
- package/src/scripts/js/utils/SVGEditor.js +94 -3
- package/src/scripts/js/utils/VerovioWrapper.js +6 -1
- package/src/scripts/js/utils/convenienceQueries.js +2 -0
- package/src/scripts/js/utils/mappings.js +322 -56
- package/src/styles/VerovioScoreEditor.css +0 -694
@@ -16,6 +16,7 @@ const ac = window.AudioContext;
|
|
16
16
|
const synth = new Tone.Synth().toDestination();
|
17
17
|
class MusicProcessor {
|
18
18
|
constructor(containerId) {
|
19
|
+
this.spaceCount = 0;
|
19
20
|
this.playBtn = (function playBtn(e) {
|
20
21
|
e.preventDefault();
|
21
22
|
this.context.resume().then(() => this.playMidi());
|
@@ -25,16 +26,45 @@ class MusicProcessor {
|
|
25
26
|
this.rewindMidi();
|
26
27
|
}).bind(this);
|
27
28
|
this.fetchAudioSeconds = (function fetchAudioSeconds(e) {
|
29
|
+
var _a;
|
30
|
+
//if ((this.container.querySelector("#recordAlignment") as HTMLInputElement)?.checked || this.audioTimes?.size === 0) return
|
31
|
+
if (((_a = this.audioTimes) === null || _a === void 0 ? void 0 : _a.size) === 0)
|
32
|
+
return;
|
28
33
|
var sec = e.target.currentTime;
|
29
|
-
this.highlight(sec,
|
34
|
+
this.highlight(sec, "", "audio");
|
30
35
|
this.drawFollowerRect();
|
31
36
|
}).bind(this);
|
37
|
+
this.resetSpacebarCount = (function resetAudioTimes(e) {
|
38
|
+
var input = e.target;
|
39
|
+
if (input.checked) {
|
40
|
+
this.spaceCount = 0;
|
41
|
+
}
|
42
|
+
}).bind(this);
|
43
|
+
this.preventAudioPause = (function preventAudioPause(e) {
|
44
|
+
if (e.code === "Space" && cq.getContainer(this.containerId).querySelector("#recordAlignment").checked) {
|
45
|
+
e.target.blur();
|
46
|
+
e.preventDefault();
|
47
|
+
}
|
48
|
+
}).bind(this);
|
49
|
+
this.recAlignmentManually = (function recAligmentManually(e) {
|
50
|
+
this.recordAlignment(e);
|
51
|
+
}).bind(this);
|
52
|
+
this.downloadAlignmentHandler = (function downloadAlignmentHandler(e) {
|
53
|
+
this.downloadAlignment(e);
|
54
|
+
}).bind(this);
|
55
|
+
this.handleCountdown = (function handleCountdown(e) {
|
56
|
+
var recToggle = this.container.querySelector("#recordAlignment");
|
57
|
+
if (!(recToggle === null || recToggle === void 0 ? void 0 : recToggle.checked))
|
58
|
+
return;
|
59
|
+
var slider = e.target;
|
60
|
+
this.countdown(slider);
|
61
|
+
}).bind(this);
|
32
62
|
this.midiPlayHandler = (function midiPlayHandler(e) {
|
33
63
|
if (!this.hasContainerFocus())
|
34
64
|
return;
|
35
65
|
this.midiPlayFunction(e);
|
36
66
|
}).bind(this);
|
37
|
-
this.
|
67
|
+
this.setCurrentHighlightHandler = (function setCurrentNoteHandler(e) {
|
38
68
|
this.currentNote = e.target;
|
39
69
|
}).bind(this);
|
40
70
|
/**
|
@@ -47,25 +77,22 @@ class MusicProcessor {
|
|
47
77
|
playingNote = cq.getVrvSVG(this.containerId).querySelector("#" + playingNote.closest("[refId]").getAttribute("refId"));
|
48
78
|
playingNote = playingNote.closest(".note") || playingNote.closest(".rest") || playingNote.closest(".mRest");
|
49
79
|
if (playingNote !== null) {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
this.restartTime = res.value.tick;
|
55
|
-
break;
|
80
|
+
this.timemap.forEach(tm => {
|
81
|
+
var _a;
|
82
|
+
if ((_a = tm.on) === null || _a === void 0 ? void 0 : _a.includes(playingNote.id)) {
|
83
|
+
this.restartTime = (tm.tstamp * this.pulse * this.tempo) / 60000;
|
56
84
|
}
|
57
|
-
|
58
|
-
}
|
85
|
+
});
|
59
86
|
}
|
60
87
|
}).bind(this);
|
61
88
|
/**
|
62
89
|
* Adds Class to be highlighted.
|
63
90
|
* Dispatches event for every Note which was started most currently
|
64
91
|
*/
|
65
|
-
this.addClass = (function addClass(
|
92
|
+
this.addClass = (function addClass(el, className) {
|
66
93
|
return new Promise(resolve => {
|
67
|
-
|
68
|
-
|
94
|
+
el.classList.add(className);
|
95
|
+
el.dispatchEvent(this.noteEvent);
|
69
96
|
resolve(true);
|
70
97
|
});
|
71
98
|
}).bind(this);
|
@@ -91,15 +118,17 @@ class MusicProcessor {
|
|
91
118
|
this.rootBBox = this.interactionOverlay.getBoundingClientRect();
|
92
119
|
var rootWidth = this.rootBBox.width.toString();
|
93
120
|
var rootHeigth = this.rootBBox.height.toString();
|
94
|
-
|
121
|
+
this.interactionOverlay = cq.getInteractOverlay(this.containerId);
|
122
|
+
this.canvasMP = this.interactionOverlay.querySelector("#canvasMusicPlayer");
|
123
|
+
if (!this.canvasMP) {
|
95
124
|
this.canvasMP = document.createElementNS(constants_1.constants._SVGNS_, "svg");
|
96
125
|
this.canvasMP.setAttribute("id", "canvasMusicPlayer");
|
97
126
|
this.canvasMP.classList.add("canvas");
|
98
|
-
this.canvasMP.setAttribute("viewBox", ["0", "0", rootWidth, rootHeigth].join(" "))
|
127
|
+
//this.canvasMP.setAttribute("viewBox", ["0", "0", rootWidth, rootHeigth].join(" "))
|
99
128
|
}
|
100
129
|
this.canvasMP.innerHTML = ""; // will delete followerRect if present (usually when score is loaded)
|
101
|
-
this.interactionOverlay = cq.getInteractOverlay(this.containerId);
|
102
130
|
this.interactionOverlay.insertBefore(this.canvasMP, this.interactionOverlay.firstChild);
|
131
|
+
return this;
|
103
132
|
}
|
104
133
|
/**
|
105
134
|
* Initialize Player
|
@@ -111,20 +140,15 @@ class MusicProcessor {
|
|
111
140
|
if (event.name === "Set Tempo") {
|
112
141
|
that.tempo = event.data;
|
113
142
|
//that.pulse = (60000/ (event.data / 2 * 24))/10 //(60000/ (event.data * 24))/10000 //duration is in seconds
|
114
|
-
that.pulse = ((60 / event.data) * 1000
|
143
|
+
that.pulse = 120; //Math.floor((60 / event.data) * 1000 / 120)
|
115
144
|
}
|
116
145
|
if (event.name === 'Note on' && event.velocity !== 0) {
|
117
146
|
var track = event.track;
|
118
147
|
var time = event.tick;
|
119
|
-
var key = track.toString() + "," + event.byteIndex.toString();
|
120
|
-
if (!that.midiDurationMap.has(key)) {
|
121
|
-
return;
|
122
|
-
}
|
123
|
-
var duration = that.midiDurationMap.get(key).duration;
|
124
148
|
that.restartTime = event.tick;
|
125
|
-
that.highlight(time,
|
149
|
+
var duration = that.highlight(time, event.noteName, "midi") / 1000; // duratioin must be in seconds
|
126
150
|
if (!that.isFirefox) {
|
127
|
-
that.drawFollowerRect()
|
151
|
+
//that.drawFollowerRect()
|
128
152
|
}
|
129
153
|
if (that.instruments != undefined) {
|
130
154
|
var instr = that.instruments[track - 2];
|
@@ -133,17 +157,40 @@ class MusicProcessor {
|
|
133
157
|
}
|
134
158
|
});
|
135
159
|
this.midiPlayer.loadArrayBuffer(buffer_1.Buffer.from(this.midi, "base64"));
|
136
|
-
this.mapDurations()
|
160
|
+
//this.mapDurations()
|
137
161
|
if (this.instruments == undefined) { // instruments only have to be updated, when new instrument (= track) is added
|
138
162
|
this.context = new ac();
|
139
163
|
this.instruments = new Array(this.midiPlayer.getEvents().length - 1);
|
140
164
|
this.initMidiInstruments();
|
141
165
|
}
|
142
166
|
}
|
143
|
-
//some change
|
144
167
|
/**
|
145
|
-
*
|
168
|
+
* Find Durations from timemap based on id of note.
|
169
|
+
* @param id id of note
|
170
|
+
* @returns duration or default 1 when no duration could be computed.
|
146
171
|
*/
|
172
|
+
findDuration(id) {
|
173
|
+
var _a, _b;
|
174
|
+
var dur;
|
175
|
+
var start;
|
176
|
+
var end;
|
177
|
+
for (let i = 0; i < this.timemap.length; i++) {
|
178
|
+
var tm = this.timemap[i];
|
179
|
+
if ((_a = tm.on) === null || _a === void 0 ? void 0 : _a.includes(id)) {
|
180
|
+
start = tm.tstamp;
|
181
|
+
}
|
182
|
+
if ((_b = tm.off) === null || _b === void 0 ? void 0 : _b.includes(id)) {
|
183
|
+
end = tm.tstamp;
|
184
|
+
dur = end - start;
|
185
|
+
break;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
return dur || 1;
|
189
|
+
}
|
190
|
+
/**
|
191
|
+
* Stop playing
|
192
|
+
*
|
193
|
+
**/
|
147
194
|
stopMidiInstruments() {
|
148
195
|
var _a;
|
149
196
|
document.dispatchEvent(this.playEndEvent);
|
@@ -196,7 +243,8 @@ class MusicProcessor {
|
|
196
243
|
});
|
197
244
|
this.midiPlayer.division = 120;
|
198
245
|
//this.player.tempo = this.tempo
|
199
|
-
this.midiPlayer.tick = this.restartTime;
|
246
|
+
this.midiPlayer.tick = this.restartTime || 0;
|
247
|
+
console.log("tick", this.midiPlayer.tick);
|
200
248
|
this.midiPlayer.skipToTick(this.restartTime);
|
201
249
|
this.midiPlayer.play();
|
202
250
|
document.dispatchEvent(this.playStartEvent);
|
@@ -204,54 +252,163 @@ class MusicProcessor {
|
|
204
252
|
}
|
205
253
|
///// LISTENERS ////
|
206
254
|
setListeners() {
|
255
|
+
var _a, _b, _c;
|
207
256
|
var that = this;
|
208
|
-
if (this.midiTimes
|
257
|
+
if (!this.midiTimes) {
|
209
258
|
return;
|
210
259
|
}
|
211
260
|
var it = this.midiTimes.values();
|
212
261
|
var result = it.next();
|
213
262
|
while (!result.done) {
|
214
263
|
var arr = result.value;
|
215
|
-
arr.forEach(
|
264
|
+
arr.forEach(noteId => {
|
216
265
|
var _a;
|
217
|
-
|
266
|
+
const note = cq.getVrvSVG(this.containerId).querySelector(`#${noteId}`);
|
267
|
+
if (!note)
|
218
268
|
return;
|
219
|
-
note.addEventListener("currentNote", this.
|
269
|
+
note.addEventListener("currentNote", this.setCurrentHighlightHandler);
|
220
270
|
var id = ((_a = note.querySelector(".notehead")) === null || _a === void 0 ? void 0 : _a.id) || note.id;
|
221
271
|
var interactRect = cq.getInteractOverlay(that.containerId).querySelector("#scoreRects g[refId=\"" + id + "\"]");
|
222
|
-
interactRect.addEventListener("click", this.startPointHandler);
|
272
|
+
interactRect === null || interactRect === void 0 ? void 0 : interactRect.addEventListener("click", this.startPointHandler);
|
223
273
|
});
|
224
274
|
result = it.next();
|
225
275
|
}
|
226
276
|
this.container.querySelector("#playBtn").addEventListener("click", this.playBtn);
|
227
277
|
this.container.querySelector("#rewindBtn").addEventListener("click", this.rewindBtn);
|
228
278
|
this.container.addEventListener("timeupdate", this.fetchAudioSeconds, true);
|
279
|
+
//this.container.querySelector("#recordAlignment")?.addEventListener("click", this.resetSpacebarCount)
|
280
|
+
(_a = this.container.querySelector("#audioSlider")) === null || _a === void 0 ? void 0 : _a.addEventListener("keydown", this.preventAudioPause);
|
281
|
+
(_b = this.container.querySelector("#audioSlider")) === null || _b === void 0 ? void 0 : _b.addEventListener("play", this.handleCountdown);
|
282
|
+
(_c = this.container.querySelector("#exportAlignment")) === null || _c === void 0 ? void 0 : _c.addEventListener("click", this.downloadAlignmentHandler);
|
283
|
+
//["ended", "pause"].forEach(ev => this.container.querySelector("#audioSlider")?.addEventListener(ev, function () { that.resetListeners() }))
|
284
|
+
document.addEventListener("keydown", this.recAlignmentManually);
|
229
285
|
}
|
230
286
|
removeListeners() {
|
287
|
+
var _a, _b, _c;
|
231
288
|
var that = this;
|
232
289
|
if (this.midiTimes == undefined) {
|
233
290
|
return;
|
234
291
|
}
|
235
292
|
var it = this.midiTimes.values();
|
236
|
-
console.log("midiTimes", this.midiTimes);
|
237
293
|
var result = it.next();
|
238
294
|
while (!result.done) {
|
239
295
|
var arr = result.value;
|
240
|
-
arr.forEach(
|
296
|
+
arr.forEach(noteId => {
|
241
297
|
var _a;
|
242
|
-
note.
|
298
|
+
const note = cq.getVrvSVG(this.containerId).querySelector(`#${noteId}`);
|
299
|
+
if (!note)
|
300
|
+
return;
|
301
|
+
note.removeEventListener("currentNote", this.setCurrentHighlightHandler);
|
243
302
|
var id = ((_a = note.querySelector(".notehead")) === null || _a === void 0 ? void 0 : _a.id) || note.id;
|
244
303
|
var interactRect = cq.getInteractOverlay(that.containerId).querySelector("#scoreRects g[refId=\"" + id + "\"]");
|
245
|
-
interactRect.removeEventListener("click", this.startPointHandler);
|
304
|
+
interactRect === null || interactRect === void 0 ? void 0 : interactRect.removeEventListener("click", this.startPointHandler);
|
246
305
|
});
|
247
306
|
result = it.next();
|
248
307
|
}
|
249
308
|
this.container.removeEventListener("timeupdate", this.fetchAudioSeconds, true);
|
309
|
+
(_a = this.container.querySelector("#audioSlider")) === null || _a === void 0 ? void 0 : _a.removeEventListener("keydown", this.preventAudioPause);
|
310
|
+
(_b = this.container.querySelector("#audioSlider")) === null || _b === void 0 ? void 0 : _b.removeEventListener("play", this.handleCountdown);
|
311
|
+
(_c = this.container.querySelector("#exportAlignment")) === null || _c === void 0 ? void 0 : _c.addEventListener("click", this.downloadAlignmentHandler);
|
312
|
+
//["ended", "pause"].forEach(ev => this.container.querySelector("#audioSlider")?.removeEventListener(ev, function () { that.resetListeners() }))
|
313
|
+
document.removeEventListener("keydown", this.recAlignmentManually);
|
250
314
|
}
|
251
315
|
resetListeners() {
|
252
316
|
this.removeListeners();
|
253
317
|
this.setListeners();
|
254
318
|
}
|
319
|
+
/**
|
320
|
+
* Record an alignment by pressing the space button.
|
321
|
+
* For each press the id of the next measure will be saved.
|
322
|
+
* @param e
|
323
|
+
* @returns
|
324
|
+
*/
|
325
|
+
recordAlignment(e) {
|
326
|
+
var _a;
|
327
|
+
var audioSlider = this.container.querySelector("#audioSlider");
|
328
|
+
var recToggle = this.container.querySelector("#recordAlignment");
|
329
|
+
if (audioSlider === null || audioSlider === void 0 ? void 0 : audioSlider.paused)
|
330
|
+
return;
|
331
|
+
if (!(recToggle === null || recToggle === void 0 ? void 0 : recToggle.checked))
|
332
|
+
return;
|
333
|
+
if (e.code === "Space") {
|
334
|
+
if (this.currentHighlight) {
|
335
|
+
this.spaceCount = parseInt(this.currentHighlight.getAttribute("n"));
|
336
|
+
(_a = this.audioTimes) === null || _a === void 0 ? void 0 : _a.forEach((v, k) => {
|
337
|
+
if (k > audioSlider.currentTime) {
|
338
|
+
this.audioTimes.delete(k);
|
339
|
+
}
|
340
|
+
});
|
341
|
+
}
|
342
|
+
e.preventDefault();
|
343
|
+
if (!this.audioTimes || this.spaceCount === 0) {
|
344
|
+
this.audioTimes = new Map();
|
345
|
+
}
|
346
|
+
this.spaceCount += 1;
|
347
|
+
const measure = this.container.querySelector(`#vrvSVG .measure[n='${this.spaceCount}']`);
|
348
|
+
this.currentHighlight = measure;
|
349
|
+
this.drawFollowerRect();
|
350
|
+
this.audioTimes.set(audioSlider.currentTime, [measure.id]);
|
351
|
+
console.log(this.audioTimes);
|
352
|
+
}
|
353
|
+
}
|
354
|
+
downloadAlignment(e) {
|
355
|
+
const d = new Date();
|
356
|
+
const fileName = d.getUTCFullYear()
|
357
|
+
+ ("0" + d.getDate()).slice(-2)
|
358
|
+
+ ("0" + d.getMonth()).slice(-2)
|
359
|
+
+ "_"
|
360
|
+
+ ("0" + d.getHours()).slice(-2)
|
361
|
+
+ ("0" + d.getMinutes()).slice(-2)
|
362
|
+
+ ("0" + d.getSeconds()).slice(-2)
|
363
|
+
+ "_"
|
364
|
+
+ "alignment_" + this.containerId + ".json";
|
365
|
+
const audioTimesJson = JSON.stringify(Object.fromEntries(this.audioTimes));
|
366
|
+
console.log("Audio JSON", audioTimesJson);
|
367
|
+
this.download(fileName, audioTimesJson);
|
368
|
+
}
|
369
|
+
download(file, text) {
|
370
|
+
//creating an invisible element
|
371
|
+
var element = document.createElement('a');
|
372
|
+
element.setAttribute('href', 'data:text/plain;charset=utf-8, '
|
373
|
+
+ encodeURIComponent(text));
|
374
|
+
element.setAttribute('download', file);
|
375
|
+
document.body.appendChild(element);
|
376
|
+
element.click();
|
377
|
+
document.body.removeChild(element);
|
378
|
+
}
|
379
|
+
/**
|
380
|
+
* Set countdown for given audioElement
|
381
|
+
* @param audioElement
|
382
|
+
*/
|
383
|
+
countdown(audioElement, seconds = 5) {
|
384
|
+
audioElement.pause();
|
385
|
+
var that = this;
|
386
|
+
that.container.querySelector("#recordDiv label").textContent = seconds.toString();
|
387
|
+
seconds--;
|
388
|
+
Tone.start();
|
389
|
+
const synth = new Tone.Synth().toDestination();
|
390
|
+
function cdSound() {
|
391
|
+
synth.triggerAttackRelease("C5", "128n", undefined, 0.25);
|
392
|
+
}
|
393
|
+
cdSound();
|
394
|
+
var timer = setInterval(function () {
|
395
|
+
var _a;
|
396
|
+
if (seconds === 1) {
|
397
|
+
synth.triggerAttackRelease("C6", "4n", undefined, 0.25);
|
398
|
+
}
|
399
|
+
else if (seconds > 0) {
|
400
|
+
cdSound();
|
401
|
+
}
|
402
|
+
that.container.querySelector("#recordDiv label").textContent = seconds.toString();
|
403
|
+
seconds--;
|
404
|
+
if (seconds < 0) {
|
405
|
+
clearInterval(timer);
|
406
|
+
(_a = that.container.querySelector("#audioSlider")) === null || _a === void 0 ? void 0 : _a.removeEventListener("play", that.handleCountdown);
|
407
|
+
audioElement.play();
|
408
|
+
that.container.querySelector("#recordDiv label").textContent = "rec";
|
409
|
+
}
|
410
|
+
}, 1000);
|
411
|
+
}
|
255
412
|
/**
|
256
413
|
* Separate Listeners to set player options externally
|
257
414
|
*/
|
@@ -266,7 +423,7 @@ class MusicProcessor {
|
|
266
423
|
return;
|
267
424
|
if (e.code === "Space") {
|
268
425
|
e.preventDefault();
|
269
|
-
if (e.shiftKey
|
426
|
+
if (e.shiftKey) {
|
270
427
|
this.context.resume().then(() => this.playMidi());
|
271
428
|
}
|
272
429
|
else if (typeof this.midiPlayer != undefined) {
|
@@ -279,11 +436,12 @@ class MusicProcessor {
|
|
279
436
|
* @param file Bufferarray of imported file (Blob)
|
280
437
|
*/
|
281
438
|
align(file) {
|
439
|
+
// this listener is not in setListener function since we can get into an infinite loop easily
|
440
|
+
["ended", "pause"].forEach(ev => this.container.querySelector("#audioSlider").addEventListener(ev, function () { that.resetListeners(); }));
|
282
441
|
var that = this;
|
283
442
|
var fd = new FormData();
|
284
|
-
fd.append('mei', new Blob([meiConverter.docToMei(this.
|
443
|
+
fd.append('mei', new Blob([meiConverter.docToMei(this.currentMEI)]));
|
285
444
|
fd.append('audio', file);
|
286
|
-
console.log("in musicprocessor", this.mei, file);
|
287
445
|
var devUrl = 'http://localhost:8001/align';
|
288
446
|
axios_1.default.post(devUrl, fd, {
|
289
447
|
headers: {
|
@@ -297,80 +455,16 @@ class MusicProcessor {
|
|
297
455
|
if (!that.audioTimes.has(val)) {
|
298
456
|
that.audioTimes.set(val, new Array());
|
299
457
|
}
|
300
|
-
that.audioTimes.get(val).push(that.container.querySelector("#" + key))
|
458
|
+
//that.audioTimes.get(val).push(that.container.querySelector("#" + key))
|
459
|
+
that.audioTimes.get(val).push(key);
|
301
460
|
}
|
302
461
|
}).catch((error) => {
|
462
|
+
alert(["Aligning Service is not Available:", error, ".", "\nToggle the record button at the audio slider to activate manual measure alignment."].join(" "));
|
303
463
|
console.log(["An error occured while aligning the soundfile:", error].join(" "));
|
304
464
|
});
|
305
465
|
}
|
306
466
|
/**
|
307
|
-
*
|
308
|
-
*/
|
309
|
-
mapDurations() {
|
310
|
-
var durationMap = new Map(); // key: tracknumber,byteindex
|
311
|
-
var mapByNote = new Map();
|
312
|
-
var eventTracks = this.midiPlayer.getEvents();
|
313
|
-
eventTracks.forEach(eventArray => {
|
314
|
-
//@ts-ignore
|
315
|
-
Array.from(eventArray).forEach((event, eventIdx) => {
|
316
|
-
var e = event;
|
317
|
-
if (e.name === "Set Tempo") {
|
318
|
-
this.tempo = e.data;
|
319
|
-
this.pulse = ((60 / e.data) * 1000) / 120;
|
320
|
-
}
|
321
|
-
else if (e.name === "Note on") {
|
322
|
-
var time = e.tick; //* this.pulse //* 1000 * 2
|
323
|
-
var notes = this.getClosestEntry(time, "midi");
|
324
|
-
if (notes == undefined) {
|
325
|
-
return;
|
326
|
-
}
|
327
|
-
//iterate because notes can be in a chord
|
328
|
-
notes.forEach(note => {
|
329
|
-
var meiNote = this.mei.getElementById(note.id);
|
330
|
-
var staffNumber = parseInt(meiNote.closest("staff").getAttribute("n")) + 1;
|
331
|
-
if (!meiNote.hasAttribute("grace")) {
|
332
|
-
var key = e.track.toString() + "," + e.byteIndex.toString();
|
333
|
-
if (!durationMap.has(key) && e.track === staffNumber) {
|
334
|
-
if (!meiNote.hasAttribute("dur")) {
|
335
|
-
meiNote = meiNote.closest("chord");
|
336
|
-
}
|
337
|
-
var baseDur = this.getDur(parseInt(meiNote.getAttribute("dur")), parseInt(meiNote.getAttribute("dots")) || 0, 4);
|
338
|
-
//find any prolongated Notes
|
339
|
-
var tie = this.mei.querySelector("tie[startid='#" + note.id + "']");
|
340
|
-
if (tie !== null) {
|
341
|
-
var endid = tie.getAttribute("endid"); //endid alway includes # at beginnig
|
342
|
-
var prolongNote = this.mei.querySelector(endid);
|
343
|
-
if (prolongNote !== null) {
|
344
|
-
var pnDur = prolongNote.getAttribute("dur");
|
345
|
-
var pnDot = prolongNote.getAttribute("dots");
|
346
|
-
baseDur += this.getDur(parseInt(pnDur), parseInt(pnDot) || 0, 4);
|
347
|
-
}
|
348
|
-
}
|
349
|
-
//concat duration
|
350
|
-
var dur = baseDur * 60 / this.tempo;
|
351
|
-
var valueFound = false;
|
352
|
-
var it = durationMap.values();
|
353
|
-
var res = it.next();
|
354
|
-
if (!valueFound) { // why do I check here?
|
355
|
-
durationMap.set(key, { note: note, duration: dur, tick: e.tick });
|
356
|
-
mapByNote.set(note, { duration: dur, tick: e.tick });
|
357
|
-
}
|
358
|
-
else {
|
359
|
-
//console.log(key, note, dur, e.tick)
|
360
|
-
//console.log(durationMap.get(key))
|
361
|
-
}
|
362
|
-
}
|
363
|
-
}
|
364
|
-
});
|
365
|
-
}
|
366
|
-
});
|
367
|
-
});
|
368
|
-
this.midiDurationMap = durationMap;
|
369
|
-
this.durationMapByNote = mapByNote;
|
370
|
-
//console.log(this.durationMap, this.durationMapByNote)
|
371
|
-
}
|
372
|
-
/**
|
373
|
-
* Computation of time and midi times have some divergence (by floating number)
|
467
|
+
* Time maps and actual values of audio elements or midi events might have some divergence (by floating number)
|
374
468
|
* Finding closest entry is sufficient, but has to be made for all entries, since the miditimes-iterator is not ordered.
|
375
469
|
* @param time
|
376
470
|
* @returns
|
@@ -379,15 +473,28 @@ class MusicProcessor {
|
|
379
473
|
var targetEntry;
|
380
474
|
var temp = Infinity;
|
381
475
|
var entries;
|
476
|
+
var map;
|
382
477
|
if (source === "midi") {
|
383
|
-
|
478
|
+
map = this.midiTimes;
|
384
479
|
}
|
385
480
|
else if (source === "audio") {
|
386
|
-
|
387
|
-
|
481
|
+
map = this.audioTimes;
|
482
|
+
// const audioMap = new Map<number, string[]>()
|
483
|
+
// map.forEach((v, k) => {
|
484
|
+
// audioMap.set(k, v.map(v => v.id)) // map ids, since highlight function only works with ids, not elements
|
485
|
+
// })
|
486
|
+
// map = audioMap
|
487
|
+
}
|
488
|
+
entries = map.entries();
|
489
|
+
targetEntry = map.get(time);
|
490
|
+
if (targetEntry)
|
491
|
+
return targetEntry;
|
388
492
|
for (const [key, value] of entries) {
|
389
493
|
var diff = Math.abs(time - key);
|
390
|
-
if
|
494
|
+
// check if diff is in range of next time to be sufficiently percieved as beeing on time
|
495
|
+
var diffCondition = diff < temp;
|
496
|
+
diffCondition && (diffCondition = source === "audio" ? diff <= 0.2 : true);
|
497
|
+
if (diffCondition) {
|
391
498
|
targetEntry = value;
|
392
499
|
temp = diff;
|
393
500
|
}
|
@@ -417,24 +524,58 @@ class MusicProcessor {
|
|
417
524
|
this.midi = midi;
|
418
525
|
return this;
|
419
526
|
}
|
527
|
+
setTimemap(tm) {
|
528
|
+
this.timemap = tm;
|
529
|
+
this.midiTimes = new Map();
|
530
|
+
this.timemap.forEach(tm => {
|
531
|
+
if (tm.on)
|
532
|
+
this.midiTimes.set(tm.tstamp, tm.on);
|
533
|
+
});
|
534
|
+
return this;
|
535
|
+
}
|
420
536
|
/**
|
421
537
|
* Highlight playing Elements
|
422
|
-
* @param time Time at which Element is played (in ms)
|
423
|
-
* @param
|
538
|
+
* @param time Time at which Element is played (in ticks or ms)
|
539
|
+
* @param noteName noteName is important to find the correct element for the current time
|
424
540
|
*/
|
425
|
-
highlight(time,
|
426
|
-
|
541
|
+
highlight(time, noteName, source) {
|
542
|
+
noteName = noteName.replace("#", "s").replace("b", "f"); // make sure the notname is compatible with the mei accid names
|
543
|
+
noteName = noteName.toLowerCase();
|
544
|
+
const timeMS = source === "audio" ? time : (time / (this.pulse)) * (60000 / this.tempo);
|
545
|
+
var soundDur;
|
546
|
+
var highlightElements = this.getClosestEntry(timeMS, source);
|
547
|
+
if (!highlightElements)
|
548
|
+
return;
|
549
|
+
highlightElements.forEach((id) => {
|
550
|
+
if (this.container.querySelector(`#${id}`)) { //.classList.contains("measure")) {
|
551
|
+
this.currentHighlight = this.container.querySelector(`#${id}`); //highlightElements[0]
|
552
|
+
const meiElement = this.currentMEI.querySelector(`#${id}`);
|
553
|
+
const pname = meiElement.getAttribute("pname");
|
554
|
+
var accid = meiElement.getAttribute("accid") || meiElement.getAttribute("accid.ges");
|
555
|
+
accid = accid === "n" || !accid ? "" : accid;
|
556
|
+
const oct = meiElement.getAttribute("oct");
|
557
|
+
const meiNoteName = pname + accid + oct;
|
558
|
+
const enhUpNoteName = (mappings_1.enharmonicToCross.get(pname + accid) || "") + oct;
|
559
|
+
const enhDownNoteName = (mappings_1.enharmonicToB.get(pname + accid) || "") + oct;
|
560
|
+
const noteNameVersions = [meiNoteName, enhUpNoteName, enhDownNoteName];
|
561
|
+
if (noteNameVersions.includes(noteName)) {
|
562
|
+
soundDur = this.findDuration(id);
|
563
|
+
}
|
564
|
+
}
|
565
|
+
});
|
427
566
|
this.timeouts = new Array();
|
428
|
-
|
429
|
-
this.
|
430
|
-
|
431
|
-
|
567
|
+
highlightElements.forEach((id) => {
|
568
|
+
const el = this.container.querySelector(`#${id}`);
|
569
|
+
this.addClass(el, currentlyPlayingFlag).then(() => {
|
570
|
+
if (source === "audio") { //we have no proper duration display yet, so no red coloring for notes, just the followerRect for the whole measure
|
571
|
+
el.classList.remove(currentlyPlayingFlag);
|
432
572
|
return;
|
433
573
|
}
|
434
|
-
var to = setTimeout(() => {
|
574
|
+
var to = setTimeout(() => { el.classList.remove(currentlyPlayingFlag); }, this.findDuration(id));
|
435
575
|
this.timeouts.push(to);
|
436
576
|
});
|
437
577
|
});
|
578
|
+
return soundDur || 500;
|
438
579
|
}
|
439
580
|
/**
|
440
581
|
* Draw follower rectangle over all staves for last sounding element
|
@@ -451,13 +592,13 @@ class MusicProcessor {
|
|
451
592
|
this.canvasMP.appendChild(followerRect);
|
452
593
|
}
|
453
594
|
var margin = 5;
|
454
|
-
var
|
455
|
-
var parentMeasureRect = this.
|
595
|
+
var ptCurrentHighlightElement = coordinates.getDOMMatrixCoordinates(this.currentHighlight, this.canvasMP);
|
596
|
+
var parentMeasureRect = this.currentHighlight.closest(".measure").getBoundingClientRect();
|
456
597
|
var ptParentMeasure = coordinates.getDOMMatrixCoordinates(parentMeasureRect, this.canvasMP);
|
457
598
|
var upperBound = (ptParentMeasure.top - margin);
|
458
599
|
var lowerBound = (ptParentMeasure.bottom + margin);
|
459
|
-
var leftBound = (
|
460
|
-
var rightBound = (
|
600
|
+
var leftBound = (ptCurrentHighlightElement.left - margin);
|
601
|
+
var rightBound = (ptCurrentHighlightElement.right + margin);
|
461
602
|
followerRect.setAttribute("id", followerRectID);
|
462
603
|
followerRect.setAttribute("y", upperBound.toString());
|
463
604
|
followerRect.setAttribute("x", leftBound.toString());
|
@@ -465,6 +606,8 @@ class MusicProcessor {
|
|
465
606
|
followerRect.setAttribute("height", (lowerBound - upperBound).toString());
|
466
607
|
}
|
467
608
|
hasContainerFocus() {
|
609
|
+
if (!document.getElementById(this.containerId))
|
610
|
+
return false;
|
468
611
|
return document.getElementById(this.containerId).classList.contains("activeContainer");
|
469
612
|
}
|
470
613
|
///SYNTH////
|
@@ -474,7 +617,7 @@ class MusicProcessor {
|
|
474
617
|
}
|
475
618
|
let note = newNote.pname;
|
476
619
|
let dur = newNote.dur + "n";
|
477
|
-
var accid = newNote.accid
|
620
|
+
var accid = typeof newNote.accid === "string" ? newNote.accid.replace("f", "b").replace("s", "#").replace("n", "") : "";
|
478
621
|
if (typeof newNote.keysig !== "undefined" && newNote.keysig !== "0") {
|
479
622
|
let signMap;
|
480
623
|
if (newNote.keysig.charAt(1) === "s") {
|
@@ -505,13 +648,13 @@ class MusicProcessor {
|
|
505
648
|
}
|
506
649
|
if (!note.includes("undefined") && !dur.includes("undefined")) {
|
507
650
|
dur = "16n";
|
508
|
-
synth.triggerAttackRelease(note, dur);
|
651
|
+
synth.triggerAttackRelease(note, dur, undefined, 0.3);
|
509
652
|
Tone.start();
|
510
653
|
}
|
511
654
|
}
|
512
655
|
// UTILS
|
513
656
|
setMEI(mei) {
|
514
|
-
this.
|
657
|
+
this.currentMEI = mei;
|
515
658
|
return this;
|
516
659
|
}
|
517
660
|
setMidiTimes(midiTimes) {
|
@@ -548,6 +691,21 @@ class MusicProcessor {
|
|
548
691
|
setRestartTimeByElement(el) {
|
549
692
|
throw Error("Not yet implemented");
|
550
693
|
}
|
694
|
+
/**
|
695
|
+
* Set audioTimes map for alignment.
|
696
|
+
* IDs are set new afterwards to account for different MEI.
|
697
|
+
* Assumes that Score and Map have the same number of measures.
|
698
|
+
* @param audioTimes
|
699
|
+
* @returns
|
700
|
+
*/
|
701
|
+
setAudioTimes(audioTimes) {
|
702
|
+
this.audioTimes = audioTimes;
|
703
|
+
this.container.querySelectorAll(`#vrvSVG .measure`).forEach((m, i) => {
|
704
|
+
const key = Array.from(this.audioTimes.keys())[i];
|
705
|
+
this.audioTimes.set(key, [m.id]);
|
706
|
+
});
|
707
|
+
return this;
|
708
|
+
}
|
551
709
|
getIsPlaying() {
|
552
710
|
var _a;
|
553
711
|
return (_a = this.midiPlayer) === null || _a === void 0 ? void 0 : _a.isPlaying();
|