pxt-core 8.2.2 → 8.2.5

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/pxtlib.js CHANGED
@@ -8713,14 +8713,32 @@ var pxt;
8713
8713
  docs.prepTemplate = prepTemplate;
8714
8714
  function setupRenderer(renderer) {
8715
8715
  renderer.image = function (href, title, text) {
8716
- const endpointName = "makecode-lucas-testing-makecodetempmediaservice-usea";
8716
+ var _a, _b;
8717
+ const endpointName = "makecodeprodmediaeastus-usea";
8717
8718
  if (href.startsWith("youtube:")) {
8718
- let out = '<div class="tutorial-video-embed"><iframe src="https://www.youtube.com/embed/' + href.split(":").pop()
8719
- + '" title="' + title + '" frameborder="0" ' + 'allowFullScreen ' + 'allow="autoplay; picture-in-picture"></iframe></div>';
8719
+ let out = '<div class="tutorial-video-embed"><iframe class="yt-embed" src="https://www.youtube.com/embed/' + href.split(":").pop()
8720
+ + '" title="' + text + '" frameborder="0" ' + 'allowFullScreen ' + 'allow="autoplay; picture-in-picture"></iframe></div>';
8720
8721
  return out;
8721
8722
  }
8722
8723
  else if (href.startsWith("azuremedia:")) {
8723
- let out = `<div class="tutorial-video-embed"><video class="ams-embed" controls src="https://${endpointName}.streaming.media.azure.net/` + href.split(":").pop() + '/manifest(format=mpd-time-cmaf)" /></div>';
8724
+ let videoID = href.split(":")[1];
8725
+ const flagsSplit = videoID.split("?");
8726
+ let startTime;
8727
+ let endTime;
8728
+ if (flagsSplit[1]) {
8729
+ videoID = flagsSplit[0];
8730
+ const passedParameters = flagsSplit[1];
8731
+ startTime = (_a = /start(?:time)?=(\d+)/i.exec(passedParameters)) === null || _a === void 0 ? void 0 : _a[1];
8732
+ endTime = (_b = /end(?:time)?=(\d+)/i.exec(passedParameters)) === null || _b === void 0 ? void 0 : _b[1];
8733
+ }
8734
+ const url = new URL(`https://${endpointName}.streaming.media.azure.net/${videoID}/manifest(format=mpd-time-csf).mpd`);
8735
+ if (startTime) {
8736
+ url.hash = `t=${startTime}`;
8737
+ }
8738
+ if (endTime) {
8739
+ url.searchParams.append("endTime", endTime);
8740
+ }
8741
+ let out = `<div class="tutorial-video-embed"><video class="ams-embed" controls src="${url.toString()}" /></div>`;
8724
8742
  return out;
8725
8743
  }
8726
8744
  else {
@@ -10500,14 +10518,17 @@ var pxt;
10500
10518
  return false;
10501
10519
  }
10502
10520
  function isRepoBanned(repo, config) {
10521
+ var _a, _b;
10503
10522
  if (isOrgBanned(repo, config))
10504
10523
  return true;
10505
10524
  if (!config)
10506
10525
  return false; // don't know
10507
- if (!repo || !repo.fullName)
10526
+ if (!repo)
10508
10527
  return true;
10528
+ const repoFull = (_a = repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
10529
+ const repoSlug = (_b = repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
10509
10530
  if (config.bannedRepos
10510
- && config.bannedRepos.some(fn => fn.toLowerCase() == repo.fullName.toLowerCase()))
10531
+ && config.bannedRepos.some(fn => fn && (fn.toLowerCase() == repoFull || fn.toLowerCase() == repoSlug)))
10511
10532
  return true;
10512
10533
  return false;
10513
10534
  }
@@ -10521,13 +10542,15 @@ var pxt;
10521
10542
  return false;
10522
10543
  }
10523
10544
  function isRepoApproved(repo, config) {
10524
- var _a;
10545
+ var _a, _b;
10525
10546
  if (isOrgApproved(repo, config))
10526
10547
  return true;
10527
- if (!repo || !config)
10548
+ const repoFull = (_a = repo === null || repo === void 0 ? void 0 : repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
10549
+ const repoSlug = (_b = repo === null || repo === void 0 ? void 0 : repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
10550
+ if (!(config === null || config === void 0 ? void 0 : config.approvedRepoLib) || !(repoFull || repoSlug))
10528
10551
  return false;
10529
- if (repo.fullName
10530
- && ((_a = config.approvedRepoLib) === null || _a === void 0 ? void 0 : _a[repo.fullName.toLowerCase()]))
10552
+ if (config.approvedRepoLib[repoFull]
10553
+ || config.approvedRepoLib[repoSlug])
10531
10554
  return true;
10532
10555
  return false;
10533
10556
  }
@@ -14270,7 +14293,7 @@ var pxt;
14270
14293
  // and pulls from master
14271
14294
  const modtag = (modid === null || modid === void 0 ? void 0 : modid.tag) || ((_a = mod.config) === null || _a === void 0 ? void 0 : _a.version);
14272
14295
  const vertag = verid.tag;
14273
- // if there is no tag on the current dependency,
14296
+ // if there is no tag on the current dependency,
14274
14297
  // assume same as existing module version if any
14275
14298
  if (modtag && !vertag) {
14276
14299
  pxt.debug(`unversioned ${ver}, using ${modtag}`);
@@ -14729,6 +14752,8 @@ var pxt;
14729
14752
  opts.otherMultiVariants.push(etarget);
14730
14753
  }
14731
14754
  else {
14755
+ etarget.target.isNative = opts.target.isNative;
14756
+ opts.target = etarget.target;
14732
14757
  ext = einfo;
14733
14758
  opts.otherMultiVariants = [];
14734
14759
  }
@@ -15684,7 +15709,12 @@ var ts;
15684
15709
  const left = param.substr(0, dotIdx);
15685
15710
  let right = param.substr(dotIdx + 1);
15686
15711
  right = pxtc.U.snakify(right).toUpperCase();
15687
- return `${left}.${right}`;
15712
+ if (left) {
15713
+ return `${left}.${right}`;
15714
+ }
15715
+ else {
15716
+ return right;
15717
+ }
15688
15718
  }
15689
15719
  return param;
15690
15720
  }
@@ -16905,6 +16935,97 @@ var pxt;
16905
16935
  return outParts.join(" ");
16906
16936
  }
16907
16937
  }
