@mulsense/xnew 0.1.12 → 0.2.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/README.md CHANGED
@@ -13,7 +13,7 @@ providing a flexible architecture well-suited for applications with dynamic scen
13
13
  ### Via CDN
14
14
  Include the following script in your HTML file:
15
15
  ```html
16
- <script src="https://unpkg.com/@mulsense/xnew@0.1.x/dist/xnew.js"></script>
16
+ <script src="https://unpkg.com/@mulsense/xnew@0.2.x/dist/xnew.js"></script>
17
17
  ```
18
18
 
19
19
  ### Via CDN (ESM)
@@ -22,7 +22,7 @@ Use the ES module version with an import map:
22
22
  <script type="importmap">
23
23
  {
24
24
  "imports": {
25
- "@mulsense/xnew": "https://unpkg.com/@mulsense/xnew@0.1.x/dist/xnew.mjs"
25
+ "@mulsense/xnew": "https://unpkg.com/@mulsense/xnew@0.2.x/dist/xnew.mjs"
26
26
  }
27
27
  }
28
28
  </script>
@@ -37,7 +37,7 @@ import xnew from '@mulsense/xnew';
37
37
  ### Via npm
38
38
  Install `xnew` using npm:
39
39
  ```bash
40
- npm install @mulsense/xnew@0.1.x
40
+ npm install @mulsense/xnew@0.2.x
41
41
  ```
42
42
 
43
43
  Then import it in your JavaScript file:
@@ -23,7 +23,7 @@
23
23
  root.isActive = true;
24
24
  root.engine = engine !== null && engine !== void 0 ? engine : Matter.Engine.create();
25
25
  xnew.context('xmatter.object', root.engine.world);
26
- self.on('update', () => {
26
+ self.on('-update', () => {
27
27
  if (root.isActive) {
28
28
  Matter.Engine.update(root.engine);
29
29
  }
@@ -33,7 +33,7 @@
33
33
  const parent = xnew.context('xmatter.object');
34
34
  xnew.context('xmatter.object', object);
35
35
  Matter.Composite.add(parent, object);
36
- self.on('finalize', () => {
36
+ self.on('-finalize', () => {
37
37
  Matter.Composite.remove(parent, object);
38
38
  });
39
39
  }
@@ -20,7 +20,7 @@ function Root(self, { engine }) {
20
20
  root.isActive = true;
21
21
  root.engine = engine !== null && engine !== void 0 ? engine : Matter.Engine.create();
22
22
  xnew.context('xmatter.object', root.engine.world);
23
- self.on('update', () => {
23
+ self.on('-update', () => {
24
24
  if (root.isActive) {
25
25
  Matter.Engine.update(root.engine);
26
26
  }
@@ -30,7 +30,7 @@ function Nest(self, { object }) {
30
30
  const parent = xnew.context('xmatter.object');
31
31
  xnew.context('xmatter.object', object);
32
32
  Matter.Composite.add(parent, object);
33
- self.on('finalize', () => {
33
+ self.on('-finalize', () => {
34
34
  Matter.Composite.remove(parent, object);
35
35
  });
36
36
  }
@@ -50,13 +50,6 @@
50
50
  var _a;
51
51
  return (_a = xnew.context('xpixi.root')) === null || _a === void 0 ? void 0 : _a.canvas;
52
52
  },
53
- capture() {
54
- var _a, _b;
55
- const render = (_a = xnew.context('xpixi.root')) === null || _a === void 0 ? void 0 : _a.renderer;
56
- const scene = (_b = xnew.context('xpixi.root')) === null || _b === void 0 ? void 0 : _b.scene;
57
- const canvas = render.extract.canvas(scene);
58
- return canvas.toDataURL('image/png', 1.0);
59
- }
60
53
  };
61
54
  function Root(self, { canvas }) {
62
55
  const root = {};
@@ -76,7 +69,7 @@
76
69
  root.updates = [];
77
70
  root.scene = new PIXI__namespace.Container();
78
71
  xnew.context('xpixi.object', root.scene);
79
- self.on('update', () => {
72
+ self.on('-update', () => {
80
73
  root.updates.forEach((update) => {
81
74
  update();
82
75
  });
@@ -89,14 +82,14 @@
89
82
  const parent = xnew.context('xpixi.object');
90
83
  xnew.context('xpixi.object', object);
91
84
  parent.addChild(object);
92
- self.on('finalize', () => {
85
+ self.on('-finalize', () => {
93
86
  parent.removeChild(object);
94
87
  });
95
88
  }
96
89
  function PreUpdate(self, callback) {
97
90
  const root = xnew.context('xpixi.root');
98
91
  root.updates.push(callback);
99
- self.on('finalize', () => {
92
+ self.on('-finalize', () => {
100
93
  root.updates = root.updates.filter((update) => update !== callback);
101
94
  });
102
95
  }
@@ -28,13 +28,6 @@ var xpixi = {
28
28
  var _a;
29
29
  return (_a = xnew.context('xpixi.root')) === null || _a === void 0 ? void 0 : _a.canvas;
30
30
  },
31
- capture() {
32
- var _a, _b;
33
- const render = (_a = xnew.context('xpixi.root')) === null || _a === void 0 ? void 0 : _a.renderer;
34
- const scene = (_b = xnew.context('xpixi.root')) === null || _b === void 0 ? void 0 : _b.scene;
35
- const canvas = render.extract.canvas(scene);
36
- return canvas.toDataURL('image/png', 1.0);
37
- }
38
31
  };
39
32
  function Root(self, { canvas }) {
40
33
  const root = {};
@@ -54,7 +47,7 @@ function Root(self, { canvas }) {
54
47
  root.updates = [];
55
48
  root.scene = new PIXI.Container();
56
49
  xnew.context('xpixi.object', root.scene);
57
- self.on('update', () => {
50
+ self.on('-update', () => {
58
51
  root.updates.forEach((update) => {
59
52
  update();
60
53
  });
@@ -67,14 +60,14 @@ function Nest(self, { object }) {
67
60
  const parent = xnew.context('xpixi.object');
68
61
  xnew.context('xpixi.object', object);
69
62
  parent.addChild(object);
70
- self.on('finalize', () => {
63
+ self.on('-finalize', () => {
71
64
  parent.removeChild(object);
72
65
  });
73
66
  }
74
67
  function PreUpdate(self, callback) {
75
68
  const root = xnew.context('xpixi.root');
76
69
  root.updates.push(callback);
77
- self.on('finalize', () => {
70
+ self.on('-finalize', () => {
78
71
  root.updates = root.updates.filter((update) => update !== callback);
79
72
  });
80
73
  }
@@ -27,7 +27,7 @@
27
27
  }
28
28
  // xnew.extend(Nest, root.world);
29
29
  });
30
- self.on('update', () => {
30
+ self.on('-update', () => {
31
31
  if (root.world) {
32
32
  root.world.step();
33
33
  }
@@ -40,7 +40,7 @@
40
40
  console.log(temp, type, object);
41
41
  // Rapier2D objects (RigidBody, Collider, etc.) are already added to the world
42
42
  // when created, so we only need to handle removal on finalize
43
- self.on('finalize', () => {
43
+ self.on('-finalize', () => {
44
44
  try {
45
45
  // Check if object is a RigidBody
46
46
  if (type === 'rigidBody') {
@@ -24,7 +24,7 @@ function Root(self, { gravity, timestep }) {
24
24
  }
25
25
  // xnew.extend(Nest, root.world);
26
26
  });
27
- self.on('update', () => {
27
+ self.on('-update', () => {
28
28
  if (root.world) {
29
29
  root.world.step();
30
30
  }
@@ -37,7 +37,7 @@ function Connect(self, { type, object }) {
37
37
  console.log(temp, type, object);
38
38
  // Rapier2D objects (RigidBody, Collider, etc.) are already added to the world
39
39
  // when created, so we only need to handle removal on finalize
40
- self.on('finalize', () => {
40
+ self.on('-finalize', () => {
41
41
  try {
42
42
  // Check if object is a RigidBody
43
43
  if (type === 'rigidBody') {
@@ -57,7 +57,7 @@
57
57
  root.camera = camera !== null && camera !== void 0 ? camera : new THREE__namespace.PerspectiveCamera(45, root.renderer.domElement.width / root.renderer.domElement.height);
58
58
  root.scene = new THREE__namespace.Scene();
59
59
  xnew.context('xthree.object', root.scene);
60
- self.on('update', () => {
60
+ self.on('-update', () => {
61
61
  root.renderer.render(root.scene, root.camera);
62
62
  });
63
63
  }
@@ -66,7 +66,7 @@
66
66
  xnew.context('xthree.object', object);
67
67
  if (parent) {
68
68
  parent === null || parent === void 0 ? void 0 : parent.add(object);
69
- self.on('finalize', () => {
69
+ self.on('-finalize', () => {
70
70
  parent === null || parent === void 0 ? void 0 : parent.remove(object);
71
71
  });
72
72
  }
@@ -35,7 +35,7 @@ function Root(self, { canvas, camera }) {
35
35
  root.camera = camera !== null && camera !== void 0 ? camera : new THREE.PerspectiveCamera(45, root.renderer.domElement.width / root.renderer.domElement.height);
36
36
  root.scene = new THREE.Scene();
37
37
  xnew.context('xthree.object', root.scene);
38
- self.on('update', () => {
38
+ self.on('-update', () => {
39
39
  root.renderer.render(root.scene, root.camera);
40
40
  });
41
41
  }
@@ -44,7 +44,7 @@ function Nest(self, { object }) {
44
44
  xnew.context('xthree.object', object);
45
45
  if (parent) {
46
46
  parent === null || parent === void 0 ? void 0 : parent.add(object);
47
- self.on('finalize', () => {
47
+ self.on('-finalize', () => {
48
48
  parent === null || parent === void 0 ? void 0 : parent.remove(object);
49
49
  });
50
50
  }
@@ -1,18 +1,8 @@
1
1
  export declare const audio: {
2
- load(path: string): AudioFile;
2
+ load(path: string): import("../core/unit").UnitPromise;
3
3
  synthesizer(props: SynthProps): Synthesizer;
4
4
  volume: number;
5
5
  };
6
- declare class AudioFile {
7
- buffer?: AudioBuffer;
8
- promise: Promise<void>;
9
- source?: AudioBufferSourceNode;
10
- amp?: GainNode;
11
- start: number | null;
12
- constructor(path: string);
13
- play(offset?: number, loop?: boolean): void;
14
- pause(): number | undefined;
15
- }
16
6
  type SynthProps = {
17
7
  oscillator: OscillatorOptions;
18
8
  amp: AmpOptions;
@@ -1,6 +1,6 @@
1
1
  export declare class Ticker {
2
2
  private id;
3
- constructor(callback: Function);
3
+ constructor(callback: Function, fps?: number);
4
4
  clear(): void;
5
5
  }
6
6
  export interface TimerOptions {
@@ -153,6 +153,5 @@ export declare const xnew: CreateUnit & {
153
153
  * }, 300)
154
154
  */
155
155
  transition(transition: Function, duration?: number, easing?: string): any;
156
- style(text: string): void;
157
156
  };
158
157
  export {};
package/dist/xnew.d.ts CHANGED
@@ -22,7 +22,7 @@ declare class MapMap<Key1, Key2, Value> extends Map<Key1, Map<Key2, Value>> {
22
22
 
23
23
  declare class Ticker {
24
24
  private id;
25
- constructor(callback: Function);
25
+ constructor(callback: Function, fps?: number);
26
26
  clear(): void;
27
27
  }
28
28
 
@@ -257,7 +257,6 @@ declare const xnew$1: CreateUnit & {
257
257
  * }, 300)
258
258
  */
259
259
  transition(transition: Function, duration?: number, easing?: string): any;
260
- style(text: string): void;
261
260
  };
262
261
 
263
262
  declare function AccordionFrame(frame: Unit, { open, duration, easing }?: {
@@ -360,20 +359,10 @@ declare function DirectionalPad(self: Unit, { size, diagonal, fill, fillOpacity,
360
359
  }): void;
361
360
 
362
361
  declare const audio: {
363
- load(path: string): AudioFile;
362
+ load(path: string): UnitPromise;
364
363
  synthesizer(props: SynthProps): Synthesizer;
365
364
  volume: number;
366
365
  };
367
- declare class AudioFile {
368
- buffer?: AudioBuffer;
369
- promise: Promise<void>;
370
- source?: AudioBufferSourceNode;
371
- amp?: GainNode;
372
- start: number | null;
373
- constructor(path: string);
374
- play(offset?: number, loop?: boolean): void;
375
- pause(): number | undefined;
376
- }
377
366
  type SynthProps = {
378
367
  oscillator: OscillatorOptions;
379
368
  amp: AmpOptions;
package/dist/xnew.js CHANGED
@@ -105,17 +105,16 @@
105
105
  // ticker
106
106
  //----------------------------------------------------------------------------------------------------
107
107
  class Ticker {
108
- constructor(callback) {
108
+ constructor(callback, fps = 60) {
109
109
  const self = this;
110
110
  this.id = null;
111
111
  let previous = 0;
112
112
  ticker();
113
113
  function ticker() {
114
- const time = Date.now();
115
- const fps = 60;
116
- if (time - previous > (1000 / fps) * 0.9) {
117
- callback(time);
118
- previous = time;
114
+ const delta = Date.now() - previous;
115
+ if (delta > (1000 / fps) * 0.9) {
116
+ callback();
117
+ previous += delta;
119
118
  }
120
119
  self.id = requestAnimationFrame(ticker);
121
120
  }
@@ -147,7 +146,7 @@
147
146
  p = Math.pow((1.0 - Math.pow((1.0 - p), 0.5)), 2.0);
148
147
  }
149
148
  else if (this.options.easing === 'ease') {
150
- p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
149
+ p = (1.0 - Math.cos(p * Math.PI)) / 2.0; // todo
151
150
  }
152
151
  else if (this.options.easing === 'ease-in-out') {
153
152
  p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
@@ -214,7 +213,7 @@
214
213
  //----------------------------------------------------------------------------------------------------
215
214
  // utils
216
215
  //----------------------------------------------------------------------------------------------------
217
- const SYSTEM_EVENTS = ['start', 'update', 'stop', 'finalize'];
216
+ const SYSTEM_EVENTS = ['-start', '-update', '-stop', '-finalize'];
218
217
  //----------------------------------------------------------------------------------------------------
219
218
  // unit
220
219
  //----------------------------------------------------------------------------------------------------
@@ -309,7 +308,7 @@
309
308
  components: [],
310
309
  listeners: new MapMap(),
311
310
  defines: {},
312
- systems: { start: [], update: [], stop: [], finalize: [] },
311
+ systems: { '-start': [], '-update': [], '-stop': [], '-finalize': [] },
313
312
  });
314
313
  // nest html element
315
314
  if (typeof unit._.target === 'string') {
@@ -325,7 +324,7 @@
325
324
  if (unit._.state !== 'finalized') {
326
325
  unit._.state = 'finalized';
327
326
  unit._.children.forEach((child) => child.finalize());
328
- unit._.systems.finalize.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
327
+ unit._.systems['-finalize'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
329
328
  unit.off();
330
329
  unit._.components.forEach((component) => Unit.component2units.delete(component, unit));
331
330
  if (unit._.elements.length > 0) {
@@ -395,7 +394,7 @@
395
394
  if (unit._.state === 'initialized' || unit._.state === 'stopped') {
396
395
  unit._.state = 'started';
397
396
  unit._.children.forEach((child) => Unit.start(child));
398
- unit._.systems.start.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
397
+ unit._.systems['-start'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
399
398
  }
400
399
  else if (unit._.state === 'started') {
401
400
  unit._.children.forEach((child) => Unit.start(child));
@@ -405,13 +404,13 @@
405
404
  if (unit._.state === 'started') {
406
405
  unit._.state = 'stopped';
407
406
  unit._.children.forEach((child) => Unit.stop(child));
408
- unit._.systems.stop.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
407
+ unit._.systems['-stop'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
409
408
  }
410
409
  }
411
410
  static update(unit) {
412
411
  if (unit._.state === 'started') {
413
412
  unit._.children.forEach((child) => Unit.update(child));
414
- unit._.systems.update.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
413
+ unit._.systems['-update'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
415
414
  }
416
415
  }
417
416
  static reset() {
@@ -419,7 +418,7 @@
419
418
  (_a = Unit.root) === null || _a === void 0 ? void 0 : _a.finalize();
420
419
  Unit.current = Unit.root = new Unit(null, null);
421
420
  (_b = Unit.ticker) === null || _b === void 0 ? void 0 : _b.clear();
422
- Unit.ticker = new Ticker((time) => {
421
+ Unit.ticker = new Ticker(() => {
423
422
  Unit.start(Unit.root);
424
423
  Unit.update(Unit.root);
425
424
  });
@@ -561,7 +560,7 @@
561
560
  }
562
561
  else if (timer.stack.length === 0) {
563
562
  timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
564
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
563
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
565
564
  }
566
565
  else {
567
566
  timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
@@ -570,7 +569,7 @@
570
569
  static next(timer) {
571
570
  if (timer.stack.length > 0) {
572
571
  timer.unit = new Unit(Unit.current, UnitTimer.Component, timer.stack.shift());
573
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
572
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
574
573
  }
575
574
  }
576
575
  static Component(unit, options) {
@@ -589,7 +588,7 @@
589
588
  }
590
589
  }, duration: options.duration, iterations: options.iterations, easing: options.easing
591
590
  });
592
- unit.on('finalize', () => timer.clear());
591
+ unit.on('-finalize', () => timer.clear());
593
592
  }
594
593
  }
595
594
 
@@ -809,15 +808,6 @@
809
808
  */
810
809
  transition(transition, duration = 0, easing = 'linear') {
811
810
  return new UnitTimer({ transition, duration, easing, iterations: 1 });
812
- },
813
- style(text) {
814
- const unit = new Unit(Unit.current);
815
- const style = document.createElement('style');
816
- style.textContent = text;
817
- document.head.appendChild(style);
818
- unit.on('finalize', () => {
819
- document.head.removeChild(style);
820
- });
821
811
  }
822
812
  });
823
813
 
@@ -909,7 +899,7 @@
909
899
  if (resize.element) {
910
900
  observer.observe(resize.element);
911
901
  }
912
- resize.on('finalize', () => {
902
+ resize.on('-finalize', () => {
913
903
  if (resize.element) {
914
904
  observer.unobserve(resize.element);
915
905
  }
@@ -939,7 +929,7 @@
939
929
  y: (state['ArrowUp'] ? -1 : 0) + (state['ArrowDown'] ? +1 : 0)
940
930
  };
941
931
  }
942
- keyboard.on('finalize', () => {
932
+ keyboard.on('-finalize', () => {
943
933
  window.removeEventListener('keydown', keydown);
944
934
  window.removeEventListener('keyup', keyup);
945
935
  });
@@ -950,8 +940,32 @@
950
940
  internal.on('pointermove', (event) => unit.emit('-pointermove', { event, position: getPosition(unit.element, event) }));
951
941
  internal.on('pointerup', (event) => unit.emit('-pointerup', { event, position: getPosition(unit.element, event) }));
952
942
  internal.on('wheel', (event) => unit.emit('-wheel', { event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } }));
953
- internal.on('mouseover', (event) => unit.emit('-mouseover', { event, position: getPosition(unit.element, event) }));
954
- internal.on('mouseout', (event) => unit.emit('-mouseout', { event, position: getPosition(unit.element, event) }));
943
+ internal.on('click', (event) => unit.emit('-click', { event, position: getPosition(unit.element, event) }));
944
+ internal.on('pointerover', (event) => unit.emit('-pointerover', { event, position: getPosition(unit.element, event) }));
945
+ internal.on('pointerout', (event) => unit.emit('-pointerout', { event, position: getPosition(unit.element, event) }));
946
+ document.addEventListener('pointerdown', pointerdownoutside);
947
+ document.addEventListener('pointerup', pointerupoutside);
948
+ document.addEventListener('click', clickoutside);
949
+ unit.on('-finalize', () => {
950
+ document.removeEventListener('pointerdown', pointerdownoutside);
951
+ document.removeEventListener('pointerup', pointerupoutside);
952
+ document.removeEventListener('click', clickoutside);
953
+ });
954
+ function pointerdownoutside(event) {
955
+ if (unit.element.contains(event.target) === false) {
956
+ unit.emit('-pointerdown:outside', { event, position: getPosition(unit.element, event) });
957
+ }
958
+ }
959
+ function pointerupoutside(event) {
960
+ if (unit.element.contains(event.target) === false) {
961
+ unit.emit('-pointerup:outside', { event, position: getPosition(unit.element, event) });
962
+ }
963
+ }
964
+ function clickoutside(event) {
965
+ if (unit.element.contains(event.target) === false) {
966
+ unit.emit('-click:outside', { event, position: getPosition(unit.element, event) });
967
+ }
968
+ }
955
969
  const drag = xnew$1(DragEvent);
956
970
  drag.on('-dragstart', (...args) => unit.emit('-dragstart', ...args));
957
971
  drag.on('-dragmove', (...args) => unit.emit('-dragmove', ...args));
@@ -1004,7 +1018,7 @@
1004
1018
  connect = false;
1005
1019
  }
1006
1020
  }
1007
- internal.on('finalize', remove);
1021
+ internal.on('-finalize', remove);
1008
1022
  });
1009
1023
  unit.emit('-dragstart', { event, position });
1010
1024
  }
@@ -1413,7 +1427,7 @@
1413
1427
  }
1414
1428
  const audio = {
1415
1429
  load(path) {
1416
- return new AudioFile(path);
1430
+ return AudioFile.load(path);
1417
1431
  },
1418
1432
  synthesizer(props) {
1419
1433
  return new Synthesizer(props);
@@ -1439,43 +1453,69 @@
1439
1453
  .catch(() => {
1440
1454
  console.warn(`"${path}" could not be loaded.`);
1441
1455
  });
1456
+ this.amp = context.createGain();
1457
+ this.amp.gain.value = 1.0;
1458
+ this.amp.connect(master);
1459
+ this.fade = context.createGain();
1460
+ this.fade.gain.value = 1.0;
1461
+ this.fade.connect(this.amp);
1462
+ this.source = null;
1442
1463
  this.start = null;
1443
1464
  }
1444
- // set volume(value: number) {
1445
- // this.amp.gain.value = value;
1446
- // }
1447
- // get volume(): number {
1448
- // return this.amp.gain.value;
1449
- // }
1450
- play(offset = 0, loop = false) {
1465
+ set volume(value) {
1466
+ this.amp.gain.value = value;
1467
+ }
1468
+ get volume() {
1469
+ return this.amp.gain.value;
1470
+ }
1471
+ play({ offset = 0, loop = false, fade = 0 } = {}) {
1451
1472
  if (this.buffer !== undefined && this.start === null) {
1452
1473
  this.source = context.createBufferSource();
1453
1474
  this.source.buffer = this.buffer;
1454
1475
  this.source.loop = loop;
1455
- this.amp = context.createGain();
1456
- this.amp.gain.value = 1.0;
1457
- this.source.connect(this.amp);
1458
- this.amp.connect(master);
1476
+ this.source.connect(this.fade);
1459
1477
  this.start = context.currentTime;
1460
1478
  this.source.playbackRate.value = 1;
1461
1479
  this.source.start(context.currentTime, offset / 1000);
1480
+ // Apply fade-in effect if fade duration is specified
1481
+ if (fade > 0) {
1482
+ this.fade.gain.setValueAtTime(0, context.currentTime);
1483
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1484
+ }
1462
1485
  this.source.onended = () => {
1463
- var _a, _b;
1486
+ var _a;
1464
1487
  this.start = null;
1465
1488
  (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1466
- (_b = this.amp) === null || _b === void 0 ? void 0 : _b.disconnect();
1489
+ this.source = null;
1467
1490
  };
1468
1491
  }
1469
1492
  }
1470
- pause() {
1471
- var _a;
1493
+ pause({ fade = 0 } = {}) {
1494
+ var _a, _b;
1472
1495
  if (this.buffer !== undefined && this.start !== null) {
1473
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime);
1474
1496
  const elapsed = (context.currentTime - this.start) % this.buffer.duration * 1000;
1497
+ // Apply fade-out effect if fade duration is specified
1498
+ if (fade > 0) {
1499
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
1500
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1501
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1502
+ }
1503
+ else {
1504
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1505
+ }
1475
1506
  this.start = null;
1476
1507
  return elapsed;
1477
1508
  }
1478
1509
  }
1510
+ static load(path) {
1511
+ const music = new AudioFile(path);
1512
+ return xnew$1.promise(music.promise).then(() => music);
1513
+ }
1514
+ static clear(file) {
1515
+ var _a;
1516
+ file.amp.disconnect();
1517
+ (_a = file.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1518
+ }
1479
1519
  }
1480
1520
  const keymap = {
1481
1521
  'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
@@ -1560,7 +1600,7 @@
1560
1600
  if (props.amp.envelope) {
1561
1601
  const ADSR = props.amp.envelope.ADSR;
1562
1602
  const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1563
- const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / adsr[0], 1.0);
1603
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1564
1604
  stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1565
1605
  }
1566
1606
  else {
@@ -1590,7 +1630,8 @@
1590
1630
  }, 2000);
1591
1631
  }
1592
1632
  function stopEnvelope(param, base, amount, ADSR) {
1593
- const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(dv / (ADSR[0] / 1000), 1.0);
1633
+ const end = dv > 0 ? dv : (context.currentTime - start);
1634
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1594
1635
  if (rate < 1.0) {
1595
1636
  param.cancelScheduledValues(start);
1596
1637
  param.setValueAtTime(base, start);
@@ -1598,7 +1639,7 @@
1598
1639
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1599
1640
  }
1600
1641
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1601
- param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv) + ADSR[3] / 1000);
1642
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1602
1643
  }
1603
1644
  function startEnvelope(param, base, amount, ADSR) {
1604
1645
  param.value = base;
package/dist/xnew.mjs CHANGED
@@ -99,17 +99,16 @@ class MapMap extends Map {
99
99
  // ticker
100
100
  //----------------------------------------------------------------------------------------------------
101
101
  class Ticker {
102
- constructor(callback) {
102
+ constructor(callback, fps = 60) {
103
103
  const self = this;
104
104
  this.id = null;
105
105
  let previous = 0;
106
106
  ticker();
107
107
  function ticker() {
108
- const time = Date.now();
109
- const fps = 60;
110
- if (time - previous > (1000 / fps) * 0.9) {
111
- callback(time);
112
- previous = time;
108
+ const delta = Date.now() - previous;
109
+ if (delta > (1000 / fps) * 0.9) {
110
+ callback();
111
+ previous += delta;
113
112
  }
114
113
  self.id = requestAnimationFrame(ticker);
115
114
  }
@@ -141,7 +140,7 @@ class Timer {
141
140
  p = Math.pow((1.0 - Math.pow((1.0 - p), 0.5)), 2.0);
142
141
  }
143
142
  else if (this.options.easing === 'ease') {
144
- p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
143
+ p = (1.0 - Math.cos(p * Math.PI)) / 2.0; // todo
145
144
  }
146
145
  else if (this.options.easing === 'ease-in-out') {
147
146
  p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
@@ -208,7 +207,7 @@ class Timer {
208
207
  //----------------------------------------------------------------------------------------------------
209
208
  // utils
210
209
  //----------------------------------------------------------------------------------------------------
211
- const SYSTEM_EVENTS = ['start', 'update', 'stop', 'finalize'];
210
+ const SYSTEM_EVENTS = ['-start', '-update', '-stop', '-finalize'];
212
211
  //----------------------------------------------------------------------------------------------------
213
212
  // unit
214
213
  //----------------------------------------------------------------------------------------------------
@@ -303,7 +302,7 @@ class Unit {
303
302
  components: [],
304
303
  listeners: new MapMap(),
305
304
  defines: {},
306
- systems: { start: [], update: [], stop: [], finalize: [] },
305
+ systems: { '-start': [], '-update': [], '-stop': [], '-finalize': [] },
307
306
  });
308
307
  // nest html element
309
308
  if (typeof unit._.target === 'string') {
@@ -319,7 +318,7 @@ class Unit {
319
318
  if (unit._.state !== 'finalized') {
320
319
  unit._.state = 'finalized';
321
320
  unit._.children.forEach((child) => child.finalize());
322
- unit._.systems.finalize.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
321
+ unit._.systems['-finalize'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
323
322
  unit.off();
324
323
  unit._.components.forEach((component) => Unit.component2units.delete(component, unit));
325
324
  if (unit._.elements.length > 0) {
@@ -389,7 +388,7 @@ class Unit {
389
388
  if (unit._.state === 'initialized' || unit._.state === 'stopped') {
390
389
  unit._.state = 'started';
391
390
  unit._.children.forEach((child) => Unit.start(child));
392
- unit._.systems.start.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
391
+ unit._.systems['-start'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
393
392
  }
394
393
  else if (unit._.state === 'started') {
395
394
  unit._.children.forEach((child) => Unit.start(child));
@@ -399,13 +398,13 @@ class Unit {
399
398
  if (unit._.state === 'started') {
400
399
  unit._.state = 'stopped';
401
400
  unit._.children.forEach((child) => Unit.stop(child));
402
- unit._.systems.stop.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
401
+ unit._.systems['-stop'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
403
402
  }
404
403
  }
405
404
  static update(unit) {
406
405
  if (unit._.state === 'started') {
407
406
  unit._.children.forEach((child) => Unit.update(child));
408
- unit._.systems.update.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
407
+ unit._.systems['-update'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
409
408
  }
410
409
  }
411
410
  static reset() {
@@ -413,7 +412,7 @@ class Unit {
413
412
  (_a = Unit.root) === null || _a === void 0 ? void 0 : _a.finalize();
414
413
  Unit.current = Unit.root = new Unit(null, null);
415
414
  (_b = Unit.ticker) === null || _b === void 0 ? void 0 : _b.clear();
416
- Unit.ticker = new Ticker((time) => {
415
+ Unit.ticker = new Ticker(() => {
417
416
  Unit.start(Unit.root);
418
417
  Unit.update(Unit.root);
419
418
  });
@@ -555,7 +554,7 @@ class UnitTimer {
555
554
  }
556
555
  else if (timer.stack.length === 0) {
557
556
  timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
558
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
557
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
559
558
  }
560
559
  else {
561
560
  timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
@@ -564,7 +563,7 @@ class UnitTimer {
564
563
  static next(timer) {
565
564
  if (timer.stack.length > 0) {
566
565
  timer.unit = new Unit(Unit.current, UnitTimer.Component, timer.stack.shift());
567
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
566
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
568
567
  }
569
568
  }
570
569
  static Component(unit, options) {
@@ -583,7 +582,7 @@ class UnitTimer {
583
582
  }
584
583
  }, duration: options.duration, iterations: options.iterations, easing: options.easing
585
584
  });
586
- unit.on('finalize', () => timer.clear());
585
+ unit.on('-finalize', () => timer.clear());
587
586
  }
588
587
  }
589
588
 
@@ -803,15 +802,6 @@ const xnew$1 = Object.assign(function (...args) {
803
802
  */
804
803
  transition(transition, duration = 0, easing = 'linear') {
805
804
  return new UnitTimer({ transition, duration, easing, iterations: 1 });
806
- },
807
- style(text) {
808
- const unit = new Unit(Unit.current);
809
- const style = document.createElement('style');
810
- style.textContent = text;
811
- document.head.appendChild(style);
812
- unit.on('finalize', () => {
813
- document.head.removeChild(style);
814
- });
815
805
  }
816
806
  });
817
807
 
@@ -903,7 +893,7 @@ function ResizeEvent(resize) {
903
893
  if (resize.element) {
904
894
  observer.observe(resize.element);
905
895
  }
906
- resize.on('finalize', () => {
896
+ resize.on('-finalize', () => {
907
897
  if (resize.element) {
908
898
  observer.unobserve(resize.element);
909
899
  }
@@ -933,7 +923,7 @@ function KeyboardEvent(keyboard) {
933
923
  y: (state['ArrowUp'] ? -1 : 0) + (state['ArrowDown'] ? +1 : 0)
934
924
  };
935
925
  }
936
- keyboard.on('finalize', () => {
926
+ keyboard.on('-finalize', () => {
937
927
  window.removeEventListener('keydown', keydown);
938
928
  window.removeEventListener('keyup', keyup);
939
929
  });
@@ -944,8 +934,32 @@ function PointerEvent(unit) {
944
934
  internal.on('pointermove', (event) => unit.emit('-pointermove', { event, position: getPosition(unit.element, event) }));
945
935
  internal.on('pointerup', (event) => unit.emit('-pointerup', { event, position: getPosition(unit.element, event) }));
946
936
  internal.on('wheel', (event) => unit.emit('-wheel', { event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } }));
947
- internal.on('mouseover', (event) => unit.emit('-mouseover', { event, position: getPosition(unit.element, event) }));
948
- internal.on('mouseout', (event) => unit.emit('-mouseout', { event, position: getPosition(unit.element, event) }));
937
+ internal.on('click', (event) => unit.emit('-click', { event, position: getPosition(unit.element, event) }));
938
+ internal.on('pointerover', (event) => unit.emit('-pointerover', { event, position: getPosition(unit.element, event) }));
939
+ internal.on('pointerout', (event) => unit.emit('-pointerout', { event, position: getPosition(unit.element, event) }));
940
+ document.addEventListener('pointerdown', pointerdownoutside);
941
+ document.addEventListener('pointerup', pointerupoutside);
942
+ document.addEventListener('click', clickoutside);
943
+ unit.on('-finalize', () => {
944
+ document.removeEventListener('pointerdown', pointerdownoutside);
945
+ document.removeEventListener('pointerup', pointerupoutside);
946
+ document.removeEventListener('click', clickoutside);
947
+ });
948
+ function pointerdownoutside(event) {
949
+ if (unit.element.contains(event.target) === false) {
950
+ unit.emit('-pointerdown:outside', { event, position: getPosition(unit.element, event) });
951
+ }
952
+ }
953
+ function pointerupoutside(event) {
954
+ if (unit.element.contains(event.target) === false) {
955
+ unit.emit('-pointerup:outside', { event, position: getPosition(unit.element, event) });
956
+ }
957
+ }
958
+ function clickoutside(event) {
959
+ if (unit.element.contains(event.target) === false) {
960
+ unit.emit('-click:outside', { event, position: getPosition(unit.element, event) });
961
+ }
962
+ }
949
963
  const drag = xnew$1(DragEvent);
950
964
  drag.on('-dragstart', (...args) => unit.emit('-dragstart', ...args));
951
965
  drag.on('-dragmove', (...args) => unit.emit('-dragmove', ...args));
@@ -998,7 +1012,7 @@ function DragEvent(unit) {
998
1012
  connect = false;
999
1013
  }
1000
1014
  }
1001
- internal.on('finalize', remove);
1015
+ internal.on('-finalize', remove);
1002
1016
  });
1003
1017
  unit.emit('-dragstart', { event, position });
1004
1018
  }
@@ -1407,7 +1421,7 @@ function initialize() {
1407
1421
  }
1408
1422
  const audio = {
1409
1423
  load(path) {
1410
- return new AudioFile(path);
1424
+ return AudioFile.load(path);
1411
1425
  },
1412
1426
  synthesizer(props) {
1413
1427
  return new Synthesizer(props);
@@ -1433,43 +1447,69 @@ class AudioFile {
1433
1447
  .catch(() => {
1434
1448
  console.warn(`"${path}" could not be loaded.`);
1435
1449
  });
1450
+ this.amp = context.createGain();
1451
+ this.amp.gain.value = 1.0;
1452
+ this.amp.connect(master);
1453
+ this.fade = context.createGain();
1454
+ this.fade.gain.value = 1.0;
1455
+ this.fade.connect(this.amp);
1456
+ this.source = null;
1436
1457
  this.start = null;
1437
1458
  }
1438
- // set volume(value: number) {
1439
- // this.amp.gain.value = value;
1440
- // }
1441
- // get volume(): number {
1442
- // return this.amp.gain.value;
1443
- // }
1444
- play(offset = 0, loop = false) {
1459
+ set volume(value) {
1460
+ this.amp.gain.value = value;
1461
+ }
1462
+ get volume() {
1463
+ return this.amp.gain.value;
1464
+ }
1465
+ play({ offset = 0, loop = false, fade = 0 } = {}) {
1445
1466
  if (this.buffer !== undefined && this.start === null) {
1446
1467
  this.source = context.createBufferSource();
1447
1468
  this.source.buffer = this.buffer;
1448
1469
  this.source.loop = loop;
1449
- this.amp = context.createGain();
1450
- this.amp.gain.value = 1.0;
1451
- this.source.connect(this.amp);
1452
- this.amp.connect(master);
1470
+ this.source.connect(this.fade);
1453
1471
  this.start = context.currentTime;
1454
1472
  this.source.playbackRate.value = 1;
1455
1473
  this.source.start(context.currentTime, offset / 1000);
1474
+ // Apply fade-in effect if fade duration is specified
1475
+ if (fade > 0) {
1476
+ this.fade.gain.setValueAtTime(0, context.currentTime);
1477
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1478
+ }
1456
1479
  this.source.onended = () => {
1457
- var _a, _b;
1480
+ var _a;
1458
1481
  this.start = null;
1459
1482
  (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1460
- (_b = this.amp) === null || _b === void 0 ? void 0 : _b.disconnect();
1483
+ this.source = null;
1461
1484
  };
1462
1485
  }
1463
1486
  }
1464
- pause() {
1465
- var _a;
1487
+ pause({ fade = 0 } = {}) {
1488
+ var _a, _b;
1466
1489
  if (this.buffer !== undefined && this.start !== null) {
1467
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime);
1468
1490
  const elapsed = (context.currentTime - this.start) % this.buffer.duration * 1000;
1491
+ // Apply fade-out effect if fade duration is specified
1492
+ if (fade > 0) {
1493
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
1494
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1495
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1496
+ }
1497
+ else {
1498
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1499
+ }
1469
1500
  this.start = null;
1470
1501
  return elapsed;
1471
1502
  }
1472
1503
  }
1504
+ static load(path) {
1505
+ const music = new AudioFile(path);
1506
+ return xnew$1.promise(music.promise).then(() => music);
1507
+ }
1508
+ static clear(file) {
1509
+ var _a;
1510
+ file.amp.disconnect();
1511
+ (_a = file.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1512
+ }
1473
1513
  }
1474
1514
  const keymap = {
1475
1515
  'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
@@ -1554,7 +1594,7 @@ class Synthesizer {
1554
1594
  if (props.amp.envelope) {
1555
1595
  const ADSR = props.amp.envelope.ADSR;
1556
1596
  const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1557
- const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / adsr[0], 1.0);
1597
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1558
1598
  stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1559
1599
  }
1560
1600
  else {
@@ -1584,7 +1624,8 @@ class Synthesizer {
1584
1624
  }, 2000);
1585
1625
  }
1586
1626
  function stopEnvelope(param, base, amount, ADSR) {
1587
- const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(dv / (ADSR[0] / 1000), 1.0);
1627
+ const end = dv > 0 ? dv : (context.currentTime - start);
1628
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1588
1629
  if (rate < 1.0) {
1589
1630
  param.cancelScheduledValues(start);
1590
1631
  param.setValueAtTime(base, start);
@@ -1592,7 +1633,7 @@ class Synthesizer {
1592
1633
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1593
1634
  }
1594
1635
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1595
- param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv) + ADSR[3] / 1000);
1636
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1596
1637
  }
1597
1638
  function startEnvelope(param, base, amount, ADSR) {
1598
1639
  param.value = base;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "Component-Oriented Programming"
6
6
  ],
7
- "version": "0.1.12",
7
+ "version": "0.2.0",
8
8
  "main": "dist/xnew.js",
9
9
  "module": "dist/xnew.mjs",
10
10
  "types": "dist/xnew.d.ts",