From f61772334857926f63b4c3f0d917d7e63d5aabd1 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Wed, 28 Nov 2018 15:39:00 -0500 Subject: [PATCH] Add support for scratch 1.x MIDI drum blocks --- src/extensions/scratch3_music/index.js | 123 +++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 7 deletions(-) diff --git a/src/extensions/scratch3_music/index.js b/src/extensions/scratch3_music/index.js index 59db62f44..e1693e405 100644 --- a/src/extensions/scratch3_music/index.js +++ b/src/extensions/scratch3_music/index.js @@ -611,6 +611,64 @@ class Scratch3MusicBlocks { ]; } + /** + * An array that is a mapping from MIDI drum numbers in range (35..81) to Scratch drum numbers. + * It's in the format [drumNum, pitch, decay]. + * The pitch and decay properties are not currently being used. + * @type {Array[]} + */ + get MIDI_DRUMS () { + return [ + [1, -4], // "BassDrum" in 2.0, "Bass Drum" in 3.0 (which was "Tom" in 2.0) + [1, 0], // Same as just above + [2, 0], + [0, 0], + [7, 0], + [0, 2], + [1, -6, 4], + [5, 0], + [1, -3, 3.2], + [5, 0], // "HiHatPedal" in 2.0, "Closed Hi-Hat" in 3.0 + [1, 0, 3], + [4, -8], + [1, 4, 3], + [1, 7, 2.7], + [3, -8], + [1, 10, 2.7], + [4, -2], + [3, -11], + [4, 2], + [6, 0], + [3, 0, 3.5], + [10, 0], + [3, -8, 3.5], + [16, -6], + [4, 2], + [12, 2], + [12, 0], + [13, 0, 0.2], + [13, 0, 2], + [13, -5, 2], + [12, 12], + [12, 5], + [10, 19], + [10, 12], + [14, 0], + [14, 0], // "Maracas" in 2.0, "Cabasa" in 3.0 (TODO: pitch up?) + [17, 12], + [17, 5], + [15, 0], // "GuiroShort" in 2.0, "Guiro" in 3.0 (which was "GuiroLong" in 2.0) (TODO: decay?) + [15, 0], + [8, 0], + [9, 0], + [9, -4], + [17, -5], + [17, 0], + [11, -6, 1], + [11, -6, 3] + ]; + } + /** * The key to load & store a target's music-related state. * @type {string} @@ -725,6 +783,27 @@ class Scratch3MusicBlocks { } } }, + { + opcode: 'midiPlayDrumForBeats', + blockType: BlockType.COMMAND, + text: formatMessage({ + id: 'music.midiPlayDrumForBeats', + default: 'play drum [DRUM] for [BEATS] beats', + description: 'play drum sample for a number of beats according to a mapping of MIDI codes' + }), + arguments: { + DRUM: { + type: ArgumentType.NUMBER, + menu: 'DRUM', + defaultValue: 1 + }, + BEATS: { + type: ArgumentType.NUMBER, + defaultValue: 0.25 + } + }, + hideFromPalette: true + }, { opcode: 'restForBeats', blockType: BlockType.COMMAND, @@ -846,14 +925,44 @@ class Scratch3MusicBlocks { * @property {number} BEATS - the duration in beats of the drum sound. */ playDrumForBeats (args, util) { + this._playDrumForBeats(args.DRUM, args.BEATS, util); + } + + /** + * Play a drum sound for some number of beats according to the range of "MIDI" drum codes supported. + * This block is implemented for compatibility with old Scratch projects that use the + * 'drum:duration:elapsed:from:' block. + * @param {object} args - the block arguments. + * @param {object} util - utility object provided by the runtime. + */ + midiPlayDrumForBeats (args, util) { + let drumNum = Cast.toNumber(args.DRUM); + drumNum = Math.round(drumNum); + const midiDescription = this.MIDI_DRUMS[drumNum - 35]; + if (midiDescription) { + drumNum = midiDescription[0]; + } else { + drumNum = 2; // Default instrument used in Scratch 2.0 + } + drumNum += 1; // drumNum input to _playDrumForBeats is one-indexed + this._playDrumForBeats(drumNum, args.BEATS, util); + } + + /** + * Internal code to play a drum sound for some number of beats. + * @param {number} drumNum - the drum number. + * @param {beats} beats - the duration in beats to pause after playing the sound. + * @param {object} util - utility object provided by the runtime. + */ + _playDrumForBeats (drumNum, beats, util) { if (this._stackTimerNeedsInit(util)) { - let drum = Cast.toNumber(args.DRUM); - drum = Math.round(drum); - drum -= 1; // drums are one-indexed - drum = MathUtil.wrapClamp(drum, 0, this.DRUM_INFO.length - 1); - let beats = Cast.toNumber(args.BEATS); + drumNum = Cast.toNumber(drumNum); + drumNum = Math.round(drumNum); + drumNum -= 1; // drums are one-indexed + drumNum = MathUtil.wrapClamp(drumNum, 0, this.DRUM_INFO.length - 1); + beats = Cast.toNumber(beats); beats = this._clampBeats(beats); - this._playDrumNum(util, drum); + this._playDrumNum(util, drumNum); this._startStackTimer(util, this._beatsToSec(beats)); } else { this._checkStackTimer(util); @@ -863,7 +972,7 @@ class Scratch3MusicBlocks { /** * 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. + * @param {number} drumNum - the number of the drum to play. * @private */ _playDrumNum (util, drumNum) {