16938
+ function soundToInstructionBuffer(sound, fxSteps, fxRange) {
16939
+ const { startFrequency, endFrequency, startVolume, endVolume, interpolation, duration } = sound;
16940
+ const steps = [];
16941
+ // Optimize the simple case
16942
+ if (sound.interpolation === "linear" && sound.effect === "none") {
16943
+ steps.push({
16944
+ frequency: startFrequency,
16945
+ volume: (startVolume / assets.MAX_VOLUME) * 1024,
16946
+ });
16947
+ steps.push({
16948
+ frequency: endFrequency,
16949
+ volume: (endVolume / assets.MAX_VOLUME) * 1024,
16950
+ });
16951
+ }
16952
+ else {
16953
+ fxSteps = Math.min(fxSteps, Math.floor(duration / 5));
16954
+ const getVolumeAt = (t) => ((startVolume + t * (endVolume - startVolume) / duration) / assets.MAX_VOLUME) * 1024;
16955
+ let getFrequencyAt;
16956
+ switch (interpolation) {
16957
+ case "linear":
16958
+ getFrequencyAt = t => startFrequency + t * (endFrequency - startFrequency) / duration;
16959
+ break;
16960
+ case "curve":
16961
+ getFrequencyAt = t => startFrequency + (endFrequency - startFrequency) * Math.sin(t / duration * (Math.PI / 2));
16962
+ break;
16963
+ case "logarithmic":
16964
+ getFrequencyAt = t => startFrequency + Math.log10(1 + 9 * (t / duration)) * (endFrequency - startFrequency);
16965
+ break;
16966
+ }
16967
+ const timeSlice = duration / fxSteps;
16968
+ for (let i = 0; i < fxSteps; i++) {
16969
+ const newStep = {
16970
+ frequency: Math.max(getFrequencyAt(i * timeSlice), 1),
16971
+ volume: getVolumeAt(i * timeSlice)
16972
+ };
16973
+ if (sound.effect === "tremolo") {
16974
+ if (i % 2 === 0) {
16975
+ newStep.volume = Math.max(newStep.volume - fxRange * 500, 0);
16976
+ }
16977
+ else {
16978
+ newStep.volume = Math.min(newStep.volume + fxRange * 500, 1023);
16979
+ }
16980
+ }
16981
+ else if (sound.effect === "vibrato") {
16982
+ if (i % 2 === 0) {
16983
+ newStep.frequency = Math.max(newStep.frequency - fxRange * 100, 1);
16984
+ }
16985
+ else {
16986
+ newStep.frequency = newStep.frequency + fxRange * 100;
16987
+ }
16988
+ }
16989
+ else if (sound.effect === "warble") {
16990
+ if (i % 2 === 0) {
16991
+ newStep.frequency = Math.max(newStep.frequency - fxRange * 1000, 1);
16992
+ }
16993
+ else {
16994
+ newStep.frequency = newStep.frequency + fxRange * 1000;
16995
+ }
16996
+ }
16997
+ steps.push(newStep);
16998
+ }
16999
+ }
17000
+ const out = new Uint8Array(12 * (steps.length - 1));
17001
+ const stepDuration = Math.floor(duration / (steps.length - 1));
17002
+ for (let i = 0; i < steps.length - 1; i++) {
17003
+ const offset = i * 12;
17004
+ out[offset] = waveToValue(sound.wave);
17005
+ set16BitNumber(out, offset + 2, steps[i].frequency);
17006
+ set16BitNumber(out, offset + 4, stepDuration);
17007
+ set16BitNumber(out, offset + 6, steps[i].volume);
17008
+ set16BitNumber(out, offset + 8, steps[i + 1].volume);
17009
+ set16BitNumber(out, offset + 10, steps[i + 1].frequency);
17010
+ }
17011
+ return out;
17012
+ }
17013
+ assets.soundToInstructionBuffer = soundToInstructionBuffer;
17014
+ function waveToValue(wave) {
17015
+ switch (wave) {
17016
+ case "square": return 15;
17017
+ case "sine": return 3;
17018
+ case "triangle": return 1;
17019
+ case "noise": return 18;
17020
+ case "sawtooth": return 2;
17021
+ }
17022
+ }
17023
+ function set16BitNumber(buf, offset, value) {
17024
+ const temp = new Uint8Array(2);
17025
+ new Uint16Array(temp.buffer)[0] = value | 0;
17026
+ buf[offset] = temp[0];
17027
+ buf[offset + 1] = temp[1];
17028
+ }
16908
17029
  })(assets = pxt.assets || (pxt.assets = {}));
