mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-13 22:11:14 -04:00
Merge 5c4e314ca8
into bb9ac40252
This commit is contained in:
commit
ce6b29940c
4 changed files with 83 additions and 114 deletions
src
test/unit
|
@ -327,25 +327,40 @@ class Scratch3LooksBlocks {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a text bubble and yield on this thread until it's done. Used for "say/think for () secs".
|
||||
* @param {number} args Block arguments
|
||||
* @param {BlockUtility} util Utility object provided by the runtime.
|
||||
* @param {string} type The type of bubble (say/think)
|
||||
* @private
|
||||
*/
|
||||
_bubbleForSecs (args, util, type) {
|
||||
if (util.stackTimerNeedsInit()) {
|
||||
this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, type, args.MESSAGE);
|
||||
|
||||
const duration = Math.max(0, 1000 * Cast.toNumber(args.SECS));
|
||||
|
||||
util.stackFrame.bubbleId = this._getBubbleState(util.target).usageId;
|
||||
util.startStackTimer(duration);
|
||||
util.yield();
|
||||
} else if (util.stackTimerFinished()) {
|
||||
// Make sure the bubble we're removing is the same bubble we created.
|
||||
// We don't want to cancel a bubble started from another script.
|
||||
if (util.stackFrame.bubbleId === this._getBubbleState(util.target).usageId) {
|
||||
this._updateBubble(util.target, type, '');
|
||||
}
|
||||
} else {
|
||||
util.yield();
|
||||
}
|
||||
}
|
||||
|
||||
say (args, util) {
|
||||
// @TODO in 2.0 calling say/think resets the right/left bias of the bubble
|
||||
this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'say', args.MESSAGE);
|
||||
}
|
||||
|
||||
sayforsecs (args, util) {
|
||||
this.say(args, util);
|
||||
const target = util.target;
|
||||
const usageId = this._getBubbleState(target).usageId;
|
||||
return new Promise(resolve => {
|
||||
this._bubbleTimeout = setTimeout(() => {
|
||||
this._bubbleTimeout = null;
|
||||
// Clear say bubble if it hasn't been changed and proceed.
|
||||
if (this._getBubbleState(target).usageId === usageId) {
|
||||
this._updateBubble(target, 'say', '');
|
||||
}
|
||||
resolve();
|
||||
}, 1000 * args.SECS);
|
||||
});
|
||||
this._bubbleForSecs(args, util, 'say');
|
||||
}
|
||||
|
||||
think (args, util) {
|
||||
|
@ -353,19 +368,7 @@ class Scratch3LooksBlocks {
|
|||
}
|
||||
|
||||
thinkforsecs (args, util) {
|
||||
this.think(args, util);
|
||||
const target = util.target;
|
||||
const usageId = this._getBubbleState(target).usageId;
|
||||
return new Promise(resolve => {
|
||||
this._bubbleTimeout = setTimeout(() => {
|
||||
this._bubbleTimeout = null;
|
||||
// Clear think bubble if it hasn't been changed and proceed.
|
||||
if (this._getBubbleState(target).usageId === usageId) {
|
||||
this._updateBubble(target, 'think', '');
|
||||
}
|
||||
resolve();
|
||||
}, 1000 * args.SECS);
|
||||
});
|
||||
this._bubbleForSecs(args, util, 'think');
|
||||
}
|
||||
|
||||
show (args, util) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const Cast = require('../util/cast');
|
||||
const MathUtil = require('../util/math-util');
|
||||
const Timer = require('../util/timer');
|
||||
|
||||
class Scratch3MotionBlocks {
|
||||
constructor (runtime) {
|
||||
|
@ -142,37 +141,34 @@ class Scratch3MotionBlocks {
|
|||
}
|
||||
|
||||
glide (args, util) {
|
||||
if (util.stackFrame.timer) {
|
||||
const timeElapsed = util.stackFrame.timer.timeElapsed();
|
||||
if (timeElapsed < util.stackFrame.duration * 1000) {
|
||||
// In progress: move to intermediate position.
|
||||
const frac = timeElapsed / (util.stackFrame.duration * 1000);
|
||||
const dx = frac * (util.stackFrame.endX - util.stackFrame.startX);
|
||||
const dy = frac * (util.stackFrame.endY - util.stackFrame.startY);
|
||||
util.target.setXY(
|
||||
util.stackFrame.startX + dx,
|
||||
util.stackFrame.startY + dy
|
||||
);
|
||||
util.yield();
|
||||
} else {
|
||||
// Finished: move to final position.
|
||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||
}
|
||||
} else {
|
||||
if (util.stackTimerNeedsInit()) {
|
||||
// First time: save data for future use.
|
||||
util.stackFrame.timer = new Timer();
|
||||
util.stackFrame.timer.start();
|
||||
util.stackFrame.duration = Cast.toNumber(args.SECS);
|
||||
const duration = Cast.toNumber(args.SECS) * 1000;
|
||||
util.startStackTimer(duration);
|
||||
util.stackFrame.startX = util.target.x;
|
||||
util.stackFrame.startY = util.target.y;
|
||||
util.stackFrame.endX = Cast.toNumber(args.X);
|
||||
util.stackFrame.endY = Cast.toNumber(args.Y);
|
||||
if (util.stackFrame.duration <= 0) {
|
||||
if (duration <= 0) {
|
||||
// Duration too short to glide.
|
||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||
return;
|
||||
}
|
||||
util.yield();
|
||||
} else if (util.stackTimerFinished()) {
|
||||
// Finished: move to final position.
|
||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||
} else {
|
||||
// In progress: move to intermediate position.
|
||||
const timeElapsed = util.stackFrame.timer.timeElapsed();
|
||||
const frac = timeElapsed / util.stackFrame.duration;
|
||||
const dx = frac * (util.stackFrame.endX - util.stackFrame.startX);
|
||||
const dy = frac * (util.stackFrame.endY - util.stackFrame.startY);
|
||||
util.target.setXY(
|
||||
util.stackFrame.startX + dx,
|
||||
util.stackFrame.startY + dy
|
||||
);
|
||||
util.yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ const Clone = require('../../util/clone');
|
|||
const Cast = require('../../util/cast');
|
||||
const formatMessage = require('format-message');
|
||||
const MathUtil = require('../../util/math-util');
|
||||
const Timer = require('../../util/timer');
|
||||
|
||||
/**
|
||||
* The instrument and drum sounds, loaded as static assets.
|
||||
|
@ -961,7 +960,7 @@ class Scratch3MusicBlocks {
|
|||
* @param {object} util - utility object provided by the runtime.
|
||||
*/
|
||||
_playDrumForBeats (drumNum, beats, util) {
|
||||
if (this._stackTimerNeedsInit(util)) {
|
||||
if (util.stackTimerNeedsInit()) {
|
||||
drumNum = Cast.toNumber(drumNum);
|
||||
drumNum = Math.round(drumNum);
|
||||
drumNum -= 1; // drums are one-indexed
|
||||
|
@ -969,9 +968,10 @@ class Scratch3MusicBlocks {
|
|||
beats = Cast.toNumber(beats);
|
||||
beats = this._clampBeats(beats);
|
||||
this._playDrumNum(util, drumNum);
|
||||
this._startStackTimer(util, this._beatsToSec(beats));
|
||||
} else {
|
||||
this._checkStackTimer(util);
|
||||
util.startStackTimer(this._beatsToMSec(beats));
|
||||
util.yield();
|
||||
} else if (!util.stackTimerFinished()) {
|
||||
util.yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1025,12 +1025,13 @@ class Scratch3MusicBlocks {
|
|||
* @property {number} BEATS - the duration in beats of the rest.
|
||||
*/
|
||||
restForBeats (args, util) {
|
||||
if (this._stackTimerNeedsInit(util)) {
|
||||
if (util.stackTimerNeedsInit()) {
|
||||
let beats = Cast.toNumber(args.BEATS);
|
||||
beats = this._clampBeats(beats);
|
||||
this._startStackTimer(util, this._beatsToSec(beats));
|
||||
} else {
|
||||
this._checkStackTimer(util);
|
||||
util.startStackTimer(this._beatsToMSec(beats));
|
||||
util.yield();
|
||||
} else if (!util.stackTimerFinished()) {
|
||||
util.yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1043,7 +1044,7 @@ class Scratch3MusicBlocks {
|
|||
* @property {number} BEATS - the duration in beats of the note.
|
||||
*/
|
||||
playNoteForBeats (args, util) {
|
||||
if (this._stackTimerNeedsInit(util)) {
|
||||
if (util.stackTimerNeedsInit()) {
|
||||
let note = Cast.toNumber(args.NOTE);
|
||||
note = MathUtil.clamp(note,
|
||||
Scratch3MusicBlocks.MIDI_NOTE_RANGE.min, Scratch3MusicBlocks.MIDI_NOTE_RANGE.max);
|
||||
|
@ -1053,13 +1054,14 @@ class Scratch3MusicBlocks {
|
|||
// but "play note for 0 beats" is silent.
|
||||
if (beats === 0) return;
|
||||
|
||||
const durationSec = this._beatsToSec(beats);
|
||||
const durationMSec = this._beatsToMSec(beats);
|
||||
|
||||
this._playNote(util, note, durationSec);
|
||||
this._playNote(util, note, durationMSec * 0.001);
|
||||
|
||||
this._startStackTimer(util, durationSec);
|
||||
} else {
|
||||
this._checkStackTimer(util);
|
||||
util.startStackTimer(durationMSec);
|
||||
util.yield();
|
||||
} else if (!util.stackTimerFinished()) {
|
||||
util.yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1199,48 +1201,13 @@ class Scratch3MusicBlocks {
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert a number of beats to a number of seconds, using the current tempo.
|
||||
* Convert a number of beats to a number of milliseconds, using the current tempo.
|
||||
* @param {number} beats - number of beats to convert to secs.
|
||||
* @return {number} seconds - number of seconds `beats` will last.
|
||||
* @return {number} number of milliseconds `beats` will last.
|
||||
* @private
|
||||
*/
|
||||
_beatsToSec (beats) {
|
||||
return (60 / this.getTempo()) * beats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the stack timer needs initialization.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @return {boolean} - true if the stack timer needs to be initialized.
|
||||
* @private
|
||||
*/
|
||||
_stackTimerNeedsInit (util) {
|
||||
return !util.stackFrame.timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the stack timer and the yield the thread if necessary.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @param {number} duration - a duration in seconds to set the timer for.
|
||||
* @private
|
||||
*/
|
||||
_startStackTimer (util, duration) {
|
||||
util.stackFrame.timer = new Timer();
|
||||
util.stackFrame.timer.start();
|
||||
util.stackFrame.duration = duration;
|
||||
util.yield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the stack timer, and if its time is not up yet, yield the thread.
|
||||
* @param {object} util - utility object provided by the runtime.
|
||||
* @private
|
||||
*/
|
||||
_checkStackTimer (util) {
|
||||
const timeElapsed = util.stackFrame.timer.timeElapsed();
|
||||
if (timeElapsed < util.stackFrame.duration * 1000) {
|
||||
util.yield();
|
||||
}
|
||||
_beatsToMSec (beats) {
|
||||
return (60 / this.getTempo()) * beats * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
const test = require('tap').test;
|
||||
const Music = require('../../src/extensions/scratch3_music/index.js');
|
||||
const Blocks = require('../../src/engine/blocks');
|
||||
const BlockUtility = require('../../src/engine/block-utility');
|
||||
const Runtime = require('../../src/engine/runtime');
|
||||
const Target = require('../../src/engine/target');
|
||||
const Thread = require('../../src/engine/thread');
|
||||
|
||||
const fakeRuntime = {
|
||||
getTargetForStage: () => ({tempo: 60}),
|
||||
on: () => {} // Stub out listener methods used in constructor.
|
||||
};
|
||||
const rt = new Runtime();
|
||||
const util = new BlockUtility();
|
||||
util.sequencer = rt.sequencer;
|
||||
util.runtime = rt;
|
||||
util.thread = new Thread(null);
|
||||
util.thread.pushStack();
|
||||
|
||||
const blocks = new Music(fakeRuntime);
|
||||
const b = new Blocks(rt);
|
||||
const tgt = new Target(rt, b);
|
||||
tgt.isStage = true;
|
||||
|
||||
const util = {
|
||||
stackFrame: Object.create(null),
|
||||
target: {
|
||||
audioPlayer: null
|
||||
},
|
||||
yield: () => null
|
||||
};
|
||||
const blocks = new Music(rt);
|
||||
|
||||
test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||
// Stub playDrumNum
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue