├── .gitignore ├── .jscsrc ├── CONTRIBUTORS ├── COPYING ├── README.md ├── TESTING.md ├── TRADEMARK_POLICY ├── compare.css ├── compare.html ├── copyright.txt ├── img ├── ask-bottom.png ├── ask-button.png ├── fullScreenOff.png ├── fullScreenOn.png ├── greenFlagOff.png ├── greenFlagOn.png ├── playerStartFlag.png ├── say-bottom.png ├── stopOff.png ├── stopOn.png └── think-bottom.png ├── index.css ├── index.html ├── js ├── IO.js ├── Interpreter.js ├── Reporter.js ├── Runtime.js ├── Scratch.js ├── Sprite.js ├── Stage.js ├── primitives │ ├── LooksPrims.js │ ├── MotionAndPenPrims.js │ ├── Primitives.js │ ├── SensingPrims.js │ ├── SoundPrims.js │ └── VarListPrims.js ├── sound │ ├── NotePlayer.js │ ├── SoundBank.js │ ├── SoundDecoder.js │ └── WAVFile.js └── util │ ├── Color.js │ ├── OffsetBuffer.js │ ├── Rectangle.js │ └── Timer.js ├── makefile ├── package.json ├── player.css ├── soundbank ├── Instr.js ├── drums │ ├── BassDrum(1b)_22k.wav │ ├── Bongo_22k.wav │ ├── Cabasa(1)_22k.wav │ ├── Clap(1)_22k.wav │ ├── Claves(1)_22k.wav │ ├── Conga(1)_22k.wav │ ├── Cowbell(3)_22k.wav │ ├── Crash(2)_22k.wav │ ├── Cuica(2)_22k.wav │ ├── GuiroLong(1)_22k.wav │ ├── GuiroShort(1)_22k.wav │ ├── HiHatClosed(1)_22k.wav │ ├── HiHatOpen(2)_22k.wav │ ├── HiHatPedal(1)_22k.wav │ ├── Maracas(1)_22k.wav │ ├── SideStick(1)_22k.wav │ ├── SnareDrum(1)_22k.wav │ ├── Tambourine(3)_22k.wav │ ├── Tom(1)_22k.wav │ ├── Triangle(1)_22k.wav │ ├── Vibraslap(1)_22k.wav │ └── WoodBlock(1)_22k.wav └── instruments │ ├── AcousticGuitar_F3_22k.wav │ ├── AcousticPiano(5)_A#3_22k.wav │ ├── AcousticPiano(5)_C4_22k.wav │ ├── AcousticPiano(5)_C6_22k.wav │ ├── AcousticPiano(5)_D#6_22k.wav │ ├── AcousticPiano(5)_D7_22k.wav │ ├── AcousticPiano(5)_F5_22k.wav │ ├── AcousticPiano(5)_G4_22k.wav │ ├── AltoSax(3)_C6_22k.wav │ ├── AltoSax_A3_22K.wav │ ├── BassTrombone_A2(2)_22k.wav │ ├── BassTrombone_A2(3)_22k.wav │ ├── Bassoon_C3_22k.wav │ ├── Cello(3)_A#2_22k.wav │ ├── Cello(3b)_C2_22k.wav │ ├── Choir(4)_F3_22k.wav │ ├── Choir(4)_F4_22k.wav │ ├── Choir(4)_F5_22k.wav │ ├── Clarinet_C4_22k.wav │ ├── ElectricBass(2)_G1_22k.wav │ ├── ElectricGuitar(2)_F3(1)_22k.wav │ ├── ElectricPiano_C2_22k.wav │ ├── ElectricPiano_C4_22k.wav │ ├── EnglishHorn(1)_D4_22k.wav │ ├── EnglishHorn(1)_F3_22k.wav │ ├── Flute(3)_B5(1)_22k.wav │ ├── Flute(3)_B5(2)_22k.wav │ ├── Marimba_C4_22k.wav │ ├── MusicBox_C4_22k.wav │ ├── Organ(2)_G2_22k.wav │ ├── Pizz(2)_A3_22k.wav │ ├── Pizz(2)_E4_22k.wav │ ├── Pizz(2)_G2_22k.wav │ ├── SteelDrum_D5_22k.wav │ ├── SynthLead(6)_C4_22k.wav │ ├── SynthLead(6)_C6_22k.wav │ ├── SynthPad(2)_A3_22k.wav │ ├── SynthPad(2)_C6_22k.wav │ ├── TenorSax(1)_C3_22k.wav │ ├── Trombone_B3_22k.wav │ ├── Trumpet_E5_22k.wav │ ├── Vibraphone_C3_22k.wav │ ├── Violin(2)_D4_22K.wav │ ├── Violin(3)_A4_22k.wav │ ├── Violin(3b)_E5_22k.wav │ └── WoodenFlute_C5_22k.wav ├── test ├── artifacts │ ├── IOMock.js │ ├── InterpreterMock.js │ ├── RuntimeMock.js │ ├── SpriteMock.js │ ├── StageMock.js │ ├── TargetMock.js │ ├── TestHelpers.js │ ├── ThreadMock.js │ ├── ask-artifact.js │ ├── io-artifact.js │ ├── reporterValues.js │ └── scratch-artifact.js ├── fixtures │ └── karma.conf.js ├── lib │ └── .git-keep └── unit │ ├── interpreterSpec.js │ ├── ioSpec.js │ ├── looksPrimitiveSpec.js │ ├── reporterSpec.js │ ├── runTimeSpec.js │ ├── scratchSpec.js │ ├── sensingPrimitiveSpec.js │ ├── spriteSpec.js │ ├── stageSpec.js │ └── threadSpec.js └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | test/lib/* 3 | .DS_Store 4 | 5 | logs 6 | 7 | npm-debug.log 8 | node_modules 9 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": [ 3 | "for", 4 | "while", 5 | "do", 6 | "try", 7 | "catch" 8 | ], 9 | "requireSpaceAfterKeywords": [ 10 | "default" 11 | ], 12 | "requireSpacesInFunctionExpression": { 13 | "beforeOpeningCurlyBrace": true 14 | }, 15 | "disallowSpacesInFunctionExpression": { 16 | "beforeOpeningRoundBrace": true 17 | }, 18 | "disallowEmptyBlocks": true, 19 | "disallowSpaceAfterObjectKeys": true, 20 | "requireCommaBeforeLineBreak": true, 21 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 22 | "validateIndentation": 4, 23 | "disallowMixedSpacesAndTabs": true, 24 | "disallowTrailingWhitespace": true, 25 | "safeContextKeyword": "self" 26 | } -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | The inital work on the HTML5 Scratch player was done by Tim Mickel. John Maloney provided feedback and guidance. Shane Clements took over the development and management late summer 2013. 2 | 3 | Scimonster fixed some bugs and added some features: the time/date blocks, list reporter block, fixed timer blocks, added timer reporter. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Deprecated 2 | #### This project is no longer actively maintained by the Scratch Team. For a list of active open source projects please visit: https://scratch.mit.edu/developers 3 | 4 | --- 5 | 6 | # Scratch HTML5 Player 7 | 8 | This project aims to create a Scratch Player in HTML5. Scratch is currently implemented with Actionscript 3 and requires the Flash Player version 10.2. Since Flash does not run on iOS (iPads, iPods, etc) and newer Android devices, we would like to have an HTML5 version to display (but not edit) projects on mobile devices. Scratch projects played in the HTML5 player should look and behave as closely as possible to the way they look and behave when played by the Flash player. We will not be accepting pull requests for new features that don't already exist in the Flash based Scratch project player. 9 | 10 | This source code is made available under the GNU General Public License version 2. Modified versions of this source code may be licensed under the GNU GPL v2 or later versions of the GNU General Public License. 11 | 12 | The [Flash version is now open source](https://github.com/LLK/scratch-flash) and will be a great help in understanding how features are currently implemented in the version on http://scratch.mit.edu/. 13 | 14 | There are a few github issues created that represent some of the missing features. At this point, the HTML5 player is about 40% complete and can run some simple projects. 15 | 16 | Unimplementable Features on iOS: Image effects for whirl, fisheye, mosaic, and pixelate. Sound and video input for loudness, video motion, and touching colors from the video. 17 | 18 | More documentation will be added as time permits. Thanks for contributing, and Scratch On! 19 | 20 | ## Contributions 21 | 22 | Thank you for your interest in helping out with the Scratch HTML5 Player. [@sclements](https://github.com/sclements/) is the maintainer of the project and reviews all code before pull requests are approved. Though we appreciate all attempts to contribute, there are some contraints that must be met before pull requests can be approved. Here are our top concerns for contributions: matching the behavior and interface of the Flash player, code cleanliness and organization, and robust well tested logic. CSS goes into player.css (not in the html or javascript). Please use [compare.html](https://github.com/LLK/scratch-html5/blob/master/compare.html) to compare your work with the production Flash player. 23 | 24 | 25 | ## Installation 26 | 27 | Special headers are sent on the Scratch asset servers to allow for cross-origin image manipulation. A proxy is no longer needed provided you are using projects hosted on the Scratch website. However, the project must either run locally with "Disabled Local File Restrictions" (Safari) or the `--disable-web-security` parameter (Google Chrome/Chromium) or you can always use a development server like [http-server](https://www.npmjs.org/package/http-server). This is compatible with Javascript security models in today's browsers. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) for more information. To test the HTML5 player against the Flash player you can use the compare.html web page. 28 | 29 | See the file `TESTING.md` for more details. 30 | 31 | 32 | Unit Tests 33 | ---------- 34 | The tests are written using Karma and there should be a 100% passing rate in order to commit any code to the project. 35 | 36 | The expectation is to add a unit test for any code that you contribute to the project. 37 | 38 | 39 | Install Node 40 | --------------------------------------- 41 | 42 | To install [Node.js](http://nodejs.org) and [NPM](http://npmjs.org) simply go to [http://nodejs.org](http://nodejs.org/), download the package for your operating system and install. Once installed, navigate to your local scratch directory and run: 43 | 44 | ```bash 45 | npm install 46 | ``` 47 | 48 | To Run the tests 49 | ---------------- 50 | 51 | ```bash 52 | npm test 53 | ``` 54 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | To set up the player for testing and development, simply download the source and open [index.html](https://github.com/LLK/scratch-html5/blob/master/index.html) in a recent web browser (Chrome, Firefox, Safari and other Webkit/Gecko-based browsers work best). You can also compare performance and features to the production Flash player by opening [compare.html](https://github.com/LLK/scratch-html5/blob/master/compare.html). 2 | 3 | The Scratch HTML5 player is no longer dependent on a local proxy. 4 | -------------------------------------------------------------------------------- /TRADEMARK_POLICY: -------------------------------------------------------------------------------- 1 | The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission. 2 | -------------------------------------------------------------------------------- /compare.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | font-family: sans-serif; 4 | color: #000; 5 | width: 1108px; 6 | margin: 0 auto; 7 | } 8 | 9 | .container { 10 | display: inline-block; 11 | vertical-align: top; 12 | } 13 | 14 | textarea { 15 | width: 482px; 16 | padding: 6px; 17 | -moz-box-sizing: border-box; 18 | box-sizing: border-box; 19 | height: 200px; 20 | display: block; 21 | margin: 16px auto; 22 | border: 1px solid #aaa; 23 | box-shadow: inset 3px 3px 3px -3px rgba(0, 0, 0, .3); 24 | } 25 | 26 | #flash-scratch { 27 | text-align: center; 28 | visibility: hidden; 29 | display: block; 30 | margin: 48px 0 62px; 31 | } 32 | 33 | #flash-scratch p { 34 | color: #aaa; 35 | font-size: 22px; 36 | margin-top: 14px; 37 | line-height: 28px; 38 | } 39 | -------------------------------------------------------------------------------- /compare.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Scratch HTML5 vs. Flash 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 80 | 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
HTML5
90 | 91 | 92 | 93 |
94 |
95 |
96 |
97 |
98 |
99 |
Loading project…
100 |
101 |
102 |
103 |
104 | 105 |
106 | http://scratch.mit.edu/projects/ 107 |
108 |
109 |
110 |

Oh Noes! Scratch project cannot display.
Flash player is disabled, missing, or less than version 10.2.

111 | 112 | Get Adobe Flash player 113 | 114 |
115 | 116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | -------------------------------------------------------------------------------- /img/ask-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/ask-bottom.png -------------------------------------------------------------------------------- /img/ask-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/ask-button.png -------------------------------------------------------------------------------- /img/fullScreenOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/fullScreenOff.png -------------------------------------------------------------------------------- /img/fullScreenOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/fullScreenOn.png -------------------------------------------------------------------------------- /img/greenFlagOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/greenFlagOff.png -------------------------------------------------------------------------------- /img/greenFlagOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/greenFlagOn.png -------------------------------------------------------------------------------- /img/playerStartFlag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/playerStartFlag.png -------------------------------------------------------------------------------- /img/say-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/say-bottom.png -------------------------------------------------------------------------------- /img/stopOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/stopOff.png -------------------------------------------------------------------------------- /img/stopOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/stopOn.png -------------------------------------------------------------------------------- /img/think-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/img/think-bottom.png -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 578px; 3 | padding: 16px; 4 | margin: 0 auto; 5 | background: #fff; 6 | font-family: sans-serif; 7 | color: #000; 8 | } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Scratch HTML5 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
HTML5
31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
Loading project…
41 |
42 |
43 |
44 |
45 | 46 |
47 | http://scratch.mit.edu/projects/ 48 |
49 | 50 |

Scratch HTML5 player

51 |

