Add support for scratch 1.x MIDI drum blocks

This commit is contained in:
Eric Rosenbaum 2018-11-28 15:39:00 -05:00
parent e0550db404
commit f617723348

View file

@ -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. * The key to load & store a target's music-related state.
* @type {string} * @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', opcode: 'restForBeats',
blockType: BlockType.COMMAND, blockType: BlockType.COMMAND,
@ -846,14 +925,44 @@ class Scratch3MusicBlocks {
* @property {number} BEATS - the duration in beats of the drum sound. * @property {number} BEATS - the duration in beats of the drum sound.
*/ */
playDrumForBeats (args, util) { 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)) { if (this._stackTimerNeedsInit(util)) {
let drum = Cast.toNumber(args.DRUM); drumNum = Cast.toNumber(drumNum);
drum = Math.round(drum); drumNum = Math.round(drumNum);
drum -= 1; // drums are one-indexed drumNum -= 1; // drums are one-indexed
drum = MathUtil.wrapClamp(drum, 0, this.DRUM_INFO.length - 1); drumNum = MathUtil.wrapClamp(drumNum, 0, this.DRUM_INFO.length - 1);
let beats = Cast.toNumber(args.BEATS); beats = Cast.toNumber(beats);
beats = this._clampBeats(beats); beats = this._clampBeats(beats);
this._playDrumNum(util, drum); this._playDrumNum(util, drumNum);
this._startStackTimer(util, this._beatsToSec(beats)); this._startStackTimer(util, this._beatsToSec(beats));
} else { } else {
this._checkStackTimer(util); this._checkStackTimer(util);