16909
17030
  })(pxt || (pxt = {}));
16910
17031
  // See https://github.com/microsoft/TouchDevelop-backend/blob/master/docs/streams.md
@@ -21507,6 +21628,18 @@ var ts;
21507
21628
  }
21508
21629
  }
21509
21630
  assembler.Line = Line;
21631
+ const MAX_OBJ_USERS = 5;
21632
+ class AsmObject {
21633
+ constructor(id, description) {
21634
+ this.id = id;
21635
+ this.description = description;
21636
+ this.sizeAdj = 0;
21637
+ this.users = [];
21638
+ }
21639
+ get size() {
21640
+ return (this.endLocation - this.startLocation) - this.sizeAdj;
21641
+ }
21642
+ }
21510
21643
  // File is the center of the action: parsing a file into a sequence of Lines
21511
21644
  // and also emitting the binary (buf)
21512
21645
  class File {
@@ -21531,6 +21664,11 @@ var ts;
21531
21664
  this.throwOnError = false;
21532
21665
  this.disablePeepHole = false;
21533
21666
  this.stackAtLabel = {};
21667
+ this.codeSizeStats = false;
21668
+ this.labelToObject = {};
21669
+ this.idToObject = {};
21670
+ this.objSuspendStart = 0;
21671
+ this.labelsToObjectDone = false;
21534
21672
  this.currLine = new Line(this, "<start>");
21535
21673
  this.currLine.lineNo = 0;
21536
21674
  this.ei = ei;
@@ -21550,6 +21688,15 @@ var ts;
21550
21688
  pc() {
21551
21689
  return this.location() + this.baseOffset;
21552
21690
  }
21691
+ useLabel(name) {
21692
+ if (!this.currObject || name[0] == '.' || this.objSuspendStart)
21693
+ return;
21694
+ const obj = pxtc.U.lookup(this.labelToObject, name);
21695
+ if (!obj || obj == this.currObject)
21696
+ return;
21697
+ if (obj.users.length < MAX_OBJ_USERS && obj.users.indexOf(this.currObject) < 0)
21698
+ obj.users.push(this.currObject);
21699
+ }
21553
21700
  // parsing of an "integer", well actually much more than
21554
21701
  // just that
21555
21702
  parseOneInt(s) {
@@ -21679,6 +21826,7 @@ var ts;
21679
21826
  return name;
21680
21827
  }
21681
21828
  lookupLabel(name, direct = false) {
21829
+ this.useLabel(name);
21682
21830
  let v = null;
21683
21831
  let scoped = this.scopedName(name);
21684
21832
  if (this.labels.hasOwnProperty(scoped)) {
@@ -21837,6 +21985,37 @@ var ts;
21837
21985
  };
21838
21986
  let num0;
21839
21987
  switch (words[0]) {
21988
+ case ".object":
21989
+ if (!this.codeSizeStats) {
21990
+ // do nothing
21991
+ }
21992
+ else if (words[1] == "PUSH") {
21993
+ this.objSuspendStart = this.location();
21994
+ }
21995
+ else if (words[1] == "POP") {
21996
+ if (this.objSuspendStart)
21997
+ this.currObject.sizeAdj += this.location() - this.objSuspendStart;
21998
+ this.objSuspendStart = 0;
21999
+ }
22000
+ else {
22001
+ if (this.currObject)
22002
+ this.currObject.endLocation = this.location();
22003
+ this.currObject = pxtc.U.lookup(this.idToObject, words[1]);
22004
+ if (!this.currObject) {
22005
+ const str = l.text.replace(/^[^"]*/, "");
22006
+ let parsed = words[1];
22007
+ if (words.length > 2) {
22008
+ parsed = parseString(str.trim());
22009
+ if (parsed == null)
22010
+ this.directiveError(lf("expecting string in .object"));
22011
+ }
22012
+ this.currObject = new AsmObject(words[1], parsed);
22013
+ this.idToObject[words[1]] = this.currObject;
22014
+ }
22015
+ this.currObject.sizeAdj = 0;
22016
+ this.currObject.startLocation = this.location();
22017
+ }
22018
+ break;
21840
22019
  case ".ascii":
21841
22020
  case ".asciz":
21842
22021
  case ".string":
@@ -22031,6 +22210,8 @@ var ts;
22031
22210
  }
22032
22211
  }
22033
22212
  handleOneInstruction(ln, instr) {
22213
+ if (this.codeSizeStats && ln.ldlitLabel)
22214
+ this.useLabel(ln.ldlitLabel);
22034
22215
  let op = instr.emit(ln);
22035
22216
  if (!op.error) {
22036
22217
  this.stack += op.stack;
@@ -22139,6 +22320,8 @@ var ts;
22139
22320
  if (l.words.length == 0)
22140
22321
  return;
22141
22322
  if (l.type == "label") {
22323
+ if (this.currObject && !this.labelsToObjectDone && l.words[0][0] != '.')
22324
+ this.labelToObject[l.words[0]] = this.currObject;
22142
22325
  let lblname = this.scopedName(l.words[0]);
22143
22326
  this.prevLabel = lblname;
22144
22327
  if (this.finalEmit) {
@@ -22179,6 +22362,8 @@ var ts;
22179
22362
  pxtc.oops();
22180
22363
  }
22181
22364
  });
22365
+ this.labelsToObjectDone = true;
22366
+ this.currObject = null;
22182
22367
  }
22183
22368
  getSourceMap() {
22184
22369
  const sourceMap = {};
@@ -22211,6 +22396,22 @@ var ts;
22211
22396
  }
22212
22397
  return sourceMap;
22213
22398
  }
22399
+ getCodeSizeStats() {
22400
+ if (!this.codeSizeStats)
22401
+ return "";
22402
+ const objs = pxtc.U.values(this.idToObject);
22403
+ objs.sort((a, b) => b.size - a.size);
22404
+ let r = ";\n; Code size:\n;\n";
22405
+ for (const obj of objs) {
22406
+ r += `; ${(" " + obj.size).slice(-6)} ${obj.description} [${obj.id}]\n`;
22407
+ if (obj.users.length >= MAX_OBJ_USERS)
22408
+ r += `; by many, including ${obj.users[0].description}\n`;
22409
+ else
22410
+ for (const x of obj.users)
22411
+ r += `; by ${x.description} [${x.id}]\n`;
22412
+ }
22413
+ return r;
22414
+ }
22214
22415
  getSource(clean, numStmts = 1, flashSize = 0) {
22215
22416
  let lenPrev = 0;
22216
22417
  let size = (lbl) => {
@@ -22226,8 +22427,11 @@ var ts;
22226
22427
  let lenLiterals = size("_literals_end");
22227
22428
  let lenAllCode = lenPrev;
22228
22429
  let totalSize = (lenTotal + this.baseOffset) & 0xffffff;
22229
- if (flashSize && totalSize > flashSize)
22230
- pxtc.U.userError(lf("program too big by {0} bytes!", totalSize - flashSize));
22430
+ if (flashSize && totalSize > flashSize) {
22431
+ const e = new Error(lf("program too big by {0} bytes!", totalSize - flashSize));
22432
+ e.ksErrorCode = 9283;
22433
+ throw e;
22434
+ }
22231
22435
  flashSize = flashSize || 128 * 1024;
22232
22436
  let totalInfo = lf("; total bytes: {0} ({1}% of {2}k flash with {3} free)", totalSize, (100 * totalSize / flashSize).toFixed(1), (flashSize / 1024).toFixed(1), flashSize - totalSize);
22233
22437
  let res =
@@ -22235,7 +22439,7 @@ var ts;
22235
22439
  lf("; generated code sizes (bytes): {0} (incl. {1} user, {2} helpers, {3} vtables, {4} lits); src size {5}\n", lenAllCode, lenCode, lenHelpers, lenVtables, lenLiterals, lenTotal - lenAllCode) +
22236
22440
  lf("; assembly: {0} lines; density: {1} bytes/stmt; ({2} stmts)\n", this.lines.length, Math.round(100 * lenCode / numStmts) / 100, numStmts) +
22237
22441
  totalInfo + "\n" +
22238
- this.stats + "\n\n";
22442
+ this.stats + this.getCodeSizeStats() + "\n\n";
22239
22443
  let skipOne = false;
22240
22444
  this.lines.forEach((ln, i) => {
22241
22445
  if (ln.words[0] == "_stored_program") {
package/built/pxtsim.d.ts CHANGED
@@ -1424,11 +1424,11 @@ declare namespace pxsim {
1424
1424
  function frequency(): number;
1425
1425
  function muteAllChannels(): void;
1426
1426
  function queuePlayInstructions(when: number, b: RefBuffer): void;
1427
- function playInstructionsAsync(b: RefBuffer): Promise<void>;
1428
1427
  function tone(frequency: number, gain: number): void;
1429
1428
  function setCurrentToneGain(gain: number): void;
1430
1429
  function playBufferAsync(buf: RefBuffer): Promise<void>;
1431
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>;
1432
1432
  function sendMidiMessage(buf: RefBuffer): void;
1433
1433
  }
1434
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');
@@ -6306,7 +6306,7 @@ var pxsim;
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 (this._runOptions.hideSimButtons) {
6309
+ if ((_a = this._runOptions) === null || _a === void 0 ? void 0 : _a.hideSimButtons) {
6310
6310
  const urlObject = new URL(furl);
6311
6311
  urlObject.searchParams.append("hideSimButtons", "1");
6312
6312
  furl = urlObject.toString();
@@ -6316,7 +6316,7 @@ var pxsim;
6316
6316
  frame.frameBorder = "0";
6317
6317
  frame.dataset['runid'] = this.runId;
6318
6318
  frame.dataset['origin'] = new URL(furl).origin || "*";
6319
- if ((_a = this._runOptions) === null || _a === void 0 ? void 0 : _a.autofocus)
6319
+ if ((_b = this._runOptions) === null || _b === void 0 ? void 0 : _b.autofocus)
6320
6320
  frame.setAttribute("autofocus", "true");
6321
6321
  wrapper.appendChild(frame);
6322
6322
  const i = document.createElement("i");
@@ -7250,92 +7250,10 @@ var pxsim;
7250
7250
  .then(() => {
7251
7251
  if (prevStop != instrStopId)
7252
7252
  return Promise.resolve();
7253
- return playInstructionsAsync(b);
7253
+ return playInstructionsAsync(b.data);
7254
7254
  });
7255
7255
  }
7256
7256
  AudioContextManager.queuePlayInstructions = queuePlayInstructions;
7257
- function playInstructionsAsync(b) {
7258
- const prevStop = instrStopId;
7259
- let ctx = context();
7260
- let idx = 0;
7261
- let ch = new Channel();
7262
- let currWave = -1;
7263
- let currFreq = -1;
7264
- let timeOff = 0;
7265
- if (channels.length > 5)
7266
- channels[0].remove();
7267
- channels.push(ch);
7268
- /** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
7269
- const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
7270
- const finish = () => {
7271
- ch.disconnectNodes();
7272
- timeOff = 0;
7273
- currWave = -1;
7274
- currFreq = -1;
7275
- };
7276
- const loopAsync = () => {
7277
- if (idx >= b.data.length || !b.data[idx])
7278
- return pxsim.U.delay(timeOff).then(finish);
7279
- const soundWaveIdx = b.data[idx];
7280
- const freq = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 2);
7281
- const duration = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 4);
7282
- const startVol = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 6);
7283
- const endVol = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 8);
7284
- const endFreq = pxsim.BufferMethods.getNumber(b, pxsim.BufferMethods.NumberFormat.UInt16LE, idx + 10);
7285
- const isSquareWave = 11 <= soundWaveIdx && soundWaveIdx <= 15;
7286
- const isFilteredNoise = soundWaveIdx == 4 || (16 <= soundWaveIdx && soundWaveIdx <= 18);
7287
- const scaledStart = scaleVol(startVol, isSquareWave);
7288
- const scaledEnd = scaleVol(endVol, isSquareWave);
7289
- if (!ctx || prevStop != instrStopId)
7290
- return pxsim.U.delay(duration);
7291
- if (currWave != soundWaveIdx || currFreq != freq || freq != endFreq) {
7292
- if (ch.generator) {
7293
- return pxsim.U.delay(timeOff)
7294
- .then(() => {
7295
- finish();
7296
- return loopAsync();
7297
- });
7298
- }
7299
- ch.generator = getGenerator(soundWaveIdx, freq);
7300
- if (!ch.generator)
7301
- return pxsim.U.delay(duration);
7302
- currWave = soundWaveIdx;
7303
- currFreq = freq;
7304
- ch.gain = ctx.createGain();
7305
- ch.gain.gain.value = 0;
7306
- ch.gain.gain.setTargetAtTime(scaledStart, _context.currentTime, 0.015);
7307
- if (endFreq != freq) {
7308
- if (ch.generator.frequency != undefined) {
7309
- // If generator is an OscillatorNode
7310
- const param = ch.generator.frequency;
7311
- param.linearRampToValueAtTime(endFreq, ctx.currentTime + ((timeOff + duration) / 1000));
7312
- }
7313
- else if (ch.generator.playbackRate != undefined) {
7314
- // If generator is an AudioBufferSourceNode
7315
- const param = ch.generator.playbackRate;
7316
- const bufferSamplesPerWave = isFilteredNoise ? 4 : 1024;
7317
- param.linearRampToValueAtTime(endFreq / (context().sampleRate / bufferSamplesPerWave), ctx.currentTime + ((timeOff + duration) / 1000));
7318
- }
7319
- }
7320
- ch.generator.connect(ch.gain);
7321
- ch.gain.connect(destination);
7322
- ch.generator.start();
7323
- }
7324
- idx += 12;
7325
- ch.gain.gain.setValueAtTime(scaledStart, ctx.currentTime + (timeOff / 1000));
7326
- timeOff += duration;
7327
- // To prevent clipping, we ramp to this value slightly earlier than intended. This is so that we
7328
- // can go for a smooth ramp to 0 in ch.mute() without this operation interrupting it. If we had
7329
- // more accurate timing this would not be necessary, but we'd probably have to do something like
7330
- // running a metronome in a webworker to get the level of precision we need
7331
- const endTime = scaledEnd !== 0 && duration > 50 ? ((timeOff - 50) / 1000) : ((timeOff - 10) / 1000);
7332
- ch.gain.gain.linearRampToValueAtTime(scaledEnd, ctx.currentTime + endTime);
7333
- return loopAsync();
7334
- };
7335
- return loopAsync()
7336
- .then(() => ch.remove());
7337
- }
7338
- AudioContextManager.playInstructionsAsync = playInstructionsAsync;
7339
7257
  function tone(frequency, gain) {
7340
7258
  if (frequency < 0)
7341
7259
  return;
@@ -7474,6 +7392,126 @@ var pxsim;
7474
7392
  function frequencyFromMidiNoteNumber(note) {
7475
7393
  return 440 * Math.pow(2, (note - 69) / 12);
7476
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
+ }
7477
7515
  function sendMidiMessage(buf) {
7478
7516
  const data = buf.data;
7479
7517
  if (!data.length) // garbage.