vibe-editor 0.0.0 → 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.
Files changed (38) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +12 -9
  3. package/package.json +11 -5
  4. package/src/scripts/js/Core.js +212 -186
  5. package/src/scripts/js/MusicProcessor.js +286 -128
  6. package/src/scripts/js/{VerovioScoreEditor.js → VIBE.js} +62 -28
  7. package/src/scripts/js/assets/mei_template.js +5 -1
  8. package/src/scripts/js/datastructures/MeasureMatrix.js +6 -85
  9. package/src/scripts/js/datastructures/ScoreGraph.js +1 -1
  10. package/src/scripts/js/entry.js +3 -2
  11. package/src/scripts/js/gui/Annotations.js +188 -111
  12. package/src/scripts/js/gui/HarmonyLabel.js +26 -2
  13. package/src/scripts/js/gui/ScoreManipulator.js +61 -31
  14. package/src/scripts/js/gui/Tabbar.js +41 -21
  15. package/src/scripts/js/gui/Toolbar.js +4 -4
  16. package/src/scripts/js/handlers/AnnotationChangeHandler.js +131 -60
  17. package/src/scripts/js/handlers/ClickModeHandler.js +406 -143
  18. package/src/scripts/js/handlers/CustomToolbarHandler.js +26 -24
  19. package/src/scripts/js/handlers/GlobalKeyboardHandler.js +12 -7
  20. package/src/scripts/js/handlers/InsertModeHandler.js +26 -32
  21. package/src/scripts/js/handlers/KeyModeHandler.js +12 -86
  22. package/src/scripts/js/handlers/LabelHandler.js +3 -2
  23. package/src/scripts/js/handlers/PhantomElementHandler.js +1 -1
  24. package/src/scripts/js/handlers/ScoreManipulatorHandler.js +101 -14
  25. package/src/scripts/js/handlers/SelectionHandler.js +80 -36
  26. package/src/scripts/js/handlers/SideBarHandler.js +10 -3
  27. package/src/scripts/js/handlers/WindowHandler.js +25 -4
  28. package/src/scripts/js/utils/DOMCreator.js +1 -1
  29. package/src/scripts/js/utils/MEIConverter.js +13 -1
  30. package/src/scripts/js/utils/MEIOperations.js +180 -187
  31. package/src/scripts/js/utils/Mouse2SVG.js +200 -43
  32. package/src/scripts/js/utils/ReactWrapper.js +46 -0
  33. package/src/scripts/js/utils/RectWrapper.js +10 -0
  34. package/src/scripts/js/utils/SVGEditor.js +94 -3
  35. package/src/scripts/js/utils/VerovioWrapper.js +6 -1
  36. package/src/scripts/js/utils/convenienceQueries.js +2 -0
  37. package/src/scripts/js/utils/mappings.js +322 -56
  38. 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, 100, "audio");
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.setCurrentNoteHandler = (function setCurrentNoteHandler(e) {
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
- var it = this.durationMap.values();
51
- var res = it.next();
52
- while (!res.done) {
53
- if (playingNote.id === res.value.note.id) {
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
- res = it.next();
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(n, className) {
92
+ this.addClass = (function addClass(el, className) {
66
93
  return new Promise(resolve => {
67
- n.classList.add(className);
68
- n.dispatchEvent(this.noteEvent);
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
- if (this.canvasMP == undefined) {
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) / 120;
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, duration * 1000, "midi");
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
- * Stop playing
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 == undefined) {
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(note => {
264
+ arr.forEach(noteId => {
216
265
  var _a;
217
- if (note == undefined)
266
+ const note = cq.getVrvSVG(this.containerId).querySelector(`#${noteId}`);
267
+ if (!note)
218
268
  return;
219
- note.addEventListener("currentNote", this.setCurrentNoteHandler);
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(note => {
296
+ arr.forEach(noteId => {
241
297
  var _a;
242
- note.removeEventListener("currentNote", this.setCurrentNoteHandler);
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 || document.getElementById("followerRect") !== null) {
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.mei)]));
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
- * Map all durations and notes to make them available asynchronically
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
- entries = this.midiTimes.entries();
478
+ map = this.midiTimes;
384
479
  }
385
480
  else if (source === "audio") {
386
- entries = this.audioTimes.entries();
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 (diff < temp) {
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 duration Duration of Element (in ms)
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, duration, source) {
426
- var notes = this.getClosestEntry(time, source);
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
- notes.forEach(n => {
429
- this.addClass(n, currentlyPlayingFlag).then(() => {
430
- if (source === "audio") { //we have no proper duration display yet, so no red coloring for notes, just the followerRect
431
- n.classList.remove(currentlyPlayingFlag);
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(() => { n.classList.remove(currentlyPlayingFlag); }, duration);
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 ptCurrentNote = coordinates.getDOMMatrixCoordinates(this.currentNote, this.canvasMP);
455
- var parentMeasureRect = this.currentNote.closest(".measure").getBoundingClientRect();
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 = (ptCurrentNote.left - margin);
460
- var rightBound = (ptCurrentNote.right + margin);
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 != undefined ? newNote.accid.replace("f", "b").replace("s", "#").replace("n", "") : "";
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.mei = mei;
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();