pxt-core 7.5.14 → 7.5.15

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/pxt.js CHANGED
@@ -153626,9 +153626,19 @@ var pxsim;
153626
153626
  let _mute = false; //mute audio
153627
153627
  // for playing WAV
153628
153628
  let audio;
153629
+ const channels = [];
153630
+ // All other nodes get connected to this node which is connected to the actual
153631
+ // destination. Used for muting
153632
+ let destination;
153629
153633
  function context() {
153630
- if (!_context)
153634
+ if (!_context) {
153631
153635
  _context = freshContext();
153636
+ if (_context) {
153637
+ destination = _context.createGain();
153638
+ destination.connect(_context.destination);
153639
+ destination.gain.setValueAtTime(1, 0);
153640
+ }
153641
+ }
153632
153642
  return _context;
153633
153643
  }
153634
153644
  function freshContext() {
@@ -153645,8 +153655,13 @@ var pxsim;
153645
153655
  }
153646
153656
  function mute(mute) {
153647
153657
  _mute = mute;
153648
- stopAll();
153649
153658
  const ctx = context();
153659
+ if (mute) {
153660
+ destination.gain.setTargetAtTime(0, ctx.currentTime, 0.015);
153661
+ }
153662
+ else {
153663
+ destination.gain.setTargetAtTime(1, ctx.currentTime, 0.015);
153664
+ }
153650
153665
  if (!mute && ctx && ctx.state === "suspended")
153651
153666
  ctx.resume();
153652
153667
  }
@@ -153840,9 +153855,8 @@ var pxsim;
153840
153855
  node.playbackRate.value = hz / (context().sampleRate / 1024);
153841
153856
  return node;
153842
153857
  }
