@mulsense/xnew 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/xnew.mjs CHANGED
@@ -200,10 +200,323 @@ class Timer {
200
200
  }
201
201
  }
202
202
 
203
- //----------------------------------------------------------------------------------------------------
204
- // utils
205
- //----------------------------------------------------------------------------------------------------
206
203
  const SYSTEM_EVENTS = ['start', 'process', 'update', 'stop', 'finalize'];
204
+
205
+ class EventManager {
206
+ constructor() {
207
+ this.map = new MapMap();
208
+ }
209
+ add(props) {
210
+ let finalize;
211
+ if (props.type === 'resize') {
212
+ finalize = this.resize(props);
213
+ }
214
+ else if (props.type === 'wheel') {
215
+ finalize = this.wheel(props);
216
+ }
217
+ else if (props.type === 'click') {
218
+ finalize = this.click(props);
219
+ }
220
+ else if (props.type === 'click.outside') {
221
+ finalize = this.click_outside(props);
222
+ }
223
+ else if (['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].includes(props.type)) {
224
+ finalize = this.pointer(props);
225
+ }
226
+ else if (['pointerdown.outside', 'pointermove.outside', 'pointerup.outside'].includes(props.type)) {
227
+ finalize = this.pointer_outside(props);
228
+ }
229
+ else if (['mousedown', 'mousemove', 'mouseup', 'mouseover', 'mouseout'].includes(props.type)) {
230
+ finalize = this.mouse(props);
231
+ }
232
+ else if (['touchstart', 'touchmove', 'touchend', 'touchcancel'].includes(props.type)) {
233
+ finalize = this.touch(props);
234
+ }
235
+ else if (['dragstart', 'dragmove', 'dragend'].includes(props.type)) {
236
+ finalize = this.drag(props);
237
+ }
238
+ else if (['gesturestart', 'gesturemove', 'gestureend'].includes(props.type)) {
239
+ finalize = this.gesture(props);
240
+ }
241
+ else if (['keydown', 'keyup'].includes(props.type)) {
242
+ finalize = this.key(props);
243
+ }
244
+ else if (['keydown.arrow', 'keyup.arrow'].includes(props.type)) {
245
+ finalize = this.key_arrow(props);
246
+ }
247
+ else {
248
+ finalize = this.basic(props);
249
+ }
250
+ this.map.set(props.type, props.listener, finalize);
251
+ }
252
+ remove({ type, listener }) {
253
+ const finalize = this.map.get(type, listener);
254
+ if (finalize) {
255
+ finalize();
256
+ this.map.delete(type, listener);
257
+ }
258
+ }
259
+ basic(props) {
260
+ const execute = (event) => {
261
+ props.listener({ event, type: event.type });
262
+ };
263
+ props.element.addEventListener(props.type, execute, props.options);
264
+ return () => {
265
+ props.element.removeEventListener(props.type, execute);
266
+ };
267
+ }
268
+ resize(props) {
269
+ const observer = new ResizeObserver(xnew$1.scope((entries) => {
270
+ for (const entry of entries) {
271
+ props.listener({ type: 'resize' });
272
+ break;
273
+ }
274
+ }));
275
+ observer.observe(props.element);
276
+ return () => {
277
+ observer.unobserve(props.element);
278
+ };
279
+ }
280
+ click(props) {
281
+ const execute = (event) => {
282
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
283
+ };
284
+ props.element.addEventListener(props.type, execute, props.options);
285
+ return () => {
286
+ props.element.removeEventListener(props.type, execute);
287
+ };
288
+ }
289
+ click_outside(props) {
290
+ const execute = (event) => {
291
+ if (props.element.contains(event.target) === false) {
292
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
293
+ }
294
+ };
295
+ document.addEventListener(props.type.split('.')[0], execute, props.options);
296
+ return () => {
297
+ document.removeEventListener(props.type.split('.')[0], execute);
298
+ };
299
+ }
300
+ pointer(props) {
301
+ const execute = (event) => {
302
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
303
+ };
304
+ props.element.addEventListener(props.type, execute, props.options);
305
+ return () => {
306
+ props.element.removeEventListener(props.type, execute);
307
+ };
308
+ }
309
+ mouse(props) {
310
+ const execute = (event) => {
311
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
312
+ };
313
+ props.element.addEventListener(props.type, execute, props.options);
314
+ return () => {
315
+ props.element.removeEventListener(props.type, execute);
316
+ };
317
+ }
318
+ touch(props) {
319
+ const execute = (event) => {
320
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
321
+ };
322
+ props.element.addEventListener(props.type, execute, props.options);
323
+ return () => {
324
+ props.element.removeEventListener(props.type, execute);
325
+ };
326
+ }
327
+ pointer_outside(props) {
328
+ const execute = (event) => {
329
+ if (props.element.contains(event.target) === false) {
330
+ props.listener({ event, type: props.type, position: pointer(props.element, event).position });
331
+ }
332
+ };
333
+ document.addEventListener(props.type.split('.')[0], execute, props.options);
334
+ return () => {
335
+ document.removeEventListener(props.type.split('.')[0], execute);
336
+ };
337
+ }
338
+ wheel(props) {
339
+ const execute = (event) => {
340
+ props.listener({ event, type: props.type, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } });
341
+ };
342
+ props.element.addEventListener(props.type, execute, props.options);
343
+ return () => {
344
+ props.element.removeEventListener(props.type, execute);
345
+ };
346
+ }
347
+ drag(props) {
348
+ let pointermove = null;
349
+ let pointerup = null;
350
+ let pointercancel = null;
351
+ const pointerdown = (event) => {
352
+ const id = event.pointerId;
353
+ const position = pointer(props.element, event).position;
354
+ let previous = position;
355
+ pointermove = (event) => {
356
+ if (event.pointerId === id) {
357
+ const position = pointer(props.element, event).position;
358
+ const delta = { x: position.x - previous.x, y: position.y - previous.y };
359
+ if (props.type === 'dragmove') {
360
+ props.listener({ event, type: props.type, position, delta });
361
+ }
362
+ previous = position;
363
+ }
364
+ };
365
+ pointerup = (event) => {
366
+ if (event.pointerId === id) {
367
+ const position = pointer(props.element, event).position;
368
+ if (props.type === 'dragend') {
369
+ props.listener({ event, type: props.type, position, delta: { x: 0, y: 0 } });
370
+ }
371
+ remove();
372
+ }
373
+ };
374
+ pointercancel = (event) => {
375
+ if (event.pointerId === id) {
376
+ const position = pointer(props.element, event).position;
377
+ if (props.type === 'dragend') {
378
+ props.listener({ event, type: props.type, position, delta: { x: 0, y: 0 } });
379
+ }
380
+ remove();
381
+ }
382
+ };
383
+ window.addEventListener('pointermove', pointermove);
384
+ window.addEventListener('pointerup', pointerup);
385
+ window.addEventListener('pointercancel', pointercancel);
386
+ if (props.type === 'dragstart') {
387
+ props.listener({ event, type: props.type, position, delta: { x: 0, y: 0 } });
388
+ }
389
+ };
390
+ function remove() {
391
+ if (pointermove)
392
+ window.removeEventListener('pointermove', pointermove);
393
+ if (pointerup)
394
+ window.removeEventListener('pointerup', pointerup);
395
+ if (pointercancel)
396
+ window.removeEventListener('pointercancel', pointercancel);
397
+ pointermove = null;
398
+ pointerup = null;
399
+ pointercancel = null;
400
+ }
401
+ props.element.addEventListener('pointerdown', pointerdown, props.options);
402
+ return () => {
403
+ props.element.removeEventListener('pointerdown', pointerdown);
404
+ remove();
405
+ };
406
+ }
407
+ gesture(props) {
408
+ let isActive = false;
409
+ const map = new Map();
410
+ const element = props.element;
411
+ const options = props.options;
412
+ const dragstart = ({ event, position }) => {
413
+ map.set(event.pointerId, Object.assign({}, position));
414
+ isActive = map.size === 2 ? true : false;
415
+ if (isActive === true && props.type === 'gesturestart') {
416
+ props.listener({ event, type: props.type });
417
+ }
418
+ };
419
+ const dragmove = ({ event, position, delta }) => {
420
+ if (map.size >= 2 && isActive === true) {
421
+ const a = map.get(event.pointerId);
422
+ const b = getOthers(event.pointerId)[0];
423
+ let scale = 0.0;
424
+ {
425
+ const v = { x: a.x - b.x, y: a.y - b.y };
426
+ const s = v.x * v.x + v.y * v.y;
427
+ scale = 1 + (s > 0.0 ? (v.x * delta.x + v.y * delta.y) / s : 0);
428
+ }
429
+ // let rotate = 0.0;
430
+ // {
431
+ // const c = { x: a.x + delta.x, y: a.y + delta.y };
432
+ // const v1 = { x: a.x - b.x, y: a.y - b.y };
433
+ // const v2 = { x: c.x - b.x, y: c.y - b.y };
434
+ // const l1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
435
+ // const l2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
436
+ // if (l1 > 0.0 && l2 > 0.0) {
437
+ // const angle = Math.acos((v1.x * v2.x + v1.y * v2.y) / (l1 * l2));
438
+ // const sign = v1.x * v2.y - v1.y * v2.x;
439
+ // rotate = sign > 0.0 ? +angle : -angle;
440
+ // }
441
+ // }
442
+ if (props.type === 'gesturemove') {
443
+ props.listener({ event, type: props.type, scale });
444
+ }
445
+ }
446
+ map.set(event.pointerId, position);
447
+ };
448
+ const dragend = ({ event }) => {
449
+ if (isActive === true) {
450
+ props.listener({ event, type: props.type, scale: 1.0 });
451
+ }
452
+ isActive = false;
453
+ map.delete(event.pointerId);
454
+ };
455
+ this.add({ element, options, type: 'dragstart', listener: dragstart });
456
+ this.add({ element, options, type: 'dragmove', listener: dragmove });
457
+ this.add({ element, options, type: 'dragend', listener: dragend });
458
+ function getOthers(id) {
459
+ const backup = map.get(id);
460
+ map.delete(id);
461
+ const others = [...map.values()];
462
+ map.set(id, backup);
463
+ return others;
464
+ }
465
+ return () => {
466
+ this.remove({ type: 'dragstart', listener: dragstart });
467
+ this.remove({ type: 'dragmove', listener: dragmove });
468
+ this.remove({ type: 'dragend', listener: dragend });
469
+ };
470
+ }
471
+ key(props) {
472
+ const execute = (event) => {
473
+ if (props.type === 'keydown' && event.repeat)
474
+ return;
475
+ props.listener({ event, type: props.type, code: event.code });
476
+ };
477
+ window.addEventListener(props.type, execute, props.options);
478
+ return () => {
479
+ window.removeEventListener(props.type, execute);
480
+ };
481
+ }
482
+ key_arrow(props) {
483
+ const keymap = {};
484
+ const keydown = (event) => {
485
+ if (event.repeat)
486
+ return;
487
+ keymap[event.code] = 1;
488
+ if (props.type === 'keydown.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
489
+ const vector = {
490
+ x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
491
+ y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
492
+ };
493
+ props.listener({ event, type: props.type, code: event.code, vector });
494
+ }
495
+ };
496
+ const keyup = (event) => {
497
+ keymap[event.code] = 0;
498
+ if (props.type === 'keyup.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
499
+ const vector = {
500
+ x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
501
+ y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
502
+ };
503
+ props.listener({ event, type: props.type, code: event.code, vector });
504
+ }
505
+ };
506
+ window.addEventListener('keydown', keydown, props.options);
507
+ window.addEventListener('keyup', keyup, props.options);
508
+ return () => {
509
+ window.removeEventListener('keydown', keydown);
510
+ window.removeEventListener('keyup', keyup);
511
+ };
512
+ }
513
+ }
514
+ function pointer(element, event) {
515
+ const rect = element.getBoundingClientRect();
516
+ const position = { x: event.clientX - rect.left, y: event.clientY - rect.top };
517
+ return { position };
518
+ }
519
+
207
520
  //----------------------------------------------------------------------------------------------------
208
521
  // unit
209
522
  //----------------------------------------------------------------------------------------------------
@@ -297,6 +610,7 @@ class Unit {
297
610
  listeners: new MapMap(),
298
611
  defines: {},
299
612
  systems: { start: [], process: [], update: [], stop: [], finalize: [] },
613
+ eventManager: new EventManager(),
300
614
  });
301
615
  // nest html element
302
616
  if (typeof unit._.target === 'string') {
@@ -481,7 +795,7 @@ class Unit {
481
795
  this._.listeners.set(type, listener, { element: this.element, execute });
482
796
  Unit.type2units.add(type, this);
483
797
  if (/^[A-Za-z]/.test(type)) {
484
- this.element.addEventListener(type, execute, options);
798
+ this._.eventManager.add({ element: this.element, type, listener: execute, options });
485
799
  }
486
800
  }
487
801
  });
@@ -494,11 +808,11 @@ class Unit {
494
808
  }
495
809
  (listener ? [listener] : [...this._.listeners.keys(type)]).forEach((listener) => {
496
810
  const item = this._.listeners.get(type, listener);
497
- if (item !== undefined) {
498
- this._.listeners.delete(type, listener);
499
- if (/^[A-Za-z]/.test(type)) {
500
- item.element.removeEventListener(type, item.execute);
501
- }
811
+ if (item === undefined)
812
+ return;
813
+ this._.listeners.delete(type, listener);
814
+ if (/^[A-Za-z]/.test(type)) {
815
+ this._.eventManager.remove({ type, listener: item.execute });
502
816
  }
503
817
  });
504
818
  if (this._.listeners.has(type) === false) {
@@ -917,209 +1231,12 @@ function AccordionContent(content, {} = {}) {
917
1231
  };
918
1232
  }
919
1233
 
920
- function ResizeEvent(resize) {
921
- const observer = new ResizeObserver(xnew$1.scope((entries) => {
922
- for (const entry of entries) {
923
- xnew$1.emit('-resize');
924
- break;
925
- }
926
- }));
927
- if (resize.element) {
928
- observer.observe(resize.element);
929
- }
930
- resize.on('finalize', () => {
931
- if (resize.element) {
932
- observer.unobserve(resize.element);
933
- }
934
- });
935
- }
936
- function DirectEvent(unit) {
937
- const state = {};
938
- const keydown = xnew$1.scope((event) => {
939
- if (event.repeat)
940
- return;
941
- state[event.code] = 1;
942
- xnew$1.emit('-keydown', { event, type: '-keydown', code: event.code });
943
- if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
944
- xnew$1.emit('-keydown.arrow', { event, type: '-keydown.arrow', code: event.code, vector: getVector() });
945
- }
946
- });
947
- const keyup = xnew$1.scope((event) => {
948
- state[event.code] = 0;
949
- xnew$1.emit('-keyup', { event, type: '-keyup', code: event.code });
950
- if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
951
- xnew$1.emit('-keyup.arrow', { event, type: '-keyup.arrow', code: event.code, vector: getVector() });
952
- }
953
- });
954
- window.addEventListener('keydown', keydown);
955
- window.addEventListener('keyup', keyup);
956
- unit.on('finalize', () => {
957
- window.removeEventListener('keydown', keydown);
958
- window.removeEventListener('keyup', keyup);
959
- });
960
- function getVector() {
961
- return {
962
- x: (state['ArrowLeft'] ? -1 : 0) + (state['ArrowRight'] ? +1 : 0),
963
- y: (state['ArrowUp'] ? -1 : 0) + (state['ArrowDown'] ? +1 : 0)
964
- };
965
- }
966
- const internal = xnew$1();
967
- internal.on('pointerdown', (event) => xnew$1.emit('-pointerdown', { event, position: getPosition(unit.element, event) }));
968
- internal.on('pointermove', (event) => xnew$1.emit('-pointermove', { event, position: getPosition(unit.element, event) }));
969
- internal.on('pointerup', (event) => xnew$1.emit('-pointerup', { event, position: getPosition(unit.element, event) }));
970
- internal.on('wheel', (event) => xnew$1.emit('-wheel', { event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } }));
971
- internal.on('click', (event) => xnew$1.emit('-click', { event, position: getPosition(unit.element, event) }));
972
- internal.on('pointerover', (event) => xnew$1.emit('-pointerover', { event, position: getPosition(unit.element, event) }));
973
- internal.on('pointerout', (event) => xnew$1.emit('-pointerout', { event, position: getPosition(unit.element, event) }));
974
- const pointerdownoutside = xnew$1.scope((event) => {
975
- if (unit.element.contains(event.target) === false) {
976
- xnew$1.emit('-pointerdown.outside', { event, position: getPosition(unit.element, event) });
977
- }
978
- });
979
- const pointerupoutside = xnew$1.scope((event) => {
980
- if (unit.element.contains(event.target) === false) {
981
- xnew$1.emit('-pointerup.outside', { event, position: getPosition(unit.element, event) });
982
- }
983
- });
984
- const clickoutside = xnew$1.scope((event) => {
985
- if (unit.element.contains(event.target) === false) {
986
- xnew$1.emit('-click.outside', { event, position: getPosition(unit.element, event) });
987
- }
988
- });
989
- document.addEventListener('pointerdown', pointerdownoutside);
990
- document.addEventListener('pointerup', pointerupoutside);
991
- document.addEventListener('click', clickoutside);
992
- unit.on('finalize', () => {
993
- document.removeEventListener('pointerdown', pointerdownoutside);
994
- document.removeEventListener('pointerup', pointerupoutside);
995
- document.removeEventListener('click', clickoutside);
996
- });
997
- const drag = xnew$1(DragEvent);
998
- drag.on('-dragstart', (...args) => xnew$1.emit('-dragstart', ...args));
999
- drag.on('-dragmove', (...args) => xnew$1.emit('-dragmove', ...args));
1000
- drag.on('-dragend', (...args) => xnew$1.emit('-dragend', ...args));
1001
- drag.on('-dragcancel', (...args) => xnew$1.emit('-dragcancel', ...args));
1002
- const gesture = xnew$1(GestureEvent);
1003
- gesture.on('-gesturestart', (...args) => xnew$1.emit('-gesturestart', ...args));
1004
- gesture.on('-gesturemove', (...args) => xnew$1.emit('-gesturemove', ...args));
1005
- gesture.on('-gestureend', (...args) => xnew$1.emit('-gestureend', ...args));
1006
- gesture.on('-gesturecancel', (...args) => xnew$1.emit('-gesturecancel', ...args));
1007
- }
1008
- function DragEvent(unit) {
1009
- const pointerdown = xnew$1.scope((event) => {
1010
- const id = event.pointerId;
1011
- const position = getPosition(unit.element, event);
1012
- let previous = position;
1013
- let connect = true;
1014
- const pointermove = xnew$1.scope((event) => {
1015
- if (event.pointerId === id) {
1016
- const position = getPosition(unit.element, event);
1017
- const delta = { x: position.x - previous.x, y: position.y - previous.y };
1018
- xnew$1.emit('-dragmove', { event, position, delta });
1019
- previous = position;
1020
- }
1021
- });
1022
- const pointerup = xnew$1.scope((event) => {
1023
- if (event.pointerId === id) {
1024
- const position = getPosition(unit.element, event);
1025
- xnew$1.emit('-dragend', { event, position, });
1026
- remove();
1027
- }
1028
- });
1029
- const pointercancel = xnew$1.scope((event) => {
1030
- if (event.pointerId === id) {
1031
- const position = getPosition(unit.element, event);
1032
- xnew$1.emit('-dragcancel', { event, position, });
1033
- remove();
1034
- }
1035
- });
1036
- window.addEventListener('pointermove', pointermove);
1037
- window.addEventListener('pointerup', pointerup);
1038
- window.addEventListener('pointercancel', pointercancel);
1039
- function remove() {
1040
- if (connect === true) {
1041
- window.removeEventListener('pointermove', pointermove);
1042
- window.removeEventListener('pointerup', pointerup);
1043
- window.removeEventListener('pointercancel', pointercancel);
1044
- connect = false;
1045
- }
1046
- }
1047
- xnew$1((unit) => unit.on('-finalize', remove));
1048
- xnew$1.emit('-dragstart', { event, position });
1049
- });
1050
- unit.on('pointerdown', pointerdown);
1051
- }
1052
- function GestureEvent(unit) {
1053
- const drag = xnew$1(DragEvent);
1054
- let isActive = false;
1055
- const map = new Map();
1056
- drag.on('-dragstart', ({ event, position }) => {
1057
- map.set(event.pointerId, Object.assign({}, position));
1058
- isActive = map.size === 2 ? true : false;
1059
- if (isActive === true) {
1060
- xnew$1.emit('-gesturestart', {});
1061
- }
1062
- });
1063
- drag.on('-dragmove', ({ event, position, delta }) => {
1064
- if (map.size >= 2 && isActive === true) {
1065
- const a = map.get(event.pointerId);
1066
- const b = getOthers(event.pointerId)[0];
1067
- let scale = 0.0;
1068
- {
1069
- const v = { x: a.x - b.x, y: a.y - b.y };
1070
- const s = v.x * v.x + v.y * v.y;
1071
- scale = 1 + (s > 0.0 ? (v.x * delta.x + v.y * delta.y) / s : 0);
1072
- }
1073
- // let rotate = 0.0;
1074
- // {
1075
- // const c = { x: a.x + delta.x, y: a.y + delta.y };
1076
- // const v1 = { x: a.x - b.x, y: a.y - b.y };
1077
- // const v2 = { x: c.x - b.x, y: c.y - b.y };
1078
- // const l1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
1079
- // const l2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
1080
- // if (l1 > 0.0 && l2 > 0.0) {
1081
- // const angle = Math.acos((v1.x * v2.x + v1.y * v2.y) / (l1 * l2));
1082
- // const sign = v1.x * v2.y - v1.y * v2.x;
1083
- // rotate = sign > 0.0 ? +angle : -angle;
1084
- // }
1085
- // }
1086
- xnew$1.emit('-gesturemove', { event, position, delta, scale });
1087
- }
1088
- map.set(event.pointerId, position);
1089
- });
1090
- drag.on('-dragend', ({ event }) => {
1091
- if (isActive === true) {
1092
- xnew$1.emit('-gestureend', {});
1093
- }
1094
- isActive = false;
1095
- map.delete(event.pointerId);
1096
- });
1097
- drag.on('-dragcancel', ({ event }) => {
1098
- if (isActive === true) {
1099
- xnew$1.emit('-gesturecancel', { event });
1100
- }
1101
- isActive = false;
1102
- map.delete(event.pointerId);
1103
- });
1104
- function getOthers(id) {
1105
- const backup = map.get(id);
1106
- map.delete(id);
1107
- const others = [...map.values()];
1108
- map.set(id, backup);
1109
- return others;
1110
- }
1111
- }
1112
- function getPosition(element, event) {
1113
- const rect = element.getBoundingClientRect();
1114
- return { x: event.clientX - rect.left, y: event.clientY - rect.top };
1115
- }
1116
-
1117
- function Screen(screen, { width = 640, height = 480, fit = 'contain' } = {}) {
1234
+ function Screen(unit, { width = 640, height = 480, fit = 'contain' } = {}) {
1118
1235
  const size = { width, height };
1119
1236
  const wrapper = xnew$1.nest('<div style="position: relative; width: 100%; height: 100%; overflow: hidden;">');
1237
+ unit.on('resize', resize);
1120
1238
  const absolute = xnew$1.nest('<div style="position: absolute; margin: auto; container-type: size; overflow: hidden;">');
1121
1239
  const canvas = xnew$1(`<canvas width="${width}" height="${height}" style="width: 100%; height: 100%; vertical-align: bottom; user-select: none; user-drag: none; pointer-events: auto;">`);
1122
- xnew$1(wrapper, ResizeEvent).on('-resize', resize);
1123
1240
  resize();
1124
1241
  function resize() {
1125
1242
  const aspect = size.width / size.height;
@@ -1260,15 +1377,14 @@ function DragFrame(frame, { x = 0, y = 0 } = {}) {
1260
1377
  const absolute = xnew$1.nest(`<div style="position: absolute; top: ${y}px; left: ${x}px;">`);
1261
1378
  xnew$1.context('xnew.dragframe', { frame, absolute });
1262
1379
  }
1263
- function DragTarget(target, {} = {}) {
1380
+ function DragTarget(unit, {} = {}) {
1264
1381
  const { frame, absolute } = xnew$1.context('xnew.dragframe');
1265
- xnew$1.nest('<div>');
1266
- const direct = xnew$1(absolute.parentElement, DirectEvent);
1382
+ const target = xnew$1(absolute.parentElement);
1267
1383
  const current = { x: 0, y: 0 };
1268
1384
  const offset = { x: 0, y: 0 };
1269
1385
  let dragged = false;
1270
- direct.on('-dragstart', ({ event, position }) => {
1271
- if (target.element.contains(event.target) === false)
1386
+ target.on('dragstart', ({ event, position }) => {
1387
+ if (unit.element.contains(event.target) === false)
1272
1388
  return;
1273
1389
  dragged = true;
1274
1390
  offset.x = position.x - parseFloat(absolute.style.left || '0');
@@ -1276,7 +1392,7 @@ function DragTarget(target, {} = {}) {
1276
1392
  current.x = position.x - offset.x;
1277
1393
  current.y = position.y - offset.y;
1278
1394
  });
1279
- direct.on('-dragmove', ({ event, delta }) => {
1395
+ target.on('dragmove', ({ event, delta }) => {
1280
1396
  if (dragged !== true)
1281
1397
  return;
1282
1398
  current.x += delta.x;
@@ -1284,9 +1400,10 @@ function DragTarget(target, {} = {}) {
1284
1400
  absolute.style.left = `${current.x}px`;
1285
1401
  absolute.style.top = `${current.y}px`;
1286
1402
  });
1287
- direct.on('-dragcancel -dragend', ({ event }) => {
1403
+ target.on('dragend', ({ event }) => {
1288
1404
  dragged = false;
1289
1405
  });
1406
+ xnew$1.nest('<div>');
1290
1407
  }
1291
1408
 
1292
1409
  //----------------------------------------------------------------------------------------------------
@@ -1295,7 +1412,7 @@ function DragTarget(target, {} = {}) {
1295
1412
  function SVGTemplate(self, { stroke = 'currentColor', strokeOpacity = 0.8, strokeWidth = 2, strokeLinejoin = 'round', fill = null, fillOpacity = 0.8 }) {
1296
1413
  xnew$1.nest(`<svg
1297
1414
  viewBox="0 0 100 100"
1298
- style="position: absolute; width: 100%; height: 100%; pointer-select: none;
1415
+ style="position: absolute; width: 100%; height: 100%; select: none;
1299
1416
  stroke: ${stroke}; stroke-opacity: ${strokeOpacity}; stroke-width: ${strokeWidth}; stroke-linejoin: ${strokeLinejoin};
1300
1417
  ${fill ? `fill: ${fill}; fill-opacity: ${fillOpacity};` : ''}
1301
1418
  ">`);
@@ -1305,7 +1422,7 @@ function AnalogStick(unit, { stroke = 'currentColor', strokeOpacity = 0.8, strok
1305
1422
  const internal = xnew$1((unit) => {
1306
1423
  let newsize = Math.min(outer.clientWidth, outer.clientHeight);
1307
1424
  const inner = xnew$1.nest(`<div style="position: absolute; width: ${newsize}px; height: ${newsize}px; margin: auto; inset: 0; cursor: pointer; pointer-select: none; pointer-events: auto; overflow: hidden;">`);
1308
- xnew$1(outer, ResizeEvent).on('-resize', () => {
1425
+ xnew$1(outer).on('resize', () => {
1309
1426
  newsize = Math.min(outer.clientWidth, outer.clientHeight);
1310
1427
  inner.style.width = `${newsize}px`;
1311
1428
  inner.style.height = `${newsize}px`;
@@ -1321,22 +1438,21 @@ function AnalogStick(unit, { stroke = 'currentColor', strokeOpacity = 0.8, strok
1321
1438
  xnew$1.extend(SVGTemplate, { fill, fillOpacity, stroke, strokeOpacity, strokeWidth, strokeLinejoin });
1322
1439
  xnew$1('<circle cx="50" cy="50" r="23">');
1323
1440
  });
1324
- const direct = xnew$1(DirectEvent);
1325
- direct.on('-dragstart', ({ event, position }) => {
1441
+ unit.on('dragstart', ({ event, position }) => {
1326
1442
  const vector = getVector(position);
1327
1443
  target.element.style.filter = 'brightness(90%)';
1328
1444
  target.element.style.left = vector.x * newsize / 4 + 'px';
1329
1445
  target.element.style.top = vector.y * newsize / 4 + 'px';
1330
1446
  xnew$1.emit('-down', { vector });
1331
1447
  });
1332
- direct.on('-dragmove', ({ event, position }) => {
1448
+ unit.on('dragmove', ({ event, position }) => {
1333
1449
  const vector = getVector(position);
1334
1450
  target.element.style.filter = 'brightness(90%)';
1335
1451
  target.element.style.left = vector.x * newsize / 4 + 'px';
1336
1452
  target.element.style.top = vector.y * newsize / 4 + 'px';
1337
1453
  xnew$1.emit('-move', { vector });
1338
1454
  });
1339
- direct.on('-dragend', ({ event }) => {
1455
+ unit.on('dragend', ({ event }) => {
1340
1456
  const vector = { x: 0, y: 0 };
1341
1457
  target.element.style.filter = '';
1342
1458
  target.element.style.left = vector.x * newsize / 4 + 'px';
@@ -1360,7 +1476,7 @@ function DirectionalPad(unit, { diagonal = true, stroke = 'currentColor', stroke
1360
1476
  const internal = xnew$1((unit) => {
1361
1477
  let newsize = Math.min(outer.clientWidth, outer.clientHeight);
1362
1478
  const inner = xnew$1.nest(`<div style="position: absolute; width: ${newsize}px; height: ${newsize}px; margin: auto; inset: 0; cursor: pointer; pointer-select: none; pointer-events: auto; overflow: hidden;">`);
1363
- xnew$1(outer, ResizeEvent).on('-resize', () => {
1479
+ xnew$1(outer).on('resize', () => {
1364
1480
  newsize = Math.min(outer.clientWidth, outer.clientHeight);
1365
1481
  inner.style.width = `${newsize}px`;
1366
1482
  inner.style.height = `${newsize}px`;
@@ -1388,8 +1504,7 @@ function DirectionalPad(unit, { diagonal = true, stroke = 'currentColor', stroke
1388
1504
  xnew$1('<polygon points="11 50 20 42 20 58">');
1389
1505
  xnew$1('<polygon points="89 50 80 42 80 58">');
1390
1506
  });
1391
- const direct = xnew$1(DirectEvent);
1392
- direct.on('-dragstart', ({ event, position }) => {
1507
+ unit.on('dragstart', ({ event, position }) => {
1393
1508
  const vector = getVector(position);
1394
1509
  targets[0].element.style.filter = (vector.y < 0) ? 'brightness(90%)' : '';
1395
1510
  targets[1].element.style.filter = (vector.y > 0) ? 'brightness(90%)' : '';
@@ -1397,7 +1512,7 @@ function DirectionalPad(unit, { diagonal = true, stroke = 'currentColor', stroke
1397
1512
  targets[3].element.style.filter = (vector.x > 0) ? 'brightness(90%)' : '';
1398
1513
  xnew$1.emit('-down', { vector });
1399
1514
  });
1400
- direct.on('-dragmove', ({ event, position }) => {
1515
+ unit.on('dragmove', ({ event, position }) => {
1401
1516
  const vector = getVector(position);
1402
1517
  targets[0].element.style.filter = (vector.y < 0) ? 'brightness(90%)' : '';
1403
1518
  targets[1].element.style.filter = (vector.y > 0) ? 'brightness(90%)' : '';
@@ -1405,7 +1520,7 @@ function DirectionalPad(unit, { diagonal = true, stroke = 'currentColor', stroke
1405
1520
  targets[3].element.style.filter = (vector.x > 0) ? 'brightness(90%)' : '';
1406
1521
  xnew$1.emit('-move', { vector });
1407
1522
  });
1408
- direct.on('-dragend', ({ event }) => {
1523
+ unit.on('dragend', ({ event }) => {
1409
1524
  const vector = { x: 0, y: 0 };
1410
1525
  targets[0].element.style.filter = '';
1411
1526
  targets[1].element.style.filter = '';
@@ -1457,249 +1572,29 @@ function TextStream(unit, { text = '', speed = 50, fade = 300 } = {}) {
1457
1572
  const index = Math.floor((new Date().getTime() - start) / speed);
1458
1573
  // Display characters up to the current index (fade in)
1459
1574
  for (let i = 0; i < chars.length; i++) {
1460
- if (i <= index) {
1461
- chars[i].element.style.opacity = '1';
1462
- }
1463
- }
1464
- if (state === 0 && index >= text.length) {
1465
- action();
1466
- }
1467
- });
1468
- xnew$1.timeout(() => {
1469
- xnew$1(document.body).on('click wheel', action);
1470
- xnew$1(DirectEvent).on('-keydown', action);
1471
- }, 100);
1472
- function action() {
1473
- if (state === 0) {
1474
- state = 1;
1475
- for (let i = 0; i < chars.length; i++) {
1476
- chars[i].element.style.opacity = '1';
1477
- }
1478
- xnew$1.emit('-complete');
1479
- }
1480
- else if (state === 1) {
1481
- state = 2;
1482
- xnew$1.emit('-next');
1483
- }
1484
- }
1485
- }
1486
-
1487
- const context = window.AudioContext ? new window.AudioContext() : (null);
1488
- const master = context ? context.createGain() : (null);
1489
- if (context) {
1490
- master.gain.value = 0.1;
1491
- master.connect(context.destination);
1492
- }
1493
- class AudioFile {
1494
- constructor(path) {
1495
- this.promise = fetch(path)
1496
- .then((response) => response.arrayBuffer())
1497
- .then((response) => context.decodeAudioData(response))
1498
- .then((response) => { this.buffer = response; })
1499
- .catch(() => {
1500
- console.warn(`"${path}" could not be loaded.`);
1501
- });
1502
- this.amp = context.createGain();
1503
- this.amp.gain.value = 1.0;
1504
- this.amp.connect(master);
1505
- this.fade = context.createGain();
1506
- this.fade.gain.value = 1.0;
1507
- this.fade.connect(this.amp);
1508
- this.source = null;
1509
- this.played = null;
1510
- }
1511
- set volume(value) {
1512
- this.amp.gain.value = value;
1513
- }
1514
- get volume() {
1515
- return this.amp.gain.value;
1516
- }
1517
- play({ offset = 0, fade = 0, loop = false } = {}) {
1518
- if (this.buffer !== undefined && this.played === null) {
1519
- this.source = context.createBufferSource();
1520
- this.source.buffer = this.buffer;
1521
- this.source.loop = loop;
1522
- this.source.connect(this.fade);
1523
- this.played = context.currentTime;
1524
- this.source.playbackRate.value = 1;
1525
- this.source.start(context.currentTime, offset / 1000);
1526
- // Apply fade-in effect if fade duration is specified
1527
- if (fade > 0) {
1528
- this.fade.gain.setValueAtTime(0, context.currentTime);
1529
- this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1530
- }
1531
- this.source.onended = () => {
1532
- var _a;
1533
- this.played = null;
1534
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1535
- this.source = null;
1536
- };
1537
- }
1538
- }
1539
- pause({ fade = 0 } = {}) {
1540
- var _a, _b;
1541
- if (this.buffer !== undefined && this.played !== null) {
1542
- const elapsed = (context.currentTime - this.played) % this.buffer.duration * 1000;
1543
- // Apply fade-out effect if fade duration is specified
1544
- if (fade > 0) {
1545
- this.fade.gain.setValueAtTime(1.0, context.currentTime);
1546
- this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1547
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1548
- }
1549
- else {
1550
- (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1551
- }
1552
- this.played = null;
1553
- return elapsed;
1554
- }
1555
- }
1556
- clear() {
1557
- var _a;
1558
- this.amp.disconnect();
1559
- this.fade.disconnect();
1560
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1561
- }
1562
- }
1563
- const keymap = {
1564
- 'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
1565
- 'C1': 32.703, 'C#1': 34.648, 'D1': 36.708, 'D#1': 38.891, 'E1': 41.203, 'F1': 43.654, 'F#1': 46.249, 'G1': 48.999, 'G#1': 51.913, 'A1': 55.000, 'A#1': 58.270, 'B1': 61.735,
1566
- 'C2': 65.406, 'C#2': 69.296, 'D2': 73.416, 'D#2': 77.782, 'E2': 82.407, 'F2': 87.307, 'F#2': 92.499, 'G2': 97.999, 'G#2': 103.826, 'A2': 110.000, 'A#2': 116.541, 'B2': 123.471,
1567
- 'C3': 130.813, 'C#3': 138.591, 'D3': 146.832, 'D#3': 155.563, 'E3': 164.814, 'F3': 174.614, 'F#3': 184.997, 'G3': 195.998, 'G#3': 207.652, 'A3': 220.000, 'A#3': 233.082, 'B3': 246.942,
1568
- 'C4': 261.626, 'C#4': 277.183, 'D4': 293.665, 'D#4': 311.127, 'E4': 329.628, 'F4': 349.228, 'F#4': 369.994, 'G4': 391.995, 'G#4': 415.305, 'A4': 440.000, 'A#4': 466.164, 'B4': 493.883,
1569
- 'C5': 523.251, 'C#5': 554.365, 'D5': 587.330, 'D#5': 622.254, 'E5': 659.255, 'F5': 698.456, 'F#5': 739.989, 'G5': 783.991, 'G#5': 830.609, 'A5': 880.000, 'A#5': 932.328, 'B5': 987.767,
1570
- 'C6': 1046.502, 'C#6': 1108.731, 'D6': 1174.659, 'D#6': 1244.508, 'E6': 1318.510, 'F6': 1396.913, 'F#6': 1479.978, 'G6': 1567.982, 'G#6': 1661.219, 'A6': 1760.000, 'A#6': 1864.655, 'B6': 1975.533,
1571
- 'C7': 2093.005, 'C#7': 2217.461, 'D7': 2349.318, 'D#7': 2489.016, 'E7': 2637.020, 'F7': 2793.826, 'F#7': 2959.955, 'G7': 3135.963, 'G#7': 3322.438, 'A7': 3520.000, 'A#7': 3729.310, 'B7': 3951.066,
1572
- 'C8': 4186.009,
1573
- };
1574
- const notemap = {
1575
- '1m': 4.000, '2n': 2.000, '4n': 1.000, '8n': 0.500, '16n': 0.250, '32n': 0.125,
1576
- };
1577
- class Synthesizer {
1578
- constructor(props) { this.props = props; }
1579
- press(frequency, duration, wait) {
1580
- var _a;
1581
- const props = this.props;
1582
- const fv = typeof frequency === 'string' ? keymap[frequency] : frequency;
1583
- const dv = typeof duration === 'string' ? (notemap[duration] * 60 / ((_a = props.bpm) !== null && _a !== void 0 ? _a : 120)) : (typeof duration === 'number' ? (duration / 1000) : 0);
1584
- const start = context.currentTime + (wait !== null && wait !== void 0 ? wait : 0) / 1000;
1585
- const nodes = {};
1586
- nodes.oscillator = context.createOscillator();
1587
- nodes.oscillator.type = props.oscillator.type;
1588
- nodes.oscillator.frequency.value = fv;
1589
- if (props.oscillator.LFO) {
1590
- nodes.oscillatorLFO = context.createOscillator();
1591
- nodes.oscillatorLFODepth = context.createGain();
1592
- nodes.oscillatorLFODepth.gain.value = fv * (Math.pow(2.0, props.oscillator.LFO.amount / 12.0) - 1.0);
1593
- nodes.oscillatorLFO.type = props.oscillator.LFO.type;
1594
- nodes.oscillatorLFO.frequency.value = props.oscillator.LFO.rate;
1595
- nodes.oscillatorLFO.start(start);
1596
- nodes.oscillatorLFO.connect(nodes.oscillatorLFODepth);
1597
- nodes.oscillatorLFODepth.connect(nodes.oscillator.frequency);
1598
- }
1599
- nodes.amp = context.createGain();
1600
- nodes.amp.gain.value = 0.0;
1601
- nodes.target = context.createGain();
1602
- nodes.target.gain.value = 1.0;
1603
- nodes.amp.connect(nodes.target);
1604
- nodes.target.connect(master);
1605
- if (props.filter) {
1606
- nodes.filter = context.createBiquadFilter();
1607
- nodes.filter.type = props.filter.type;
1608
- nodes.filter.frequency.value = props.filter.cutoff;
1609
- nodes.oscillator.connect(nodes.filter);
1610
- nodes.filter.connect(nodes.amp);
1611
- }
1612
- else {
1613
- nodes.oscillator.connect(nodes.amp);
1614
- }
1615
- if (props.reverb) {
1616
- nodes.convolver = context.createConvolver();
1617
- nodes.convolver.buffer = impulseResponse({ time: props.reverb.time });
1618
- nodes.convolverDepth = context.createGain();
1619
- nodes.convolverDepth.gain.value = 1.0;
1620
- nodes.convolverDepth.gain.value *= props.reverb.mix;
1621
- nodes.target.gain.value *= (1.0 - props.reverb.mix);
1622
- nodes.amp.connect(nodes.convolver);
1623
- nodes.convolver.connect(nodes.convolverDepth);
1624
- nodes.convolverDepth.connect(master);
1625
- }
1626
- if (props.oscillator.envelope) {
1627
- const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1628
- startEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1629
- }
1630
- if (props.amp.envelope) {
1631
- startEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1632
- }
1633
- nodes.oscillator.start(start);
1634
- if (dv > 0) {
1635
- release();
1636
- }
1637
- else {
1638
- return { release };
1639
- }
1640
- function release() {
1641
- let stop = null;
1642
- const end = dv > 0 ? dv : (context.currentTime - start);
1643
- if (props.amp.envelope) {
1644
- const ADSR = props.amp.envelope.ADSR;
1645
- const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1646
- const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1647
- stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1648
- }
1649
- else {
1650
- stop = start + end;
1651
- }
1652
- if (nodes.oscillatorLFO) {
1653
- nodes.oscillatorLFO.stop(stop);
1654
- }
1655
- if (props.oscillator.envelope) {
1656
- const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1657
- stopEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1658
- }
1659
- if (props.amp.envelope) {
1660
- stopEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1661
- }
1662
- nodes.oscillator.stop(stop);
1663
- setTimeout(() => {
1664
- var _a, _b, _c, _d, _e;
1665
- nodes.oscillator.disconnect();
1666
- nodes.amp.disconnect();
1667
- nodes.target.disconnect();
1668
- (_a = nodes.oscillatorLFO) === null || _a === void 0 ? void 0 : _a.disconnect();
1669
- (_b = nodes.oscillatorLFODepth) === null || _b === void 0 ? void 0 : _b.disconnect();
1670
- (_c = nodes.filter) === null || _c === void 0 ? void 0 : _c.disconnect();
1671
- (_d = nodes.convolver) === null || _d === void 0 ? void 0 : _d.disconnect();
1672
- (_e = nodes.convolverDepth) === null || _e === void 0 ? void 0 : _e.disconnect();
1673
- }, 2000);
1674
- }
1675
- function stopEnvelope(param, base, amount, ADSR) {
1676
- const end = dv > 0 ? dv : (context.currentTime - start);
1677
- const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1678
- if (rate < 1.0) {
1679
- param.cancelScheduledValues(start);
1680
- param.setValueAtTime(base, start);
1681
- param.linearRampToValueAtTime(base + amount * rate, start + ADSR[0] / 1000 * rate);
1682
- param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1575
+ if (i <= index) {
1576
+ chars[i].element.style.opacity = '1';
1683
1577
  }
1684
- param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1685
- param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1686
1578
  }
1687
- function startEnvelope(param, base, amount, ADSR) {
1688
- param.value = base;
1689
- param.setValueAtTime(base, start);
1690
- param.linearRampToValueAtTime(base + amount, start + ADSR[0] / 1000);
1691
- param.linearRampToValueAtTime(base + amount * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000);
1579
+ if (state === 0 && index >= text.length) {
1580
+ action();
1692
1581
  }
1693
- function impulseResponse({ time, decay = 2.0 }) {
1694
- const length = context.sampleRate * time / 1000;
1695
- const impulse = context.createBuffer(2, length, context.sampleRate);
1696
- const ch0 = impulse.getChannelData(0);
1697
- const ch1 = impulse.getChannelData(1);
1698
- for (let i = 0; i < length; i++) {
1699
- ch0[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1700
- ch1[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1582
+ });
1583
+ xnew$1.timeout(() => {
1584
+ xnew$1(document.body).on('click wheel', action);
1585
+ unit.on('keydown', action);
1586
+ }, 100);
1587
+ function action() {
1588
+ if (state === 0) {
1589
+ state = 1;
1590
+ for (let i = 0; i < chars.length; i++) {
1591
+ chars[i].element.style.opacity = '1';
1701
1592
  }
1702
- return impulse;
1593
+ xnew$1.emit('-complete');
1594
+ }
1595
+ else if (state === 1) {
1596
+ state = 2;
1597
+ xnew$1.emit('-next');
1703
1598
  }
1704
1599
  }
1705
1600
  }
@@ -3346,35 +3241,228 @@ const icons = {
3346
3241
  XMark(unit, props) { icon('XMark', props); },
3347
3242
  };
3348
3243
 
3349
- function VolumeController(unit, {} = {}) {
3350
- xnew$1.nest(`<div style="position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: flex-end; pointer-events: none; container-type: size;">`);
3351
- xnew$1.extend(DirectEvent);
3352
- unit.on('-pointerdown', ({ event }) => event.stopPropagation());
3353
- const slider = xnew$1(`<input type="range" min="0" max="100" value="${master.gain.value * 100}"
3354
- style="display: none; width: calc(96cqw - 100cqh); margin: 0 2cqw; cursor: pointer; pointer-events: auto;"
3355
- >`);
3356
- unit.on('-click:outside', () => slider.element.style.display = 'none');
3357
- const button = xnew$1((button) => {
3358
- xnew$1.nest('<div style="position: relative; width: 100cqh; height: 100cqh; cursor: pointer; pointer-events: auto;">');
3359
- let icon = xnew$1(master.gain.value > 0 ? icons.SpeakerWave : icons.SpeakerXMark);
3360
- return {
3361
- update() {
3362
- icon === null || icon === void 0 ? void 0 : icon.finalize();
3363
- icon = xnew$1(master.gain.value > 0 ? icons.SpeakerWave : icons.SpeakerXMark);
3244
+ const context = window.AudioContext ? new window.AudioContext() : (null);
3245
+ const master = context ? context.createGain() : (null);
3246
+ if (context) {
3247
+ master.gain.value = 0.1;
3248
+ master.connect(context.destination);
3249
+ }
3250
+ class AudioFile {
3251
+ constructor(path) {
3252
+ this.promise = fetch(path)
3253
+ .then((response) => response.arrayBuffer())
3254
+ .then((response) => context.decodeAudioData(response))
3255
+ .then((response) => { this.buffer = response; })
3256
+ .catch(() => {
3257
+ console.warn(`"${path}" could not be loaded.`);
3258
+ });
3259
+ this.amp = context.createGain();
3260
+ this.amp.gain.value = 1.0;
3261
+ this.amp.connect(master);
3262
+ this.fade = context.createGain();
3263
+ this.fade.gain.value = 1.0;
3264
+ this.fade.connect(this.amp);
3265
+ this.source = null;
3266
+ this.played = null;
3267
+ }
3268
+ set volume(value) {
3269
+ this.amp.gain.value = value;
3270
+ }
3271
+ get volume() {
3272
+ return this.amp.gain.value;
3273
+ }
3274
+ play({ offset = 0, fade = 0, loop = false } = {}) {
3275
+ if (this.buffer !== undefined && this.played === null) {
3276
+ this.source = context.createBufferSource();
3277
+ this.source.buffer = this.buffer;
3278
+ this.source.loop = loop;
3279
+ this.source.connect(this.fade);
3280
+ this.played = context.currentTime;
3281
+ this.source.playbackRate.value = 1;
3282
+ this.source.start(context.currentTime, offset / 1000);
3283
+ // Apply fade-in effect if fade duration is specified
3284
+ if (fade > 0) {
3285
+ this.fade.gain.setValueAtTime(0, context.currentTime);
3286
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
3364
3287
  }
3365
- };
3366
- });
3367
- button.on('click', () => slider.element.style.display = slider.element.style.display !== 'none' ? 'none' : 'flex');
3368
- slider.on('input', (event) => {
3369
- master.gain.value = parseFloat(event.target.value) / 100;
3370
- button.update();
3371
- });
3288
+ this.source.onended = () => {
3289
+ var _a;
3290
+ this.played = null;
3291
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
3292
+ this.source = null;
3293
+ };
3294
+ }
3295
+ }
3296
+ pause({ fade = 0 } = {}) {
3297
+ var _a, _b;
3298
+ if (this.buffer !== undefined && this.played !== null) {
3299
+ const elapsed = (context.currentTime - this.played) % this.buffer.duration * 1000;
3300
+ // Apply fade-out effect if fade duration is specified
3301
+ if (fade > 0) {
3302
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
3303
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
3304
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
3305
+ }
3306
+ else {
3307
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
3308
+ }
3309
+ this.played = null;
3310
+ return elapsed;
3311
+ }
3312
+ }
3313
+ clear() {
3314
+ var _a;
3315
+ this.amp.disconnect();
3316
+ this.fade.disconnect();
3317
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
3318
+ }
3319
+ }
3320
+ const keymap = {
3321
+ 'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
3322
+ 'C1': 32.703, 'C#1': 34.648, 'D1': 36.708, 'D#1': 38.891, 'E1': 41.203, 'F1': 43.654, 'F#1': 46.249, 'G1': 48.999, 'G#1': 51.913, 'A1': 55.000, 'A#1': 58.270, 'B1': 61.735,
3323
+ 'C2': 65.406, 'C#2': 69.296, 'D2': 73.416, 'D#2': 77.782, 'E2': 82.407, 'F2': 87.307, 'F#2': 92.499, 'G2': 97.999, 'G#2': 103.826, 'A2': 110.000, 'A#2': 116.541, 'B2': 123.471,
3324
+ 'C3': 130.813, 'C#3': 138.591, 'D3': 146.832, 'D#3': 155.563, 'E3': 164.814, 'F3': 174.614, 'F#3': 184.997, 'G3': 195.998, 'G#3': 207.652, 'A3': 220.000, 'A#3': 233.082, 'B3': 246.942,
3325
+ 'C4': 261.626, 'C#4': 277.183, 'D4': 293.665, 'D#4': 311.127, 'E4': 329.628, 'F4': 349.228, 'F#4': 369.994, 'G4': 391.995, 'G#4': 415.305, 'A4': 440.000, 'A#4': 466.164, 'B4': 493.883,
3326
+ 'C5': 523.251, 'C#5': 554.365, 'D5': 587.330, 'D#5': 622.254, 'E5': 659.255, 'F5': 698.456, 'F#5': 739.989, 'G5': 783.991, 'G#5': 830.609, 'A5': 880.000, 'A#5': 932.328, 'B5': 987.767,
3327
+ 'C6': 1046.502, 'C#6': 1108.731, 'D6': 1174.659, 'D#6': 1244.508, 'E6': 1318.510, 'F6': 1396.913, 'F#6': 1479.978, 'G6': 1567.982, 'G#6': 1661.219, 'A6': 1760.000, 'A#6': 1864.655, 'B6': 1975.533,
3328
+ 'C7': 2093.005, 'C#7': 2217.461, 'D7': 2349.318, 'D#7': 2489.016, 'E7': 2637.020, 'F7': 2793.826, 'F#7': 2959.955, 'G7': 3135.963, 'G#7': 3322.438, 'A7': 3520.000, 'A#7': 3729.310, 'B7': 3951.066,
3329
+ 'C8': 4186.009,
3330
+ };
3331
+ const notemap = {
3332
+ '1m': 4.000, '2n': 2.000, '4n': 1.000, '8n': 0.500, '16n': 0.250, '32n': 0.125,
3333
+ };
3334
+ class Synthesizer {
3335
+ constructor(props) { this.props = props; }
3336
+ press(frequency, duration, wait) {
3337
+ var _a;
3338
+ const props = this.props;
3339
+ const fv = typeof frequency === 'string' ? keymap[frequency] : frequency;
3340
+ const dv = typeof duration === 'string' ? (notemap[duration] * 60 / ((_a = props.bpm) !== null && _a !== void 0 ? _a : 120)) : (typeof duration === 'number' ? (duration / 1000) : 0);
3341
+ const start = context.currentTime + (wait !== null && wait !== void 0 ? wait : 0) / 1000;
3342
+ const nodes = {};
3343
+ nodes.oscillator = context.createOscillator();
3344
+ nodes.oscillator.type = props.oscillator.type;
3345
+ nodes.oscillator.frequency.value = fv;
3346
+ if (props.oscillator.LFO) {
3347
+ nodes.oscillatorLFO = context.createOscillator();
3348
+ nodes.oscillatorLFODepth = context.createGain();
3349
+ nodes.oscillatorLFODepth.gain.value = fv * (Math.pow(2.0, props.oscillator.LFO.amount / 12.0) - 1.0);
3350
+ nodes.oscillatorLFO.type = props.oscillator.LFO.type;
3351
+ nodes.oscillatorLFO.frequency.value = props.oscillator.LFO.rate;
3352
+ nodes.oscillatorLFO.start(start);
3353
+ nodes.oscillatorLFO.connect(nodes.oscillatorLFODepth);
3354
+ nodes.oscillatorLFODepth.connect(nodes.oscillator.frequency);
3355
+ }
3356
+ nodes.amp = context.createGain();
3357
+ nodes.amp.gain.value = 0.0;
3358
+ nodes.target = context.createGain();
3359
+ nodes.target.gain.value = 1.0;
3360
+ nodes.amp.connect(nodes.target);
3361
+ nodes.target.connect(master);
3362
+ if (props.filter) {
3363
+ nodes.filter = context.createBiquadFilter();
3364
+ nodes.filter.type = props.filter.type;
3365
+ nodes.filter.frequency.value = props.filter.cutoff;
3366
+ nodes.oscillator.connect(nodes.filter);
3367
+ nodes.filter.connect(nodes.amp);
3368
+ }
3369
+ else {
3370
+ nodes.oscillator.connect(nodes.amp);
3371
+ }
3372
+ if (props.reverb) {
3373
+ nodes.convolver = context.createConvolver();
3374
+ nodes.convolver.buffer = impulseResponse({ time: props.reverb.time });
3375
+ nodes.convolverDepth = context.createGain();
3376
+ nodes.convolverDepth.gain.value = 1.0;
3377
+ nodes.convolverDepth.gain.value *= props.reverb.mix;
3378
+ nodes.target.gain.value *= (1.0 - props.reverb.mix);
3379
+ nodes.amp.connect(nodes.convolver);
3380
+ nodes.convolver.connect(nodes.convolverDepth);
3381
+ nodes.convolverDepth.connect(master);
3382
+ }
3383
+ if (props.oscillator.envelope) {
3384
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
3385
+ startEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
3386
+ }
3387
+ if (props.amp.envelope) {
3388
+ startEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
3389
+ }
3390
+ nodes.oscillator.start(start);
3391
+ if (dv > 0) {
3392
+ release();
3393
+ }
3394
+ else {
3395
+ return { release };
3396
+ }
3397
+ function release() {
3398
+ let stop = null;
3399
+ const end = dv > 0 ? dv : (context.currentTime - start);
3400
+ if (props.amp.envelope) {
3401
+ const ADSR = props.amp.envelope.ADSR;
3402
+ const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
3403
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
3404
+ stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
3405
+ }
3406
+ else {
3407
+ stop = start + end;
3408
+ }
3409
+ if (nodes.oscillatorLFO) {
3410
+ nodes.oscillatorLFO.stop(stop);
3411
+ }
3412
+ if (props.oscillator.envelope) {
3413
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
3414
+ stopEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
3415
+ }
3416
+ if (props.amp.envelope) {
3417
+ stopEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
3418
+ }
3419
+ nodes.oscillator.stop(stop);
3420
+ setTimeout(() => {
3421
+ var _a, _b, _c, _d, _e;
3422
+ nodes.oscillator.disconnect();
3423
+ nodes.amp.disconnect();
3424
+ nodes.target.disconnect();
3425
+ (_a = nodes.oscillatorLFO) === null || _a === void 0 ? void 0 : _a.disconnect();
3426
+ (_b = nodes.oscillatorLFODepth) === null || _b === void 0 ? void 0 : _b.disconnect();
3427
+ (_c = nodes.filter) === null || _c === void 0 ? void 0 : _c.disconnect();
3428
+ (_d = nodes.convolver) === null || _d === void 0 ? void 0 : _d.disconnect();
3429
+ (_e = nodes.convolverDepth) === null || _e === void 0 ? void 0 : _e.disconnect();
3430
+ }, 2000);
3431
+ }
3432
+ function stopEnvelope(param, base, amount, ADSR) {
3433
+ const end = dv > 0 ? dv : (context.currentTime - start);
3434
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
3435
+ if (rate < 1.0) {
3436
+ param.cancelScheduledValues(start);
3437
+ param.setValueAtTime(base, start);
3438
+ param.linearRampToValueAtTime(base + amount * rate, start + ADSR[0] / 1000 * rate);
3439
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
3440
+ }
3441
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
3442
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
3443
+ }
3444
+ function startEnvelope(param, base, amount, ADSR) {
3445
+ param.value = base;
3446
+ param.setValueAtTime(base, start);
3447
+ param.linearRampToValueAtTime(base + amount, start + ADSR[0] / 1000);
3448
+ param.linearRampToValueAtTime(base + amount * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000);
3449
+ }
3450
+ function impulseResponse({ time, decay = 2.0 }) {
3451
+ const length = context.sampleRate * time / 1000;
3452
+ const impulse = context.createBuffer(2, length, context.sampleRate);
3453
+ const ch0 = impulse.getChannelData(0);
3454
+ const ch1 = impulse.getChannelData(1);
3455
+ for (let i = 0; i < length; i++) {
3456
+ ch0[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
3457
+ ch1[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
3458
+ }
3459
+ return impulse;
3460
+ }
3461
+ }
3372
3462
  }
3373
3463
 
3374
3464
  const basics = {
3375
3465
  Screen,
3376
- DirectEvent,
3377
- ResizeEvent,
3378
3466
  ModalFrame,
3379
3467
  ModalContent,
3380
3468
  AccordionFrame,
@@ -3389,7 +3477,6 @@ const basics = {
3389
3477
  DragTarget,
3390
3478
  AnalogStick,
3391
3479
  DirectionalPad,
3392
- VolumeController
3393
3480
  };
3394
3481
  const audio = {
3395
3482
  load(path) {