@mulsense/xnew 0.4.5 → 0.4.6

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.d.ts CHANGED
@@ -56,6 +56,9 @@ interface Internal {
56
56
  parent: Unit | null;
57
57
  target: Object | null;
58
58
  props?: Object;
59
+ config: {
60
+ protect: boolean;
61
+ };
59
62
  baseElement: UnitElement;
60
63
  baseContext: Context;
61
64
  baseComponent: Function;
@@ -65,7 +68,6 @@ interface Internal {
65
68
  anchor: UnitElement | null;
66
69
  state: string;
67
70
  tostart: boolean;
68
- protected: boolean;
69
71
  ancestors: Unit[];
70
72
  children: Unit[];
71
73
  promises: UnitPromise[];
@@ -274,7 +276,6 @@ declare const xnew$1: CreateUnit & {
274
276
  * }, 300)
275
277
  */
276
278
  transition(transition: Function, duration?: number, easing?: string): any;
277
- protect(): void;
278
279
  };
279
280
 
280
281
  declare function Accordion(unit: Unit, { open, duration, easing }?: {
@@ -297,6 +298,14 @@ declare function Screen(unit: Unit, { width, height, fit }?: {
297
298
  resize(width: number, height: number): void;
298
299
  };
299
300
 
301
+ declare function Modal(unit: Unit, { duration, easing }?: {
302
+ duration?: number;
303
+ easing?: string;
304
+ }): {
305
+ state: number;
306
+ close(): void;
307
+ };
308
+
300
309
  declare function AnalogStick(unit: Unit, { stroke, strokeOpacity, strokeWidth, strokeLinejoin, fill, fillOpacity }?: {
301
310
  stroke?: string;
302
311
  strokeOpacity?: number;
@@ -322,19 +331,66 @@ declare function TextStream(unit: Unit, { text, speed, fade }?: {
322
331
  fade?: number;
323
332
  }): void;
324
333
 
334
+ type SynthesizerOptions = {
335
+ oscillator: OscillatorOptions;
336
+ amp: AmpOptions;
337
+ filter?: FilterOptions;
338
+ reverb?: ReverbOptions;
339
+ bpm?: number;
340
+ };
341
+ type OscillatorOptions = {
342
+ type: OscillatorType;
343
+ envelope?: Envelope;
344
+ LFO?: LFO;
345
+ };
346
+ type FilterOptions = {
347
+ type: BiquadFilterType;
348
+ cutoff: number;
349
+ };
350
+ type AmpOptions = {
351
+ envelope: Envelope;
352
+ };
353
+ type ReverbOptions = {
354
+ time: number;
355
+ mix: number;
356
+ };
357
+ type Envelope = {
358
+ amount: number;
359
+ ADSR: [number, number, number, number];
360
+ };
361
+ type LFO = {
362
+ amount: number;
363
+ type: OscillatorType;
364
+ rate: number;
365
+ };
366
+ declare class Synthesizer {
367
+ props: SynthesizerOptions;
368
+ constructor(props: SynthesizerOptions);
369
+ press(frequency: number | string, duration?: number | string, wait?: number): {
370
+ release: () => void;
371
+ } | undefined;
372
+ }
373
+
325
374
  declare const basics: {
326
375
  Screen: typeof Screen;
327
- Modal: any;
376
+ Modal: typeof Modal;
328
377
  Accordion: typeof Accordion;
329
378
  TextStream: typeof TextStream;
330
379
  AnalogStick: typeof AnalogStick;
331
380
  DirectionalPad: typeof DirectionalPad;
332
381
  };
382
+
383
+ declare const audio: {
384
+ load(path: string): UnitPromise;
385
+ synthesizer(props: SynthesizerOptions): Synthesizer;
386
+ volume: number;
387
+ };
333
388
  declare namespace xnew {
334
389
  type Unit = InstanceType<typeof Unit>;
335
390
  }
336
391
  declare const xnew: (typeof xnew$1) & {
337
392
  basics: typeof basics;
393
+ audio: typeof audio;
338
394
  };
339
395
 
340
396
  export { xnew as default };
package/dist/xnew.js CHANGED
@@ -529,7 +529,7 @@
529
529
  //----------------------------------------------------------------------------------------------------
530
530
  class Unit {
531
531
  constructor(parent, ...args) {
532
- var _a;
532
+ var _a, _b;
533
533
  let target;
534
534
  if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement) {
535
535
  target = args.shift(); // an existing html element
@@ -548,6 +548,7 @@
548
548
  }
549
549
  const component = args.shift();
550
550
  const props = args.shift();
551
+ const config = args.shift();
551
552
  let baseElement;
552
553
  if (target instanceof HTMLElement || target instanceof SVGElement) {
553
554
  baseElement = target;
@@ -569,7 +570,8 @@
569
570
  baseComponent = (unit) => { };
570
571
  }
571
572
  const baseContext = (_a = parent === null || parent === void 0 ? void 0 : parent._.currentContext) !== null && _a !== void 0 ? _a : { stack: null };
572
- this._ = { parent, target, baseElement, baseContext, baseComponent, props };
573
+ const protect = (_b = config === null || config === void 0 ? void 0 : config.protect) !== null && _b !== void 0 ? _b : false;
574
+ this._ = { parent, target, baseElement, baseContext, baseComponent, props, config: { protect } };
573
575
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
574
576
  Unit.initialize(this, null);
575
577
  }
@@ -608,7 +610,6 @@
608
610
  anchor,
609
611
  state: 'invoked',
610
612
  tostart: true,
611
- protected: false,
612
613
  ancestors: [...(unit._.parent ? [unit._.parent] : []), ...((_b = (_a = unit._.parent) === null || _a === void 0 ? void 0 : _a._.ancestors) !== null && _b !== void 0 ? _b : [])],
613
614
  children: [],
614
615
  elements: [],
@@ -838,7 +839,7 @@
838
839
  if (type[0] === '+') {
839
840
  (_a = Unit.type2units.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((unit) => {
840
841
  var _a;
841
- const find = [unit, ...unit._.ancestors].find(u => u._.protected === true);
842
+ const find = [unit, ...unit._.ancestors].find(u => u._.config.protect === true);
842
843
  if (find === undefined || current._.ancestors.includes(find) === true || current === find) {
843
844
  (_a = unit._.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.execute(...args));
844
845
  }
@@ -1157,19 +1158,12 @@
1157
1158
  transition(transition, duration = 0, easing = 'linear') {
1158
1159
  return new UnitTimer({ transition, duration, easing, iterations: 1 });
1159
1160
  },
1160
- protect() {
1161
- Unit.currentUnit._.protected = true;
1162
- }
1163
1161
  });
1164
1162
 
1165
1163
  function Accordion(unit, { open = false, duration = 200, easing = 'ease' } = {}) {
1166
1164
  xnew$1.context('xnew.accordion', unit);
1167
- unit.on('-transition', ({ state }) => {
1168
- unit.state = state;
1169
- });
1170
- xnew$1.timeout(() => {
1171
- xnew$1.emit('-transition', { state: open ? 1.0 : 0.0 });
1172
- });
1165
+ unit.on('-transition', ({ state }) => unit.state = state);
1166
+ xnew$1.timeout(() => xnew$1.emit('-transition', { state: open ? 1.0 : 0.0 }));
1173
1167
  return {
1174
1168
  state: open ? 1.0 : 0.0,
1175
1169
  toggle() {
@@ -1244,9 +1238,8 @@
1244
1238
  xnew$1.context('xnew.modalframe', unit);
1245
1239
  xnew$1.nest('<div style="position: fixed; inset: 0; z-index: 1000;">');
1246
1240
  unit.on('click', ({ event }) => unit.close());
1247
- xnew$1.transition((x) => {
1248
- xnew$1.emit('-transition', { state: x });
1249
- }, duration, easing);
1241
+ unit.on('-transition', ({ state }) => unit.state = state);
1242
+ xnew$1.transition((x) => xnew$1.emit('-transition', { state: x }), duration, easing);
1250
1243
  return {
1251
1244
  state: 0.0,
1252
1245
  close() {
@@ -1293,7 +1286,7 @@
1293
1286
  const d = Math.min(1.0, Math.sqrt(x * x + y * y) / (newsize / 4));
1294
1287
  const a = (y !== 0 || x !== 0) ? Math.atan2(y, x) : 0;
1295
1288
  const vector = { x: Math.cos(a) * d, y: Math.sin(a) * d };
1296
- target.element.style.filter = 'brightness(90%)';
1289
+ target.element.style.filter = 'brightness(80%)';
1297
1290
  target.element.style.left = `${vector.x * newsize / 4}px`;
1298
1291
  target.element.style.top = `${vector.y * newsize / 4}px`;
1299
1292
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
@@ -1357,10 +1350,10 @@
1357
1350
  vector.x = 0;
1358
1351
  vector.y = Math.abs(vector.y) > 0.5 ? Math.sign(vector.y) : 0;
1359
1352
  }
1360
- targets[0].element.style.filter = (vector.y < 0) ? 'brightness(90%)' : '';
1361
- targets[1].element.style.filter = (vector.y > 0) ? 'brightness(90%)' : '';
1362
- targets[2].element.style.filter = (vector.x < 0) ? 'brightness(90%)' : '';
1363
- targets[3].element.style.filter = (vector.x > 0) ? 'brightness(90%)' : '';
1353
+ targets[0].element.style.filter = (vector.y < 0) ? 'brightness(80%)' : '';
1354
+ targets[1].element.style.filter = (vector.y > 0) ? 'brightness(80%)' : '';
1355
+ targets[2].element.style.filter = (vector.x < 0) ? 'brightness(80%)' : '';
1356
+ targets[3].element.style.filter = (vector.x > 0) ? 'brightness(80%)' : '';
1364
1357
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
1365
1358
  xnew$1.emit(nexttype, { type: nexttype, vector });
1366
1359
  });
@@ -1419,6 +1412,230 @@
1419
1412
  }
1420
1413
  }
1421
1414
 
1415
+ const context = new window.AudioContext();
1416
+ const master = context.createGain();
1417
+ //----------------------------------------------------------------------------------------------------
1418
+ // master volume
1419
+ //----------------------------------------------------------------------------------------------------
1420
+ master.gain.value = 0.1;
1421
+ master.connect(context.destination);
1422
+ //----------------------------------------------------------------------------------------------------
1423
+ // audio file
1424
+ //----------------------------------------------------------------------------------------------------
1425
+ class AudioFile {
1426
+ constructor(path) {
1427
+ this.promise = fetch(path)
1428
+ .then((response) => response.arrayBuffer())
1429
+ .then((response) => context.decodeAudioData(response))
1430
+ .then((response) => { this.buffer = response; })
1431
+ .catch(() => {
1432
+ console.warn(`"${path}" could not be loaded.`);
1433
+ });
1434
+ this.amp = context.createGain();
1435
+ this.amp.gain.value = 1.0;
1436
+ this.amp.connect(master);
1437
+ this.fade = context.createGain();
1438
+ this.fade.gain.value = 1.0;
1439
+ this.fade.connect(this.amp);
1440
+ this.source = null;
1441
+ this.start = null;
1442
+ }
1443
+ set volume(value) {
1444
+ this.amp.gain.value = value;
1445
+ }
1446
+ get volume() {
1447
+ return this.amp.gain.value;
1448
+ }
1449
+ play({ offset = 0, fade = 0, loop = false } = {}) {
1450
+ if (this.buffer !== undefined && this.start === null) {
1451
+ this.source = context.createBufferSource();
1452
+ this.source.buffer = this.buffer;
1453
+ this.source.loop = loop;
1454
+ this.source.connect(this.fade);
1455
+ this.start = context.currentTime;
1456
+ this.source.playbackRate.value = 1;
1457
+ this.source.start(context.currentTime, offset / 1000);
1458
+ // Apply fade-in effect if fade duration is specified
1459
+ if (fade > 0) {
1460
+ this.fade.gain.setValueAtTime(0, context.currentTime);
1461
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1462
+ }
1463
+ this.source.onended = () => {
1464
+ var _a;
1465
+ this.start = null;
1466
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1467
+ this.source = null;
1468
+ };
1469
+ }
1470
+ }
1471
+ pause({ fade = 0 } = {}) {
1472
+ var _a, _b;
1473
+ if (this.buffer !== undefined && this.start !== null) {
1474
+ const elapsed = (context.currentTime - this.start) % this.buffer.duration * 1000;
1475
+ // Apply fade-out effect if fade duration is specified
1476
+ if (fade > 0) {
1477
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
1478
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1479
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1480
+ }
1481
+ else {
1482
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1483
+ }
1484
+ this.start = null;
1485
+ return elapsed;
1486
+ }
1487
+ }
1488
+ clear() {
1489
+ var _a;
1490
+ this.amp.disconnect();
1491
+ this.fade.disconnect();
1492
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1493
+ }
1494
+ }
1495
+ const keymap = {
1496
+ 'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
1497
+ '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,
1498
+ '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,
1499
+ '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,
1500
+ '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,
1501
+ '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,
1502
+ '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,
1503
+ '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,
1504
+ 'C8': 4186.009,
1505
+ };
1506
+ const notemap = {
1507
+ '1m': 4.000, '2n': 2.000, '4n': 1.000, '8n': 0.500, '16n': 0.250, '32n': 0.125,
1508
+ };
1509
+ class Synthesizer {
1510
+ constructor(props) { this.props = props; }
1511
+ press(frequency, duration, wait) {
1512
+ var _a;
1513
+ const props = this.props;
1514
+ const fv = typeof frequency === 'string' ? keymap[frequency] : frequency;
1515
+ const dv = typeof duration === 'string' ? (notemap[duration] * 60 / ((_a = props.bpm) !== null && _a !== void 0 ? _a : 120)) : (typeof duration === 'number' ? (duration / 1000) : 0);
1516
+ const start = context.currentTime + (wait !== null && wait !== void 0 ? wait : 0) / 1000;
1517
+ const nodes = {};
1518
+ nodes.oscillator = context.createOscillator();
1519
+ nodes.oscillator.type = props.oscillator.type;
1520
+ nodes.oscillator.frequency.value = fv;
1521
+ if (props.oscillator.LFO) {
1522
+ nodes.oscillatorLFO = context.createOscillator();
1523
+ nodes.oscillatorLFODepth = context.createGain();
1524
+ nodes.oscillatorLFODepth.gain.value = fv * (Math.pow(2.0, props.oscillator.LFO.amount / 12.0) - 1.0);
1525
+ nodes.oscillatorLFO.type = props.oscillator.LFO.type;
1526
+ nodes.oscillatorLFO.frequency.value = props.oscillator.LFO.rate;
1527
+ nodes.oscillatorLFO.start(start);
1528
+ nodes.oscillatorLFO.connect(nodes.oscillatorLFODepth);
1529
+ nodes.oscillatorLFODepth.connect(nodes.oscillator.frequency);
1530
+ }
1531
+ nodes.amp = context.createGain();
1532
+ nodes.amp.gain.value = 0.0;
1533
+ nodes.target = context.createGain();
1534
+ nodes.target.gain.value = 1.0;
1535
+ nodes.amp.connect(nodes.target);
1536
+ nodes.target.connect(master);
1537
+ if (props.filter) {
1538
+ nodes.filter = context.createBiquadFilter();
1539
+ nodes.filter.type = props.filter.type;
1540
+ nodes.filter.frequency.value = props.filter.cutoff;
1541
+ nodes.oscillator.connect(nodes.filter);
1542
+ nodes.filter.connect(nodes.amp);
1543
+ }
1544
+ else {
1545
+ nodes.oscillator.connect(nodes.amp);
1546
+ }
1547
+ if (props.reverb) {
1548
+ nodes.convolver = context.createConvolver();
1549
+ nodes.convolver.buffer = impulseResponse({ time: props.reverb.time });
1550
+ nodes.convolverDepth = context.createGain();
1551
+ nodes.convolverDepth.gain.value = 1.0;
1552
+ nodes.convolverDepth.gain.value *= props.reverb.mix;
1553
+ nodes.target.gain.value *= (1.0 - props.reverb.mix);
1554
+ nodes.amp.connect(nodes.convolver);
1555
+ nodes.convolver.connect(nodes.convolverDepth);
1556
+ nodes.convolverDepth.connect(master);
1557
+ }
1558
+ if (props.oscillator.envelope) {
1559
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1560
+ startEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1561
+ }
1562
+ if (props.amp.envelope) {
1563
+ startEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1564
+ }
1565
+ nodes.oscillator.start(start);
1566
+ if (dv > 0) {
1567
+ release();
1568
+ }
1569
+ else {
1570
+ return { release };
1571
+ }
1572
+ function release() {
1573
+ let stop = null;
1574
+ const end = dv > 0 ? dv : (context.currentTime - start);
1575
+ if (props.amp.envelope) {
1576
+ const ADSR = props.amp.envelope.ADSR;
1577
+ const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1578
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1579
+ stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1580
+ }
1581
+ else {
1582
+ stop = start + end;
1583
+ }
1584
+ if (nodes.oscillatorLFO) {
1585
+ nodes.oscillatorLFO.stop(stop);
1586
+ }
1587
+ if (props.oscillator.envelope) {
1588
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1589
+ stopEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1590
+ }
1591
+ if (props.amp.envelope) {
1592
+ stopEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1593
+ }
1594
+ nodes.oscillator.stop(stop);
1595
+ setTimeout(() => {
1596
+ var _a, _b, _c, _d, _e;
1597
+ nodes.oscillator.disconnect();
1598
+ nodes.amp.disconnect();
1599
+ nodes.target.disconnect();
1600
+ (_a = nodes.oscillatorLFO) === null || _a === void 0 ? void 0 : _a.disconnect();
1601
+ (_b = nodes.oscillatorLFODepth) === null || _b === void 0 ? void 0 : _b.disconnect();
1602
+ (_c = nodes.filter) === null || _c === void 0 ? void 0 : _c.disconnect();
1603
+ (_d = nodes.convolver) === null || _d === void 0 ? void 0 : _d.disconnect();
1604
+ (_e = nodes.convolverDepth) === null || _e === void 0 ? void 0 : _e.disconnect();
1605
+ }, 2000);
1606
+ }
1607
+ function stopEnvelope(param, base, amount, ADSR) {
1608
+ const end = dv > 0 ? dv : (context.currentTime - start);
1609
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1610
+ if (rate < 1.0) {
1611
+ param.cancelScheduledValues(start);
1612
+ param.setValueAtTime(base, start);
1613
+ param.linearRampToValueAtTime(base + amount * rate, start + ADSR[0] / 1000 * rate);
1614
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1615
+ }
1616
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1617
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1618
+ }
1619
+ function startEnvelope(param, base, amount, ADSR) {
1620
+ param.value = base;
1621
+ param.setValueAtTime(base, start);
1622
+ param.linearRampToValueAtTime(base + amount, start + ADSR[0] / 1000);
1623
+ param.linearRampToValueAtTime(base + amount * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000);
1624
+ }
1625
+ function impulseResponse({ time, decay = 2.0 }) {
1626
+ const length = context.sampleRate * time / 1000;
1627
+ const impulse = context.createBuffer(2, length, context.sampleRate);
1628
+ const ch0 = impulse.getChannelData(0);
1629
+ const ch1 = impulse.getChannelData(1);
1630
+ for (let i = 0; i < length; i++) {
1631
+ ch0[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1632
+ ch1[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1633
+ }
1634
+ return impulse;
1635
+ }
1636
+ }
1637
+ }
1638
+
1422
1639
  const basics = {
1423
1640
  Screen,
1424
1641
  Modal,
@@ -1427,7 +1644,34 @@
1427
1644
  AnalogStick,
1428
1645
  DirectionalPad,
1429
1646
  };
1430
- const xnew = Object.assign(xnew$1, { basics });
1647
+ const audio = {
1648
+ load(path) {
1649
+ const music = new AudioFile(path);
1650
+ const object = {
1651
+ play(options = {}) {
1652
+ const unit = xnew();
1653
+ if (music.start === null) {
1654
+ music.play(options);
1655
+ unit.on('finalize', () => music.pause({ fade: options.fade }));
1656
+ }
1657
+ },
1658
+ pause(options = {}) {
1659
+ music.pause(options);
1660
+ }
1661
+ };
1662
+ return xnew.promise(music.promise).then(() => object);
1663
+ },
1664
+ synthesizer(props) {
1665
+ return new Synthesizer(props);
1666
+ },
1667
+ get volume() {
1668
+ return master.gain.value;
1669
+ },
1670
+ set volume(value) {
1671
+ master.gain.value = value;
1672
+ }
1673
+ };
1674
+ const xnew = Object.assign(xnew$1, { basics, audio });
1431
1675
 
1432
1676
  return xnew;
1433
1677
 
package/dist/xnew.mjs CHANGED
@@ -523,7 +523,7 @@ function pointer(element, event) {
523
523
  //----------------------------------------------------------------------------------------------------
524
524
  class Unit {
525
525
  constructor(parent, ...args) {
526
- var _a;
526
+ var _a, _b;
527
527
  let target;
528
528
  if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement) {
529
529
  target = args.shift(); // an existing html element
@@ -542,6 +542,7 @@ class Unit {
542
542
  }
543
543
  const component = args.shift();
544
544
  const props = args.shift();
545
+ const config = args.shift();
545
546
  let baseElement;
546
547
  if (target instanceof HTMLElement || target instanceof SVGElement) {
547
548
  baseElement = target;
@@ -563,7 +564,8 @@ class Unit {
563
564
  baseComponent = (unit) => { };
564
565
  }
565
566
  const baseContext = (_a = parent === null || parent === void 0 ? void 0 : parent._.currentContext) !== null && _a !== void 0 ? _a : { stack: null };
566
- this._ = { parent, target, baseElement, baseContext, baseComponent, props };
567
+ const protect = (_b = config === null || config === void 0 ? void 0 : config.protect) !== null && _b !== void 0 ? _b : false;
568
+ this._ = { parent, target, baseElement, baseContext, baseComponent, props, config: { protect } };
567
569
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
568
570
  Unit.initialize(this, null);
569
571
  }
@@ -602,7 +604,6 @@ class Unit {
602
604
  anchor,
603
605
  state: 'invoked',
604
606
  tostart: true,
605
- protected: false,
606
607
  ancestors: [...(unit._.parent ? [unit._.parent] : []), ...((_b = (_a = unit._.parent) === null || _a === void 0 ? void 0 : _a._.ancestors) !== null && _b !== void 0 ? _b : [])],
607
608
  children: [],
608
609
  elements: [],
@@ -832,7 +833,7 @@ class Unit {
832
833
  if (type[0] === '+') {
833
834
  (_a = Unit.type2units.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((unit) => {
834
835
  var _a;
835
- const find = [unit, ...unit._.ancestors].find(u => u._.protected === true);
836
+ const find = [unit, ...unit._.ancestors].find(u => u._.config.protect === true);
836
837
  if (find === undefined || current._.ancestors.includes(find) === true || current === find) {
837
838
  (_a = unit._.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.execute(...args));
838
839
  }
@@ -1151,19 +1152,12 @@ const xnew$1 = Object.assign(function (...args) {
1151
1152
  transition(transition, duration = 0, easing = 'linear') {
1152
1153
  return new UnitTimer({ transition, duration, easing, iterations: 1 });
1153
1154
  },
1154
- protect() {
1155
- Unit.currentUnit._.protected = true;
1156
- }
1157
1155
  });
1158
1156
 
1159
1157
  function Accordion(unit, { open = false, duration = 200, easing = 'ease' } = {}) {
1160
1158
  xnew$1.context('xnew.accordion', unit);
1161
- unit.on('-transition', ({ state }) => {
1162
- unit.state = state;
1163
- });
1164
- xnew$1.timeout(() => {
1165
- xnew$1.emit('-transition', { state: open ? 1.0 : 0.0 });
1166
- });
1159
+ unit.on('-transition', ({ state }) => unit.state = state);
1160
+ xnew$1.timeout(() => xnew$1.emit('-transition', { state: open ? 1.0 : 0.0 }));
1167
1161
  return {
1168
1162
  state: open ? 1.0 : 0.0,
1169
1163
  toggle() {
@@ -1238,9 +1232,8 @@ function Modal(unit, { duration = 200, easing = 'ease' } = {}) {
1238
1232
  xnew$1.context('xnew.modalframe', unit);
1239
1233
  xnew$1.nest('<div style="position: fixed; inset: 0; z-index: 1000;">');
1240
1234
  unit.on('click', ({ event }) => unit.close());
1241
- xnew$1.transition((x) => {
1242
- xnew$1.emit('-transition', { state: x });
1243
- }, duration, easing);
1235
+ unit.on('-transition', ({ state }) => unit.state = state);
1236
+ xnew$1.transition((x) => xnew$1.emit('-transition', { state: x }), duration, easing);
1244
1237
  return {
1245
1238
  state: 0.0,
1246
1239
  close() {
@@ -1287,7 +1280,7 @@ function AnalogStick(unit, { stroke = 'currentColor', strokeOpacity = 0.8, strok
1287
1280
  const d = Math.min(1.0, Math.sqrt(x * x + y * y) / (newsize / 4));
1288
1281
  const a = (y !== 0 || x !== 0) ? Math.atan2(y, x) : 0;
1289
1282
  const vector = { x: Math.cos(a) * d, y: Math.sin(a) * d };
1290
- target.element.style.filter = 'brightness(90%)';
1283
+ target.element.style.filter = 'brightness(80%)';
1291
1284
  target.element.style.left = `${vector.x * newsize / 4}px`;
1292
1285
  target.element.style.top = `${vector.y * newsize / 4}px`;
1293
1286
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
@@ -1351,10 +1344,10 @@ function DirectionalPad(unit, { diagonal = true, stroke = 'currentColor', stroke
1351
1344
  vector.x = 0;
1352
1345
  vector.y = Math.abs(vector.y) > 0.5 ? Math.sign(vector.y) : 0;
1353
1346
  }
1354
- targets[0].element.style.filter = (vector.y < 0) ? 'brightness(90%)' : '';
1355
- targets[1].element.style.filter = (vector.y > 0) ? 'brightness(90%)' : '';
1356
- targets[2].element.style.filter = (vector.x < 0) ? 'brightness(90%)' : '';
1357
- targets[3].element.style.filter = (vector.x > 0) ? 'brightness(90%)' : '';
1347
+ targets[0].element.style.filter = (vector.y < 0) ? 'brightness(80%)' : '';
1348
+ targets[1].element.style.filter = (vector.y > 0) ? 'brightness(80%)' : '';
1349
+ targets[2].element.style.filter = (vector.x < 0) ? 'brightness(80%)' : '';
1350
+ targets[3].element.style.filter = (vector.x > 0) ? 'brightness(80%)' : '';
1358
1351
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
1359
1352
  xnew$1.emit(nexttype, { type: nexttype, vector });
1360
1353
  });
@@ -1413,6 +1406,230 @@ function TextStream(unit, { text = '', speed = 50, fade = 300 } = {}) {
1413
1406
  }
1414
1407
  }
1415
1408
 
1409
+ const context = new window.AudioContext();
1410
+ const master = context.createGain();
1411
+ //----------------------------------------------------------------------------------------------------
1412
+ // master volume
1413
+ //----------------------------------------------------------------------------------------------------
1414
+ master.gain.value = 0.1;
1415
+ master.connect(context.destination);
1416
+ //----------------------------------------------------------------------------------------------------
1417
+ // audio file
1418
+ //----------------------------------------------------------------------------------------------------
1419
+ class AudioFile {
1420
+ constructor(path) {
1421
+ this.promise = fetch(path)
1422
+ .then((response) => response.arrayBuffer())
1423
+ .then((response) => context.decodeAudioData(response))
1424
+ .then((response) => { this.buffer = response; })
1425
+ .catch(() => {
1426
+ console.warn(`"${path}" could not be loaded.`);
1427
+ });
1428
+ this.amp = context.createGain();
1429
+ this.amp.gain.value = 1.0;
1430
+ this.amp.connect(master);
1431
+ this.fade = context.createGain();
1432
+ this.fade.gain.value = 1.0;
1433
+ this.fade.connect(this.amp);
1434
+ this.source = null;
1435
+ this.start = null;
1436
+ }
1437
+ set volume(value) {
1438
+ this.amp.gain.value = value;
1439
+ }
1440
+ get volume() {
1441
+ return this.amp.gain.value;
1442
+ }
1443
+ play({ offset = 0, fade = 0, loop = false } = {}) {
1444
+ if (this.buffer !== undefined && this.start === null) {
1445
+ this.source = context.createBufferSource();
1446
+ this.source.buffer = this.buffer;
1447
+ this.source.loop = loop;
1448
+ this.source.connect(this.fade);
1449
+ this.start = context.currentTime;
1450
+ this.source.playbackRate.value = 1;
1451
+ this.source.start(context.currentTime, offset / 1000);
1452
+ // Apply fade-in effect if fade duration is specified
1453
+ if (fade > 0) {
1454
+ this.fade.gain.setValueAtTime(0, context.currentTime);
1455
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1456
+ }
1457
+ this.source.onended = () => {
1458
+ var _a;
1459
+ this.start = null;
1460
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1461
+ this.source = null;
1462
+ };
1463
+ }
1464
+ }
1465
+ pause({ fade = 0 } = {}) {
1466
+ var _a, _b;
1467
+ if (this.buffer !== undefined && this.start !== null) {
1468
+ const elapsed = (context.currentTime - this.start) % this.buffer.duration * 1000;
1469
+ // Apply fade-out effect if fade duration is specified
1470
+ if (fade > 0) {
1471
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
1472
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1473
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1474
+ }
1475
+ else {
1476
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1477
+ }
1478
+ this.start = null;
1479
+ return elapsed;
1480
+ }
1481
+ }
1482
+ clear() {
1483
+ var _a;
1484
+ this.amp.disconnect();
1485
+ this.fade.disconnect();
1486
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1487
+ }
1488
+ }
1489
+ const keymap = {
1490
+ 'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
1491
+ '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,
1492
+ '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,
1493
+ '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,
1494
+ '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,
1495
+ '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,
1496
+ '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,
1497
+ '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,
1498
+ 'C8': 4186.009,
1499
+ };
1500
+ const notemap = {
1501
+ '1m': 4.000, '2n': 2.000, '4n': 1.000, '8n': 0.500, '16n': 0.250, '32n': 0.125,
1502
+ };
1503
+ class Synthesizer {
1504
+ constructor(props) { this.props = props; }
1505
+ press(frequency, duration, wait) {
1506
+ var _a;
1507
+ const props = this.props;
1508
+ const fv = typeof frequency === 'string' ? keymap[frequency] : frequency;
1509
+ const dv = typeof duration === 'string' ? (notemap[duration] * 60 / ((_a = props.bpm) !== null && _a !== void 0 ? _a : 120)) : (typeof duration === 'number' ? (duration / 1000) : 0);
1510
+ const start = context.currentTime + (wait !== null && wait !== void 0 ? wait : 0) / 1000;
1511
+ const nodes = {};
1512
+ nodes.oscillator = context.createOscillator();
1513
+ nodes.oscillator.type = props.oscillator.type;
1514
+ nodes.oscillator.frequency.value = fv;
1515
+ if (props.oscillator.LFO) {
1516
+ nodes.oscillatorLFO = context.createOscillator();
1517
+ nodes.oscillatorLFODepth = context.createGain();
1518
+ nodes.oscillatorLFODepth.gain.value = fv * (Math.pow(2.0, props.oscillator.LFO.amount / 12.0) - 1.0);
1519
+ nodes.oscillatorLFO.type = props.oscillator.LFO.type;
1520
+ nodes.oscillatorLFO.frequency.value = props.oscillator.LFO.rate;
1521
+ nodes.oscillatorLFO.start(start);
1522
+ nodes.oscillatorLFO.connect(nodes.oscillatorLFODepth);
1523
+ nodes.oscillatorLFODepth.connect(nodes.oscillator.frequency);
1524
+ }
1525
+ nodes.amp = context.createGain();
1526
+ nodes.amp.gain.value = 0.0;
1527
+ nodes.target = context.createGain();
1528
+ nodes.target.gain.value = 1.0;
1529
+ nodes.amp.connect(nodes.target);
1530
+ nodes.target.connect(master);
1531
+ if (props.filter) {
1532
+ nodes.filter = context.createBiquadFilter();
1533
+ nodes.filter.type = props.filter.type;
1534
+ nodes.filter.frequency.value = props.filter.cutoff;
1535
+ nodes.oscillator.connect(nodes.filter);
1536
+ nodes.filter.connect(nodes.amp);
1537
+ }
1538
+ else {
1539
+ nodes.oscillator.connect(nodes.amp);
1540
+ }
1541
+ if (props.reverb) {
1542
+ nodes.convolver = context.createConvolver();
1543
+ nodes.convolver.buffer = impulseResponse({ time: props.reverb.time });
1544
+ nodes.convolverDepth = context.createGain();
1545
+ nodes.convolverDepth.gain.value = 1.0;
1546
+ nodes.convolverDepth.gain.value *= props.reverb.mix;
1547
+ nodes.target.gain.value *= (1.0 - props.reverb.mix);
1548
+ nodes.amp.connect(nodes.convolver);
1549
+ nodes.convolver.connect(nodes.convolverDepth);
1550
+ nodes.convolverDepth.connect(master);
1551
+ }
1552
+ if (props.oscillator.envelope) {
1553
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1554
+ startEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1555
+ }
1556
+ if (props.amp.envelope) {
1557
+ startEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1558
+ }
1559
+ nodes.oscillator.start(start);
1560
+ if (dv > 0) {
1561
+ release();
1562
+ }
1563
+ else {
1564
+ return { release };
1565
+ }
1566
+ function release() {
1567
+ let stop = null;
1568
+ const end = dv > 0 ? dv : (context.currentTime - start);
1569
+ if (props.amp.envelope) {
1570
+ const ADSR = props.amp.envelope.ADSR;
1571
+ const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1572
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1573
+ stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1574
+ }
1575
+ else {
1576
+ stop = start + end;
1577
+ }
1578
+ if (nodes.oscillatorLFO) {
1579
+ nodes.oscillatorLFO.stop(stop);
1580
+ }
1581
+ if (props.oscillator.envelope) {
1582
+ const amount = fv * (Math.pow(2.0, props.oscillator.envelope.amount / 12.0) - 1.0);
1583
+ stopEnvelope(nodes.oscillator.frequency, fv, amount, props.oscillator.envelope.ADSR);
1584
+ }
1585
+ if (props.amp.envelope) {
1586
+ stopEnvelope(nodes.amp.gain, 0.0, props.amp.envelope.amount, props.amp.envelope.ADSR);
1587
+ }
1588
+ nodes.oscillator.stop(stop);
1589
+ setTimeout(() => {
1590
+ var _a, _b, _c, _d, _e;
1591
+ nodes.oscillator.disconnect();
1592
+ nodes.amp.disconnect();
1593
+ nodes.target.disconnect();
1594
+ (_a = nodes.oscillatorLFO) === null || _a === void 0 ? void 0 : _a.disconnect();
1595
+ (_b = nodes.oscillatorLFODepth) === null || _b === void 0 ? void 0 : _b.disconnect();
1596
+ (_c = nodes.filter) === null || _c === void 0 ? void 0 : _c.disconnect();
1597
+ (_d = nodes.convolver) === null || _d === void 0 ? void 0 : _d.disconnect();
1598
+ (_e = nodes.convolverDepth) === null || _e === void 0 ? void 0 : _e.disconnect();
1599
+ }, 2000);
1600
+ }
1601
+ function stopEnvelope(param, base, amount, ADSR) {
1602
+ const end = dv > 0 ? dv : (context.currentTime - start);
1603
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1604
+ if (rate < 1.0) {
1605
+ param.cancelScheduledValues(start);
1606
+ param.setValueAtTime(base, start);
1607
+ param.linearRampToValueAtTime(base + amount * rate, start + ADSR[0] / 1000 * rate);
1608
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1609
+ }
1610
+ param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1611
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1612
+ }
1613
+ function startEnvelope(param, base, amount, ADSR) {
1614
+ param.value = base;
1615
+ param.setValueAtTime(base, start);
1616
+ param.linearRampToValueAtTime(base + amount, start + ADSR[0] / 1000);
1617
+ param.linearRampToValueAtTime(base + amount * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000);
1618
+ }
1619
+ function impulseResponse({ time, decay = 2.0 }) {
1620
+ const length = context.sampleRate * time / 1000;
1621
+ const impulse = context.createBuffer(2, length, context.sampleRate);
1622
+ const ch0 = impulse.getChannelData(0);
1623
+ const ch1 = impulse.getChannelData(1);
1624
+ for (let i = 0; i < length; i++) {
1625
+ ch0[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1626
+ ch1[i] = (2 * Math.random() - 1) * Math.pow(1 - i / length, decay);
1627
+ }
1628
+ return impulse;
1629
+ }
1630
+ }
1631
+ }
1632
+
1416
1633
  const basics = {
1417
1634
  Screen,
1418
1635
  Modal,
@@ -1421,6 +1638,33 @@ const basics = {
1421
1638
  AnalogStick,
1422
1639
  DirectionalPad,
1423
1640
  };
1424
- const xnew = Object.assign(xnew$1, { basics });
1641
+ const audio = {
1642
+ load(path) {
1643
+ const music = new AudioFile(path);
1644
+ const object = {
1645
+ play(options = {}) {
1646
+ const unit = xnew();
1647
+ if (music.start === null) {
1648
+ music.play(options);
1649
+ unit.on('finalize', () => music.pause({ fade: options.fade }));
1650
+ }
1651
+ },
1652
+ pause(options = {}) {
1653
+ music.pause(options);
1654
+ }
1655
+ };
1656
+ return xnew.promise(music.promise).then(() => object);
1657
+ },
1658
+ synthesizer(props) {
1659
+ return new Synthesizer(props);
1660
+ },
1661
+ get volume() {
1662
+ return master.gain.value;
1663
+ },
1664
+ set volume(value) {
1665
+ master.gain.value = value;
1666
+ }
1667
+ };
1668
+ const xnew = Object.assign(xnew$1, { basics, audio });
1425
1669
 
1426
1670
  export { xnew as default };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "Component-Oriented Programming"
6
6
  ],
7
- "version": "0.4.5",
7
+ "version": "0.4.6",
8
8
  "main": "dist/xnew.js",
9
9
  "module": "dist/xnew.mjs",
10
10
  "types": "dist/xnew.d.ts",