pxt-core 11.4.25 → 11.4.27

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/cli.js CHANGED
@@ -1200,8 +1200,9 @@ function uploadCoreAsync(opts) {
1200
1200
  opts.fileList = U.values(U.toDictionary(opts.fileList, uploadFileName));
1201
1201
  // check size
1202
1202
  const maxSize = checkFileSize(opts.fileList);
1203
- if (maxSize > 30000000) // 30Mb max
1204
- U.userError(`file too big for upload`);
1203
+ const maxAllowedFileSize = (pxt.appTarget.cloud.maxFileSize || (30000000)); // default to 30Mb
1204
+ if (maxSize > maxAllowedFileSize)
1205
+ U.userError(`file too big for upload: ${maxSize} bytes, max is ${maxAllowedFileSize} bytes`);
1205
1206
  pxt.log('');
1206
1207
  if (opts.localDir)
1207
1208
  return U.promisePoolAsync(15, opts.fileList, uploadFileAsync)
@@ -5457,7 +5458,7 @@ function internalCheckDocsAsync(compileSnippets, re, fix, pycheck) {
5457
5458
  let snippets = [];
5458
5459
  const maxFileSize = checkFileSize(nodeutil.allFiles("docs", { maxDepth: 10, allowMissing: true, includeDirs: true, ignoredFileMarker: ".ignorelargefiles" }));
5459
5460
  if (!pxt.appTarget.ignoreDocsErrors
5460
- && maxFileSize > (pxt.appTarget.cloud.maxFileSize || (5000000)))
5461
+ && maxFileSize > (pxt.appTarget.cloud.maxFileSize || (30000000)))
5461
5462
  U.userError(`files too big in docs folder`);
5462
5463
  // scan and fix image links
5463
5464
  nodeutil.allFiles("docs", { ignoredFileMarker: ignoredFoldersKey })
package/built/pxt.js CHANGED
@@ -100797,6 +100797,26 @@ var ts;
100797
100797
  }
100798
100798
  }
100799
100799
  Util.bresenhamLine = bresenhamLine;
100800
+ /**
100801
+ * Check if the specified feature is enabled for the user (based on pxtarget configuration and region).
100802
+ */
100803
+ function isFeatureEnabled(featureKey) {
100804
+ var _a, _b;
100805
+ const feature = (_b = (_a = pxt.appTarget.appTheme) === null || _a === void 0 ? void 0 : _a.enabledFeatures) === null || _b === void 0 ? void 0 : _b[featureKey];
100806
+ if (!feature)
100807
+ return false;
100808
+ let enabled = true;
100809
+ const regionNormalised = pxt.Cloud.getRegion() ? pxt.Cloud.getRegion().toUpperCase() : undefined;
100810
+ if (feature.includeRegions) {
100811
+ enabled = regionNormalised && feature.includeRegions.some(r => r.toUpperCase() == regionNormalised);
100812
+ }
100813
+ // Include and exclude shouldn't really be used together, but if they are, exclude takes precedence
100814
+ if (enabled && feature.excludeRegions) {
100815
+ enabled = regionNormalised && !feature.excludeRegions.some(r => r.toUpperCase() == regionNormalised);
100816
+ }
100817
+ return enabled;
100818
+ }
100819
+ Util.isFeatureEnabled = isFeatureEnabled;
100800
100820
  })(Util = pxtc.Util || (pxtc.Util = {}));
100801
100821
  })(pxtc = ts.pxtc || (ts.pxtc = {}));
100802
100822
  })(ts || (ts = {}));
@@ -103969,6 +103989,7 @@ var pxt;
103969
103989
  const DEV_BACKEND_STAGING = "https://staging.pxt.io";
103970
103990
  const DEV_BACKEND_LOCALHOST = "http://localhost:8080";
103971
103991
  cloud.DEV_BACKEND = DEV_BACKEND_STAGING;
103992
+ cloud.DEV_REGION = "US";
103972
103993
  function devBackendType() {
103973
103994
  if (cloud.DEV_BACKEND === DEV_BACKEND_PROD)
103974
103995
  return "prod";
@@ -104043,6 +104064,7 @@ var pxt;
104043
104064
  WebUSBPairResult[WebUSBPairResult["Failed"] = 0] = "Failed";
104044
104065
  WebUSBPairResult[WebUSBPairResult["Success"] = 1] = "Success";
104045
104066
  WebUSBPairResult[WebUSBPairResult["UserRejected"] = 2] = "UserRejected";
104067
+ WebUSBPairResult[WebUSBPairResult["DownloadOnly"] = 3] = "DownloadOnly";
104046
104068
  })(WebUSBPairResult = commands.WebUSBPairResult || (commands.WebUSBPairResult = {}));