The Scratch HTML5 player is still in development. Feedback is welcome! Please report any bugs (or differences from the Flash player) on Github.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /js/IO.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // IO.js 18 | // Tim Mickel, March 2012 19 | 20 | // IO handles JSON communication and processing. 21 | // We make the sprites and threads here. 22 | 23 | 'use strict'; 24 | 25 | var IO = function() { 26 | this.data = null; 27 | this.project_base = 'http://cdn.projects.scratch.mit.edu/internalapi/project/'; 28 | this.project_suffix = '/get/'; 29 | this.asset_base = 'http://cdn.assets.scratch.mit.edu/internalapi/asset/'; 30 | this.asset_suffix = '/get/'; 31 | this.soundbank_base = 'soundbank/'; 32 | this.spriteLayerCount = 0; 33 | }; 34 | 35 | IO.prototype.loadProject = function(project_id) { 36 | var self = this; 37 | $.getJSON(this.project_base + project_id + this.project_suffix, function(data) { 38 | self.data = data; 39 | self.makeObjects(); 40 | self.loadThreads(); 41 | self.loadNotesDrums(); 42 | runtime.loadStart(); // Try to run the project. 43 | }); 44 | }; 45 | 46 | IO.prototype.soundRequest = function(sound, sprite) { 47 | var request = new XMLHttpRequest(); 48 | request.open('GET', this.asset_base + sound['md5'] + this.asset_suffix, true); 49 | request.responseType = 'arraybuffer'; 50 | request.onload = function() { 51 | var waveData = request.response; 52 | // Decode the waveData and populate a buffer channel with the samples 53 | var snd = new SoundDecoder(waveData); 54 | var samples = snd.getAllSamples(); 55 | sound.buffer = runtime.audioContext.createBuffer(1, samples.length, runtime.audioContext.sampleRate); 56 | var data = sound.buffer.getChannelData(0); 57 | for (var i = 0; i < data.length; i++) { 58 | data[i] = samples[i]; 59 | } 60 | sprite.soundsLoaded++; 61 | }; 62 | request.send(); 63 | }; 64 | 65 | IO.prototype.loadNotesDrums = function() { 66 | var self = this; 67 | $.each(Instr.wavs, function(name, file) { 68 | var request = new XMLHttpRequest(); 69 | request.open('GET', self.soundbank_base + escape(file), true); 70 | request.responseType = 'arraybuffer'; 71 | request.onload = function() { 72 | var waveData = new OffsetBuffer(request.response); 73 | // Decode the waveData and populate a buffer channel with the samples 74 | var info = WAVFile.decode(request.response); 75 | waveData.offset = info.sampleDataStart; 76 | var soundBuffer = waveData.readBytes(2 * info.sampleCount); 77 | Instr.samples[name] = soundBuffer; 78 | Instr.wavsLoaded++; 79 | }; 80 | request.send(); 81 | }); 82 | }; 83 | 84 | IO.prototype.makeObjects = function() { 85 | // Create the stage 86 | runtime.stage = new Stage(this.data); 87 | runtime.stage.attach(runtime.scene); 88 | runtime.stage.attachPenLayer(runtime.scene); 89 | runtime.stage.loadSounds(); 90 | // Create the sprites and watchers 91 | function createObj(obj, sprite) { 92 | var newSprite; 93 | function createSprite(obj) { 94 | var newSprite = new Sprite(obj); 95 | newSprite.loadSounds(); 96 | return newSprite; 97 | } 98 | function createReporter(obj, sprite) { 99 | var newSprite; 100 | if (obj.listName) { // list 101 | if (!(sprite===runtime.stage && !runtime.stage.lists[obj.listName])) { // for local lists, only if in sprite 102 | newSprite = new List(obj, sprite.objName); 103 | runtime.reporters.push(newSprite); 104 | } 105 | } else { 106 | newSprite = new Reporter(obj); 107 | runtime.reporters.push(newSprite); 108 | } 109 | return newSprite; 110 | } 111 | if (typeof(obj.objName) === "string") { // sprite 112 | newSprite = createSprite(obj); 113 | sprite = newSprite; 114 | } else { 115 | newSprite = createReporter(obj, sprite); 116 | } 117 | if (newSprite) { 118 | runtime.sprites.push(newSprite); 119 | newSprite.attach(runtime.scene); 120 | } 121 | } 122 | $.each(this.data.children, function(index, obj) { 123 | createObj(obj, runtime.stage); // create children of stage - sprites, watchers, and stage's lists 124 | }); 125 | $.each(runtime.sprites.filter(function(sprite) {return sprite instanceof Sprite}), function(index, sprite) { // list of sprites 126 | $.each(sprite.lists, function(index, list) { 127 | createObj(list, sprite); // create local lists 128 | }); 129 | }); 130 | }; 131 | 132 | IO.prototype.loadThreads = function() { 133 | var target = runtime.stage; 134 | var scripts = target.data.scripts; 135 | if (scripts) { 136 | for (var s in scripts) { 137 | target.stacks.push(interp.makeBlockList(scripts[s][2])); 138 | } 139 | } 140 | $.each(this.data.children, function(index, obj) { 141 | target = runtime.sprites[index]; 142 | if (typeof(target) != 'undefined' && target.data && target.data.scripts) { 143 | $.each(target.data.scripts, function(j, s) { 144 | target.stacks.push(interp.makeBlockList(s[2])); 145 | }); 146 | } 147 | }); 148 | }; 149 | 150 | // Returns the number sprite we are rendering 151 | // used for initial layering assignment 152 | IO.prototype.getCount = function() { 153 | var rv = this.spriteLayerCount; 154 | this.spriteLayerCount++; 155 | return rv; 156 | }; 157 | -------------------------------------------------------------------------------- /js/Reporter.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var Reporter = function(data) { 19 | this.cmd = data.cmd; 20 | this.color = data.color; 21 | this.isDiscrete = data.isDiscrete; 22 | this.mode = data.mode; 23 | this.param = data.param; 24 | this.sliderMin = data.sliderMin; 25 | this.sliderMax = data.sliderMax; 26 | this.target = data.target; 27 | this.visible = data.visible; 28 | this.x = data.x; 29 | this.y = data.y; 30 | this.z = io.getCount(); 31 | 32 | //Set the label after hydrating the cmd and param variables 33 | this.label = this.determineReporterLabel(); 34 | 35 | this.el = null; // jQuery Element for the outer box 36 | this.valueEl = null; // jQ element containing the reporter value 37 | this.slider = null; // slider jQ element 38 | }; 39 | 40 | Reporter.prototype.determineReporterLabel = function() { 41 | if (this.target === 'Stage' && this.cmd === "getVar:") return this.param; 42 | if (this.target === 'Stage' && this.param === null) return this.cmd; 43 | return this.target + ': ' + this.param; 44 | } 45 | 46 | Reporter.prototype.attach = function(scene) { 47 | switch (this.mode) { 48 | case 1: // Normal 49 | case 3: // Slider 50 | this.el = $('
' + this.label + '
'); 51 | this.valueEl = $('
null
'); 52 | this.el.append(this.valueEl); 53 | if (this.mode == 3) { 54 | // Slider-specific 55 | // Temporarily, set the value to sliderMin until an update 56 | this.slider = $(''); 60 | this.slider.change(this.changeSlider); 61 | this.el.append('
'); 62 | this.el.append(this.slider); 63 | } 64 | break; 65 | case 2: // Large 66 | this.el = $('
null
'); 67 | this.valueEl = this.el; 68 | break; 69 | } 70 | this.el.css('left', this.x); 71 | this.el.css('top', this.y); 72 | this.el.css('z-index', this.z); 73 | var cR = (this.color >> 16); 74 | var cG = (this.color >> 8 & 255); 75 | var cB = (this.color & 255); 76 | this.valueEl.css('background-color', 'rgb(' + cR + ',' + cG + ',' + cB + ')'); 77 | this.el.css('display', this.visible ? 'inline-block' : 'none'); 78 | scene.append(this.el); 79 | }; 80 | 81 | Reporter.prototype.update = function() { 82 | this.el.css('display', this.visible ? 'inline-block' : 'none'); 83 | if (!this.visible) return; 84 | 85 | var newValue = ''; 86 | var target = runtime.spriteNamed(this.target); 87 | switch (this.cmd) { 88 | case 'answer': 89 | newValue = target.askAnswer; 90 | break; 91 | case 'getVar:': 92 | newValue = target.variables[this.param]; 93 | break; 94 | case 'xpos': 95 | newValue = target.scratchX; 96 | break; 97 | case 'ypos': 98 | newValue = target.scratchY; 99 | break; 100 | case 'heading': 101 | newValue = target.direction; 102 | break; 103 | case 'scale': 104 | newValue = target.getSize(); 105 | break; 106 | case 'sceneName': 107 | newValue = runtime.stage.costumes[runtime.stage.currentCostumeIndex].costumeName; 108 | break; 109 | case 'costumeIndex': 110 | newValue = target.currentCostumeIndex + 1; 111 | break; 112 | case 'timer': 113 | newValue = '' + Math.round(interp.primitiveTable.timer() * 10) / 10; 114 | break; 115 | } 116 | if (typeof newValue === 'number' && Math.abs(newValue) > 0.001) { 117 | newValue = Math.round(newValue * 1000) / 1000; 118 | } 119 | newValue = '' + newValue; 120 | this.valueEl.html(newValue); 121 | if (this.mode == 3) { 122 | this.slider.val(Number(newValue)); 123 | } 124 | }; 125 | 126 | Reporter.prototype.updateLayer = function() { 127 | this.el.css('z-index', this.z); 128 | }; 129 | 130 | Reporter.prototype.changeSlider = function() { 131 | var newValue = Number($(this).val()); 132 | var target = runtime.spriteNamed($(this).attr('data-target')); 133 | var variable = $(this).attr('data-var'); 134 | target.variables[variable] = newValue; 135 | }; 136 | 137 | var List = function(data, sprite) { 138 | this.contents = data.contents; 139 | this.listName = data.listName; 140 | 141 | this.height = data.height; 142 | this.width = data.width; 143 | this.x = data.x; 144 | this.y = data.y; 145 | this.z = io.getCount(); 146 | this.visible = data.visible; 147 | 148 | this.target = sprite; 149 | 150 | // this.isPersistent = data.isPersistent; 151 | 152 | this.el = null; // jQuery element for list 153 | this.containerEl = null; 154 | this.scrollbar = null; 155 | }; 156 | 157 | List.prototype.attach = function(scene) { 158 | this.el = $('
'); 159 | this.el.append('
'+(this.target==='Stage'?'':this.target+' ')+this.listName); 160 | var c = this.containerEl = $('
').appendTo(this.el).width(this.width-13).height(this.height-34); 161 | var s = this.scrollbar = $('
').appendTo(this.el); 162 | var sb = s.children('.list-scrollbar'); 163 | sb.mousedown(function(e) { 164 | if (e.which===1) $(this).data({scrolling:true,startY:e.pageY}); // left button 165 | }); 166 | $('body').mousemove(function(e) { 167 | if (sb.data('scrolling')) { 168 | var offset = parseInt(sb.css('top'))+e.pageY-sb.data('startY'); 169 | if (offset < 0) { 170 | offset = 0; 171 | } 172 | if (offset > c.height()-sb.height()) { 173 | offset = c.height()-sb.height(); 174 | } 175 | sb.css('top',offset); 176 | c.scrollTop(c.height()/sb.height()*offset); 177 | } 178 | }).mouseup(function() { 179 | if ($.hasData(sb[0],'scrolling')) sb.removeData(['scrolling','startY']); 180 | }); 181 | // this.el.append('
+'); // disabled because it doesn't do anything even in the original 182 | this.el.append('
length: '+this.contents.length); 183 | scene.append(this.el); 184 | this.update(); 185 | this.el.css('left', this.x); 186 | this.el.css('top', this.y); 187 | this.el.width(this.width); 188 | this.el.height(this.height); 189 | this.el.css('z-index', this.z); 190 | this.el.css('display', this.visible ? 'inline-block' : 'none'); 191 | }; 192 | 193 | List.prototype.update = function() { 194 | this.contents = findList(runtime.spriteNamed(this.target),this.listName); 195 | 196 | this.el.css('display', this.visible ? 'inline-block' : 'none'); 197 | if (!this.visible) return; 198 | 199 | var c = this.containerEl.html(''); // so that it can be used inside the forEach 200 | this.contents.forEach(function(val,i) { 201 | $('
').appendTo(c).append('
'+(i+1),'
'+val); 202 | }); 203 | c.find('.list-index').width(c.find('.list-index').last().width()); 204 | c.find('.list-item').width(c.width()-c.find('.list-index').width()-15); 205 | var s = this.scrollbar.height(c.height()); 206 | s.children('.list-scrollbar').height(s.height()/c[0].scrollHeight*s.height()).css('display', s.children('.list-scrollbar').height()===c.height() ? 'none' : 'inline-block'); 207 | this.el.find('.list-length').text('length: '+this.contents.length); 208 | }; 209 | 210 | List.prototype.updateLayer = function() { 211 | this.el.css('z-index', this.z); 212 | }; 213 | 214 | -------------------------------------------------------------------------------- /js/Runtime.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // Runtime.js 18 | // Tim Mickel, July 2011 19 | 20 | // Runtime takes care of the rendering and stepping logic. 21 | 22 | 'use strict'; 23 | 24 | var t = new Timer(); 25 | 26 | var Runtime = function() { 27 | this.scene = null; 28 | this.sprites = []; 29 | this.reporters = []; 30 | this.keysDown = {}; 31 | this.mouseDown = false; 32 | this.mousePos = [0, 0]; 33 | this.audioContext = null; 34 | this.audioGain = null; 35 | this.audioPlaying = []; 36 | this.notesPlaying = []; 37 | this.projectLoaded = false; 38 | }; 39 | 40 | // Initializer for the drawing and audio contexts. 41 | Runtime.prototype.init = function() { 42 | this.scene = $('#container'); 43 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 44 | this.audioContext = new AudioContext(); 45 | try { 46 | this.audioGain = this.audioContext.createGain(); 47 | } catch(err) { 48 | this.audioGain = this.audioContext.createGainNode(); 49 | } 50 | this.audioGain.connect(runtime.audioContext.destination); 51 | }; 52 | 53 | // Load start waits for the stage and the sprites to be loaded, without 54 | // hanging the browser. When the loading is finished, we begin the step 55 | // and animate methods. 56 | Runtime.prototype.loadStart = function() { 57 | if (!runtime.stage.isLoaded()) { 58 | setTimeout(function(runtime) { runtime.loadStart(); }, 50, this); 59 | return; 60 | } 61 | for (var obj = 0; obj < runtime.sprites.length; obj++) { 62 | if (typeof(runtime.sprites[obj]) == 'object' && runtime.sprites[obj].constructor == Sprite) { 63 | if (!runtime.sprites[obj].isLoaded()) { 64 | setTimeout(function(runtime) { runtime.loadStart(); }, 50, this); 65 | return; 66 | } 67 | } 68 | } 69 | if (Instr.wavsLoaded != Instr.wavCount) { 70 | setTimeout(function(runtime) { runtime.loadStart(); }, 50, this); 71 | return; 72 | } 73 | $('#preloader').css('display', 'none'); 74 | setInterval(this.step, 33); 75 | this.projectLoaded = true; 76 | }; 77 | 78 | Runtime.prototype.greenFlag = function() { 79 | if (this.projectLoaded) { 80 | interp.activeThread = new Thread(null); 81 | interp.threads = []; 82 | interp.primitiveTable.timerReset(); 83 | this.startGreenFlags(); 84 | } 85 | }; 86 | 87 | Runtime.prototype.stopAll = function() { 88 | interp.activeThread = new Thread(null); 89 | interp.threads = []; 90 | stopAllSounds(); 91 | // Hide sprite bubbles, resetFilters and doAsk prompts 92 | for (var s = 0; s < runtime.sprites.length; s++) { 93 | if (runtime.sprites[s].hideBubble) runtime.sprites[s].hideBubble(); 94 | if (runtime.sprites[s].resetFilters) runtime.sprites[s].resetFilters(); 95 | if (runtime.sprites[s].hideAsk) runtime.sprites[s].hideAsk(); 96 | } 97 | // Reset graphic effects 98 | runtime.stage.resetFilters(); 99 | }; 100 | 101 | // Step method for execution - called every 33 milliseconds 102 | Runtime.prototype.step = function() { 103 | interp.stepThreads(); 104 | for (var r = 0; r < runtime.reporters.length; r++) { 105 | runtime.reporters[r].update(); 106 | } 107 | }; 108 | 109 | // Stack functions -- push and remove stacks 110 | // to be run by the interpreter as threads. 111 | Runtime.prototype.allStacksDo = function(f) { 112 | var stage = runtime.stage; 113 | var stack; 114 | for (var i = runtime.sprites.length-1; i >= 0; i--) { 115 | var o = runtime.sprites[i]; 116 | if (typeof(o) == 'object' && o.constructor == Sprite) { 117 | $.each(o.stacks, function(index, stack) { 118 | f(stack, o); 119 | }); 120 | } 121 | } 122 | $.each(stage.stacks, function(index, stack) { 123 | f(stack, stage); 124 | }); 125 | }; 126 | 127 | // Hat triggers 128 | Runtime.prototype.startGreenFlags = function() { 129 | function startIfGreenFlag(stack, target) { 130 | if (stack.op == 'whenGreenFlag') interp.toggleThread(stack, target); 131 | } 132 | this.allStacksDo(startIfGreenFlag); 133 | }; 134 | 135 | Runtime.prototype.startKeyHats = function(ch) { 136 | var keyName = null; 137 | if (('A'.charCodeAt(0) <= ch) && (ch <= 'Z'.charCodeAt(0)) || 138 | ('a'.charCodeAt(0) <= ch) && (ch <= 'z'.charCodeAt(0))) 139 | keyName = String.fromCharCode(ch).toLowerCase(); 140 | if (('0'.charCodeAt(0) <= ch) && (ch <= '9'.charCodeAt(0))) 141 | keyName = String.fromCharCode(ch); 142 | 143 | if (ch == 37) keyName = "left arrow"; 144 | if (ch == 39) keyName = "right arrow"; 145 | if (ch == 38) keyName = "up arrow"; 146 | if (ch == 40) keyName = "down arrow"; 147 | if (ch == 32) keyName = "space"; 148 | 149 | if (keyName == null) return; 150 | var startMatchingKeyHats = function(stack, target) { 151 | if ((stack.op == "whenKeyPressed") && (stack.args[0] == keyName)) { 152 | // Only start the stack if it is not already running 153 | if (!interp.isRunning(stack)) { 154 | interp.toggleThread(stack, target); 155 | } 156 | } 157 | } 158 | runtime.allStacksDo(startMatchingKeyHats); 159 | }; 160 | 161 | Runtime.prototype.startClickedHats = function(sprite) { 162 | function startIfClicked(stack, target) { 163 | if (target == sprite && stack.op == "whenClicked" && !interp.isRunning(stack)) { 164 | interp.toggleThread(stack, target); 165 | } 166 | } 167 | runtime.allStacksDo(startIfClicked); 168 | }; 169 | 170 | // Returns true if a key is pressed. 171 | Runtime.prototype.keyIsDown = function(ch) { 172 | return this.keysDown[ch] || false; 173 | }; 174 | 175 | // Sprite named -- returns one of the sprites on the stage. 176 | Runtime.prototype.spriteNamed = function(n) { 177 | if (n == 'Stage') return this.stage; 178 | var selected_sprite = null; 179 | $.each(this.sprites, function(index, s) { 180 | if (s.objName == n) { 181 | selected_sprite = s; 182 | return false; 183 | } 184 | }); 185 | return selected_sprite; 186 | }; 187 | 188 | Runtime.prototype.getTimeString = function(which) { 189 | // Return local time properties. 190 | var now = new Date(); 191 | switch (which) { 192 | case 'hour': return now.getHours(); 193 | case 'minute': return now.getMinutes(); 194 | case 'second': return now.getSeconds(); 195 | case 'year': return now.getFullYear(); // four digit year (e.g. 2012) 196 | case 'month': return now.getMonth() + 1; // 1-12 197 | case 'date': return now.getDate(); // 1-31 198 | case 'day of week': return now.getDay() + 1; // 1-7, where 1 is Sunday 199 | } 200 | return ''; // shouldn't happen 201 | }; 202 | 203 | // Reassigns z-indices for layer functions 204 | Runtime.prototype.reassignZ = function(target, move) { 205 | var sprites = this.sprites; 206 | var oldIndex = -1; 207 | $.each(this.sprites, function(index, sprite) { 208 | if (sprite == target) { 209 | // Splice out the sprite from its old position 210 | oldIndex = index; 211 | sprites.splice(index, 1); 212 | } 213 | }); 214 | 215 | if (move == null) { 216 | // Move to the front 217 | this.sprites.splice(this.sprites.length, 0, target); 218 | } else if (oldIndex - move >= 0 && oldIndex - move < this.sprites.length + 1) { 219 | // Move to the new position 220 | this.sprites.splice(oldIndex - move, 0, target); 221 | } else { 222 | // No change is required 223 | this.sprites.splice(oldIndex, 0, target); 224 | } 225 | 226 | // Renumber the z-indices 227 | var newZ = 1; 228 | $.each(this.sprites, function(index, sprite) { 229 | sprite.z = newZ; 230 | sprite.updateLayer(); 231 | newZ++; 232 | }); 233 | }; 234 | -------------------------------------------------------------------------------- /js/Scratch.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // Scratch.js 18 | // Tim Mickel, July 2011 19 | 20 | // Here we define the actions taken on window load. 21 | // The three application-wide global variables are defined here. 22 | 23 | 'use strict'; 24 | 25 | var runtime, interp, io, iosAudioActive = false; 26 | function Scratch(project_id) { 27 | runtime = new Runtime(); 28 | runtime.init(); 29 | 30 | $(window).keydown(function(e) { 31 | runtime.keysDown[e.which] = true; 32 | runtime.startKeyHats(e.which); 33 | }); 34 | 35 | $(window).keyup(function(e) { 36 | delete runtime.keysDown[e.which]; 37 | }); 38 | 39 | var address = $('#address-hint'); 40 | var project = $('#project-id'); 41 | 42 | // Update the project ID field 43 | project.val(project_id); 44 | 45 | // Validate project ID field 46 | project.keyup(function() { 47 | var n = this.value; 48 | 49 | // Allow URL pasting 50 | var e = /projects\/(\d+)/.exec(n); 51 | if (e) { 52 | n = this.value = e[1]; 53 | } 54 | 55 | // Eventually, this will xhr to /projects/{{this.value}}/ and 56 | // change color based on whether the response is 404 or 200. 57 | $('#project-id, #address-hint').toggleClass('error', isNaN(n)); 58 | }); 59 | 60 | // Focus the actual input when the user clicks on the URL hint 61 | address.click(function() { 62 | project.select(); 63 | }); 64 | 65 | var width = address.outerWidth(); 66 | project.css({ 67 | paddingLeft: width, 68 | marginLeft: -width 69 | }); 70 | 71 | // Go project button behavior 72 | $('#go-project').click(function() { 73 | window.location = '#' + parseInt($('#project-id').val()); 74 | window.location.reload(true); 75 | }); 76 | 77 | // Green flag behavior 78 | $('#trigger-green-flag, #overlay').click(function() { 79 | if (!runtime.projectLoaded) return; 80 | $('#overlay').css('display', 'none'); 81 | runtime.greenFlag() 82 | }); 83 | 84 | // Stop button behavior 85 | $('#trigger-stop').click(function() { 86 | runtime.stopAll(); 87 | }); 88 | 89 | // Canvas container mouse events 90 | $('#container').mousedown(function(e) { 91 | runtime.mouseDown = true; 92 | //e.preventDefault(); 93 | }); 94 | 95 | $('#container').mouseup(function(e) { 96 | runtime.mouseDown = false; 97 | //e.preventDefault(); 98 | }); 99 | 100 | $('#container').mousemove(function(e) { 101 | var bb = this.getBoundingClientRect(); 102 | var absX = e.clientX - bb.left; 103 | var absY = e.clientY - bb.top; 104 | runtime.mousePos = [absX-240, -absY+180]; 105 | }); 106 | 107 | // Touch events - EXPERIMENTAL 108 | $(window).bind('touchstart', function(e) { 109 | // On iOS, we need to activate the Web Audio API 110 | // with an empty sound play on the first touch event. 111 | if (!iosAudioActive) { 112 | var ibuffer = runtime.audioContext.createBuffer(1, 1, 22050); 113 | var isource = runtime.audioContext.createBufferSource(); 114 | isource.buffer = ibuffer; 115 | isource.connect(runtime.audioContext.destination); 116 | isource.start(); 117 | iosAudioActive = true; 118 | } 119 | }); 120 | 121 | $('#container').bind('touchstart', function(e) { 122 | runtime.mouseDown = true; 123 | }); 124 | 125 | $('#container').bind('touchend', function(e) { 126 | runtime.mouseDown = true; 127 | }); 128 | 129 | $('#container').bind('touchmove', function(e) { 130 | var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]; 131 | var bb = this.getBoundingClientRect(); 132 | var absX = touch.clientX - bb.left; 133 | var absY = touch.clientY - bb.top; 134 | runtime.mousePos = [absX-240, -absY+180]; 135 | }); 136 | 137 | // Border touch events - EXPERIMENTAL 138 | $('#left').bind('touchstart touchmove', function(e) { runtime.keysDown[37] = true; runtime.startKeyHats(37); }); 139 | $('#left').bind('touchend', function(e) { delete runtime.keysDown[37]; }); 140 | $('#up').bind('touchstart touchmove', function(e) { runtime.keysDown[38] = true; runtime.startKeyHats(38); }); 141 | $('#up').bind('touchend', function(e) { delete runtime.keysDown[38]; }); 142 | $('#right').bind('touchstart touchmove', function(e) { runtime.keysDown[39] = true; runtime.startKeyHats(39); }); 143 | $('#right').bind('touchend', function(e) { delete runtime.keysDown[39]; }); 144 | $('#down').bind('touchstart touchmove', function(e) { runtime.keysDown[40] = true; runtime.startKeyHats(40); }); 145 | $('#down').bind('touchend', function(e) { delete runtime.keysDown[40]; }); 146 | 147 | // Load the interpreter and primitives 148 | interp = new Interpreter(); 149 | interp.initPrims(); 150 | 151 | // Load the requested project and go! 152 | io = new IO(); 153 | io.loadProject(project_id); 154 | }; 155 | -------------------------------------------------------------------------------- /js/Stage.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // Stage.js 18 | // Tim Mickel, July 2011 - March 2012 19 | 20 | // Provides the basic logic for the Stage, a special kind of Sprite. 21 | 22 | 'use strict'; 23 | 24 | var Stage = function(data) { 25 | // Place the background layer in the very back. 26 | // The pen layer is right above the stage background, 27 | // and all sprites are above that. 28 | this.z = -2; 29 | 30 | // Pen layer and canvas cache. 31 | this.penLayerLoaded = false; 32 | this.lineCanvas = document.createElement('canvas'); 33 | this.lineCanvas.width = 480; 34 | this.lineCanvas.height = 360; 35 | this.lineCache = this.lineCanvas.getContext('2d'); 36 | this.isStage = true; 37 | this.askAnswer = ""; //this is a private variable and should be blank 38 | 39 | Sprite.call(this, data); 40 | }; 41 | 42 | Stage.prototype = Object.create(Sprite.prototype); 43 | Stage.prototype.constructor = Stage; 44 | 45 | Stage.prototype.attachPenLayer = function(scene) { 46 | if (this.penLayerLoaded) return; 47 | this.penLayerLoaded = true; 48 | $(this.lineCanvas).css('position', 'absolute'); 49 | $(this.lineCanvas).css('z-index', '-1'); 50 | scene.append(this.lineCanvas); 51 | }; 52 | 53 | Stage.prototype.isLoaded = function() { 54 | return this.penLayerLoaded && this.costumesLoaded == this.costumes.length && this.soundsLoaded == Object.keys(this.sounds).length; 55 | }; 56 | 57 | // Pen functions 58 | Stage.prototype.clearPenStrokes = function() { 59 | this.lineCache.clearRect(0, 0, 480, 360); 60 | }; 61 | 62 | Stage.prototype.stroke = function(from, to, width, color) { 63 | this.lineCache.lineWidth = width; 64 | this.lineCache.lineCap = 'round'; 65 | this.lineCache.beginPath(); 66 | // Use .5 offsets for canvas rigid pixel drawing 67 | this.lineCache.moveTo(from[0] + 240.5, 180.5 - from[1]); 68 | this.lineCache.lineTo(to[0] + 240.5, 180.5 - to[1]); 69 | this.lineCache.strokeStyle = 'rgb(' + (color >>> 16 & 255) + ',' + (color >>> 8 & 255) + ',' + (color >>> 0 & 255) + ')'; 70 | this.lineCache.stroke(); 71 | }; 72 | -------------------------------------------------------------------------------- /js/primitives/LooksPrims.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var LooksPrims = function() {}; 19 | 20 | LooksPrims.prototype.addPrimsTo = function(primTable) { 21 | primTable['show'] = this.primShow; 22 | primTable['hide'] = this.primHide; 23 | 24 | primTable['nextCostume'] = this.primNextCostume; 25 | primTable['lookLike:'] = this.primShowCostume; 26 | primTable['costumeIndex'] = this.primCostumeNum; 27 | 28 | primTable['nextScene'] = this.primNextCostume; 29 | primTable['showBackground:'] = this.primShowCostume; 30 | primTable['backgroundIndex'] = this.primCostumeNum; 31 | 32 | primTable['startScene'] = this.primStartScene; 33 | primTable['backgroundIndex'] = this.primCostumeNum; 34 | 35 | primTable['changeSizeBy:'] = this.primChangeSize; 36 | primTable['setSizeTo:'] = this.primSetSize; 37 | primTable['scale'] = this.primSize; 38 | 39 | primTable['comeToFront'] = this.primGoFront; 40 | primTable['goBackByLayers:'] = this.primGoBack; 41 | 42 | primTable['changeGraphicEffect:by:'] = this.primChangeEffect; 43 | primTable['setGraphicEffect:to:'] = this.primSetEffect; 44 | primTable['filterReset'] = this.primClearEffects; 45 | 46 | primTable['say:'] = function(b) { showBubble(b, 'say'); }; 47 | primTable['say:duration:elapsed:from:'] = function(b) { showBubbleAndWait(b, 'say'); }; 48 | primTable['think:'] = function(b) { showBubble(b, 'think'); }; 49 | primTable['think:duration:elapsed:from:'] = function(b) { showBubbleAndWait(b, 'think'); }; 50 | }; 51 | 52 | LooksPrims.prototype.primShow = function(b) { 53 | interp.targetSprite().setVisible(true); 54 | interp.redraw(); 55 | }; 56 | 57 | LooksPrims.prototype.primHide = function(b) { 58 | interp.targetSprite().setVisible(false); 59 | interp.redraw(); 60 | }; 61 | 62 | LooksPrims.prototype.primNextCostume = function(b) { 63 | interp.targetSprite().showCostume(interp.targetSprite().currentCostumeIndex + 1); 64 | interp.redraw(); 65 | }; 66 | 67 | LooksPrims.prototype.primShowCostume = function(b) { 68 | var s = interp.targetSprite(); 69 | if (s == null) return; 70 | var arg = interp.arg(b, 0); 71 | if (typeof(arg) == 'number') { 72 | s.showCostume(arg - 1); 73 | } else { 74 | if ((arg == 'CAMERA') || (arg == 'CAMERA - MIRROR')) { 75 | s.showCostumeNamed(arg); 76 | return; 77 | } 78 | var i = s.indexOfCostumeNamed(arg); 79 | if (i >= 0) { 80 | s.showCostume(i); 81 | } else { 82 | var n = parseInt(arg, 10); 83 | if (n === n) { // if n is not NaN 84 | s.showCostume(n - 1); 85 | } else { 86 | return; // arg did not match a costume name nor is a valid number 87 | } 88 | } 89 | } 90 | if (s.visible) interp.redraw(); 91 | }; 92 | 93 | LooksPrims.prototype.primStartScene = function(b) { 94 | var s = runtime.stage; 95 | var arg = interp.arg(b, 0); 96 | if (typeof(arg) == 'number') { 97 | s.showCostume(arg - 1); 98 | } else { 99 | if ((arg == 'CAMERA') || (arg == 'CAMERA - MIRROR')) { 100 | s.showCostumeNamed(arg); 101 | return; 102 | } 103 | var i = s.indexOfCostumeNamed(arg); 104 | if (i >= 0) { 105 | s.showCostume(i); 106 | } else { 107 | var n = parseInt(arg, 10); 108 | if (n === n) { // fast !isNaN check 109 | s.showCostume(n - 1); 110 | } else { 111 | return; // arg did not match a costume name nor is a valid number 112 | } 113 | } 114 | } 115 | if (s.visible) interp.redraw(); 116 | }; 117 | 118 | LooksPrims.prototype.primCostumeNum = function(b) { 119 | var s = interp.targetSprite(); 120 | return s == null ? 1 : s.currentCostumeIndex + 1; 121 | }; 122 | 123 | LooksPrims.prototype.primChangeSize = function(b) { 124 | var s = interp.targetSprite(); 125 | if (s == null) return; 126 | s.setSize(s.getSize() + interp.numarg(b, 0)); 127 | if (s.visible) interp.redraw(); 128 | }; 129 | 130 | LooksPrims.prototype.primSetSize = function(b) { 131 | var s = interp.targetSprite(); 132 | if (s == null) return; 133 | s.setSize(interp.numarg(b, 0)); 134 | if (s.visible) interp.redraw(); 135 | }; 136 | 137 | LooksPrims.prototype.primSize = function(b) { 138 | var s = interp.targetSprite(); 139 | if (s == null) return 100; 140 | return s.getSize(); 141 | }; 142 | 143 | LooksPrims.prototype.primGoFront = function(b) { 144 | var s = interp.targetSprite(); 145 | runtime.reassignZ(s, null); 146 | if (s.visible) interp.redraw(); 147 | }; 148 | 149 | LooksPrims.prototype.primGoBack = function(b) { 150 | var s = interp.targetSprite(); 151 | runtime.reassignZ(s, interp.numarg(b, 0)); 152 | if(s.visible) interp.redraw(); 153 | }; 154 | 155 | LooksPrims.prototype.primChangeEffect = function(b) { 156 | var s = interp.targetSprite(); 157 | s.filters[interp.arg(b, 0)] += interp.numarg(b, 1); 158 | s.updateFilters(); 159 | }; 160 | 161 | LooksPrims.prototype.primSetEffect = function(b) { 162 | var s = interp.targetSprite(); 163 | s.filters[interp.arg(b, 0)] = interp.numarg(b, 1); 164 | s.updateFilters(); 165 | }; 166 | 167 | LooksPrims.prototype.primClearEffects = function(b) { 168 | var s = interp.targetSprite(); 169 | s.resetFilters(); 170 | s.updateFilters(); 171 | }; 172 | 173 | var showBubble = function(b, type) { 174 | var s = interp.targetSprite(); 175 | if (s !== null) s.showBubble(interp.arg(b, 0), type); 176 | }; 177 | 178 | var showBubbleAndWait = function(b, type) { 179 | var s = interp.targetSprite(); 180 | if (s === null) return; 181 | if (interp.activeThread.firstTime) { 182 | var text = interp.arg(b, 0); 183 | var secs = interp.numarg(b, 1); 184 | s.showBubble(text, type); 185 | if (s.visible) interp.redraw(); 186 | interp.startTimer(secs); 187 | } else { 188 | if (interp.checkTimer()) s.hideBubble(); 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /js/primitives/MotionAndPenPrims.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var MotionAndPenPrims = function() {}; 19 | 20 | MotionAndPenPrims.prototype.addPrimsTo = function(primTable) { 21 | primTable['forward:'] = this.primMove; 22 | primTable['turnLeft:'] = this.primTurnLeft; 23 | primTable['turnRight:'] = this.primTurnRight; 24 | primTable['heading:'] = this.primSetDirection; 25 | primTable['pointTowards:'] = this.primPointTowards; 26 | primTable['gotoX:y:'] = this.primGoTo; 27 | primTable['gotoSpriteOrMouse:'] = this.primGoToSpriteOrMouse; 28 | primTable['glideSecs:toX:y:elapsed:from:'] = this.primGlide; 29 | 30 | primTable['changeXposBy:'] = this.primChangeX; 31 | primTable['xpos:'] = this.primSetX; 32 | primTable['changeYposBy:'] = this.primChangeY; 33 | primTable['ypos:'] = this.primSetY; 34 | 35 | primTable['bounceOffEdge'] = this.primBounceOffEdge; 36 | primTable['setRotationStyle'] = this.primSetRotationStyle; 37 | 38 | primTable['xpos'] = this.primXPosition; 39 | primTable['ypos'] = this.primYPosition; 40 | primTable['heading'] = this.primDirection; 41 | 42 | primTable['clearPenTrails'] = this.primClear; 43 | primTable['putPenDown'] = this.primPenDown; 44 | primTable['putPenUp'] = this.primPenUp; 45 | primTable['penColor:'] = this.primSetPenColor; 46 | primTable['setPenHueTo:'] = this.primSetPenHue; 47 | primTable['changePenHueBy:'] = this.primChangePenHue; 48 | primTable['setPenShadeTo:'] = this.primSetPenShade; 49 | primTable['changePenShadeBy:'] = this.primChangePenShade; 50 | primTable['penSize:'] = this.primSetPenSize; 51 | primTable['changePenSizeBy:'] = this.primChangePenSize; 52 | 53 | primTable['stampCostume'] = this.primStamp; 54 | primTable['stampTransparent'] = this.primStampTransparent; 55 | }; 56 | 57 | MotionAndPenPrims.prototype.primMove = function(b) { 58 | var s = interp.targetSprite(); 59 | var radians = (90 - s.direction) * Math.PI / 180; 60 | var d = interp.numarg(b, 0); 61 | 62 | moveSpriteTo(s, s.scratchX + d * Math.cos(radians), s.scratchY + d * Math.sin(radians)); 63 | if (s.visible) interp.redraw(); 64 | }; 65 | 66 | MotionAndPenPrims.prototype.primTurnLeft = function(b) { 67 | var s = interp.targetSprite(); 68 | var d = s.direction - interp.numarg(b, 0); 69 | s.setDirection(d); 70 | if (s.visible) interp.redraw(); 71 | }; 72 | 73 | MotionAndPenPrims.prototype.primTurnRight = function(b) { 74 | var s = interp.targetSprite(); 75 | var d = s.direction + interp.numarg(b, 0); 76 | s.setDirection(d); 77 | if (s.visible) interp.redraw(); 78 | }; 79 | 80 | MotionAndPenPrims.prototype.primSetDirection = function(b) { 81 | var s = interp.targetSprite(); 82 | s.setDirection(interp.numarg(b, 0)); 83 | if (s.visible) interp.redraw(); 84 | }; 85 | 86 | MotionAndPenPrims.prototype.primPointTowards = function(b) { 87 | var s = interp.targetSprite(); 88 | var p = mouseOrSpritePosition(interp.arg(b, 0)); 89 | if (s == null || p == null) return; 90 | var dx = p.x - s.scratchX; 91 | var dy = p.y - s.scratchY; 92 | var angle = 90 - Math.atan2(dy, dx) * 180 / Math.PI; 93 | s.setDirection(angle); 94 | if (s.visible) interp.redraw(); 95 | }; 96 | 97 | MotionAndPenPrims.prototype.primGoTo = function(b) { 98 | var s = interp.targetSprite(); 99 | if (s != null) moveSpriteTo(s, interp.numarg(b, 0), interp.numarg(b, 1)); 100 | }; 101 | 102 | MotionAndPenPrims.prototype.primGoToSpriteOrMouse = function(b) { 103 | var s = interp.targetSprite(); 104 | var p = mouseOrSpritePosition(interp.arg(b, 0)); 105 | if (s == null || p == null) return; 106 | moveSpriteTo(s, p.x, p.y); 107 | }; 108 | 109 | MotionAndPenPrims.prototype.primGlide = function(b) { 110 | var s = interp.targetSprite(); 111 | if (s == null) return; 112 | if (interp.activeThread.firstTime) { 113 | var secs = interp.numarg(b, 0); 114 | var destX = interp.numarg(b, 1); 115 | var destY = interp.numarg(b, 2); 116 | if (secs <= 0) { 117 | moveSpriteTo(s, destX, destY); 118 | return; 119 | } 120 | // record state: [0]start msecs, [1]duration, [2]startX, [3]startY, [4]endX, [5]endY 121 | interp.activeThread.tmpObj = [interp.currentMSecs, 1000 * secs, s.scratchX, s.scratchY, destX, destY]; 122 | interp.startTimer(secs); 123 | } else { 124 | var state = interp.activeThread.tmpObj; 125 | if (!interp.checkTimer()) { 126 | // in progress: move to intermediate position along path 127 | var frac = (interp.currentMSecs - state[0]) / state[1]; 128 | var newX = state[2] + frac * (state[4] - state[2]); 129 | var newY = state[3] + frac * (state[5] - state[3]); 130 | moveSpriteTo(s, newX, newY); 131 | } else { 132 | // finished: move to final position and clear state 133 | moveSpriteTo(s, state[4], state[5]); 134 | interp.activeThread.tmpObj = null; 135 | } 136 | } 137 | }; 138 | 139 | MotionAndPenPrims.prototype.primChangeX = function(b) { 140 | var s = interp.targetSprite(); 141 | if (s != null) moveSpriteTo(s, s.scratchX + interp.numarg(b, 0), s.scratchY); 142 | }; 143 | 144 | MotionAndPenPrims.prototype.primSetX = function(b) { 145 | var s = interp.targetSprite(); 146 | if (s != null) moveSpriteTo(s, interp.numarg(b, 0), s.scratchY); 147 | }; 148 | 149 | MotionAndPenPrims.prototype.primChangeY = function(b) { 150 | var s = interp.targetSprite(); 151 | if (s != null) moveSpriteTo(s, s.scratchX, s.scratchY + interp.numarg(b, 0)); 152 | }; 153 | 154 | MotionAndPenPrims.prototype.primSetY = function(b) { 155 | var s = interp.targetSprite(); 156 | if (s != null) moveSpriteTo(s, s.scratchX, interp.numarg(b, 0)); 157 | }; 158 | 159 | MotionAndPenPrims.prototype.primBounceOffEdge = function(b) { 160 | var s = interp.targetSprite(); 161 | if (s == null) return; 162 | if (!turnAwayFromEdge(s)) return; 163 | ensureOnStageOnBounce(s); 164 | if (s.visible) interp.redraw(); 165 | }; 166 | 167 | MotionAndPenPrims.prototype.primSetRotationStyle = function(b) { 168 | var s = interp.targetSprite(); 169 | if (s == null) return; 170 | var request = interp.arg(b, 0); 171 | var rotationStyle = 'normal'; 172 | if (request == 'all around') rotationStyle = 'normal'; 173 | else if (request == 'left-right') rotationStyle = 'leftRight'; 174 | else if (request == 'none') rotationStyle = 'none'; 175 | s.setRotationStyle(rotationStyle); 176 | }; 177 | 178 | MotionAndPenPrims.prototype.primXPosition = function(b) { 179 | var s = interp.targetSprite(); 180 | return s != null ? s.scratchX : 0; 181 | }; 182 | 183 | MotionAndPenPrims.prototype.primYPosition = function(b) { 184 | var s = interp.targetSprite(); 185 | return s != null ? s.scratchY : 0; 186 | }; 187 | 188 | MotionAndPenPrims.prototype.primDirection = function(b) { 189 | var s = interp.targetSprite(); 190 | return s != null ? s.direction : 0; 191 | }; 192 | 193 | MotionAndPenPrims.prototype.primClear = function(b) { 194 | runtime.stage.clearPenStrokes(); 195 | interp.redraw(); 196 | }; 197 | 198 | MotionAndPenPrims.prototype.primPenDown = function(b) { 199 | var s = interp.targetSprite(); 200 | if (s != null) s.penIsDown = true; 201 | stroke(s, s.scratchX, s.scratchY, s.scratchX + 0.2, s.scratchY + 0.2); 202 | interp.redraw(); 203 | }; 204 | 205 | MotionAndPenPrims.prototype.primPenUp = function(b) { 206 | var s = interp.targetSprite(); 207 | if (s != null) s.penIsDown = false; 208 | }; 209 | 210 | MotionAndPenPrims.prototype.primSetPenColor = function(b) { 211 | var s = interp.targetSprite(); 212 | if (s != null) s.setPenColor(interp.numarg(b, 0)); 213 | }; 214 | 215 | MotionAndPenPrims.prototype.primSetPenHue = function(b) { 216 | var s = interp.targetSprite(); 217 | if (s != null) s.setPenHue(interp.numarg(b, 0)); 218 | }; 219 | 220 | MotionAndPenPrims.prototype.primChangePenHue = function(b) { 221 | var s = interp.targetSprite(); 222 | if (s != null) s.setPenHue(s.penHue + interp.numarg(b, 0)); 223 | }; 224 | 225 | MotionAndPenPrims.prototype.primSetPenShade = function(b) { 226 | var s = interp.targetSprite(); 227 | if (s != null) s.setPenShade(interp.numarg(b, 0)); 228 | }; 229 | 230 | MotionAndPenPrims.prototype.primChangePenShade = function(b) { 231 | var s = interp.targetSprite(); 232 | if (s != null) s.setPenShade(s.penShade + interp.numarg(b, 0)); 233 | }; 234 | 235 | MotionAndPenPrims.prototype.primSetPenSize = function(b) { 236 | var s = interp.targetSprite(); 237 | var w = Math.max(0, Math.min(interp.numarg(b, 0), 100)); 238 | if (s != null) s.penWidth = w; 239 | }; 240 | 241 | MotionAndPenPrims.prototype.primChangePenSize = function(b) { 242 | var s = interp.targetSprite(); 243 | var w = Math.max(0, Math.min(s.penWidth + interp.numarg(b, 0), 100)); 244 | if (s != null) s.penWidth = w; 245 | }; 246 | 247 | MotionAndPenPrims.prototype.primStamp = function(b) { 248 | var s = interp.targetSprite(); 249 | s.stamp(runtime.stage.lineCache, 100); 250 | }; 251 | 252 | MotionAndPenPrims.prototype.primStampTransparent = function(b) { 253 | var s = interp.targetSprite(); 254 | var transparency = Math.max(0, Math.min(interp.numarg(b, 0), 100)); 255 | var alpha = 100 - transparency; 256 | s.stamp(runtime.stage.lineCache, alpha); 257 | }; 258 | 259 | // Helpers 260 | var stroke = function(s, oldX, oldY, newX, newY) { 261 | runtime.stage.stroke([oldX, oldY], [newX, newY], s.penWidth, s.penColorCache); 262 | interp.redraw(); 263 | }; 264 | 265 | var mouseOrSpritePosition = function(arg) { 266 | if (arg == '_mouse_') { 267 | var w = runtime.stage; 268 | return new Point(runtime.mousePos[0], runtime.mousePos[1]); 269 | } else { 270 | var s = runtime.spriteNamed(arg); 271 | if (s == null) return null; 272 | return new Point(s.scratchX, s.scratchY); 273 | } 274 | return null; 275 | }; 276 | 277 | var moveSpriteTo = function(s, newX, newY) { 278 | var oldX = s.scratchX; 279 | var oldY = s.scratchY; 280 | s.setXY(newX, newY); 281 | s.keepOnStage(); 282 | if (s.penIsDown) stroke(s, oldX, oldY, s.scratchX, s.scratchY); 283 | if (s.penIsDown || s.visible) interp.redraw(); 284 | }; 285 | 286 | var turnAwayFromEdge = function(s) { 287 | // turn away from the nearest edge if it's close enough; otherwise do nothing 288 | // Note: comparisions are in the stage coordinates, with origin (0, 0) 289 | // use bounding rect of the sprite to account for costume rotation and scale 290 | var r = s.getRect(); 291 | // measure distance to edges 292 | var d1 = Math.max(0, r.left); 293 | var d2 = Math.max(0, r.top); 294 | var d3 = Math.max(0, 480 - r.right); 295 | var d4 = Math.max(0, 360 - r.bottom); 296 | // find the nearest edge 297 | var e = 0, minDist = 100000; 298 | if (d1 < minDist) { minDist = d1; e = 1; } 299 | if (d2 < minDist) { minDist = d2; e = 2; } 300 | if (d3 < minDist) { minDist = d3; e = 3; } 301 | if (d4 < minDist) { minDist = d4; e = 4; } 302 | if (minDist > 0) return false; // not touching to any edge 303 | // point away from nearest edge 304 | var radians = (90 - s.direction) * Math.PI / 180; 305 | var dx = Math.cos(radians); 306 | var dy = -Math.sin(radians); 307 | if (e == 1) { dx = Math.max(0.2, Math.abs(dx)); } 308 | if (e == 2) { dy = Math.max(0.2, Math.abs(dy)); } 309 | if (e == 3) { dx = 0 - Math.max(0.2, Math.abs(dx)); } 310 | if (e == 4) { dy = 0 - Math.max(0.2, Math.abs(dy)); } 311 | var newDir = Math.atan2(dy, dx) * 180 / Math.PI + 90; 312 | s.direction = newDir; 313 | return true; 314 | }; 315 | 316 | var ensureOnStageOnBounce = function(s) { 317 | var r = s.getRect(); 318 | if (r.left < 0) moveSpriteTo(s, s.scratchX - r.left, s.scratchY); 319 | if (r.top < 0) moveSpriteTo(s, s.scratchX, s.scratchY + r.top); 320 | if (r.right > 480) { 321 | moveSpriteTo(s, s.scratchX - (r.right - 480), s.scratchY); 322 | } 323 | if (r.bottom > 360) { 324 | moveSpriteTo(s, s.scratchX, s.scratchY + (r.bottom - 360)); 325 | } 326 | }; 327 | -------------------------------------------------------------------------------- /js/primitives/Primitives.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // Primitives.js 18 | // Tim Mickel, July 2011 19 | 20 | // Provides the basic primitives for the interpreter and loads in the more 21 | // complicated primitives, e.g. MotionAndPenPrims. 22 | 23 | 'use strict'; 24 | 25 | var Primitives = function() {} 26 | 27 | Primitives.prototype.addPrimsTo = function(primTable) { 28 | // Math primitives 29 | primTable['+'] = function(b) { return interp.numarg(b, 0) + interp.numarg(b, 1); }; 30 | primTable['-'] = function(b) { return interp.numarg(b, 0) - interp.numarg(b, 1); }; 31 | primTable['*'] = function(b) { return interp.numarg(b, 0) * interp.numarg(b, 1); }; 32 | primTable['/'] = function(b) { return interp.numarg(b, 0) / interp.numarg(b, 1); }; 33 | primTable['%'] = this.primModulo; 34 | primTable['randomFrom:to:'] = this.primRandom; 35 | primTable['<'] = function(b) { return (interp.numarg(b, 0) < interp.numarg(b, 1)); }; 36 | primTable['='] = function(b) { return (interp.arg(b, 0) == interp.arg(b, 1)); }; 37 | primTable['>'] = function(b) { return (interp.numarg(b, 0) > interp.numarg(b, 1)); }; 38 | primTable['&'] = function(b) { return interp.boolarg(b, 0) && interp.boolarg(b, 1); }; 39 | primTable['|'] = function(b) { return interp.boolarg(b, 0) || interp.boolarg(b, 1); }; 40 | primTable['not'] = function(b) { return !interp.boolarg(b, 0); }; 41 | primTable['abs'] = function(b) { return Math.abs(interp.numarg(b, 0)); }; 42 | primTable['sqrt'] = function(b) { return Math.sqrt(interp.numarg(b, 0)); }; 43 | 44 | primTable['\\\\'] = this.primModulo; 45 | primTable['rounded'] = function(b) { return Math.round(interp.numarg(b, 0)); }; 46 | primTable['computeFunction:of:'] = this.primMathFunction; 47 | 48 | // String primitives 49 | primTable['concatenate:with:'] = function(b) { return '' + interp.arg(b, 0) + interp.arg(b, 1); }; 50 | primTable['letter:of:'] = this.primLetterOf; 51 | primTable['stringLength:'] = function(b) { return interp.arg(b, 0).length; }; 52 | 53 | new VarListPrims().addPrimsTo(primTable); 54 | new MotionAndPenPrims().addPrimsTo(primTable); 55 | new LooksPrims().addPrimsTo(primTable); 56 | new SensingPrims().addPrimsTo(primTable); 57 | new SoundPrims().addPrimsTo(primTable); 58 | } 59 | 60 | Primitives.prototype.primRandom = function(b) { 61 | var n1 = interp.numarg(b, 0); 62 | var n2 = interp.numarg(b, 1); 63 | var low = n1 <= n2 ? n1 : n2; 64 | var hi = n1 <= n2 ? n2 : n1; 65 | if (low == hi) return low; 66 | // if both low and hi are ints, truncate the result to an int 67 | if (Math.floor(low) == low && Math.floor(hi) == hi) { 68 | return low + Math.floor(Math.random() * (hi + 1 - low)); 69 | } 70 | return Math.random() * (hi - low) + low; 71 | } 72 | 73 | Primitives.prototype.primLetterOf = function(b) { 74 | var s = interp.arg(b, 1); 75 | var i = interp.numarg(b, 0) - 1; 76 | if (i < 0 || i >= s.length) return ''; 77 | return s.charAt(i); 78 | } 79 | 80 | Primitives.prototype.primModulo = function(b) { 81 | var dividend = interp.numarg(b, 1); 82 | var n = interp.numarg(b, 0) % dividend; 83 | if (n / dividend < 0) n += dividend; 84 | return n; 85 | } 86 | 87 | Primitives.prototype.primMathFunction = function(b) { 88 | var op = interp.arg(b, 0); 89 | var n = interp.numarg(b, 1); 90 | switch(op) { 91 | case 'abs': return Math.abs(n); 92 | case 'sqrt': return Math.sqrt(n); 93 | case 'sin': return Math.sin(n * Math.PI / 180); 94 | case 'cos': return Math.cos(n * Math.PI / 180); 95 | case 'tan': return Math.tan(n * Math.PI / 180); 96 | case 'asin': return Math.asin(n) * 180 / Math.PI; 97 | case 'acos': return Math.acos(n) * 180 / Math.PI; 98 | case 'atan': return Math.atan(n) * 180 / Math.PI; 99 | case 'ln': return Math.log(n); 100 | case 'log': return Math.log(n) / Math.LN10; 101 | case 'e ^': return Math.exp(n); 102 | case '10 ^': return Math.exp(n * Math.LN10); 103 | case 'floor': return Math.floor(n); 104 | case 'ceiling': return Math.ceil(n); 105 | } 106 | return 0; 107 | } 108 | 109 | -------------------------------------------------------------------------------- /js/primitives/SensingPrims.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var SensingPrims = function() {}; 19 | 20 | SensingPrims.prototype.addPrimsTo = function(primTable) { 21 | primTable['touching:'] = this.primTouching; 22 | primTable['touchingColor:'] = this.primTouchingColor; 23 | primTable['color:sees:'] = this.primColorTouchingColor; 24 | 25 | primTable['doAsk'] = this.primDoAsk; 26 | primTable['answer'] = this.primAnswer; 27 | 28 | primTable['keyPressed:'] = this.primKeyPressed; 29 | primTable['mousePressed'] = function(b) { return runtime.mouseDown; }; 30 | primTable['mouseX'] = function(b) { return runtime.mousePos[0]; }; 31 | primTable['mouseY'] = function(b) { return runtime.mousePos[1]; }; 32 | primTable['distanceTo:'] = this.primDistanceTo; 33 | 34 | primTable['getAttribute:of:'] = this.primGetAttribute; 35 | 36 | primTable['timeAndDate'] = function(b) { return runtime.getTimeString(interp.arg(b, 0)); }; 37 | primTable['timestamp'] = this.primTimestamp; 38 | }; 39 | 40 | SensingPrims.prototype.primTouching = function(b) { 41 | var s = interp.targetSprite(); 42 | if (s == null || !s.visible) return false; 43 | 44 | var arg = interp.arg(b, 0); 45 | if (arg == '_edge_') { 46 | return false; // TODO 47 | } 48 | 49 | if (arg == '_mouse_') { 50 | return false; // TODO 51 | } 52 | 53 | var s2 = runtime.spriteNamed(arg); 54 | if (s2 == null || !s2.visible) return false; 55 | 56 | return spriteHitTest(s, s2); 57 | }; 58 | 59 | SensingPrims.prototype.primTouchingColor = function(b) { 60 | var s = interp.targetSprite(); 61 | if (s == null || !s.visible) return false; 62 | 63 | var color = interp.arg(b, 0); 64 | 65 | return stageColorHitTest(s, color); 66 | }; 67 | 68 | SensingPrims.prototype.primColorTouchingColor = function(b) { 69 | var s = interp.targetSprite(); 70 | if (s == null || !s.visible) return false; 71 | 72 | var myColor = interp.arg(b, 0); 73 | var stageColor = interp.arg(b, 1); 74 | 75 | return stageColorByColorHitTest(s, myColor, stageColor); 76 | }; 77 | 78 | var spriteHitTest = function(a, b) { 79 | var hitCanvas = document.createElement('canvas'); 80 | hitCanvas.width = 480; 81 | hitCanvas.height = 360; 82 | var hitTester = hitCanvas.getContext('2d'); 83 | hitTester.globalCompositeOperation = 'source-over'; 84 | a.stamp(hitTester, 100); 85 | hitTester.globalCompositeOperation = 'source-in'; 86 | b.stamp(hitTester, 100); 87 | 88 | var aData = hitTester.getImageData(0, 0, 480, 360).data; 89 | 90 | var pxCount = aData.length; 91 | for (var i = 0; i < pxCount; i += 4) { 92 | if (aData[i+3] > 0) { 93 | return true; 94 | } 95 | } 96 | return false; 97 | }; 98 | 99 | var stageColorHitTest = function(target, color) { 100 | var r, g, b; 101 | r = (color >> 16); 102 | g = (color >> 8 & 255); 103 | b = (color & 255); 104 | 105 | var targetCanvas = document.createElement('canvas'); 106 | targetCanvas.width = 480; 107 | targetCanvas.height = 360; 108 | var targetTester = targetCanvas.getContext('2d'); 109 | target.stamp(targetTester, 100); 110 | 111 | var stageCanvas = document.createElement('canvas'); 112 | stageCanvas.width = 480; 113 | stageCanvas.height = 360; 114 | var stageContext = stageCanvas.getContext('2d'); 115 | 116 | $.each(runtime.sprites, function(i, sprite) { 117 | if (sprite != target) 118 | sprite.stamp(stageContext, 100); 119 | }); 120 | 121 | var hitData = stageContext.getImageData(0, 0, stageCanvas.width, stageCanvas.height).data; 122 | var meshData = targetTester.getImageData(0, 0, targetCanvas.width, targetCanvas.height).data; 123 | var pxCount = meshData.length; 124 | for (var i = 0; i < pxCount; i += 4) { 125 | if (meshData[i+3] > 0 && hitData[i] == r && hitData[i+1] == g && hitData[i+2] == b) 126 | return true; 127 | } 128 | return false; 129 | }; 130 | 131 | var stageColorByColorHitTest = function(target, myColor, otherColor) { 132 | var threshold_acceptable = function(a, b, c, x, y, z) { 133 | var diff_a = Math.abs(a-x); 134 | var diff_b = Math.abs(b-y); 135 | var diff_c = Math.abs(c-z); 136 | if (diff_a + diff_b + diff_c < 100) { 137 | return true; 138 | } 139 | return false; 140 | }; 141 | var targetCanvas = document.createElement('canvas'); 142 | targetCanvas.width = 480; 143 | targetCanvas.height = 360; 144 | var targetTester = targetCanvas.getContext('2d'); 145 | target.stamp(targetTester, 100); 146 | var targetData = targetTester.getImageData(0, 0, targetCanvas.width, targetCanvas.height).data; 147 | 148 | // Calculate RGB values of the colors - TODO thresholding 149 | //myColor = Math.abs(myColor); 150 | //otherColor = Math.abs(otherColor); 151 | var mr, mg, mb, or, og, ob; 152 | mr = (myColor >> 16); 153 | mg = (myColor >> 8 & 255); 154 | mb = (myColor & 255); 155 | or = (otherColor >> 16); 156 | og = (otherColor >> 8 & 255); 157 | ob = (otherColor & 255); 158 | 159 | // Create the hit canvas for comparison 160 | var hitCanvas = document.createElement('canvas'); 161 | hitCanvas.width = 480; 162 | hitCanvas.height = 360; 163 | var hitCtx = hitCanvas.getContext('2d'); 164 | $.each(runtime.sprites, function(i, sprite) { 165 | if (sprite != target) { 166 | sprite.stamp(hitCtx, 100); 167 | } 168 | }); 169 | 170 | var hitData = hitCtx.getImageData(0, 0, hitCanvas.width, hitCanvas.height).data; 171 | var pxCount = targetData.length; 172 | for (var i = 0; i < pxCount; i += 4) { 173 | if (threshold_acceptable(targetData[i], targetData[i+1], targetData[i+2], mr, mg, mb) && threshold_acceptable(hitData[i], hitData[i+1], hitData[i+2], or, og, ob)) { 174 | return true; 175 | } 176 | } 177 | return false; 178 | }; 179 | 180 | SensingPrims.prototype.primDoAsk= function(b) { 181 | showBubble(b, "doAsk"); 182 | var s = interp.targetSprite(); 183 | if (s !== null) { 184 | interp.activeThread.paused = true; 185 | s.showAsk(); 186 | } 187 | }; 188 | 189 | SensingPrims.prototype.primAnswer = function(b) { 190 | var s = interp.targetStage(); 191 | return (s !== null ? s.askAnswer : undefined); 192 | }; 193 | 194 | 195 | SensingPrims.prototype.primKeyPressed = function(b) { 196 | var key = interp.arg(b, 0); 197 | var ch = key.charCodeAt(0); 198 | if (ch > 127) return false; 199 | if (key == "left arrow") ch = 37; 200 | if (key == "right arrow") ch = 39; 201 | if (key == "up arrow") ch = 38; 202 | if (key == "down arrow") ch = 40; 203 | if (key == "space") ch = 32; 204 | return (typeof(runtime.keysDown[ch]) != 'undefined'); 205 | }; 206 | 207 | SensingPrims.prototype.primDistanceTo = function(b) { 208 | var s = interp.targetSprite(); 209 | var p = mouseOrSpritePosition(interp.arg(b, 0)); 210 | if (s == null || p == null) return 0; 211 | var dx = p.x - s.scratchX; 212 | var dy = p.y - s.scratchY; 213 | return Math.sqrt((dx * dx) + (dy * dy)); 214 | }; 215 | 216 | SensingPrims.prototype.primGetAttribute = function(b) { 217 | var attr = interp.arg(b, 0); 218 | var targetSprite = runtime.spriteNamed(interp.arg(b, 1)); 219 | if (targetSprite == null) return 0; 220 | if (attr == 'x position') return targetSprite.scratchX; 221 | if (attr == 'y position') return targetSprite.scratchY; 222 | if (attr == 'direction') return targetSprite.direction; 223 | if (attr == 'costume #') return targetSprite.currentCostumeIndex + 1; 224 | if (attr == 'costume name') return targetSprite.costumes[targetSprite.currentCostumeIndex]['costumeName']; 225 | if (attr == 'size') return targetSprite.getSize(); 226 | if (attr == 'volume') return targetSprite.volume; 227 | return 0; 228 | }; 229 | 230 | SensingPrims.prototype.primTimeDate = function(b) { 231 | var dt = interp.arg(b, 0); 232 | var now = new Date(); 233 | if (dt == 'year') return now.getFullYear(); 234 | if (dt == 'month') return now.getMonth()+1; 235 | if (dt == 'date') return now.getDate(); 236 | if (dt == 'day of week') return now.getDay()+1; 237 | if (dt == 'hour') return now.getHours(); 238 | if (dt == 'minute') return now.getMinutes(); 239 | if (dt == 'second') return now.getSeconds(); 240 | return 0; 241 | }; 242 | 243 | SensingPrims.prototype.primTimestamp = function(b) { 244 | var now = new Date(); 245 | var epoch = new Date(2000, 0, 1); 246 | var dst = now.getTimezoneOffset() - epoch.getTimezoneOffset(); 247 | var msSince = now.getTime() - epoch.getTime(); 248 | msSince -= dst * 60000; 249 | return msSince / 86400000; 250 | }; 251 | 252 | // Helpers 253 | SensingPrims.prototype.mouseOrSpritePosition = function(arg) { 254 | if (arg == "_mouse_") { 255 | var w = runtime.stage; 256 | return new Point(runtime.mousePos[0], runtime.mousePos[1]); 257 | } else { 258 | var s = runtime.spriteNamed(arg); 259 | if (s == null) return null; 260 | return new Point(s.scratchX, s.scratchY); 261 | } 262 | return null; 263 | }; 264 | -------------------------------------------------------------------------------- /js/primitives/SoundPrims.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var SoundPrims = function() {}; 19 | 20 | SoundPrims.prototype.addPrimsTo = function(primTable) { 21 | primTable['playSound:'] = this.primPlaySound; 22 | primTable['doPlaySoundAndWait'] = this.primPlaySoundUntilDone; 23 | primTable['stopAllSounds'] = this.primStopAllSounds; 24 | 25 | primTable['playDrum'] = this.primPlayDrum; 26 | primTable['rest:elapsed:from:'] = this.primPlayRest; 27 | primTable['noteOn:duration:elapsed:from:'] = this.primPlayNote; 28 | primTable['instrument:'] = this.primSetInstrument; 29 | 30 | /*primTable['changeVolumeBy:'] = this.primChangeVolume; 31 | primTable['setVolumeTo:'] = this.primSetVolume; 32 | primTable['volume'] = this.primVolume;*/ 33 | 34 | primTable['changeTempoBy:'] = function(b) { runtime.stage.data.tempoBPM = runtime.stage.data.tempoBPM + interp.arg(b, 0); }; 35 | primTable['setTempoTo:'] = function(b) { runtime.stage.data.tempoBPM = interp.arg(b, 0); }; 36 | primTable['tempo'] = function(b) { return runtime.stage.data.tempoBPM; }; 37 | }; 38 | 39 | var playSound = function(snd) { 40 | if (snd.source) { 41 | // If this particular sound is already playing, stop it. 42 | snd.source.disconnect(); 43 | snd.source = null; 44 | } 45 | 46 | snd.source = runtime.audioContext.createBufferSource(); 47 | snd.source.buffer = snd.buffer; 48 | snd.source.connect(runtime.audioGain); 49 | 50 | // Track the sound's completion state 51 | snd.source.done = false; 52 | snd.source.finished = function() { 53 | // Remove from the active audio list and disconnect the source from 54 | // the sound dictionary. 55 | var i = runtime.audioPlaying.indexOf(snd); 56 | if (i > -1 && runtime.audioPlaying[i].source != null) { 57 | runtime.audioPlaying[i].source.done = true; 58 | runtime.audioPlaying[i].source = null; 59 | runtime.audioPlaying.splice(i, 1); 60 | } 61 | } 62 | window.setTimeout(snd.source.finished, snd.buffer.duration * 1000); 63 | // Add the global list of playing sounds and start playing. 64 | runtime.audioPlaying.push(snd); 65 | snd.source.start(); 66 | return snd.source; 67 | }; 68 | 69 | var playDrum = function(drum, secs, client) { 70 | var player = SoundBank.getDrumPlayer(drum, secs); 71 | player.client = client; 72 | player.setDuration(secs); 73 | var source = runtime.audioContext.createScriptProcessor(4096, 1, 1); 74 | source.onaudioprocess = function(e) { player.writeSampleData(e); }; 75 | source.soundPlayer = player; 76 | source.connect(runtime.audioGain); 77 | runtime.notesPlaying.push(source); 78 | source.finished = function() { 79 | var i = runtime.notesPlaying.indexOf(source); 80 | if (i > -1 && runtime.notesPlaying[i] != null) { 81 | runtime.notesPlaying.splice(i, 1); 82 | } 83 | } 84 | window.setTimeout(source.finished, secs * 1000); 85 | return player; 86 | }; 87 | 88 | var playNote = function(instrument, midiKey, secs, client) { 89 | var player = SoundBank.getNotePlayer(instrument, midiKey); 90 | player.client = client; 91 | player.setNoteAndDuration(midiKey, secs); 92 | var source = runtime.audioContext.createScriptProcessor(4096, 1, 1); 93 | source.onaudioprocess = function(e) { player.writeSampleData(e); }; 94 | source.connect(runtime.audioGain); 95 | runtime.notesPlaying.push(source); 96 | source.finished = function() { 97 | var i = runtime.notesPlaying.indexOf(source); 98 | if (i > -1 && runtime.notesPlaying[i] != null) { 99 | runtime.notesPlaying.splice(i, 1); 100 | } 101 | } 102 | window.setTimeout(source.finished, secs * 1000); 103 | return player; 104 | }; 105 | 106 | var stopAllSounds = function() { 107 | var oldPlaying = runtime.audioPlaying; 108 | runtime.audioPlaying = []; 109 | for (var s = 0; s < oldPlaying.length; s++) { 110 | if (oldPlaying[s].source) { 111 | oldPlaying[s].source.disconnect(); 112 | oldPlaying[s].source.finished(); 113 | } 114 | } 115 | 116 | var oldPlaying = runtime.notesPlaying; 117 | runtime.notesPlaying = []; 118 | for (var s = 0; s < oldPlaying.length; s++) { 119 | if (oldPlaying[s]) { 120 | oldPlaying[s].disconnect(); 121 | oldPlaying[s].finished(); 122 | } 123 | } 124 | }; 125 | 126 | SoundPrims.prototype.primPlaySound = function(b) { 127 | var s = interp.targetSprite(); 128 | if (s == null) return; 129 | var snd = s.soundNamed(interp.arg(b, 0)); 130 | if (snd != null) playSound(snd); 131 | }; 132 | 133 | SoundPrims.prototype.primPlaySoundUntilDone = function(b) { 134 | var activeThread = interp.activeThread; 135 | if (activeThread.firstTime) { 136 | var snd = interp.targetSprite().soundNamed(interp.arg(b, 0)); 137 | if (snd == null) return; 138 | activeThread.tmpObj = playSound(snd); 139 | activeThread.firstTime = false; 140 | } 141 | var player = activeThread.tmpObj; 142 | if (player == null || player.done || player.playbackState == 3) { 143 | activeThread.tmpObj = null; 144 | activeThread.firstTime = true; 145 | } else { 146 | interp.yield = true; 147 | } 148 | }; 149 | 150 | var beatsToSeconds = function(beats) { 151 | return beats * 60 / runtime.stage.data.tempoBPM; 152 | }; 153 | 154 | SoundPrims.prototype.primPlayNote = function(b) { 155 | var s = interp.targetSprite(); 156 | if (s == null) return; 157 | if (interp.activeThread.firstTime) { 158 | var key = interp.numarg(b, 0); 159 | var secs = beatsToSeconds(interp.numarg(b, 1)); 160 | playNote(s.instrument, key, secs, s); 161 | interp.startTimer(secs); 162 | } else { 163 | interp.checkTimer(); 164 | } 165 | }; 166 | 167 | SoundPrims.prototype.primPlayDrum = function(b) { 168 | var s = interp.targetSprite(); 169 | if (s == null) return; 170 | if (interp.activeThread.firstTime) { 171 | var drum = Math.round(interp.numarg(b, 0)); 172 | var secs = beatsToSeconds(interp.numarg(b, 1)); 173 | playDrum(drum, secs, s); 174 | interp.startTimer(secs); 175 | } else { 176 | interp.checkTimer(); 177 | } 178 | }; 179 | 180 | SoundPrims.prototype.primPlayRest = function(b) { 181 | var s = interp.targetSprite(); 182 | if (s == null) return; 183 | if (interp.activeThread.firstTime) { 184 | var secs = beatsToSeconds(interp.numarg(b, 0)); 185 | interp.startTimer(secs); 186 | } else { 187 | interp.checkTimer(); 188 | } 189 | }; 190 | 191 | SoundPrims.prototype.primSetInstrument = function(b) { 192 | var s = interp.targetSprite(); 193 | if (s != null) s.instrument = interp.arg(b, 0); 194 | }; 195 | 196 | SoundPrims.prototype.primStopAllSounds = function(b) { 197 | stopAllSounds(); 198 | }; 199 | 200 | SoundPrims.prototype.primChangeVolume = function(b) { 201 | var s = interp.targetSprite(); 202 | if (s != null) s.volume += interp.numarg(b, 0); 203 | }; 204 | 205 | SoundPrims.prototype.primSetVolume = function(b) { 206 | var s = interp.targetSprite(); 207 | if (s != null) s.volume = interp.numarg(b, 0); 208 | }; 209 | 210 | SoundPrims.prototype.primVolume = function(b) { 211 | var s = interp.targetSprite(); 212 | return s != null ? s.volume : 0; 213 | }; 214 | -------------------------------------------------------------------------------- /js/primitives/VarListPrims.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | 'use strict'; 17 | 18 | var VarListPrims = function() {} 19 | 20 | VarListPrims.prototype.addPrimsTo = function(primTable) { 21 | // Variable primitives 22 | primTable['readVariable'] = this.primReadVar; 23 | primTable['setVar:to:'] = this.primSetVar; 24 | primTable['changeVar:by:'] = this.primChangeVar; 25 | primTable['hideVariable:'] = this.primHideVar; 26 | primTable['showVariable:'] = this.primShowVar; 27 | 28 | // List primitives 29 | primTable['contentsOfList:'] = this.primReadList; 30 | primTable['append:toList:'] = this.primListAppend; 31 | primTable['deleteLine:ofList:'] = this.primListDeleteLine; 32 | primTable['insert:at:ofList:'] = this.primListInsertAt; 33 | primTable['setLine:ofList:to:'] = this.primListSetLine; 34 | primTable['lineCountOfList:'] = this.primListLength; 35 | primTable['getLine:ofList:'] = this.primListGetLine; 36 | primTable['list:contains:'] = this.primListContains; 37 | primTable['hideList:'] = this.primHideList; 38 | primTable['showList:'] = this.primShowList; 39 | }; 40 | 41 | // Variable primitive implementations 42 | 43 | VarListPrims.prototype.primReadVar = function(b) { 44 | var s = interp.targetSprite(); 45 | if (s == null) return; 46 | var targetVar = interp.arg(b, 0); 47 | if (targetVar in s.variables) { 48 | return s.variables[targetVar]; 49 | } else if (targetVar in runtime.stage.variables) { 50 | return runtime.stage.variables[targetVar]; 51 | } 52 | }; 53 | 54 | VarListPrims.prototype.primSetVar = function(b) { 55 | var s = interp.targetSprite(); 56 | if (s == null) return; 57 | var targetVar = interp.arg(b, 0); 58 | if (targetVar in s.variables) { 59 | s.variables[targetVar] = interp.arg(b, 1); 60 | } else if (targetVar in runtime.stage.variables) { 61 | runtime.stage.variables[targetVar] = interp.arg(b, 1); 62 | } 63 | }; 64 | 65 | VarListPrims.prototype.primChangeVar = function(b) { 66 | var s = interp.targetSprite(); 67 | if (s == null) return; 68 | var targetVar = interp.arg(b, 0); 69 | if (targetVar in s.variables) { 70 | s.variables[targetVar] = parseFloat(s.variables[targetVar]) + interp.numarg(b, 1); 71 | } else if (targetVar in runtime.stage.variables) { 72 | runtime.stage.variables[targetVar] = parseFloat(runtime.stage.variables[targetVar]) + interp.numarg(b, 1); 73 | } 74 | }; 75 | 76 | VarListPrims.prototype.primHideVar = function(b) { 77 | var targetVar = interp.arg(b, 0), targetSprite = interp.targetSprite().objName; 78 | for (var r = 0; r < runtime.reporters.length; r++) { 79 | if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) { 80 | runtime.reporters[r].visible = false; 81 | return; 82 | } 83 | } 84 | }; 85 | 86 | VarListPrims.prototype.primShowVar = function(b) { 87 | var targetVar = interp.arg(b, 0), targetSprite = interp.targetSprite().objName; 88 | for (var r = 0; r < runtime.reporters.length; r++) { 89 | if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) { 90 | runtime.reporters[r].visible = true; 91 | return; 92 | } 93 | } 94 | }; 95 | 96 | // List primitive implementations 97 | 98 | // Take a list name and target sprite and return the JS list itself 99 | function findList(targetSprite, listName) { 100 | if (targetSprite == null) targetSprite = runtime.stage; 101 | if (listName in targetSprite.lists) { 102 | return targetSprite.lists[listName].contents; 103 | } else if (listName in runtime.stage.lists) { 104 | return runtime.stage.lists[listName].contents; 105 | } 106 | return null; 107 | } 108 | 109 | VarListPrims.prototype.primReadList = function(b) { 110 | var list = findList(interp.targetSprite(), interp.arg(b, 0)); 111 | if (list) { 112 | var allOne = list.map(function(val) { return val.length; }).reduce(function(old,val) { return old + val; }, 0) === list.length; 113 | return list.join(allOne ? '' : ' '); 114 | } 115 | }; 116 | 117 | VarListPrims.prototype.primListAppend = function(b) { 118 | var list = findList(interp.targetSprite(), interp.arg(b, 1)); 119 | if (list) list.push(interp.arg(b, 0)); 120 | }; 121 | 122 | VarListPrims.prototype.primListDeleteLine = function(b) { 123 | var list = findList(interp.targetSprite(), interp.arg(b, 1)); 124 | if (!list) return; 125 | var line = interp.arg(b, 0); 126 | if (line == 'all' || list.length == 0) { 127 | list.length = 0; 128 | } else if (line == 'last') { 129 | list.splice(list.length - 1, 1); 130 | } else if (parseInt(line, 10) - 1 in list) { 131 | list.splice(parseInt(line, 10) - 1, 1); 132 | } 133 | }; 134 | 135 | VarListPrims.prototype.primListInsertAt = function(b) { 136 | var list = findList(interp.targetSprite(), interp.arg(b, 2)); 137 | if (!list) return; 138 | var newItem = interp.arg(b, 0); 139 | 140 | var position = interp.arg(b, 1); 141 | if (position == 'last') { 142 | position = list.length; 143 | } else if (position == 'random') { 144 | position = Math.round(Math.random() * list.length); 145 | } else { 146 | position = parseInt(position, 10) - 1; 147 | } 148 | if (position > list.length) return; 149 | 150 | list.splice(position, 0, newItem); 151 | }; 152 | 153 | VarListPrims.prototype.primListSetLine = function(b) { 154 | var list = findList(interp.targetSprite(), interp.arg(b, 1)); 155 | if (!list) return; 156 | var newItem = interp.arg(b, 2); 157 | var position = interp.arg(b, 0); 158 | 159 | if (position == 'last') { 160 | position = list.length - 1; 161 | } else if (position == 'random') { 162 | position = Math.floor(Math.random() * list.length); 163 | } else { 164 | position = parseInt(position, 10) - 1; 165 | } 166 | 167 | if (position > list.length - 1) return; 168 | list[position] = newItem; 169 | }; 170 | 171 | VarListPrims.prototype.primListLength = function(b) { 172 | var list = findList(interp.targetSprite(), interp.arg(b, 0)); 173 | if (!list) return 0; 174 | return list.length; 175 | }; 176 | 177 | VarListPrims.prototype.primListGetLine = function(b) { 178 | var list = findList(interp.targetSprite(), interp.arg(b, 1)); 179 | if (!list) return 0; 180 | var line = interp.arg(b, 0); 181 | if (list.length == 0) return 0; 182 | if (line == 'random') line = Math.round(Math.random() * list.length); 183 | else if (line == 'last') line = list.length; 184 | else if (list.length < line) return 0; 185 | return list[line - 1]; 186 | }; 187 | 188 | VarListPrims.prototype.primListContains = function(b) { 189 | var list = findList(interp.targetSprite(), interp.arg(b, 0)); 190 | if (!list) return 0; 191 | var searchItem = interp.arg(b, 1); 192 | if (parseFloat(searchItem) == searchItem) searchItem = parseFloat(searchItem); 193 | return $.inArray(searchItem, list) > -1; 194 | }; 195 | 196 | VarListPrims.prototype.primHideList = function(b) { 197 | var targetList = interp.arg(b, 0), targetSprite = interp.targetSprite().objName; 198 | for (var r = 0; r < runtime.reporters.length; r++) { 199 | if (runtime.reporters[r] instanceof List && runtime.reporters[r].listName == targetList && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) { 200 | runtime.reporters[r].visible = false; 201 | return; 202 | } 203 | } 204 | }; 205 | 206 | VarListPrims.prototype.primShowList = function(b) { 207 | var targetList = interp.arg(b, 0), targetSprite = interp.targetSprite().objName; 208 | for (var r = 0; r < runtime.reporters.length; r++) { 209 | if (runtime.reporters[r] instanceof List && runtime.reporters[r].listName == targetList && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) { 210 | runtime.reporters[r].visible = true; 211 | return; 212 | } 213 | } 214 | }; 215 | -------------------------------------------------------------------------------- /js/sound/NotePlayer.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // NotePlayer.js 17 | // Tim Mickel, 2013 18 | // Based entirely on the AS version by John Maloney 19 | // 20 | // Subclass of SoundDecoder to play notes on a sampled instrument or drum. 21 | // 22 | // A sampled instrument outputs interpolated sound samples from an array of signed, 23 | // 16-bit integers with an original sampling rate of 22050 samples/sec. The pitch is 24 | // shifted by change the step size while iterating through this array. An instrument 25 | // may also be looped so that it can be sustained and it may have a volume envelope 26 | // to control the attack and decay of the note. 27 | 28 | var NotePlayer = function(wavFileData, originalPitch, loopStart, loopEnd, env) { 29 | this.originalPitch = originalPitch || null; 30 | this.index = 0; 31 | this.samplesRemaining = 0; // determines note duration 32 | 33 | // Looping 34 | this.isLooped = false; 35 | this.loopPoint = 0; // final sample in loop 36 | this.loopLength = 0; 37 | 38 | // Volume Envelope 39 | this.envelopeValue = 1; 40 | this.samplesSinceStart = 0; 41 | this.attackEnd = 0; 42 | this.attackRate = 0; 43 | this.holdEnd = 0; 44 | this.decayRate = 1; 45 | 46 | if (wavFileData == null) wavFileData = new ArrayBuffer(); 47 | 48 | var stepSize = 0.5; // default - no pitch shift 49 | var startOffset = 0; 50 | this.endOffset = wavFileData.byteLength / 2; // end of sample data 51 | var getSample = function() { return 0; } // called once at startup time 52 | this.soundData = new Uint8Array(wavFileData); 53 | 54 | if ((loopStart >= 0) && (loopStart < this.endOffset)) { 55 | this.isLooped = true; 56 | this.loopPoint = loopStart; 57 | if ((loopEnd > 0) && (loopEnd <= this.endOffset)) this.endOffset = loopEnd; 58 | this.loopLength = this.endOffset - this.loopPoint; 59 | 60 | // Compute the original pitch more exactly from the loop length: 61 | var oneCycle = 22050 / this.originalPitch; 62 | var cycles = Math.round(this.loopLength / oneCycle); 63 | this.originalPitch = 22050 / (this.loopLength / cycles); 64 | } 65 | if (env) { 66 | this.attackEnd = env[0] * 44.100; 67 | if (this.attackEnd > 0) this.attackRate = Math.pow(33000, 1 / this.attackEnd); 68 | this.holdEnd = this.attackEnd + env[1] * 44.100; 69 | var decayCount = env[2] * 44100; 70 | this.decayRate = decayCount == 0 ? 1 : Math.pow(33000, -1 / decayCount); 71 | } 72 | }; 73 | 74 | NotePlayer.prototype = Object.create(SoundDecoder.prototype); 75 | NotePlayer.prototype.constructor = NotePlayer; 76 | 77 | NotePlayer.prototype.setNoteAndDuration = function(midiKey, secs) { 78 | midiKey = Math.max(0, Math.min(midiKey, 127)); 79 | var pitch = 440 * Math.pow(2, (midiKey - 69) / 12); // midi key 69 is A (440 Hz) 80 | this.stepSize = pitch / (2 * this.originalPitch); // adjust for original sampling rate of 22050 81 | this.setDuration(secs); 82 | }; 83 | 84 | NotePlayer.prototype.setDuration = function(secs) { 85 | this.samplesSinceStart = 0; 86 | this.samplesRemaining = 44100 * secs; 87 | if (!this.isLooped) this.samplesRemaining = Math.min(this.samplesRemaining, this.endOffset / this.stepSize); 88 | this.envelopeValue = this.attackEnd > 0 ? 1 / 33000 : 1; 89 | }; 90 | 91 | NotePlayer.prototype.interpolatedSample = function() { 92 | if (this.samplesRemaining-- <= 0) { this.noteFinished(); return 0; } 93 | this.index += this.stepSize; 94 | while (this.index >= this.endOffset) { 95 | if (!this.isLooped) return 0; 96 | this.index -= this.loopLength; 97 | } 98 | var i = Math.floor(this.index); 99 | var frac = this.index - i; 100 | var curr = this.rawSample(i); 101 | var next = this.rawSample(i + 1); 102 | var sample = (curr + frac * (next - curr)) / 100000; // xxx 32000; attenuate... 103 | if (this.samplesRemaining < 1000) sample *= (this.samplesRemaining / 1000.0); // relaase phease 104 | this.updateEnvelope(); 105 | return this.envelopeValue * sample; 106 | }; 107 | 108 | NotePlayer.prototype.rawSample = function(sampleIndex) { 109 | if (sampleIndex >= this.endOffset) { 110 | if (!this.isLooped) return 0; 111 | sampleIndex = this.loopPoint; 112 | } 113 | var byteIndex = 2 * sampleIndex; 114 | var result = (this.soundData[byteIndex + 1] << 8) + this.soundData[byteIndex]; 115 | return result <= 32767 ? result : result - 65536; 116 | }; 117 | 118 | NotePlayer.prototype.updateEnvelope = function() { 119 | // Compute envelopeValue for the current sample. 120 | this.samplesSinceStart++; 121 | if (this.samplesSinceStart < this.attackEnd) { 122 | this.envelopeValue *= this.attackRate; 123 | } else if (this.samplesSinceStart == this.attackEnd) { 124 | this.envelopeValue = 1; 125 | } else if (this.samplesSinceStart > this.holdEnd) { 126 | if (this.decayRate < 1) this.envelopeValue *= this.decayRate; 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /js/sound/SoundBank.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // SoundBank.js 17 | // Tim Mickel, 2013 18 | // Based on the original AS by John Maloney - Scratch 1.4 compatibility removed 19 | // 20 | // A collection of instrument and drum resources to support the note and drum commands. 21 | 22 | var SoundBank = function() {}; 23 | 24 | // ----------------------------- 25 | // Scratch 2.0 Instrument Definitions 26 | //------------------------------ 27 | 28 | // Each instrument is an array of one or more key-span entries of the following form: 29 | // 30 | // top key of key span, sampleName, midiKey, loopStart, loopEnd, [attack, hold, decay] 31 | // 32 | // The loop points are -1 if the sound is unlooped (e.g. Marimba). 33 | // The three-element envelop array may be omitted if the instrument has no envelope. 34 | SoundBank.instruments = [ 35 | [ 36 | [38, 'AcousticPiano_As3', 58, 10266, 17053, [0, 100, 22]], 37 | [44, 'AcousticPiano_C4', 60, 13968, 18975, [0, 100, 20]], 38 | [51, 'AcousticPiano_G4', 67, 12200, 12370, [0, 80, 18]], 39 | [62, 'AcousticPiano_C6', 84, 13042, 13276, [0, 80, 16]], 40 | [70, 'AcousticPiano_F5', 77, 12425, 12965, [0, 40, 14]], 41 | [77, 'AcousticPiano_Ds6', 87, 12368, 12869, [0, 20, 10]], 42 | [85, 'AcousticPiano_Ds6', 87, 12368, 12869, [0, 0, 8]], 43 | [90, 'AcousticPiano_Ds6', 87, 12368, 12869, [0, 0, 6]], 44 | [96, 'AcousticPiano_D7', 98, 7454, 7606, [0, 0, 3]], 45 | [128, 'AcousticPiano_D7', 98, 7454, 7606, [0, 0, 2]] 46 | ], 47 | [ 48 | [48, 'ElectricPiano_C2', 36, 15338, 17360, [0, 80, 10]], 49 | [74, 'ElectricPiano_C4', 60, 11426, 12016, [0, 40, 8]], 50 | [128, 'ElectricPiano_C4', 60, 11426, 12016, [0, 0, 6]] 51 | ], 52 | [ 53 | [128, 'Organ_G2', 43, 1306, 3330] 54 | ], 55 | [ 56 | [40, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 15]], 57 | [56, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 13.5]], 58 | [60, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 12]], 59 | [67, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 8.5]], 60 | [72, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 7]], 61 | [83, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 5.5]], 62 | [128, 'AcousticGuitar_F3', 53, 36665, 36791, [0, 0, 4.5]] 63 | ], 64 | [ 65 | [40, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 15]], 66 | [56, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 13.5]], 67 | [60, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 12]], 68 | [67, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 8.5]], 69 | [72, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 7]], 70 | [83, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 5.5]], 71 | [128, 'ElectricGuitar_F3', 53, 34692, 34945, [0, 0, 4.5]] 72 | ], 73 | [ 74 | [34, 'ElectricBass_G1', 31, 41912, 42363, [0, 0, 17]], 75 | [48, 'ElectricBass_G1', 31, 41912, 42363, [0, 0, 14]], 76 | [64, 'ElectricBass_G1', 31, 41912, 42363, [0, 0, 12]], 77 | [128, 'ElectricBass_G1', 31, 41912, 42363, [0, 0, 10]] 78 | ], 79 | [ 80 | [38, 'Pizz_G2', 43, 8554, 8782, [0, 0, 5]], 81 | [45, 'Pizz_G2', 43, 8554, 8782, [0, 12, 4]], 82 | [56, 'Pizz_A3', 57, 11460, 11659, [0, 0, 4]], 83 | [64, 'Pizz_A3', 57, 11460, 11659, [0, 0, 3.2]], 84 | [72, 'Pizz_E4', 64, 17525, 17592, [0, 0, 2.8]], 85 | [80, 'Pizz_E4', 64, 17525, 17592, [0, 0, 2.2]], 86 | [128, 'Pizz_E4', 64, 17525, 17592, [0, 0, 1.5]] 87 | ], 88 | [ 89 | [41, 'Cello_C2', 36, 8548, 8885], 90 | [52, 'Cello_As2', 46, 7465, 7845], 91 | [62, 'Violin_D4', 62, 10608, 11360], 92 | [75, 'Violin_A4', 69, 3111, 3314, [70, 0, 0]], 93 | [128, 'Violin_E5', 76, 2383, 2484] 94 | ], 95 | [ 96 | [30, 'BassTrombone_A2_3', 45, 1357, 2360], 97 | [40, 'BassTrombone_A2_2', 45, 1893, 2896], 98 | [55, 'Trombone_B3', 59, 2646, 3897], 99 | [88, 'Trombone_B3', 59, 2646, 3897, [50, 0, 0]], 100 | [128, 'Trumpet_E5', 76, 2884, 3152] 101 | ], 102 | [ 103 | [128, 'Clarinet_C4', 60, 14540, 15468] 104 | ], 105 | [ 106 | [40, 'TenorSax_C3', 48, 8939, 10794], 107 | [50, 'TenorSax_C3', 48, 8939, 10794, [20, 0, 0]], 108 | [59, 'TenorSax_C3', 48, 8939, 10794, [40, 0, 0]], 109 | [67, 'AltoSax_A3', 57, 8546, 9049], 110 | [75, 'AltoSax_A3', 57, 8546, 9049, [20, 0, 0]], 111 | [80, 'AltoSax_A3', 57, 8546, 9049, [20, 0, 0]], 112 | [128, 'AltoSax_C6', 84, 1258, 1848] 113 | ], 114 | [ 115 | [61, 'Flute_B5_2', 83, 1859, 2259], 116 | [128, 'Flute_B5_1', 83, 2418, 2818] 117 | ], 118 | [ 119 | [128, 'WoodenFlute_C5', 72, 11426, 15724] 120 | ], 121 | [ 122 | [57, 'Bassoon_C3', 48, 2428, 4284], 123 | [67, 'Bassoon_C3', 48, 2428, 4284, [40, 0, 0]], 124 | [76, 'Bassoon_C3', 48, 2428, 4284, [80, 0, 0]], 125 | [84, 'EnglishHorn_F3', 53, 7538, 8930, [40, 0, 0]], 126 | [128, 'EnglishHorn_D4', 62, 4857, 5231] 127 | ], 128 | [ 129 | [39, 'Choir_F3', 53, 14007, 41281], 130 | [50, 'Choir_F3', 53, 14007, 41281, [40, 0, 0]], 131 | [61, 'Choir_F3', 53, 14007, 41281, [60, 0, 0]], 132 | [72, 'Choir_F4', 65, 16351, 46436], 133 | [128, 'Choir_F5', 77, 18440, 45391] 134 | ], 135 | [ 136 | [38, 'Vibraphone_C3', 48, 6202, 6370, [0, 100, 8]], 137 | [48, 'Vibraphone_C3', 48, 6202, 6370, [0, 100, 7.5]], 138 | [59, 'Vibraphone_C3', 48, 6202, 6370, [0, 60, 7]], 139 | [70, 'Vibraphone_C3', 48, 6202, 6370, [0, 40, 6]], 140 | [78, 'Vibraphone_C3', 48, 6202, 6370, [0, 20, 5]], 141 | [86, 'Vibraphone_C3', 48, 6202, 6370, [0, 0, 4]], 142 | [128, 'Vibraphone_C3', 48, 6202, 6370, [0, 0, 3]] 143 | ], 144 | [ 145 | [128, 'MusicBox_C4', 60, 14278, 14700, [0, 0, 2]] 146 | ], 147 | [ 148 | [128, 'SteelDrum_D5', 74.4, -1, -1, [0, 0, 2]] 149 | ], 150 | [ 151 | [128, 'Marimba_C4', 60, -1, -1] 152 | ], 153 | [ 154 | [80, 'SynthLead_C4', 60, 135, 1400], 155 | [128, 'SynthLead_C6', 84, 124, 356] 156 | ], 157 | [ 158 | [38, 'SynthPad_A3', 57, 4212, 88017, [50, 0, 0]], 159 | [50, 'SynthPad_A3', 57, 4212, 88017, [80, 0, 0]], 160 | [62, 'SynthPad_A3', 57, 4212, 88017, [110, 0, 0]], 161 | [74, 'SynthPad_A3', 57, 4212, 88017, [150, 0, 0]], 162 | [86, 'SynthPad_A3', 57, 4212, 88017, [200, 0, 0]], 163 | [128, 'SynthPad_C6', 84, 2575, 9202] 164 | ] 165 | ]; 166 | 167 | // ----------------------------- 168 | // Scratch 2.0 Drum Definitions 169 | //------------------------------ 170 | 171 | // Each drum entry is an array of of the form: 172 | // 173 | // sampleName, pitchAdjust, [loopStart, loopEnd, decay] 174 | // 175 | // pitchAdjust (pitch shift in semitones) adjusts the original pitch. 176 | // The loop points and decay parameter may be omitted if the drum is unlooped. 177 | // (A few drums are looped to create several different pitched drums from one sample.) 178 | SoundBank.drums = [ 179 | ['SnareDrum', 0], 180 | ['Tom', 0], 181 | ['SideStick', 0], 182 | ['Crash', -7], 183 | ['HiHatOpen', -8], 184 | ['HiHatClosed', 0], 185 | ['Tambourine', 0], 186 | ['Clap', 0], 187 | ['Claves', 0], 188 | ['WoodBlock', -4], 189 | ['Cowbell', 0], 190 | ['Triangle', -6, 16843, 17255, 2], 191 | ['Bongo', 2], 192 | ['Conga', -7, 4247, 4499, 2], // jhm decay 193 | ['Cabasa', 0], 194 | ['GuiroLong', 0], 195 | ['Vibraslap', -6], 196 | ['Cuica', -5], 197 | ]; 198 | 199 | SoundBank.getNotePlayer = function(instNum, midiKey) { 200 | // Return a NotePlayer for the given Scratch 2.0 instrument number (1..21) 201 | // and MIDI key (0..127). If the instrument is out of range, use 1. 202 | var r = SoundBank.getNoteRecord(instNum - 1, midiKey); 203 | var env = r.length > 5 ? r[5] : null; 204 | return new NotePlayer(Instr.samples[r[1]], SoundBank.pitchForKey(r[2]), r[3], r[4], env); 205 | }; 206 | 207 | SoundBank.getNoteRecord = function(instNum, midiKey) { 208 | // Get a note record for the given instrument number. 209 | if (instNum < 0 || instNum >= SoundBank.instruments.length) instNum = 0; 210 | var keyRanges = SoundBank.instruments[instNum]; 211 | for (var r = 0; r < keyRanges.length; r++) { 212 | var topOfKeyRange = keyRanges[r][0]; 213 | if (midiKey <= topOfKeyRange) return keyRanges[r]; 214 | } 215 | return keyRanges[keyRanges.length - 1]; // return the note record for the top key range. 216 | }; 217 | 218 | SoundBank.pitchForKey = function(midiKey) { 219 | return 440 * Math.pow(2, (midiKey - 69) / 12); // midi key 69 is A=440 Hz 220 | }; 221 | 222 | SoundBank.getDrumPlayer = function(drumNum, secs) { 223 | // Return a NotePlayer for the given drum number. 224 | var entry = SoundBank.drums[drumNum - 1]; 225 | if (entry == null) entry = SoundBank.drums[2]; 226 | var loopStart = -1, loopEnd = -1, env = null; 227 | if (entry.length >= 4) { 228 | loopStart = entry[2]; 229 | loopEnd = entry[3]; 230 | } 231 | if (entry.length >= 5) env = [0, 0, entry[4]]; 232 | var player = new NotePlayer(Instr.samples[entry[0]], SoundBank.pitchForKey(60), loopStart, loopEnd, env); 233 | player.setNoteAndDuration(60 + entry[1], 0); 234 | return player; 235 | }; 236 | -------------------------------------------------------------------------------- /js/sound/SoundDecoder.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // SoundDecoder.js 17 | // Decode WAV Files (8-bit, 16-bit, and ADPCM) for playing by Sprites. 18 | // For best performance, this should be run only once per WAV and 19 | // the decoded buffer should be cached. 20 | 21 | // Based almost entirely on John Maloney's AS implementation. 22 | 23 | var SoundDecoder = function(wavFileData) { 24 | this.scratchSound = null; 25 | 26 | this.soundData = null; 27 | this.startOffset = 0; 28 | this.endOffset = 0; 29 | this.stepSize = 0; 30 | this.adpcmBlockSize = 0; 31 | this.bytePosition = 0; 32 | this.soundChannel = null; 33 | this.lastBufferTime = 0; 34 | 35 | this.getSample = null; 36 | this.fraction = 0.0; 37 | this.thisSample = 0; 38 | 39 | // decoder state 40 | this.sample = 0; 41 | this.index = 0; 42 | this.lastByte = -1; // -1 indicates that there is no saved lastByte 43 | 44 | this.nextSample = 0; 45 | 46 | this.info = null; 47 | 48 | getSample = this.getSample16Uncompressed; 49 | if (wavFileData != null) { 50 | var info = WAVFile.decode(wavFileData); 51 | this.info = info; 52 | this.startOffset = info.sampleDataStart; 53 | this.endOffset = this.startOffset + info.sampleDataSize; 54 | this.soundData = new Uint8Array(wavFileData.slice(this.startOffset, this.endOffset)); 55 | this.stepSize = info.samplesPerSecond / 44100.0; 56 | if (info.encoding == 17) { 57 | this.adpcmBlockSize = info.adpcmBlockSize; 58 | this.getSample = this.getSampleADPCM; 59 | } else { 60 | if (info.bitsPerSample == 8) this.getSample = this.getSample8Uncompressed; 61 | if (info.bitsPerSample == 16) this.getSample = this.getSample16Uncompressed; 62 | } 63 | } 64 | }; 65 | 66 | SoundDecoder.prototype.noteFinished = function() { 67 | // Called by subclasses to force ending condition to be true in writeSampleData() 68 | this.bytePosition = this.endOffset; 69 | }; 70 | 71 | // Used for Notes and Drums - Web Audio API ScriptProcessorNodes use this 72 | // as a callback function to fill the buffers with sample data. 73 | SoundDecoder.prototype.writeSampleData = function(evt) { 74 | var i = 0; 75 | var output = evt.outputBuffer.getChannelData(0); 76 | //this.updateVolume(); 77 | for (i = 0; i < output.length; i++) { 78 | var n = this.interpolatedSample(); 79 | output[i] = n; 80 | } 81 | }; 82 | 83 | // For pre-caching the samples of WAV sounds 84 | // Return a full list of samples generated by the decoder. 85 | SoundDecoder.prototype.getAllSamples = function() { 86 | var samples = [], smp = 0; 87 | smp = this.interpolatedSample(); 88 | while (smp != null) { 89 | samples.push(smp); 90 | smp = this.interpolatedSample(); 91 | } 92 | return samples; 93 | }; 94 | 95 | // Provide the next sample for the buffer 96 | SoundDecoder.prototype.interpolatedSample = function() { 97 | this.fraction += this.stepSize; 98 | while (this.fraction >= 1.0) { 99 | this.thisSample = this.nextSample; 100 | this.nextSample = this.getSample(); 101 | this.fraction -= 1.0; 102 | } 103 | if (this.nextSample == null) { return null; } 104 | var out = this.fraction == 0 ? this.thisSample : this.thisSample + this.fraction * (this.nextSample - this.thisSample); 105 | return out / 32768.0; 106 | }; 107 | 108 | // 16-bit samples, big-endian 109 | SoundDecoder.prototype.getSample16Uncompressed = function() { 110 | var result = 0; 111 | if (this.bytePosition <= (this.info.sampleDataSize - 2)) { 112 | result = (this.soundData[this.bytePosition + 1] << 8) + this.soundData[this.bytePosition]; 113 | if (result > 32767) result -= 65536; 114 | this.bytePosition += 2; 115 | } else { 116 | this.bytePosition = this.endOffset; 117 | result = null; 118 | } 119 | return result; 120 | }; 121 | 122 | // 8-bit samples, uncompressed 123 | SoundDecoder.prototype.getSample8Uncompressed = function() { 124 | if (this.bytePosition >= this.info.sampleDataSize) return null; 125 | return (this.soundData[this.bytePosition++] - 128) << 8; 126 | }; 127 | 128 | /*SoundDecoder.prototype.updateVolume = function() { 129 | if (this.client == null) { 130 | this.volume = 1.0; 131 | return; 132 | } 133 | if (this.client.volume == this.lastClientVolume) return; // optimization 134 | this.volume = Math.max(0.0, Math.min(this.client.volume / 100.0, 1.0)); 135 | this.lastClientVolume = this.client.volume; 136 | }*/ 137 | 138 | // Decoder for IMA ADPCM compressed sounds 139 | SoundDecoder.indexTable = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]; 140 | 141 | SoundDecoder.stepTable = [ 142 | 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 143 | 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 144 | 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 145 | 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 146 | 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 147 | 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 148 | ]; 149 | 150 | SoundDecoder.prototype.getSampleADPCM = function() { 151 | // Decompress sample data using the IMA ADPCM algorithm. 152 | // Note: Handles only one channel, 4-bits/sample. 153 | var step = 0, code = 0, delta = 0; 154 | 155 | if (this.bytePosition % this.adpcmBlockSize == 0 && this.lastByte < 0) { // read block header 156 | if (this.bytePosition > this.info.sampleDataSize - 4) return null; 157 | this.sample = (this.soundData[this.bytePosition + 1] << 8) + this.soundData[this.bytePosition]; 158 | if (this.sample > 32767) this.sample -= 65536; 159 | this.index = this.soundData[this.bytePosition + 2]; 160 | this.bytePosition += 4; 161 | if (this.index > 88) this.index = 88; 162 | this.lastByte = -1; 163 | return this.sample; 164 | } else { 165 | // read 4-bit code and compute delta 166 | if (this.lastByte < 0) { 167 | if (this.bytePosition >= this.info.sampleDataSize) return null; 168 | this.lastByte = this.soundData[this.bytePosition++]; 169 | code = this.lastByte & 0xF; 170 | } else { 171 | code = (this.lastByte >> 4) & 0xF; 172 | this.lastByte = -1; 173 | } 174 | step = SoundDecoder.stepTable[this.index]; 175 | delta = 0; 176 | if (code & 4) delta += step; 177 | if (code & 2) delta += step >> 1; 178 | if (code & 1) delta += step >> 2; 179 | delta += step >> 3; 180 | // compute next index 181 | this.index += SoundDecoder.indexTable[code]; 182 | if (this.index > 88) this.index = 88; 183 | if (this.index < 0) this.index = 0; 184 | // compute and output sample 185 | this.sample += code & 8 ? -delta : delta; 186 | if (this.sample > 32767) this.sample = 32767; 187 | if (this.sample < -32768) this.sample = -32768; 188 | return this.sample; 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /js/sound/WAVFile.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // WAVFile.js 17 | // Utility class for reading and decoding WAV file metadata 18 | // Based directly on John Maloney's AS version for the Scratch Flash Player 19 | 20 | var WAVFile = function() {}; 21 | 22 | WAVFile.decode = function(waveData) { 23 | // Decode the given WAV file data and return an Object with the format and sample data. 24 | var result = {}; 25 | 26 | var data = new OffsetBuffer(waveData); 27 | 28 | // read WAVE File Header 29 | if (data.readString(4) != 'RIFF') { console.log("WAVFile: bad file header"); return; } 30 | var totalSize = data.readInt(); 31 | if (data.getLength() != (totalSize + 8)) console.log("WAVFile: bad RIFF size; ignoring"); 32 | if (data.readString(4) != 'WAVE') { console.log("WAVFile: not a WAVE file"); return; } 33 | 34 | // read format chunk 35 | var formatChunk = WAVFile.extractChunk('fmt ', data); 36 | if (formatChunk.getLength() < 16) { console.log("WAVFile: format chunk is too small"); return; } 37 | 38 | var encoding = formatChunk.readShort(); 39 | result.encoding = encoding; 40 | result.channels = formatChunk.readShort(); 41 | result.samplesPerSecond = formatChunk.readInt(); 42 | result.bytesPerSecond = formatChunk.readInt(); 43 | result.blockAlignment = formatChunk.readShort(); 44 | result.bitsPerSample = formatChunk.readShort(); 45 | 46 | // get size of data chunk 47 | var sampleDataStartAndSize = WAVFile.dataChunkStartAndSize(data); 48 | result.sampleDataStart = sampleDataStartAndSize[0]; 49 | result.sampleDataSize = sampleDataStartAndSize[1]; 50 | 51 | // handle various encodings 52 | if (encoding == 1) { 53 | if (!((result.bitsPerSample == 8) || (result.bitsPerSample == 16))) { 54 | console.log("WAVFile: can only handle 8-bit or 16-bit uncompressed PCM data"); 55 | return; 56 | } 57 | result.sampleCount = result.sampleDataSize / 2; 58 | } else if (encoding == 17) { 59 | if (formatChunk.length < 20) { console.log("WAVFile: adpcm format chunk is too small"); return; } 60 | if (result.channels != 1) { console.log("WAVFile: adpcm supports only one channel (monophonic)"); return; } 61 | formatChunk.offset += 2; // skip extra header byte count 62 | var samplesPerBlock = formatChunk.readShort(); 63 | result.adpcmBlockSize = ((samplesPerBlock - 1) / 2) + 4; // block size in bytes 64 | var factChunk = WAVFile.extractChunk('fact', data); 65 | if ((factChunk != null) && (factChunk.getLength() == 4)) { 66 | result.sampleCount = factChunk.readInt(); 67 | } else { 68 | // this should never happen, since there should always be a 'fact' chunk 69 | // slight over-estimate (doesn't take ADPCM headers into account) 70 | result.sampleCount = 2 * result.sampleDataSize; 71 | } 72 | } else { 73 | console.log("WAVFile: unknown encoding " + encoding); 74 | return; 75 | } 76 | return result; 77 | }; 78 | 79 | WAVFile.extractChunk = function(desiredType, data) { 80 | // Return the contents of the first chunk of the given type or an empty OffsetBuffer if it is not found. 81 | data.offset = 12; 82 | while (data.bytesAvailable() > 8) { 83 | var chunkType = data.readString(4); 84 | var chunkSize = data.readUint(); 85 | if (chunkType == desiredType) { 86 | if (chunkSize > data.bytesAvailable()) return null; 87 | var result = new OffsetBuffer(data.readBytes(chunkSize)); 88 | return result; 89 | } else { 90 | data.offset += chunkSize; 91 | } 92 | } 93 | return new OffsetBuffer(new ArrayBuffer()); 94 | }; 95 | 96 | WAVFile.dataChunkStartAndSize = function(data) { 97 | // Return an array with the starting offset and size of the first chunk of the given type. 98 | data.offset = 12; 99 | while (data.bytesAvailable() >= 8) { 100 | var chunkType = data.readString(4); 101 | var chunkSize = data.readUint(); 102 | if (chunkType == 'data') { 103 | if (chunkSize > data.bytesAvailable()) return [0, 0]; // bad wave file 104 | return [data.offset, chunkSize]; 105 | } else { 106 | data.offset += chunkSize; 107 | } 108 | } 109 | return [0, 0]; // chunk not found; bad wave file 110 | }; 111 | -------------------------------------------------------------------------------- /js/util/Color.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Scratch HTML5 Player 17 | // Color.js 18 | // Based on the original by John Maloney 19 | 20 | Color = function() {}; 21 | 22 | Color.fromHSV = function(h, s, v) { 23 | var r, g, b; 24 | h = h % 360; 25 | if (h < 0) h += 360; 26 | s = Math.max(0, Math.min(s, 1)); 27 | v = Math.max(0, Math.min(v, 1)); 28 | 29 | var i = Math.floor(h / 60); 30 | var f = (h / 60) - i; 31 | var p = v * (1 - s); 32 | var q = v * (1 - s * f); 33 | var t = v * (1 - s * (1 - f)); 34 | if (i == 0) { r = v; g = t; b = p; } 35 | else if (i == 1) { r = q; g = v; b = p; } 36 | else if (i == 2) { r = p; g = v; b = t; } 37 | else if (i == 3) { r = p; g = q; b = v; } 38 | else if (i == 4) { r = t; g = p; b = v; } 39 | else if (i == 5) { r = v; g = p; b = q; } 40 | r = Math.floor(r * 255); 41 | g = Math.floor(g * 255); 42 | b = Math.floor(b * 255); 43 | return (r << 16) | (g << 8) | b; 44 | }; 45 | 46 | Color.rgb2hsv = function(rgb) { 47 | var h, s, v, x, f, i; 48 | var r = ((rgb >> 16) & 255) / 255; 49 | var g = ((rgb >> 8) & 255) / 255; 50 | var b = (rgb & 255) / 255; 51 | x = Math.min(Math.min(r, g), b); 52 | v = Math.max(Math.max(r, g), b); 53 | if (x == v) return [0, 0, v]; // gray; hue arbitrarily reported as zero 54 | f = r == x ? g - b : g == x ? b - r : r - g; 55 | i = r == x ? 3 : g == x ? 5 : 1; 56 | h = ((i - f / (v - x)) * 60) % 360; 57 | s = (v - x) / v; 58 | return [h, s, v]; 59 | }; 60 | 61 | Color.scaleBrightness = function(rgb, scale) { 62 | var hsv = Color.rgb2hsv(rgb); 63 | scale = Math.max(0, Math.min(scale, 1)); 64 | return Color.fromHSV(hsv[0], hsv[1], scale * hsv[2]); 65 | }; 66 | 67 | Color.mixRGB = function(rgb1, rgb2, fraction) { 68 | // Mix rgb1 with rgb2. 0 gives all rgb1, 1 gives rbg2, .5 mixes them 50/50. 69 | if (fraction <= 0) return rgb1; 70 | if (fraction >= 1) return rgb2; 71 | var r1 = (rgb1 >> 16) & 255; 72 | var g1 = (rgb1 >> 8) & 255; 73 | var b1 = rgb1 & 255 74 | var r2 = (rgb2 >> 16) & 255; 75 | var g2 = (rgb2 >> 8) & 255; 76 | var b2 = rgb2 & 255 77 | var r = ((fraction * r2) + ((1.0 - fraction) * r1)) & 255; 78 | var g = ((fraction * g2) + ((1.0 - fraction) * g1)) & 255; 79 | var b = ((fraction * b2) + ((1.0 - fraction) * b1)) & 255; 80 | return (r << 16) | (g << 8) | b; 81 | }; 82 | 83 | Color.random = function() { 84 | // return a random color 85 | var h = 360 * Math.random(); 86 | var s = 0.7 + (0.3 * Math.random()); 87 | var v = 0.6 + (0.4 * Math.random()); 88 | return Color.fromHSV(h, s, v); 89 | }; 90 | -------------------------------------------------------------------------------- /js/util/OffsetBuffer.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Provides the equivalent functionality of an AS ByteArray 17 | // using JavaScript ArrayBuffers and viewers 18 | 19 | var OffsetBuffer = function(data) { 20 | this.offset = 0; 21 | this.ab = data; 22 | }; 23 | 24 | // Read various datatypes from the ArrayBuffer, seeking the offset. 25 | OffsetBuffer.prototype.readString = function(length) { 26 | var str = this.ab2str(this.ab.slice(this.offset, this.offset + length)); 27 | this.offset += length; 28 | return str; 29 | }; 30 | 31 | OffsetBuffer.prototype.readInt = function() { 32 | var num = this.ab2int(this.ab.slice(this.offset, this.offset + 4)); 33 | this.offset += 4; 34 | return num; 35 | }; 36 | 37 | OffsetBuffer.prototype.readUint = function() { 38 | var num = this.ab2uint(this.ab.slice(this.offset, this.offset + 4)); 39 | this.offset += 4; 40 | return num; 41 | }; 42 | 43 | OffsetBuffer.prototype.readShort = function() { 44 | var num = this.ab2short(this.ab.slice(this.offset, this.offset + 2)); 45 | this.offset += 2; 46 | return num; 47 | }; 48 | 49 | OffsetBuffer.prototype.readBytes = function(length) { 50 | var bytes = this.ab.slice(this.offset, this.offset + length); 51 | this.offset += length; 52 | return bytes; 53 | }; 54 | 55 | // Length of the internal buffer 56 | OffsetBuffer.prototype.getLength = function() { 57 | return this.ab.byteLength; 58 | }; 59 | 60 | // Number of bytes remaining from the current offset 61 | OffsetBuffer.prototype.bytesAvailable = function() { 62 | return this.getLength() - this.offset; 63 | }; 64 | 65 | // ArrayBuffer -> JS type conversion methods 66 | OffsetBuffer.prototype.ab2str = function(buf) { 67 | return String.fromCharCode.apply(null, new Uint8Array(buf)); 68 | }; 69 | 70 | // These create Javascript Numbers 71 | OffsetBuffer.prototype.ab2int = function(buf) { 72 | return new Int32Array(buf)[0]; 73 | }; 74 | 75 | OffsetBuffer.prototype.ab2uint = function(buf) { 76 | return new Uint32Array(buf)[0]; 77 | }; 78 | 79 | OffsetBuffer.prototype.ab2short = function(buf) { 80 | return new Int16Array(buf)[0]; 81 | }; 82 | -------------------------------------------------------------------------------- /js/util/Rectangle.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | var Point = function(x, y) { 17 | this.x = x; 18 | this.y = y; 19 | }; 20 | 21 | var Rectangle = function(x, y, width, height) { 22 | this.x = x; 23 | this.y = y; 24 | this.width = width; 25 | this.height = height; 26 | this.left = x; 27 | this.right = x + width; 28 | this.top = y; 29 | this.bottom = y + height; 30 | }; 31 | 32 | Rectangle.prototype.intersects = function(other) { 33 | return !(this.left > other.right || this.right < other.left || this.top > other.bottom || this.bottom < other.top); 34 | }; 35 | -------------------------------------------------------------------------------- /js/util/Timer.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | /* 17 | * Timer for the interpeter and performance testing 18 | * Tim Mickel, July 2011 19 | */ 20 | var Timer = function() { 21 | var trials = []; 22 | var last_trial = 0; 23 | var start_time = 0; 24 | }; 25 | 26 | Timer.prototype.time = function() { 27 | return Date.now(); 28 | }; 29 | 30 | Timer.prototype.start = function() { 31 | start_time = this.time(); 32 | }; 33 | 34 | Timer.prototype.stop = function() { 35 | end = this.time(); 36 | last_trial = end - start_time; 37 | trials.push(last_trial); 38 | }; 39 | 40 | Timer.prototype.count = function() { 41 | return trials.length; 42 | }; 43 | 44 | Timer.prototype.average = function() { 45 | sum = 0; 46 | for (i = 0; i < this.count(); i++) { 47 | sum += trials[i]; 48 | } 49 | return sum / this.count(); 50 | }; 51 | 52 | Timer.prototype.print = function(element) { 53 | text = "Trial: " + last_trial + "ms" + 54 | "
\nTrials: " + this.count() + ", Avg: " + this.average() + "ms"; 55 | if (element) { 56 | $(element).html(text); 57 | } else { 58 | console.log(text); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | JSCS_PATH = ./node_modules/.bin/jscs 2 | KARMA_PATH = ./node_modules/.bin/karma 3 | KARMA_CONFIG = ./test/fixtures/karma.conf.js 4 | 5 | # Performs code governance (lint + style) test 6 | lint: 7 | @$(JSCS_PATH) ./js/* 8 | @$(JSCS_PATH) ./test/unit/* 9 | 10 | # Performs unit tests 11 | unit: 12 | @$(KARMA_PATH) start $(KARMA_CONFIG) $* 13 | 14 | # Run all test targets 15 | test: 16 | @make lint 17 | @make unit 18 | 19 | # Ignore directory structure 20 | .PHONY: lint unit test -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scratch-html5", 3 | "description": "HTML 5 based Scratch project player", 4 | "repository": "https://github.com/LLK/scratch-html5", 5 | "scripts": { 6 | "postinstall": "curl http://code.jquery.com/jquery-1.11.0.min.js > ./test/lib/jquery-1.11.0.min.js", 7 | "test": "make test" 8 | }, 9 | "engines": { 10 | "node": ">=0.10" 11 | }, 12 | "dependencies": {}, 13 | "devDependencies": { 14 | "jasmine-jquery": "~1.3.3", 15 | "jscs": "~1.3.0", 16 | "karma": "~0.10", 17 | "karma-html2js-preprocessor": "~0.1.0", 18 | "underscore": "~1.6.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /player.css: -------------------------------------------------------------------------------- 1 | /* Wrapper wraps the entire player, and the text below and above. Ideally, 2 | all CSS selectors would be descendants of #wrapper so that the player 3 | is embeddable. 4 | */ 5 | #player-container { 6 | position: relative; 7 | width: 482px; 8 | padding: 48px; 9 | margin: 0 auto; 10 | font-family: sans-serif; 11 | /* Before, we accomplished this with e.preventDefault 12 | * on the context div. But, we'd like to use those click events 13 | * for some things like reporter sliders. 14 | */ 15 | -webkit-touch-callout: none; 16 | -webkit-user-select: none; 17 | -khtml-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | } 22 | 23 | /* Control bar above the stage */ 24 | #player-header { 25 | width: 480px; 26 | height: 38px; 27 | position: relative; 28 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e6e6e6)); 29 | background: -webkit-linear-gradient(#fff, #e6e6e6); 30 | background: -moz-linear-gradient(#fff, #e6e6e6); 31 | background: -ms-linear-gradient(#fff, #e6e6e6); 32 | background: -o-linear-gradient(#fff, #e6e6e6); 33 | background: linear-gradient(#fff, #e6e6e6); 34 | border: 1px solid #d1d1d1; 35 | border-bottom: 0; 36 | border-radius: 7px 7px 0 0; 37 | } 38 | 39 | /* Button styling */ 40 | #player-header button { 41 | border: 0; 42 | background: center center no-repeat; 43 | width: 33px; 44 | height: 38px; 45 | float: right; 46 | -moz-box-sizing: border-box; 47 | box-sizing: border-box; 48 | } 49 | #player-header-preload { 50 | width: 1px; 51 | height: 1px; 52 | position: absolute; 53 | top: -1px; 54 | left: -1px; 55 | background: url(img/fullScreenOn.png), url(img/greenFlagOn.png), url(img/stopOn.png); 56 | } 57 | button#toggle-fullscreen { 58 | background-image: url(img/fullScreenOff.png); 59 | background-position: 10px 8px; 60 | width: 40px; 61 | position: absolute; 62 | top: 0; 63 | left: 0; 64 | } 65 | button#trigger-green-flag { 66 | background-image: url(img/greenFlagOff.png); 67 | } 68 | button#trigger-green-flag:hover { 69 | background-image: url(img/greenFlagOn.png); 70 | } 71 | button#trigger-stop { 72 | background-image: url(img/stopOff.png); 73 | } 74 | button#trigger-stop:hover { 75 | background-image: url(img/stopOn.png); 76 | } 77 | 78 | /* Version number */ 79 | #player-header-version { 80 | position: absolute; 81 | top: 28px; 82 | left: 0; 83 | width: 45px; 84 | text-align: center; 85 | color: rgba(0, 0, 0, .4); 86 | font: 9px sans-serif; 87 | } 88 | 89 | /* Wrapper for the Stage */ 90 | #player-content { 91 | position: relative; 92 | border: 1px solid #d1d1d1; 93 | border-top: 0; 94 | width: 480px; 95 | height: 360px; 96 | } 97 | 98 | /* The Stage */ 99 | #container { 100 | position: absolute; 101 | top: 0; 102 | left: 0; 103 | width: 480px; 104 | height: 360px; 105 | cursor: default; 106 | overflow: hidden; 107 | } 108 | 109 | /* Pane over the Stage with the green flag on startup */ 110 | #overlay { 111 | position: absolute; 112 | top: 0; 113 | left: 0; 114 | width: 480px; 115 | height: 360px; 116 | z-index: 10000; 117 | background: url(img/playerStartFlag.png) rgba(0, 0, 0, .26) center center no-repeat; 118 | } 119 | 120 | /* Preloader */ 121 | #preloader { 122 | position: absolute; 123 | z-index: 10001; 124 | top: 50%; 125 | left: 50%; 126 | margin-left: -156px; 127 | margin-top: -61px; 128 | width: 309px; 129 | height: 119px; 130 | border: 1px solid rgb(208, 209, 210); 131 | background: #fff; 132 | border-radius: 12px; 133 | -webkit-box-shadow: 4px 4px 6px rgba(0, 0, 0, .5); 134 | box-shadow: 4px 4px 6px rgba(0, 0, 0, .5); 135 | cursor: default; 136 | } 137 | #preloader-progress { 138 | margin: 24px 30px 14px 29px; 139 | background: rgb(185, 187, 189); 140 | border-radius: 5px; 141 | height: 22px; 142 | } 143 | #preloader-progress-bar { 144 | height: 22px; 145 | background: rgb(0, 161, 216); 146 | border-radius: 5px; 147 | width: 0; 148 | } 149 | #preloader-caption, 150 | #preloader-details { 151 | margin: 14px 0 0 8px; 152 | text-align: center; 153 | font-size: 18px; 154 | color: rgba(0, 0, 0, .65); 155 | } 156 | #preloader-details { 157 | margin: 6px 0 0 4px; 158 | font-size: 12px; 159 | } 160 | 161 | /* iPad arrow key frame */ 162 | .arrow { 163 | position: absolute; 164 | top: 0; 165 | left: 0; 166 | width: 578px; 167 | height: 400px; 168 | text-align: center; 169 | font: 24px/48px sans-serif; 170 | color: rgba(0, 0, 0, .2); 171 | cursor: default; 172 | } 173 | .arrow.vertical { 174 | height: 48px; 175 | } 176 | .arrow.horizontal { 177 | width: 48px; 178 | top: 48px; 179 | line-height: 400px; 180 | } 181 | #right { 182 | left: 530px; 183 | } 184 | #down { 185 | top: 448px; 186 | } 187 | #up::before { 188 | content: '\25b2'; 189 | } 190 | #down::before { 191 | content: '\25bc'; 192 | } 193 | #left::before { 194 | content: '\25c0'; 195 | } 196 | #right::before { 197 | content: '\25b6'; 198 | } 199 | 200 | /* Panel for project ID input */ 201 | #project-picker { 202 | margin: 16px 0; 203 | text-align: center; 204 | font-size: 16px; 205 | line-height: 18px; 206 | } 207 | #project-id { 208 | border: 1px solid #aaa; 209 | -webkit-border-radius: 0; 210 | padding: 6px; 211 | -webkit-box-shadow: inset 3px 3px 3px -3px rgba(0, 0, 0, .3); 212 | box-shadow: inset 3px 3px 3px -3px rgba(0, 0, 0, .3); 213 | background: 0; 214 | margin: 0; 215 | font: inherit; 216 | width: 6em; 217 | position: relative; 218 | } 219 | #project-id.error { 220 | background: #fee; 221 | -webkit-box-shadow: inset 3px 3px 3px -3px rgba(100, 0, 0, .3); 222 | box-shadow: inset 3px 3px 3px -3px rgba(100, 0, 0, .3); 223 | } 224 | #project-id:focus { 225 | z-index: 1; 226 | } 227 | #address-hint { 228 | padding-left: 6px; 229 | vertical-align: middle; 230 | } 231 | #go-project { 232 | padding: 6px; 233 | margin: 0; 234 | border: 1px solid #aaa; 235 | border-left: 0; 236 | font: inherit; 237 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ddd)); 238 | background: -webkit-linear-gradient(#fff, #ddd); 239 | background: -moz-linear-gradient(#fff, #ddd); 240 | background: -ms-linear-gradient(#fff, #ddd); 241 | background: -o-linear-gradient(#fff, #ddd); 242 | background: linear-gradient(#fff, #ddd); 243 | -webkit-box-shadow: inset 0 -1px rgba(255, 255, 255, .2); 244 | box-shadow: inset 0 -1px rgba(255, 255, 255, .2); 245 | cursor: pointer; 246 | position: relative; 247 | } 248 | #go-project:active { 249 | background: -webkit-gradient(linear, left top, left bottom, from(#ddd), to(#eee)); 250 | background: -webkit-linear-gradient(#ddd, #eee); 251 | background: -moz-linear-gradient(#ddd, #eee); 252 | background: -ms-linear-gradient(#ddd, #eee); 253 | background: -o-linear-gradient(#ddd, #eee); 254 | background: linear-gradient(#ddd, #eee); 255 | -webkit-box-shadow: inset 0 1px rgba(255, 255, 255, .3), inset 0 2px 5px rgba(0, 0, 0, .1); 256 | box-shadow: inset 0 1px rgba(255, 255, 255, .3), inset 0 2px 5px rgba(0, 0, 0, .1); 257 | } 258 | 259 | #project-id, 260 | #go-project { 261 | -moz-box-sizing: content-box; 262 | box-sizing: content-box; 263 | height: 18px; 264 | vertical-align: middle; 265 | } 266 | #go-project::-moz-focus-inner { 267 | border: 0; 268 | padding: 0; 269 | } 270 | 271 | 272 | /* Reporter styles */ 273 | .reporter-normal { 274 | display: inline-block; 275 | padding: 2px 7px 3px 5px; 276 | background-color: rgb(193, 196, 199); 277 | border: 1px solid rgb(148, 145, 145); 278 | border-radius: 6px; 279 | font: bold 11px/15px sans-serif; 280 | color: #000; 281 | position: absolute; 282 | } 283 | .reporter-inset { 284 | display: inline-block; 285 | vertical-align: top; 286 | min-width: 38px; 287 | margin-left: 6px; 288 | padding: 1px; 289 | border: 1px solid #fff; 290 | border-radius: 6px; 291 | -webkit-box-shadow: inset -1px -1px 3px rgba(255, 255, 255, 0.7), inset 1px 1px 2px rgba(0, 0, 0, 0.35); 292 | box-shadow: inset -1px -1px 3px rgba(255, 255, 255, 0.7), inset 1px 1px 2px rgba(0, 0, 0, 0.35); 293 | text-align: center; 294 | font: bold 9px/11px sans-serif; 295 | height: 11px; 296 | color: #fff; 297 | } 298 | .reporter-large { 299 | display: inline-block; 300 | min-width: 40px; 301 | padding: 2px 5px 1px 5px; 302 | border-radius: 4px; 303 | -webkit-box-shadow: 0 -3px 3px -3px #fff inset, -3px 0 3px -3px #fff inset, 0 3px 3px -3px #000 inset, 3px 0 3px -3px #000 inset; 304 | box-shadow: 0 -3px 3px -3px #fff inset, -3px 0 3px -3px #fff inset, 0 3px 3px -3px #000 inset, 3px 0 3px -3px #000 inset; 305 | font-family: bold 15px sans-serif; 306 | text-align: center; 307 | color: #fff; 308 | position: absolute; 309 | } 310 | 311 | /* List watcher styles */ 312 | .list { 313 | float: left; 314 | border-radius: 7px; 315 | -webkit-border-radius: 7px; 316 | -moz-border-radius: 7px; 317 | border-style: solid; 318 | border-color: gray; 319 | border-width: 2px; 320 | background-color: #C1C4C7; 321 | padding-left: 3px; 322 | font: bold 11px sans-serif; 323 | position: absolute; 324 | } 325 | 326 | .list .list-title { 327 | padding-top: 2px; 328 | text-align: center; 329 | color: black; 330 | font-weight: bold; 331 | margin-bottom: 4px; 332 | } 333 | 334 | .list .list-scrollbar-container { 335 | float: right; 336 | width: 10px; 337 | position: relative; 338 | } 339 | 340 | .list .list-scrollbar { 341 | position: absolute; 342 | top: 0px; 343 | width: 10px; 344 | background-color: #a8aaad; 345 | border-radius: 10px; 346 | } 347 | 348 | .list .list-index { 349 | color: rgb(81, 81, 81); 350 | float: left; 351 | padding: 2px; 352 | margin-top: 0px; 353 | text-align: right; 354 | font: bold 11px; 355 | } 356 | 357 | .list .list-item { 358 | float: right; 359 | height: 16px; 360 | overflow: hidden; 361 | margin-bottom: 0px; 362 | margin-right: 2px; 363 | padding-top: 2px; 364 | padding-left: 5px; 365 | border-color: white; 366 | border-style: solid; 367 | border-width: 1px; 368 | border-radius: 4px; 369 | background-color: #cc5b22; 370 | color: white; 371 | word-wrap: break-word; 372 | font: bolder 11px sans-serif; 373 | } 374 | 375 | .list .list-add { 376 | clear: both; 377 | background-color: #dedede; 378 | width: 12px; 379 | height: 12px; 380 | border-radius: 12px; 381 | color: #707070; 382 | font: bold 10px sans-serif; 383 | text-align: center; 384 | position: absolute; 385 | bottom: 2px; 386 | left: 2px; 387 | } 388 | 389 | .list .list-length { 390 | font-size: 10px; 391 | font-weight: normal; 392 | text-align: center; 393 | color: black; 394 | position: absolute; 395 | bottom: 2px; 396 | left: 0px; 397 | right: 0px; 398 | } 399 | 400 | /* Say/think bubble styles */ 401 | .bubble-container { 402 | position: absolute; 403 | } 404 | .bubble { 405 | position: relative; 406 | display: inline-block; 407 | max-width: 120px; 408 | min-width: 40px; 409 | padding: 6px 11px 6px 11px; 410 | border-radius: 10px; 411 | background: #fff; 412 | font-family: sans-serif; 413 | font-weight: bold; 414 | font-size: 14px; 415 | color: #000; 416 | text-align: center; 417 | } 418 | .bubble.say-think-border { 419 | border: 3px solid rgb(160, 160, 160); 420 | } 421 | .bubble.ask-border, .ask-container { 422 | border: 3px solid rgb(74, 173, 222); 423 | } 424 | .bubble-ask { 425 | position: absolute; 426 | margin-top: -3px; 427 | margin-left: 8px; 428 | width: 44px; 429 | height: 18px; 430 | background: url(img/ask-bottom.png) transparent no-repeat; 431 | } 432 | .bubble-say { 433 | position: absolute; 434 | margin-top: -3px; 435 | margin-left: 8px; 436 | width: 44px; 437 | height: 18px; 438 | background: url(img/say-bottom.png) transparent no-repeat; 439 | } 440 | .bubble-think { 441 | position: absolute; 442 | margin-top: 0px; 443 | margin-left: 8px; 444 | width: 44px; 445 | height: 19px; 446 | background: url(img/think-bottom.png) transparent no-repeat; 447 | } 448 | .ask-container { 449 | position: absolute; 450 | display: inline-block; 451 | padding: 5px 0px 0px 5px; 452 | border-radius: 5px; 453 | background: #fff; 454 | font-family: sans-serif; 455 | font-weight: bold; 456 | font-size: 14px; 457 | color: #000; 458 | } 459 | .ask-container .ask-field .ask-text-field { 460 | width: 405px; 461 | height: 16px; 462 | font-family: sans-serif; 463 | font-weight: light; 464 | font-size: 12px; 465 | background: #EAEAEA; 466 | } 467 | 468 | .ask-container .ask-button { 469 | position: absolute; 470 | right: 2px; 471 | top: 2px; 472 | width: 25px; 473 | height: 25px; 474 | background: url(img/ask-button.png) transparent no-repeat; 475 | } 476 | 477 | 478 | -------------------------------------------------------------------------------- /soundbank/Instr.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Massachusetts Institute of Technology 2 | // 3 | // This program is free software; you can redistribute it and/or 4 | // modify it under the terms of the GNU General Public License version 2, 5 | // as published by the Free Software Foundation. 6 | // 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program; if not, write to the Free Software 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 15 | 16 | // Instr.js 17 | // Tim Mickel, 2013 18 | // Based entirely on the AS by John Maloney, April 2012 19 | // 20 | // This class interacts with IO to load Scratch instruments and drums. 21 | // The variable 'samples' is a dictionary of named sound buffers. 22 | // Call initSamples() to initialize 'samples' before using (during load). 23 | // 24 | // All instrument and drum samples were created for Scratch by: 25 | // 26 | // Paul Madden, paulmatthewmadden@yahoo.com 27 | // 28 | // Paul is an excellent sound designer and we appreciate all the effort 29 | // he put into this project. 30 | 31 | var Instr = function() {} 32 | 33 | Instr.samples = {}; 34 | Instr.wavsLoaded = 0; 35 | 36 | Instr.wavs = { 37 | 'AcousticGuitar_F3': 'instruments/AcousticGuitar_F3_22k.wav', 38 | 'AcousticPiano_As3': 'instruments/AcousticPiano(5)_A#3_22k.wav', 39 | 'AcousticPiano_C4': 'instruments/AcousticPiano(5)_C4_22k.wav', 40 | 'AcousticPiano_G4': 'instruments/AcousticPiano(5)_G4_22k.wav', 41 | 'AcousticPiano_F5': 'instruments/AcousticPiano(5)_F5_22k.wav', 42 | 'AcousticPiano_C6': 'instruments/AcousticPiano(5)_C6_22k.wav', 43 | 'AcousticPiano_Ds6': 'instruments/AcousticPiano(5)_D#6_22k.wav', 44 | 'AcousticPiano_D7': 'instruments/AcousticPiano(5)_D7_22k.wav', 45 | 'AltoSax_A3': 'instruments/AltoSax_A3_22K.wav', 46 | 'AltoSax_C6': 'instruments/AltoSax(3)_C6_22k.wav', 47 | 'Bassoon_C3': 'instruments/Bassoon_C3_22k.wav', 48 | 'BassTrombone_A2_2': 'instruments/BassTrombone_A2(2)_22k.wav', 49 | 'BassTrombone_A2_3': 'instruments/BassTrombone_A2(3)_22k.wav', 50 | 'Cello_C2': 'instruments/Cello(3b)_C2_22k.wav', 51 | 'Cello_As2': 'instruments/Cello(3)_A#2_22k.wav', 52 | 'Choir_F3': 'instruments/Choir(4)_F3_22k.wav', 53 | 'Choir_F4': 'instruments/Choir(4)_F4_22k.wav', 54 | 'Choir_F5': 'instruments/Choir(4)_F5_22k.wav', 55 | 'Clarinet_C4': 'instruments/Clarinet_C4_22k.wav', 56 | 'ElectricBass_G1': 'instruments/ElectricBass(2)_G1_22k.wav', 57 | 'ElectricGuitar_F3': 'instruments/ElectricGuitar(2)_F3(1)_22k.wav', 58 | 'ElectricPiano_C2': 'instruments/ElectricPiano_C2_22k.wav', 59 | 'ElectricPiano_C4': 'instruments/ElectricPiano_C4_22k.wav', 60 | 'EnglishHorn_D4': 'instruments/EnglishHorn(1)_D4_22k.wav', 61 | 'EnglishHorn_F3': 'instruments/EnglishHorn(1)_F3_22k.wav', 62 | 'Flute_B5_1': 'instruments/Flute(3)_B5(1)_22k.wav', 63 | 'Flute_B5_2': 'instruments/Flute(3)_B5(2)_22k.wav', 64 | 'Marimba_C4': 'instruments/Marimba_C4_22k.wav', 65 | 'MusicBox_C4': 'instruments/MusicBox_C4_22k.wav', 66 | 'Organ_G2': 'instruments/Organ(2)_G2_22k.wav', 67 | 'Pizz_A3': 'instruments/Pizz(2)_A3_22k.wav', 68 | 'Pizz_E4': 'instruments/Pizz(2)_E4_22k.wav', 69 | 'Pizz_G2': 'instruments/Pizz(2)_G2_22k.wav', 70 | 'SteelDrum_D5': 'instruments/SteelDrum_D5_22k.wav', 71 | 'SynthLead_C4': 'instruments/SynthLead(6)_C4_22k.wav', 72 | 'SynthLead_C6': 'instruments/SynthLead(6)_C6_22k.wav', 73 | 'SynthPad_A3': 'instruments/SynthPad(2)_A3_22k.wav', 74 | 'SynthPad_C6': 'instruments/SynthPad(2)_C6_22k.wav', 75 | 'TenorSax_C3': 'instruments/TenorSax(1)_C3_22k.wav', 76 | 'Trombone_B3': 'instruments/Trombone_B3_22k.wav', 77 | 'Trumpet_E5': 'instruments/Trumpet_E5_22k.wav', 78 | 'Vibraphone_C3': 'instruments/Vibraphone_C3_22k.wav', 79 | 'Violin_D4': 'instruments/Violin(2)_D4_22K.wav', 80 | 'Violin_A4': 'instruments/Violin(3)_A4_22k.wav', 81 | 'Violin_E5': 'instruments/Violin(3b)_E5_22k.wav', 82 | 'WoodenFlute_C5': 'instruments/WoodenFlute_C5_22k.wav', 83 | // Drums 84 | 'BassDrum': 'drums/BassDrum(1b)_22k.wav', 85 | 'Bongo': 'drums/Bongo_22k.wav', 86 | 'Cabasa': 'drums/Cabasa(1)_22k.wav', 87 | 'Clap': 'drums/Clap(1)_22k.wav', 88 | 'Claves': 'drums/Claves(1)_22k.wav', 89 | 'Conga': 'drums/Conga(1)_22k.wav', 90 | 'Cowbell': 'drums/Cowbell(3)_22k.wav', 91 | 'Crash': 'drums/Crash(2)_22k.wav', 92 | 'Cuica': 'drums/Cuica(2)_22k.wav', 93 | 'GuiroLong': 'drums/GuiroLong(1)_22k.wav', 94 | 'GuiroShort': 'drums/GuiroShort(1)_22k.wav', 95 | 'HiHatClosed': 'drums/HiHatClosed(1)_22k.wav', 96 | 'HiHatOpen': 'drums/HiHatOpen(2)_22k.wav', 97 | 'HiHatPedal': 'drums/HiHatPedal(1)_22k.wav', 98 | 'Maracas': 'drums/Maracas(1)_22k.wav', 99 | 'SideStick': 'drums/SideStick(1)_22k.wav', 100 | 'SnareDrum': 'drums/SnareDrum(1)_22k.wav', 101 | 'Tambourine': 'drums/Tambourine(3)_22k.wav', 102 | 'Tom': 'drums/Tom(1)_22k.wav', 103 | 'Triangle': 'drums/Triangle(1)_22k.wav', 104 | 'Vibraslap': 'drums/Vibraslap(1)_22k.wav', 105 | 'WoodBlock': 'drums/WoodBlock(1)_22k.wav' 106 | }; 107 | 108 | Instr.wavCount = Object.keys(Instr.wavs).length; 109 | -------------------------------------------------------------------------------- /soundbank/drums/BassDrum(1b)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/BassDrum(1b)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Bongo_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Bongo_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Cabasa(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Cabasa(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Clap(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Clap(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Claves(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Claves(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Conga(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Conga(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Cowbell(3)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Cowbell(3)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Crash(2)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Crash(2)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Cuica(2)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Cuica(2)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/GuiroLong(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/GuiroLong(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/GuiroShort(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/GuiroShort(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/HiHatClosed(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/HiHatClosed(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/HiHatOpen(2)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/HiHatOpen(2)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/HiHatPedal(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/HiHatPedal(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Maracas(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Maracas(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/SideStick(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/SideStick(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/SnareDrum(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/SnareDrum(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Tambourine(3)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Tambourine(3)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Tom(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Tom(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Triangle(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Triangle(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/Vibraslap(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/Vibraslap(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/drums/WoodBlock(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/drums/WoodBlock(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticGuitar_F3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticGuitar_F3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_A#3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_A#3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_C6_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_C6_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_D#6_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_D#6_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_D7_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_D7_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_F5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_F5_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AcousticPiano(5)_G4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AcousticPiano(5)_G4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AltoSax(3)_C6_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AltoSax(3)_C6_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/AltoSax_A3_22K.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/AltoSax_A3_22K.wav -------------------------------------------------------------------------------- /soundbank/instruments/BassTrombone_A2(2)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/BassTrombone_A2(2)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/BassTrombone_A2(3)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/BassTrombone_A2(3)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Bassoon_C3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Bassoon_C3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Cello(3)_A#2_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Cello(3)_A#2_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Cello(3b)_C2_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Cello(3b)_C2_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Choir(4)_F3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Choir(4)_F3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Choir(4)_F4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Choir(4)_F4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Choir(4)_F5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Choir(4)_F5_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Clarinet_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Clarinet_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/ElectricBass(2)_G1_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/ElectricBass(2)_G1_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/ElectricGuitar(2)_F3(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/ElectricGuitar(2)_F3(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/ElectricPiano_C2_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/ElectricPiano_C2_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/ElectricPiano_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/ElectricPiano_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/EnglishHorn(1)_D4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/EnglishHorn(1)_D4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/EnglishHorn(1)_F3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/EnglishHorn(1)_F3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Flute(3)_B5(1)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Flute(3)_B5(1)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Flute(3)_B5(2)_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Flute(3)_B5(2)_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Marimba_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Marimba_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/MusicBox_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/MusicBox_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Organ(2)_G2_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Organ(2)_G2_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Pizz(2)_A3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Pizz(2)_A3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Pizz(2)_E4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Pizz(2)_E4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Pizz(2)_G2_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Pizz(2)_G2_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/SteelDrum_D5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/SteelDrum_D5_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/SynthLead(6)_C4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/SynthLead(6)_C4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/SynthLead(6)_C6_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/SynthLead(6)_C6_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/SynthPad(2)_A3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/SynthPad(2)_A3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/SynthPad(2)_C6_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/SynthPad(2)_C6_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/TenorSax(1)_C3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/TenorSax(1)_C3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Trombone_B3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Trombone_B3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Trumpet_E5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Trumpet_E5_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Vibraphone_C3_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Vibraphone_C3_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Violin(2)_D4_22K.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Violin(2)_D4_22K.wav -------------------------------------------------------------------------------- /soundbank/instruments/Violin(3)_A4_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Violin(3)_A4_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/Violin(3b)_E5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/Violin(3b)_E5_22k.wav -------------------------------------------------------------------------------- /soundbank/instruments/WoodenFlute_C5_22k.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/soundbank/instruments/WoodenFlute_C5_22k.wav -------------------------------------------------------------------------------- /test/artifacts/IOMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ioMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'getCount' : function() { return getArgs('getCount'); } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/artifacts/InterpreterMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var interpreterMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'targetSprite' : function() { return getArgs('targetSprite'); }, 24 | 'arg': function(block, index) { return getArgs('arg');}, 25 | 'activeThread': new threadMock(), 26 | 'targetStage': function() { var rtMock = new runtimeMock(); return rtMock.stage} 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /test/artifacts/RuntimeMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var runtimeMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'sprites' : [ 24 | new spriteMock() 25 | ], 26 | 'stage': new stageMock() 27 | 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/artifacts/SpriteMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var spriteMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'hideBubble' : function() { return getArgs('hideBubble'); }, 24 | 'hideAsk': function() { return getArgs('hideAsk');}, 25 | 'resetFilters': function() { return getArgs('resetFilters');} 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/artifacts/StageMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var stageMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'resetFilters' : function() { return getArgs('resetFilters'); }, 24 | 'askAnswer' : 12 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/artifacts/TargetMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var targetMock = function() { 4 | return { 5 | 'showBubble' : function() {}, 6 | 'showAsk' : function() {}, 7 | 'askAnswer' : 22 8 | }; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /test/artifacts/TestHelpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var deepCopy = function(object) { 4 | return jQuery.extend(true, {}, object); 5 | } 6 | -------------------------------------------------------------------------------- /test/artifacts/ThreadMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var threadMock = function() { 4 | var args = createArgs(arguments); 5 | 6 | function getArgs(argKey) { 7 | return ((argKey in args) ? args[argKey] : null); 8 | } 9 | 10 | function createArgs(methodArgs) { 11 | var args = {}; 12 | if (methodArgs.length) { 13 | _.each(methodArgs, function(newObject) { 14 | _.each(newObject, function(value, key) { 15 | args[key] = value; 16 | }); 17 | }); 18 | } 19 | return args; 20 | } 21 | 22 | return { 23 | 'paused' : getArgs('paused') 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/artifacts/ask-artifact.js: -------------------------------------------------------------------------------- 1 | var sensingData = { 2 | "objName": "Stage", 3 | "variables": [{ 4 | "name": "myAnswer", 5 | "value": 0, 6 | "isPersistent": false 7 | }], 8 | "costumes": [{ 9 | "costumeName": "backdrop1", 10 | "baseLayerID": -1, 11 | "baseLayerMD5": "b61b1077b0ea1931abee9dbbfa7903ff.png", 12 | "bitmapResolution": 2, 13 | "rotationCenterX": 480, 14 | "rotationCenterY": 360 15 | }], 16 | "currentCostumeIndex": 0, 17 | "penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png", 18 | "tempoBPM": 60, 19 | "videoAlpha": 0.5, 20 | "children": [{ 21 | "objName": "Sprite1", 22 | "variables": [{ 23 | "name": "myAnswer2", 24 | "value": 0, 25 | "isPersistent": false 26 | }, { 27 | "name": "answer", 28 | "value": 0, 29 | "isPersistent": false 30 | }], 31 | "scripts": [[42, 40.5, [["whenGreenFlag"], ["doAsk", "What's your name?"]]], 32 | [44.5, 33 | 155.5, 34 | [["whenGreenFlag"], 35 | ["say:", "Hello!"], 36 | ["doIf", ["=", ["timeAndDate", "minute"], "60"], [["say:", ["timestamp"]]]]]]], 37 | "costumes": [{ 38 | "costumeName": "costume1", 39 | "baseLayerID": -1, 40 | "baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg", 41 | "bitmapResolution": 1, 42 | "rotationCenterX": 47, 43 | "rotationCenterY": 55 44 | }], 45 | "currentCostumeIndex": 0, 46 | "scratchX": 0, 47 | "scratchY": 0, 48 | "scale": 1, 49 | "direction": 90, 50 | "rotationStyle": "normal", 51 | "isDraggable": false, 52 | "indexInLibrary": 1, 53 | "visible": true, 54 | "spriteInfo": { 55 | } 56 | }], 57 | "info": { 58 | "projectID": "18926654", 59 | "spriteCount": 1, 60 | "flashVersion": "MAC 12,0,0,70", 61 | "swfVersion": "v396", 62 | "userAgent": "Mozilla\/5.0 (Macintosh; Intel Mac OS X 10.9; rv:27.0) Gecko\/20100101 Firefox\/27.0", 63 | "videoOn": false, 64 | "scriptCount": 2, 65 | "hasCloudData": false 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /test/artifacts/io-artifact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var io_base = 'proxy.php?resource=internalapi/'; 4 | var project_base = 'http://cdn.projects.scratch.mit.edu/internalapi/project/'; 5 | var project_suffix = '/get/'; 6 | var asset_base = 'http://cdn.assets.scratch.mit.edu/internalapi/asset/'; 7 | var asset_suffix = '/get/'; 8 | var soundbank_base = 'soundbank/'; 9 | var spriteLayerCount = 0; 10 | -------------------------------------------------------------------------------- /test/artifacts/reporterValues.js: -------------------------------------------------------------------------------- 1 | 'use Strict;' 2 | 3 | var ReporterValues = function() { 4 | return { 5 | 'getStageVariables': function() { 6 | return { 7 | 'cmd' : "getVar:", 8 | 'color' : 15629590, 9 | 'isDiscrete' : true, 10 | 'mode' : 1, 11 | 'param' : "myAnswer", 12 | 'sliderMax' : 100, 13 | 'sliderMin' : 0, 14 | 'target' : "Stage", 15 | 'visible' : true, 16 | 'x' : 5, 17 | 'y' : 5, 18 | }; 19 | } 20 | } 21 | }; 22 | /* 23 | Additional Reporter examples 24 | cmd : "getVar:" 25 | color : 15629590 26 | isDiscrete : true 27 | mode : 1 28 | param : "myAnswer2" 29 | sliderMax : 100 30 | sliderMin : 0 31 | target : "Sprite1" 32 | visible : true 33 | x : 5 34 | y : 32 35 | 36 | cmd : "getVar:" 37 | color : 15629590 38 | isDiscrete : true 39 | mode : 1 40 | param : "answer" 41 | sliderMax : 100 42 | sliderMin : 0 43 | target : "Sprite1" 44 | visible : true 45 | x : 5 46 | y : 59 47 | */ 48 | -------------------------------------------------------------------------------- /test/artifacts/scratch-artifact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var project_id = 123456789; 4 | 5 | var returnData = { 6 | "objName": "Stage", 7 | "sounds": [{ 8 | "soundName": "pop", 9 | "soundID": -1, 10 | "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", 11 | "sampleCount": 258, 12 | "rate": 11025, 13 | "format": "" 14 | }], 15 | "costumes": [{ 16 | "costumeName": "Scene 1", 17 | "baseLayerID": -1, 18 | "baseLayerMD5": "510da64cf172d53750dffd23fbf73563.png", 19 | "rotationCenterX": 240, 20 | "rotationCenterY": 180, 21 | "spritesHiddenInScene": null 22 | }], 23 | "currentCostumeIndex": 0, 24 | "penLayerMD5": "279467d0d49e152706ed66539b577c00.png", 25 | "tempoBPM": 60, 26 | "children": [{ 27 | "objName": "Sprite1", 28 | "scripts": [[283, 29 | 151, 30 | [["whenClicked"], 31 | ["clearPenTrails"], 32 | ["penColor:", 10485886], 33 | ["putPenDown"], 34 | ["doForever", 35 | [["gotoX:y:", ["randomFrom:to:", -240, 240], ["randomFrom:to:", -180, 180]], ["changePenShadeBy:", 10]]]]]], 36 | "sounds": [{ 37 | "soundName": "pop", 38 | "soundID": -1, 39 | "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", 40 | "sampleCount": 258, 41 | "rate": 11025, 42 | "format": "" 43 | }], 44 | "costumes": [{ 45 | "costumeName": "Costume1", 46 | "baseLayerID": -1, 47 | "baseLayerMD5": "cce61b6e9ad98ea8c8c2e9556a94b7ab.png", 48 | "rotationCenterX": 47, 49 | "rotationCenterY": 55, 50 | "spritesHiddenInScene": null 51 | }, 52 | { 53 | "costumeName": "Costume2", 54 | "baseLayerID": -1, 55 | "baseLayerMD5": "51f6fa1871f17de1a21cdfead7aad574.png", 56 | "rotationCenterX": 47, 57 | "rotationCenterY": 55, 58 | "spritesHiddenInScene": null 59 | }], 60 | "currentCostumeIndex": 0, 61 | "scratchX": 120, 62 | "scratchY": -101, 63 | "scale": 1, 64 | "direction": 90, 65 | "rotationStyle": "normal", 66 | "isDraggable": false, 67 | "indexInLibrary": 0, 68 | "visible": true 69 | }, 70 | { 71 | "objName": "fish31", 72 | "scripts": [[181, 138, [["whenClicked"], ["nextCostume"]]]], 73 | "sounds": [{ 74 | "soundName": "pop", 75 | "soundID": -1, 76 | "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", 77 | "sampleCount": 258, 78 | "rate": 11025, 79 | "format": "" 80 | }], 81 | "costumes": [{ 82 | "costumeName": "fish3", 83 | "baseLayerID": -1, 84 | "baseLayerMD5": "5ab571cf8c6e6bcf0ee2443b5df17dcb.png", 85 | "rotationCenterX": 90, 86 | "rotationCenterY": 79, 87 | "spritesHiddenInScene": null 88 | }, 89 | { 90 | "costumeName": "crab1-a", 91 | "baseLayerID": -1, 92 | "baseLayerMD5": "110bf75ed212eb072acec2fa2c39456d.png", 93 | "rotationCenterX": 92, 94 | "rotationCenterY": 62, 95 | "spritesHiddenInScene": null 96 | }, 97 | { 98 | "costumeName": "ballerina-a", 99 | "baseLayerID": -1, 100 | "baseLayerMD5": "4c789664cc6f69d1ef4678ac8b4cb812.png", 101 | "rotationCenterX": 51, 102 | "rotationCenterY": 84, 103 | "spritesHiddenInScene": null 104 | }], 105 | "currentCostumeIndex": 2, 106 | "scratchX": 108, 107 | "scratchY": -28, 108 | "scale": 1, 109 | "direction": 90, 110 | "rotationStyle": "normal", 111 | "isDraggable": false, 112 | "indexInLibrary": 0, 113 | "visible": true 114 | }], 115 | "info": { 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /test/fixtures/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | basePath : '../../', 4 | 5 | files : [ 6 | 'test/artifacts/**/*.js', 7 | 'test/lib/**/*.js', 8 | 'test/unit/**/*.js', 9 | 'js/sound/SoundDecoder.js', 10 | 'js/sound/**/*.js', 11 | 'js/util/**/*.js', 12 | 'js/**/*.js', 13 | 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', 14 | 'node_modules/underscore/underscore.js' 15 | ], 16 | 17 | exclude : [ 18 | ], 19 | 20 | preprocessors: { 21 | '*.html': ['html2js'] 22 | }, 23 | 24 | autoWatch : true, 25 | 26 | frameworks: ['jasmine'], 27 | 28 | browsers : ['Chrome'], 29 | 30 | plugins : [ 31 | 'karma-jasmine', 32 | 'jasmine-jquery', 33 | 'karma-html2js-preprocessor', 34 | 'karma-chrome-launcher', 35 | 'karma-firefox-launcher' 36 | ] 37 | })} 38 | -------------------------------------------------------------------------------- /test/lib/.git-keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-html5/e7c3b48a2ae903d46d27b1936baf32da20a7b93b/test/lib/.git-keep -------------------------------------------------------------------------------- /test/unit/interpreterSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Interpreter.js go here */ 2 | 3 | describe('Interpreter', function() { 4 | var interp; 5 | 6 | beforeEach(function() { 7 | interp = Interpreter; 8 | }); 9 | 10 | describe('Initialized variables', function() { 11 | var initInterp, realThread, realTimer; 12 | beforeEach(function() { 13 | realThread = Thread; 14 | realTimer = Timer; 15 | Thread = threadMock; 16 | Timer = function() {}; 17 | initInterp = new interp(); 18 | }); 19 | 20 | afterEach(function() { 21 | Thread = realThread; 22 | Timer = realTimer; 23 | }); 24 | 25 | describe('Interpreter Variables', function() { 26 | it('should have a primitiveTable collection', function() { 27 | expect(initInterp.primitiveTable).toEqual({}); 28 | }); 29 | 30 | it('should have a variables collection', function() { 31 | expect(initInterp.variables).toEqual({}); 32 | }); 33 | 34 | it('should have a threads array', function() { 35 | expect(initInterp.threads).toEqual([]); 36 | }); 37 | 38 | it('should have an activeThread variable', function() { 39 | expect(initInterp.activeThread).toEqual(threadMock()); 40 | }); 41 | 42 | it('should have a WorkTime variable', function() { 43 | expect(initInterp.WorkTime).toBe(30); 44 | }); 45 | 46 | it('should have a currentMSecs variable', function() { 47 | expect(initInterp.currentMSecs).toBe(null); 48 | }); 49 | 50 | it('should have a timer variable', function() { 51 | expect(initInterp.timer).toEqual({}); 52 | }); 53 | 54 | it('should have a yield variable', function() { 55 | expect(initInterp.yield).toBe(false); 56 | }); 57 | 58 | it('should have a doRedraw variable', function() { 59 | expect(initInterp.doRedraw).toBe(false); 60 | }); 61 | 62 | it('should have an opCount variable', function() { 63 | expect(initInterp.opCount).toBe(0); 64 | }); 65 | 66 | it('should have a debugOps variable', function() { 67 | expect(initInterp.debugOps).toBe(false); 68 | }); 69 | 70 | it('should have a debugFunc variable', function() { 71 | expect(initInterp.debugFunc).toBe(null); 72 | }); 73 | 74 | it('should have an opCount2 variable', function() { 75 | expect(initInterp.opCount2).toBe(0); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('TargetStage', function() { 81 | it('should return the target.stage object', function() { 82 | runtime = new runtimeMock(); 83 | expect(interp.prototype.targetStage()).toEqual(runtime.stage); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/unit/ioSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for IO.js go here */ 4 | 5 | describe('IO', function() { 6 | var io; 7 | 8 | beforeEach(function() { 9 | io = new IO(); 10 | }); 11 | 12 | it('should have "null" data', function() { 13 | expect(io.data).toBe(null); 14 | }); 15 | 16 | it('should have a project_base', function() { 17 | expect(io.project_base).toBe(project_base); 18 | }); 19 | 20 | it('should have a project_suffix', function() { 21 | expect(io.project_suffix).toBe(project_suffix); 22 | }); 23 | 24 | it('should have an asset_base', function() { 25 | expect(io.asset_base).toBe(asset_base); 26 | }); 27 | 28 | it('should have an asset_suffix', function() { 29 | expect(io.asset_suffix).toBe(asset_suffix); 30 | }); 31 | 32 | it('should have an soundbank_base', function() { 33 | expect(io.soundbank_base).toBe(soundbank_base); 34 | }); 35 | 36 | it('should have a spriteLayerCount', function() { 37 | expect(io.spriteLayerCount).toBe(spriteLayerCount); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/looksPrimitiveSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for primitives/LooksPrims.js go here */ 2 | 3 | describe('LooksPrims', function() { 4 | var looksPrims, targetSpriteMock; 5 | beforeEach(function() { 6 | looksPrims = LooksPrims; 7 | targetSpriteMock = targetMock(); 8 | }); 9 | 10 | describe('showBubble for say', function() { 11 | var sayBlock; 12 | beforeEach(function() { 13 | sayBlock = {'args': ['what to say']}; 14 | interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': sayBlock}); 15 | }); 16 | 17 | it('should call the showBubble method on the targetedSprite', function() { 18 | spyOn(targetSpriteMock, "showBubble"); 19 | showBubble(sayBlock, "say"); 20 | expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to say']}, 'say'); 21 | }); 22 | }); 23 | 24 | describe('showBubble for think', function() { 25 | var thinkBlock; 26 | beforeEach(function() { 27 | thinkBlock = {'args': ['what to think']}; 28 | interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': thinkBlock}); 29 | }); 30 | 31 | it('should call the showBubble method on the targetedSprite', function() { 32 | spyOn(targetSpriteMock, "showBubble"); 33 | showBubble(thinkBlock, "think"); 34 | expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to think']}, 'think'); 35 | }); 36 | }); 37 | 38 | describe('showBubble for ask', function() { 39 | var askBlock; 40 | beforeEach(function() { 41 | askBlock = {'args': ['what to ask']}; 42 | interp = interpreterMock({'targetSprite': targetSpriteMock }, {'arg': askBlock}); 43 | }); 44 | 45 | it('should call the showBubble method on the targetedSprite', function() { 46 | spyOn(targetSpriteMock, "showBubble"); 47 | showBubble(askBlock, "ask"); 48 | expect(targetSpriteMock.showBubble).toHaveBeenCalledWith({args:['what to ask']}, 'ask'); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/reporterSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Reporter.js go here */ 2 | 3 | describe('Reporter', function() { 4 | var reporter, reporterValues; 5 | 6 | beforeEach(function() { 7 | reporter = Reporter; 8 | reporterValues = new ReporterValues(); 9 | }); 10 | 11 | describe('Initialized variables', function() { 12 | var initReporter; 13 | beforeEach(function() { 14 | io = new ioMock({'getCount': 4}); 15 | initReporter = new reporter(reporterValues.getStageVariables()); 16 | }); 17 | 18 | describe('Reporter Variables', function() { 19 | it('should have a cmd variable', function() { 20 | expect(initReporter.cmd).toBe('getVar:'); 21 | }); 22 | 23 | it('should have a color variable', function() { 24 | expect(initReporter.color).toBe(15629590); 25 | }); 26 | 27 | it('should have a isDiscrete variable', function() { 28 | expect(initReporter.isDiscrete).toBe(true); 29 | }); 30 | 31 | it('should have a mode variable', function() { 32 | expect(initReporter.mode).toBe(1); 33 | }); 34 | 35 | it('should have a param variable', function() { 36 | expect(initReporter.param).toBe('myAnswer'); 37 | }); 38 | 39 | it('should have a sliderMax variable', function() { 40 | expect(initReporter.sliderMax).toBe(100); 41 | }); 42 | 43 | it('should have a sliderMin variable', function() { 44 | expect(initReporter.sliderMin).toBe(0); 45 | }); 46 | 47 | it('should have a target variable', function() { 48 | expect(initReporter.target).toBe('Stage'); 49 | }); 50 | 51 | it('should have a visible variable', function() { 52 | expect(initReporter.visible).toBe(true); 53 | }); 54 | 55 | it('should have a x variable', function() { 56 | expect(initReporter.x).toBe(5); 57 | }); 58 | 59 | it('should have a y variable', function() { 60 | expect(initReporter.y).toBe(5); 61 | }); 62 | 63 | it('should have a z variable', function() { 64 | expect(initReporter.z).toBe(4); 65 | }); 66 | 67 | it('should have a label variable', function() { 68 | expect(initReporter.label).toBe('myAnswer'); 69 | }); 70 | 71 | it('should have an el variable', function() { 72 | expect(initReporter.el).toBe(null); 73 | }); 74 | 75 | it('should have an valueEl variable', function() { 76 | expect(initReporter.valueEl).toBe(null); 77 | }); 78 | 79 | it('should have an slider variable', function() { 80 | expect(initReporter.slider).toBe(null); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('determineReporterLabel', function() { 86 | it('should return a stage variable', function() { 87 | reporter.prototype.target = "Stage"; 88 | reporter.prototype.param = "myAnswer"; 89 | reporter.prototype.cmd = "getVar:"; 90 | expect(reporter.prototype.determineReporterLabel()).toBe('myAnswer'); 91 | }); 92 | 93 | it('should return a sprite variable', function() { 94 | reporter.prototype.target = "Sprite 1"; 95 | reporter.prototype.param = "localAnswer"; 96 | reporter.prototype.cmd = "getVar:"; 97 | expect(reporter.prototype.determineReporterLabel()).toBe('Sprite 1: localAnswer'); 98 | }); 99 | 100 | it('should return a stage answer variable', function() { 101 | reporter.prototype.target = "Stage"; 102 | reporter.prototype.param = null; 103 | reporter.prototype.cmd = "answer"; 104 | expect(reporter.prototype.determineReporterLabel()).toBe('answer'); 105 | }); 106 | 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/unit/runTimeSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Runtime.js go here */ 2 | 3 | describe('Runtime', function() { 4 | var runtimeObj; 5 | 6 | beforeEach(function() { 7 | runtimeObj = Runtime; 8 | }); 9 | 10 | describe('Initialized variables', function() { 11 | var initRuntime, lineCanvas; 12 | beforeEach(function() { 13 | initRuntime = new runtimeObj(); 14 | }); 15 | 16 | describe('Runtime Variables', function() { 17 | it('should have a scene variable', function() { 18 | expect(initRuntime.scene).toBe(null); 19 | }); 20 | 21 | it('should have a sprites array', function() { 22 | expect(initRuntime.sprites).toEqual([]); 23 | }); 24 | 25 | it('should have a reporters array', function() { 26 | expect(initRuntime.reporters).toEqual([]); 27 | }); 28 | 29 | it('should have a keysDown array', function() { 30 | expect(initRuntime.keysDown).toEqual([]); 31 | }); 32 | 33 | it('should have a mouseDown variable', function() { 34 | expect(initRuntime.mouseDown).toBe(false); 35 | }); 36 | 37 | it('should have a mousePos array', function() { 38 | expect(initRuntime.mousePos).toEqual([0,0]); 39 | }); 40 | 41 | it('should have an audioContext variable', function() { 42 | expect(initRuntime.audioContext).toBe(null); 43 | }); 44 | 45 | it('should have an audoGain variable', function() { 46 | expect(initRuntime.audioGain).toBe(null); 47 | }); 48 | 49 | it('should have an audioPlaying array', function() { 50 | expect(initRuntime.audioPlaying).toEqual([]); 51 | }); 52 | 53 | it('should have a notesPlaying array', function() { 54 | expect(initRuntime.notesPlaying).toEqual([]); 55 | }); 56 | 57 | it('should have a projectLoaded variable', function() { 58 | expect(initRuntime.projectLoaded).toBe(false); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('Stop All', function() { 64 | var realThread; 65 | beforeEach(function() { 66 | runtime = new runtimeMock 67 | spyOn(window, "stopAllSounds"); 68 | spyOn(runtime.stage, "resetFilters"); 69 | spyOn(runtime.sprites[0], "hideBubble"); 70 | spyOn(runtime.sprites[0], "resetFilters"); 71 | spyOn(runtime.sprites[0], "hideAsk"); 72 | realThread = Thread; 73 | Thread = threadMock; 74 | interp = new interpreterMock(); 75 | }); 76 | 77 | afterEach(function() { 78 | Thread = realThread; 79 | }); 80 | 81 | it('should call a new Thread Object', function() { 82 | runtimeObj.prototype.stopAll(); 83 | expect(interp.activeThread).toEqual(new threadMock()); 84 | }); 85 | 86 | it('should intitialize an empty threads array', function() { 87 | runtimeObj.prototype.stopAll(); 88 | expect(interp.threads).toEqual([]); 89 | }); 90 | 91 | it('should call stopAllSounds', function() { 92 | runtimeObj.prototype.stopAll(); 93 | expect(window.stopAllSounds).toHaveBeenCalled(); 94 | }); 95 | 96 | it('should call sprites.hideBubble', function() { 97 | runtimeObj.prototype.stopAll(); 98 | expect(runtime.sprites[0].hideBubble).toHaveBeenCalled(); 99 | }); 100 | 101 | it('should call sprites.resetFilters', function() { 102 | runtimeObj.prototype.stopAll(); 103 | expect(runtime.sprites[0].resetFilters).toHaveBeenCalled(); 104 | }); 105 | 106 | it('should call sprites.hideAsk', function() { 107 | runtimeObj.prototype.stopAll(); 108 | expect(runtime.sprites[0].hideAsk).toHaveBeenCalled(); 109 | }); 110 | 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /test/unit/scratchSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Scratch.js go here */ 2 | 3 | describe('Scratch', function() { 4 | var scratch; 5 | 6 | beforeEach(function() { 7 | spyOn(IO.prototype, "loadProject"); 8 | spyOn(Runtime.prototype, "init"); 9 | spyOn(Interpreter.prototype, "initPrims"); 10 | scratch = Scratch; 11 | }); 12 | 13 | describe('Scratch - Load Project', function() { 14 | beforeEach(function() { 15 | scratch(project_id); 16 | }); 17 | 18 | it('should call the IO loadProject Method', function() { 19 | expect(IO.prototype.loadProject).toHaveBeenCalled(); 20 | }); 21 | 22 | it('should call the Runtime init method', function() { 23 | expect(Runtime.prototype.init).toHaveBeenCalled(); 24 | }); 25 | 26 | it('should call the Interpreter initPrims method', function() { 27 | expect(Interpreter.prototype.initPrims).toHaveBeenCalled(); 28 | }); 29 | }); 30 | 31 | describe('Scratch - Click Green Flag', function() { 32 | beforeEach(function() { 33 | setFixtures('
'); 34 | scratch(project_id); 35 | }); 36 | 37 | it('should not click on the green flag if the project is loading', function() { 38 | runtime.projectLoaded = false; 39 | spyOn(runtime, 'greenFlag'); 40 | $('#trigger-green-flag').click(); 41 | expect(runtime.greenFlag).not.toHaveBeenCalled(); 42 | expect($('#overlay').css('display')).toBe('block'); 43 | }); 44 | 45 | it('should click on the green flag if the project is loaded', function() { 46 | runtime.projectLoaded = true; 47 | spyOn(runtime, 'greenFlag'); 48 | $('#trigger-green-flag').click(); 49 | expect(runtime.greenFlag).toHaveBeenCalled(); 50 | expect($('#overlay').css('display')).toBe('none'); 51 | }); 52 | }); 53 | 54 | describe('Scratch - Click Stop', function() { 55 | beforeEach(function() { 56 | setFixtures(''); 57 | scratch(project_id); 58 | }); 59 | 60 | it('should not click on the green flag if the project is loading', function() { 61 | spyOn(runtime, 'stopAll'); 62 | $('#trigger-stop').click(); 63 | expect(runtime.stopAll).toHaveBeenCalled(); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/unit/sensingPrimitiveSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for primitives/SensingPrims.js go here */ 2 | 3 | describe('SensingPrims', function() { 4 | var sensingPrims; 5 | beforeEach(function() { 6 | sensingPrims = SensingPrims; 7 | realDate = Date; 8 | }); 9 | 10 | afterEach(function() { 11 | Date = realDate; 12 | }); 13 | 14 | describe('primTimestamp', function() { 15 | beforeEach(function() { 16 | /* MonkeyPatching the built-in Javascript Date */ 17 | var epochDate = new Date(2000, 0, 1); 18 | var nowDate = new Date(2014, 5, 16); 19 | Date = function() { 20 | return (arguments.length ? epochDate : nowDate); 21 | }; 22 | }); 23 | 24 | it('should return the days since 2000', function() { 25 | expect(sensingPrims.prototype.primTimestamp()).toBeCloseTo(5280); 26 | }); 27 | }); 28 | 29 | describe('primTimeDate', function() { 30 | beforeEach(function() { 31 | /* MonkeyPatching the built-in Javascript Date */ 32 | Date = function() { 33 | return { 34 | 'getFullYear': function() { return 2014;}, 35 | 'getMonth': function() { return 4;}, 36 | 'getDate': function() { return 16;}, 37 | 'getDay': function() { return 4;}, 38 | 'getHours': function() { return 9;}, 39 | 'getMinutes': function() { return 18;}, 40 | 'getSeconds': function() { return 36;}, 41 | 'getTime': function() {} 42 | }; 43 | }; 44 | }); 45 | 46 | it('should return the year', function() { 47 | var block = {'args': ['year']}; 48 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(2014); 49 | }); 50 | 51 | it('should return the month of the year', function() { 52 | var block = {'args': ['month']}; 53 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(5); 54 | }); 55 | 56 | it('should return the day of the week', function() { 57 | var block = {'args': ['day of week']}; 58 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(5); 59 | }); 60 | 61 | it('should return the hour of the day', function() { 62 | var block = {'args': ['hour']}; 63 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(9); 64 | }); 65 | 66 | it('should return the minute of the hour', function() { 67 | var block = {'args': ['minute']}; 68 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(18); 69 | }); 70 | 71 | it('should return the second of the minute', function() { 72 | var block = {'args': ['second']}; 73 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(36); 74 | }); 75 | 76 | it('should return the 0 on year', function() { 77 | var block = {'args': ['anythingElse']}; 78 | expect(sensingPrims.prototype.primTimeDate(block)).toEqual(0); 79 | }); 80 | }); 81 | 82 | describe('primAnswer', function() { 83 | beforeEach(function() { 84 | interp = interpreterMock({'targetSprite': new targetMock()}); 85 | }); 86 | 87 | it('should return the answer variable from the targetedSprite', function() { 88 | expect(sensingPrims.prototype.primAnswer()).toBe(12); 89 | }); 90 | }); 91 | 92 | describe('primDoAsk', function() { 93 | var askBlock, targetSpriteMock; 94 | beforeEach(function() { 95 | targetSpriteMock = targetMock(); 96 | askBlock = {'args': 'what to ask'}; 97 | interp = interpreterMock({'targetSprite': targetSpriteMock}, {'arg': askBlock}); 98 | }); 99 | 100 | it('should call the showBubble method on the targetedSprite', function() { 101 | spyOn(window, "showBubble"); 102 | spyOn(targetSpriteMock, "showAsk"); 103 | sensingPrims.prototype.primDoAsk(askBlock); 104 | expect(window.showBubble).toHaveBeenCalledWith({args:'what to ask'}, 'doAsk'); 105 | expect(targetSpriteMock.showAsk).toHaveBeenCalled; 106 | expect(interp.activeThread.paused).toBe(true); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/unit/stageSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Stage.js go here */ 2 | 3 | describe('Stage', function() { 4 | var stage; 5 | 6 | beforeEach(function() { 7 | stage = Stage; 8 | }); 9 | 10 | describe('Initialized variables', function() { 11 | var initStage, lineCanvas; 12 | beforeEach(function() { 13 | spyOn(Sprite, "call"); 14 | initStage = new stage(sensingData); 15 | }); 16 | 17 | describe('Stage Variables', function() { 18 | it('should have a z variable', function() { 19 | expect(initStage.z).toBe(-2); 20 | }); 21 | 22 | it('should have a penLayerLoaded variable', function() { 23 | expect(initStage.penLayerLoaded).toBe(false); 24 | }); 25 | 26 | it('should have a lineCanvas element', function() { 27 | expect(initStage.lineCanvas).toBeDefined(); 28 | }); 29 | 30 | it('should have a lineCanvas width', function() { 31 | expect(initStage.lineCanvas.width).toBe(480); 32 | }); 33 | 34 | it('should have a lineCanvas height', function() { 35 | expect(initStage.lineCanvas.height).toBe(360); 36 | }); 37 | 38 | it('should have a lineCache variable', function() { 39 | expect(initStage.lineCache).toBeDefined(); 40 | }); 41 | 42 | it('should have a isStage variable', function() { 43 | expect(initStage.isStage).toBe(true); 44 | }); 45 | 46 | it('should have an askAnswer variable', function() { 47 | expect(initStage.askAnswer).toBe(""); 48 | }); 49 | 50 | it('should have called Sprite.call', function() { 51 | expect(Sprite.call).toHaveBeenCalled(); 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/threadSpec.js: -------------------------------------------------------------------------------- 1 | /* jasmine specs for Interpreter.js -> Thread go here */ 2 | 3 | describe('Thread', function() { 4 | var thread; 5 | 6 | beforeEach(function() { 7 | thread = Thread; 8 | }); 9 | 10 | describe('Initialized variables', function() { 11 | var initThread; 12 | beforeEach(function() { 13 | initThread = new thread('block', 'target'); 14 | }); 15 | 16 | describe('Thread Variables', function() { 17 | it('should have a nextBlock variable', function() { 18 | expect(initThread.nextBlock).toBe('block'); 19 | }); 20 | 21 | it('should have a firstBlock variable', function() { 22 | expect(initThread.firstBlock).toBe('block'); 23 | }); 24 | 25 | it('should have a stack variable', function() { 26 | expect(initThread.stack).toEqual([]); 27 | }); 28 | 29 | it('should have a target variable', function() { 30 | expect(initThread.target).toBe('target'); 31 | }); 32 | 33 | it('should have a tmp variable', function() { 34 | expect(initThread.tmp).toBe(null); 35 | }); 36 | 37 | it('should have a tmpObj variable', function() { 38 | expect(initThread.tmpObj).toEqual([]); 39 | }); 40 | 41 | it('should have a firstTime variable', function() { 42 | expect(initThread.firstTime).toBe(true); 43 | }); 44 | 45 | it('should have a paused variable', function() { 46 | expect(initThread.paused).toBe(false); 47 | }); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | To-do and notes: HTML5 Player 2 | Last updated: June 2013, Tim Mickel 3 | 4 | To do: 5 | -Implement volume control for playSound (it's working for notes and drums, 6 | but the way I'm preloading the sample data for playSound means changing the volume 7 | mid-play is a little more tricky. Perhaps use a GainNode per sprite?) 8 | Also need volume/tempo watchers. 9 | 10 | -Cloning 11 | This is potentially easy; duplicate the object using deep copy on clone. 12 | Then remove the object from runtime.sprites on self-destruct - be careful of mem. leak 13 | 14 | -Procedures 15 | 16 | -Trigger hats (e.g. When Scene Starts?) 17 | 18 | -SVGs/Clicks/Correct collisions and bounce 19 | (see Chromium bug https://code.google.com/p/chromium/issues/detail?id=249037). 20 | 21 | -Cloud variables/Scratch 2.0 API calls 22 | 23 | -List watchers 24 | 25 | -Implement sliders via drawing instead of 26 | 27 | -Graphic effects 28 | Some are simple to implement in canvas (ghost, brightness, etc.) 29 | Potentially use CSS filters (CSS3 hue-rotate looks promising) 30 | 31 | -Sound input, camera input 32 | 33 | 34 | Performance considerations: 35 | -Touching, touching color, color touching color can definitely be improved 36 | 37 | Known bugs: 38 | -Touching color, color touching color is currently finicky 39 | -"Keep on stage" / "Bounce on edge" do not match flash player; need to consider transparency 40 | -Experimental touch support for iPad was put in - this needs to be played with especially for non-iOS. 41 | --------------------------------------------------------------------------------