Skip to content
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

Revert #1260, #1258, #1239 #1262

Merged
merged 1 commit into from
Jun 22, 2018
Merged
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
47 changes: 26 additions & 21 deletions src/blocks/scratch3_sound.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ class Scratch3SoundBlocks {
if (!soundState) {
soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE);
target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState);
target.soundEffects = soundState.effects;
}
return soundState;
}
Expand Down Expand Up @@ -140,19 +139,20 @@ class Scratch3SoundBlocks {
}

playSound (args, util) {
// Don't return the promise, it's the only difference for AndWait
this.playSoundAndWait(args, util);
const index = this._getSoundIndex(args.SOUND_MENU, util);
if (index >= 0) {
const soundId = util.target.sprite.sounds[index].soundId;
if (util.target.audioPlayer === null) return;
util.target.audioPlayer.playSound(soundId);
}
}

playSoundAndWait (args, util) {
const index = this._getSoundIndex(args.SOUND_MENU, util);
if (index >= 0) {
const {target} = util;
const {sprite} = target;
const {soundId} = sprite.sounds[index];
if (sprite.soundBank) {
return sprite.soundBank.playSound(target, soundId);
}
const soundId = util.target.sprite.sounds[index].soundId;
if (util.target.audioPlayer === null) return;
return util.target.audioPlayer.playSound(soundId);
}
}

Expand Down Expand Up @@ -199,9 +199,8 @@ class Scratch3SoundBlocks {
}

_stopAllSoundsForTarget (target) {
if (target.sprite.soundBank) {
target.sprite.soundBank.stopAllSounds(target);
}
if (target.audioPlayer === null) return;
target.audioPlayer.stopAllSounds();
}

setEffect (args, util) {
Expand All @@ -225,19 +224,23 @@ class Scratch3SoundBlocks {
soundState.effects[effect] = value;
}

const {min, max} = Scratch3SoundBlocks.EFFECT_RANGE[effect];
soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], min, max);
const effectRange = Scratch3SoundBlocks.EFFECT_RANGE[effect];
soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], effectRange.min, effectRange.max);

if (util.target.audioPlayer === null) return;
util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);

this._syncEffectsForTarget(util.target);
// Yield until the next tick.
return Promise.resolve();
}

_syncEffectsForTarget (target) {
if (!target || !target.sprite.soundBank) return;
target.soundEffects = this._getSoundState(target).effects;

target.sprite.soundBank.setEffects(target);
if (!target || !target.audioPlayer) return;
const soundState = this._getSoundState(target);
for (const effect in soundState.effects) {
if (!soundState.effects.hasOwnProperty(effect)) continue;
target.audioPlayer.setEffect(effect, soundState.effects[effect]);
}
}

clearEffects (args, util) {
Expand All @@ -250,7 +253,8 @@ class Scratch3SoundBlocks {
if (!soundState.effects.hasOwnProperty(effect)) continue;
soundState.effects[effect] = 0;
}
this._syncEffectsForTarget(target);
if (target.audioPlayer === null) return;
target.audioPlayer.clearEffects();
}