104047
104069
  commands.deployCoreAsync = undefined;
104048
104070
  commands.deployFallbackAsync = undefined;
@@ -123044,6 +123066,7 @@ var pxt;
123044
123066
  Cloud.localToken = "";
123045
123067
  let _isOnline = true;
123046
123068
  Cloud.onOffline = () => { };
123069
+ let region = undefined;
123047
123070
  function offlineError(url) {
123048
123071
  let e = new Error(Util.lf("Cannot access {0} while offline", url));
123049
123072
  e.isOffline = true;
@@ -123330,6 +123353,36 @@ var pxt;
123330
123353
  return undefined;
123331
123354
  }
123332
123355
  Cloud.parseScriptId = parseScriptId;
123356
+ async function initRegionAsync() {
123357
+ var _a;
123358
+ if (pxt.BrowserUtils.isLocalHost()) {
123359
+ region = pxt.cloud.DEV_REGION;
123360
+ return;
123361
+ }
123362
+ if (region !== undefined || !((_a = pxt.webConfig) === null || _a === void 0 ? void 0 : _a.cdnUrl)) {
123363
+ return;
123364
+ }
123365
+ const url = new URL("geo", pxt.webConfig.cdnUrl).toString();
123366
+ const options = { url };
123367
+ try {
123368
+ const response = await Util.requestAsync(options);
123369
+ if (response.statusCode !== 200) {
123370
+ pxt.error(`Failed to get region: ${response.statusCode}`);
123371
+ }
123372
+ region = response.text.trim();
123373
+ }
123374
+ catch (e) {
123375
+ handleNetworkError(options, e);
123376
+ }
123377
+ }
123378
+ Cloud.initRegionAsync = initRegionAsync;
123379
+ function getRegion() {
123380
+ if (!region) {
123381
+ pxt.error("Accessing region before it is initialized. Call initRegionAsync first.");
123382
+ }
123383
+ return region;
123384
+ }
123385
+ Cloud.getRegion = getRegion;
123333
123386
  })(Cloud = pxt.Cloud || (pxt.Cloud = {}));
123334
123387
  })(pxt || (pxt = {}));
123335
123388
  var ts;
@@ -154379,6 +154432,7 @@ var pxsim;
154379
154432
  let isFloat = fmt >= NumberFormat.Float32LE;
154380
154433
  return { size, signed, swap, isFloat };
154381
154434
  }
154435
+ BufferMethods.fmtInfo = fmtInfo;
154382
154436
  function getNumber(buf, fmt, offset) {
154383
154437
  typeCheck(buf);
154384
154438
  let inf = fmtInfo(fmt);
@@ -157929,6 +157983,11 @@ var pxsim;
157929
157983
  return node;
157930
157984
  }
