pxt-core 8.2.1 → 8.2.4
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/built/backendutils.js +3 -3
- package/built/gdb.js +3 -3
- package/built/pxt.js +236 -97
- package/built/pxtblockly.js +21 -1
- package/built/pxtblocks.d.ts +1 -0
- package/built/pxtblocks.js +21 -1
- package/built/pxteditor.js +2 -1
- package/built/pxtlib.d.ts +1 -0
- package/built/pxtlib.js +105 -9
- package/built/pxtrunner.d.ts +1 -0
- package/built/pxtrunner.js +1 -0
- package/built/pxtsim.d.ts +2 -1
- package/built/pxtsim.js +131 -88
- package/built/target.js +1 -1
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtblockly.js +1 -1
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxteditor.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtrunner.js +1 -1
- package/built/web/pxtsim.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/semantic.css +1 -1
- package/package.json +1 -1
- package/theme/tutorial.less +8 -0
- package/webapp/public/run.html +4 -2
package/built/pxtlib.js
CHANGED
|
@@ -8713,10 +8713,10 @@ var pxt;
|
|
|
8713
8713
|
docs.prepTemplate = prepTemplate;
|
|
8714
8714
|
function setupRenderer(renderer) {
|
|
8715
8715
|
renderer.image = function (href, title, text) {
|
|
8716
|
-
const endpointName = "
|
|
8716
|
+
const endpointName = "makecodeprodmediaeastus-usea";
|
|
8717
8717
|
if (href.startsWith("youtube:")) {
|
|
8718
|
-
let out = '<div class="tutorial-video-embed"><iframe src="https://www.youtube.com/embed/' + href.split(":").pop()
|
|
8719
|
-
+ '" title="' +
|
|
8718
|
+
let out = '<div class="tutorial-video-embed"><iframe class="yt-embed" src="https://www.youtube.com/embed/' + href.split(":").pop()
|
|
8719
|
+
+ '" title="' + text + '" frameborder="0" ' + 'allowFullScreen ' + 'allow="autoplay; picture-in-picture"></iframe></div>';
|
|
8720
8720
|
return out;
|
|
8721
8721
|
}
|
|
8722
8722
|
else if (href.startsWith("azuremedia:")) {
|
|
@@ -10500,14 +10500,17 @@ var pxt;
|
|
|
10500
10500
|
return false;
|
|
10501
10501
|
}
|
|
10502
10502
|
function isRepoBanned(repo, config) {
|
|
10503
|
+
var _a, _b;
|
|
10503
10504
|
if (isOrgBanned(repo, config))
|
|
10504
10505
|
return true;
|
|
10505
10506
|
if (!config)
|
|
10506
10507
|
return false; // don't know
|
|
10507
|
-
if (!repo
|
|
10508
|
+
if (!repo)
|
|
10508
10509
|
return true;
|
|
10510
|
+
const repoFull = (_a = repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
10511
|
+
const repoSlug = (_b = repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
10509
10512
|
if (config.bannedRepos
|
|
10510
|
-
&& config.bannedRepos.some(fn => fn.toLowerCase() ==
|
|
10513
|
+
&& config.bannedRepos.some(fn => fn && (fn.toLowerCase() == repoFull || fn.toLowerCase() == repoSlug)))
|
|
10511
10514
|
return true;
|
|
10512
10515
|
return false;
|
|
10513
10516
|
}
|
|
@@ -10521,13 +10524,15 @@ var pxt;
|
|
|
10521
10524
|
return false;
|
|
10522
10525
|
}
|
|
10523
10526
|
function isRepoApproved(repo, config) {
|
|
10524
|
-
var _a;
|
|
10527
|
+
var _a, _b;
|
|
10525
10528
|
if (isOrgApproved(repo, config))
|
|
10526
10529
|
return true;
|
|
10527
|
-
|
|
10530
|
+
const repoFull = (_a = repo === null || repo === void 0 ? void 0 : repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
10531
|
+
const repoSlug = (_b = repo === null || repo === void 0 ? void 0 : repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
10532
|
+
if (!(config === null || config === void 0 ? void 0 : config.approvedRepoLib) || !(repoFull || repoSlug))
|
|
10528
10533
|
return false;
|
|
10529
|
-
if (
|
|
10530
|
-
|
|
10534
|
+
if (config.approvedRepoLib[repoFull]
|
|
10535
|
+
|| config.approvedRepoLib[repoSlug])
|
|
10531
10536
|
return true;
|
|
10532
10537
|
return false;
|
|
10533
10538
|
}
|
|
@@ -16905,6 +16910,97 @@ var pxt;
|
|
|
16905
16910
|
return outParts.join(" ");
|
|
16906
16911
|
}
|
|
16907
16912
|
}
|
|
16913
|
+
function soundToInstructionBuffer(sound, fxSteps, fxRange) {
|
|
16914
|
+
const { startFrequency, endFrequency, startVolume, endVolume, interpolation, duration } = sound;
|
|
16915
|
+
const steps = [];
|
|
16916
|
+
// Optimize the simple case
|
|
16917
|
+
if (sound.interpolation === "linear" && sound.effect === "none") {
|
|
16918
|
+
steps.push({
|
|
16919
|
+
frequency: startFrequency,
|
|
16920
|
+
volume: (startVolume / assets.MAX_VOLUME) * 1024,
|
|
16921
|
+
});
|
|
16922
|
+
steps.push({
|
|
16923
|
+
frequency: endFrequency,
|
|
16924
|
+
volume: (endVolume / assets.MAX_VOLUME) * 1024,
|
|
16925
|
+
});
|
|
16926
|
+
}
|
|
16927
|
+
else {
|
|
16928
|
+
fxSteps = Math.min(fxSteps, Math.floor(duration / 5));
|
|
16929
|
+
const getVolumeAt = (t) => ((startVolume + t * (endVolume - startVolume) / duration) / assets.MAX_VOLUME) * 1024;
|
|
16930
|
+
let getFrequencyAt;
|
|
16931
|
+
switch (interpolation) {
|
|
16932
|
+
case "linear":
|
|
16933
|
+
getFrequencyAt = t => startFrequency + t * (endFrequency - startFrequency) / duration;
|
|
16934
|
+
break;
|
|
16935
|
+
case "curve":
|
|
16936
|
+
getFrequencyAt = t => startFrequency + (endFrequency - startFrequency) * Math.sin(t / duration * (Math.PI / 2));
|
|
16937
|
+
break;
|
|
16938
|
+
case "logarithmic":
|
|
16939
|
+
getFrequencyAt = t => startFrequency + Math.log10(1 + 9 * (t / duration)) * (endFrequency - startFrequency);
|
|
16940
|
+
break;
|
|
16941
|
+
}
|
|
16942
|
+
const timeSlice = duration / fxSteps;
|
|
16943
|
+
for (let i = 0; i < fxSteps; i++) {
|
|
16944
|
+
const newStep = {
|
|
16945
|
+
frequency: Math.max(getFrequencyAt(i * timeSlice), 1),
|
|
16946
|
+
volume: getVolumeAt(i * timeSlice)
|
|
16947
|
+
};
|
|
16948
|
+
if (sound.effect === "tremolo") {
|
|
16949
|
+
if (i % 2 === 0) {
|
|
16950
|
+
newStep.volume = Math.max(newStep.volume - fxRange * 500, 0);
|
|
16951
|
+
}
|
|
16952
|
+
else {
|
|
16953
|
+
newStep.volume = Math.min(newStep.volume + fxRange * 500, 1023);
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
else if (sound.effect === "vibrato") {
|
|
16957
|
+
if (i % 2 === 0) {
|
|
16958
|
+
newStep.frequency = Math.max(newStep.frequency - fxRange * 100, 1);
|
|
16959
|
+
}
|
|
16960
|
+
else {
|
|
16961
|
+
newStep.frequency = newStep.frequency + fxRange * 100;
|
|
16962
|
+
}
|
|
16963
|
+
}
|
|
16964
|
+
else if (sound.effect === "warble") {
|
|
16965
|
+
if (i % 2 === 0) {
|
|
16966
|
+
newStep.frequency = Math.max(newStep.frequency - fxRange * 1000, 1);
|
|
16967
|
+
}
|
|
16968
|
+
else {
|
|
16969
|
+
newStep.frequency = newStep.frequency + fxRange * 1000;
|
|
16970
|
+
}
|
|
16971
|
+
}
|
|
16972
|
+
steps.push(newStep);
|
|
16973
|
+
}
|
|
16974
|
+
}
|
|
16975
|
+
const out = new Uint8Array(12 * (steps.length - 1));
|
|
16976
|
+
const stepDuration = Math.floor(duration / (steps.length - 1));
|
|
16977
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
16978
|
+
const offset = i * 12;
|
|
16979
|
+
out[offset] = waveToValue(sound.wave);
|
|
16980
|
+
set16BitNumber(out, offset + 2, steps[i].frequency);
|
|
16981
|
+
set16BitNumber(out, offset + 4, stepDuration);
|
|
16982
|
+
set16BitNumber(out, offset + 6, steps[i].volume);
|
|
16983
|
+
set16BitNumber(out, offset + 8, steps[i + 1].volume);
|
|
16984
|
+
set16BitNumber(out, offset + 10, steps[i + 1].frequency);
|
|
16985
|
+
}
|
|
16986
|
+
return out;
|
|
16987
|
+
}
|
|
16988
|
+
assets.soundToInstructionBuffer = soundToInstructionBuffer;
|
|
16989
|
+
function waveToValue(wave) {
|
|
16990
|
+
switch (wave) {
|
|
16991
|
+
case "square": return 15;
|
|
16992
|
+
case "sine": return 3;
|
|
16993
|
+
case "triangle": return 1;
|
|
16994
|
+
case "noise": return 18;
|
|
16995
|
+
case "sawtooth": return 2;
|
|
16996
|
+
}
|
|
16997
|
+
}
|
|
16998
|
+
function set16BitNumber(buf, offset, value) {
|
|
16999
|
+
const temp = new Uint8Array(2);
|
|
17000
|
+
new Uint16Array(temp.buffer)[0] = value | 0;
|
|
17001
|
+
buf[offset] = temp[0];
|
|
17002
|
+
buf[offset + 1] = temp[1];
|
|
17003
|
+
}
|
|
16908
17004
|
})(assets = pxt.assets || (pxt.assets = {}));
|
|
16909
17005
|
})(pxt || (pxt = {}));
|
|
16910
17006
|
// See https://github.com/microsoft/TouchDevelop-backend/blob/master/docs/streams.md
|
package/built/pxtrunner.d.ts
CHANGED
package/built/pxtrunner.js
CHANGED
|
@@ -1750,6 +1750,7 @@ var pxt;
|
|
|
1750
1750
|
storedState: storedState,
|
|
1751
1751
|
light: simOptions.light,
|
|
1752
1752
|
single: simOptions.single,
|
|
1753
|
+
hideSimButtons: simOptions.hideSimButtons,
|
|
1753
1754
|
autofocus: simOptions.autofocus
|
|
1754
1755
|
};
|
|
1755
1756
|
if (pxt.appTarget.simulator && !simOptions.fullScreen)
|
package/built/pxtsim.d.ts
CHANGED
|
@@ -1268,6 +1268,7 @@ declare namespace pxsim {
|
|
|
1268
1268
|
ipc?: boolean;
|
|
1269
1269
|
dependencies?: Map<string>;
|
|
1270
1270
|
single?: boolean;
|
|
1271
|
+
hideSimButtons?: boolean;
|
|
1271
1272
|
autofocus?: boolean;
|
|
1272
1273
|
}
|
|
1273
1274
|
interface HwDebugger {
|
|
@@ -1423,11 +1424,11 @@ declare namespace pxsim {
|
|
|
1423
1424
|
function frequency(): number;
|
|
1424
1425
|
function muteAllChannels(): void;
|
|
1425
1426
|
function queuePlayInstructions(when: number, b: RefBuffer): void;
|
|
1426
|
-
function playInstructionsAsync(b: RefBuffer): Promise<void>;
|
|
1427
1427
|
function tone(frequency: number, gain: number): void;
|
|
1428
1428
|
function setCurrentToneGain(gain: number): void;
|
|
1429
1429
|
function playBufferAsync(buf: RefBuffer): Promise<void>;
|
|
1430
1430
|
function playPCMBufferStreamAsync(pull: () => Float32Array, sampleRate: number, volume?: number, isCancelled?: () => boolean): Promise<void>;
|
|
1431
|
+
function playInstructionsAsync(instructions: Uint8Array, isCancelled?: () => boolean, onPull?: (freq: number, volume: number) => void): Promise<void>;
|
|
1431
1432
|
function sendMidiMessage(buf: RefBuffer): void;
|
|
1432
1433
|
}
|
|
1433
1434
|
interface IPointerEvents {
|
package/built/pxtsim.js
CHANGED
|
@@ -6295,7 +6295,7 @@ var pxsim;
|
|
|
6295
6295
|
}
|
|
6296
6296
|
}
|
|
6297
6297
|
createFrame(url) {
|
|
6298
|
-
var _a;
|
|
6298
|
+
var _a, _b;
|
|
6299
6299
|
const wrapper = document.createElement("div");
|
|
6300
6300
|
wrapper.className = `simframe ui embed`;
|
|
6301
6301
|
const frame = document.createElement('iframe');
|
|
@@ -6305,12 +6305,18 @@ var pxsim;
|
|
|
6305
6305
|
frame.setAttribute('allow', 'autoplay;microphone');
|
|
6306
6306
|
frame.setAttribute('sandbox', 'allow-same-origin allow-scripts');
|
|
6307
6307
|
frame.className = 'no-select';
|
|
6308
|
-
|
|
6308
|
+
let furl = url || this.getSimUrl().toString();
|
|
6309
|
+
if ((_a = this._runOptions) === null || _a === void 0 ? void 0 : _a.hideSimButtons) {
|
|
6310
|
+
const urlObject = new URL(furl);
|
|
6311
|
+
urlObject.searchParams.append("hideSimButtons", "1");
|
|
6312
|
+
furl = urlObject.toString();
|
|
6313
|
+
}
|
|
6314
|
+
furl += '#' + frame.id;
|
|
6309
6315
|
frame.src = furl;
|
|
6310
6316
|
frame.frameBorder = "0";
|
|
6311
6317
|
frame.dataset['runid'] = this.runId;
|
|
6312
6318
|
frame.dataset['origin'] = new URL(furl).origin || "*";
|
|
6313
|
-
if ((
|
|
6319
|
+
if ((_b = this._runOptions) === null || _b === void 0 ? void 0 : _b.autofocus)
|
|
6314
6320
|
frame.setAttribute("autofocus", "true");
|
|
6315
6321
|
wrapper.appendChild(frame);
|
|
6316
6322
|
const i = document.createElement("i");
|
|
@@ -6547,8 +6553,7 @@ var pxsim;
|
|
|
6547
6553
|
msg.frameCounter = ++this.frameCounter;
|
|
6548
6554
|
msg.options = {
|
|
6549
6555
|
theme: this.themes[this.nextFrameId++ % this.themes.length],
|
|
6550
|
-
mpRole: (_b = (_a = /[\&\?]mp=(server|client)/i.exec(window.location.href)) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.toLowerCase()
|
|
6551
|
-
hideSimButtons: /hidesimbuttons(?:[:=])1/i.test(window.location.href)
|
|
6556
|
+
mpRole: (_b = (_a = /[\&\?]mp=(server|client)/i.exec(window.location.href)) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.toLowerCase()
|
|
6552
6557
|
};
|
|
6553
6558
|
msg.id = `${msg.options.theme}-${this.nextId()}`;
|
|
6554
6559
|
frame.dataset['runid'] = this.runId;
|
|
@@ -7245,92 +7250,10 @@ var pxsim;
|
|
|
7245
7250
|
.then(() => {
|
|
7246
7251
|
if (prevStop != instrStopId)
|
|
7247
7252
|
return Promise.resolve();
|
|
7248
|
-
return playInstructionsAsync(b);
|
|
7253
|
+
return playInstructionsAsync(b.data);
|
|
7249
7254
|
});
|
|
7250
7255
|
}
|
|
7251
7256
|
AudioContextManager.queuePlayInstructions = queuePlayInstructions;
|
|
7252
|
-
function playInstructionsAsync(b) {
|
|
7253
|
-
const prevStop = instrStopId;
|
|
7254
|
-
let ctx = context();
|
|
7255
|
-
let idx = 0;
|
|
7256
|
-
let ch = new Channel();
|
|
7257
|
-
let currWave = -1;
|
|
7258
|
-
let currFreq = -1;
|
|
7259
|
-
let timeOff = 0;
|
|
7260
|
-
if (channels.length > 5)
|
|
7261
|
-
channels[0].remove();
|
|
7262
|
-
channels.push(ch);
|
|
7263
|
-
/** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
|
|
7264
|
-
const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
|
|
7265
|
-
const finish = () => {
|
|
7266
|
-
ch.disconnectNodes();
|
|
7267
|
-
timeOff = 0;
|
|
7268
|
-
currWave = -1;
|
|
7269
|
-
currFreq = -1;
|
|
7270
|
-
};
|
|
7271
|
-
const loopAsync = () => {
|
|
7272
|
-
if (idx >= b.data.length || !b.data[idx])
|
|
7273
|
-
return pxsim.U.delay(timeOff).then(finish);
|
|
7274
|
-
const soundWaveIdx = b.data[idx];
|
|
7275
|
-
const freq = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 2);
|
|
7276
|
-
const duration = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 4);
|
|
7277
|
-
const startVol = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 6);
|
|
7278
|
-
const endVol = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 8);
|
|
7279
|
-
const endFreq = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 10);
|
|
7280
|
-
const isSquareWave = 11 <= soundWaveIdx && soundWaveIdx <= 15;
|
|
7281
|
-
const isFilteredNoise = soundWaveIdx == 4 || (16 <= soundWaveIdx && soundWaveIdx <= 18);
|
|
7282
|
-
const scaledStart = scaleVol(startVol, isSquareWave);
|
|
7283
|
-
const scaledEnd = scaleVol(endVol, isSquareWave);
|
|
7284
|
-
if (!ctx || prevStop != instrStopId)
|
|
7285
|
-
return pxsim.U.delay(duration);
|
|
7286
|
-
if (currWave != soundWaveIdx || currFreq != freq || freq != endFreq) {
|
|
7287
|
-
if (ch.generator) {
|
|
7288
|
-
return pxsim.U.delay(timeOff)
|
|
7289
|
-
.then(() => {
|
|
7290
|
-
finish();
|
|
7291
|
-
return loopAsync();
|
|
7292
|
-
});
|
|
7293
|
-
}
|
|
7294
|
-
ch.generator = getGenerator(soundWaveIdx, freq);
|
|
7295
|
-
if (!ch.generator)
|
|
7296
|
-
return pxsim.U.delay(duration);
|
|
7297
|
-
currWave = soundWaveIdx;
|
|
7298
|
-
currFreq = freq;
|
|
7299
|
-
ch.gain = ctx.createGain();
|
|
7300
|
-
ch.gain.gain.value = 0;
|
|
7301
|
-
ch.gain.gain.setTargetAtTime(scaledStart, _context.currentTime, 0.015);
|
|
7302
|
-
if (endFreq != freq) {
|
|
7303
|
-
if (ch.generator.frequency != undefined) {
|
|
7304
|
-
// If generator is an OscillatorNode
|
|
7305
|
-
const param = ch.generator.frequency;
|
|
7306
|
-
param.linearRampToValueAtTime(endFreq, ctx.currentTime + ((timeOff + duration) / 1000));
|
|
7307
|
-
}
|
|
7308
|
-
else if (ch.generator.playbackRate != undefined) {
|
|
7309
|
-
// If generator is an AudioBufferSourceNode
|
|
7310
|
-
const param = ch.generator.playbackRate;
|
|
7311
|
-
const bufferSamplesPerWave = isFilteredNoise ? 4 : 1024;
|
|
7312
|
-
param.linearRampToValueAtTime(endFreq / (context().sampleRate / bufferSamplesPerWave), ctx.currentTime + ((timeOff + duration) / 1000));
|
|
7313
|
-
}
|
|
7314
|
-
}
|
|
7315
|
-
ch.generator.connect(ch.gain);
|
|
7316
|
-
ch.gain.connect(destination);
|
|
7317
|
-
ch.generator.start();
|
|
7318
|
-
}
|
|
7319
|
-
idx += 12;
|
|
7320
|
-
ch.gain.gain.setValueAtTime(scaledStart, ctx.currentTime + (timeOff / 1000));
|
|
7321
|
-
timeOff += duration;
|
|
7322
|
-
// To prevent clipping, we ramp to this value slightly earlier than intended. This is so that we
|
|
7323
|
-
// can go for a smooth ramp to 0 in ch.mute() without this operation interrupting it. If we had
|
|
7324
|
-
// more accurate timing this would not be necessary, but we'd probably have to do something like
|
|
7325
|
-
// running a metronome in a webworker to get the level of precision we need
|
|
7326
|
-
const endTime = scaledEnd !== 0 && duration > 50 ? ((timeOff - 50) / 1000) : ((timeOff - 10) / 1000);
|
|
7327
|
-
ch.gain.gain.linearRampToValueAtTime(scaledEnd, ctx.currentTime + endTime);
|
|
7328
|
-
return loopAsync();
|
|
7329
|
-
};
|
|
7330
|
-
return loopAsync()
|
|
7331
|
-
.then(() => ch.remove());
|
|
7332
|
-
}
|
|
7333
|
-
AudioContextManager.playInstructionsAsync = playInstructionsAsync;
|
|
7334
7257
|
function tone(frequency, gain) {
|
|
7335
7258
|
if (frequency < 0)
|
|
7336
7259
|
return;
|
|
@@ -7469,6 +7392,126 @@ var pxsim;
|
|
|
7469
7392
|
function frequencyFromMidiNoteNumber(note) {
|
|
7470
7393
|
return 440 * Math.pow(2, (note - 69) / 12);
|
|
7471
7394
|
}
|
|
7395
|
+
function playInstructionsAsync(instructions, isCancelled, onPull) {
|
|
7396
|
+
return new Promise(async (resolve) => {
|
|
7397
|
+
let resolved = false;
|
|
7398
|
+
let ctx = context();
|
|
7399
|
+
let channel = new Channel();
|
|
7400
|
+
if (channels.length > 5)
|
|
7401
|
+
channels[0].remove();
|
|
7402
|
+
channels.push(channel);
|
|
7403
|
+
channel.gain = ctx.createGain();
|
|
7404
|
+
channel.gain.gain.value = 1;
|
|
7405
|
+
channel.gain.connect(destination);
|
|
7406
|
+
const oscillators = {};
|
|
7407
|
+
const gains = {};
|
|
7408
|
+
let startTime = ctx.currentTime;
|
|
7409
|
+
let currentTime = startTime;
|
|
7410
|
+
let currentWave = 0;
|
|
7411
|
+
let totalDuration = 0;
|
|
7412
|
+
/** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
|
|
7413
|
+
const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
|
|
7414
|
+
const disconnectNodes = () => {
|
|
7415
|
+
if (resolved)
|
|
7416
|
+
return;
|
|
7417
|
+
resolved = true;
|
|
7418
|
+
channel.disconnectNodes();
|
|
7419
|
+
for (const wave of Object.keys(oscillators)) {
|
|
7420
|
+
oscillators[wave].stop();
|
|
7421
|
+
oscillators[wave].disconnect();
|
|
7422
|
+
gains[wave].disconnect();
|
|
7423
|
+
}
|
|
7424
|
+
resolve();
|
|
7425
|
+
};
|
|
7426
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
7427
|
+
const wave = instructions[i];
|
|
7428
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
7429
|
+
const duration = readUint16(instructions, i + 4) / 1000;
|
|
7430
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
7431
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
7432
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
7433
|
+
totalDuration += duration;
|
|
7434
|
+
const isSquareWave = 11 <= wave && wave <= 15;
|
|
7435
|
+
if (!oscillators[wave]) {
|
|
7436
|
+
oscillators[wave] = getGenerator(wave, startFrequency);
|
|
7437
|
+
gains[wave] = ctx.createGain();
|
|
7438
|
+
gains[wave].gain.value = 0;
|
|
7439
|
+
gains[wave].connect(channel.gain);
|
|
7440
|
+
oscillators[wave].connect(gains[wave]);
|
|
7441
|
+
oscillators[wave].start();
|
|
7442
|
+
}
|
|
7443
|
+
if (currentWave && wave !== currentWave) {
|
|
7444
|
+
gains[currentWave].gain.setTargetAtTime(0, currentTime, 0.015);
|
|
7445
|
+
}
|
|
7446
|
+
const osc = oscillators[wave];
|
|
7447
|
+
const gain = gains[wave];
|
|
7448
|
+
if (osc instanceof OscillatorNode) {
|
|
7449
|
+
osc.frequency.setValueAtTime(startFrequency, currentTime);
|
|
7450
|
+
osc.frequency.linearRampToValueAtTime(endFrequency, currentTime + duration);
|
|
7451
|
+
}
|
|
7452
|
+
else {
|
|
7453
|
+
const isFilteredNoise = wave == 4 || (16 <= wave && wave <= 18);
|
|
7454
|
+
if (isFilteredNoise)
|
|
7455
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (ctx.sampleRate / 4), currentTime + duration);
|
|
7456
|
+
else if (wave != 5)
|
|
7457
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (ctx.sampleRate / 1024), currentTime + duration);
|
|
7458
|
+
}
|
|
7459
|
+
gain.gain.setValueAtTime(scaleVol(startVolume, isSquareWave), currentTime);
|
|
7460
|
+
gain.gain.linearRampToValueAtTime(scaleVol(endVolume, isSquareWave), currentTime + duration);
|
|
7461
|
+
currentWave = wave;
|
|
7462
|
+
currentTime += duration;
|
|
7463
|
+
}
|
|
7464
|
+
channel.gain.gain.setTargetAtTime(0, currentTime, 0.015);
|
|
7465
|
+
if (isCancelled || onPull) {
|
|
7466
|
+
const handleAnimationFrame = () => {
|
|
7467
|
+
const time = ctx.currentTime;
|
|
7468
|
+
if (time > startTime + totalDuration) {
|
|
7469
|
+
return;
|
|
7470
|
+
}
|
|
7471
|
+
if (isCancelled && isCancelled()) {
|
|
7472
|
+
disconnectNodes();
|
|
7473
|
+
return;
|
|
7474
|
+
}
|
|
7475
|
+
const { frequency, volume } = findFrequencyAndVolumeAtTime((time - startTime) * 1000, instructions);
|
|
7476
|
+
onPull(frequency, volume / 1024);
|
|
7477
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
7478
|
+
};
|
|
7479
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
7480
|
+
}
|
|
7481
|
+
await pxsim.U.delay(totalDuration * 1000);
|
|
7482
|
+
disconnectNodes();
|
|
7483
|
+
});
|
|
7484
|
+
}
|
|
7485
|
+
AudioContextManager.playInstructionsAsync = playInstructionsAsync;
|
|
7486
|
+
function readUint16(buf, offset) {
|
|
7487
|
+
const temp = new Uint8Array(2);
|
|
7488
|
+
temp[0] = buf[offset];
|
|
7489
|
+
temp[1] = buf[offset + 1];
|
|
7490
|
+
return new Uint16Array(temp.buffer)[0];
|
|
7491
|
+
}
|
|
7492
|
+
function findFrequencyAndVolumeAtTime(millis, instructions) {
|
|
7493
|
+
let currentTime = 0;
|
|
7494
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
7495
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
7496
|
+
const duration = readUint16(instructions, i + 4);
|
|
7497
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
7498
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
7499
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
7500
|
+
if (currentTime + duration < millis) {
|
|
7501
|
+
currentTime += duration;
|
|
7502
|
+
continue;
|
|
7503
|
+
}
|
|
7504
|
+
const offset = (millis - currentTime) / duration;
|
|
7505
|
+
return {
|
|
7506
|
+
frequency: startFrequency + (endFrequency - startFrequency) * offset,
|
|
7507
|
+
volume: startVolume + (endVolume - startVolume) * offset,
|
|
7508
|
+
};
|
|
7509
|
+
}
|
|
7510
|
+
return {
|
|
7511
|
+
frequency: -1,
|
|
7512
|
+
volume: -1
|
|
7513
|
+
};
|
|
7514
|
+
}
|
|
7472
7515
|
function sendMidiMessage(buf) {
|
|
7473
7516
|
const data = buf.data;
|
|
7474
7517
|
if (!data.length) // garbage.
|