@scratch/scratch-vm 11.0.0-UEPR-176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/dist/node/extension-worker.js +2 -0
- package/dist/node/extension-worker.js.map +1 -0
- package/dist/node/scratch-vm.js +3 -0
- package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
- package/dist/node/scratch-vm.js.map +1 -0
- package/dist/web/extension-worker.js +2 -0
- package/dist/web/extension-worker.js.map +1 -0
- package/dist/web/scratch-vm.js +3 -0
- package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
- package/dist/web/scratch-vm.js.map +1 -0
- package/package.json +108 -0
- package/src/.eslintrc.js +7 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
const Cast = require('../util/cast');
|
|
2
|
+
const Timer = require('../util/timer');
|
|
3
|
+
const getMonitorIdForBlockWithArgs = require('../util/get-monitor-id');
|
|
4
|
+
|
|
5
|
+
class Scratch3SensingBlocks {
|
|
6
|
+
constructor (runtime) {
|
|
7
|
+
/**
|
|
8
|
+
* The runtime instantiating this block package.
|
|
9
|
+
* @type {Runtime}
|
|
10
|
+
*/
|
|
11
|
+
this.runtime = runtime;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The "answer" block value.
|
|
15
|
+
* @type {string}
|
|
16
|
+
*/
|
|
17
|
+
this._answer = '';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The timer utility.
|
|
21
|
+
* @type {Timer}
|
|
22
|
+
*/
|
|
23
|
+
this._timer = new Timer();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The stored microphone loudness measurement.
|
|
27
|
+
* @type {number}
|
|
28
|
+
*/
|
|
29
|
+
this._cachedLoudness = -1;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The time of the most recent microphone loudness measurement.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
this._cachedLoudnessTimestamp = 0;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The list of queued questions and respective `resolve` callbacks.
|
|
39
|
+
* @type {!Array}
|
|
40
|
+
*/
|
|
41
|
+
this._questionList = [];
|
|
42
|
+
|
|
43
|
+
this.runtime.on('ANSWER', this._onAnswer.bind(this));
|
|
44
|
+
this.runtime.on('PROJECT_START', this._resetAnswer.bind(this));
|
|
45
|
+
this.runtime.on('PROJECT_STOP_ALL', this._clearAllQuestions.bind(this));
|
|
46
|
+
this.runtime.on('STOP_FOR_TARGET', this._clearTargetQuestions.bind(this));
|
|
47
|
+
this.runtime.on('RUNTIME_DISPOSED', this._resetAnswer.bind(this));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Retrieve the block primitives implemented by this package.
|
|
52
|
+
* @return {object.<string, Function>} Mapping of opcode to Function.
|
|
53
|
+
*/
|
|
54
|
+
getPrimitives () {
|
|
55
|
+
return {
|
|
56
|
+
sensing_touchingobject: this.touchingObject,
|
|
57
|
+
sensing_touchingcolor: this.touchingColor,
|
|
58
|
+
sensing_coloristouchingcolor: this.colorTouchingColor,
|
|
59
|
+
sensing_distanceto: this.distanceTo,
|
|
60
|
+
sensing_timer: this.getTimer,
|
|
61
|
+
sensing_resettimer: this.resetTimer,
|
|
62
|
+
sensing_of: this.getAttributeOf,
|
|
63
|
+
sensing_mousex: this.getMouseX,
|
|
64
|
+
sensing_mousey: this.getMouseY,
|
|
65
|
+
sensing_setdragmode: this.setDragMode,
|
|
66
|
+
sensing_mousedown: this.getMouseDown,
|
|
67
|
+
sensing_keypressed: this.getKeyPressed,
|
|
68
|
+
sensing_current: this.current,
|
|
69
|
+
sensing_dayssince2000: this.daysSince2000,
|
|
70
|
+
sensing_loudness: this.getLoudness,
|
|
71
|
+
sensing_loud: this.isLoud,
|
|
72
|
+
sensing_askandwait: this.askAndWait,
|
|
73
|
+
sensing_answer: this.getAnswer,
|
|
74
|
+
sensing_username: this.getUsername,
|
|
75
|
+
sensing_userid: () => {} // legacy no-op block
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getMonitored () {
|
|
80
|
+
return {
|
|
81
|
+
sensing_answer: {
|
|
82
|
+
getId: () => 'answer'
|
|
83
|
+
},
|
|
84
|
+
sensing_loudness: {
|
|
85
|
+
getId: () => 'loudness'
|
|
86
|
+
},
|
|
87
|
+
sensing_timer: {
|
|
88
|
+
getId: () => 'timer'
|
|
89
|
+
},
|
|
90
|
+
sensing_current: {
|
|
91
|
+
// This is different from the default toolbox xml id in order to support
|
|
92
|
+
// importing multiple monitors from the same opcode from sb2 files,
|
|
93
|
+
// something that is not currently supported in scratch 3.
|
|
94
|
+
getId: (_, fields) => getMonitorIdForBlockWithArgs('current', fields) // _${param}`
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_onAnswer (answer) {
|
|
100
|
+
this._answer = answer;
|
|
101
|
+
const questionObj = this._questionList.shift();
|
|
102
|
+
if (questionObj) {
|
|
103
|
+
const [_question, resolve, target, wasVisible, wasStage] = questionObj;
|
|
104
|
+
// If the target was visible when asked, hide the say bubble unless the target was the stage.
|
|
105
|
+
if (wasVisible && !wasStage) {
|
|
106
|
+
this.runtime.emit('SAY', target, 'say', '');
|
|
107
|
+
}
|
|
108
|
+
resolve();
|
|
109
|
+
this._askNextQuestion();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_resetAnswer () {
|
|
114
|
+
this._answer = '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_enqueueAsk (question, resolve, target, wasVisible, wasStage) {
|
|
118
|
+
this._questionList.push([question, resolve, target, wasVisible, wasStage]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_askNextQuestion () {
|
|
122
|
+
if (this._questionList.length > 0) {
|
|
123
|
+
const [question, _resolve, target, wasVisible, wasStage] = this._questionList[0];
|
|
124
|
+
// If the target is visible, emit a blank question and use the
|
|
125
|
+
// say event to trigger a bubble unless the target was the stage.
|
|
126
|
+
if (wasVisible && !wasStage) {
|
|
127
|
+
this.runtime.emit('SAY', target, 'say', question);
|
|
128
|
+
this.runtime.emit('QUESTION', '');
|
|
129
|
+
} else {
|
|
130
|
+
this.runtime.emit('QUESTION', question);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_clearAllQuestions () {
|
|
136
|
+
this._questionList = [];
|
|
137
|
+
this.runtime.emit('QUESTION', null);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_clearTargetQuestions (stopTarget) {
|
|
141
|
+
const currentlyAsking = this._questionList.length > 0 && this._questionList[0][2] === stopTarget;
|
|
142
|
+
this._questionList = this._questionList.filter(question => (
|
|
143
|
+
question[2] !== stopTarget
|
|
144
|
+
));
|
|
145
|
+
|
|
146
|
+
if (currentlyAsking) {
|
|
147
|
+
this.runtime.emit('SAY', stopTarget, 'say', '');
|
|
148
|
+
if (this._questionList.length > 0) {
|
|
149
|
+
this._askNextQuestion();
|
|
150
|
+
} else {
|
|
151
|
+
this.runtime.emit('QUESTION', null);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
askAndWait (args, util) {
|
|
157
|
+
const _target = util.target;
|
|
158
|
+
return new Promise(resolve => {
|
|
159
|
+
const isQuestionAsked = this._questionList.length > 0;
|
|
160
|
+
this._enqueueAsk(String(args.QUESTION), resolve, _target, _target.visible, _target.isStage);
|
|
161
|
+
if (!isQuestionAsked) {
|
|
162
|
+
this._askNextQuestion();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getAnswer () {
|
|
168
|
+
return this._answer;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
touchingObject (args, util) {
|
|
172
|
+
return util.target.isTouchingObject(args.TOUCHINGOBJECTMENU);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
touchingColor (args, util) {
|
|
176
|
+
const color = Cast.toRgbColorList(args.COLOR);
|
|
177
|
+
return util.target.isTouchingColor(color);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
colorTouchingColor (args, util) {
|
|
181
|
+
const maskColor = Cast.toRgbColorList(args.COLOR);
|
|
182
|
+
const targetColor = Cast.toRgbColorList(args.COLOR2);
|
|
183
|
+
return util.target.colorIsTouchingColor(targetColor, maskColor);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
distanceTo (args, util) {
|
|
187
|
+
if (util.target.isStage) return 10000;
|
|
188
|
+
|
|
189
|
+
let targetX = 0;
|
|
190
|
+
let targetY = 0;
|
|
191
|
+
if (args.DISTANCETOMENU === '_mouse_') {
|
|
192
|
+
targetX = util.ioQuery('mouse', 'getScratchX');
|
|
193
|
+
targetY = util.ioQuery('mouse', 'getScratchY');
|
|
194
|
+
} else {
|
|
195
|
+
args.DISTANCETOMENU = Cast.toString(args.DISTANCETOMENU);
|
|
196
|
+
const distTarget = this.runtime.getSpriteTargetByName(
|
|
197
|
+
args.DISTANCETOMENU
|
|
198
|
+
);
|
|
199
|
+
if (!distTarget) return 10000;
|
|
200
|
+
targetX = distTarget.x;
|
|
201
|
+
targetY = distTarget.y;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const dx = util.target.x - targetX;
|
|
205
|
+
const dy = util.target.y - targetY;
|
|
206
|
+
return Math.sqrt((dx * dx) + (dy * dy));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
setDragMode (args, util) {
|
|
210
|
+
util.target.setDraggable(args.DRAG_MODE === 'draggable');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getTimer (args, util) {
|
|
214
|
+
return util.ioQuery('clock', 'projectTimer');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
resetTimer (args, util) {
|
|
218
|
+
util.ioQuery('clock', 'resetProjectTimer');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getMouseX (args, util) {
|
|
222
|
+
return util.ioQuery('mouse', 'getScratchX');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
getMouseY (args, util) {
|
|
226
|
+
return util.ioQuery('mouse', 'getScratchY');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getMouseDown (args, util) {
|
|
230
|
+
return util.ioQuery('mouse', 'getIsDown');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
current (args) {
|
|
234
|
+
const menuOption = Cast.toString(args.CURRENTMENU).toLowerCase();
|
|
235
|
+
const date = new Date();
|
|
236
|
+
switch (menuOption) {
|
|
237
|
+
case 'year': return date.getFullYear();
|
|
238
|
+
case 'month': return date.getMonth() + 1; // getMonth is zero-based
|
|
239
|
+
case 'date': return date.getDate();
|
|
240
|
+
case 'dayofweek': return date.getDay() + 1; // getDay is zero-based, Sun=0
|
|
241
|
+
case 'hour': return date.getHours();
|
|
242
|
+
case 'minute': return date.getMinutes();
|
|
243
|
+
case 'second': return date.getSeconds();
|
|
244
|
+
}
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getKeyPressed (args, util) {
|
|
249
|
+
return util.ioQuery('keyboard', 'getKeyIsDown', [args.KEY_OPTION]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
daysSince2000 () {
|
|
253
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
254
|
+
const start = new Date(2000, 0, 1); // Months are 0-indexed.
|
|
255
|
+
const today = new Date();
|
|
256
|
+
const dstAdjust = today.getTimezoneOffset() - start.getTimezoneOffset();
|
|
257
|
+
let mSecsSinceStart = today.valueOf() - start.valueOf();
|
|
258
|
+
mSecsSinceStart += ((today.getTimezoneOffset() - dstAdjust) * 60 * 1000);
|
|
259
|
+
return mSecsSinceStart / msPerDay;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getLoudness () {
|
|
263
|
+
if (typeof this.runtime.audioEngine === 'undefined') return -1;
|
|
264
|
+
if (this.runtime.currentStepTime === null) return -1;
|
|
265
|
+
|
|
266
|
+
// Only measure loudness once per step
|
|
267
|
+
const timeSinceLoudness = this._timer.time() - this._cachedLoudnessTimestamp;
|
|
268
|
+
if (timeSinceLoudness < this.runtime.currentStepTime) {
|
|
269
|
+
return this._cachedLoudness;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this._cachedLoudnessTimestamp = this._timer.time();
|
|
273
|
+
this._cachedLoudness = this.runtime.audioEngine.getLoudness();
|
|
274
|
+
return this._cachedLoudness;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
isLoud () {
|
|
278
|
+
return this.getLoudness() > 10;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getAttributeOf (args) {
|
|
282
|
+
let attrTarget;
|
|
283
|
+
|
|
284
|
+
if (args.OBJECT === '_stage_') {
|
|
285
|
+
attrTarget = this.runtime.getTargetForStage();
|
|
286
|
+
} else {
|
|
287
|
+
args.OBJECT = Cast.toString(args.OBJECT);
|
|
288
|
+
attrTarget = this.runtime.getSpriteTargetByName(args.OBJECT);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// attrTarget can be undefined if the target does not exist
|
|
292
|
+
// (e.g. single sprite uploaded from larger project referencing
|
|
293
|
+
// another sprite that wasn't uploaded)
|
|
294
|
+
if (!attrTarget) return 0;
|
|
295
|
+
|
|
296
|
+
// Generic attributes
|
|
297
|
+
if (attrTarget.isStage) {
|
|
298
|
+
switch (args.PROPERTY) {
|
|
299
|
+
// Scratch 1.4 support
|
|
300
|
+
case 'background #': return attrTarget.currentCostume + 1;
|
|
301
|
+
|
|
302
|
+
case 'backdrop #': return attrTarget.currentCostume + 1;
|
|
303
|
+
case 'backdrop name':
|
|
304
|
+
return attrTarget.getCostumes()[attrTarget.currentCostume].name;
|
|
305
|
+
case 'volume': return attrTarget.volume;
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
switch (args.PROPERTY) {
|
|
309
|
+
case 'x position': return attrTarget.x;
|
|
310
|
+
case 'y position': return attrTarget.y;
|
|
311
|
+
case 'direction': return attrTarget.direction;
|
|
312
|
+
case 'costume #': return attrTarget.currentCostume + 1;
|
|
313
|
+
case 'costume name':
|
|
314
|
+
return attrTarget.getCostumes()[attrTarget.currentCostume].name;
|
|
315
|
+
case 'size': return attrTarget.size;
|
|
316
|
+
case 'volume': return attrTarget.volume;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Target variables.
|
|
321
|
+
const varName = args.PROPERTY;
|
|
322
|
+
const variable = attrTarget.lookupVariableByNameAndType(varName, '', true);
|
|
323
|
+
if (variable) {
|
|
324
|
+
return variable.value;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Otherwise, 0
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
getUsername (args, util) {
|
|
332
|
+
return util.ioQuery('userData', 'getUsername');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = Scratch3SensingBlocks;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
const MathUtil = require('../util/math-util');
|
|
2
|
+
const Cast = require('../util/cast');
|
|
3
|
+
const Clone = require('../util/clone');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Occluded boolean value to make its use more understandable.
|
|
7
|
+
* @const {boolean}
|
|
8
|
+
*/
|
|
9
|
+
const STORE_WAITING = true;
|
|
10
|
+
|
|
11
|
+
class Scratch3SoundBlocks {
|
|
12
|
+
constructor (runtime) {
|
|
13
|
+
/**
|
|
14
|
+
* The runtime instantiating this block package.
|
|
15
|
+
* @type {Runtime}
|
|
16
|
+
*/
|
|
17
|
+
this.runtime = runtime;
|
|
18
|
+
|
|
19
|
+
this.waitingSounds = {};
|
|
20
|
+
|
|
21
|
+
// Clear sound effects on green flag and stop button events.
|
|
22
|
+
this.stopAllSounds = this.stopAllSounds.bind(this);
|
|
23
|
+
this._stopWaitingSoundsForTarget = this._stopWaitingSoundsForTarget.bind(this);
|
|
24
|
+
this._clearEffectsForAllTargets = this._clearEffectsForAllTargets.bind(this);
|
|
25
|
+
if (this.runtime) {
|
|
26
|
+
this.runtime.on('PROJECT_STOP_ALL', this.stopAllSounds);
|
|
27
|
+
this.runtime.on('PROJECT_STOP_ALL', this._clearEffectsForAllTargets);
|
|
28
|
+
this.runtime.on('STOP_FOR_TARGET', this._stopWaitingSoundsForTarget);
|
|
29
|
+
this.runtime.on('PROJECT_START', this._clearEffectsForAllTargets);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this._onTargetCreated = this._onTargetCreated.bind(this);
|
|
33
|
+
if (this.runtime) {
|
|
34
|
+
runtime.on('targetWasCreated', this._onTargetCreated);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The key to load & store a target's sound-related state.
|
|
40
|
+
* @type {string}
|
|
41
|
+
*/
|
|
42
|
+
static get STATE_KEY () {
|
|
43
|
+
return 'Scratch.sound';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The default sound-related state, to be used when a target has no existing sound state.
|
|
48
|
+
* @type {SoundState}
|
|
49
|
+
*/
|
|
50
|
+
static get DEFAULT_SOUND_STATE () {
|
|
51
|
+
return {
|
|
52
|
+
effects: {
|
|
53
|
+
pitch: 0,
|
|
54
|
+
pan: 0
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The minimum and maximum MIDI note numbers, for clamping the input to play note.
|
|
61
|
+
* @type {{min: number, max: number}}
|
|
62
|
+
*/
|
|
63
|
+
static get MIDI_NOTE_RANGE () {
|
|
64
|
+
return {min: 36, max: 96}; // C2 to C7
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The minimum and maximum beat values, for clamping the duration of play note, play drum and rest.
|
|
69
|
+
* 100 beats at the default tempo of 60bpm is 100 seconds.
|
|
70
|
+
* @type {{min: number, max: number}}
|
|
71
|
+
*/
|
|
72
|
+
static get BEAT_RANGE () {
|
|
73
|
+
return {min: 0, max: 100};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The minimum and maximum tempo values, in bpm.
|
|
77
|
+
* @type {{min: number, max: number}}
|
|
78
|
+
*/
|
|
79
|
+
static get TEMPO_RANGE () {
|
|
80
|
+
return {min: 20, max: 500};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The minimum and maximum values for each sound effect.
|
|
84
|
+
* @type {{effect:{min: number, max: number}}}
|
|
85
|
+
*/
|
|
86
|
+
static get EFFECT_RANGE () {
|
|
87
|
+
return {
|
|
88
|
+
pitch: {min: -360, max: 360}, // -3 to 3 octaves
|
|
89
|
+
pan: {min: -100, max: 100} // 100% left to 100% right
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Target} target - collect sound state for this target.
|
|
95
|
+
* @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary.
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
_getSoundState (target) {
|
|
99
|
+
let soundState = target.getCustomState(Scratch3SoundBlocks.STATE_KEY);
|
|
100
|
+
if (!soundState) {
|
|
101
|
+
soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE);
|
|
102
|
+
target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState);
|
|
103
|
+
target.soundEffects = soundState.effects;
|
|
104
|
+
}
|
|
105
|
+
return soundState;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* When a Target is cloned, clone the sound state.
|
|
110
|
+
* @param {Target} newTarget - the newly created target.
|
|
111
|
+
* @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.
|
|
112
|
+
* @listens Runtime#event:targetWasCreated
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
_onTargetCreated (newTarget, sourceTarget) {
|
|
116
|
+
if (sourceTarget) {
|
|
117
|
+
const soundState = sourceTarget.getCustomState(Scratch3SoundBlocks.STATE_KEY);
|
|
118
|
+
if (soundState && newTarget) {
|
|
119
|
+
newTarget.setCustomState(Scratch3SoundBlocks.STATE_KEY, Clone.simple(soundState));
|
|
120
|
+
this._syncEffectsForTarget(newTarget);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Retrieve the block primitives implemented by this package.
|
|
127
|
+
* @return {object.<string, Function>} Mapping of opcode to Function.
|
|
128
|
+
*/
|
|
129
|
+
getPrimitives () {
|
|
130
|
+
return {
|
|
131
|
+
sound_play: this.playSound,
|
|
132
|
+
sound_playuntildone: this.playSoundAndWait,
|
|
133
|
+
sound_stopallsounds: this.stopAllSounds,
|
|
134
|
+
sound_seteffectto: this.setEffect,
|
|
135
|
+
sound_changeeffectby: this.changeEffect,
|
|
136
|
+
sound_cleareffects: this.clearEffects,
|
|
137
|
+
sound_sounds_menu: this.soundsMenu,
|
|
138
|
+
sound_beats_menu: this.beatsMenu,
|
|
139
|
+
sound_effects_menu: this.effectsMenu,
|
|
140
|
+
sound_setvolumeto: this.setVolume,
|
|
141
|
+
sound_changevolumeby: this.changeVolume,
|
|
142
|
+
sound_volume: this.getVolume
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getMonitored () {
|
|
147
|
+
return {
|
|
148
|
+
sound_volume: {
|
|
149
|
+
isSpriteSpecific: true,
|
|
150
|
+
getId: targetId => `${targetId}_volume`
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
playSound (args, util) {
|
|
156
|
+
// Don't return the promise, it's the only difference for AndWait
|
|
157
|
+
this._playSound(args, util);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
playSoundAndWait (args, util) {
|
|
161
|
+
return this._playSound(args, util, STORE_WAITING);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_playSound (args, util, storeWaiting) {
|
|
165
|
+
const index = this._getSoundIndex(args.SOUND_MENU, util);
|
|
166
|
+
if (index >= 0) {
|
|
167
|
+
const {target} = util;
|
|
168
|
+
const {sprite} = target;
|
|
169
|
+
const {soundId} = sprite.sounds[index];
|
|
170
|
+
if (sprite.soundBank) {
|
|
171
|
+
if (storeWaiting === STORE_WAITING) {
|
|
172
|
+
this._addWaitingSound(target.id, soundId);
|
|
173
|
+
} else {
|
|
174
|
+
this._removeWaitingSound(target.id, soundId);
|
|
175
|
+
}
|
|
176
|
+
return sprite.soundBank.playSound(target, soundId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_addWaitingSound (targetId, soundId) {
|
|
182
|
+
if (!this.waitingSounds[targetId]) {
|
|
183
|
+
this.waitingSounds[targetId] = new Set();
|
|
184
|
+
}
|
|
185
|
+
this.waitingSounds[targetId].add(soundId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_removeWaitingSound (targetId, soundId) {
|
|
189
|
+
if (!this.waitingSounds[targetId]) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.waitingSounds[targetId].delete(soundId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_getSoundIndex (soundName, util) {
|
|
196
|
+
// if the sprite has no sounds, return -1
|
|
197
|
+
const len = util.target.sprite.sounds.length;
|
|
198
|
+
if (len === 0) {
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// look up by name first
|
|
203
|
+
const index = this.getSoundIndexByName(soundName, util);
|
|
204
|
+
if (index !== -1) {
|
|
205
|
+
return index;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// then try using the sound name as a 1-indexed index
|
|
209
|
+
const oneIndexedIndex = parseInt(soundName, 10);
|
|
210
|
+
if (!isNaN(oneIndexedIndex)) {
|
|
211
|
+
return MathUtil.wrapClamp(oneIndexedIndex - 1, 0, len - 1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// could not be found as a name or converted to index, return -1
|
|
215
|
+
return -1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getSoundIndexByName (soundName, util) {
|
|
219
|
+
const sounds = util.target.sprite.sounds;
|
|
220
|
+
for (let i = 0; i < sounds.length; i++) {
|
|
221
|
+
if (sounds[i].name === soundName) {
|
|
222
|
+
return i;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// if there is no sound by that name, return -1
|
|
226
|
+
return -1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
stopAllSounds () {
|
|
230
|
+
if (this.runtime.targets === null) return;
|
|
231
|
+
const allTargets = this.runtime.targets;
|
|
232
|
+
for (let i = 0; i < allTargets.length; i++) {
|
|
233
|
+
this._stopAllSoundsForTarget(allTargets[i]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_stopAllSoundsForTarget (target) {
|
|
238
|
+
if (target.sprite.soundBank) {
|
|
239
|
+
target.sprite.soundBank.stopAllSounds(target);
|
|
240
|
+
if (this.waitingSounds[target.id]) {
|
|
241
|
+
this.waitingSounds[target.id].clear();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
_stopWaitingSoundsForTarget (target) {
|
|
247
|
+
if (target.sprite.soundBank) {
|
|
248
|
+
if (this.waitingSounds[target.id]) {
|
|
249
|
+
for (const soundId of this.waitingSounds[target.id].values()) {
|
|
250
|
+
target.sprite.soundBank.stop(target, soundId);
|
|
251
|
+
}
|
|
252
|
+
this.waitingSounds[target.id].clear();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setEffect (args, util) {
|
|
258
|
+
return this._updateEffect(args, util, false);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
changeEffect (args, util) {
|
|
262
|
+
return this._updateEffect(args, util, true);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_updateEffect (args, util, change) {
|
|
266
|
+
const effect = Cast.toString(args.EFFECT).toLowerCase();
|
|
267
|
+
const value = Cast.toNumber(args.VALUE);
|
|
268
|
+
|
|
269
|
+
const soundState = this._getSoundState(util.target);
|
|
270
|
+
if (!Object.prototype.hasOwnProperty.call(soundState.effects, effect)) return;
|
|
271
|
+
|
|
272
|
+
if (change) {
|
|
273
|
+
soundState.effects[effect] += value;
|
|
274
|
+
} else {
|
|
275
|
+
soundState.effects[effect] = value;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const {min, max} = Scratch3SoundBlocks.EFFECT_RANGE[effect];
|
|
279
|
+
soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], min, max);
|
|
280
|
+
|
|
281
|
+
this._syncEffectsForTarget(util.target);
|
|
282
|
+
// Yield until the next tick.
|
|
283
|
+
return Promise.resolve();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
_syncEffectsForTarget (target) {
|
|
287
|
+
if (!target || !target.sprite.soundBank) return;
|
|
288
|
+
target.soundEffects = this._getSoundState(target).effects;
|
|
289
|
+
|
|
290
|
+
target.sprite.soundBank.setEffects(target);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
clearEffects (args, util) {
|
|
294
|
+
this._clearEffectsForTarget(util.target);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_clearEffectsForTarget (target) {
|
|
298
|
+
const soundState = this._getSoundState(target);
|
|
299
|
+
for (const effect in soundState.effects) {
|
|
300
|
+
if (!Object.prototype.hasOwnProperty.call(soundState.effects, effect)) continue;
|
|
301
|
+
soundState.effects[effect] = 0;
|
|
302
|
+
}
|
|
303
|
+
this._syncEffectsForTarget(target);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_clearEffectsForAllTargets () {
|
|
307
|
+
if (this.runtime.targets === null) return;
|
|
308
|
+
const allTargets = this.runtime.targets;
|
|
309
|
+
for (let i = 0; i < allTargets.length; i++) {
|
|
310
|
+
this._clearEffectsForTarget(allTargets[i]);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
setVolume (args, util) {
|
|
315
|
+
const volume = Cast.toNumber(args.VOLUME);
|
|
316
|
+
return this._updateVolume(volume, util);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
changeVolume (args, util) {
|
|
320
|
+
const volume = Cast.toNumber(args.VOLUME) + util.target.volume;
|
|
321
|
+
return this._updateVolume(volume, util);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_updateVolume (volume, util) {
|
|
325
|
+
volume = MathUtil.clamp(volume, 0, 100);
|
|
326
|
+
util.target.volume = volume;
|
|
327
|
+
this._syncEffectsForTarget(util.target);
|
|
328
|
+
|
|
329
|
+
// Yield until the next tick.
|
|
330
|
+
return Promise.resolve();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getVolume (args, util) {
|
|
334
|
+
return util.target.volume;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
soundsMenu (args) {
|
|
338
|
+
return args.SOUND_MENU;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
beatsMenu (args) {
|
|
342
|
+
return args.BEATS;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
effectsMenu (args) {
|
|
346
|
+
return args.EFFECT;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = Scratch3SoundBlocks;
|