Skip to content

Old MIDI drum block support, featuring approximations via pitch shift #1613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 124 additions & 10 deletions src/extensions/scratch3_music/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -725,6 +783,27 @@ class Scratch3MusicBlocks {
}
}
},
{
opcode: 'midiPlayDrumForBeats',
blockType: BlockType.COMMAND,
text: formatMessage({
id: 'music.midiPlayDrumForBeats',
default: 'play drum [DRUM] for [BEATS] beats',

This comment was marked as abuse.

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,
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -886,16 +995,21 @@ class Scratch3MusicBlocks {
}

const engine = util.runtime.audioEngine;
const chain = engine.createEffectChain();
chain.setEffectsFromTarget(util.target);
player.connect(chain);
const context = engine.audioContext;
const volumeGain = context.createGain();
volumeGain.gain.setValueAtTime(util.target.volume / 100, engine.currentTime);
volumeGain.connect(engine.getInputNode());

this._concurrencyCounter++;
player.once('stop', () => {
this._concurrencyCounter--;
});

player.play();
// Connect the player to the gain node.
player.connect({getInputNode () {
return volumeGain;
}});
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/serialization/sb2_specmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,21 @@ const specMap = {
}
]
},
'drum:duration:elapsed:from:': {
opcode: 'music_midiPlayDrumForBeats',
argMap: [
{
type: 'input',
inputOp: 'math_number',
inputName: 'DRUM'
},
{
type: 'input',
inputOp: 'math_number',
inputName: 'BEATS'
}
]
},
'rest:elapsed:from:': {
opcode: 'music_restForBeats',
argMap: [
Expand Down