mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-02 17:20:32 -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) {
|
say (args, util) {
|
||||||
// @TODO in 2.0 calling say/think resets the right/left bias of the bubble
|
// @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);
|
this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'say', args.MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
sayforsecs (args, util) {
|
sayforsecs (args, util) {
|
||||||
this.say(args, util);
|
this._bubbleForSecs(args, util, 'say');
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
think (args, util) {
|
think (args, util) {
|
||||||
|
@ -353,19 +368,7 @@ class Scratch3LooksBlocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
thinkforsecs (args, util) {
|
thinkforsecs (args, util) {
|
||||||
this.think(args, util);
|
this._bubbleForSecs(args, util, 'think');
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show (args, util) {
|
show (args, util) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const Cast = require('../util/cast');
|
const Cast = require('../util/cast');
|
||||||
const MathUtil = require('../util/math-util');
|
const MathUtil = require('../util/math-util');
|
||||||
const Timer = require('../util/timer');
|
|
||||||
|
|
||||||
class Scratch3MotionBlocks {
|
class Scratch3MotionBlocks {
|
||||||
constructor (runtime) {
|
constructor (runtime) {
|
||||||
|
@ -142,37 +141,34 @@ class Scratch3MotionBlocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
glide (args, util) {
|
glide (args, util) {
|
||||||
if (util.stackFrame.timer) {
|
if (util.stackTimerNeedsInit()) {
|
||||||
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 {
|
|
||||||
// First time: save data for future use.
|
// First time: save data for future use.
|
||||||
util.stackFrame.timer = new Timer();
|
const duration = Cast.toNumber(args.SECS) * 1000;
|
||||||
util.stackFrame.timer.start();
|
util.startStackTimer(duration);
|
||||||
util.stackFrame.duration = Cast.toNumber(args.SECS);
|
|
||||||
util.stackFrame.startX = util.target.x;
|
util.stackFrame.startX = util.target.x;
|
||||||
util.stackFrame.startY = util.target.y;
|
util.stackFrame.startY = util.target.y;
|
||||||
util.stackFrame.endX = Cast.toNumber(args.X);
|
util.stackFrame.endX = Cast.toNumber(args.X);
|
||||||
util.stackFrame.endY = Cast.toNumber(args.Y);
|
util.stackFrame.endY = Cast.toNumber(args.Y);
|
||||||
if (util.stackFrame.duration <= 0) {
|
if (duration <= 0) {
|
||||||
// Duration too short to glide.
|
// Duration too short to glide.
|
||||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
util.yield();
|
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 Cast = require('../../util/cast');
|
||||||
const formatMessage = require('format-message');
|
const formatMessage = require('format-message');
|
||||||
const MathUtil = require('../../util/math-util');
|
const MathUtil = require('../../util/math-util');
|
||||||
const Timer = require('../../util/timer');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The instrument and drum sounds, loaded as static assets.
|
* The instrument and drum sounds, loaded as static assets.
|
||||||
|
@ -961,7 +960,7 @@ class Scratch3MusicBlocks {
|
||||||
* @param {object} util - utility object provided by the runtime.
|
* @param {object} util - utility object provided by the runtime.
|
||||||
*/
|
*/
|
||||||
_playDrumForBeats (drumNum, beats, util) {
|
_playDrumForBeats (drumNum, beats, util) {
|
||||||
if (this._stackTimerNeedsInit(util)) {
|
if (util.stackTimerNeedsInit()) {
|
||||||
drumNum = Cast.toNumber(drumNum);
|
drumNum = Cast.toNumber(drumNum);
|
||||||
drumNum = Math.round(drumNum);
|
drumNum = Math.round(drumNum);
|
||||||
drumNum -= 1; // drums are one-indexed
|
drumNum -= 1; // drums are one-indexed
|
||||||
|
@ -969,9 +968,10 @@ class Scratch3MusicBlocks {
|
||||||
beats = Cast.toNumber(beats);
|
beats = Cast.toNumber(beats);
|
||||||
beats = this._clampBeats(beats);
|
beats = this._clampBeats(beats);
|
||||||
this._playDrumNum(util, drumNum);
|
this._playDrumNum(util, drumNum);
|
||||||
this._startStackTimer(util, this._beatsToSec(beats));
|
util.startStackTimer(this._beatsToMSec(beats));
|
||||||
} else {
|
util.yield();
|
||||||
this._checkStackTimer(util);
|
} else if (!util.stackTimerFinished()) {
|
||||||
|
util.yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,12 +1025,13 @@ class Scratch3MusicBlocks {
|
||||||
* @property {number} BEATS - the duration in beats of the rest.
|
* @property {number} BEATS - the duration in beats of the rest.
|
||||||
*/
|
*/
|
||||||
restForBeats (args, util) {
|
restForBeats (args, util) {
|
||||||
if (this._stackTimerNeedsInit(util)) {
|
if (util.stackTimerNeedsInit()) {
|
||||||
let beats = Cast.toNumber(args.BEATS);
|
let beats = Cast.toNumber(args.BEATS);
|
||||||
beats = this._clampBeats(beats);
|
beats = this._clampBeats(beats);
|
||||||
this._startStackTimer(util, this._beatsToSec(beats));
|
util.startStackTimer(this._beatsToMSec(beats));
|
||||||
} else {
|
util.yield();
|
||||||
this._checkStackTimer(util);
|
} else if (!util.stackTimerFinished()) {
|
||||||
|
util.yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1043,7 +1044,7 @@ class Scratch3MusicBlocks {
|
||||||
* @property {number} BEATS - the duration in beats of the note.
|
* @property {number} BEATS - the duration in beats of the note.
|
||||||
*/
|
*/
|
||||||
playNoteForBeats (args, util) {
|
playNoteForBeats (args, util) {
|
||||||
if (this._stackTimerNeedsInit(util)) {
|
if (util.stackTimerNeedsInit()) {
|
||||||
let note = Cast.toNumber(args.NOTE);
|
let note = Cast.toNumber(args.NOTE);
|
||||||
note = MathUtil.clamp(note,
|
note = MathUtil.clamp(note,
|
||||||
Scratch3MusicBlocks.MIDI_NOTE_RANGE.min, Scratch3MusicBlocks.MIDI_NOTE_RANGE.max);
|
Scratch3MusicBlocks.MIDI_NOTE_RANGE.min, Scratch3MusicBlocks.MIDI_NOTE_RANGE.max);
|
||||||
|
@ -1053,13 +1054,14 @@ class Scratch3MusicBlocks {
|
||||||
// but "play note for 0 beats" is silent.
|
// but "play note for 0 beats" is silent.
|
||||||
if (beats === 0) return;
|
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);
|
util.startStackTimer(durationMSec);
|
||||||
} else {
|
util.yield();
|
||||||
this._checkStackTimer(util);
|
} 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.
|
* @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
|
* @private
|
||||||
*/
|
*/
|
||||||
_beatsToSec (beats) {
|
_beatsToMSec (beats) {
|
||||||
return (60 / this.getTempo()) * beats;
|
return (60 / this.getTempo()) * beats * 1000;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
const test = require('tap').test;
|
const test = require('tap').test;
|
||||||
const Music = require('../../src/extensions/scratch3_music/index.js');
|
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 = {
|
const rt = new Runtime();
|
||||||
getTargetForStage: () => ({tempo: 60}),
|
const util = new BlockUtility();
|
||||||
on: () => {} // Stub out listener methods used in constructor.
|
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 = {
|
const blocks = new Music(rt);
|
||||||
stackFrame: Object.create(null),
|
|
||||||
target: {
|
|
||||||
audioPlayer: null
|
|
||||||
},
|
|
||||||
yield: () => null
|
|
||||||
};
|
|
||||||
|
|
||||||
test('playDrum uses 1-indexing and wrap clamps', t => {
|
test('playDrum uses 1-indexing and wrap clamps', t => {
|
||||||
// Stub playDrumNum
|
// Stub playDrumNum
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue