atome 0.5.7.5.9 → 0.5.7.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1676 @@
1
+ customElements.define("webaudio-pianoroll", class Pianoroll extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.noteIdCounter = 0;
5
+ this.editing = true
6
+ this.refuse = false
7
+ this.tool = 'create'
8
+ }
9
+
10
+ defineprop() {
11
+ const plist = this.module.properties;
12
+ for (let k in plist) {
13
+ const v = plist[k];
14
+ this["_" + k] = this.getAttr(k, v.value);
15
+ Object.defineProperty(this, k, {
16
+ get: () => {
17
+ return this["_" + k]
18
+ },
19
+ set: (val) => {
20
+ this["_" + k] = val;
21
+ if (typeof (this[v.observer]) == "function")
22
+ this[v.observer]();
23
+ }
24
+ });
25
+ }
26
+ }
27
+
28
+
29
+ marker(position, id, label) {
30
+ const playhead = document.createElement("div");
31
+ playhead.className = "marker";
32
+ playhead.style.position = "absolute";
33
+ playhead.style.left = `${(position - this.xoffset) * this.stepw + this.yruler + this.kbwidth}px`;
34
+
35
+ // Ajout de l'id et du label comme contenu de la div
36
+ playhead.id = id;
37
+ playhead.dataset.id = id;
38
+ playhead.dataset.label = label;
39
+ playhead.textContent = label;
40
+
41
+ // Création du trait rouge
42
+ const locator = document.createElement("div");
43
+ locator.style.position = "absolute";
44
+ locator.style.width = "2px"; // Épaisseur du trait
45
+ locator.style.height = "100%"; // S'étend sur toute la hauteur du conteneur
46
+ locator.style.backgroundColor = "red";
47
+ locator.style.left = '0px'; // Positionner au centre du playhead
48
+ locator.style.top = '0px';
49
+ locator.style.transform = "translateX(-50%)"; // Centrer précisément le trait
50
+
51
+ playhead.appendChild(locator);
52
+
53
+ playhead.addEventListener('click', () => {
54
+ console.log(`Playhead ID: ${id}`);
55
+ });
56
+
57
+ playhead.addEventListener('mousedown', (e) => {
58
+ const initialX = e.clientX;
59
+ const initialLeft = parseInt(playhead.style.left, 10);
60
+
61
+ const onMouseMove = (e) => {
62
+ const deltaX = e.clientX - initialX;
63
+ let newLeft = initialLeft + deltaX;
64
+
65
+ let newPosition = (newLeft - this.yruler - this.kbwidth) / this.stepw + this.xoffset;
66
+
67
+ newPosition = Math.round(newPosition / this.snap) * this.snap;
68
+
69
+ newLeft = (newPosition - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
70
+ playhead.style.left = `${newLeft}px`;
71
+
72
+ console.log(`Playhead ${id} moved to quantized position: ${newPosition}`);
73
+
74
+ const markerEvent = this.sequence.find(ev => ev.id === id && ev.type === 'marker');
75
+ if (markerEvent) {
76
+ markerEvent.t = newPosition;
77
+ console.log(`Updated event in sequence for marker ID: ${id}, new position: ${newPosition}`);
78
+ }
79
+ };
80
+
81
+ const onMouseUp = () => {
82
+ document.removeEventListener('mousemove', onMouseMove);
83
+ document.removeEventListener('mouseup', onMouseUp);
84
+ };
85
+
86
+ document.addEventListener('mousemove', onMouseMove);
87
+ document.addEventListener('mouseup', onMouseUp);
88
+ });
89
+
90
+ this.canvas.parentElement.appendChild(playhead);
91
+ const ev = {
92
+ id: id,
93
+ t: position,
94
+ n: 0, // makers have no note info
95
+ g: 0, // makers have no length
96
+ f: 0, // not selected
97
+ type: 'marker',
98
+ details: {label: label, element: playhead},
99
+ textureApplied: false
100
+ };
101
+
102
+ this.sequence.push(ev);
103
+ }
104
+
105
+ removeMarker(id) {
106
+ const playhead = document.getElementById(id);
107
+ if (playhead) {
108
+ playhead.parentElement.removeChild(playhead);
109
+ console.log(`Playhead with ID: ${id} has been removed.`);
110
+ } else {
111
+ console.log(`Playhead with ID: ${id} does not exist.`);
112
+ }
113
+ const eventIndex = this.sequence.findIndex(ev => ev.id === id && ev.type === 'marker');
114
+ if (eventIndex !== -1) {
115
+ this.sequence.splice(eventIndex, 1);
116
+ console.log(`Event associated with marker ID: ${id} has been removed from the sequence.`);
117
+ } else {
118
+ console.log(`No event found in the sequence for marker ID: ${id}.`);
119
+ }
120
+ }
121
+
122
+ connectedCallback() {
123
+ let root;
124
+ root = this;
125
+ this.module = {
126
+ is: "webaudio-pianoroll",
127
+ properties: {
128
+ lowestnote: {type: Number, value: 33, observer: 'layout'},
129
+ highestnote: {type: Number, value: 39, observer: 'layout'},
130
+ width: {type: Number, value: 640, observer: 'layout'},
131
+ height: {type: Number, value: 320, observer: 'layout'},
132
+ timebase: {type: Number, value: 16, observer: 'layout'},
133
+ editmode: {type: String, value: "dragpoly"},
134
+ xrange: {type: Number, value: 16, observer: 'layout'},
135
+ yrange: {type: Number, value: 16, observer: 'layout'},
136
+ xoffset: {type: Number, value: 0, observer: 'layout'},
137
+ yoffset: {type: Number, value: 60, observer: 'layout'},
138
+ grid: {type: Number, value: 4},
139
+ snap: {type: Number, value: 1},
140
+ wheelzoom: {type: Number, value: 0},
141
+ wheelzoomx: {type: Number, value: 0},
142
+ wheelzoomy: {type: Number, value: 0},
143
+ xscroll: {type: Number, value: 0},
144
+ yscroll: {type: Number, value: 0},
145
+ gridnoteratio: {type: Number, value: 0.5, observer: 'updateTimer'},
146
+ xruler: {type: Number, value: 24, observer: 'layout'},
147
+ yruler: {type: Number, value: 24, observer: 'layout'},
148
+ octadj: {type: Number, value: -1},
149
+ cursor: {type: Number, value: 0, observer: 'redrawMarker'},
150
+ markstart: {type: Number, value: 0, observer: 'redrawMarker'},
151
+ markend: {type: Number, value: 8, observer: 'redrawMarker'},
152
+ defvelo: {type: Number, value: 100},
153
+ collt: {type: String, value: "#ccc"},
154
+ coldk: {type: String, value: "#aaa"},
155
+ colgrid: {type: String, value: "#666"},
156
+ colnote: {type: String, value: "#f22"},
157
+ colnotesel: {type: String, value: "#0f0"},
158
+ colnoteborder: {type: String, value: "#000"},
159
+ colnoteselborder: {type: String, value: "#fff"},
160
+ colrulerbg: {type: String, value: "#666"},
161
+ colrulerfg: {type: String, value: "#fff"},
162
+ colrulerborder: {type: String, value: "#000"},
163
+ colselarea: {type: String, value: "rgba(0,0,0,0.3)"},
164
+ bgsrc: {type: String, value: null, observer: 'layout'},
165
+ cursorsrc: {
166
+ type: String,
167
+ value: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj4NCjxwYXRoIGZpbGw9InJnYmEoMjU1LDEwMCwxMDAsMC44KSIgZD0iTTAsMSAyNCwxMiAwLDIzIHoiLz4NCjwvc3ZnPg0K"
168
+ },
169
+ cursoroffset: {type: Number, value: 0},
170
+ markstartsrc: {
171
+ type: String,
172
+ value: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4NCjxwYXRoIGZpbGw9IiMwYzAiIGQ9Ik0wLDEgMjQsMSAwLDIzIHoiLz4NCjwvc3ZnPg0K"
173
+ },
174
+ markstartoffset: {type: Number, value: 0},
175
+ markendsrc: {
176
+ type: String,
177
+ value: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4NCjxwYXRoIGZpbGw9IiMwYzAiIGQ9Ik0wLDEgMjQsMSAyNCwyMyB6Ii8+DQo8L3N2Zz4NCg=="
178
+ },
179
+ markendoffset: {type: Number, value: -24},
180
+ kbsrc: {
181
+ type: String,
182
+ value: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSI0ODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8cGF0aCBmaWxsPSIjZmZmIiBzdHJva2U9IiMwMDAiIGQ9Ik0wLDAgaDI0djQ4MGgtMjR6Ii8+CjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0wLDQwIGgxMnY0MGgtMTJ6IE0wLDEyMCBoMTJ2NDBoLTEyeiBNMCwyMDAgaDEydjQwaC0xMnogTTAsMzIwIGgxMnY0MGgtMTJ6IE0wLDQwMCBoMTJ2NDBoLTEyeiIvPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIGQ9Ik0wLDYwIGgyNCBNMCwxNDAgaDI0IE0wLDIyMCBoMjQgTTAsMjgwIGgyNCBNMCwzNDAgaDI0IE0wLDQyMCBoMjQiLz4KPC9zdmc+Cg==",
183
+ observer: 'layout'
184
+ },
185
+ kbwidth: {type: Number, value: 40},
186
+ loop: {type: Number, value: 0},
187
+ preload: {type: Number, value: 1.0},
188
+ tempo: {type: Number, value: 120, observer: 'updateTimer'},
189
+ enable: {type: Boolean, value: true},
190
+ },
191
+ };
192
+ this.defineprop();
193
+ root.innerHTML =
194
+ `<style>
195
+ .pianoroll{
196
+ background:#ccc;
197
+ }
198
+ :host {
199
+ user-select: none;
200
+ display: inline-block;
201
+ font-family: sans-serif;
202
+ font-size: 11px;
203
+ padding:0;
204
+ margin:0;
205
+ }
206
+ #wac-body {
207
+ position: relative;
208
+ margin:0;
209
+ padding:0;
210
+ width: 100%;
211
+ height: 100%;
212
+ overflow: hidden;
213
+ }
214
+ #wac-pianoroll {
215
+ cursor: pointer;
216
+ margin:0;
217
+ padding:0;
218
+ width: 100%;
219
+ height: 100%;
220
+ background-size:100% calc(100%*12/16);
221
+ background-position:left bottom;
222
+ }
223
+ #wac-menu {
224
+ display:none;
225
+ position:absolute;
226
+ top:0;
227
+ left:0;
228
+ background:#eef;
229
+ color:#000;
230
+ padding:2px 10px;
231
+ border:1px solid #66f;
232
+ border-radius: 4px;
233
+ cursor:pointer;
234
+ }
235
+ .marker{
236
+ position: absolute;
237
+ left:0;
238
+ top:0;
239
+ cursor:ew-resize;
240
+ }
241
+ #wac-kb{
242
+ position:absolute;
243
+ left:0;
244
+ top:0;
245
+ width:100px;
246
+ height:100%;
247
+ background: repeat-y;
248
+ background-size:100% calc(100%*12/16);
249
+ background-position:left bottom;
250
+ }
251
+ </style>
252
+ <div class="wac-body" id="wac-body" >
253
+ <canvas id="wac-pianoroll" tabindex="0"></canvas>
254
+ <div id="wac-kb"></div>
255
+ <img id="wac-markstart" class="marker" src="${this.markstartsrc}" alt=""/>
256
+ <img id="wac-markend" class="marker" src="${this.markendsrc}" alt=""/>
257
+ <img id="wac-cursor" class="marker" src="${this.cursorsrc}" alt=""/>
258
+ <div id="wac-menu">Delete</div>
259
+ </div>`;
260
+
261
+ this.sortSequence = function () {
262
+ this.sequence.sort((x, y) => {
263
+ return x.t - y.t;
264
+ });
265
+ };
266
+ this.findNextEv = function (tick) {
267
+ for (let i = 0; i < this.sequence.length; ++i) {
268
+ const nev = this.sequence[i];
269
+ if (nev.t >= this.markend)
270
+ return {t1: tick, n2: this.markend, dt: this.markend - tick, i: -1};
271
+ if (nev.t >= tick)
272
+ return {t1: tick, t2: nev.t, dt: nev.t - tick, i: i};
273
+ }
274
+ return {t1: tick, t2: this.markend, dt: this.markend - tick, i: -1};
275
+ };
276
+ this.locate = function (tick) {
277
+ this.cursor = tick;
278
+ };
279
+ this.updateTimer = function () {
280
+ this.tick2time = 4 * 60 / this.tempo / this.timebase;
281
+ };
282
+ this.play = function (playcallback, tick) {
283
+ if (typeof (tick) != "undefined") {
284
+ this.locate(tick);
285
+ }
286
+ if (this.timer != null) {
287
+ return;
288
+ }
289
+
290
+ this.playcallback = playcallback;
291
+ this.timestack = [];
292
+ this.time0 = this.time1 = performance.now() / 1000 + 0.1;
293
+ this.tick0 = this.tick1 = this.cursor;
294
+ this.tick2time = 4 * 60 / this.tempo / this.timebase;
295
+ const p = this.findNextEv(this.cursor);
296
+ this.index1 = p.i;
297
+ this.timestack.push([0, this.cursor, 0]);
298
+ this.timestack.push([this.time0, this.cursor, this.tick2time]);
299
+ this.time1 += p.dt * this.tick2time;
300
+ if (p.i < 0) {
301
+ this.timestack.push([this.time1, this.markstart, this.tick2time]);
302
+ } else {
303
+ this.timestack.push([this.time1, p.t1, this.tick2time]);
304
+ }
305
+
306
+ const frameRate = 1000 / 40; // 25ms, equivalent to 40 FPS
307
+ let lastTime = performance.now();
308
+
309
+
310
+ const playLoop = () => {
311
+ const currentTime = performance.now();
312
+ const deltaTime = currentTime - lastTime;
313
+
314
+ if (deltaTime >= frameRate) {
315
+ const current = performance.now() / 1000;
316
+
317
+ while (this.timestack.length > 1 && current >= this.timestack[1][0]) {
318
+ this.timestack.shift();
319
+ }
320
+
321
+ this.cursor = this.timestack[0][1] + (current - this.timestack[0][0]) / this.timestack[0][2];
322
+ this.redrawMarker();
323
+
324
+ while (current + this.preload >= this.time1) {
325
+ this.time0 = this.time1;
326
+ this.tick0 = this.tick1;
327
+ let e = this.sequence[this.index1];
328
+
329
+ if (!e || e.t >= this.markend) {
330
+ this.timestack.push([this.time1, this.markstart, this.tick2time]);
331
+ const p = this.findNextEv(this.markstart);
332
+ this.time1 += p.dt * this.tick2time;
333
+ this.index1 = p.i;
334
+ } else {
335
+ this.tick1 = e.t;
336
+ this.timestack.push([this.time1, e.t, this.tick2time]);
337
+ let gmax = Math.min(e.t + e.g, this.markend) - e.t;
338
+
339
+ if (this.editmode == "gridmono" || this.editmode == "gridpoly") {
340
+ gmax *= this.gridnoteratio;
341
+ }
342
+
343
+ const cbev = {t: this.time1, g: this.time1 + gmax * this.tick2time, n: e.n};
344
+
345
+ if (this.playcallback) {
346
+ this.playcallback(cbev);
347
+ }
348
+
349
+ e = this.sequence[++this.index1];
350
+
351
+ if (!e || e.t >= this.markend) {
352
+ this.time1 += (this.markend - this.tick1) * this.tick2time;
353
+ const p = this.findNextEv(this.markstart);
354
+ this.timestack.push([this.time1, this.markstart, this.tick2time]);
355
+ this.time1 += p.dt * this.tick2time;
356
+ this.index1 = p.i;
357
+ } else {
358
+ this.time1 += (e.t - this.tick1) * this.tick2time;
359
+ }
360
+ }
361
+ }
362
+
363
+ lastTime = currentTime;
364
+ }
365
+
366
+ this.timer = requestAnimationFrame(playLoop);
367
+ };
368
+
369
+
370
+ this.timer = requestAnimationFrame(playLoop);
371
+
372
+ this.stop = function () {
373
+ if (this.timer) {
374
+ cancelAnimationFrame(this.timer);
375
+ this.timer = null;
376
+ }
377
+ };
378
+
379
+ };
380
+ this.stop = function () {
381
+
382
+ if (this.timer)
383
+ clearInterval(this.timer);
384
+ this.timer = null;
385
+ };
386
+ this.setMMLString = function (s) {
387
+ this.sequence = [];
388
+ let i, l, n, t, defo, defl, tie, evlast;
389
+ const parse = {s: s, i: i, tb: this.timebase};
390
+
391
+ function getNum(p) {
392
+ var n = 0;
393
+ while (p.s[p.i] >= "0" && p.s[p.i] <= "9") {
394
+ n = n * 10 + parseInt(p.s[p.i]);
395
+ ++p.i;
396
+ }
397
+ return n;
398
+ }
399
+
400
+ function getLen(p) {
401
+ var n = getNum(p);
402
+ if (n == 0)
403
+ n = defl;
404
+ n = p.tb / n;
405
+ var n2 = n;
406
+ while (p.s[p.i] == ".") {
407
+ ++p.i;
408
+ n += (n2 >>= 1);
409
+ }
410
+ return n;
411
+ }
412
+
413
+ function getNote(p) {
414
+ switch (p.s[p.i]) {
415
+ case "c":
416
+ case "C":
417
+ n = 0;
418
+ break;
419
+ case "d":
420
+ case "D":
421
+ n = 2;
422
+ break;
423
+ case "e":
424
+ case "E":
425
+ n = 4;
426
+ break;
427
+ case "f":
428
+ case "F":
429
+ n = 5;
430
+ break;
431
+ case "g":
432
+ case "G":
433
+ n = 7;
434
+ break;
435
+ case "a":
436
+ case "A":
437
+ n = 9;
438
+ break;
439
+ case "b":
440
+ case "B":
441
+ n = 11;
442
+ break;
443
+ default:
444
+ n = -1;
445
+ }
446
+ ++p.i;
447
+ if (n < 0)
448
+ return -1;
449
+ for (; ;) {
450
+ switch (p.s[p.i]) {
451
+ case "-":
452
+ --n;
453
+ break;
454
+ case "+":
455
+ ++n;
456
+ break;
457
+ case "#":
458
+ ++n;
459
+ break;
460
+ default:
461
+ return n;
462
+ }
463
+ ++p.i;
464
+ }
465
+ }
466
+
467
+ defo = 4;
468
+ defl = 8;
469
+ t = 0;
470
+ tie = 0;
471
+ evlast = null;
472
+ for (parse.i = 0; parse.i < parse.s.length;) {
473
+ switch (parse.s[parse.i]) {
474
+ case '>':
475
+ ++parse.i;
476
+ ++defo;
477
+ n = -1;
478
+ l = 0;
479
+ break;
480
+ case '<':
481
+ ++parse.i;
482
+ --defo;
483
+ n = -1;
484
+ l = 0;
485
+ break;
486
+ case '&':
487
+ case '^':
488
+ ++parse.i;
489
+ tie = 1;
490
+ n = -1;
491
+ l = 0;
492
+ break;
493
+ case 't':
494
+ case 'T':
495
+ ++parse.i;
496
+ n = -1;
497
+ l = 0;
498
+ this.tempo = getNum(parse);
499
+ break;
500
+ case 'o':
501
+ case 'O':
502
+ ++parse.i;
503
+ n = -1;
504
+ l = 0;
505
+ defo = getNum(parse);
506
+ break;
507
+ case 'l':
508
+ case 'L':
509
+ ++parse.i;
510
+ n = -1;
511
+ l = 0;
512
+ defl = getNum(parse);
513
+ break;
514
+ case 'r':
515
+ case 'R':
516
+ ++parse.i;
517
+ n = -1;
518
+ l = getLen(parse);
519
+ break;
520
+ default:
521
+ n = getNote(parse);
522
+ if (n >= 0)
523
+ l = getLen(parse);
524
+ else
525
+ l = 0;
526
+ break;
527
+ }
528
+ if (n >= 0) {
529
+ n = (defo - this.octadj) * 12 + n;
530
+ if (tie && evlast && evlast.n == n) {
531
+ evlast.g += l;
532
+ tie = 0;
533
+ } else
534
+ this.sequence.push(evlast = {t: t, n: n, g: l, f: 0});
535
+ }
536
+ t += l;
537
+ }
538
+ this.redraw();
539
+ };
540
+ this.getMMLString = function () {
541
+ function makeNote(n, l, tb) {
542
+ var mmlnote = "";
543
+ var ltab = [
544
+ [960, "1"], [840, "2.."], [720, "2."], [480, "2"],
545
+ [420, "4.."], [360, "4."], [240, "4"],
546
+ [210, "8.."], [180, "8."], [120, ""],
547
+ [105, "16.."], [90, "16."], [60, "16"],
548
+ [45, "32."], [30, "32"], [16, "60"], [15, "64"],
549
+ [8, "120"], [4, "240"], [2, "480"], [1, "960"]
550
+ ];
551
+ l = l * 960 / tb;
552
+ while (l > 0) {
553
+ for (let j = 0; j < ltab.length; ++j) {
554
+ while (l >= ltab[j][0]) {
555
+ l -= ltab[j][0];
556
+ mmlnote += "&" + n + ltab[j][1];
557
+ }
558
+ }
559
+ }
560
+ return mmlnote.substring(1);
561
+ }
562
+
563
+ var mml = "t" + this.tempo + "o4l8";
564
+ var ti = 0, meas = 0, oct = 5, n;
565
+ var notes = ["c", "d-", "d", "e-", "e", "f", "g-", "g", "a-", "a", "b-", "b"];
566
+ for (let i = 0; i < this.sequence.length; ++i) {
567
+ var ev = this.sequence[i];
568
+ if (ev.t > ti) {
569
+ var l = ev.t - ti;
570
+ mml += makeNote("r", l, this.timebase);
571
+ ti = ev.t;
572
+ }
573
+ var n = ev.n;
574
+ if (n < oct * 12 || n >= oct * 12 + 12) {
575
+ oct = (n / 12) | 0;
576
+ mml += "o" + (oct + this.octadj);
577
+ }
578
+ n = notes[n % 12];
579
+ var l = ev.g;
580
+ if (i + 1 < this.sequence.length) {
581
+ var ev2 = this.sequence[i + 1];
582
+ if (ev2.t < ev.t + l) {
583
+ l = ev2.t - ev.t;
584
+ ti = ev2.t;
585
+ } else
586
+ ti = ev.t + ev.g;
587
+ } else
588
+ ti = ev.t + ev.g;
589
+ mml += makeNote(n, l, this.timebase);
590
+ }
591
+ return mml;
592
+ };
593
+ this.hitTest = function (pos) {
594
+ const ht = {t: 0, n: 0, i: -1, m: " "};
595
+ const l = this.sequence.length;
596
+ if (pos.t == this.menu) {
597
+ ht.m = "m";
598
+ return ht;
599
+ }
600
+ ht.t = (this.xoffset + (pos.x - this.yruler - this.kbwidth) / this.swidth * this.xrange);
601
+ ht.n = this.yoffset - (pos.y - this.height) / this.steph;
602
+ if (pos.y >= this.height || pos.x >= this.width) {
603
+ return ht;
604
+ }
605
+ if (pos.y < this.xruler) {
606
+ ht.m = "x";
607
+ return ht;
608
+ }
609
+ if (pos.x < this.yruler + this.kbwidth) {
610
+ ht.m = "y";
611
+ return ht;
612
+ }
613
+ for (let i = 0; i < l; ++i) {
614
+ const ev = this.sequence[i];
615
+ if ((ht.n | 0) == ev.n) {
616
+ if (ev.f && Math.abs(ev.t - ht.t) * this.stepw < 8) {
617
+ ht.m = "B";
618
+ ht.i = i;
619
+ return ht;
620
+ }
621
+ if (ev.f && Math.abs(ev.t + ev.g - ht.t) * this.stepw < 8) {
622
+ ht.m = "E";
623
+ ht.i = i;
624
+ return ht;
625
+ }
626
+ if (ht.t >= ev.t && ht.t < ev.t + ev.g) {
627
+ ht.i = i;
628
+ if (this.sequence[i].f)
629
+ ht.m = "N";
630
+ else
631
+ ht.m = "n";
632
+ return ht;
633
+ }
634
+ }
635
+ }
636
+ ht.m = "s";
637
+ return ht;
638
+ };
639
+ this.applyTexture = function (ev) {
640
+ if (this.noteTexture && this.noteTexture.complete) {
641
+ const w = ev.g * this.stepw;
642
+ const x = (ev.t - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
643
+ const y = this.height - (ev.n - this.yoffset) * this.steph;
644
+ const x2 = (x + w) | 0;
645
+ const y2 = (y - this.steph) | 0;
646
+
647
+ if (ev.f) {
648
+ this.ctx.fillStyle = this.colnotesel; // green if selected
649
+ } else {
650
+ this.ctx.fillStyle = this.colnote; // red if not selected
651
+ }
652
+ this.ctx.fillRect(x, y2, x2 - x, y - y2);
653
+
654
+ this.ctx.globalAlpha = 0.5; // texture opacity
655
+ this.ctx.drawImage(this.noteTexture, x, y2, x2 - x, y - y2);
656
+ this.ctx.globalAlpha = 1.0; // réinitialiser l'opacité
657
+ ev.textureApplied = true;
658
+ } else {
659
+ console.warn('noteTexture is not yet loaded or initialized.');
660
+ }
661
+ };
662
+
663
+ this.addNote = function (t, n, g, v, f, type = 'note', details = {}) {
664
+ if (t >= 0 && n >= 0 && n < 128) {
665
+ const id = this.noteIdCounter++;
666
+ const ev = {id: id, t: t, n: n, g: g, v: v, f: f, type: type, details: details};
667
+ console.log('programatic note creation : ' + id + ' type: ' + type + ', details' + details);
668
+
669
+ if (!this.noteTexture || !this.noteTexture.complete) {
670
+ this.noteTexture = new Image();
671
+ this.noteTexture.src = 'medias/images/waveform.png';
672
+ this.noteTexture.onload = () => {
673
+ this.applyTexture(ev);
674
+ };
675
+ } else {
676
+ this.applyTexture(ev);
677
+ }
678
+
679
+ this.sequence.push(ev);
680
+ this.sortSequence();
681
+ this.redraw();
682
+ return ev;
683
+ }
684
+ return null;
685
+ };
686
+ this.selAreaNote = function (t1, t2, n1, n2) {
687
+ let t, i = 0, e = this.sequence[i];
688
+ if (n1 > n2)
689
+ t = n1, n1 = n2, n2 = t;
690
+ if (t1 > t2)
691
+ t = t1, t1 = t2, t2 = t;
692
+ while (e) {
693
+ if (e.t >= t1 && e.t < t2 && e.n >= n1 && e.n <= n2)
694
+ e.f = 1;
695
+ else
696
+ e.f = 0;
697
+ e = this.sequence[++i];
698
+ }
699
+ };
700
+ this.delNote = function (idx) {
701
+ this.sequence.splice(idx, 1);
702
+ this.redraw();
703
+ };
704
+ this.delAreaNote = function (t, g, n) {
705
+ const l = this.sequence.length;
706
+ for (let i = l - 1; i >= 0; --i) {
707
+ const ev = this.sequence[i];
708
+ if (typeof (n) != "undefined" && n != i) {
709
+ if (t <= ev.t && t + g >= ev.t + ev.g) {
710
+ this.sequence.splice(i, 1);
711
+ } else if (t <= ev.t && t + g > ev.t && t + g < ev.t + ev.g) {
712
+ ev.g = ev.t + ev.g - (t + g);
713
+ ev.t = t + g;
714
+ } else if (t >= ev.t && t < ev.t + ev.g && t + g >= ev.t + ev.g) {
715
+ ev.g = t - ev.t;
716
+ } else if (t > ev.t && t + g < ev.t + ev.g) {
717
+ this.addNote(t + g, ev.n, ev.t + ev.g - t - g, this.defvelo);
718
+ ev.g = t - ev.t;
719
+ }
720
+ }
721
+ }
722
+ };
723
+ this.delSelectedNote = function () {
724
+ console.log('deleting note')
725
+ const l = this.sequence.length;
726
+ for (let i = l - 1; i >= 0; --i) {
727
+ const ev = this.sequence[i];
728
+ if (ev.f)
729
+ this.sequence.splice(i, 1);
730
+ }
731
+ this.refuse = true // to prevent any new note creation when clicking to delete
732
+ };
733
+ this.moveSelectedNote = function (dt, dn) {
734
+ console.log('moving note')
735
+ const l = this.sequence.length;
736
+ for (let i = 0; i < l; ++i) {
737
+ const ev = this.sequence[i];
738
+ if (ev.f && ev.ot + dt < 0)
739
+ dt = -ev.ot;
740
+ }
741
+ for (let i = 0; i < l; ++i) {
742
+ const ev = this.sequence[i];
743
+ if (ev.f) {
744
+ ev.t = (((ev.ot + dt) / this.snap + .5) | 0) * this.snap;
745
+ ev.n = ev.on + dn;
746
+ }
747
+ }
748
+ };
749
+ this.clearSel = function () {
750
+ const l = this.sequence.length;
751
+ for (let i = 0; i < l; ++i) {
752
+ this.sequence[i].f = 0;
753
+ }
754
+ };
755
+ this.selectedNotes = function () {
756
+ let obj = [];
757
+ for (let i = this.sequence.length - 1; i >= 0; --i) {
758
+ const ev = this.sequence[i];
759
+ if (ev.f)
760
+ obj.push({i: i, ev: ev, t: ev.t, g: ev.g});
761
+ }
762
+ return obj;
763
+ };
764
+ this.editDragDown = function (pos) {
765
+ const ht = this.hitTest(pos);
766
+ let ev;
767
+ if (ht.m == "N") {
768
+ ev = this.sequence[ht.i];
769
+ this.dragging = {o: "D", m: "N", i: ht.i, t: ht.t, n: ev.n, dt: ht.t - ev.t};
770
+ for (let i = 0, l = this.sequence.length; i < l; ++i) {
771
+ ev = this.sequence[i];
772
+ if (ev.f)
773
+ ev.on = ev.n, ev.ot = ev.t, ev.og = ev.g;
774
+ }
775
+ this.redraw();
776
+ } else if (ht.m == "n") {
777
+ ev = this.sequence[ht.i];
778
+ this.clearSel();
779
+ ev.f = 1;
780
+ this.redraw();
781
+ } else if (ht.m == "E") {
782
+ this.tool = 'trim_end'
783
+ const ev = this.sequence[ht.i];
784
+ console.log('1 note end changed:');
785
+
786
+ this.dragging = {o: "D", m: "E", i: ht.i, t: ev.t, g: ev.g, ev: this.selectedNotes()};
787
+ } else if (ht.m == "B") {
788
+ this.tool = 'trim_start'
789
+ const ev = this.sequence[ht.i];
790
+ console.log('2 note start changed:');
791
+
792
+ this.dragging = {o: "D", m: "B", i: ht.i, t: ev.t, g: ev.g, ev: this.selectedNotes()};
793
+ } else if (ht.m == "s" && ht.t >= 0) {
794
+ this.clearSel();
795
+
796
+ if (this.editing === true && !this.refuse) {
797
+ var t = ((ht.t / this.snap) | 0) * this.snap;
798
+ const id = this.noteIdCounter++;
799
+ console.log('visual note creation : ' + id);
800
+ var details = {in: 0, out: 0, group: {}};
801
+
802
+ const ev = {
803
+ id: id,
804
+ t: t,
805
+ n: ht.n | 0,
806
+ g: 1,
807
+ f: 1,
808
+ type: 'note',
809
+ details: details,
810
+ textureApplied: false
811
+ };
812
+ this.sequence.push(ev);
813
+
814
+ if (!this.noteTexture || !this.noteTexture.complete) {
815
+ this.noteTexture = new Image();
816
+ this.noteTexture.src = 'medias/images/waveform.png';
817
+ this.noteTexture.onload = () => {
818
+ this.applyTexture(ev);
819
+ this.redraw();
820
+ };
821
+ } else {
822
+ this.applyTexture(ev);
823
+ this.redraw();
824
+ }
825
+
826
+ this.dragging = {
827
+ o: "D",
828
+ m: "E",
829
+ i: this.sequence.length - 1,
830
+ t: t,
831
+ g: 1,
832
+ ev: [{t: t, g: 1, ev: this.sequence[this.sequence.length - 1]}]
833
+ };
834
+ this.refuse = false;
835
+ } else {
836
+ switch (this.downht.m) {
837
+ case "N":
838
+ case "B":
839
+ case "E":
840
+ console.log('open menu');
841
+ this.popMenu(this.downpos);
842
+ this.dragging = {o: "m"};
843
+ break;
844
+ default:
845
+ if (this.editmode == "dragmono" || this.editmode == "dragpoly")
846
+ this.dragging = {
847
+ o: "A",
848
+ p: this.downpos,
849
+ p2: this.downpos,
850
+ t1: this.downht.t,
851
+ n1: this.downht.n
852
+ };
853
+ this.refuse = false
854
+ console.log('===> accept/refuse' + this.refuse)
855
+ break;
856
+ }
857
+ this.canvas.focus();
858
+ return false;
859
+ }
860
+
861
+ this.redraw();
862
+ }
863
+ };
864
+ this.editDragMove = function (pos) {
865
+ const ht = this.hitTest(pos);
866
+ let ev, t;
867
+ if (this.dragging.o == "D") {
868
+ switch (this.dragging.m) {
869
+ case "E":
870
+ if (this.dragging.ev) {
871
+ const dt = ((Math.max(0, ht.t) / this.snap + 0.9) | 0) * this.snap - this.dragging.t - this.dragging.g;
872
+ const list = this.dragging.ev;
873
+ for (let i = list.length - 1; i >= 0; --i) {
874
+ const ev = list[i].ev;
875
+ ev.g = list[i].g + dt;
876
+
877
+ if (ev.g <= 0)
878
+ ev.g = 1;
879
+ if (this.editmove == "dragmono")
880
+ this.delAreaNote(ev.t, ev.g);
881
+ }
882
+ }
883
+ this.redraw();
884
+ break;
885
+ case "B":
886
+ if (this.dragging.ev) {
887
+ const dt = ((Math.max(0, ht.t) / this.snap + 0.9) | 0) * this.snap - this.dragging.t;
888
+ const list = this.dragging.ev;
889
+ for (let i = list.length - 1; i >= 0; --i) {
890
+ const ev = list[i].ev;
891
+ ev.t = list[i].t + dt;
892
+ ev.g = list[i].g - dt;
893
+
894
+ if (ev.g <= 0)
895
+ ev.g = 1;
896
+ if (this.editmove == "dragmono")
897
+ this.delAreaNote(ev.t, ev.g);
898
+ }
899
+ }
900
+ this.redraw();
901
+ break;
902
+
903
+ ev = this.sequence[this.dragging.i];
904
+ t = ((Math.max(0, ht.t) / this.snap + 0.5) | 0) * this.snap;
905
+ ev.g = ev.t + ev.g - t;
906
+ ev.t = t;
907
+ if (ev.g < 0) {
908
+ ev.t += ev.g;
909
+ ev.g = -ev.g;
910
+ this.dragging.m = "E";
911
+ } else if (ev.g == 0) {
912
+ ev.t = t - 1;
913
+ ev.g = 1;
914
+ }
915
+ this.redraw();
916
+ break;
917
+ case "N":
918
+ this.tool = 'drag'
919
+ ev = this.sequence[this.dragging.i];
920
+ console.log('4 note. dragged : ');
921
+ this.moveSelectedNote((ht.t - this.dragging.t) | 0, (ht.n | 0) - this.dragging.n);
922
+ this.redraw();
923
+ break;
924
+ }
925
+ }
926
+ };
927
+ this.editGridDown = function (pos) {
928
+ const ht = this.hitTest(pos);
929
+ if (ht.m == "n") {
930
+ this.delNote(ht.i);
931
+ this.dragging = {o: "G", m: "0"};
932
+ } else if (ht.m == "s" && ht.t >= 0) {
933
+ const pt = Math.floor(ht.t);
934
+ if (this.editmode == "gridmono")
935
+ this.delAreaNote(pt, 1, ht.i);
936
+ this.addNote(pt, ht.n | 0, 1, this.defvelo);
937
+ this.dragging = {o: "G", m: "1"};
938
+ }
939
+ };
940
+ this.editGridMove = function (pos) {
941
+ const ht = this.hitTest(pos);
942
+ if (this.dragging.o == "G") {
943
+ switch (this.dragging.m) {
944
+ case "1":
945
+ const px = Math.floor(ht.t);
946
+ if (ht.m == "s") {
947
+ if (this.editmode == "gridmono")
948
+ this.delAreaNote(px, 1, ht.i);
949
+ this.addNote(px, ht.n | 0, 1, this.defvelo);
950
+ }
951
+ break;
952
+ case "0":
953
+ if (ht.m == "n")
954
+ this.delNote(ht.i);
955
+ break;
956
+ }
957
+ }
958
+ };
959
+ this.setListener = function (el, mode) {
960
+ this.bindcontextmenu = this.contextmenu.bind(this);
961
+ this.bindpointermove = this.pointermove.bind(this);
962
+ this.bindcancel = this.cancel.bind(this);
963
+ el.addEventListener("mousedown", this.pointerdown.bind(this), true);
964
+ el.addEventListener("touchstart", this.pointerdown.bind(this), false);
965
+ if (mode) {
966
+ el.addEventListener("mouseover", this.pointerover.bind(this), false);
967
+ el.addEventListener("mouseout", this.pointerout.bind(this), false);
968
+ }
969
+ };
970
+ this.handleKeyboardClick = function (e) {
971
+ const kbRect = this.kb.getBoundingClientRect();
972
+ const clickY = e.clientY - kbRect.top;
973
+
974
+ const noteNumber = Math.floor(clickY / this.steph);
975
+ console.log("note to trig :", noteNumber);
976
+ };
977
+ this.ready = function () {
978
+ this.body = root.children[1];
979
+ this.elem = root.childNodes[2];
980
+ this.proll = this.elem.children[0];
981
+ this.canvas = this.elem.children[0];
982
+ this.kb = this.elem.children[1];
983
+ this.ctx = this.canvas.getContext("2d");
984
+ this.kbimg = this.elem.children[1];
985
+ this.markstartimg = this.elem.children[2];
986
+ this.markendimg = this.elem.children[3];
987
+ this.cursorimg = this.elem.children[4];
988
+ this.menu = this.elem.children[5];
989
+ this.rcMenu = {x: 0, y: 0, width: 0, height: 0};
990
+ this.lastx = 0;
991
+ this.lasty = 0;
992
+ this.kb.addEventListener('click', this.handleKeyboardClick.bind(this), false);
993
+ this.canvas.addEventListener('mousemove', this.mousemove.bind(this), false);
994
+ this.canvas.addEventListener('keydown', this.keydown.bind(this), false);
995
+ this.canvas.addEventListener('DOMMouseScroll', this.wheel.bind(this), false);
996
+ this.canvas.addEventListener('mousewheel', this.wheel.bind(this), false);
997
+ this.setListener(this.canvas, true);
998
+ this.setListener(this.markendimg, true);
999
+ this.setListener(this.markstartimg, true);
1000
+ this.setListener(this.cursorimg, true);
1001
+ this.setListener(this.menu, false);
1002
+ this.sequence = [];
1003
+ this.dragging = {o: null};
1004
+ this.kbimg.style.height = this.sheight + "px";
1005
+ this.kbimg.style.backgroundSize = (this.steph * 12) + "px";
1006
+ this.layout();
1007
+ this.initialized = 1;
1008
+ this.redraw();
1009
+ };
1010
+ this.setupImage = function () {
1011
+ };
1012
+ this.preventScroll = function (e) {
1013
+ if (e.preventDefault)
1014
+ e.preventDefault();
1015
+ };
1016
+ this.getPos = function (e) {
1017
+ let t = null;
1018
+ if (e) {
1019
+ t = e.target;
1020
+ this.lastx = e.clientX - this.rcTarget.left;
1021
+ this.lasty = e.clientY - this.rcTarget.top;
1022
+ }
1023
+ if (this.lastx >= this.rcMenu.x && this.lastx < this.rcMenu.x + this.rcMenu.width
1024
+ && this.lasty >= this.rcMenu.y && this.lasty < this.rcMenu.y + this.rcMenu.height)
1025
+ t = this.menu;
1026
+ return {t: t, x: this.lastx, y: this.lasty};
1027
+ };
1028
+ this.contextmenu = function (e) {
1029
+ e.stopPropagation();
1030
+ e.preventDefault();
1031
+ window.removeEventListener("contextmenu", this.bindcontextmenu);
1032
+ return false;
1033
+ };
1034
+ this.keydown = function (e) {
1035
+ switch (e.keyCode) {
1036
+ case 8: //delNote using backspace key
1037
+ this.delSelectedNote();
1038
+ this.redraw();
1039
+ break;
1040
+ }
1041
+ };
1042
+ this.popMenu = function (pos) {
1043
+ console.log('pop menu call from shortcut')
1044
+ };
1045
+ this.longtapcountup = function () {
1046
+ if (++this.longtapcount >= 18) {
1047
+ clearInterval(this.longtaptimer);
1048
+ switch (this.downht.m) {
1049
+ case "N":
1050
+ case "B":
1051
+ case "E":
1052
+ this.popMenu(this.downpos);
1053
+ this.dragging = {o: "m"};
1054
+ break;
1055
+ }
1056
+ }
1057
+ };
1058
+ this.pointerdown = function (ev) {
1059
+ let e;
1060
+ if (!this.enable) {
1061
+ console.log('here 1');
1062
+ return;
1063
+ }
1064
+
1065
+ if (ev.touches) {
1066
+ console.log('here 2');
1067
+
1068
+ e = ev.touches[0];
1069
+ } else {
1070
+ e = ev;
1071
+ this.rcTarget = this.canvas.getBoundingClientRect();
1072
+ this.downpos = this.getPos(e);
1073
+ this.downht = this.hitTest(this.downpos);
1074
+
1075
+ if (this.downht.i >= 0) {
1076
+ let clickedNote = this.sequence[this.downht.i];
1077
+ let noteId = clickedNote.id;
1078
+ console.log("Note ID :", noteId + ' note type: ' + clickedNote.type + ', note detail : ' + clickedNote.details);
1079
+ console.log('-- details below ---')
1080
+ console.log(clickedNote.details.group)
1081
+ console.log(clickedNote.details.in)
1082
+ console.log('-- end details ---')
1083
+ }
1084
+
1085
+
1086
+ this.longtapcount = 0;
1087
+ this.longtaptimer = setInterval(this.longtapcountup.bind(this), 100);
1088
+ window.addEventListener("touchmove", this.bindpointermove, false);
1089
+ window.addEventListener("mousemove", this.bindpointermove, false);
1090
+ window.addEventListener("touchend", this.bindcancel);
1091
+ window.addEventListener("mouseup", this.bindcancel);
1092
+ window.addEventListener("contextmenu", this.bindcontextmenu);
1093
+
1094
+
1095
+ if (e.button == 2 || e.ctrlKey) {
1096
+
1097
+ console.log(' open the menu now!!!')
1098
+ switch (this.downht.m) {
1099
+ case "N":
1100
+ case "B":
1101
+ case "E":
1102
+ console.log('open menu');
1103
+ this.popMenu(this.downpos);
1104
+ this.dragging = {o: "m"};
1105
+ break;
1106
+ default:
1107
+ if (this.editmode == "dragmono" || this.editmode == "dragpoly")
1108
+ this.dragging = {
1109
+ o: "A",
1110
+ p: this.downpos,
1111
+ p2: this.downpos,
1112
+ t1: this.downht.t,
1113
+ n1: this.downht.n
1114
+ };
1115
+ break;
1116
+ }
1117
+ ev.preventDefault();
1118
+ ev.stopPropagation();
1119
+ this.canvas.focus();
1120
+ return false;
1121
+ }
1122
+ switch (e.target) {
1123
+ case this.markendimg:
1124
+ this.dragging = {o: "E", x: this.downpos.x, m: this.markend};
1125
+ ev.preventDefault();
1126
+ ev.stopPropagation();
1127
+ return false;
1128
+ case this.markstartimg:
1129
+ this.dragging = {o: "S", x: this.downpos.x, m: this.markstart};
1130
+ ev.preventDefault();
1131
+ ev.stopPropagation();
1132
+ return false;
1133
+ case this.cursorimg:
1134
+ this.dragging = {o: "P", x: this.downpos.x, m: this.cursor};
1135
+ ev.preventDefault();
1136
+ ev.stopPropagation();
1137
+ return false;
1138
+ }
1139
+ this.dragging = {
1140
+ o: null,
1141
+ x: this.downpos.x,
1142
+ y: this.downpos.y,
1143
+ offsx: this.xoffset,
1144
+ offsy: this.yoffset
1145
+ };
1146
+ this.canvas.focus();
1147
+ switch (this.editmode) {
1148
+ case "gridpoly":
1149
+ case "gridmono":
1150
+ this.editGridDown(this.downpos);
1151
+ break;
1152
+ case "dragpoly":
1153
+ case "dragmono":
1154
+ this.editDragDown(this.downpos);
1155
+ break;
1156
+ }
1157
+ this.press = 1;
1158
+ if (ev.preventDefault)
1159
+ ev.preventDefault();
1160
+ if (ev.stopPropagation)
1161
+ ev.stopPropagation();
1162
+ return false;
1163
+ }
1164
+ };
1165
+ this.mousemove = function (e) {
1166
+ if (this.dragging.o == null) {
1167
+ this.rcTarget = this.canvas.getBoundingClientRect();
1168
+ const pos = this.getPos(e);
1169
+ const ht = this.hitTest(pos);
1170
+ switch (ht.m) {
1171
+ case "E":
1172
+ this.canvas.style.cursor = "e-resize";
1173
+ break;
1174
+ case "B":
1175
+ this.canvas.style.cursor = "w-resize";
1176
+ break;
1177
+ case "N":
1178
+ this.canvas.style.cursor = "move";
1179
+ break;
1180
+ case "n":
1181
+ this.canvas.style.cursor = "pointer";
1182
+ break;
1183
+ case "s":
1184
+ this.canvas.style.cursor = "pointer";
1185
+ break;
1186
+ }
1187
+ }
1188
+ };
1189
+ this.pointermove = function (ev) {
1190
+ let e;
1191
+ this.rcTarget = this.canvas.getBoundingClientRect();
1192
+ if (ev.touches)
1193
+ e = ev.touches[0];
1194
+ else
1195
+ e = ev;
1196
+ if (this.longtaptimer)
1197
+ clearInterval(this.longtaptimer);
1198
+ const pos = this.getPos(e);
1199
+ const ht = this.hitTest(pos);
1200
+ switch (this.dragging.o) {
1201
+ case null:
1202
+ if (this.xscroll)
1203
+ this.xoffset = this.dragging.offsx + (this.dragging.x - pos.x) * (this.xrange / this.width);
1204
+ if (this.yscroll)
1205
+ this.yoffset = this.dragging.offsy + (pos.y - this.dragging.y) * (this.yrange / this.height);
1206
+ break;
1207
+ case "m":
1208
+ if (ht.m == "m") {
1209
+ this.menu.style.background = "#ff6";
1210
+ } else {
1211
+ this.menu.style.background = "#eef";
1212
+ }
1213
+ break;
1214
+ case "A":
1215
+ this.dragging.p2 = pos;
1216
+ this.dragging.t2 = ht.t;
1217
+ this.dragging.n2 = ht.n;
1218
+ this.redraw();
1219
+ break;
1220
+ case "E":
1221
+ console.log('marker end')
1222
+ var p = Math.max(1, (this.dragging.m + (pos.x - this.dragging.x) / this.stepw + .5) | 0);
1223
+ if (this.markstart >= p)
1224
+ this.markstart = p - 1;
1225
+ this.markend = p;
1226
+ break;
1227
+ case "S":
1228
+ console.log('marker start')
1229
+ var p = Math.max(0, (this.dragging.m + (pos.x - this.dragging.x) / this.stepw + .5) | 0);
1230
+ if (this.markend <= p)
1231
+ this.markend = p + 1;
1232
+ this.markstart = p;
1233
+ break;
1234
+ case "P":
1235
+ console.log('playhead')
1236
+ this.cursor = Math.max(0, (this.dragging.m + (pos.x - this.dragging.x) / this.stepw + .5) | 0);
1237
+ break;
1238
+ }
1239
+ switch (this.editmode) {
1240
+ case "gridpoly":
1241
+ case "gridmono":
1242
+ this.editGridMove(pos);
1243
+ break;
1244
+ case "dragpoly":
1245
+ case "dragmono":
1246
+ this.editDragMove(pos);
1247
+ break;
1248
+ }
1249
+ ev.preventDefault();
1250
+ ev.stopPropagation();
1251
+ return false;
1252
+ };
1253
+ this.cancel = function (ev) {
1254
+ let e;
1255
+ if (ev.touches)
1256
+ e = null;
1257
+ else
1258
+ e = ev;
1259
+ if (this.longtaptimer)
1260
+ clearInterval(this.longtaptimer);
1261
+ const pos = this.getPos(e);
1262
+ if (this.dragging.o == "m") {
1263
+
1264
+ }
1265
+ if (this.dragging.o == "A") {
1266
+ this.selAreaNote(this.dragging.t1, this.dragging.t2, this.dragging.n1, this.dragging.n2);
1267
+ this.dragging = {o: null};
1268
+ this.redraw();
1269
+ }
1270
+ if (this.editmode == "dragmono") {
1271
+ for (let ii = this.sequence.length - 1; ii >= 0; --ii) {
1272
+ const ev = this.sequence[ii];
1273
+ if (ev && ev.f) {
1274
+ this.delAreaNote(ev.t, ev.g, ii);
1275
+ }
1276
+ }
1277
+ }
1278
+ this.redraw();
1279
+ this.dragging = {o: null};
1280
+ if (this.press) {
1281
+ this.sortSequence();
1282
+ }
1283
+ this.press = 0;
1284
+ window.removeEventListener('touchstart', this.preventScroll, false);
1285
+ window.removeEventListener("mousemove", this.bindpointermove, false);
1286
+ window.removeEventListener("touchend", this.bindcancel, false);
1287
+ window.removeEventListener("mouseup", this.bindcancel, false);
1288
+ ev.preventDefault();
1289
+ ev.stopPropagation();
1290
+ return false;
1291
+ };
1292
+ this.pointerover = function (e) {
1293
+ };
1294
+ this.pointerout = function (e) {
1295
+ };
1296
+ this.wheel = function (e) {
1297
+ let delta = 0;
1298
+ const pos = this.getPos(e);
1299
+ if (!e)
1300
+ e = window.event;
1301
+ if (e.wheelDelta)
1302
+ delta = e.wheelDelta / 120;
1303
+ else if (e.detail)
1304
+ delta = -e.detail / 3;
1305
+ const ht = this.hitTest(pos);
1306
+ if ((this.wheelzoomx || this.wheelzoom) && ht.m == "x") {
1307
+ if (delta > 0) {
1308
+ this.xoffset = ht.t - (ht.t - this.xoffset) / 1.2
1309
+ this.xrange /= 1.2;
1310
+ } else {
1311
+ this.xoffset = ht.t - (ht.t - this.xoffset) * 1.2
1312
+ this.xrange *= 1.2;
1313
+ }
1314
+ }
1315
+ if ((this.wheelzoomy || this.wheelzoom) && ht.m == "y") {
1316
+ if (delta > 0) {
1317
+ this.yoffset = ht.n - (ht.n - this.yoffset) / 1.2
1318
+ this.yrange /= 1.2;
1319
+ } else {
1320
+ this.yoffset = ht.n - (ht.n - this.yoffset) * 1.2
1321
+ this.yrange *= 1.2;
1322
+ }
1323
+
1324
+ }
1325
+ e.preventDefault();
1326
+ };
1327
+ this.layout = function () {
1328
+ if (typeof (this.kbwidth) == "undefined")
1329
+ return;
1330
+ const proll = this.proll;
1331
+ const bodystyle = this.body.style;
1332
+ if (this.bgsrc)
1333
+ proll.style.background = "url('" + this.bgsrc + "')";
1334
+ this.kbimg.style.background = "url('" + this.kbsrc + "')";
1335
+ if (this.width) {
1336
+ proll.width = this.width;
1337
+ bodystyle.width = proll.style.width = this.width + "px";
1338
+ }
1339
+ if (this.height) {
1340
+ proll.height = this.height;
1341
+ bodystyle.height = proll.style.height = this.height + "px";
1342
+ }
1343
+ this.swidth = proll.width - this.yruler;
1344
+ this.swidth -= this.kbwidth;
1345
+ this.sheight = proll.height - this.xruler;
1346
+ this.redraw();
1347
+ };
1348
+ this.redrawMarker = function () {
1349
+ if (!this.initialized)
1350
+ return;
1351
+ const cur = (this.cursor - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
1352
+ this.cursorimg.style.left = (cur + this.cursoroffset) + "px";
1353
+ const start = (this.markstart - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
1354
+ this.markstartimg.style.left = (start + this.markstartoffset) + "px";
1355
+ const end = (this.markend - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
1356
+ this.markendimg.style.left = (end + this.markendoffset) + "px";
1357
+ };
1358
+ this.redrawGrid = function () {
1359
+ for (let y = 0; y < 128; ++y) {
1360
+ if (this.semiflag[y % 12] & 1)
1361
+ this.ctx.fillStyle = this.coldk;
1362
+ else
1363
+ this.ctx.fillStyle = this.collt;
1364
+ let ys = this.height - (y - this.yoffset) * this.steph;
1365
+ this.ctx.fillRect(this.yruler + this.kbwidth, ys | 0, this.swidth, -this.steph);
1366
+ this.ctx.fillStyle = this.colgrid;
1367
+ this.ctx.fillRect(this.yruler + this.kbwidth, ys | 0, this.swidth, 1);
1368
+ }
1369
+ for (let t = 0; ; t += this.grid) {
1370
+ let x = this.stepw * (t - this.xoffset) + this.yruler + this.kbwidth;
1371
+ this.ctx.fillRect(x | 0, this.xruler, 1, this.sheight);
1372
+ if (x >= this.width)
1373
+ break;
1374
+ }
1375
+ };
1376
+ this.semiflag = [6, 1, 0, 1, 0, 2, 1, 0, 1, 0, 1, 0];
1377
+ this.redrawXRuler = function () {
1378
+ if (this.xruler) {
1379
+ this.ctx.textAlign = "left";
1380
+ this.ctx.font = (this.xruler / 2) + "px 'sans-serif'";
1381
+ this.ctx.fillStyle = this.colrulerbg;
1382
+ this.ctx.fillRect(0, 0, this.width, this.xruler);
1383
+ this.ctx.fillStyle = this.colrulerborder;
1384
+ this.ctx.fillRect(0, 0, this.width, 1);
1385
+ this.ctx.fillRect(0, 0, 1, this.xruler);
1386
+ this.ctx.fillRect(0, this.xruler - 1, this.width, 1);
1387
+ this.ctx.fillRect(this.width - 1, 0, 1, this.xruler);
1388
+ this.ctx.fillStyle = this.colrulerfg;
1389
+ for (let t = 0; ; t += this.timebase) {
1390
+ let x = (t - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
1391
+ this.ctx.fillRect(x, 0, 1, this.xruler);
1392
+ this.ctx.fillText(t / this.timebase + 1, x + 4, this.xruler - 8);
1393
+ if (x >= this.width)
1394
+ break;
1395
+ }
1396
+ }
1397
+ };
1398
+ this.redrawYRuler = function () {
1399
+ if (this.yruler) {
1400
+ this.ctx.textAlign = "right";
1401
+ this.ctx.font = (this.steph / 2) + "px 'sans-serif'";
1402
+ this.ctx.fillStyle = this.colrulerbg;
1403
+ this.ctx.fillRect(0, this.xruler, this.yruler, this.sheight);
1404
+ this.ctx.fillStyle = this.colrulerborder;
1405
+ this.ctx.fillRect(0, this.xruler, 1, this.sheight);
1406
+ this.ctx.fillRect(this.yruler, this.xruler, 1, this.sheight);
1407
+ this.ctx.fillRect(0, this.height - 1, this.yruler, 1);
1408
+ this.ctx.fillStyle = this.colrulerfg;
1409
+ for (let y = 0; y < 128; y += 12) {
1410
+ const ys = this.height - this.steph * (y - this.yoffset);
1411
+ this.ctx.fillRect(0, ys | 0, this.yruler, -1);
1412
+ this.ctx.fillText("C" + (((y / 12) | 0) + this.octadj), this.yruler - 4, ys - 4);
1413
+ }
1414
+ }
1415
+ this.kbimg.style.top = (this.xruler) + "px";
1416
+ this.kbimg.style.left = this.yruler + "px";
1417
+ this.kbimg.style.width = this.kbwidth + "px";
1418
+ this.kbimg.style.backgroundSize = "100% " + (this.steph * 12) + "px";
1419
+ this.kbimg.style.backgroundPosition = "0px " + (this.sheight + this.steph * this.yoffset) + "px";
1420
+ };
1421
+ this.redrawKeyboard = function () {
1422
+ if (this.yruler) {
1423
+ this.ctx.textAlign = "right";
1424
+ this.ctx.font = (this.steph / 2) + "px 'sans-serif'";
1425
+ this.ctx.fillStyle = this.colortab.kbwh;
1426
+ this.ctx.fillRect(1, this.xruler, this.yruler, this.sheight);
1427
+ this.ctx.fillStyle = this.colortab.kbbk;
1428
+ for (let y = 0; y < 128; ++y) {
1429
+ const ys = this.height - this.steph * (y - this.yoffset);
1430
+ const ysemi = y % 12;
1431
+ const fsemi = this.semiflag[ysemi];
1432
+ if (fsemi & 1) {
1433
+ this.ctx.fillRect(0, ys, this.yruler / 2, -this.steph);
1434
+ this.ctx.fillRect(0, (ys - this.steph / 2) | 0, this.yruler, -1);
1435
+ }
1436
+ if (fsemi & 2)
1437
+ this.ctx.fillRect(0, ys | 0, this.yruler, -1);
1438
+ if (fsemi & 4)
1439
+ this.ctx.fillText("C" + (((y / 12) | 0) + this.octadj), this.yruler - 4, ys - 4);
1440
+ }
1441
+ this.ctx.fillRect(this.yruler, this.xruler, 1, this.sheight);
1442
+ }
1443
+ };
1444
+ this.redrawAreaSel = function () {
1445
+ if (this.dragging && this.dragging.o == "A") {
1446
+ this.ctx.fillStyle = this.colselarea;
1447
+ this.ctx.fillRect(this.dragging.p.x, this.dragging.p.y, this.dragging.p2.x - this.dragging.p.x, this.dragging.p2.y - this.dragging.p.y);
1448
+ }
1449
+ };
1450
+ this.redraw = function () {
1451
+ let x, w, y;
1452
+ if (!this.ctx) return;
1453
+
1454
+ this.ctx.clearRect(0, 0, this.width, this.height);
1455
+ this.stepw = this.swidth / this.xrange;
1456
+ this.steph = this.sheight / this.yrange;
1457
+
1458
+ this.redrawGrid();
1459
+
1460
+ const l = this.sequence.length;
1461
+ for (let s = 0; s < l; ++s) {
1462
+ const ev = this.sequence[s];
1463
+ const noteHeight = this.steph;
1464
+ console.log('tool active: ' + this.tool + ', length : ' + ev.g + ' start: ' + ev.t)
1465
+
1466
+ if (ev.f) {
1467
+ this.ctx.fillStyle = this.colnotesel;
1468
+ } else {
1469
+ this.ctx.fillStyle = this.colnote;
1470
+ }
1471
+
1472
+ w = ev.g * this.stepw;
1473
+ x = (ev.t - this.xoffset) * this.stepw + this.yruler + this.kbwidth;
1474
+ y = this.height - (ev.n - this.yoffset) * this.steph;
1475
+
1476
+ this.ctx.fillRect(x, y - noteHeight, w, noteHeight);
1477
+
1478
+ this.applyTexture(ev);
1479
+ }
1480
+
1481
+ this.redrawYRuler();
1482
+ this.redrawXRuler();
1483
+ this.redrawMarker();
1484
+ this.redrawAreaSel();
1485
+ };
1486
+ this.ready();
1487
+ }
1488
+
1489
+ getAttr(n, def) {
1490
+ let v = this.getAttribute(n);
1491
+ if (v == "" || v == null) return def;
1492
+ switch (typeof (def)) {
1493
+ case "number":
1494
+ if (v == "true") return 1;
1495
+ v = +v;
1496
+ if (isNaN(v)) return 0;
1497
+ return v;
1498
+ }
1499
+ return v;
1500
+ }
1501
+ });
1502
+
1503
+
1504
+ /// pianoroll creator :
1505
+
1506
+ function aRoll(id, target, width, height) {
1507
+ // we build the pianoroll here
1508
+ const pianoRoll = document.createElement('webaudio-pianoroll');
1509
+
1510
+ pianoRoll.setAttribute('id', id);
1511
+ pianoRoll.setAttribute('width', width);
1512
+ pianoRoll.setAttribute('height', height);
1513
+
1514
+ const targetElement = document.getElementById(target);
1515
+
1516
+ if (targetElement) {
1517
+ targetElement.appendChild(pianoRoll);
1518
+ } else {
1519
+ console.error('Target element not found');
1520
+ }
1521
+ }
1522
+
1523
+ /// pianoroll builder below
1524
+
1525
+ function setTempo(id) {
1526
+ let pianoRoll = document.getElementById(id);
1527
+ pianoRoll.tempo = 33;
1528
+ pianoRoll.updateTimer();
1529
+ console.log('Tempo:', pianoRoll.tempo);
1530
+ }
1531
+
1532
+ function changeEditMode(id, mode) {
1533
+ document.getElementById(id).editmode = mode;
1534
+ }
1535
+
1536
+ function AddNote(id) {
1537
+ let sequence = document.getElementById(id);
1538
+ sequence.addNote(
1539
+ 0, // Tick
1540
+ 66, // Note
1541
+ 2, // Duration
1542
+ 39, // velocity???
1543
+ 0, // selected or not
1544
+ 'notes' // type (group)
1545
+ );
1546
+ }
1547
+
1548
+ function setMarkStart(id) {
1549
+ let sequence = document.getElementById(id);
1550
+ sequence.markstart = (3)
1551
+ }
1552
+
1553
+ function setMarkEnd(id) {
1554
+ let sequence = document.getElementById(id);
1555
+ sequence.markend = (7)
1556
+ }
1557
+
1558
+ function playHead(id) {
1559
+ let sequence = document.getElementById(id);
1560
+ sequence.locate(3);
1561
+ }
1562
+
1563
+ function menu(id) {
1564
+ console.log('open a menu here!!!')
1565
+ }
1566
+
1567
+ function editing(id) {
1568
+ let sequence = document.getElementById(id);
1569
+ if (sequence.editing) {
1570
+ sequence.editing = false
1571
+ sequence.tool = 'select'
1572
+ console.log('no editing')
1573
+ } else {
1574
+ sequence.editing = true
1575
+ sequence.tool = 'create'
1576
+ console.log('editing active')
1577
+ }
1578
+
1579
+ }
1580
+
1581
+ function createExtendedNote(notes) {
1582
+ if (notes.length === 0) {
1583
+ return null;
1584
+ }
1585
+
1586
+ let selectedNotes = notes.filter(note => note.f === 1);
1587
+
1588
+ if (selectedNotes.length === 0) {
1589
+ return null;
1590
+ }
1591
+
1592
+ let earliestStartNote = selectedNotes.reduce((earliest, note) => note.t < earliest.t ? note : earliest, selectedNotes[0]);
1593
+
1594
+ let latestEndNote = selectedNotes.reduce((latest, note) => (note.t + note.g) > (latest.t + latest.g) ? note : latest, selectedNotes[0]);
1595
+
1596
+ let newNote = {
1597
+ id: Math.max(...notes.map(note => note.id)) + 1, // new id build on previous build id
1598
+ t: earliestStartNote.t, // start of first selected note in timecode
1599
+ n: earliestStartNote.n, // use the same pitch as the first seledted note
1600
+ g: (latestEndNote.t + latestEndNote.g) - earliestStartNote.t, // compute total length
1601
+ f: 1 // by default select the newly created note
1602
+ };
1603
+
1604
+ return newNote;
1605
+ }
1606
+
1607
+
1608
+ function deleteSelectedNotes(id) {
1609
+ let pianoroll = document.getElementById(id);
1610
+ let sequence = pianoroll.sequence;
1611
+ if (Array.isArray(sequence)) {
1612
+ pianoroll.sequence = sequence.filter(note => note.f !== 1);
1613
+ if (typeof pianoroll.redraw === 'function') {
1614
+ pianoroll.redraw();
1615
+ }
1616
+ }
1617
+ }
1618
+
1619
+ function group(id) {
1620
+ let sequence = document.getElementById(id);
1621
+ let notes = sequence.sequence;
1622
+ let newNote = createExtendedNote(notes)
1623
+ let noteToDel = [];
1624
+ notes.forEach(note => {
1625
+ if (note.f === 1) {
1626
+ noteToDel.push(note)
1627
+ }
1628
+ });
1629
+ noteToDel.forEach(note => {
1630
+ const index = sequence.sequence.indexOf(note);
1631
+ if (index !== -1) {
1632
+ sequence.sequence.splice(index, 1);
1633
+ }
1634
+ });
1635
+ sequence.addNote(newNote.t, 60, newNote.g, 8, 1, 'group', {in: 0, out: 0, group: noteToDel});
1636
+ }
1637
+
1638
+ function notes(id) {
1639
+ let sequence = document.getElementById(id);
1640
+ let notes = sequence.sequence;
1641
+ console.log(notes)
1642
+
1643
+ }
1644
+
1645
+ function selectAll(id) {
1646
+ let pianoroll = document.getElementById(id);
1647
+ pianoroll.sequence.forEach(note => {
1648
+ note.f = 1;
1649
+ });
1650
+ pianoroll.redraw();
1651
+ }
1652
+
1653
+ function deSelectAll(id) {
1654
+ let pianoroll = document.getElementById(id);
1655
+ pianoroll.sequence.forEach(note => {
1656
+ note.f = 0;
1657
+ });
1658
+ pianoroll.redraw();
1659
+ }
1660
+
1661
+ function marker(id) {
1662
+ const pianoRoll = document.getElementById(id);
1663
+ pianoRoll.marker(12, 'playheadID1', 'My First Playhead');
1664
+ }
1665
+
1666
+ function removeMarker(id) {
1667
+ const pianoRoll = document.getElementById(id);
1668
+ pianoRoll.removeMarker('playheadID1');
1669
+ }
1670
+
1671
+ ///
1672
+
1673
+ function clear_now() {
1674
+ console.clear()
1675
+ }
1676
+