mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Merge pull request #769 from ericrosenbaum/feature/extension-music
Move music blocks into an extension
This commit is contained in:
commit
61c7a2473e
7 changed files with 438 additions and 124 deletions
371
src/blocks/scratch3_music.js
Normal file
371
src/blocks/scratch3_music.js
Normal file
|
@ -0,0 +1,371 @@
|
|||
const ArgumentType = require('../extension-support/argument-type');
|
||||
const BlockType = require('../extension-support/block-type');
|
||||
const Clone = require('../util/clone');
|
||||
const Cast = require('../util/cast');
|
||||
const MathUtil = require('../util/math-util');
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {Runtime} runtime - the runtime instantiating this block package.
|
||||
* @constructor
|
||||
*/
|
||||
class Scratch3MusicBlocks {
|
||||
constructor (runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
this.drumMenu = this._buildMenu(drumNames);
|
||||
this.instrumentMenu = this._buildMenu(instrumentNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a menu using an array of strings.
|
||||
* Used for creating the drum and instrument menus.
|
||||
* @param {string[]} names - An array of names.
|
||||
* @return {array} - An array of objects with text and value properties, for constructing a block menu.
|
||||
* @private
|
||||
*/
|
||||
_buildMenu (names) {
|
||||
const menu = [];
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const entry = {};
|
||||
const num = i + 1; // Menu numbers are one-indexed
|
||||
entry.text = `(${num}) ${names[i]}`;
|
||||
entry.value = String(num);
|
||||
menu.push(entry);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key to load & store a target's music-related state.
|
||||
* @type {string}
|
||||
*/
|
||||
static get STATE_KEY () {
|
||||
return 'Scratch.music';
|
||||
}
|
||||
|
||||
/**
|
||||
* The default music-related state, to be used when a target has no existing music state.
|
||||
* @type {MusicState}
|
||||
*/
|
||||
static get DEFAULT_MUSIC_STATE () {
|
||||
return {
|
||||
currentInstrument: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum and maximum MIDI note numbers, for clamping the input to play note.
|
||||
* @type {{min: number, max: number}}
|
||||
*/
|
||||
static get MIDI_NOTE_RANGE () {
|
||||
return {min: 36, max: 96}; // C2 to C7
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum and maximum beat values, for clamping the duration of play note, play drum and rest.
|
||||
* 100 beats at the default tempo of 60bpm is 100 seconds.
|
||||
* @type {{min: number, max: number}}
|
||||
*/
|
||||
static get BEAT_RANGE () {
|
||||
return {min: 0, max: 100};
|
||||
}
|
||||
|
||||
/** The minimum and maximum tempo values, in bpm.
|
||||
* @type {{min: number, max: number}}
|
||||
*/
|
||||
static get TEMPO_RANGE () {
|
||||
return {min: 20, max: 500};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @private
|
||||
*/
|
||||
_getMusicState (target) {
|
||||
let musicState = target.getCustomState(Scratch3MusicBlocks.STATE_KEY);
|
||||
if (!musicState) {
|
||||
musicState = Clone.simple(Scratch3MusicBlocks.DEFAULT_MUSIC_STATE);
|
||||
target.setCustomState(Scratch3MusicBlocks.STATE_KEY, musicState);
|
||||
}
|
||||
return musicState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} metadata for this extension and its blocks.
|
||||
*/
|
||||
getInfo () {
|
||||
return {
|
||||
id: 'music',
|
||||
name: 'Music',
|
||||
blocks: [
|
||||
{
|
||||
opcode: 'playDrumForBeats',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'play drum [DRUM] for [BEATS] beats',
|
||||
arguments: {
|
||||
DRUM: {
|
||||
type: ArgumentType.NUMBER,
|
||||
menu: 'drums',
|
||||
defaultValue: 1
|
||||
},
|
||||
BEATS: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 0.25
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'restForBeats',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'rest for [BEATS] beats',
|
||||
arguments: {
|
||||
BEATS: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 0.25
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'playNoteForBeats',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'play note [NOTE] for [BEATS] beats',
|
||||
arguments: {
|
||||
NOTE: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 60
|
||||
},
|
||||
BEATS: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 0.25
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'setInstrument',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'set instrument to [INSTRUMENT]',
|
||||
arguments: {
|
||||
INSTRUMENT: {
|
||||
type: ArgumentType.NUMBER,
|
||||
menu: 'instruments',
|
||||
defaultValue: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'setTempo',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'set tempo to [TEMPO]',
|
||||
arguments: {
|
||||
TEMPO: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'changeTempo',
|
||||
blockType: BlockType.COMMAND,
|
||||
text: 'change tempo by [TEMPO]',
|
||||
arguments: {
|
||||
TEMPO: {
|
||||
type: ArgumentType.NUMBER,
|
||||
defaultValue: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
opcode: 'getTempo',
|
||||
text: 'tempo',
|
||||
blockType: BlockType.REPORTER
|
||||
}
|
||||
],
|
||||
menus: {
|
||||
drums: this.drumMenu,
|
||||
instruments: this.instrumentMenu
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a drum sound for some number of beats.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @property {int} DRUM - the number of the drum to play.
|
||||
* @property {number} BEATS - the duration in beats of the drum sound.
|
||||
* @return {Promise} - a promise which will resolve at the end of the duration.
|
||||
*/
|
||||
playDrumForBeats (args, util) {
|
||||
let drum = Cast.toNumber(args.DRUM);
|
||||
drum -= 1; // drums are one-indexed
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums - 1);
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
if (util.target.audioPlayer === null) return;
|
||||
return util.target.audioPlayer.playDrumForBeats(drum, beats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest for some number of beats.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @property {number} BEATS - the duration in beats of the rest.
|
||||
* @return {Promise} - a promise which will resolve at the end of the duration.
|
||||
*/
|
||||
restForBeats (args) {
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.waitForBeats(beats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a note using the current musical instrument for some number of beats.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @property {number} NOTE - the pitch of the note to play, interpreted as a MIDI note number.
|
||||
* @property {number} BEATS - the duration in beats of the note.
|
||||
* @return {Promise} - a promise which will resolve at the end of the duration.
|
||||
*/
|
||||
playNoteForBeats (args, util) {
|
||||
let note = Cast.toNumber(args.NOTE);
|
||||
note = MathUtil.clamp(note, Scratch3MusicBlocks.MIDI_NOTE_RANGE.min, Scratch3MusicBlocks.MIDI_NOTE_RANGE.max);
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
const musicState = this._getMusicState(util.target);
|
||||
const inst = musicState.currentInstrument;
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.playNoteForBeatsWithInstAndVol(note, beats, inst, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an instrument for playing notes.
|
||||
* @param {object} args - the block arguments.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @property {int} INSTRUMENT - the number of the instrument to select.
|
||||
* @return {Promise} - a promise which will resolve once the instrument has loaded.
|
||||
*/
|
||||
setInstrument (args, util) {
|
||||
const musicState = this._getMusicState(util.target);
|
||||
let instNum = Cast.toNumber(args.INSTRUMENT);
|
||||
instNum -= 1; // instruments are one-indexed
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
instNum = MathUtil.wrapClamp(instNum, 0, this.runtime.audioEngine.numInstruments - 1);
|
||||
musicState.currentInstrument = instNum;
|
||||
return this.runtime.audioEngine.instrumentPlayer.loadInstrument(musicState.currentInstrument);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp a duration in beats to the allowed min and max duration.
|
||||
* @param {number} beats - a duration in beats.
|
||||
* @return {number} - the clamped duration.
|
||||
* @private
|
||||
*/
|
||||
_clampBeats (beats) {
|
||||
return MathUtil.clamp(beats, Scratch3MusicBlocks.BEAT_RANGE.min, Scratch3MusicBlocks.BEAT_RANGE.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current tempo to a new value.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} TEMPO - the tempo, in beats per minute.
|
||||
*/
|
||||
setTempo (args) {
|
||||
const tempo = Cast.toNumber(args.TEMPO);
|
||||
this._updateTempo(tempo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current tempo by some amount.
|
||||
* @param {object} args - the block arguments.
|
||||
* @property {number} TEMPO - the amount to change the tempo, in beats per minute.
|
||||
*/
|
||||
changeTempo (args) {
|
||||
const change = Cast.toNumber(args.TEMPO);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
const tempo = change + this.runtime.audioEngine.currentTempo;
|
||||
this._updateTempo(tempo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current tempo, clamping it to the min and max allowable range.
|
||||
* @param {number} tempo - the tempo to set, in beats per minute.
|
||||
* @private
|
||||
*/
|
||||
_updateTempo (tempo) {
|
||||
tempo = MathUtil.clamp(tempo, Scratch3MusicBlocks.TEMPO_RANGE.min, Scratch3MusicBlocks.TEMPO_RANGE.max);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
this.runtime.audioEngine.setTempo(tempo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current tempo.
|
||||
* @return {number} - the current tempo, in beats per minute.
|
||||
*/
|
||||
getTempo () {
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.currentTempo;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Scratch3MusicBlocks;
|
|
@ -91,10 +91,6 @@ class Scratch3SoundBlocks {
|
|||
sound_play: this.playSound,
|
||||
sound_playuntildone: this.playSoundAndWait,
|
||||
sound_stopallsounds: this.stopAllSounds,
|
||||
sound_playnoteforbeats: this.playNoteForBeats,
|
||||
sound_playdrumforbeats: this.playDrumForBeats,
|
||||
sound_restforbeats: this.restForBeats,
|
||||
sound_setinstrumentto: this.setInstrument,
|
||||
sound_seteffectto: this.setEffect,
|
||||
sound_changeeffectby: this.changeEffect,
|
||||
sound_cleareffects: this.clearEffects,
|
||||
|
@ -103,10 +99,7 @@ class Scratch3SoundBlocks {
|
|||
sound_effects_menu: this.effectsMenu,
|
||||
sound_setvolumeto: this.setVolume,
|
||||
sound_changevolumeby: this.changeVolume,
|
||||
sound_volume: this.getVolume,
|
||||
sound_settempotobpm: this.setTempo,
|
||||
sound_changetempoby: this.changeTempo,
|
||||
sound_tempo: this.getTempo
|
||||
sound_volume: this.getVolume
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -167,50 +160,6 @@ class Scratch3SoundBlocks {
|
|||
util.target.audioPlayer.stopAllSounds();
|
||||
}
|
||||
|
||||
playNoteForBeats (args, util) {
|
||||
let note = Cast.toNumber(args.NOTE);
|
||||
note = MathUtil.clamp(note, Scratch3SoundBlocks.MIDI_NOTE_RANGE.min, Scratch3SoundBlocks.MIDI_NOTE_RANGE.max);
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
const soundState = this._getSoundState(util.target);
|
||||
const inst = soundState.currentInstrument;
|
||||
const vol = soundState.volume;
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.playNoteForBeatsWithInstAndVol(note, beats, inst, vol);
|
||||
}
|
||||
|
||||
playDrumForBeats (args, util) {
|
||||
let drum = Cast.toNumber(args.DRUM);
|
||||
drum -= 1; // drums are one-indexed
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums - 1);
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
if (util.target.audioPlayer === null) return;
|
||||
return util.target.audioPlayer.playDrumForBeats(drum, beats);
|
||||
}
|
||||
|
||||
restForBeats (args) {
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.waitForBeats(beats);
|
||||
}
|
||||
|
||||
_clampBeats (beats) {
|
||||
return MathUtil.clamp(beats, Scratch3SoundBlocks.BEAT_RANGE.min, Scratch3SoundBlocks.BEAT_RANGE.max);
|
||||
}
|
||||
|
||||
setInstrument (args, util) {
|
||||
const soundState = this._getSoundState(util.target);
|
||||
let instNum = Cast.toNumber(args.INSTRUMENT);
|
||||
instNum -= 1; // instruments are one-indexed
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
instNum = MathUtil.wrapClamp(instNum, 0, this.runtime.audioEngine.numInstruments - 1);
|
||||
soundState.currentInstrument = instNum;
|
||||
return this.runtime.audioEngine.instrumentPlayer.loadInstrument(soundState.currentInstrument);
|
||||
}
|
||||
|
||||
setEffect (args, util) {
|
||||
this._updateEffect(args, util, false);
|
||||
}
|
||||
|
@ -273,29 +222,6 @@ class Scratch3SoundBlocks {
|
|||
return soundState.volume;
|
||||
}
|
||||
|
||||
setTempo (args) {
|
||||
const tempo = Cast.toNumber(args.TEMPO);
|
||||
this._updateTempo(tempo);
|
||||
}
|
||||
|
||||
changeTempo (args) {
|
||||
const change = Cast.toNumber(args.TEMPO);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
const tempo = change + this.runtime.audioEngine.currentTempo;
|
||||
this._updateTempo(tempo);
|
||||
}
|
||||
|
||||
_updateTempo (tempo) {
|
||||
tempo = MathUtil.clamp(tempo, Scratch3SoundBlocks.TEMPO_RANGE.min, Scratch3SoundBlocks.TEMPO_RANGE.max);
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
this.runtime.audioEngine.setTempo(tempo);
|
||||
}
|
||||
|
||||
getTempo () {
|
||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||
return this.runtime.audioEngine.currentTempo;
|
||||
}
|
||||
|
||||
soundsMenu (args) {
|
||||
return args.SOUND_MENU;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ const BlockType = require('./block-type');
|
|||
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
|
||||
const Scratch3PenBlocks = require('../blocks/scratch3_pen');
|
||||
const Scratch3WeDo2Blocks = require('../blocks/scratch3_wedo2');
|
||||
const Scratch3MusicBlocks = require('../blocks/scratch3_music');
|
||||
const builtinExtensions = {
|
||||
pen: Scratch3PenBlocks,
|
||||
wedo2: Scratch3WeDo2Blocks
|
||||
wedo2: Scratch3WeDo2Blocks,
|
||||
music: Scratch3MusicBlocks
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -418,7 +418,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'playDrum': {
|
||||
opcode: 'sound_playdrumforbeats',
|
||||
opcode: 'music.playDrumForBeats',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -433,7 +433,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'rest:elapsed:from:': {
|
||||
opcode: 'sound_restforbeats',
|
||||
opcode: 'music.restForBeats',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -443,7 +443,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'noteOn:duration:elapsed:from:': {
|
||||
opcode: 'sound_playnoteforbeats',
|
||||
opcode: 'music.playNoteForBeats',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -458,7 +458,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'instrument:': {
|
||||
opcode: 'sound_setinstrumentto',
|
||||
opcode: 'music.setInstrument',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -493,7 +493,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'changeTempoBy:': {
|
||||
opcode: 'sound_changetempoby',
|
||||
opcode: 'music.changeTempo',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -503,7 +503,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'setTempoTo:': {
|
||||
opcode: 'sound_settempotobpm',
|
||||
opcode: 'music.setTempo',
|
||||
argMap: [
|
||||
{
|
||||
type: 'input',
|
||||
|
@ -513,7 +513,7 @@ const specMap = {
|
|||
]
|
||||
},
|
||||
'tempo': {
|
||||
opcode: 'sound_tempo',
|
||||
opcode: 'music.getTempo',
|
||||
argMap: [
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
const Worker = require('tiny-worker');
|
||||
const path = require('path');
|
||||
const test = require('tap').test;
|
||||
const makeTestStorage = require('../fixtures/make-test-storage');
|
||||
const extract = require('../fixtures/extract');
|
||||
const VirtualMachine = require('../../src/index');
|
||||
const dispatch = require('../../src/dispatch/central-dispatch');
|
||||
|
||||
const uri = path.resolve(__dirname, '../fixtures/sound.sb2');
|
||||
const project = extract(uri);
|
||||
|
||||
// By default Central Dispatch works with the Worker class built into the browser. Tell it to use TinyWorker instead.
|
||||
dispatch.workerClass = Worker;
|
||||
|
||||
test('sound', t => {
|
||||
const vm = new VirtualMachine();
|
||||
vm.attachStorage(makeTestStorage());
|
||||
|
|
48
test/unit/blocks_music.js
Normal file
48
test/unit/blocks_music.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
const test = require('tap').test;
|
||||
const Music = require('../../src/blocks/scratch3_music');
|
||||
let playedDrum;
|
||||
let playedInstrument;
|
||||
const runtime = {
|
||||
audioEngine: {
|
||||
numDrums: 3,
|
||||
numInstruments: 3,
|
||||
instrumentPlayer: {
|
||||
loadInstrument: instrument => (playedInstrument = instrument)
|
||||
}
|
||||
}
|
||||
};
|
||||
const blocks = new Music(runtime);
|
||||
const util = {
|
||||
target: {
|
||||
audioPlayer: {
|
||||
playDrumForBeats: drum => (playedDrum = drum)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||
let args = {DRUM: 1};
|
||||
blocks.playDrumForBeats(args, util);
|
||||
t.strictEqual(playedDrum, 0);
|
||||
|
||||
args = {DRUM: runtime.audioEngine.numDrums + 1};
|
||||
blocks.playDrumForBeats(args, util);
|
||||
t.strictEqual(playedDrum, 0);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('setInstrument uses 1-indexing and wrap clamps', t => {
|
||||
// Stub getMusicState
|
||||
blocks._getMusicState = () => ({});
|
||||
|
||||
let args = {INSTRUMENT: 1};
|
||||
blocks.setInstrument(args, util);
|
||||
t.strictEqual(playedInstrument, 0);
|
||||
|
||||
args = {INSTRUMENT: runtime.audioEngine.numInstruments + 1};
|
||||
blocks.setInstrument(args, util);
|
||||
t.strictEqual(playedInstrument, 0);
|
||||
|
||||
t.end();
|
||||
});
|
|
@ -1,18 +1,8 @@
|
|||
const test = require('tap').test;
|
||||
const Sound = require('../../src/blocks/scratch3_sound');
|
||||
let playedSound;
|
||||
let playedDrum;
|
||||
let playedInstrument;
|
||||
const runtime = {
|
||||
audioEngine: {
|
||||
numDrums: 3,
|
||||
numInstruments: 3,
|
||||
instrumentPlayer: {
|
||||
loadInstrument: instrument => (playedInstrument = instrument)
|
||||
}
|
||||
}
|
||||
};
|
||||
const blocks = new Sound(runtime);
|
||||
|
||||
const blocks = new Sound();
|
||||
const util = {
|
||||
target: {
|
||||
sprite: {
|
||||
|
@ -24,8 +14,7 @@ const util = {
|
|||
]
|
||||
},
|
||||
audioPlayer: {
|
||||
playSound: soundId => (playedSound = soundId),
|
||||
playDrumForBeats: drum => (playedDrum = drum)
|
||||
playSound: soundId => (playedSound = soundId)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -82,30 +71,3 @@ test('playSound prioritizes sound name if given a string', t => {
|
|||
t.strictEqual(playedSound, 'fourth soundId');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||
let args = {DRUM: 1};
|
||||
blocks.playDrumForBeats(args, util);
|
||||
t.strictEqual(playedDrum, 0);
|
||||
|
||||
args = {DRUM: runtime.audioEngine.numDrums + 1};
|
||||
blocks.playDrumForBeats(args, util);
|
||||
t.strictEqual(playedDrum, 0);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('setInstrument uses 1-indexing and wrap clamps', t => {
|
||||
// Stub getSoundState
|
||||
blocks._getSoundState = () => ({});
|
||||
|
||||
let args = {INSTRUMENT: 1};
|
||||
blocks.setInstrument(args, util);
|
||||
t.strictEqual(playedInstrument, 0);
|
||||
|
||||
args = {INSTRUMENT: runtime.audioEngine.numInstruments + 1};
|
||||
blocks.setInstrument(args, util);
|
||||
t.strictEqual(playedInstrument, 0);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue