q5 2.17.0 → 2.18.3
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 +3 -3
- package/deno.json +1 -1
- package/package.json +2 -2
- package/q5.d.ts +246 -61
- package/q5.js +370 -11
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.18
|
|
4
4
|
* @author quinton-ashley, Tezumie, and LingDong-
|
|
5
5
|
* @license LGPL-3.0
|
|
6
6
|
* @class Q5
|
|
@@ -223,6 +223,8 @@ function Q5(scope, parent, renderer) {
|
|
|
223
223
|
$.postProcess ??= () => {};
|
|
224
224
|
|
|
225
225
|
let userFns = [
|
|
226
|
+
'setup',
|
|
227
|
+
'postProcess',
|
|
226
228
|
'mouseMoved',
|
|
227
229
|
'mousePressed',
|
|
228
230
|
'mouseReleased',
|
|
@@ -311,7 +313,7 @@ function createCanvas(w, h, opt) {
|
|
|
311
313
|
}
|
|
312
314
|
}
|
|
313
315
|
|
|
314
|
-
Q5.version = Q5.VERSION = '2.
|
|
316
|
+
Q5.version = Q5.VERSION = '2.18';
|
|
315
317
|
|
|
316
318
|
if (typeof document == 'object') {
|
|
317
319
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -2082,8 +2084,8 @@ Q5.modules.ai = ($) => {
|
|
|
2082
2084
|
let parts = errFile.split(':');
|
|
2083
2085
|
let lineNum = parseInt(parts.at(-2));
|
|
2084
2086
|
if (askAI) lineNum++;
|
|
2085
|
-
parts[
|
|
2086
|
-
let fileUrl = parts.slice(0, 2).join(':');
|
|
2087
|
+
parts[parts.length - 1] = parts.at(-1).split(')')[0];
|
|
2088
|
+
let fileUrl = parts.slice(0, -2).join(':');
|
|
2087
2089
|
let fileBase = fileUrl.split('/').at(-1);
|
|
2088
2090
|
|
|
2089
2091
|
try {
|
|
@@ -2107,7 +2109,7 @@ Q5.modules.ai = ($) => {
|
|
|
2107
2109
|
askAI && e.message.length > 10 ? e.message.slice(10) : 'Whats+wrong+with+this+line%3F+short+answer';
|
|
2108
2110
|
|
|
2109
2111
|
let url =
|
|
2110
|
-
'https://chatgpt.com/?q=q5.js+' +
|
|
2112
|
+
'https://chatgpt.com/?q=using+q5.js+not+p5.js+' +
|
|
2111
2113
|
question +
|
|
2112
2114
|
(askAI ? '' : '%0A%0A' + encodeURIComponent(e.name + ': ' + e.message)) +
|
|
2113
2115
|
'%0A%0ALine%3A+' +
|
|
@@ -2625,7 +2627,7 @@ Q5.modules.dom = ($) => {
|
|
|
2625
2627
|
};
|
|
2626
2628
|
|
|
2627
2629
|
el.show = () => {
|
|
2628
|
-
el.style.display = '
|
|
2630
|
+
el.style.display = '';
|
|
2629
2631
|
return el;
|
|
2630
2632
|
};
|
|
2631
2633
|
|
|
@@ -2662,6 +2664,7 @@ Q5.modules.dom = ($) => {
|
|
|
2662
2664
|
let lbl = $.createEl('label', label);
|
|
2663
2665
|
lbl.addEventListener('click', () => {
|
|
2664
2666
|
el.checked = !el.checked;
|
|
2667
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2665
2668
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2666
2669
|
});
|
|
2667
2670
|
el.insertAdjacentElement('afterend', lbl);
|
|
@@ -2715,12 +2718,13 @@ Q5.modules.dom = ($) => {
|
|
|
2715
2718
|
btn.type = 'radio';
|
|
2716
2719
|
btn.name = el.name;
|
|
2717
2720
|
btn.value = value || label;
|
|
2718
|
-
btn.addEventListener('
|
|
2721
|
+
btn.addEventListener('input', () => (el.selected = btn));
|
|
2719
2722
|
|
|
2720
2723
|
let lbl = $.createEl('label', label);
|
|
2721
2724
|
lbl.addEventListener('click', () => {
|
|
2722
2725
|
btn.checked = true;
|
|
2723
2726
|
el.selected = btn;
|
|
2727
|
+
btn.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2724
2728
|
btn.dispatchEvent(new Event('change', { bubbles: true }));
|
|
2725
2729
|
});
|
|
2726
2730
|
|
|
@@ -2744,14 +2748,19 @@ Q5.modules.dom = ($) => {
|
|
|
2744
2748
|
}
|
|
2745
2749
|
Object.defineProperty(el, 'selected', {
|
|
2746
2750
|
get: () => {
|
|
2747
|
-
if (el.multiple)
|
|
2748
|
-
|
|
2751
|
+
if (el.multiple) {
|
|
2752
|
+
return Array.from(el.selectedOptions).map((opt) => opt.textContent);
|
|
2753
|
+
}
|
|
2754
|
+
return el.selectedOptions[0]?.textContent;
|
|
2749
2755
|
},
|
|
2750
2756
|
set: (v) => {
|
|
2751
2757
|
if (el.multiple) {
|
|
2752
|
-
el.options.forEach((
|
|
2758
|
+
Array.from(el.options).forEach((opt) => {
|
|
2759
|
+
opt.selected = v.includes(opt.textContent);
|
|
2760
|
+
});
|
|
2753
2761
|
} else {
|
|
2754
|
-
|
|
2762
|
+
const option = Array.from(el.options).find((opt) => opt.textContent === v);
|
|
2763
|
+
if (option) option.selected = true;
|
|
2755
2764
|
}
|
|
2756
2765
|
}
|
|
2757
2766
|
});
|
|
@@ -3160,6 +3169,14 @@ Q5.modules.math = ($, q) => {
|
|
|
3160
3169
|
}
|
|
3161
3170
|
};
|
|
3162
3171
|
|
|
3172
|
+
if ($._renderer == 'c2d' && !$._webgpuFallback) {
|
|
3173
|
+
$.randomX = (v = 0) => $.random(-v, $.canvas.w + v);
|
|
3174
|
+
$.randomY = (v = 0) => $.random(-v, $.canvas.h + v);
|
|
3175
|
+
} else {
|
|
3176
|
+
$.randomX = (v = 0) => $.random(-$.canvas.hw - v, $.canvas.hw + v);
|
|
3177
|
+
$.randomY = (v = 0) => $.random(-$.canvas.hh - v, $.canvas.hh + v);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3163
3180
|
$.randomGenerator = (method) => {
|
|
3164
3181
|
if (method == $.LCG) rng1 = lcg();
|
|
3165
3182
|
else if (method == $.SHR3) rng1 = shr3();
|
|
@@ -3448,6 +3465,348 @@ Q5.PerlinNoise = class extends Q5.Noise {
|
|
|
3448
3465
|
return (total / maxAmp + 1) / 2;
|
|
3449
3466
|
}
|
|
3450
3467
|
};
|
|
3468
|
+
Q5.modules.record = ($, q) => {
|
|
3469
|
+
let rec, btn0, btn1, timer, formatSelect, qualitySelect;
|
|
3470
|
+
|
|
3471
|
+
$.recording = false;
|
|
3472
|
+
|
|
3473
|
+
function initRecorder(opt = {}) {
|
|
3474
|
+
document.head.insertAdjacentHTML(
|
|
3475
|
+
'beforeend',
|
|
3476
|
+
`<style>
|
|
3477
|
+
.rec {
|
|
3478
|
+
display: flex;
|
|
3479
|
+
z-index: 1000;
|
|
3480
|
+
gap: 6px;
|
|
3481
|
+
background: #1a1b1d;
|
|
3482
|
+
padding: 6px 8px;
|
|
3483
|
+
border-radius: 21px;
|
|
3484
|
+
box-shadow: #0000001a 0px 4px 12px;
|
|
3485
|
+
border: 2px solid transparent;
|
|
3486
|
+
opacity: 0.6;
|
|
3487
|
+
transition: all 0.3s;
|
|
3488
|
+
width: 134px;
|
|
3489
|
+
overflow: hidden;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
.rec:hover {
|
|
3493
|
+
width: unset;
|
|
3494
|
+
opacity: 0.96;
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
.rec.recording { border-color: #cc3e44; }
|
|
3498
|
+
|
|
3499
|
+
.rec button,
|
|
3500
|
+
.rec select { cursor: pointer; }
|
|
3501
|
+
|
|
3502
|
+
.rec button,
|
|
3503
|
+
.rec select,
|
|
3504
|
+
.rec .record-timer {
|
|
3505
|
+
font-family: sans-serif;
|
|
3506
|
+
font-size: 14px;
|
|
3507
|
+
padding: 2px 10px;
|
|
3508
|
+
border-radius: 18px;
|
|
3509
|
+
outline: none;
|
|
3510
|
+
background-color: #232529;
|
|
3511
|
+
color: #d4dae6;
|
|
3512
|
+
box-shadow: #0000001a 0px 4px 12px;
|
|
3513
|
+
border: 1px solid #46494e;
|
|
3514
|
+
vertical-align: middle;
|
|
3515
|
+
line-height: 18px;
|
|
3516
|
+
transition: all 0.3s;
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
.rec .record-button {
|
|
3520
|
+
color: #cc3e44;
|
|
3521
|
+
font-size: 18px;
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
.rec select:hover,
|
|
3525
|
+
.rec button:hover { background-color: #292b30; }
|
|
3526
|
+
|
|
3527
|
+
.rec button:disabled {
|
|
3528
|
+
opacity: 0.5;
|
|
3529
|
+
color: #969ba5;
|
|
3530
|
+
cursor: not-allowed;
|
|
3531
|
+
}
|
|
3532
|
+
</style>`
|
|
3533
|
+
);
|
|
3534
|
+
|
|
3535
|
+
rec = $.createEl('div');
|
|
3536
|
+
rec.className = 'rec';
|
|
3537
|
+
rec.innerHTML = `
|
|
3538
|
+
<button class="record-button"></button>
|
|
3539
|
+
<span class="record-timer"></span>
|
|
3540
|
+
<button></button>
|
|
3541
|
+
`;
|
|
3542
|
+
|
|
3543
|
+
[btn0, timer, btn1] = rec.children;
|
|
3544
|
+
|
|
3545
|
+
rec.x = rec.y = 8;
|
|
3546
|
+
|
|
3547
|
+
rec.resetTimer = () => (rec.time = { hours: 0, minutes: 0, seconds: 0, frames: 0 });
|
|
3548
|
+
rec.resetTimer();
|
|
3549
|
+
|
|
3550
|
+
rec.formats = opt.formats || {
|
|
3551
|
+
'H.264': 'video/mp4; codecs="avc1.42E01E"',
|
|
3552
|
+
VP9: 'video/mp4; codecs=vp9'
|
|
3553
|
+
};
|
|
3554
|
+
|
|
3555
|
+
// remove unsupported formats
|
|
3556
|
+
for (let format in rec.formats) {
|
|
3557
|
+
if (!MediaRecorder.isTypeSupported(rec.formats[format])) {
|
|
3558
|
+
delete rec.formats[format];
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
formatSelect = $.createSelect('format');
|
|
3563
|
+
for (const name in rec.formats) {
|
|
3564
|
+
formatSelect.option(name, rec.formats[name]);
|
|
3565
|
+
}
|
|
3566
|
+
formatSelect.title = 'Video Format';
|
|
3567
|
+
rec.append(formatSelect);
|
|
3568
|
+
|
|
3569
|
+
let qMult = {
|
|
3570
|
+
min: 0.1,
|
|
3571
|
+
low: 0.25,
|
|
3572
|
+
mid: 0.5,
|
|
3573
|
+
high: 0.75,
|
|
3574
|
+
ultra: 0.9,
|
|
3575
|
+
max: 1
|
|
3576
|
+
};
|
|
3577
|
+
|
|
3578
|
+
qualitySelect = $.createSelect('quality');
|
|
3579
|
+
for (let name in qMult) {
|
|
3580
|
+
qualitySelect.option(name, qMult[name]);
|
|
3581
|
+
}
|
|
3582
|
+
qualitySelect.title = 'Video Quality';
|
|
3583
|
+
rec.append(qualitySelect);
|
|
3584
|
+
|
|
3585
|
+
rec.encoderSettings = {};
|
|
3586
|
+
|
|
3587
|
+
function changeFormat() {
|
|
3588
|
+
rec.encoderSettings.mimeType = formatSelect.value;
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
function changeQuality() {
|
|
3592
|
+
rec.encoderSettings.videoBitsPerSecond = maxVideoBitRate * qualitySelect.value;
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
formatSelect.addEventListener('change', changeFormat);
|
|
3596
|
+
qualitySelect.addEventListener('change', changeQuality);
|
|
3597
|
+
|
|
3598
|
+
Object.defineProperty(rec, 'quality', {
|
|
3599
|
+
get: () => qualitySelect.selected,
|
|
3600
|
+
set: (v) => {
|
|
3601
|
+
v = v.toLowerCase();
|
|
3602
|
+
if (qMult[v]) {
|
|
3603
|
+
qualitySelect.selected = v;
|
|
3604
|
+
changeQuality();
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
});
|
|
3608
|
+
|
|
3609
|
+
Object.defineProperty(rec, 'format', {
|
|
3610
|
+
get: () => formatSelect.selected,
|
|
3611
|
+
set: (v) => {
|
|
3612
|
+
v = v.toUpperCase();
|
|
3613
|
+
if (rec.formats[v]) {
|
|
3614
|
+
formatSelect.selected = v;
|
|
3615
|
+
changeFormat();
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
|
|
3620
|
+
let h = $.canvas.height;
|
|
3621
|
+
|
|
3622
|
+
if (h >= 1440 && rec.formats.VP9) rec.format = 'VP9';
|
|
3623
|
+
else rec.format = 'H.264';
|
|
3624
|
+
|
|
3625
|
+
let maxVideoBitRate =
|
|
3626
|
+
(h >= 4320 ? 128 : h >= 2160 ? 75 : h >= 1440 ? 36 : h >= 1080 ? 28 : h >= 720 ? 22 : 16) * 1000000;
|
|
3627
|
+
|
|
3628
|
+
rec.quality = 'high';
|
|
3629
|
+
|
|
3630
|
+
btn0.addEventListener('click', () => {
|
|
3631
|
+
if (!$.recording) start();
|
|
3632
|
+
else if (!rec.paused) $.pauseRecording();
|
|
3633
|
+
else resumeRecording();
|
|
3634
|
+
});
|
|
3635
|
+
|
|
3636
|
+
btn1.addEventListener('click', () => {
|
|
3637
|
+
if (rec.paused) $.saveRecording();
|
|
3638
|
+
else $.deleteRecording();
|
|
3639
|
+
});
|
|
3640
|
+
|
|
3641
|
+
resetUI();
|
|
3642
|
+
|
|
3643
|
+
$.registerMethod('post', updateTimer);
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
function start() {
|
|
3647
|
+
if ($.recording) return;
|
|
3648
|
+
|
|
3649
|
+
if (!rec.stream) {
|
|
3650
|
+
rec.frameRate ??= $.getTargetFrameRate();
|
|
3651
|
+
let canvasStream = $.canvas.captureStream(rec.frameRate);
|
|
3652
|
+
// let audioStream = Q5.aud.createMediaStreamDestination().stream;
|
|
3653
|
+
// rec.stream = new MediaStream([canvasStream.getTracks()[0], ...audioStream.getTracks()]);
|
|
3654
|
+
rec.stream = canvasStream;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
try {
|
|
3658
|
+
rec.mediaRecorder = new MediaRecorder(rec.stream, rec.encoderSettings);
|
|
3659
|
+
} catch (e) {
|
|
3660
|
+
console.error('Failed to initialize MediaRecorder: ', e);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
rec.chunks = [];
|
|
3665
|
+
rec.mediaRecorder.addEventListener('dataavailable', (e) => {
|
|
3666
|
+
if (e.data.size > 0) rec.chunks.push(e.data);
|
|
3667
|
+
});
|
|
3668
|
+
|
|
3669
|
+
rec.mediaRecorder.start();
|
|
3670
|
+
q.recording = true;
|
|
3671
|
+
rec.paused = false;
|
|
3672
|
+
rec.classList.add('recording');
|
|
3673
|
+
|
|
3674
|
+
rec.resetTimer();
|
|
3675
|
+
resetUI(true);
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
function resumeRecording() {
|
|
3679
|
+
if (!$.recording || !rec.paused) return;
|
|
3680
|
+
|
|
3681
|
+
rec.mediaRecorder.resume();
|
|
3682
|
+
rec.paused = false;
|
|
3683
|
+
resetUI(true);
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
function stop() {
|
|
3687
|
+
if (!$.recording) return;
|
|
3688
|
+
|
|
3689
|
+
rec.resetTimer();
|
|
3690
|
+
rec.mediaRecorder.stop();
|
|
3691
|
+
q.recording = false;
|
|
3692
|
+
rec.paused = false;
|
|
3693
|
+
rec.classList.remove('recording');
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
function resetUI(r) {
|
|
3697
|
+
btn0.textContent = r ? '⏸' : '⏺';
|
|
3698
|
+
btn0.title = (r ? 'Pause' : 'Start') + ' Recording';
|
|
3699
|
+
btn1.textContent = r ? '🗑️' : '💾';
|
|
3700
|
+
btn1.title = (r ? 'Delete' : 'Save') + ' Recording';
|
|
3701
|
+
btn1.disabled = !r;
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
function updateTimer() {
|
|
3705
|
+
if ($.recording && !rec.paused) {
|
|
3706
|
+
rec.time.frames++;
|
|
3707
|
+
let fr = $.getTargetFrameRate();
|
|
3708
|
+
|
|
3709
|
+
if (rec.time.frames >= fr) {
|
|
3710
|
+
rec.time.seconds += Math.floor(rec.time.frames / fr);
|
|
3711
|
+
rec.time.frames %= fr;
|
|
3712
|
+
|
|
3713
|
+
if (rec.time.seconds >= 60) {
|
|
3714
|
+
rec.time.minutes += Math.floor(rec.time.seconds / 60);
|
|
3715
|
+
rec.time.seconds %= 60;
|
|
3716
|
+
|
|
3717
|
+
if (rec.time.minutes >= 60) {
|
|
3718
|
+
rec.time.hours += Math.floor(rec.time.minutes / 60);
|
|
3719
|
+
rec.time.minutes %= 60;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
timer.textContent = formatTime();
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
function formatTime() {
|
|
3728
|
+
let { hours, minutes, seconds, frames } = rec.time;
|
|
3729
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(
|
|
3730
|
+
2,
|
|
3731
|
+
'0'
|
|
3732
|
+
)}:${String(frames).padStart(2, '0')}`;
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
$.createRecorder = (opt) => {
|
|
3736
|
+
if (!rec) initRecorder(opt);
|
|
3737
|
+
return rec;
|
|
3738
|
+
};
|
|
3739
|
+
|
|
3740
|
+
$.record = (opt) => {
|
|
3741
|
+
if (!rec) {
|
|
3742
|
+
initRecorder(opt);
|
|
3743
|
+
rec.hide();
|
|
3744
|
+
}
|
|
3745
|
+
if (!$.recording) start();
|
|
3746
|
+
else if (rec.paused) resumeRecording();
|
|
3747
|
+
};
|
|
3748
|
+
|
|
3749
|
+
$.pauseRecording = () => {
|
|
3750
|
+
if (!$.recording || rec.paused) return;
|
|
3751
|
+
|
|
3752
|
+
rec.mediaRecorder.pause();
|
|
3753
|
+
rec.paused = true;
|
|
3754
|
+
|
|
3755
|
+
resetUI();
|
|
3756
|
+
btn0.title = 'Resume Recording';
|
|
3757
|
+
btn1.disabled = false;
|
|
3758
|
+
};
|
|
3759
|
+
|
|
3760
|
+
$.deleteRecording = () => {
|
|
3761
|
+
stop();
|
|
3762
|
+
resetUI();
|
|
3763
|
+
q.recording = false;
|
|
3764
|
+
};
|
|
3765
|
+
|
|
3766
|
+
$.saveRecording = async (fileName) => {
|
|
3767
|
+
if (!$.recording) return;
|
|
3768
|
+
|
|
3769
|
+
await new Promise((resolve) => {
|
|
3770
|
+
rec.mediaRecorder.onstop = resolve;
|
|
3771
|
+
stop();
|
|
3772
|
+
});
|
|
3773
|
+
|
|
3774
|
+
let type = rec.encoderSettings.mimeType,
|
|
3775
|
+
extension = type.slice(6, type.indexOf(';')),
|
|
3776
|
+
dataUrl = URL.createObjectURL(new Blob(rec.chunks, { type })),
|
|
3777
|
+
iframe = document.createElement('iframe'),
|
|
3778
|
+
a = document.createElement('a');
|
|
3779
|
+
|
|
3780
|
+
// Create an invisible iframe to detect load completion
|
|
3781
|
+
iframe.style.display = 'none';
|
|
3782
|
+
iframe.name = 'download_' + Date.now();
|
|
3783
|
+
document.body.append(iframe);
|
|
3784
|
+
|
|
3785
|
+
a.target = iframe.name;
|
|
3786
|
+
a.href = dataUrl;
|
|
3787
|
+
fileName ??=
|
|
3788
|
+
document.title +
|
|
3789
|
+
' ' +
|
|
3790
|
+
new Date()
|
|
3791
|
+
.toLocaleString(undefined, { hour12: false })
|
|
3792
|
+
.replace(',', ' at')
|
|
3793
|
+
.replaceAll('/', '-')
|
|
3794
|
+
.replaceAll(':', '_');
|
|
3795
|
+
a.download = `${fileName}.${extension}`;
|
|
3796
|
+
|
|
3797
|
+
await new Promise((resolve) => {
|
|
3798
|
+
iframe.onload = () => {
|
|
3799
|
+
document.body.removeChild(iframe);
|
|
3800
|
+
resolve();
|
|
3801
|
+
};
|
|
3802
|
+
a.click();
|
|
3803
|
+
});
|
|
3804
|
+
|
|
3805
|
+
setTimeout(() => URL.revokeObjectURL(dataUrl), 1000);
|
|
3806
|
+
resetUI();
|
|
3807
|
+
q.recording = false;
|
|
3808
|
+
};
|
|
3809
|
+
};
|
|
3451
3810
|
Q5.modules.sound = ($, q) => {
|
|
3452
3811
|
$.Sound = Q5.Sound;
|
|
3453
3812
|
let sounds = [];
|