mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
Merge branch 'develop' into greenkeeper/expose-loader-0.7.4
This commit is contained in:
commit
51bd0349b0
24 changed files with 367 additions and 105 deletions
12
package.json
12
package.json
|
@ -33,9 +33,9 @@
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
"eslint": "^4.5.0",
|
"eslint": "^4.5.0",
|
||||||
"eslint-config-scratch": "^4.0.0",
|
"eslint-config-scratch": "^5.0.0",
|
||||||
"expose-loader": "0.7.4",
|
"expose-loader": "0.7.4",
|
||||||
"gh-pages": "^0.12.0",
|
"gh-pages": "^1.1.0",
|
||||||
"got": "5.7.1",
|
"got": "5.7.1",
|
||||||
"highlightjs": "^9.8.0",
|
"highlightjs": "^9.8.0",
|
||||||
"htmlparser2": "3.9.2",
|
"htmlparser2": "3.9.2",
|
||||||
|
@ -44,18 +44,18 @@
|
||||||
"json": "^9.0.4",
|
"json": "^9.0.4",
|
||||||
"lodash.defaultsdeep": "4.6.0",
|
"lodash.defaultsdeep": "4.6.0",
|
||||||
"minilog": "3.1.0",
|
"minilog": "3.1.0",
|
||||||
"promise": "7.1.1",
|
"promise": "8.0.1",
|
||||||
"scratch-audio": "latest",
|
"scratch-audio": "latest",
|
||||||
"scratch-blocks": "latest",
|
"scratch-blocks": "latest",
|
||||||
"scratch-render": "latest",
|
"scratch-render": "latest",
|
||||||
"scratch-storage": "^0.3.0",
|
"scratch-storage": "^0.3.0",
|
||||||
"script-loader": "0.7.0",
|
"script-loader": "0.7.2",
|
||||||
"socket.io-client": "1.7.3",
|
"socket.io-client": "2.0.4",
|
||||||
"stats.js": "^0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "^10.2.0",
|
"tap": "^10.2.0",
|
||||||
"tiny-worker": "^2.1.1",
|
"tiny-worker": "^2.1.1",
|
||||||
"webpack": "^2.4.1",
|
"webpack": "^2.4.1",
|
||||||
"webpack-dev-server": "^2.4.1",
|
"webpack-dev-server": "^2.4.1",
|
||||||
"worker-loader": "0.8.1"
|
"worker-loader": "1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,30 @@ class Blocks {
|
||||||
* @type {Array.<String>}
|
* @type {Array.<String>}
|
||||||
*/
|
*/
|
||||||
this._scripts = [];
|
this._scripts = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime Cache
|
||||||
|
* @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._cache = {
|
||||||
|
/**
|
||||||
|
* Cache block inputs by block id
|
||||||
|
* @type {object.<string, !Array.<object>>}
|
||||||
|
*/
|
||||||
|
inputs: {},
|
||||||
|
/**
|
||||||
|
* Cache procedure Param Names by block id
|
||||||
|
* @type {object.<string, ?Array.<string>>}
|
||||||
|
*/
|
||||||
|
procedureParamNames: {},
|
||||||
|
/**
|
||||||
|
* Cache procedure definitions by block id
|
||||||
|
* @type {object.<string, ?string>}
|
||||||
|
*/
|
||||||
|
procedureDefinitions: {}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,11 +129,16 @@ class Blocks {
|
||||||
/**
|
/**
|
||||||
* Get all non-branch inputs for a block.
|
* Get all non-branch inputs for a block.
|
||||||
* @param {?object} block the block to query.
|
* @param {?object} block the block to query.
|
||||||
* @return {!object} All non-branch inputs and their associated blocks.
|
* @return {?Array.<object>} All non-branch inputs and their associated blocks.
|
||||||
*/
|
*/
|
||||||
getInputs (block) {
|
getInputs (block) {
|
||||||
if (typeof block === 'undefined') return null;
|
if (typeof block === 'undefined') return null;
|
||||||
const inputs = {};
|
let inputs = this._cache.inputs[block.id];
|
||||||
|
if (typeof inputs !== 'undefined') {
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs = {};
|
||||||
for (const input in block.inputs) {
|
for (const input in block.inputs) {
|
||||||
// Ignore blocks prefixed with branch prefix.
|
// Ignore blocks prefixed with branch prefix.
|
||||||
if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==
|
if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==
|
||||||
|
@ -117,6 +146,8 @@ class Blocks {
|
||||||
inputs[input] = block.inputs[input];
|
inputs[input] = block.inputs[input];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._cache.inputs[block.id] = inputs;
|
||||||
return inputs;
|
return inputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,16 +180,24 @@ class Blocks {
|
||||||
* @return {?string} ID of procedure definition.
|
* @return {?string} ID of procedure definition.
|
||||||
*/
|
*/
|
||||||
getProcedureDefinition (name) {
|
getProcedureDefinition (name) {
|
||||||
|
const blockID = this._cache.procedureDefinitions[name];
|
||||||
|
if (typeof blockID !== 'undefined') {
|
||||||
|
return blockID;
|
||||||
|
}
|
||||||
|
|
||||||
for (const id in this._blocks) {
|
for (const id in this._blocks) {
|
||||||
if (!this._blocks.hasOwnProperty(id)) continue;
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
const block = this._blocks[id];
|
const block = this._blocks[id];
|
||||||
if (block.opcode === 'procedures_definition') {
|
if (block.opcode === 'procedures_definition') {
|
||||||
const internal = this._getCustomBlockInternal(block);
|
const internal = this._getCustomBlockInternal(block);
|
||||||
if (internal && internal.mutation.proccode === name) {
|
if (internal && internal.mutation.proccode === name) {
|
||||||
return id; // The outer define block id
|
this._cache.procedureDefinitions[name] = id; // The outer define block id
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._cache.procedureDefinitions[name] = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,14 +207,23 @@ class Blocks {
|
||||||
* @return {?Array.<string>} List of param names for a procedure.
|
* @return {?Array.<string>} List of param names for a procedure.
|
||||||
*/
|
*/
|
||||||
getProcedureParamNames (name) {
|
getProcedureParamNames (name) {
|
||||||
|
const cachedNames = this._cache.procedureParamNames[name];
|
||||||
|
if (typeof cachedNames !== 'undefined') {
|
||||||
|
return cachedNames;
|
||||||
|
}
|
||||||
|
|
||||||
for (const id in this._blocks) {
|
for (const id in this._blocks) {
|
||||||
if (!this._blocks.hasOwnProperty(id)) continue;
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
const block = this._blocks[id];
|
const block = this._blocks[id];
|
||||||
if (block.opcode === 'procedures_prototype' &&
|
if (block.opcode === 'procedures_prototype' &&
|
||||||
block.mutation.proccode === name) {
|
block.mutation.proccode === name) {
|
||||||
return JSON.parse(block.mutation.argumentnames);
|
const paramNames = JSON.parse(block.mutation.argumentnames);
|
||||||
|
this._cache.procedureParamNames[name] = paramNames;
|
||||||
|
return paramNames;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._cache.procedureParamNames[name] = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,6 +319,15 @@ class Blocks {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all runtime caches.
|
||||||
|
*/
|
||||||
|
resetCache () {
|
||||||
|
this._cache.inputs = {};
|
||||||
|
this._cache.procedureParamNames = {};
|
||||||
|
this._cache.procedureDefinitions = {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block management: create blocks and scripts from a `create` event
|
* Block management: create blocks and scripts from a `create` event
|
||||||
* @param {!object} block Blockly create event to be processed
|
* @param {!object} block Blockly create event to be processed
|
||||||
|
@ -289,6 +346,8 @@ class Blocks {
|
||||||
if (block.topLevel) {
|
if (block.topLevel) {
|
||||||
this._addScript(block.id);
|
this._addScript(block.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resetCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,7 +360,6 @@ class Blocks {
|
||||||
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return;
|
if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return;
|
||||||
const block = this._blocks[args.id];
|
const block = this._blocks[args.id];
|
||||||
if (typeof block === 'undefined') return;
|
if (typeof block === 'undefined') return;
|
||||||
|
|
||||||
const wasMonitored = block.isMonitored;
|
const wasMonitored = block.isMonitored;
|
||||||
switch (args.element) {
|
switch (args.element) {
|
||||||
case 'field':
|
case 'field':
|
||||||
|
@ -337,6 +395,8 @@ class Blocks {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.resetCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,6 +453,7 @@ class Blocks {
|
||||||
}
|
}
|
||||||
this._blocks[e.id].parent = e.newParent;
|
this._blocks[e.id].parent = e.newParent;
|
||||||
}
|
}
|
||||||
|
this.resetCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,6 +508,8 @@ class Blocks {
|
||||||
|
|
||||||
// Delete block itself.
|
// Delete block itself.
|
||||||
delete this._blocks[blockId];
|
delete this._blocks[blockId];
|
||||||
|
|
||||||
|
this.resetCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
|
@ -8,7 +8,7 @@ const BlockType = require('./block-type');
|
||||||
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
|
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
|
||||||
const Scratch3PenBlocks = require('../blocks/scratch3_pen');
|
const Scratch3PenBlocks = require('../blocks/scratch3_pen');
|
||||||
const Scratch3WeDo2Blocks = require('../blocks/scratch3_wedo2');
|
const Scratch3WeDo2Blocks = require('../blocks/scratch3_wedo2');
|
||||||
const Scratch3MusicBlocks = require('../blocks/scratch3_music');
|
const Scratch3MusicBlocks = require('../extensions/scratch3_music');
|
||||||
const builtinExtensions = {
|
const builtinExtensions = {
|
||||||
pen: Scratch3PenBlocks,
|
pen: Scratch3PenBlocks,
|
||||||
wedo2: Scratch3WeDo2Blocks,
|
wedo2: Scratch3WeDo2Blocks,
|
||||||
|
|
BIN
src/extensions/scratch3_music/assets/1-snare.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/1-snare.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/10-wood-block.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/10-wood-block.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/11-cowbell.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/11-cowbell.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/12-triangle.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/12-triangle.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/13-bongo.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/13-bongo.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/14-conga.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/14-conga.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/15-cabasa.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/15-cabasa.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/16-guiro.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/16-guiro.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/17-vibraslap.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/17-vibraslap.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/18-cuica.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/18-cuica.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/2-bass-drum.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/2-bass-drum.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/3-side-stick.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/3-side-stick.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/4-crash-cymbal.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/4-crash-cymbal.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/5-open-hi-hat.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/5-open-hi-hat.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/6-closed-hi-hat.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/6-closed-hi-hat.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/7-tambourine.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/7-tambourine.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/8-hand-clap.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/8-hand-clap.mp3
Normal file
Binary file not shown.
BIN
src/extensions/scratch3_music/assets/9-claves.mp3
Normal file
BIN
src/extensions/scratch3_music/assets/9-claves.mp3
Normal file
Binary file not shown.
|
@ -1,62 +1,9 @@
|
||||||
const ArgumentType = require('../extension-support/argument-type');
|
const ArgumentType = require('../../extension-support/argument-type');
|
||||||
const BlockType = require('../extension-support/block-type');
|
const BlockType = require('../../extension-support/block-type');
|
||||||
const Clone = require('../util/clone');
|
const Clone = require('../../util/clone');
|
||||||
const Cast = require('../util/cast');
|
const Cast = require('../../util/cast');
|
||||||
const MathUtil = require('../util/math-util');
|
const MathUtil = require('../../util/math-util');
|
||||||
const Timer = require('../util/timer');
|
const Timer = require('../../util/timer');
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of drum names, used in the play drum block.
|
|
||||||
* @type {string[]}
|
|
||||||
*/
|
|
||||||
const drumNames = [
|
|
||||||
'Snare Drum',
|
|
||||||
'Bass Drum',
|
|
||||||
'Side Stick',
|
|
||||||
'Crash Cymbal',
|
|
||||||
'Open Hi-Hat',
|
|
||||||
'Closed Hi-Hat',
|
|
||||||
'Tambourine',
|
|
||||||
'Hand Clap',
|
|
||||||
'Claves',
|
|
||||||
'Wood Block',
|
|
||||||
'Cowbell',
|
|
||||||
'Triangle',
|
|
||||||
'Bongo',
|
|
||||||
'Conga',
|
|
||||||
'Cabasa',
|
|
||||||
'Guiro',
|
|
||||||
'Vibraslap',
|
|
||||||
'Open Cuica'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of instrument names, used in the set instrument block.
|
|
||||||
* @type {string[]}
|
|
||||||
*/
|
|
||||||
const instrumentNames = [
|
|
||||||
'Piano',
|
|
||||||
'Electric Piano',
|
|
||||||
'Organ',
|
|
||||||
'Guitar',
|
|
||||||
'Electric Guitar',
|
|
||||||
'Bass',
|
|
||||||
'Pizzicato',
|
|
||||||
'Cello',
|
|
||||||
'Trombone',
|
|
||||||
'Clarinet',
|
|
||||||
'Saxophone',
|
|
||||||
'Flute',
|
|
||||||
'Wooden Flute',
|
|
||||||
'Bassoon',
|
|
||||||
'Choir',
|
|
||||||
'Vibraphone',
|
|
||||||
'Music Box',
|
|
||||||
'Steel Drum',
|
|
||||||
'Marimba',
|
|
||||||
'Synth Lead',
|
|
||||||
'Synth Pad'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for the music-related blocks in Scratch 3.0
|
* Class for the music-related blocks in Scratch 3.0
|
||||||
|
@ -78,27 +25,246 @@ class Scratch3MusicBlocks {
|
||||||
*/
|
*/
|
||||||
this.tempo = 60;
|
this.tempo = 60;
|
||||||
|
|
||||||
this.drumMenu = this._buildMenu(drumNames);
|
/**
|
||||||
this.instrumentMenu = this._buildMenu(instrumentNames);
|
* The number of drum sounds currently being played simultaneously.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._drumConcurrencyCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of audio buffers, one for each drum sound.
|
||||||
|
* @type {Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._drumBuffers = [];
|
||||||
|
|
||||||
|
this._loadAllDrumSounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a menu using an array of strings.
|
* Download and decode the full set of drum sounds, and store the audio buffers
|
||||||
* Used for creating the drum and instrument menus.
|
* in the drum buffers array.
|
||||||
* @param {string[]} names - An array of names.
|
* @TODO: Also load the instrument sounds here (rename this fn)
|
||||||
* @return {array} - An array of objects with text and value properties, for constructing a block menu.
|
*/
|
||||||
|
_loadAllDrumSounds () {
|
||||||
|
const loadingPromises = [];
|
||||||
|
this.DRUM_INFO.forEach((drumInfo, index) => {
|
||||||
|
const promise = this._loadSound(drumInfo.fileName, index, this._drumBuffers);
|
||||||
|
loadingPromises.push(promise);
|
||||||
|
});
|
||||||
|
Promise.all(loadingPromises).then(() => {
|
||||||
|
// @TODO: Update the extension status indicator.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download and decode a sound, and store the buffer in an array.
|
||||||
|
* @param {string} fileName - the audio file name.
|
||||||
|
* @param {number} index - the index at which to store the audio buffer.
|
||||||
|
* @param {array} bufferArray - the array of buffers in which to store it.
|
||||||
|
* @return {Promise} - a promise which will resolve once the sound has loaded.
|
||||||
|
*/
|
||||||
|
_loadSound (fileName, index, bufferArray) {
|
||||||
|
if (!this.runtime.storage) return;
|
||||||
|
if (!this.runtime.audioEngine) return;
|
||||||
|
return this.runtime.storage.load(this.runtime.storage.AssetType.Sound, fileName, 'mp3')
|
||||||
|
.then(soundAsset =>
|
||||||
|
this.runtime.audioEngine.audioContext.decodeAudioData(soundAsset.data.buffer)
|
||||||
|
)
|
||||||
|
.then(buffer => {
|
||||||
|
bufferArray[index] = buffer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create data for a menu in scratch-blocks format, consisting of an array of objects with text and
|
||||||
|
* value properties. The text is a translated string, and the value is one-indexed.
|
||||||
|
* @param {object[]} info - An array of info objects each having a name property.
|
||||||
|
* @return {array} - An array of objects with text and value properties.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_buildMenu (names) {
|
_buildMenu (info) {
|
||||||
const menu = [];
|
return info.map((entry, index) => {
|
||||||
for (let i = 0; i < names.length; i++) {
|
const obj = {};
|
||||||
const entry = {};
|
obj.text = entry.name;
|
||||||
const num = i + 1; // Menu numbers are one-indexed
|
obj.value = index + 1;
|
||||||
entry.text = `(${num}) ${names[i]}`;
|
return obj;
|
||||||
entry.value = String(num);
|
});
|
||||||
menu.push(entry);
|
|
||||||
}
|
}
|
||||||
return menu;
|
|
||||||
|
/**
|
||||||
|
* An array of translatable drum names and corresponding audio file names.
|
||||||
|
* @type {array}
|
||||||
|
*/
|
||||||
|
get DRUM_INFO () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: '(1) Snare Drum',
|
||||||
|
fileName: '1-snare'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(2) Bass Drum',
|
||||||
|
fileName: '2-bass-drum'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(3) Side Stick',
|
||||||
|
fileName: '3-side-stick'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(4) Crash Cymbal',
|
||||||
|
fileName: '4-crash-cymbal'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(5) Open Hi-Hat',
|
||||||
|
fileName: '5-open-hi-hat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(6) Closed Hi-Hat',
|
||||||
|
fileName: '6-closed-hi-hat'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(7) Tambourine',
|
||||||
|
fileName: '7-tambourine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(8) Hand Clap',
|
||||||
|
fileName: '8-hand-clap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(9) Claves',
|
||||||
|
fileName: '9-claves'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(10) Wood Block',
|
||||||
|
fileName: '10-wood-block'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(11) Cowbell',
|
||||||
|
fileName: '11-cowbell'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(12) Triangle',
|
||||||
|
fileName: '12-triangle'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(13) Bongo',
|
||||||
|
fileName: '13-bongo'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(14) Conga',
|
||||||
|
fileName: '14-conga'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(15) Cabasa',
|
||||||
|
fileName: '15-cabasa'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(16) Guiro',
|
||||||
|
fileName: '16-guiro'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(17) Vibraslap',
|
||||||
|
fileName: '17-vibraslap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(18) Cuica',
|
||||||
|
fileName: '18-cuica'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of translatable instrument names and corresponding audio file names.
|
||||||
|
* @type {array}
|
||||||
|
*/
|
||||||
|
get INSTRUMENT_INFO () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: '(1) Piano',
|
||||||
|
fileName: '1-piano'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(2) Electric Piano',
|
||||||
|
fileName: '2-electric-piano'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(3) Organ',
|
||||||
|
fileName: '3-organ'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(4) Guitar',
|
||||||
|
fileName: '4-guitar'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(5) Electric Guitar',
|
||||||
|
fileName: '5-electric-guitar'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(6) Bass',
|
||||||
|
fileName: '6-bass'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(7) Pizzicato',
|
||||||
|
fileName: '7-pizzicato'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(8) Cello',
|
||||||
|
fileName: '8-cello'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(9) Trombone',
|
||||||
|
fileName: '9-trombone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(10) Clarinet',
|
||||||
|
fileName: '10-clarinet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(11) Saxophone',
|
||||||
|
fileName: '11-saxophone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(12) Flute',
|
||||||
|
fileName: '12-flute'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(13) Wooden Flute',
|
||||||
|
fileName: '13-wooden-flute'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(14) Bassoon',
|
||||||
|
fileName: '14-bassoon'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(15) Choir',
|
||||||
|
fileName: '15-choir'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(16) Vibraphone',
|
||||||
|
fileName: '16-vibraphone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(17) Music Box',
|
||||||
|
fileName: '17-music-box'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(18) Steel Drum',
|
||||||
|
fileName: '18-steel-drum'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(19) Marimba',
|
||||||
|
fileName: '19-marimba'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(20) Synth Lead',
|
||||||
|
fileName: '20-synth-lead'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '(21) Synth Pad',
|
||||||
|
fileName: '21-synth-pad'
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,6 +309,14 @@ class Scratch3MusicBlocks {
|
||||||
return {min: 20, max: 500};
|
return {min: 20, max: 500};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of sounds to allow to play simultaneously.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
static get CONCURRENCY_LIMIT () {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Target} target - collect music state for this target.
|
* @param {Target} target - collect music state for this target.
|
||||||
* @returns {MusicState} the mutable music state associated with that target. This will be created if necessary.
|
* @returns {MusicState} the mutable music state associated with that target. This will be created if necessary.
|
||||||
|
@ -248,8 +422,8 @@ class Scratch3MusicBlocks {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
menus: {
|
menus: {
|
||||||
drums: this.drumMenu,
|
drums: this._buildMenu(this.DRUM_INFO),
|
||||||
instruments: this.instrumentMenu
|
instruments: this._buildMenu(this.INSTRUMENT_INFO)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -264,20 +438,41 @@ class Scratch3MusicBlocks {
|
||||||
playDrumForBeats (args, util) {
|
playDrumForBeats (args, util) {
|
||||||
if (this._stackTimerNeedsInit(util)) {
|
if (this._stackTimerNeedsInit(util)) {
|
||||||
let drum = Cast.toNumber(args.DRUM);
|
let drum = Cast.toNumber(args.DRUM);
|
||||||
|
drum = Math.round(drum);
|
||||||
drum -= 1; // drums are one-indexed
|
drum -= 1; // drums are one-indexed
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
drum = MathUtil.wrapClamp(drum, 0, this.DRUM_INFO.length - 1);
|
||||||
drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums - 1);
|
|
||||||
let beats = Cast.toNumber(args.BEATS);
|
let beats = Cast.toNumber(args.BEATS);
|
||||||
beats = this._clampBeats(beats);
|
beats = this._clampBeats(beats);
|
||||||
if (util.target.audioPlayer !== null) {
|
this._playDrumNum(util, drum);
|
||||||
util.target.audioPlayer.playDrumForBeats(drum, beats);
|
|
||||||
}
|
|
||||||
this._startStackTimer(util, this._beatsToSec(beats));
|
this._startStackTimer(util, this._beatsToSec(beats));
|
||||||
} else {
|
} else {
|
||||||
this._checkStackTimer(util);
|
this._checkStackTimer(util);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a drum sound using its 0-indexed number.
|
||||||
|
* @param {object} util - utility object provided by the runtime.
|
||||||
|
* @param {number} drumNum - the number of the drum to play.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_playDrumNum (util, drumNum) {
|
||||||
|
if (util.target.audioPlayer === null) return;
|
||||||
|
// If we're playing too many sounds, do not play the drum sound.
|
||||||
|
if (this._drumConcurrencyCounter > Scratch3MusicBlocks.CONCURRENCY_LIMIT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const outputNode = util.target.audioPlayer.getInputNode();
|
||||||
|
const bufferSource = this.runtime.audioEngine.audioContext.createBufferSource();
|
||||||
|
bufferSource.buffer = this._drumBuffers[drumNum];
|
||||||
|
bufferSource.connect(outputNode);
|
||||||
|
bufferSource.start();
|
||||||
|
this._drumConcurrencyCounter++;
|
||||||
|
bufferSource.onended = () => {
|
||||||
|
this._drumConcurrencyCounter--;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rest for some number of beats.
|
* Rest for some number of beats.
|
||||||
* @param {object} args - the block arguments.
|
* @param {object} args - the block arguments.
|
||||||
|
@ -359,10 +554,8 @@ class Scratch3MusicBlocks {
|
||||||
util.stackFrame.timer = new Timer();
|
util.stackFrame.timer = new Timer();
|
||||||
util.stackFrame.timer.start();
|
util.stackFrame.timer.start();
|
||||||
util.stackFrame.duration = duration;
|
util.stackFrame.duration = duration;
|
||||||
if (util.stackFrame.duration > 0) {
|
|
||||||
util.yield();
|
util.yield();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the stack timer, and if its time is not up yet, yield the thread.
|
* Check the stack timer, and if its time is not up yet, yield the thread.
|
|
@ -1,10 +1,9 @@
|
||||||
const test = require('tap').test;
|
const test = require('tap').test;
|
||||||
const Music = require('../../src/blocks/scratch3_music');
|
const Music = require('../../src/extensions/scratch3_music/index.js');
|
||||||
let playedDrum;
|
let playedDrum;
|
||||||
let playedInstrument;
|
let playedInstrument;
|
||||||
const runtime = {
|
const runtime = {
|
||||||
audioEngine: {
|
audioEngine: {
|
||||||
numDrums: 3,
|
|
||||||
numInstruments: 3,
|
numInstruments: 3,
|
||||||
instrumentPlayer: {
|
instrumentPlayer: {
|
||||||
loadInstrument: instrument => (playedInstrument = instrument)
|
loadInstrument: instrument => (playedInstrument = instrument)
|
||||||
|
@ -12,13 +11,14 @@ const runtime = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const blocks = new Music(runtime);
|
const blocks = new Music(runtime);
|
||||||
|
blocks._playDrumNum = (util, drum) => (playedDrum = drum);
|
||||||
|
|
||||||
const util = {
|
const util = {
|
||||||
|
stackFrame: Object.create(null),
|
||||||
target: {
|
target: {
|
||||||
audioPlayer: {
|
audioPlayer: null
|
||||||
playDrumForBeats: drum => (playedDrum = drum)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
stackFrame: Object.create(null)
|
yield: () => null
|
||||||
};
|
};
|
||||||
|
|
||||||
test('playDrum uses 1-indexing and wrap clamps', t => {
|
test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||||
|
@ -26,7 +26,7 @@ test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||||
blocks.playDrumForBeats(args, util);
|
blocks.playDrumForBeats(args, util);
|
||||||
t.strictEqual(playedDrum, 0);
|
t.strictEqual(playedDrum, 0);
|
||||||
|
|
||||||
args = {DRUM: runtime.audioEngine.numDrums + 1};
|
args = {DRUM: blocks.DRUM_INFO.length + 1};
|
||||||
blocks.playDrumForBeats(args, util);
|
blocks.playDrumForBeats(args, util);
|
||||||
t.strictEqual(playedDrum, 0);
|
t.strictEqual(playedDrum, 0);
|
||||||
|
|
|
@ -60,7 +60,13 @@ module.exports = [
|
||||||
libraryTarget: 'commonjs2',
|
libraryTarget: 'commonjs2',
|
||||||
path: path.resolve(__dirname, 'dist/node'),
|
path: path.resolve(__dirname, 'dist/node'),
|
||||||
filename: '[name].js'
|
filename: '[name].js'
|
||||||
}
|
},
|
||||||
|
plugins: base.plugins.concat([
|
||||||
|
new CopyWebpackPlugin([{
|
||||||
|
from: './src/extensions/scratch3_music/assets',
|
||||||
|
to: 'assets/scratch3_music'
|
||||||
|
}])
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
// Playground
|
// Playground
|
||||||
defaultsDeep({}, base, {
|
defaultsDeep({}, base, {
|
||||||
|
|
Loading…
Reference in a new issue