_clearEffectsForAllTargets () {
Expand All @@ -274,7 +278,8 @@ class Scratch3SoundBlocks {
_updateVolume (volume, util) {
volume = MathUtil.clamp(volume, 0, 100);
util.target.volume = volume;
this._syncEffectsForTarget(util.target);
if (util.target.audioPlayer === null) return;
util.target.audioPlayer.setVolume(util.target.volume);

// Yield until the next tick.
return Promise.resolve();
Expand Down
151 changes: 64 additions & 87 deletions src/extensions/scratch3_music/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,18 @@ class Scratch3MusicBlocks {
this._concurrencyCounter = 0;

/**
* An array of sound players, one for each drum sound.
* An array of audio buffers, one for each drum sound.
* @type {Array}
* @private
*/
this._drumPlayers = [];
this._drumBuffers = [];

/**
* An array of arrays of sound players. Each instrument has one or more audio players.
* An array of arrays of audio buffers. Each instrument has one or more audio buffers.
* @type {Array[]}
* @private
*/
this._instrumentPlayerArrays = [];

/**
* An array of arrays of sound players. Each instrument mya have an audio player for each playable note.
* @type {Array[]}
* @private
*/
this._instrumentPlayerNoteArrays = [];
this._instrumentBufferArrays = [];

/**
* An array of audio bufferSourceNodes. Each time you play an instrument or drum sound,
Expand All @@ -94,15 +87,14 @@ class Scratch3MusicBlocks {
const loadingPromises = [];
this.DRUM_INFO.forEach((drumInfo, index) => {
const filePath = `drums/${drumInfo.fileName}`;
const promise = this._storeSound(filePath, index, this._drumPlayers);
const promise = this._storeSound(filePath, index, this._drumBuffers);
loadingPromises.push(promise);
});
this.INSTRUMENT_INFO.forEach((instrumentInfo, instrumentIndex) => {
this._instrumentPlayerArrays[instrumentIndex] = [];
this._instrumentPlayerNoteArrays[instrumentIndex] = [];
this._instrumentBufferArrays[instrumentIndex] = [];
instrumentInfo.samples.forEach((sample, noteIndex) => {
const filePath = `instruments/${instrumentInfo.dirName}/${sample}`;
const promise = this._storeSound(filePath, noteIndex, this._instrumentPlayerArrays[instrumentIndex]);
const promise = this._storeSound(filePath, noteIndex, this._instrumentBufferArrays[instrumentIndex]);
loadingPromises.push(promise);
});
});
Expand All @@ -112,22 +104,22 @@ class Scratch3MusicBlocks {
}

/**
* Decode a sound and store the player in an array.
* Decode a sound and store the buffer in an array.
* @param {string} filePath - the audio file name.
* @param {number} index - the index at which to store the audio player.
* @param {array} playerArray - the array of players in which to store it.
* @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 been stored.
*/
_storeSound (filePath, index, playerArray) {
_storeSound (filePath, index, bufferArray) {
const fullPath = `${filePath}.mp3`;

if (!assetData[fullPath]) return;

// The sound player has already been downloaded via the manifest file required above.
// The sound buffer has already been downloaded via the manifest file required above.
const soundBuffer = assetData[fullPath];

return this._decodeSound(soundBuffer).then(player => {
playerArray[index] = player;
return this._decodeSound(soundBuffer).then(buffer => {
bufferArray[index] = buffer;
});
}

Expand All @@ -137,14 +129,24 @@ class Scratch3MusicBlocks {
* @return {Promise} - a promise which will resolve once the sound has decoded.
*/
_decodeSound (soundBuffer) {
const engine = this.runtime.audioEngine;
const context = this.runtime.audioEngine && this.runtime.audioEngine.audioContext;

if (!engine) {
if (!context) {
return Promise.reject(new Error('No Audio Context Detected'));
}

// Check for newer promise-based API
return engine.decodeSoundPlayer({data: {buffer: soundBuffer}});
if (context.decodeAudioData.length === 1) {
return context.decodeAudioData(soundBuffer);
} else { // eslint-disable-line no-else-return
// Fall back to callback API
return new Promise((resolve, reject) =>
context.decodeAudioData(soundBuffer,
buffer => resolve(buffer),
error => reject(error)
)
);
}
}

/**
Expand Down Expand Up @@ -776,34 +778,26 @@ class Scratch3MusicBlocks {
*/
_playDrumNum (util, drumNum) {
if (util.runtime.audioEngine === null) return;
if (util.target.sprite.soundBank === null) return;
if (util.target.audioPlayer === null) return;
// If we're playing too many sounds, do not play the drum sound.
if (this._concurrencyCounter > Scratch3MusicBlocks.CONCURRENCY_LIMIT) {
return;
}
const outputNode = util.target.audioPlayer.getInputNode();
const context = util.runtime.audioEngine.audioContext;
const bufferSource = context.createBufferSource();
bufferSource.buffer = this._drumBuffers[drumNum];
bufferSource.connect(outputNode);
bufferSource.start();

const player = this._drumPlayers[drumNum];

if (typeof player === 'undefined') return;

if (player.isPlaying) {
// Take the internal player state and create a new player with it.
// `.play` does this internally but then instructs the sound to
// stop.
player.take();
}

const engine = util.runtime.audioEngine;
const chain = engine.createEffectChain();
chain.setEffectsFromTarget(util.target);
player.connect(chain);
const bufferSourceIndex = this._bufferSources.length;
this._bufferSources.push(bufferSource);

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

player.play();
delete this._bufferSources[bufferSourceIndex];
};
}

/**
Expand Down Expand Up @@ -862,7 +856,7 @@ class Scratch3MusicBlocks {
*/
_playNote (util, note, durationSec) {
if (util.runtime.audioEngine === null) return;
if (util.target.sprite.soundBank === null) return;
if (util.target.audioPlayer === null) return;

// If we're playing too many sounds, do not play the note.
if (this._concurrencyCounter > Scratch3MusicBlocks.CONCURRENCY_LIMIT) {
Expand All @@ -877,37 +871,28 @@ class Scratch3MusicBlocks {
const sampleIndex = this._selectSampleIndexForNote(note, sampleArray);

// If the audio sample has not loaded yet, bail out
if (typeof this._instrumentPlayerArrays[inst] === 'undefined') return;
if (typeof this._instrumentPlayerArrays[inst][sampleIndex] === 'undefined') return;

// Fetch the sound player to play the note.
const engine = util.runtime.audioEngine;

if (!this._instrumentPlayerNoteArrays[inst][note]) {
this._instrumentPlayerNoteArrays[inst][note] = this._instrumentPlayerArrays[inst][sampleIndex].take();
}

const player = this._instrumentPlayerNoteArrays[inst][note];
if (typeof this._instrumentBufferArrays[inst] === 'undefined') return;
if (typeof this._instrumentBufferArrays[inst][sampleIndex] === 'undefined') return;

if (player.isPlaying) {
// Take the internal player state and create a new player with it.
// `.play` does this internally but then instructs the sound to
// stop.
player.take();
}
// Create the audio buffer to play the note, and set its pitch
const context = util.runtime.audioEngine.audioContext;
const bufferSource = context.createBufferSource();

const chain = engine.createEffectChain();
chain.setEffectsFromTarget(util.target);
const bufferSourceIndex = this._bufferSources.length;
this._bufferSources.push(bufferSource);

// Set its pitch.
bufferSource.buffer = this._instrumentBufferArrays[inst][sampleIndex];
const sampleNote = sampleArray[sampleIndex];
const notePitchInterval = this._ratioForPitchInterval(note - sampleNote);
bufferSource.playbackRate.value = this._ratioForPitchInterval(note - sampleNote);

// Create a gain node for this note, and connect it to the sprite's
// simulated effectChain.
const context = engine.audioContext;
const releaseGain = context.createGain();
releaseGain.connect(chain.getInputNode());
// Create a gain node for this note, and connect it to the sprite's audioPlayer.
const gainNode = context.createGain();
bufferSource.connect(gainNode);
const outputNode = util.target.audioPlayer.getInputNode();
gainNode.connect(outputNode);

// Start playing the note
bufferSource.start();

// Schedule the release of the note, ramping its gain down to zero,
// and then stopping the sound.
Expand All @@ -917,24 +902,16 @@ class Scratch3MusicBlocks {
}
const releaseStart = context.currentTime + durationSec;
const releaseEnd = releaseStart + releaseDuration;
releaseGain.gain.setValueAtTime(1, releaseStart);
releaseGain.gain.linearRampToValueAtTime(0.0001, releaseEnd);
gainNode.gain.setValueAtTime(1, releaseStart);
gainNode.gain.linearRampToValueAtTime(0.0001, releaseEnd);
bufferSource.stop(releaseEnd);

// Update the concurrency counter
this._concurrencyCounter++;
player.once('stop', () => {
bufferSource.onended = () => {
this._concurrencyCounter--;
});

// Start playing the note
player.play();
// Connect the player to the gain node.
player.connect({getInputNode () {
return releaseGain;
}});
// Set playback now after play creates the outputNode.
player.outputNode.playbackRate.value = notePitchInterval;
// Schedule playback to stop.
player.outputNode.stop(releaseEnd);
delete this._bufferSources[bufferSourceIndex];
};
}

/**
Expand Down
Loading