157931
157985
  class Channel {
157986
+ constructor() {
157987
+ this.gain = context().createGain();
157988
+ this.gain.connect(destination);
157989
+ this.gain.gain.value = 0;
157990
+ }
157932
157991
  disconnectNodes() {
157933
157992
  if (this.gain)
157934
157993
  disconnectVca(this.gain, this.generator);
@@ -158032,14 +158091,8 @@ var pxsim;
158032
158091
  let nodes = [];
158033
158092
  let nextTime = context().currentTime;
158034
158093
  let allScheduled = false;
158035
- const channel = new Channel();
158036
- channel.gain = context().createGain();
158037
- channel.gain.gain.value = 0;
158094
+ const channel = getChannel();
158038
158095
  channel.gain.gain.setValueAtTime(volume, context().currentTime);
158039
- channel.gain.connect(destination);
158040
- if (channels.length > 20)
158041
- channels[0].remove();
158042
- channels.push(channel);
158043
158096
  const checkCancel = () => {
158044
158097
  if (isCancelled && isCancelled() || !channel.gain) {
158045
158098
  if (resolve)
@@ -158107,13 +158160,8 @@ var pxsim;
158107
158160
  AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
158108
158161
  let resolved = false;
158109
158162
  let ctx = context();
158110
- let channel = new Channel();
158111
- if (channels.length > 20)
158112
- channels[0].remove();
158113
- channels.push(channel);
158114
- channel.gain = ctx.createGain();
158163
+ let channel = getChannel();
158115
158164
  channel.gain.gain.value = 1;
158116
- channel.gain.connect(destination);
158117
158165
  const oscillators = {};
158118
158166
  const gains = {};
158119
158167
  let startTime = ctx.currentTime;
@@ -158253,6 +158301,62 @@ var pxsim;
158253
158301
  }
158254
158302
  }
158255
158303
  AudioContextManager.sendMidiMessage = sendMidiMessage;
158304
+ function startSamplePlayback(sample, format, sampleRange, sampleRate, gain) {
158305
+ let channel;
158306
+ let _resolve;
158307
+ const cancel = () => {
158308
+ if (!channel)
158309
+ return;
158310
+ channel.remove();
158311
+ channel = undefined;
158312
+ _resolve();
158313
+ };
158314
+ const promise = new Promise(resolve => {
158315
+ _resolve = resolve;
158316
+ let playbackRate = 1;
158317
+ // chrome errors out if the sample rate is outside [3000, 768000]
158318
+ if (sampleRate < 3000) {
158319
+ playbackRate = sampleRate / 3000;
158320
+ sampleRate = 3000;
158321
+ }
158322
+ else if (sampleRate > 768000) {
158323
+ playbackRate = sampleRate / 768000;
158324
+ sampleRate = 768000;
158325
+ }
158326
+ const size = pxsim.BufferMethods.fmtInfo(format).size;
158327
+ const buf = context().createBuffer(1, sample.data.length / size, sampleRate);
158328
+ const data = buf.getChannelData(0);
158329
+ for (let i = 0; i < buf.length; i++) {
158330
+ data[i] = (pxsim.BufferMethods.getNumber(sample, format, i * size) / sampleRange) * 2 - 1;
158331
+ }
158332
+ channel = getChannel();
158333
+ const node = context().createBufferSource();
158334
+ ;
158335
+ node.playbackRate.value = playbackRate;
158336
+ channel.gain.gain.value = gain;
158337
+ channel.generator = node;
158338
+ channel.generator.buffer = buf;
158339
+ channel.generator.connect(channel.gain);
158340
+ channel.generator.start(0);
158341
+ channel.generator.addEventListener("ended", () => {
158342
+ channel.remove();
158343
+ channel = undefined;
158344
+ resolve();
158345
+ });
158346
+ });
158347
+ return {
158348
+ promise,
158349
+ cancel
158350
+ };
158351
+ }
158352
+ AudioContextManager.startSamplePlayback = startSamplePlayback;
158353
+ function getChannel() {
158354
+ if (channels.length > 20)
158355
+ channels[0].remove();
158356
+ const channel = new Channel();
158357
+ channels.push(channel);
158358
+ return channel;
158359
+ }
158256
158360
  })(AudioContextManager = pxsim.AudioContextManager || (pxsim.AudioContextManager = {}));
158257
158361
  function isTouchEnabled() {
158258
158362
  return typeof window !== "undefined" &&
@@ -163165,8 +163269,9 @@ function uploadCoreAsync(opts) {
163165
163269
  opts.fileList = U.values(U.toDictionary(opts.fileList, uploadFileName));
163166
163270
  // check size
163167
163271
  const maxSize = checkFileSize(opts.fileList);
163168
- if (maxSize > 30000000) // 30Mb max
163169
- U.userError(`file too big for upload`);
163272
+ const maxAllowedFileSize = (pxt.appTarget.cloud.maxFileSize || (30000000)); // default to 30Mb
163273
+ if (maxSize > maxAllowedFileSize)
163274
+ U.userError(`file too big for upload: ${maxSize} bytes, max is ${maxAllowedFileSize} bytes`);
163170
163275
  pxt.log('');
163171
163276
  if (opts.localDir)
163172
163277
  return U.promisePoolAsync(15, opts.fileList, uploadFileAsync)
@@ -167422,7 +167527,7 @@ function internalCheckDocsAsync(compileSnippets, re, fix, pycheck) {
167422
167527
  let snippets = [];
167423
167528
  const maxFileSize = checkFileSize(nodeutil.allFiles("docs", { maxDepth: 10, allowMissing: true, includeDirs: true, ignoredFileMarker: ".ignorelargefiles" }));
167424
167529
  if (!pxt.appTarget.ignoreDocsErrors
167425
- && maxFileSize > (pxt.appTarget.cloud.maxFileSize || (5000000)))
167530
+ && maxFileSize > (pxt.appTarget.cloud.maxFileSize || (30000000)))
167426
167531
  U.userError(`files too big in docs folder`);
167427
167532
  // scan and fix image links
167428
167533
  nodeutil.allFiles("docs", { ignoredFileMarker: ignoredFoldersKey })
@@ -38,6 +38,7 @@ export declare abstract class Bubble implements Blockly.IDeletable, Blockly.IBub
38
38
  /** The position of the left of the bubble realtive to its anchor. */
39
39
  private relativeLeft;
40
40
  private dragStrategy;
41
+ private focusableElement;
41
42
  private topBar;
42
43
  protected deleteIcon: SVGImageElement;
43
44
  private collapseIcon;
@@ -51,7 +52,7 @@ export declare abstract class Bubble implements Blockly.IDeletable, Blockly.IBub
51
52
  * @param ownerRect An optional rect we don't want the bubble to overlap with
52
53
  * when automatically positioning.
53
54
  */
54
- constructor(workspace: Blockly.WorkspaceSvg, anchor: Blockly.utils.Coordinate, ownerRect?: Blockly.utils.Rect);
55
+ constructor(workspace: Blockly.WorkspaceSvg, anchor: Blockly.utils.Coordinate, ownerRect?: Blockly.utils.Rect, overriddenFocusableElement?: SVGElement | HTMLElement);
55
56
  /** Dispose of this bubble. */
56
57
  dispose(): void;
57
58
  /**
@@ -55,7 +55,8 @@ export declare class TextInputBubble extends Bubble {
55
55
  /** Adds a change listener to be notified when this bubble's size changes. */
56
56
  addSizeChangeListener(listener: () => void): void;
57
57
  addPositionChangeListener(listener: () => void): void;
58
- /** Creates the editor UI for this bubble. */
58
+ private static createTextArea;
59
+ /** Creates and returns the UI container element for this bubble's editor. */
59
60
  private createEditor;
60
61
  /** Binds events to the text area element. */
61
62
  private bindTextAreaEvents;
@@ -79,10 +80,6 @@ export declare class TextInputBubble extends Bubble {
79
80
  private onResizePointerUp;
80
81
  /** Handles pointer move events on the resize target. */
81
82
  private onResizePointerMove;
82
- /**
83
- * Handles starting an edit of the text area. Brings the bubble to the front.
84
- */
85
- private onStartEdit;
86
83
  /** Handles a text change event for the text area. Calls event listeners. */
87
84
  private onTextChange;
88
85
  /** Handles a size change event for the text area. Calls event listeners. */
package/built/pxtlib.d.ts CHANGED
@@ -496,6 +496,10 @@ declare namespace ts.pxtc.Util {
496
496
  export function isExperienceSupported(experienceId: string): boolean;
497
497
  export function ocvEnabled(): string;
498
498
  export function bresenhamLine(x0: number, y0: number, x1: number, y1: number, handler: (x: number, y: number) => void): void;
499
+ /**
500
+ * Check if the specified feature is enabled for the user (based on pxtarget configuration and region).
501
+ */
502
+ export function isFeatureEnabled(featureKey: string): boolean;
499
503
  export {};
500
504
  }
501
505
  declare namespace ts.pxtc.BrowserImpl {
@@ -992,6 +996,7 @@ declare namespace pxt.cloud {
992
996
  const DEV_BACKEND_LOCALHOST = "http://localhost:8080";
993
997
  type BackendUrls = typeof DEV_BACKEND_PROD | typeof DEV_BACKEND_STAGING | typeof DEV_BACKEND_LOCALHOST;
994
998
  export const DEV_BACKEND: BackendUrls;
999
+ export const DEV_REGION = "US";
995
1000
  export function devBackendType(): DevBackendType;
996
1001
  export type CloudStatus = "none" | "synced" | "justSynced" | "offline" | "syncing" | "conflict" | "localEdits";
997
1002
  export type CloudStatusInfo = {
@@ -1011,7 +1016,8 @@ declare namespace pxt.commands {
1011
1016
  enum WebUSBPairResult {
1012
1017
  Failed = 0,
1013
1018
  Success = 1,
1014
- UserRejected = 2
1019
+ UserRejected = 2,
1020
+ DownloadOnly = 3
1015
1021
  }
1016
1022
  interface RecompileOptions {
1017
1023
  recompile: boolean;
@@ -2486,7 +2492,8 @@ declare namespace ts.pxtc.service {
2486
2492
  }
2487
2493
  interface ExtensionMeta {
2488
2494
  name: string;
2489
- fullName?: string;
2495
+ displayName?: string;
2496
+ fullRepo?: string;
2490
2497
  description?: string;
2491
2498
  imageUrl?: string;
2492
2499
  type?: ExtensionType;
@@ -3890,6 +3897,8 @@ declare namespace pxt.Cloud {
3890
3897
  function getServiceUrl(): string;
3891
3898
  function getUserId(): string;
3892
3899
  function parseScriptId(uri: string): string;
3900
+ function initRegionAsync(): Promise<void>;
3901
+ function getRegion(): string;
3893
3902
  interface JsonIdObject {
3894
3903
  kind: string;
3895
3904
  id: string;
package/built/pxtlib.js CHANGED
@@ -3111,6 +3111,26 @@ var ts;
3111
3111
  }
3112
3112
  }
3113
3113
  Util.bresenhamLine = bresenhamLine;
3114
+ /**
3115
+ * Check if the specified feature is enabled for the user (based on pxtarget configuration and region).
3116
+ */
3117
+ function isFeatureEnabled(featureKey) {
3118
+ var _a, _b;
3119
+ const feature = (_b = (_a = pxt.appTarget.appTheme) === null || _a === void 0 ? void 0 : _a.enabledFeatures) === null || _b === void 0 ? void 0 : _b[featureKey];
3120
+ if (!feature)
3121
+ return false;
3122
+ let enabled = true;
3123
+ const regionNormalised = pxt.Cloud.getRegion() ? pxt.Cloud.getRegion().toUpperCase() : undefined;
3124
+ if (feature.includeRegions) {
3125
+ enabled = regionNormalised && feature.includeRegions.some(r => r.toUpperCase() == regionNormalised);
3126
+ }
3127
+ // Include and exclude shouldn't really be used together, but if they are, exclude takes precedence
3128
+ if (enabled && feature.excludeRegions) {
3129
+ enabled = regionNormalised && !feature.excludeRegions.some(r => r.toUpperCase() == regionNormalised);
3130
+ }
3131
+ return enabled;
3132
+ }
3133
+ Util.isFeatureEnabled = isFeatureEnabled;
3114
3134
  })(Util = pxtc.Util || (pxtc.Util = {}));
3115
3135
  })(pxtc = ts.pxtc || (ts.pxtc = {}));
3116
3136
  })(ts || (ts = {}));
@@ -6283,6 +6303,7 @@ var pxt;
6283
6303
  const DEV_BACKEND_STAGING = "https://staging.pxt.io";
6284
6304
  const DEV_BACKEND_LOCALHOST = "http://localhost:8080";
6285
6305
  cloud.DEV_BACKEND = DEV_BACKEND_STAGING;
6306
+ cloud.DEV_REGION = "US";
6286
6307
  function devBackendType() {
6287
6308
  if (cloud.DEV_BACKEND === DEV_BACKEND_PROD)
6288
6309
  return "prod";
@@ -6357,6 +6378,7 @@ var pxt;
6357
6378
  WebUSBPairResult[WebUSBPairResult["Failed"] = 0] = "Failed";
6358
6379
  WebUSBPairResult[WebUSBPairResult["Success"] = 1] = "Success";
6359
6380
  WebUSBPairResult[WebUSBPairResult["UserRejected"] = 2] = "UserRejected";
6381
+ WebUSBPairResult[WebUSBPairResult["DownloadOnly"] = 3] = "DownloadOnly";
6360
6382
  })(WebUSBPairResult = commands.WebUSBPairResult || (commands.WebUSBPairResult = {}));
6361
6383
  commands.deployCoreAsync = undefined;
6362
6384
  commands.deployFallbackAsync = undefined;
@@ -25358,6 +25380,7 @@ var pxt;
25358
25380
  Cloud.localToken = "";
25359
25381
  let _isOnline = true;
25360
25382
  Cloud.onOffline = () => { };
25383
+ let region = undefined;
25361
25384
  function offlineError(url) {
25362
25385
  let e = new Error(Util.lf("Cannot access {0} while offline", url));
25363
25386
  e.isOffline = true;
@@ -25644,6 +25667,36 @@ var pxt;
25644
25667
  return undefined;
25645
25668
  }
25646
25669
  Cloud.parseScriptId = parseScriptId;
25670
+ async function initRegionAsync() {
25671
+ var _a;
25672
+ if (pxt.BrowserUtils.isLocalHost()) {
25673
+ region = pxt.cloud.DEV_REGION;
25674
+ return;
25675
+ }
25676
+ if (region !== undefined || !((_a = pxt.webConfig) === null || _a === void 0 ? void 0 : _a.cdnUrl)) {
25677
+ return;
25678
+ }
25679
+ const url = new URL("geo", pxt.webConfig.cdnUrl).toString();
25680
+ const options = { url };
25681
+ try {
25682
+ const response = await Util.requestAsync(options);
25683
+ if (response.statusCode !== 200) {
25684
+ pxt.error(`Failed to get region: ${response.statusCode}`);
25685
+ }
25686
+ region = response.text.trim();
25687
+ }
25688
+ catch (e) {
25689
+ handleNetworkError(options, e);
25690
+ }
25691
+ }
25692
+ Cloud.initRegionAsync = initRegionAsync;
25693
+ function getRegion() {
25694
+ if (!region) {
25695
+ pxt.error("Accessing region before it is initialized. Call initRegionAsync first.");
25696
+ }
25697
+ return region;
25698
+ }
25699
+ Cloud.getRegion = getRegion;
25647
25700
  })(Cloud = pxt.Cloud || (pxt.Cloud = {}));