153843
- const channels = [];
153844
153858
  class Channel {
153845
- mute() {
153859
+ disconnectNodes() {
153846
153860
  if (this.gain)
153847
153861
  disconnectVca(this.gain, this.generator);
153848
153862
  else if (this.generator) {
@@ -153856,7 +153870,7 @@ var pxsim;
153856
153870
  const idx = channels.indexOf(this);
153857
153871
  if (idx >= 0)
153858
153872
  channels.splice(idx, 1);
153859
- this.mute();
153873
+ this.disconnectNodes();
153860
153874
  }
153861
153875
  }
153862
153876
  let instrStopId = 1;
@@ -153890,7 +153904,7 @@ var pxsim;
153890
153904
  /** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
153891
153905
  const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
153892
153906
  const finish = () => {
153893
- ch.mute();
153907
+ ch.disconnectNodes();
153894
153908
  timeOff = 0;
153895
153909
  currWave = -1;
153896
153910
  currFreq = -1;
@@ -153918,7 +153932,7 @@ var pxsim;
153918
153932
  return loopAsync();
153919
153933
  });
153920
153934
  }
153921
- ch.generator = _mute ? null : getGenerator(soundWaveIdx, freq);
153935
+ ch.generator = getGenerator(soundWaveIdx, freq);
153922
153936
  if (!ch.generator)
153923
153937
  return pxsim.U.delay(duration);
153924
153938
  currWave = soundWaveIdx;
@@ -153940,7 +153954,7 @@ var pxsim;
153940
153954
  }
153941
153955
  }
153942
153956
  ch.generator.connect(ch.gain);
153943
- ch.gain.connect(ctx.destination);
153957
+ ch.gain.connect(destination);
153944
153958
  ch.generator.start();
153945
153959
  }
153946
153960
  idx += 12;
@@ -153973,7 +153987,7 @@ var pxsim;
153973
153987
  _vca.gain.value = 0;
153974
153988
  _vco.type = 'triangle';
153975
153989
  _vco.connect(_vca);
153976
- _vca.connect(ctx.destination);
153990
+ _vca.connect(destination);
153977
153991
  _vco.start(0);
153978
153992
  }
153979
153993
  setCurrentToneGain(gain);
@@ -153989,7 +154003,7 @@ var pxsim;
153989
154003
  AudioContextManager.tone = tone;
153990
154004
  function setCurrentToneGain(gain) {
153991
154005
  if (_vca === null || _vca === void 0 ? void 0 : _vca.gain) {
153992
- _vca.gain.setTargetAtTime(_mute ? 0 : gain, _context.currentTime, 0.015);
154006
+ _vca.gain.setTargetAtTime(gain, _context.currentTime, 0.015);
153993
154007
  }
153994
154008
  }
153995
154009
  AudioContextManager.setCurrentToneGain = setCurrentToneGain;
@@ -154026,13 +154040,30 @@ var pxsim;
154026
154040
  let nodes = [];
154027
154041
  let nextTime = context().currentTime;
154028
154042
  let allScheduled = false;
154043
+ const channel = new Channel();
154044
+ channel.gain = context().createGain();
154045
+ channel.gain.gain.value = 0;
154046
+ channel.gain.gain.setValueAtTime(volume, context().currentTime);
154047
+ channel.gain.connect(destination);
154048
+ if (channels.length > 5)
154049
+ channels[0].remove();
154050
+ channels.push(channel);
154051
+ const checkCancel = () => {
154052
+ if (isCancelled && isCancelled() || !channel.gain) {
154053
+ if (resolve)
154054
+ resolve();
154055
+ resolve = undefined;
154056
+ channel.remove();
154057
+ return true;
154058
+ }
154059
+ return false;
154060
+ };
154029
154061
  // Every time we pull a buffer, schedule a node in the future to play it.
154030
154062
  // Scheduling the nodes ahead of time sounds much smoother than trying to
154031
154063
  // do it when the previous node completes (which sounds SUPER choppy in
154032
154064
  // FireFox).
154033
154065
  function playNext() {
154034
- const cancelled = isCancelled && isCancelled();
154035
- while (!allScheduled && nodes.length < MAX_SCHEDULED_BUFFER_NODES && !cancelled) {
154066
+ while (!allScheduled && nodes.length < MAX_SCHEDULED_BUFFER_NODES && !checkCancel()) {
154036
154067
  const data = pull();
154037
154068
  if (!data || !data.length) {
154038
154069
  allScheduled = true;
@@ -154048,6 +154079,8 @@ var pxsim;
154048
154079
  }
154049
154080
  }
154050
154081
  function play(data) {
154082
+ if (checkCancel())
154083
+ return;
154051
154084
  const buff = context().createBuffer(1, data.length, sampleRate);
154052
154085
  if (buff.copyToChannel) {
154053
154086
  buff.copyToChannel(data, 0);
@@ -154070,15 +154103,6 @@ var pxsim;
154070
154103
  newNode.start(nextTime);
154071
154104
  nextTime += buff.duration;
154072
154105
  }
154073
- const channel = new Channel();
154074
- channel.gain = context().createGain();
154075
- channel.gain.gain.value = 0;
154076
- if (!_mute)
154077
- channel.gain.gain.setValueAtTime(volume, context().currentTime);
154078
- channel.gain.connect(context().destination);
154079
- if (channels.length > 5)
154080
- channels[0].remove();
154081
- channels.push(channel);
154082
154106
  playNext();
154083
154107
  });
154084
154108
  }
@@ -155103,18 +155127,62 @@ var pxsim;
155103
155127
  result = "0" + result;
155104
155128
  return result;
155105
155129
  }
155106
- let soundPromise;
155130
+ let playing = false;
155131
+ let soundQueue;
155132
+ let cancellationToken = {
155133
+ cancelled: false
155134
+ };
155107
155135
  function __playSoundExpression(notes, waitTillDone) {
155136
+ if (!soundQueue)
155137
+ soundQueue = [];
155108
155138
  const cb = pxsim.getResume();
155109
- if (!soundPromise)
155110
- soundPromise = Promise.resolve();
155111
- soundPromise = soundPromise.then(() => playSoundExpressionAsync(notes));
155139
+ const soundPromise = new Promise((resolve, reject) => {
155140
+ soundQueue.push({
155141
+ notes,
155142
+ onFinished: resolve,
155143
+ onCancelled: reject
155144
+ });
155145
+ });
155146
+ if (!playing) {
155147
+ playNextSoundAsync();
155148
+ }
155112
155149
  if (!waitTillDone)
155113
155150
  cb();
155114
155151
  else
155115
- soundPromise = soundPromise.then(cb);
155152
+ soundPromise.then(cb);
155116
155153
  }
155117
155154
  music.__playSoundExpression = __playSoundExpression;
155155
+ async function playNextSoundAsync() {
155156
+ if (soundQueue.length) {
155157
+ playing = true;
155158
+ const sound = soundQueue.shift();
155159
+ let currentToken = cancellationToken;
155160
+ try {
155161
+ await playSoundExpressionAsync(sound.notes, () => currentToken.cancelled);
155162
+ if (currentToken.cancelled) {
155163
+ sound.onCancelled();
155164
+ }
155165
+ else {
155166
+ sound.onFinished();
155167
+ }
155168
+ }
155169
+ catch (_a) {
155170
+ sound.onCancelled();
155171
+ }
155172
+ playNextSoundAsync();
155173
+ }
155174
+ else {
155175
+ playing = false;
155176
+ }
155177
+ }
155178
+ function clearSoundQueue() {
155179
+ soundQueue = [];
155180
+ cancellationToken.cancelled = true;
155181
+ cancellationToken = {
155182
+ cancelled: false
155183
+ };
155184
+ }
155185
+ music.clearSoundQueue = clearSoundQueue;
155118
155186
  function playSoundExpressionAsync(notes, isCancelled, onPull) {
155119
155187
  const synth = new music.SoundEmojiSynthesizer(0);
155120
155188
  const soundEffects = parseSoundEffects(notes);
@@ -155146,6 +155214,7 @@ var pxsim;
155146
155214
  }
155147
155215
  music.playSoundExpressionAsync = playSoundExpressionAsync;
155148
155216
  function __stopSoundExpressions() {
155217
+ clearSoundQueue();
155149
155218
  pxsim.AudioContextManager.stopAll();
155150
155219
  }
155151
155220
  music.__stopSoundExpressions = __stopSoundExpressions;
@@ -7828,6 +7828,7 @@ var pxt;
7828
7828
  return;
7829
7829
  }
7830
7830
  }
7831
+ const hasInput = (name) => { var _a; return (_a = block.inputList) === null || _a === void 0 ? void 0 : _a.some(i => i.name === name); };
7831
7832
  inputs.forEach(inputParts => {
7832
7833
  const fields = [];
7833
7834
  let inputName;
@@ -7976,12 +7977,19 @@ var pxt;
7976
7977
  });
7977
7978
  let input;
7978
7979
  if (inputName) {
7980
+ // Don't add duplicate inputs
7981
+ if (hasInput(inputName))
7982
+ return;
7979
7983
  input = block.appendValueInput(inputName);
7980
7984
  input.setAlign(Blockly.ALIGN_LEFT);
7981
7985
  }
7982
7986
  else if (expanded) {
7983
7987
  const prefix = hasParameter ? blocks_4.optionalInputWithFieldPrefix : blocks_4.optionalDummyInputPrefix;
7984
- input = block.appendDummyInput(prefix + (anonIndex++));
7988
+ inputName = prefix + (anonIndex++);
7989
+ // Don't add duplicate inputs
7990
+ if (hasInput(inputName))
7991
+ return;
7992
+ input = block.appendDummyInput(inputName);
7985
7993
  }
7986
7994
  else {
7987
7995
  input = block.appendDummyInput();
@@ -15149,6 +15157,7 @@ var pxtblockly;
15149
15157
  class FieldSoundEffect extends pxtblockly.FieldBase {
15150
15158
  constructor() {
15151
15159
  super(...arguments);
15160
+ this.registeredChangeListener = false;
15152
15161
  this.onWorkspaceChange = (ev) => {
15153
15162
  if (ev.type !== Blockly.Events.CHANGE)
15154
15163
  return;
@@ -15178,10 +15187,19 @@ var pxtblockly;
15178
15187
  if (!this.options.effectFieldName)
15179
15188
  this.options.effectFieldName = "effect";
15180
15189
  this.redrawPreview();
15181
- this.sourceBlock_.workspace.addChangeListener(this.onWorkspaceChange);
15190
+ if (this.sourceBlock_.workspace) {
15191
+ this.workspace = this.sourceBlock_.workspace;
15192
+ if (!this.sourceBlock_.isShadow() && !this.sourceBlock_.isInsertionMarker()) {
15193
+ this.registeredChangeListener = true;
15194
+ this.workspace.addChangeListener(this.onWorkspaceChange);
15195
+ }
15196
+ }
15182
15197
  }
15183
15198
  onDispose() {
15184
- this.sourceBlock_.workspace.removeChangeListener(this.onWorkspaceChange);
15199
+ if (this.workspace && this.registeredChangeListener) {
15200
+ this.workspace.removeChangeListener(this.onWorkspaceChange);
15201
+ this.registeredChangeListener = false;
15202
+ }
15185
15203
  }
15186
15204
  onValueChanged(newValue) {
15187
15205
  return newValue;
@@ -15347,7 +15365,7 @@ var pxtblockly;
15347
15365
  render_() {
15348
15366
  super.render_();
15349
15367
  this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
15350
- this.size_.width = TOTAL_WIDTH;
15368
+ this.size_.width = TOTAL_WIDTH + X_PADDING;
15351
15369
  }
15352
15370
  updateSiblingBlocks(sound) {
15353
15371
  this.setNumberInputValue(this.options.durationInputName, sound.duration);
@@ -1145,6 +1145,8 @@ declare namespace pxtblockly {
1145
1145
  class FieldSoundEffect extends FieldBase<FieldSoundEffectParams> {
1146
1146
  protected mostRecentValue: pxt.assets.Sound;
1147
1147
  protected drawnSound: pxt.assets.Sound;
1148
+ protected workspace: Blockly.Workspace;
1149
+ protected registeredChangeListener: boolean;
1148
1150
  protected onInit(): void;
1149
1151
  protected onDispose(): void;
1150
1152
  protected onValueChanged(newValue: string): string;
@@ -4266,6 +4266,7 @@ var pxt;
4266
4266
  return;
4267
4267
  }
4268
4268
  }
4269
+ const hasInput = (name) => { var _a; return (_a = block.inputList) === null || _a === void 0 ? void 0 : _a.some(i => i.name === name); };
4269
4270
  inputs.forEach(inputParts => {
4270
4271
  const fields = [];
4271
4272
  let inputName;
@@ -4414,12 +4415,19 @@ var pxt;
4414
4415
  });
4415
4416
  let input;
4416
4417
  if (inputName) {
4418
+ // Don't add duplicate inputs
4419
+ if (hasInput(inputName))
4420
+ return;
4417
4421
  input = block.appendValueInput(inputName);
4418
4422
  input.setAlign(Blockly.ALIGN_LEFT);
4419
4423
  }
4420
4424
  else if (expanded) {
4421
4425
  const prefix = hasParameter ? blocks_4.optionalInputWithFieldPrefix : blocks_4.optionalDummyInputPrefix;
4422
- input = block.appendDummyInput(prefix + (anonIndex++));
4426
+ inputName = prefix + (anonIndex++);
4427
+ // Don't add duplicate inputs
4428
+ if (hasInput(inputName))
4429
+ return;
4430
+ input = block.appendDummyInput(inputName);
4423
4431
  }
4424
4432
  else {
4425
4433
  input = block.appendDummyInput();
@@ -11587,6 +11595,7 @@ var pxtblockly;
11587
11595
  class FieldSoundEffect extends pxtblockly.FieldBase {
11588
11596
  constructor() {
11589
11597
  super(...arguments);
11598
+ this.registeredChangeListener = false;
11590
11599
  this.onWorkspaceChange = (ev) => {
11591
11600
  if (ev.type !== Blockly.Events.CHANGE)
11592
11601
  return;
@@ -11616,10 +11625,19 @@ var pxtblockly;
11616
11625
  if (!this.options.effectFieldName)
11617
11626
  this.options.effectFieldName = "effect";
11618
11627
  this.redrawPreview();
11619
- this.sourceBlock_.workspace.addChangeListener(this.onWorkspaceChange);
11628
+ if (this.sourceBlock_.workspace) {
11629
+ this.workspace = this.sourceBlock_.workspace;
11630
+ if (!this.sourceBlock_.isShadow() && !this.sourceBlock_.isInsertionMarker()) {
11631
+ this.registeredChangeListener = true;
11632
+ this.workspace.addChangeListener(this.onWorkspaceChange);
11633
+ }
11634
+ }
11620
11635
  }
11621
11636
  onDispose() {
11622
- this.sourceBlock_.workspace.removeChangeListener(this.onWorkspaceChange);
11637
+ if (this.workspace && this.registeredChangeListener) {
11638
+ this.workspace.removeChangeListener(this.onWorkspaceChange);
11639
+ this.registeredChangeListener = false;
11640
+ }
11623
11641
  }
11624
11642
  onValueChanged(newValue) {
11625
11643
  return newValue;
@@ -11785,7 +11803,7 @@ var pxtblockly;
11785
11803
  render_() {
11786
11804
  super.render_();
11787
11805
  this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
11788
- this.size_.width = TOTAL_WIDTH;
11806
+ this.size_.width = TOTAL_WIDTH + X_PADDING;
11789
11807
  }
11790
11808
  updateSiblingBlocks(sound) {
11791
11809
  this.setNumberInputValue(this.options.durationInputName, sound.duration);
package/built/pxtsim.d.ts CHANGED
@@ -1728,6 +1728,7 @@ declare namespace pxsim.codal.music {
1728
1728
  protected getValue(offset: number, length: number): number;
1729
1729
  }
1730
1730
  function __playSoundExpression(notes: string, waitTillDone: boolean): void;
1731
+ function clearSoundQueue(): void;
1731
1732
  function playSoundExpressionAsync(notes: string, isCancelled?: () => boolean, onPull?: (freq: number, volume: number) => void): Promise<void>;
1732
1733
  function __stopSoundExpressions(): void;
1733
1734
  interface TonePrint {
package/built/pxtsim.js CHANGED
@@ -6973,9 +6973,19 @@ var pxsim;
6973
6973
  let _mute = false; //mute audio
6974
6974
  // for playing WAV
6975
6975
  let audio;
6976
+ const channels = [];
6977
+ // All other nodes get connected to this node which is connected to the actual
6978
+ // destination. Used for muting
6979
+ let destination;
6976
6980
  function context() {
6977
- if (!_context)
6981
+ if (!_context) {
6978
6982
  _context = freshContext();
6983
+ if (_context) {
6984
+ destination = _context.createGain();
6985
+ destination.connect(_context.destination);
6986
+ destination.gain.setValueAtTime(1, 0);
6987
+ }
6988
+ }
6979
6989
  return _context;
6980
6990
  }
6981
6991
  function freshContext() {
@@ -6992,8 +7002,13 @@ var pxsim;
6992
7002
  }
6993
7003
  function mute(mute) {
6994
7004
  _mute = mute;
6995
- stopAll();
6996
7005
  const ctx = context();
7006
+ if (mute) {
7007
+ destination.gain.setTargetAtTime(0, ctx.currentTime, 0.015);
7008
+ }
7009
+ else {
7010
+ destination.gain.setTargetAtTime(1, ctx.currentTime, 0.015);
7011
+ }
6997
7012
  if (!mute && ctx && ctx.state === "suspended")
6998
7013
  ctx.resume();
6999
7014
  }
@@ -7187,9 +7202,8 @@ var pxsim;
7187
7202
  node.playbackRate.value = hz / (context().sampleRate / 1024);
7188
7203
  return node;
7189
7204
  }
7190
- const channels = [];
7191
7205
  class Channel {
7192
- mute() {
7206
+ disconnectNodes() {
7193
7207
  if (this.gain)
7194
7208
  disconnectVca(this.gain, this.generator);
7195
7209
  else if (this.generator) {
@@ -7203,7 +7217,7 @@ var pxsim;
7203
7217
  const idx = channels.indexOf(this);
7204
7218
  if (idx >= 0)
7205
7219
  channels.splice(idx, 1);
7206
- this.mute();
7220
+ this.disconnectNodes();
7207
7221
  }
7208
7222
  }
7209
7223
  let instrStopId = 1;
@@ -7237,7 +7251,7 @@ var pxsim;
7237
7251
  /** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
7238
7252
  const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
7239
7253
  const finish = () => {
7240
- ch.mute();
7254
+ ch.disconnectNodes();
7241
7255
  timeOff = 0;
7242
7256
  currWave = -1;
7243
7257
  currFreq = -1;
@@ -7265,7 +7279,7 @@ var pxsim;
7265
7279
  return loopAsync();
7266
7280
  });
7267
7281
  }
7268
- ch.generator = _mute ? null : getGenerator(soundWaveIdx, freq);
7282
+ ch.generator = getGenerator(soundWaveIdx, freq);
7269
7283
  if (!ch.generator)
7270
7284
  return pxsim.U.delay(duration);
7271
7285
  currWave = soundWaveIdx;
@@ -7287,7 +7301,7 @@ var pxsim;
7287
7301
  }
7288
7302
  }
7289
7303
  ch.generator.connect(ch.gain);
7290
- ch.gain.connect(ctx.destination);
7304
+ ch.gain.connect(destination);
7291
7305
  ch.generator.start();
7292
7306
  }
7293
7307
  idx += 12;
@@ -7320,7 +7334,7 @@ var pxsim;
7320
7334
  _vca.gain.value = 0;
7321
7335
  _vco.type = 'triangle';
7322
7336
  _vco.connect(_vca);
7323
- _vca.connect(ctx.destination);
7337
+ _vca.connect(destination);
7324
7338
  _vco.start(0);
7325
7339
  }
7326
7340
  setCurrentToneGain(gain);
@@ -7336,7 +7350,7 @@ var pxsim;
7336
7350
  AudioContextManager.tone = tone;
7337
7351
  function setCurrentToneGain(gain) {
7338
7352
  if (_vca === null || _vca === void 0 ? void 0 : _vca.gain) {
7339
- _vca.gain.setTargetAtTime(_mute ? 0 : gain, _context.currentTime, 0.015);
7353
+ _vca.gain.setTargetAtTime(gain, _context.currentTime, 0.015);
7340
7354
  }
7341
7355
  }
7342
7356
  AudioContextManager.setCurrentToneGain = setCurrentToneGain;
@@ -7373,13 +7387,30 @@ var pxsim;
7373
7387
  let nodes = [];
7374
7388
  let nextTime = context().currentTime;
7375
7389
  let allScheduled = false;
7390
+ const channel = new Channel();
7391
+ channel.gain = context().createGain();
7392
+ channel.gain.gain.value = 0;
7393
+ channel.gain.gain.setValueAtTime(volume, context().currentTime);
7394
+ channel.gain.connect(destination);
7395
+ if (channels.length > 5)
7396
+ channels[0].remove();
7397
+ channels.push(channel);
7398
+ const checkCancel = () => {
7399
+ if (isCancelled && isCancelled() || !channel.gain) {
7400
+ if (resolve)
7401
+ resolve();
7402
+ resolve = undefined;
7403
+ channel.remove();
7404
+ return true;
7405
+ }
7406
+ return false;
7407
+ };
7376
7408
  // Every time we pull a buffer, schedule a node in the future to play it.
7377
7409
  // Scheduling the nodes ahead of time sounds much smoother than trying to
7378
7410
  // do it when the previous node completes (which sounds SUPER choppy in
7379
7411
  // FireFox).
7380
7412
  function playNext() {
7381
- const cancelled = isCancelled && isCancelled();
7382
- while (!allScheduled && nodes.length < MAX_SCHEDULED_BUFFER_NODES && !cancelled) {
7413
+ while (!allScheduled && nodes.length < MAX_SCHEDULED_BUFFER_NODES && !checkCancel()) {
7383
7414
  const data = pull();
7384
7415
  if (!data || !data.length) {
7385
7416
  allScheduled = true;
@@ -7395,6 +7426,8 @@ var pxsim;
7395
7426
  }
7396
7427
  }
7397
7428
  function play(data) {
7429
+ if (checkCancel())
7430
+ return;
7398
7431
  const buff = context().createBuffer(1, data.length, sampleRate);
7399
7432
  if (buff.copyToChannel) {
7400
7433
  buff.copyToChannel(data, 0);
@@ -7417,15 +7450,6 @@ var pxsim;
7417
7450
  newNode.start(nextTime);
7418
7451
  nextTime += buff.duration;
7419
7452
  }
7420
- const channel = new Channel();
7421
- channel.gain = context().createGain();
7422
- channel.gain.gain.value = 0;
7423
- if (!_mute)
7424
- channel.gain.gain.setValueAtTime(volume, context().currentTime);
7425
- channel.gain.connect(context().destination);
7426
- if (channels.length > 5)
7427
- channels[0].remove();
7428
- channels.push(channel);
7429
7453
  playNext();
7430
7454
  });
7431
7455
  }
@@ -8450,18 +8474,62 @@ var pxsim;
8450
8474
  result = "0" + result;
8451
8475
  return result;
8452
8476
  }
8453
- let soundPromise;
8477
+ let playing = false;
8478
+ let soundQueue;
8479
+ let cancellationToken = {
8480
+ cancelled: false
8481
+ };
8454
8482
  function __playSoundExpression(notes, waitTillDone) {
8483
+ if (!soundQueue)
8484
+ soundQueue = [];
8455
8485
  const cb = pxsim.getResume();
8456
- if (!soundPromise)
8457
- soundPromise = Promise.resolve();
8458
- soundPromise = soundPromise.then(() => playSoundExpressionAsync(notes));
8486
+ const soundPromise = new Promise((resolve, reject) => {
8487
+ soundQueue.push({
8488
+ notes,
8489
+ onFinished: resolve,
8490
+ onCancelled: reject
8491
+ });
8492
+ });
8493
+ if (!playing) {
8494
+ playNextSoundAsync();
8495
+ }
8459
8496
  if (!waitTillDone)
8460
8497
  cb();
8461
8498
  else
8462
- soundPromise = soundPromise.then(cb);
8499
+ soundPromise.then(cb);
8463
8500
  }
8464
8501
  music.__playSoundExpression = __playSoundExpression;
8502
+ async function playNextSoundAsync() {
8503
+ if (soundQueue.length) {
8504
+ playing = true;
8505
+ const sound = soundQueue.shift();
8506
+ let currentToken = cancellationToken;
8507
+ try {
8508
+ await playSoundExpressionAsync(sound.notes, () => currentToken.cancelled);
8509
+ if (currentToken.cancelled) {
8510
+ sound.onCancelled();
8511
+ }
8512
+ else {
8513
+ sound.onFinished();
8514
+ }
8515
+ }
8516
+ catch (_a) {
8517
+ sound.onCancelled();
8518
+ }
8519
+ playNextSoundAsync();
8520
+ }
8521
+ else {
8522
+ playing = false;
8523
+ }
8524
+ }
8525
+ function clearSoundQueue() {
8526
+ soundQueue = [];
8527
+ cancellationToken.cancelled = true;
8528
+ cancellationToken = {
8529
+ cancelled: false
8530
+ };
8531
+ }
8532
+ music.clearSoundQueue = clearSoundQueue;
8465
8533
  function playSoundExpressionAsync(notes, isCancelled, onPull) {
8466
8534
  const synth = new music.SoundEmojiSynthesizer(0);
8467
8535
  const soundEffects = parseSoundEffects(notes);
@@ -8493,6 +8561,7 @@ var pxsim;
8493
8561
  }
8494
8562
  music.playSoundExpressionAsync = playSoundExpressionAsync;
8495
8563
  function __stopSoundExpressions() {
8564
+ clearSoundQueue();
8496
8565
  pxsim.AudioContextManager.stopAll();
8497
8566
  }
8498
8567
  music.__stopSoundExpressions = __stopSoundExpressions;