pxt-core 12.2.26 → 12.2.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/pxt.js +294 -10
- package/built/pxtlib.js +1 -0
- package/built/pxtsim.d.ts +11 -0
- package/built/pxtsim.js +293 -10
- package/built/target.js +1 -1
- package/built/targetlight.js +1 -1
- package/built/tests/blocksrunner.js +49 -5
- package/built/tests/blockssetup.js +49 -5
- package/built/web/blockly.css +1 -1
- package/built/web/main.js +2 -2
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +2 -2
- package/built/web/pxtembed.js +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtsim.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/rtlblockly.css +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/runnerembed.js +1 -1
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/js/{main.d1d7cd5b.js → main.5472c093.js} +2 -2
- package/built/web/teachertool/css/main.a4357c60.css +1 -0
- package/localtypings/pxteditor.d.ts +1 -0
- package/package.json +1 -1
- package/react-common/components/share/ShareInfo.tsx +1 -1
- package/theme/blockly-core.less +2 -2
- package/theme/debugger.less +5 -1
- package/theme/fieldeditors.less +4 -0
- package/webapp/public/blockly/media/1x1.gif +0 -0
- package/webapp/public/blockly/media/click.mp3 +0 -0
- package/webapp/public/blockly/media/click.ogg +0 -0
- package/webapp/public/blockly/media/click.wav +0 -0
- package/webapp/public/blockly/media/delete.mp3 +0 -0
- package/webapp/public/blockly/media/delete.ogg +0 -0
- package/webapp/public/blockly/media/delete.wav +0 -0
- package/webapp/public/blockly/media/disconnect.mp3 +0 -0
- package/webapp/public/blockly/media/disconnect.ogg +0 -0
- package/webapp/public/blockly/media/disconnect.wav +0 -0
- package/webapp/public/blockly/media/handclosed.cur +0 -0
- package/webapp/public/blockly/media/handdelete.cur +0 -0
- package/webapp/public/blockly/media/handopen.cur +0 -0
- package/webapp/public/blockly/media/pilcrow.png +0 -0
- package/webapp/public/blockly/media/quote0.png +0 -0
- package/webapp/public/blockly/media/quote1.png +0 -0
- package/webapp/public/blockly/media/sprites.png +0 -0
- package/webapp/public/skillmap.html +1 -1
- package/webapp/public/teachertool.html +1 -1
- package/built/web/teachertool/css/main.a240bbae.css +0 -1
package/built/pxt.js
CHANGED
|
@@ -119272,6 +119272,7 @@ var pxt;
|
|
|
119272
119272
|
}
|
|
119273
119273
|
if (entry.tilemapTile) {
|
|
119274
119274
|
tags.push("tile");
|
|
119275
|
+
tags.push("category-" + namespaceName);
|
|
119275
119276
|
}
|
|
119276
119277
|
}
|
|
119277
119278
|
if (mimeType === pxt.IMAGE_MIME_TYPE) {
|
|
@@ -159070,20 +159071,13 @@ var pxsim;
|
|
|
159070
159071
|
}
|
|
159071
159072
|
async function playInstructionsAsync(instructions, isCancelled, onPull) {
|
|
159072
159073
|
AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
|
|
159073
|
-
|
|
159074
|
-
let channel;
|
|
159074
|
+
const channel = new AudioContextManager.PlayInstructionsSource(context(), destination);
|
|
159075
159075
|
let finished = false;
|
|
159076
159076
|
if (onPull) {
|
|
159077
|
-
channel
|
|
159078
|
-
initOscilloscope(onPull, channel.analyser, () => finished || (isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()));
|
|
159079
|
-
}
|
|
159080
|
-
else {
|
|
159081
|
-
channel = AudioContextManager.AudioWorkletSource.getAvailableSource();
|
|
159082
|
-
if (!channel) {
|
|
159083
|
-
channel = new AudioContextManager.AudioWorkletSource(context(), destination);
|
|
159084
|
-
}
|
|
159077
|
+
initOscilloscope(onPull, channel.analyser, () => finished || (isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()) || channel.isDisposed());
|
|
159085
159078
|
}
|
|
159086
159079
|
await channel.playInstructionsAsync(instructions, isCancelled);
|
|
159080
|
+
channel.dispose();
|
|
159087
159081
|
finished = true;
|
|
159088
159082
|
}
|
|
159089
159083
|
AudioContextManager.playInstructionsAsync = playInstructionsAsync;
|
|
@@ -159612,6 +159606,296 @@ var pxsim;
|
|
|
159612
159606
|
})(music = codal.music || (codal.music = {}));
|
|
159613
159607
|
})(codal = pxsim.codal || (pxsim.codal = {}));
|
|
159614
159608
|
})(pxsim || (pxsim = {}));
|
|
159609
|
+
var pxsim;
|
|
159610
|
+
(function (pxsim) {
|
|
159611
|
+
var AudioContextManager;
|
|
159612
|
+
(function (AudioContextManager) {
|
|
159613
|
+
const waveForms = [null, "triangle", "sawtooth", "sine"];
|
|
159614
|
+
let noiseBuffer;
|
|
159615
|
+
let rectNoiseBuffer;
|
|
159616
|
+
let cycleNoiseBuffer = [];
|
|
159617
|
+
let squareBuffer = [];
|
|
159618
|
+
function getNoiseBuffer(context) {
|
|
159619
|
+
if (!noiseBuffer) {
|
|
159620
|
+
const bufferSize = 100000;
|
|
159621
|
+
noiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
159622
|
+
const output = noiseBuffer.getChannelData(0);
|
|
159623
|
+
let x = 0xf01ba80;
|
|
159624
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
159625
|
+
x ^= x << 13;
|
|
159626
|
+
x ^= x >> 17;
|
|
159627
|
+
x ^= x << 5;
|
|
159628
|
+
output[i] = ((x & 1023) / 512.0) - 1.0;
|
|
159629
|
+
}
|
|
159630
|
+
}
|
|
159631
|
+
return noiseBuffer;
|
|
159632
|
+
}
|
|
159633
|
+
function getRectNoiseBuffer(context) {
|
|
159634
|
+
// Create a square wave filtered by a pseudorandom bit sequence.
|
|
159635
|
+
// This uses four samples per cycle to create square-ish waves.
|
|
159636
|
+
// The Web Audio API's frequency scaling may be using linear
|
|
159637
|
+
// interpolation which would turn a two-sample wave into a triangle.
|
|
159638
|
+
if (!rectNoiseBuffer) {
|
|
159639
|
+
const bufferSize = 131072; // must be a multiple of 4
|
|
159640
|
+
rectNoiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
159641
|
+
const output = rectNoiseBuffer.getChannelData(0);
|
|
159642
|
+
let x = 0xf01ba80;
|
|
159643
|
+
for (let i = 0; i < bufferSize; i += 4) {
|
|
159644
|
+
// see https://en.wikipedia.org/wiki/Xorshift
|
|
159645
|
+
x ^= x << 13;
|
|
159646
|
+
x ^= x >> 17;
|
|
159647
|
+
x ^= x << 5;
|
|
159648
|
+
if (x & 0x8000) {
|
|
159649
|
+
output[i] = 1.0;
|
|
159650
|
+
output[i + 1] = 1.0;
|
|
159651
|
+
output[i + 2] = -1.0;
|
|
159652
|
+
output[i + 3] = -1.0;
|
|
159653
|
+
}
|
|
159654
|
+
else {
|
|
159655
|
+
output[i] = 0.0;
|
|
159656
|
+
output[i + 1] = 0.0;
|
|
159657
|
+
output[i + 2] = 0.0;
|
|
159658
|
+
output[i + 3] = 0.0;
|
|
159659
|
+
}
|
|
159660
|
+
}
|
|
159661
|
+
}
|
|
159662
|
+
return rectNoiseBuffer;
|
|
159663
|
+
}
|
|
159664
|
+
function getCycleNoiseBuffer(context, bits) {
|
|
159665
|
+
if (!cycleNoiseBuffer[bits]) {
|
|
159666
|
+
// Buffer size needs to be a multiple of 4x the largest cycle length,
|
|
159667
|
+
// 4*64 in this case.
|
|
159668
|
+
const bufferSize = 1024;
|
|
159669
|
+
const buf = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
159670
|
+
const output = buf.getChannelData(0);
|
|
159671
|
+
// See pxt-common-packages's libs/mixer/melody.cpp for details.
|
|
159672
|
+
// "bits" must be in the range 4..6.
|
|
159673
|
+
const cycle_bits = [0x2df0eb47, 0xc8165a93];
|
|
159674
|
+
const mask_456 = [0xf, 0x1f, 0x3f];
|
|
159675
|
+
for (let i = 0; i < bufferSize; i += 4) {
|
|
159676
|
+
let cycle = i / 4;
|
|
159677
|
+
let is_on;
|
|
159678
|
+
let cycle_mask = mask_456[bits - 4];
|
|
159679
|
+
cycle &= cycle_mask;
|
|
159680
|
+
is_on = (cycle_bits[cycle >> 5] & (1 << (cycle & 0x1f))) != 0;
|
|
159681
|
+
if (is_on) {
|
|
159682
|
+
output[i] = 1.0;
|
|
159683
|
+
output[i + 1] = 1.0;
|
|
159684
|
+
output[i + 2] = -1.0;
|
|
159685
|
+
output[i + 3] = -1.0;
|
|
159686
|
+
}
|
|
159687
|
+
else {
|
|
159688
|
+
output[i] = 0.0;
|
|
159689
|
+
output[i + 1] = 0.0;
|
|
159690
|
+
output[i + 2] = 0.0;
|
|
159691
|
+
output[i + 3] = 0.0;
|
|
159692
|
+
}
|
|
159693
|
+
}
|
|
159694
|
+
cycleNoiseBuffer[bits] = buf;
|
|
159695
|
+
}
|
|
159696
|
+
return cycleNoiseBuffer[bits];
|
|
159697
|
+
}
|
|
159698
|
+
function getSquareBuffer(context, param) {
|
|
159699
|
+
if (!squareBuffer[param]) {
|
|
159700
|
+
const bufferSize = 1024;
|
|
159701
|
+
const buf = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
159702
|
+
const output = buf.getChannelData(0);
|
|
159703
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
159704
|
+
output[i] = i < (param / 100 * bufferSize) ? 1 : -1;
|
|
159705
|
+
}
|
|
159706
|
+
squareBuffer[param] = buf;
|
|
159707
|
+
}
|
|
159708
|
+
return squareBuffer[param];
|
|
159709
|
+
}
|
|
159710
|
+
/*
|
|
159711
|
+
#define SW_TRIANGLE 1
|
|
159712
|
+
#define SW_SAWTOOTH 2
|
|
159713
|
+
#define SW_SINE 3
|
|
159714
|
+
#define SW_TUNEDNOISE 4
|
|
159715
|
+
#define SW_NOISE 5
|
|
159716
|
+
#define SW_SQUARE_10 11
|
|
159717
|
+
#define SW_SQUARE_50 15
|
|
159718
|
+
#define SW_SQUARE_CYCLE_16 16
|
|
159719
|
+
#define SW_SQUARE_CYCLE_32 17
|
|
159720
|
+
#define SW_SQUARE_CYCLE_64 18
|
|
159721
|
+
*/
|
|
159722
|
+
/*
|
|
159723
|
+
struct SoundInstruction {
|
|
159724
|
+
uint8_t soundWave;
|
|
159725
|
+
uint8_t flags;
|
|
159726
|
+
uint16_t frequency;
|
|
159727
|
+
uint16_t duration;
|
|
159728
|
+
uint16_t startVolume;
|
|
159729
|
+
uint16_t endVolume;
|
|
159730
|
+
};
|
|
159731
|
+
*/
|
|
159732
|
+
function getGenerator(context, waveFormIdx, hz) {
|
|
159733
|
+
let form = waveForms[waveFormIdx];
|
|
159734
|
+
if (form) {
|
|
159735
|
+
let src = context.createOscillator();
|
|
159736
|
+
src.type = form;
|
|
159737
|
+
src.frequency.value = hz;
|
|
159738
|
+
return src;
|
|
159739
|
+
}
|
|
159740
|
+
let buffer;
|
|
159741
|
+
if (waveFormIdx == 4)
|
|
159742
|
+
buffer = getRectNoiseBuffer(context);
|
|
159743
|
+
else if (waveFormIdx == 5)
|
|
159744
|
+
buffer = getNoiseBuffer(context);
|
|
159745
|
+
else if (11 <= waveFormIdx && waveFormIdx <= 15)
|
|
159746
|
+
buffer = getSquareBuffer(context, (waveFormIdx - 10) * 10);
|
|
159747
|
+
else if (16 <= waveFormIdx && waveFormIdx <= 18)
|
|
159748
|
+
buffer = getCycleNoiseBuffer(context, (waveFormIdx - 16) + 4);
|
|
159749
|
+
else
|
|
159750
|
+
return null;
|
|
159751
|
+
let node = context.createBufferSource();
|
|
159752
|
+
node.buffer = buffer;
|
|
159753
|
+
node.loop = true;
|
|
159754
|
+
const isFilteredNoise = waveFormIdx == 4 || (16 <= waveFormIdx && waveFormIdx <= 18);
|
|
159755
|
+
if (isFilteredNoise)
|
|
159756
|
+
node.playbackRate.value = hz / (context.sampleRate / 4);
|
|
159757
|
+
else if (waveFormIdx != 5)
|
|
159758
|
+
node.playbackRate.value = hz / (context.sampleRate / 1024);
|
|
159759
|
+
return node;
|
|
159760
|
+
}
|
|
159761
|
+
class PlayInstructionsSource extends AudioContextManager.AudioSource {
|
|
159762
|
+
constructor(context, destination) {
|
|
159763
|
+
super(context, destination);
|
|
159764
|
+
this.context = context;
|
|
159765
|
+
this.destination = destination;
|
|
159766
|
+
this.analyser = context.createAnalyser();
|
|
159767
|
+
this.analyser.fftSize = 2048;
|
|
159768
|
+
this.analyser.connect(this.vca);
|
|
159769
|
+
this.gain = context.createGain();
|
|
159770
|
+
this.gain.connect(this.analyser);
|
|
159771
|
+
}
|
|
159772
|
+
playInstructionsAsync(instructions, isCancelled, onPull) {
|
|
159773
|
+
return new Promise(async (resolve) => {
|
|
159774
|
+
AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
|
|
159775
|
+
let resolved = false;
|
|
159776
|
+
const oscillators = {};
|
|
159777
|
+
const gains = {};
|
|
159778
|
+
let startTime = this.context.currentTime;
|
|
159779
|
+
let currentTime = startTime;
|
|
159780
|
+
let currentWave = 0;
|
|
159781
|
+
let totalDuration = 0;
|
|
159782
|
+
/** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
|
|
159783
|
+
const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
|
|
159784
|
+
const disconnectNodes = () => {
|
|
159785
|
+
if (resolved)
|
|
159786
|
+
return;
|
|
159787
|
+
resolved = true;
|
|
159788
|
+
for (const wave of Object.keys(oscillators)) {
|
|
159789
|
+
oscillators[wave].stop();
|
|
159790
|
+
oscillators[wave].disconnect();
|
|
159791
|
+
gains[wave].disconnect();
|
|
159792
|
+
}
|
|
159793
|
+
resolve();
|
|
159794
|
+
};
|
|
159795
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
159796
|
+
const wave = instructions[i];
|
|
159797
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
159798
|
+
const duration = readUint16(instructions, i + 4) / 1000;
|
|
159799
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
159800
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
159801
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
159802
|
+
totalDuration += duration;
|
|
159803
|
+
if (wave === 0) {
|
|
159804
|
+
currentTime += duration;
|
|
159805
|
+
continue;
|
|
159806
|
+
}
|
|
159807
|
+
const isSquareWave = 11 <= wave && wave <= 15;
|
|
159808
|
+
if (!oscillators[wave]) {
|
|
159809
|
+
oscillators[wave] = getGenerator(this.context, wave, startFrequency);
|
|
159810
|
+
gains[wave] = this.context.createGain();
|
|
159811
|
+
gains[wave].gain.value = 0;
|
|
159812
|
+
gains[wave].connect(this.gain);
|
|
159813
|
+
oscillators[wave].connect(gains[wave]);
|
|
159814
|
+
oscillators[wave].start();
|
|
159815
|
+
}
|
|
159816
|
+
if (currentWave && wave !== currentWave) {
|
|
159817
|
+
gains[currentWave].gain.setTargetAtTime(0, currentTime, 0.015);
|
|
159818
|
+
}
|
|
159819
|
+
const osc = oscillators[wave];
|
|
159820
|
+
const gain = gains[wave];
|
|
159821
|
+
if (osc instanceof OscillatorNode) {
|
|
159822
|
+
osc.frequency.setValueAtTime(startFrequency, currentTime);
|
|
159823
|
+
osc.frequency.linearRampToValueAtTime(endFrequency, currentTime + duration);
|
|
159824
|
+
}
|
|
159825
|
+
else {
|
|
159826
|
+
const isFilteredNoise = wave == 4 || (16 <= wave && wave <= 18);
|
|
159827
|
+
if (isFilteredNoise)
|
|
159828
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (this.context.sampleRate / 4), currentTime + duration);
|
|
159829
|
+
else if (wave != 5)
|
|
159830
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (this.context.sampleRate / 1024), currentTime + duration);
|
|
159831
|
+
}
|
|
159832
|
+
gain.gain.setValueAtTime(scaleVol(startVolume, isSquareWave), currentTime);
|
|
159833
|
+
gain.gain.linearRampToValueAtTime(scaleVol(endVolume, isSquareWave), currentTime + duration);
|
|
159834
|
+
currentWave = wave;
|
|
159835
|
+
currentTime += duration;
|
|
159836
|
+
}
|
|
159837
|
+
this.gain.gain.setTargetAtTime(0, currentTime, 0.015);
|
|
159838
|
+
if (isCancelled || onPull) {
|
|
159839
|
+
const handleAnimationFrame = () => {
|
|
159840
|
+
const time = this.context.currentTime;
|
|
159841
|
+
if (time > startTime + totalDuration) {
|
|
159842
|
+
return;
|
|
159843
|
+
}
|
|
159844
|
+
if ((isCancelled && isCancelled()) || this.isDisposed()) {
|
|
159845
|
+
disconnectNodes();
|
|
159846
|
+
return;
|
|
159847
|
+
}
|
|
159848
|
+
const { frequency, volume } = findFrequencyAndVolumeAtTime((time - startTime) * 1000, instructions);
|
|
159849
|
+
if (onPull)
|
|
159850
|
+
onPull(frequency, volume / 1024);
|
|
159851
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
159852
|
+
};
|
|
159853
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
159854
|
+
}
|
|
159855
|
+
await pxsim.U.delay(totalDuration * 1000);
|
|
159856
|
+
disconnectNodes();
|
|
159857
|
+
});
|
|
159858
|
+
}
|
|
159859
|
+
dispose() {
|
|
159860
|
+
if (this.isDisposed())
|
|
159861
|
+
return;
|
|
159862
|
+
super.dispose();
|
|
159863
|
+
this.analyser.disconnect();
|
|
159864
|
+
this.gain.disconnect();
|
|
159865
|
+
}
|
|
159866
|
+
}
|
|
159867
|
+
AudioContextManager.PlayInstructionsSource = PlayInstructionsSource;
|
|
159868
|
+
function readUint16(buf, offset) {
|
|
159869
|
+
const temp = new Uint8Array(2);
|
|
159870
|
+
temp[0] = buf[offset];
|
|
159871
|
+
temp[1] = buf[offset + 1];
|
|
159872
|
+
return new Uint16Array(temp.buffer)[0];
|
|
159873
|
+
}
|
|
159874
|
+
function findFrequencyAndVolumeAtTime(millis, instructions) {
|
|
159875
|
+
let currentTime = 0;
|
|
159876
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
159877
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
159878
|
+
const duration = readUint16(instructions, i + 4);
|
|
159879
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
159880
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
159881
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
159882
|
+
if (currentTime + duration < millis) {
|
|
159883
|
+
currentTime += duration;
|
|
159884
|
+
continue;
|
|
159885
|
+
}
|
|
159886
|
+
const offset = (millis - currentTime) / duration;
|
|
159887
|
+
return {
|
|
159888
|
+
frequency: startFrequency + (endFrequency - startFrequency) * offset,
|
|
159889
|
+
volume: startVolume + (endVolume - startVolume) * offset,
|
|
159890
|
+
};
|
|
159891
|
+
}
|
|
159892
|
+
return {
|
|
159893
|
+
frequency: -1,
|
|
159894
|
+
volume: -1
|
|
159895
|
+
};
|
|
159896
|
+
}
|
|
159897
|
+
})(AudioContextManager = pxsim.AudioContextManager || (pxsim.AudioContextManager = {}));
|
|
159898
|
+
})(pxsim || (pxsim = {}));
|
|
159615
159899
|
/// <reference path="../../localtypings/pxtmusic.d.ts" />
|
|
159616
159900
|
var pxsim;
|
|
159617
159901
|
(function (pxsim) {
|
package/built/pxtlib.js
CHANGED
|
@@ -21551,6 +21551,7 @@ var pxt;
|
|
|
21551
21551
|
}
|
|
21552
21552
|
if (entry.tilemapTile) {
|
|
21553
21553
|
tags.push("tile");
|
|
21554
|
+
tags.push("category-" + namespaceName);
|
|
21554
21555
|
}
|
|
21555
21556
|
}
|
|
21556
21557
|
if (mimeType === pxt.IMAGE_MIME_TYPE) {
|
package/built/pxtsim.d.ts
CHANGED
|
@@ -1865,6 +1865,17 @@ declare namespace pxsim.codal.music.MusicalProgressions {
|
|
|
1865
1865
|
*/
|
|
1866
1866
|
function calculateFrequencyFromProgression(root: number, progression: Progression, offset: number): number;
|
|
1867
1867
|
}
|
|
1868
|
+
declare namespace pxsim.AudioContextManager {
|
|
1869
|
+
class PlayInstructionsSource extends AudioSource {
|
|
1870
|
+
context: AudioContext;
|
|
1871
|
+
destination: AudioNode;
|
|
1872
|
+
analyser: AnalyserNode;
|
|
1873
|
+
gain: GainNode;
|
|
1874
|
+
constructor(context: AudioContext, destination: AudioNode);
|
|
1875
|
+
playInstructionsAsync(instructions: Uint8Array, isCancelled?: () => boolean, onPull?: (freq: number, volume: number) => void): Promise<void>;
|
|
1876
|
+
dispose(): void;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1868
1879
|
declare namespace pxsim.music {
|
|
1869
1880
|
type SequencerState = "play" | "loop" | "stop";
|
|
1870
1881
|
type SequencerEvent = "tick" | "play" | "stop" | "loop" | "state-change" | "looped";
|
package/built/pxtsim.js
CHANGED
|
@@ -8242,20 +8242,13 @@ var pxsim;
|
|
|
8242
8242
|
}
|
|
8243
8243
|
async function playInstructionsAsync(instructions, isCancelled, onPull) {
|
|
8244
8244
|
AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
|
|
8245
|
-
|
|
8246
|
-
let channel;
|
|
8245
|
+
const channel = new AudioContextManager.PlayInstructionsSource(context(), destination);
|
|
8247
8246
|
let finished = false;
|
|
8248
8247
|
if (onPull) {
|
|
8249
|
-
channel
|
|
8250
|
-
initOscilloscope(onPull, channel.analyser, () => finished || (isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()));
|
|
8251
|
-
}
|
|
8252
|
-
else {
|
|
8253
|
-
channel = AudioContextManager.AudioWorkletSource.getAvailableSource();
|
|
8254
|
-
if (!channel) {
|
|
8255
|
-
channel = new AudioContextManager.AudioWorkletSource(context(), destination);
|
|
8256
|
-
}
|
|
8248
|
+
initOscilloscope(onPull, channel.analyser, () => finished || (isCancelled === null || isCancelled === void 0 ? void 0 : isCancelled()) || channel.isDisposed());
|
|
8257
8249
|
}
|
|
8258
8250
|
await channel.playInstructionsAsync(instructions, isCancelled);
|
|
8251
|
+
channel.dispose();
|
|
8259
8252
|
finished = true;
|
|
8260
8253
|
}
|
|
8261
8254
|
AudioContextManager.playInstructionsAsync = playInstructionsAsync;
|
|
@@ -8784,6 +8777,296 @@ var pxsim;
|
|
|
8784
8777
|
})(music = codal.music || (codal.music = {}));
|
|
8785
8778
|
})(codal = pxsim.codal || (pxsim.codal = {}));
|
|
8786
8779
|
})(pxsim || (pxsim = {}));
|
|
8780
|
+
var pxsim;
|
|
8781
|
+
(function (pxsim) {
|
|
8782
|
+
var AudioContextManager;
|
|
8783
|
+
(function (AudioContextManager) {
|
|
8784
|
+
const waveForms = [null, "triangle", "sawtooth", "sine"];
|
|
8785
|
+
let noiseBuffer;
|
|
8786
|
+
let rectNoiseBuffer;
|
|
8787
|
+
let cycleNoiseBuffer = [];
|
|
8788
|
+
let squareBuffer = [];
|
|
8789
|
+
function getNoiseBuffer(context) {
|
|
8790
|
+
if (!noiseBuffer) {
|
|
8791
|
+
const bufferSize = 100000;
|
|
8792
|
+
noiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
8793
|
+
const output = noiseBuffer.getChannelData(0);
|
|
8794
|
+
let x = 0xf01ba80;
|
|
8795
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
8796
|
+
x ^= x << 13;
|
|
8797
|
+
x ^= x >> 17;
|
|
8798
|
+
x ^= x << 5;
|
|
8799
|
+
output[i] = ((x & 1023) / 512.0) - 1.0;
|
|
8800
|
+
}
|
|
8801
|
+
}
|
|
8802
|
+
return noiseBuffer;
|
|
8803
|
+
}
|
|
8804
|
+
function getRectNoiseBuffer(context) {
|
|
8805
|
+
// Create a square wave filtered by a pseudorandom bit sequence.
|
|
8806
|
+
// This uses four samples per cycle to create square-ish waves.
|
|
8807
|
+
// The Web Audio API's frequency scaling may be using linear
|
|
8808
|
+
// interpolation which would turn a two-sample wave into a triangle.
|
|
8809
|
+
if (!rectNoiseBuffer) {
|
|
8810
|
+
const bufferSize = 131072; // must be a multiple of 4
|
|
8811
|
+
rectNoiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
8812
|
+
const output = rectNoiseBuffer.getChannelData(0);
|
|
8813
|
+
let x = 0xf01ba80;
|
|
8814
|
+
for (let i = 0; i < bufferSize; i += 4) {
|
|
8815
|
+
// see https://en.wikipedia.org/wiki/Xorshift
|
|
8816
|
+
x ^= x << 13;
|
|
8817
|
+
x ^= x >> 17;
|
|
8818
|
+
x ^= x << 5;
|
|
8819
|
+
if (x & 0x8000) {
|
|
8820
|
+
output[i] = 1.0;
|
|
8821
|
+
output[i + 1] = 1.0;
|
|
8822
|
+
output[i + 2] = -1.0;
|
|
8823
|
+
output[i + 3] = -1.0;
|
|
8824
|
+
}
|
|
8825
|
+
else {
|
|
8826
|
+
output[i] = 0.0;
|
|
8827
|
+
output[i + 1] = 0.0;
|
|
8828
|
+
output[i + 2] = 0.0;
|
|
8829
|
+
output[i + 3] = 0.0;
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
return rectNoiseBuffer;
|
|
8834
|
+
}
|
|
8835
|
+
function getCycleNoiseBuffer(context, bits) {
|
|
8836
|
+
if (!cycleNoiseBuffer[bits]) {
|
|
8837
|
+
// Buffer size needs to be a multiple of 4x the largest cycle length,
|
|
8838
|
+
// 4*64 in this case.
|
|
8839
|
+
const bufferSize = 1024;
|
|
8840
|
+
const buf = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
8841
|
+
const output = buf.getChannelData(0);
|
|
8842
|
+
// See pxt-common-packages's libs/mixer/melody.cpp for details.
|
|
8843
|
+
// "bits" must be in the range 4..6.
|
|
8844
|
+
const cycle_bits = [0x2df0eb47, 0xc8165a93];
|
|
8845
|
+
const mask_456 = [0xf, 0x1f, 0x3f];
|
|
8846
|
+
for (let i = 0; i < bufferSize; i += 4) {
|
|
8847
|
+
let cycle = i / 4;
|
|
8848
|
+
let is_on;
|
|
8849
|
+
let cycle_mask = mask_456[bits - 4];
|
|
8850
|
+
cycle &= cycle_mask;
|
|
8851
|
+
is_on = (cycle_bits[cycle >> 5] & (1 << (cycle & 0x1f))) != 0;
|
|
8852
|
+
if (is_on) {
|
|
8853
|
+
output[i] = 1.0;
|
|
8854
|
+
output[i + 1] = 1.0;
|
|
8855
|
+
output[i + 2] = -1.0;
|
|
8856
|
+
output[i + 3] = -1.0;
|
|
8857
|
+
}
|
|
8858
|
+
else {
|
|
8859
|
+
output[i] = 0.0;
|
|
8860
|
+
output[i + 1] = 0.0;
|
|
8861
|
+
output[i + 2] = 0.0;
|
|
8862
|
+
output[i + 3] = 0.0;
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
cycleNoiseBuffer[bits] = buf;
|
|
8866
|
+
}
|
|
8867
|
+
return cycleNoiseBuffer[bits];
|
|
8868
|
+
}
|
|
8869
|
+
function getSquareBuffer(context, param) {
|
|
8870
|
+
if (!squareBuffer[param]) {
|
|
8871
|
+
const bufferSize = 1024;
|
|
8872
|
+
const buf = context.createBuffer(1, bufferSize, context.sampleRate);
|
|
8873
|
+
const output = buf.getChannelData(0);
|
|
8874
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
8875
|
+
output[i] = i < (param / 100 * bufferSize) ? 1 : -1;
|
|
8876
|
+
}
|
|
8877
|
+
squareBuffer[param] = buf;
|
|
8878
|
+
}
|
|
8879
|
+
return squareBuffer[param];
|
|
8880
|
+
}
|
|
8881
|
+
/*
|
|
8882
|
+
#define SW_TRIANGLE 1
|
|
8883
|
+
#define SW_SAWTOOTH 2
|
|
8884
|
+
#define SW_SINE 3
|
|
8885
|
+
#define SW_TUNEDNOISE 4
|
|
8886
|
+
#define SW_NOISE 5
|
|
8887
|
+
#define SW_SQUARE_10 11
|
|
8888
|
+
#define SW_SQUARE_50 15
|
|
8889
|
+
#define SW_SQUARE_CYCLE_16 16
|
|
8890
|
+
#define SW_SQUARE_CYCLE_32 17
|
|
8891
|
+
#define SW_SQUARE_CYCLE_64 18
|
|
8892
|
+
*/
|
|
8893
|
+
/*
|
|
8894
|
+
struct SoundInstruction {
|
|
8895
|
+
uint8_t soundWave;
|
|
8896
|
+
uint8_t flags;
|
|
8897
|
+
uint16_t frequency;
|
|
8898
|
+
uint16_t duration;
|
|
8899
|
+
uint16_t startVolume;
|
|
8900
|
+
uint16_t endVolume;
|
|
8901
|
+
};
|
|
8902
|
+
*/
|
|
8903
|
+
function getGenerator(context, waveFormIdx, hz) {
|
|
8904
|
+
let form = waveForms[waveFormIdx];
|
|
8905
|
+
if (form) {
|
|
8906
|
+
let src = context.createOscillator();
|
|
8907
|
+
src.type = form;
|
|
8908
|
+
src.frequency.value = hz;
|
|
8909
|
+
return src;
|
|
8910
|
+
}
|
|
8911
|
+
let buffer;
|
|
8912
|
+
if (waveFormIdx == 4)
|
|
8913
|
+
buffer = getRectNoiseBuffer(context);
|
|
8914
|
+
else if (waveFormIdx == 5)
|
|
8915
|
+
buffer = getNoiseBuffer(context);
|
|
8916
|
+
else if (11 <= waveFormIdx && waveFormIdx <= 15)
|
|
8917
|
+
buffer = getSquareBuffer(context, (waveFormIdx - 10) * 10);
|
|
8918
|
+
else if (16 <= waveFormIdx && waveFormIdx <= 18)
|
|
8919
|
+
buffer = getCycleNoiseBuffer(context, (waveFormIdx - 16) + 4);
|
|
8920
|
+
else
|
|
8921
|
+
return null;
|
|
8922
|
+
let node = context.createBufferSource();
|
|
8923
|
+
node.buffer = buffer;
|
|
8924
|
+
node.loop = true;
|
|
8925
|
+
const isFilteredNoise = waveFormIdx == 4 || (16 <= waveFormIdx && waveFormIdx <= 18);
|
|
8926
|
+
if (isFilteredNoise)
|
|
8927
|
+
node.playbackRate.value = hz / (context.sampleRate / 4);
|
|
8928
|
+
else if (waveFormIdx != 5)
|
|
8929
|
+
node.playbackRate.value = hz / (context.sampleRate / 1024);
|
|
8930
|
+
return node;
|
|
8931
|
+
}
|
|
8932
|
+
class PlayInstructionsSource extends AudioContextManager.AudioSource {
|
|
8933
|
+
constructor(context, destination) {
|
|
8934
|
+
super(context, destination);
|
|
8935
|
+
this.context = context;
|
|
8936
|
+
this.destination = destination;
|
|
8937
|
+
this.analyser = context.createAnalyser();
|
|
8938
|
+
this.analyser.fftSize = 2048;
|
|
8939
|
+
this.analyser.connect(this.vca);
|
|
8940
|
+
this.gain = context.createGain();
|
|
8941
|
+
this.gain.connect(this.analyser);
|
|
8942
|
+
}
|
|
8943
|
+
playInstructionsAsync(instructions, isCancelled, onPull) {
|
|
8944
|
+
return new Promise(async (resolve) => {
|
|
8945
|
+
AudioContextManager.soundEventCallback === null || AudioContextManager.soundEventCallback === void 0 ? void 0 : AudioContextManager.soundEventCallback("playinstructions", instructions);
|
|
8946
|
+
let resolved = false;
|
|
8947
|
+
const oscillators = {};
|
|
8948
|
+
const gains = {};
|
|
8949
|
+
let startTime = this.context.currentTime;
|
|
8950
|
+
let currentTime = startTime;
|
|
8951
|
+
let currentWave = 0;
|
|
8952
|
+
let totalDuration = 0;
|
|
8953
|
+
/** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
|
|
8954
|
+
const scaleVol = (n, isSqWave) => (n / 1024) / 4 * (isSqWave ? .5 : 1);
|
|
8955
|
+
const disconnectNodes = () => {
|
|
8956
|
+
if (resolved)
|
|
8957
|
+
return;
|
|
8958
|
+
resolved = true;
|
|
8959
|
+
for (const wave of Object.keys(oscillators)) {
|
|
8960
|
+
oscillators[wave].stop();
|
|
8961
|
+
oscillators[wave].disconnect();
|
|
8962
|
+
gains[wave].disconnect();
|
|
8963
|
+
}
|
|
8964
|
+
resolve();
|
|
8965
|
+
};
|
|
8966
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
8967
|
+
const wave = instructions[i];
|
|
8968
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
8969
|
+
const duration = readUint16(instructions, i + 4) / 1000;
|
|
8970
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
8971
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
8972
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
8973
|
+
totalDuration += duration;
|
|
8974
|
+
if (wave === 0) {
|
|
8975
|
+
currentTime += duration;
|
|
8976
|
+
continue;
|
|
8977
|
+
}
|
|
8978
|
+
const isSquareWave = 11 <= wave && wave <= 15;
|
|
8979
|
+
if (!oscillators[wave]) {
|
|
8980
|
+
oscillators[wave] = getGenerator(this.context, wave, startFrequency);
|
|
8981
|
+
gains[wave] = this.context.createGain();
|
|
8982
|
+
gains[wave].gain.value = 0;
|
|
8983
|
+
gains[wave].connect(this.gain);
|
|
8984
|
+
oscillators[wave].connect(gains[wave]);
|
|
8985
|
+
oscillators[wave].start();
|
|
8986
|
+
}
|
|
8987
|
+
if (currentWave && wave !== currentWave) {
|
|
8988
|
+
gains[currentWave].gain.setTargetAtTime(0, currentTime, 0.015);
|
|
8989
|
+
}
|
|
8990
|
+
const osc = oscillators[wave];
|
|
8991
|
+
const gain = gains[wave];
|
|
8992
|
+
if (osc instanceof OscillatorNode) {
|
|
8993
|
+
osc.frequency.setValueAtTime(startFrequency, currentTime);
|
|
8994
|
+
osc.frequency.linearRampToValueAtTime(endFrequency, currentTime + duration);
|
|
8995
|
+
}
|
|
8996
|
+
else {
|
|
8997
|
+
const isFilteredNoise = wave == 4 || (16 <= wave && wave <= 18);
|
|
8998
|
+
if (isFilteredNoise)
|
|
8999
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (this.context.sampleRate / 4), currentTime + duration);
|
|
9000
|
+
else if (wave != 5)
|
|
9001
|
+
osc.playbackRate.linearRampToValueAtTime(endFrequency / (this.context.sampleRate / 1024), currentTime + duration);
|
|
9002
|
+
}
|
|
9003
|
+
gain.gain.setValueAtTime(scaleVol(startVolume, isSquareWave), currentTime);
|
|
9004
|
+
gain.gain.linearRampToValueAtTime(scaleVol(endVolume, isSquareWave), currentTime + duration);
|
|
9005
|
+
currentWave = wave;
|
|
9006
|
+
currentTime += duration;
|
|
9007
|
+
}
|
|
9008
|
+
this.gain.gain.setTargetAtTime(0, currentTime, 0.015);
|
|
9009
|
+
if (isCancelled || onPull) {
|
|
9010
|
+
const handleAnimationFrame = () => {
|
|
9011
|
+
const time = this.context.currentTime;
|
|
9012
|
+
if (time > startTime + totalDuration) {
|
|
9013
|
+
return;
|
|
9014
|
+
}
|
|
9015
|
+
if ((isCancelled && isCancelled()) || this.isDisposed()) {
|
|
9016
|
+
disconnectNodes();
|
|
9017
|
+
return;
|
|
9018
|
+
}
|
|
9019
|
+
const { frequency, volume } = findFrequencyAndVolumeAtTime((time - startTime) * 1000, instructions);
|
|
9020
|
+
if (onPull)
|
|
9021
|
+
onPull(frequency, volume / 1024);
|
|
9022
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
9023
|
+
};
|
|
9024
|
+
requestAnimationFrame(handleAnimationFrame);
|
|
9025
|
+
}
|
|
9026
|
+
await pxsim.U.delay(totalDuration * 1000);
|
|
9027
|
+
disconnectNodes();
|
|
9028
|
+
});
|
|
9029
|
+
}
|
|
9030
|
+
dispose() {
|
|
9031
|
+
if (this.isDisposed())
|
|
9032
|
+
return;
|
|
9033
|
+
super.dispose();
|
|
9034
|
+
this.analyser.disconnect();
|
|
9035
|
+
this.gain.disconnect();
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
AudioContextManager.PlayInstructionsSource = PlayInstructionsSource;
|
|
9039
|
+
function readUint16(buf, offset) {
|
|
9040
|
+
const temp = new Uint8Array(2);
|
|
9041
|
+
temp[0] = buf[offset];
|
|
9042
|
+
temp[1] = buf[offset + 1];
|
|
9043
|
+
return new Uint16Array(temp.buffer)[0];
|
|
9044
|
+
}
|
|
9045
|
+
function findFrequencyAndVolumeAtTime(millis, instructions) {
|
|
9046
|
+
let currentTime = 0;
|
|
9047
|
+
for (let i = 0; i < instructions.length; i += 12) {
|
|
9048
|
+
const startFrequency = readUint16(instructions, i + 2);
|
|
9049
|
+
const duration = readUint16(instructions, i + 4);
|
|
9050
|
+
const startVolume = readUint16(instructions, i + 6);
|
|
9051
|
+
const endVolume = readUint16(instructions, i + 8);
|
|
9052
|
+
const endFrequency = readUint16(instructions, i + 10);
|
|
9053
|
+
if (currentTime + duration < millis) {
|
|
9054
|
+
currentTime += duration;
|
|
9055
|
+
continue;
|
|
9056
|
+
}
|
|
9057
|
+
const offset = (millis - currentTime) / duration;
|
|
9058
|
+
return {
|
|
9059
|
+
frequency: startFrequency + (endFrequency - startFrequency) * offset,
|
|
9060
|
+
volume: startVolume + (endVolume - startVolume) * offset,
|
|
9061
|
+
};
|
|
9062
|
+
}
|
|
9063
|
+
return {
|
|
9064
|
+
frequency: -1,
|
|
9065
|
+
volume: -1
|
|
9066
|
+
};
|
|
9067
|
+
}
|
|
9068
|
+
})(AudioContextManager = pxsim.AudioContextManager || (pxsim.AudioContextManager = {}));
|
|
9069
|
+
})(pxsim || (pxsim = {}));
|
|
8787
9070
|
/// <reference path="../../localtypings/pxtmusic.d.ts" />
|
|
8788
9071
|
var pxsim;
|
|
8789
9072
|
(function (pxsim) {
|