25648
25701
  })(pxt || (pxt = {}));
25649
25702
  var ts;
package/built/pxtsim.d.ts CHANGED
@@ -1016,6 +1016,12 @@ declare namespace pxsim {
1016
1016
  Float32BE = 15,
1017
1017
  Float64BE = 16
1018
1018
  }
1019
+ function fmtInfo(fmt: NumberFormat): {
1020
+ size: number;
1021
+ signed: boolean;
1022
+ swap: boolean;
1023
+ isFloat: boolean;
1024
+ };
1019
1025
  function getNumber(buf: RefBuffer, fmt: NumberFormat, offset: number): number;
1020
1026
  function setNumber(buf: RefBuffer, fmt: NumberFormat, offset: number, r: number): void;
1021
1027
  function createBuffer(size: number): RefBuffer;
@@ -1595,6 +1601,11 @@ declare namespace pxsim {
1595
1601
  function playPCMBufferStreamAsync(pull: () => Float32Array, sampleRate: number, volume?: number, isCancelled?: () => boolean): Promise<void>;
1596
1602
  function playInstructionsAsync(instructions: Uint8Array, isCancelled?: () => boolean, onPull?: (freq: number, volume: number) => void): Promise<void>;
1597
1603
  function sendMidiMessage(buf: RefBuffer): void;
1604
+ interface PlaySampleResult {
1605
+ promise: Promise<void>;
1606
+ cancel: () => void;
1607
+ }
1608
+ function startSamplePlayback(sample: RefBuffer, format: BufferMethods.NumberFormat, sampleRange: number, sampleRate: number, gain: number): PlaySampleResult;
1598
1609
  }
