diff options
| -rw-r--r-- | blocks.js | 34 | ||||
| -rw-r--r-- | gui.js | 57 | ||||
| -rw-r--r-- | objects.js | 123 | ||||
| -rw-r--r-- | threads.js | 27 |
4 files changed, 231 insertions, 10 deletions
@@ -7774,6 +7774,8 @@ SymbolMorph.prototype.names = [ 'pointRight', 'gears', 'file', + 'mutedSounds', + 'unmutedSounds', 'fullScreen', 'normalScreen', 'smallStage', @@ -7898,6 +7900,10 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) { return this.drawSymbolGears(canvas, aColor); case 'file': return this.drawSymbolFile(canvas, aColor); + case 'mutedSounds': + return this.drawSymbolMutedSounds(canvas, aColor); + case 'unmutedSounds': + return this.drawSymbolUnmutedSounds(canvas, aColor); case 'fullScreen': return this.drawSymbolFullScreen(canvas, aColor); case 'normalScreen': @@ -8100,6 +8106,34 @@ SymbolMorph.prototype.drawSymbolFile = function (canvas, color) { return canvas; }; +SymbolMorph.prototype.drawSymbolMutedSounds = function (canvas, color) { + // answer a canvas showing a muted sounds toggling symbol + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + w2 = w / 2, + h2 = h / 2; + + ctx.fillStyle = color.darker(40).toString(); + ctx.fillRect(0, 0, w, h); + + return canvas; +}; + +SymbolMorph.prototype.drawSymbolUnmutedSounds = function (canvas, color) { + // answer a canvas showing a UNmuted sounds toggling symbol + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + w2 = w / 2, + h2 = h / 2; + + ctx.fillStyle = color.darker(60).toString(); + ctx.fillRect(0, 0, w, h); + + return canvas; +}; + SymbolMorph.prototype.drawSymbolFullScreen = function (canvas, color) { // answer a canvas showing two arrows pointing diagonally outwards var ctx = canvas.getContext('2d'), @@ -212,6 +212,7 @@ IDE_Morph.prototype.init = function (isAutoFill) { this.corral = null; this.isAutoFill = isAutoFill || true; + this.isMuted = false; this.isAppMode = false; this.isSmallStage = false; this.filePicker = null; @@ -459,6 +460,7 @@ IDE_Morph.prototype.createControlBar = function () { stopButton, pauseButton, startButton, + muteSoundsButton, projectButton, settingsButton, stageSizeButton, @@ -548,6 +550,38 @@ IDE_Morph.prototype.createControlBar = function () { this.controlBar.add(appModeButton); this.controlBar.appModeButton = appModeButton; // for refreshing + //muteSoundsButton + button = new ToggleButtonMorph( + null, //colors, + myself, // the IDE is the target + 'toggleMuteSounds', + [ + new SymbolMorph('mutedSounds', 14), + new SymbolMorph('unmutedSounds', 14) + ], + function () { // query + return myself.isMuted; + } + ); + + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'sounds\nmuted & unmuted'; + button.fixLayout(); + button.refresh(); + muteSoundsButton = button; + this.controlBar.add(muteSoundsButton); + this.controlBar.muteSoundsButton = button; // for refreshing + // stopButton button = new PushButtonMorph( this, @@ -712,7 +746,7 @@ IDE_Morph.prototype.createControlBar = function () { myself.right() - StageMorph.prototype.dimensions.x * (myself.isSmallStage ? myself.stageRatio : 1) ); - [stageSizeButton, appModeButton].forEach( + [stageSizeButton, appModeButton, muteSoundsButton].forEach( function (button) { x += padding; button.setCenter(myself.controlBar.center()); @@ -3394,6 +3428,27 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) { } }; +IDE_Morph.prototype.toggleMuteSounds = function (isMuted) { + this.isMuted = isNil(isMuted) ? !this.isMuted : isMuted; + this.controlBar.muteSoundsButton.refresh(); + + /* stage.activeSounds holds all active sounds + * a sprite's .activeSounds holds just its own + * so you have to use the stage to mute + * and the sprite to unmute, because the stage's volume + * overrides the sprite's one. + */ + + if (this.isMuted === false) { + this.stage.unmuteAllSounds(); + this.sprites.asArray().forEach(function (sprt) { + sprt.unmuteAllSounds(); + }); + } else { + this.stage.muteAllSounds(); + } +}; + IDE_Morph.prototype.createNewProject = function () { var myself = this; this.confirm( @@ -459,6 +459,23 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sound', spec: 'stop all sounds' }, + doSetVolume: { + type: 'command', + category: 'sound', + spec: 'set volume to %n %', + defaults: [100] + }, + doChangeVolume: { + type: 'command', + category: 'sound', + spec: 'change volume by %n', + defaults: [-10] + }, + reportVolume: { + type: 'reporter', + category: 'sound', + spec: 'volume' + }, doRest: { type: 'command', category: 'sound', @@ -1345,6 +1362,8 @@ SpriteMorph.prototype.init = function (globals) { this.version = Date.now(); // for observer optimization this.isClone = false; // indicate a "temporary" Scratch-style clone this.cloneOriginName = ''; + this.volume = 100; + this.activeSounds = []; // sprite nesting properties this.parts = []; // not serialized, only anchor (name) @@ -1818,6 +1837,11 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('doPlaySoundUntilDone')); blocks.push(block('doStopAllSounds')); blocks.push('-'); + blocks.push(block('doSetVolume')); + blocks.push(block('doChangeVolume')); + blocks.push(watcherToggle('reportVolume')); + blocks.push(block('reportVolume')); + blocks.push('-'); blocks.push(block('doRest')); blocks.push('-'); blocks.push(block('doPlayNote')); @@ -2645,7 +2669,8 @@ SpriteMorph.prototype.reportCostumes = function () { // SpriteMorph sound management SpriteMorph.prototype.addSound = function (audio, name) { - this.sounds.add(new Sound(audio, name)); + var volume = this.volume; + this.sounds.add(new Sound(audio, name, volume)); }; SpriteMorph.prototype.playSound = function (name) { @@ -2656,7 +2681,18 @@ SpriteMorph.prototype.playSound = function (name) { ), active; if (sound) { + sound.volume = this.volume; active = sound.play(); + + if (stage.muted === true) { + active.volume = 0; + } + + this.activeSounds.push(active); + this.activeSounds = this.activeSounds.filter(function (aud) { + return !aud.ended && !aud.terminated; + }); + if (stage) { stage.activeSounds.push(active); stage.activeSounds = stage.activeSounds.filter(function (aud) { @@ -2667,6 +2703,34 @@ SpriteMorph.prototype.playSound = function (name) { } }; +SpriteMorph.prototype.doSetVolume = function (val) { + var myself = this; + myself.volume = Math.min(Math.max(0, val), 100); + + if (myself.parentThatIsA(StageMorph).muted === true) { + return; + } + + myself.activeSounds.forEach(function (snd) { + snd.volume = myself.volume / 100; // 'audio' objects + }); +}; + +SpriteMorph.prototype.doChangeVolume = function (val) { + this.doSetVolume(this.volume + val); +}; + +SpriteMorph.prototype.reportVolume = function () { + return this.volume; +} + +SpriteMorph.prototype.unmuteAllSounds = function () { + var stage = this.parentThatIsA(StageMorph); + stage.muted = false; + + this.doSetVolume(this.volume); +}; + SpriteMorph.prototype.reportSounds = function () { return this.sounds; }; @@ -4332,6 +4396,8 @@ StageMorph.prototype.init = function (globals) { this.version = Date.now(); // for observers this.isFastTracked = false; this.cloneCount = 0; + this.volume = 100; + this.muted = false; this.timerStart = Date.now(); this.tempo = 60; // bpm @@ -4869,7 +4935,7 @@ StageMorph.prototype.fireGreenFlagEvent = function () { StageMorph.prototype.fireStopAllEvent = function () { var ide = this.parentThatIsA(IDE_Morph); - this.threads.resumeAll(this.stage); + //this.threads.resumeAll(this.stage); // leads to a strange Note bug this.keysPressed = {}; this.threads.stopAll(); this.stopAllActiveSounds(); @@ -5023,6 +5089,11 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doPlaySoundUntilDone')); blocks.push(block('doStopAllSounds')); blocks.push('-'); + blocks.push(block('doSetVolume')); + blocks.push(block('doChangeVolume')); + blocks.push(watcherToggle('reportVolume')); + blocks.push(block('reportVolume')); + blocks.push('-'); blocks.push(block('doRest')); blocks.push('-'); blocks.push(block('doPlayNote')); @@ -5565,6 +5636,15 @@ StageMorph.prototype.addSound StageMorph.prototype.playSound = SpriteMorph.prototype.playSound; +StageMorph.prototype.doSetVolume + = SpriteMorph.prototype.doSetVolume; + +StageMorph.prototype.doChangeVolume + = SpriteMorph.prototype.doChangeVolume; + +StageMorph.prototype.reportVolume + = SpriteMorph.prototype.reportVolume; + StageMorph.prototype.stopAllActiveSounds = function () { this.activeSounds.forEach(function (audio) { audio.pause(); @@ -5579,11 +5659,29 @@ StageMorph.prototype.pauseAllActiveSounds = function () { }; StageMorph.prototype.resumeAllActiveSounds = function () { + var newSounds = []; // remove Sounds that have been played so they do not resume + + this.activeSounds.forEach(function (audio) { + if (audio.ended === false) { + newSounds.push(audio); + audio.play(); + } + }); + + this.activeSounds = newSounds; +}; + +StageMorph.prototype.muteAllSounds = function () { + this.muted = true; + this.activeSounds.forEach(function (audio) { - audio.play(); + audio.volume = 0; }); }; +StageMorph.prototype.unmuteAllSounds + = SpriteMorph.prototype.unmuteAllSounds; + StageMorph.prototype.reportSounds = SpriteMorph.prototype.reportSounds; @@ -6334,9 +6432,10 @@ CostumeEditorMorph.prototype.mouseMove // Sound instance creation -function Sound(audio, name) { +function Sound(audio, name, volume) { this.audio = audio; // mandatory this.name = name || "Sound"; + this.volume = volume || 100; } Sound.prototype.play = function () { @@ -6344,6 +6443,7 @@ Sound.prototype.play = function () { // externally (i.e. by the stage) var aud = document.createElement('audio'); aud.src = this.audio.src; + aud.volume = Math.min(Math.max(0, this.volume), 100) / 100; aud.play(); return aud; }; @@ -6353,7 +6453,8 @@ Sound.prototype.copy = function () { cpy; snd.src = this.audio.src; - cpy = new Sound(snd, this.name ? copy(this.name) : null); + snd.volume = this.volume; + cpy = new Sound(snd, this.name ? copy(this.name) : null, this.volume ? copy(this.volume) : null); return cpy; }; @@ -6367,8 +6468,9 @@ Sound.prototype.toDataURL = function () { // Note instance creation -function Note(pitch) { +function Note(pitch, volume) { this.pitch = pitch === 0 ? 0 : pitch || 69; + this.volume = volume; this.setupContext(); this.oscillator = null; } @@ -6399,12 +6501,13 @@ Note.prototype.setupContext = function () { } Note.prototype.audioContext = new AudioContext(); Note.prototype.gainNode = Note.prototype.audioContext.createGain(); - Note.prototype.gainNode.gain.value = 0.25; // reduce volume by 1/4 }; // Note playing Note.prototype.play = function () { + this.gainNode.gain.value = 0.25 * this.volume / 100; // reduce volume by 1/4 + this.oscillator = this.audioContext.createOscillator(); if (!this.oscillator.start) { this.oscillator.start = this.oscillator.noteOn; @@ -6420,6 +6523,12 @@ Note.prototype.play = function () { this.oscillator.start(0); }; +Note.prototype.setVolume = function (volume) { + this.stop(); + this.volume = volume; + this.play(); +} + Note.prototype.stop = function () { if (this.oscillator) { this.oscillator.stop(0); @@ -448,11 +448,20 @@ Process.prototype.pause = function () { if (this.context && this.context.startTime) { this.pauseOffset = Date.now() - this.context.startTime; } + if (this.context.activeNote) { + this.context.activeNote.stop(); + } }; Process.prototype.resume = function () { this.isPaused = false; this.pauseOffset = null; + if (this.context.activeNote) { + if (this.context.activeNote.oscillator === null) { + // prevents Note from resuming twice + this.context.activeNote.play(); + } + } }; Process.prototype.pauseStep = function () { @@ -1455,7 +1464,7 @@ Process.prototype.doStopAll = function () { if (this.homeContext.receiver) { stage = this.homeContext.receiver.parentThatIsA(StageMorph); if (stage) { - stage.threads.resumeAll(stage); + //stage.threads.resumeAll(stage); // leads to a strange Note bug stage.keysPressed = {}; stage.threads.stopAll(); stage.stopAllActiveSounds(); @@ -2898,18 +2907,32 @@ Process.prototype.doPlayNote = function (pitch, beats) { Process.prototype.doPlayNoteForSecs = function (pitch, secs) { // interpolated + var receiver = this.homeContext.receiver; + var volume = receiver.volume; + var muted = receiver.parentThatIsA(StageMorph).muted; + + if (muted === true) { + volume = 0; + } + if (!this.context.startTime) { this.context.startTime = Date.now(); - this.context.activeNote = new Note(pitch); + this.context.activeNote = new Note(pitch, volume); this.context.activeNote.play(); } + if ((Date.now() - this.context.startTime) >= (secs * 1000)) { if (this.context.activeNote) { this.context.activeNote.stop(); this.context.activeNote = null; } return null; + } else if (this.context.activeNote) { + if (this.context.activeNote.volume !== volume) { + this.context.activeNote.setVolume(volume); + } } + this.pushContext('doYield'); this.pushContext(); }; |