1599
1610
  interface IPointerEvents {
1600
1611
  up: string;
package/built/pxtsim.js CHANGED
@@ -4234,6 +4234,7 @@ var pxsim;
4234
4234
  let isFloat = fmt >= NumberFormat.Float32LE;
4235
4235
  return { size, signed, swap, isFloat };
4236
4236
  }
4237
+ BufferMethods.fmtInfo = fmtInfo;
4237
4238
  function getNumber(buf, fmt, offset) {
4238
4239
  typeCheck(buf);
4239
4240
  let inf = fmtInfo(fmt);
@@ -7784,6 +7785,11 @@ var pxsim;
7784
7785
  return node;
7785
7786
  }
7786
7787
  class Channel {
7788
+ constructor() {
7789
+ this.gain = context().createGain();
7790
+ this.gain.connect(destination);
7791
+ this.gain.gain.value = 0;
7792
+ }
7787
7793
  disconnectNodes() {
7788
7794
  if (this.gain)
7789
7795
  disconnectVca(this.gain, this.generator);
@@ -7887,14 +7893,8 @@ var pxsim;
7887
7893
  let nodes = [];
7888
7894
  let nextTime = context().currentTime;
7889
7895
  let allScheduled = false;
7890
- const channel = new Channel();
7891
- channel.gain = context().createGain();
7892
- channel.gain.gain.value = 0;
7896
+ const channel = getChannel();
7893
7897
  channel.gain.gain.setValueAtTime(volume, context().currentTime);
7894
- channel.gain.connect(destination);
7895
- if (channels.length > 20)
7896
- channels[0].remove();
7897
- channels.push(channel);
7898
7898
  const checkCancel = () => {
7899
7899
  if (isCancelled && isCancelled() || !channel.gain) {
7900
7900
  if (resolve)
@@ -7962,13 +7962,8 @@ var pxsim;
7962
7962
  AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
7963
7963
  let resolved = false;
7964
7964
  let ctx = context();
7965
- let channel = new Channel();
7966
- if (channels.length > 20)
7967
- channels[0].remove();
7968
- channels.push(channel);
7969
- channel.gain = ctx.createGain();
7965
+ let channel = getChannel();
7970
7966
  channel.gain.gain.value = 1;
7971
- channel.gain.connect(destination);
7972
7967
  const oscillators = {};
7973
7968
  const gains = {};
7974
7969
  let startTime = ctx.currentTime;
@@ -8108,6 +8103,62 @@ var pxsim;
8108
8103
  }
8109
8104
  }
8110
8105
  AudioContextManager.sendMidiMessage = sendMidiMessage;
8106
+ function startSamplePlayback(sample, format, sampleRange, sampleRate, gain) {
8107
+ let channel;
8108
+ let _resolve;
8109
+ const cancel = () => {
8110
+ if (!channel)
8111
+ return;
8112
+ channel.remove();
8113
+ channel = undefined;
8114
+ _resolve();
8115
+ };
8116
+ const promise = new Promise(resolve => {
8117
+ _resolve = resolve;
8118
+ let playbackRate = 1;
8119
+ // chrome errors out if the sample rate is outside [3000, 768000]
8120
+ if (sampleRate < 3000) {
8121
+ playbackRate = sampleRate / 3000;
8122
+ sampleRate = 3000;
8123
+ }
8124
+ else if (sampleRate > 768000) {
8125
+ playbackRate = sampleRate / 768000;
8126
+ sampleRate = 768000;
8127
+ }
8128
+ const size = pxsim.BufferMethods.fmtInfo(format).size;
8129
+ const buf = context().createBuffer(1, sample.data.length / size, sampleRate);
8130
+ const data = buf.getChannelData(0);
8131
+ for (let i = 0; i < buf.length; i++) {
8132
+ data[i] = (pxsim.BufferMethods.getNumber(sample, format, i * size) / sampleRange) * 2 - 1;
8133
+ }
8134
+ channel = getChannel();
8135
+ const node = context().createBufferSource();
8136
+ ;
8137
+ node.playbackRate.value = playbackRate;
8138
+ channel.gain.gain.value = gain;
8139
+ channel.generator = node;
8140
+ channel.generator.buffer = buf;
8141
+ channel.generator.connect(channel.gain);
8142
+ channel.generator.start(0);
8143
+ channel.generator.addEventListener("ended", () => {
8144
+ channel.remove();
8145
+ channel = undefined;
8146
+ resolve();
8147
+ });
8148
+ });
8149
+ return {
8150
+ promise,
8151
+ cancel
8152
+ };
8153
+ }
8154
+ AudioContextManager.startSamplePlayback = startSamplePlayback;
8155
+ function getChannel() {
8156
+ if (channels.length > 20)
8157
+ channels[0].remove();
8158
+ const channel = new Channel();
8159
+ channels.push(channel);
8160
+ return channel;
8161
+ }
8111
8162
  })(AudioContextManager = pxsim.AudioContextManager || (pxsim.AudioContextManager = {}));
8112
8163
  function isTouchEnabled() {
8113
8164
  return typeof window !== "undefined" &&