// Copyright (c) 2014-2017 Walter Bender // Copyright (c) 2015 Yash Khandelwal // // This program is free software; you can redistribute it and/or // modify it under the terms of the The GNU Affero General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. // // You should have received a copy of the GNU Affero General Public // License along with this library; if not, write to the Free Software // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA const DEFAULTVOLUME = 50; const TONEBPM = 240; // Seems to be the default. const TARGETBPM = 90; // What we'd like to use for beats per minute const DEFAULTDELAY = 500; // milleseconds const TURTLESTEP = -1; // Run in step-by-step mode const NOTEDIV = 8; // Number of steps to divide turtle graphics const OSCVOLUMEADJUSTMENT = 1.5 // The oscillator runs hot. We need // to scale back its volume. const NOMICERRORMSG = 'The microphone is not available.'; const NANERRORMSG = 'Not a number.'; const NOSTRINGERRORMSG = 'Not a string.'; const NOBOXERRORMSG = 'Cannot find box'; const NOACTIONERRORMSG = 'Cannot find action.'; const NOINPUTERRORMSG = 'Missing argument.'; const NOSQRTERRORMSG = 'Cannot take square root of negative number.'; const ZERODIVIDEERRORMSG = 'Cannot divide by zero.'; const EMPTYHEAPERRORMSG = 'empty heap.'; const INVALIDPITCH = 'Not a valid pitch name'; const POSNUMBER = 'Argument must be a positive number'; function Logo () { this.canvas = null; this.blocks = null; this.turtles = null; this.stage = null; this.refreshCanvas = null; this.textMsg = null; this.errorMsg = null; this.hideMsgs = null; this.onStopTurtle = null; this.onRunTurtle = null; this.getStageX = null; this.getStageY = null; this.getStageMouseDown = null; this.getCurrentKeyCode = null; this.clearCurrentKeyCode = null; this.meSpeak = null; this.saveLocally = null; this.pitchTimeMatrix = null; this.pitchDrumMatrix = null; this.rhythmRuler = null; this.pitchStaircase = null; this.tempo = null; this.pitchSlider = null; this.modeWidget = null; this.statusMatrix = null; this.evalFlowDict = {}; this.evalArgDict = {}; this.evalParameterDict = {}; this.evalSetterDict = {}; this.evalOnStartList = {}; this.evalOnStopList = {}; this.eventList = {}; this.boxes = {}; this.actions = {}; this.returns = []; this.turtleHeaps = {}; this.invertList = {}; // When we leave a clamp block, we need to dispatch a signal. this.endOfClampSignals = {}; this.time = 0; this.firstNoteTime = null; this.waitTimes = {}; this.turtleDelay = 0; this.sounds = []; this.cameraID = null; this.stopTurtle = false; this.lastKeyCode = null; this.saveTimeout = 0; // Music-related attributes this.notesPlayed = {}; // pitch-drum matrix this.showPitchDrumMatrix = false; this.inPitchDrumMatrix = false; //rhythm-ruler this.inRhythmRuler = false; this.rhythmRulerMeasure = null; this.inPitchStaircase = false; this.inTempo = false; this.inPitchSlider = false; this._currentDrumBlock = null; // pitch-rhythm matrix this.inMatrix = false; this.keySignature = {}; this.tupletRhythms = []; this.addingNotesToTuplet = false; this.drumBlocks = []; this.pitchBlocks = []; this.inNoteBlock = []; this.whichNoteBlock = []; // parameters used by pitch this.transposition = {}; // parameters used by notes this._masterBPM = TARGETBPM; this.defaultBPMFactor = TONEBPM / this._masterBPM; this.beatFactor = {}; this.dotCount = {}; this.noteBeat = {}; this.oscList = {}; this.noteDrums = {}; this.notePitches = {}; this.noteOctaves = {}; this.noteCents = {}; this.noteHertz = {}; this.noteTranspositions = {}; this.noteBeatValues = {}; this.lastNotePlayed = {}; this.noteStatus = {}; this.pitchNumberOffset = 39; // C4 // graphics listeners during note play this.forwardListener = {}; this.rightListener = {}; this.arcListener = {}; // status of note being played // Deprecated (ref lastNotePlayed) this.currentNotes = {}; this.currentOctaves = {}; // parameters used by the note block this.bpm = {}; this.turtleTime = []; this.noteDelay = 0; this.playedNote = {}; this.playedNoteTimes = {}; this.pushedNote = {}; this.duplicateFactor = {}; this.skipFactor = {}; this.skipIndex = {}; this.crescendoDelta = {}; this.crescendoVolume = {}; this.crescendoInitialVolume = {}; this.intervals = {}; this.perfect = {}; this.diminished = {}; this.augmented = {}; this.major = {}; this.minor = {}; this.staccato = {}; this.swing = {}; this.swingTarget = {}; this.swingCarryOver = {}; this.tie = {}; this.tieNote = {}; this.tieCarryOver = {}; this.polyVolume = {}; this.validNote = true; this.drift = {}; this.drumStyle = {}; this.voices = {}; this.backward = {}; this.vibratoIntensity = {}; this.vibratoRate = {}; this.justCounting = {}; // When counting notes or generating lilypond output... this.suppressOutput = {}; // scale factor for turtle graphics embedded in notes this.dispatchFactor = {}; // tuplet this.tuplet = false; this.tupletParams = []; // pitch to drum mapping this.pitchDrumTable = {}; // parameters used by notations this.checkingLilypond = false; this.checkingLilypond = false; this.lilypondNotes = {}; this.lilypondStaging = {}; this.lilypondOutput = getLilypondHeader(); this.runningLilypond = false; this.numerator = 3; this.denominator = 4; if (_THIS_IS_MUSIC_BLOCKS_) { // Load the default synthesizer this.synth = new Synth(); this.synth.loadSynth('poly'); } else { this.turtleOscs = {}; } // Mode widget this._modeBlock = null; // Status matrix this.inStatusMatrix = false; this.updatingStatusMatrix = false; this.statusFields = []; // When running in step-by-step mode, the next command to run is // queued here. this.stepQueue = {}; this.unhighlightStepQueue = {}; // Control points for bezier curves this.cp1x = {}; this.cp1y = {}; this.cp2x = {}; this.cp2y = {}; this.svgOutput = ''; this.svgBackground = true; if (_THIS_IS_MUSIC_BLOCKS_) { this.mic = new Tone.UserMedia(); this.limit = 1024; this.analyser = new Tone.Analyser({ "type" : "waveform", "size" : this.limit }); this.mic.connect(this.analyser); } else { try { this.mic = new p5.AudioIn() } catch (e) { console.log(e); console.log(NOMICERRORMSG); this.mic = null; } } this.setCanvas = function (canvas) { this.canvas = canvas; return this; }; this.setBlocks = function (blocks) { this.blocks = blocks; return this; }; this.setTurtles = function (turtles) { this.turtles = turtles; return this; }; this.setStage = function (stage) { this.stage = stage; return this; }; this.setRefreshCanvas = function (refreshCanvas) { this.refreshCanvas = refreshCanvas; return this; }; this.setTextMsg = function (textMsg) { this.textMsg = textMsg; return this; }; this.setHideMsgs = function (hideMsgs) { this.hideMsgs = hideMsgs; return this; }; this.setErrorMsg = function (errorMsg) { this.errorMsg = errorMsg; return this; }; this.setOnStopTurtle = function (onStopTurtle) { this.onStopTurtle = onStopTurtle; return this; }; this.setOnRunTurtle = function (onRunTurtle) { this.onRunTurtle = onRunTurtle; return this; }; this.setGetStageX = function (getStageX) { this.getStageX = getStageX; return this; }; this.setGetStageY = function (getStageY) { this.getStageY = getStageY; return this; }; this.setGetStageMouseDown = function (getStageMouseDown) { this.getStageMouseDown = getStageMouseDown; return this; }; this.setGetCurrentKeyCode = function (getCurrentKeyCode) { this.getCurrentKeyCode = getCurrentKeyCode; return this; }; this.setClearCurrentKeyCode = function (clearCurrentKeyCode) { this.clearCurrentKeyCode = clearCurrentKeyCode; return this; }; this.setMeSpeak = function (meSpeak) { this.meSpeak = meSpeak; return this; }; this.setSaveLocally = function (saveLocally) { this.saveLocally = saveLocally; return this; }; // Used to pause between each block as the program executes. this.setTurtleDelay = function (turtleDelay) { this.turtleDelay = turtleDelay; this.noteDelay = 0; }; // Used to pause between each note as the program executes. this.setNoteDelay = function (noteDelay) { this.noteDelay = noteDelay; this.turtleDelay = 0; }; this.step = function () { // Take one step for each turtle in excuting Logo commands. for (var turtle in this.stepQueue) { if (this.stepQueue[turtle].length > 0) { if (turtle in this.unhighlightStepQueue && this.unhighlightStepQueue[turtle] != null) { if (this.blocks.visible) { this.blocks.unhighlight(this.unhighlightStepQueue[turtle]); } this.unhighlightStepQueue[turtle] = null; } var blk = this.stepQueue[turtle].pop(); if (blk != null) { this._runFromBlockNow(this, turtle, blk, 0, null); } } } }; this.stepNote = function () { // Step through one note for each turtle in excuting Logo // commands, but run through other blocks at full speed. var tempStepQueue = {}; var notesFinish = {}; var thisNote = {}; var that = this; __stepNote(); function __stepNote() { for (var turtle in that.stepQueue) { // Have we already played a note for this turtle? if (turtle in that.playedNote && that.playedNote[turtle]) { continue; } if (that.stepQueue[turtle].length > 0) { if (turtle in that.unhighlightStepQueue && that.unhighlightStepQueue[turtle] != null) { if (that.blocks.visible) { that.blocks.unhighlight(that.unhighlightStepQueue[turtle]); } that.unhighlightStepQueue[turtle] = null; } var blk = that.stepQueue[turtle].pop(); if (blk != null && blk !== notesFinish[turtle]) { var block = that.blocks.blockList[blk]; if (block.name === 'newnote') { tempStepQueue[turtle] = blk; notesFinish[turtle] = last(block.connections); if (notesFinish[turtle] == null) { // end of flow notesFinish[turtle] = last(that.turtles.turtleList[turtle].queue) && last(that.turtles.turtleList[turtle].queue).blk; // catch case of null - end of project } // that.playedNote[turtle] = true; that.playedNoteTimes[turtle] = that.playedNoteTimes[turtle] || 0; thisNote[turtle] = Math.pow(that.parseArg(that, turtle, block.connections[1], blk, null), -1); that.playedNoteTimes[turtle] += thisNote[turtle]; // Keep track of how long the note played for, so we can go back and play it again if needed } that._runFromBlockNow(that, turtle, blk, 0, null); } else { that.playedNote[turtle] = true; } } } // At this point, some turtles have played notes and others // have not. We need to keep stepping until they all have. var keepGoing = false; for (var turtle in that.stepQueue) { if (that.stepQueue[turtle].length > 0 && !that.playedNote[turtle]) { keepGoing = true; break; } } if (keepGoing) { __stepNote(); // that.step(); } else { var notesArray = []; for (var turtle in that.playedNote) { that.playedNote[turtle] = false; notesArray.push(that.playedNoteTimes[turtle]); } // If some notes are supposed to play for longer, add // them back to the queue var shortestNote = Math.min.apply(null, notesArray); var continueFrom; for (var turtle in that.playedNoteTimes) { if (that.playedNoteTimes[turtle] > shortestNote) { continueFrom = tempStepQueue[turtle]; // Subtract the time, as if we haven't played it yet that.playedNoteTimes[turtle] -= thisNote[turtle]; } else { continueFrom = notesFinish[turtle]; } that._runFromBlock(that, turtle, continueFrom, 0, null); } if (shortestNote === Math.max.apply(null, notesArray)) { that.playedNoteTimes = {}; } } }; }; this.doStopTurtle = function () { // The stop button was pressed. Stop the turtle and clean up a // few odds and ends. this.stopTurtle = true; this.turtles.markAsStopped(); for (var sound in this.sounds) { this.sounds[sound].stop(); } this.sounds = []; if (_THIS_IS_MUSIC_BLOCKS_) { this.synth.stopSound('default'); this.synth.stop(); } if (this.cameraID != null) { doStopVideoCam(this.cameraID, this.setCameraID); } this.onStopTurtle(); this.blocks.bringToTop(); this.stepQueue = {}; this.unhighlightQueue = {}; }; this._clearParameterBlocks = function () { for (var blk in this.blocks.blockList) { if (this.blocks.blockList[blk].parameter) { this.blocks.blockList[blk].text.text = ''; this.blocks.blockList[blk].container.updateCache(); } } this.refreshCanvas(); }; this._updateParameterBlock = function (that, turtle, blk) { // Update the label on parameter blocks. if (this.blocks.blockList[blk].protoblock.parameter) { var name = this.blocks.blockList[blk].name; var value = 0; switch (name) { case 'and': case 'or': case 'not': case 'less': case 'greater': case 'equal': if (this.blocks.blockList[blk].value) { value = _('true'); } else { value = _('false'); } break; case 'random': case 'mod': case 'sqrt': case 'int': case 'plus': case 'minus': case 'multiply': case 'power': value = toFixed2(this.blocks.blockList[blk].value); break; case 'divide': value = this.blocks.blockList[blk].value; break; case 'namedbox': var name = this.blocks.blockList[blk].privateData; if (name in this.boxes) { value = this.boxes[name]; } else { this.errorMsg(NOBOXERRORMSG, blk, name); } break; case 'box': var cblk = this.blocks.blockList[blk].connections[1]; var boxname = this.parseArg(that, turtle, cblk, blk); if (boxname in this.boxes) { value = this.boxes[boxname]; } else { this.errorMsg(NOBOXERRORMSG, blk, boxname); } break; case 'x': value = toFixed2(this.turtles.turtleList[turtle].x); break; case 'y': value = toFixed2(this.turtles.turtleList[turtle].y); break; case 'heading': value = toFixed2(this.turtles.turtleList[turtle].orientation); break; case 'color': case 'hue': value = toFixed2(this.turtles.turtleList[turtle].color); break; case 'shade': value = toFixed2(this.turtles.turtleList[turtle].value); break; case 'grey': value = toFixed2(this.turtles.turtleList[turtle].chroma); break; case 'pensize': value = toFixed2(this.turtles.turtleList[turtle].stroke); break; case 'time': var d = new Date(); value = (d.getTime() - this.time) / 1000; break; case 'mousex': value = toFixed2(this.getStageX()); break; case 'mousey': value = toFixed2(this.getStageY()); break; case 'keyboard': value = this.lastKeyCode; break; case 'loudness': if (that.mic == null) { that.errorMsg(NOMICERRORMSG); value = 0; } else { if (_THIS_IS_TURTLE_BLOCKS) { value = Math.round(that.mic.getLevel() * 1000); } else { var values = that.analyser.analyse(); var sum = 0; for (var k = 0; k < that.limit; k++){ sum += (values[k] * values[k]); } value = Math.round(Math.sqrt(sum / that.limit)); } } break; case 'consonantstepsizeup': if (this.lastNotePlayed[turtle] !== null) { var len = this.lastNotePlayed[turtle][0].length; value = getStepSizeUp(this.keySignature[turtle], this.lastNotePlayed[turtle][0].slice(0, len - 1)); } else { value = getStepSizeUp(this.keySignature[turtle], 'A'); } break; case 'consonantstepsizedown': if (this.lastNotePlayed[turtle] !== null) { var len = this.lastNotePlayed[turtle][0].length; value = getStepSizeDown(this.keySignature[turtle], this.lastNotePlayed[turtle][0].slice(0, len - 1)); } else { value = getStepSizeDown(this.keySignature[turtle], 'A'); } break; case 'transpositionfactor': value = this.transposition[turtle]; break; case 'staccatofactor': value = last(this.staccato[turtle]); break; case 'slurfactor': value = -last(this.staccato[turtle]); break; case 'beatfactor': value = this.beatFactor[turtle]; break; case 'elapsednotes': value = this.notesPlayed[turtle]; break; case 'duplicatefactor': value = this.duplicateFactor[turtle]; break; case 'skipfactor': value = this.skipFactor[turtle]; break; case 'notevolumefactor': // FIX ME: bias and scaling value = last(this.polyVolume[turtle]); break; case 'turtlepitch': if (this.lastNotePlayed[turtle] !== null) { var len = this.lastNotePlayed[turtle][0].length; value = pitchToNumber(this.lastNotePlayed[turtle][0].slice(0, len - 1), parseInt(this.lastNotePlayed[turtle][0].slice(len - 1)), this.keySignature[turtle]) - that.pitchNumberOffset; } else { console.log('Could not find a note for turtle ' + turtle); value = pitchToNumber('A', 4, this.keySignature[turtle]) - that.pitchNumberOffset; } break; // Deprecated case 'currentnote': value = this.currentNotes[turtle]; break; // Deprecated case 'currentoctave': value = this.currentoctave[turtle]; break; case 'bpmfactor': if (this.bpm[turtle].length > 0) { value = last(this.bpm[turtle]); } else { value = this._masterBPM; } break; default: if (name in this.evalParameterDict) { eval(this.evalParameterDict[name]); } else { return; } break; } if (typeof(value) === 'string') { if (value.length > 6) { value = value.substr(0, 5) + '...'; } this.blocks.blockList[blk].text.text = value; } else if (name === 'divide') { this.blocks.blockList[blk].text.text = mixedNumber(value); } this.blocks.blockList[blk].container.updateCache(); this.refreshCanvas(); } }; this.runLogoCommands = function (startHere, env) { // Save the state before running. this.saveLocally(); for (var arg in this.evalOnStartList) { eval(this.evalOnStartList[arg]); } this.stopTurtle = false; this.blocks.unhighlightAll(); this.blocks.bringToTop(); // Draw under blocks. this.hideMsgs(); // We run the Logo commands here. var d = new Date(); this.time = d.getTime(); this.firstNoteTime = null; // Ensure we have at least one turtle. if (this.turtles.turtleList.length === 0) { this.turtles.add(null); } for (var turtle in this.forwardListener) { for (var b in this.forwardListener[turtle]) { for (var i = 0; i < this.forwardListener[turtle][b].length; i++) { this.stage.removeEventListener('_forward_' + turtle, this.forwardListener[turtle][b][i], false); } } } for (var turtle in this.rightListener) { for (var b in this.rightListener[turtle]) { for (var i = 0; i < this.rightListener[turtle][b].length; i++) { this.stage.removeEventListener('_right_' + turtle, this.rightListener[turtle][b][i], false); } } } for (var turtle in this.arcListener) { for (var b in this.arcListener[turtle]) { for (var i = 0; i < this.arcListener[turtle][b].length; i++) { this.stage.removeEventListener('_arc_' + turtle, this.arcListener[turtle][b][i], false); } } } this._masterBPM = TARGETBPM; this.defaultBPMFactor = TONEBPM / this._masterBPM; // Each turtle needs to keep its own wait time and music // states. for (var turtle = 0; turtle < this.turtles.turtleList.length; turtle++) { this.turtleTime[turtle] = 0; this.waitTimes[turtle] = 0; this.endOfClampSignals[turtle] = {}; this.cp1x[turtle] = 0; this.cp1y[turtle] = 100; this.cp2x[turtle] = 100; this.cp2y[turtle] = 100; this.inNoteBlock[turtle] = 0; this.transposition[turtle] = 0; this.noteBeat[turtle] = []; this.noteCents[turtle] = []; this.noteHertz[turtle] = []; this.lastNotePlayed[turtle] = null; this.noteStatus[turtle] = null; this.noteDrums[turtle] = []; this.notePitches[turtle] = []; this.noteOctaves[turtle] = []; this.currentNotes[turtle] = 'G'; this.currentOctaves[turtle] = 4; this.noteTranspositions[turtle] = []; this.noteBeatValues[turtle] = []; this.forwardListener[turtle] = {}; this.rightListener[turtle] = {}; this.arcListener[turtle] = {}; this.beatFactor[turtle] = 1; this.dotCount[turtle] = 0; this.invertList[turtle] = []; this.duplicateFactor[turtle] = 1; this.skipFactor[turtle] = 1; this.skipIndex[turtle] = 0; this.notesPlayed[turtle] = 0; this.keySignature[turtle] = 'C ' + _('major'); this.pushedNote[turtle] = false; this.polyVolume[turtle] = [DEFAULTVOLUME]; this.oscList[turtle] = []; this.bpm[turtle] = []; this.crescendoDelta[turtle] = []; this.crescendoInitialVolume[turtle] = []; this.crescendoVolume[turtle] = []; this.intervals[turtle] = []; this.perfect[turtle] = []; this.diminished[turtle] = []; this.augmented[turtle] = []; this.major[turtle] = []; this.minor[turtle] = []; this.staccato[turtle] = []; this.swing[turtle] = []; this.swingTarget[turtle] = []; this.swingCarryOver[turtle] = 0; this.tie[turtle] = false; this.tieNote[turtle] = []; this.tieCarryOver[turtle] = 0; this.drift[turtle] = 0; this.drumStyle[turtle] = []; this.voices[turtle] = []; this.pitchDrumTable[turtle] = {}; this.backward[turtle] = []; this.vibratoIntensity[turtle] = []; this.vibratoRate[turtle] = []; this.dispatchFactor[turtle] = 1; this.justCounting[turtle] = false; this.suppressOutput[turtle] = this.runningLilypond; } this.pitchNumberOffset = 39; // C4 if (!this.suppressOutput[turtle]) { this._setSynthVolume(DEFAULTVOLUME, Math.max(this.turtles.turtleList.length - 1), 0); } this.inPitchDrumMatrix = false; this.inMatrix = false; this.inRhythmRuler = false; this.rhythmRulerMeasure = null; this._currentDrumBlock = null; this.inStatusMatrix = false; this.pitchBlocks = []; this.drumBlocks = []; this.tuplet = false; this._modeBlock = null; // Remove any listeners that might be still active for (var turtle = 0; turtle < this.turtles.turtleList.length; turtle++) { for (var listener in this.turtles.turtleList[turtle].listeners) { this.stage.removeEventListener(listener, this.turtles.turtleList[turtle].listeners[listener], false); } this.turtles.turtleList[turtle].listeners = {}; } // First we need to reconcile the values in all the value // blocks with their associated textareas. // FIXME: Do we still need this check??? for (var blk = 0; blk < this.blocks.blockList.length; blk++) { if (this.blocks.blockList[blk].label != null) { if (this.blocks.blockList[blk].labelattr != null && this.blocks.blockList[blk].labelattr.value !== '♮') { this.blocks.blockList[blk].value = this.blocks.blockList[blk].label.value + this.blocks.blockList[blk].labelattr.value; } else { this.blocks.blockList[blk].value = this.blocks.blockList[blk].label.value; } } } // Init the graphic state. for (var turtle = 0; turtle < this.turtles.turtleList.length; turtle++) { this.turtles.turtleList[turtle].container.x = this.turtles.turtleX2screenX(this.turtles.turtleList[turtle].x); this.turtles.turtleList[turtle].container.y = this.turtles.turtleY2screenY(this.turtles.turtleList[turtle].y); } // Set up status block if (docById('statusDiv').style.visibility === 'visible') { this.statusMatrix.init(this); } // Execute turtle code here... Find the start block (or the // top of each stack) and build a list of all of the named // action stacks. var startBlocks = []; this.blocks.findStacks(); this.actions = {}; for (var blk = 0; blk < this.blocks.stackList.length; blk++) { if (this.blocks.blockList[this.blocks.stackList[blk]].name === 'start' || this.blocks.blockList[this.blocks.stackList[blk]].name === 'drum') { // Don't start on a start block in the trash. if (!this.blocks.blockList[this.blocks.stackList[blk]].trash) { // Don't start on a start block with no connections. if (this.blocks.blockList[this.blocks.stackList[blk]].connections[1] != null) { startBlocks.push(this.blocks.stackList[blk]); } } } else if (this.blocks.blockList[this.blocks.stackList[blk]].name === 'action') { // Does the action stack have a name? var c = this.blocks.blockList[this.blocks.stackList[blk]].connections[1]; var b = this.blocks.blockList[this.blocks.stackList[blk]].connections[2]; if (c != null && b != null) { // Don't use an action block in the trash. if (!this.blocks.blockList[this.blocks.stackList[blk]].trash) { this.actions[this.blocks.blockList[c].value] = b; } } } } this.svgOutput = ''; this.svgBackground = true; this.parentFlowQueue = {}; this.unhightlightQueue = {}; this.parameterQueue = {}; if (this.turtleDelay === 0) { // Don't update parameters when running full speed. this._clearParameterBlocks(); } this.onRunTurtle(); // And mark all turtles as not running. for (var turtle = 0; turtle < this.turtles.turtleList.length; turtle++) { this.turtles.turtleList[turtle].running = false; } // (2) Execute the stack. // A bit complicated because we have lots of corner cases: if (startHere != null) { // console.log('startHere is ' + this.blocks.blockList[startHere].name); // If a block to start from was passed, find its // associated turtle, i.e., which turtle should we use? var turtle = 0; while(this.blocks.turtles.turtleList[turtle].trash && turtle < this.turtles.turtleList.length) { turtle += 1; } if (this.blocks.blockList[startHere].name === 'start' || this.blocks.blockList[startHere].name === 'drum') { var turtle = this.blocks.blockList[startHere].value; // console.log('starting on start with turtle ' + turtle); // } else { // console.log('starting on ' + this.blocks.blockList[startHere].name + ' with turtle ' + turtle); } this.turtles.turtleList[turtle].queue = []; this.parentFlowQueue[turtle] = []; this.unhightlightQueue[turtle] = []; this.parameterQueue[turtle] = []; this.turtles.turtleList[turtle].running = true; this._runFromBlock(this, turtle, startHere, 0, env); } else if (startBlocks.length > 0) { // If there are start blocks, run them all. for (var b = 0; b < startBlocks.length; b++) { var turtle = this.blocks.blockList[startBlocks[b]].value; this.turtles.turtleList[turtle].queue = []; this.parentFlowQueue[turtle] = []; this.unhightlightQueue[turtle] = []; this.parameterQueue[turtle] = []; if (!this.turtles.turtleList[turtle].trash) { this.turtles.turtleList[turtle].running = true; this._runFromBlock(this, turtle, startBlocks[b], 0, env); } } } else { // console.log('nothing to run'); if (this.suppressOutput[turtle]) { this.errorMsg(NOACTIONERRORMSG, null, _('start')); this.suppressOutput[turtle] = false; this.checkingLilypond = false; // Reset cursor. document.body.style.cursor = 'default'; } } this.refreshCanvas(); }; this._runFromBlock = function (that, turtle, blk, isflow, receivedArg) { if (blk == null) { return; } var delay = that.turtleDelay + that.waitTimes[turtle]; that.waitTimes[turtle] = 0; if (!that.stopTurtle) { if (that.turtleDelay === TURTLESTEP) { // Step mode if (!(turtle in that.stepQueue)) { that.stepQueue[turtle] = []; } that.stepQueue[turtle].push(blk); } else { setTimeout(function () { that._runFromBlockNow(that, turtle, blk, isflow, receivedArg); }, delay); } } }; this._blockSetter = function (blk, value, turtle) { var turtleObj = this.turtles.turtleList[turtle]; switch (this.blocks.blockList[blk].name) { case 'x': turtleObj.doSetXY(value, turtleObj.x); break; case 'y': turtleObj.doSetXY(turtleObj.y, value); break; case 'heading': turtleObj.doSetHeading(value); break; case 'color': turtleObj.doSetColor(value); break; case 'shade': turtleObj.doSetValue(value); break; case 'grey': turtleObj.doSetChroma(value); break; case 'pensize': turtleObj.doSetPensize(value); break; case 'namedbox': var name = this.blocks.blockList[blk].privateData; if (name in this.boxes) { this.boxes[name] = value; } else { this.errorMsg(NOBOXERRORMSG, blk, name); } break; case 'box': var cblk = this.blocks.blockList[blk].connections[1]; var name = this.parseArg(this, turtle, cblk, blk); if (name in this.boxes) { this.boxes[name] = value; } else { this.errorMsg(NOBOXERRORMSG, blk, name); } break; case 'bpmfactor': var len = this.bpm[turtle].length; if (len > 0) { this.bpm[turtle][len - 1] = value; } break; case 'transpositionfactor': var len = this.transposition[turtle].length; if (len > 0) { this.transposition[turtle][len - 1] = value; } break; case 'staccatofactor': var len = this.staccato[turtle].length; if (len > 0) { this.staccato[turtle][len - 1] = value; } break; case 'slurfactor': // Slur is stored as a negative staccato. var len = this.staccato[turtle].length; if (len > 0) { this.staccato[turtle][len - 1] = -value; } break; case 'beatfactor': this.beatFactor[turtle] = value; break; case 'duplicatefactor': var len = this.duplicateFactor[turtle].length; if (len > 0) { this.duplicateFactor[turtle][len - 1] = value; } break; case 'skipfactor': var len = this.skipFactor[turtle].length; if (len > 0) { this.skipFactor[turtle][len - 1] = value; } break; case 'turtlepitch': var obj = numberToPitch(value + this.pitchNumberOffset); this.lastNotePlayed[turtle] = [obj[0]+obj[1], this.lastNotePlayed[turtle][1]]; break; // Deprecated case 'currentnote': // A bit ugly because the setter call added the value // to the current note. var len = this.currentNotes[turtle].length; value = parseInt(value.slice(len)); var newNoteObj = getNote(this.currentNotes[turtle], this.currentOctaves[turtle], value, this.keySignature[turtle]); this.currentNotes[turtle] = newNoteObj[0]; this.currentOctaves[turtle] = newNoteObj[1]; break; // Deprecated case 'currentoctave': this.currentOctaves[turtle] = Math.round(value); if (this.currentOctaves[turtle] < 1) { this.currentOctaves[turtle] = 1; } break; case 'notevolumefactor': var len = this.transposition[turtle].length; this.polyVolume[turtle][len - 1] = value; this._setSynthVolume(value, turtle); break; default: if (this.blocks.blockList[blk].name in this.evalSetterDict) { eval(this.evalSetterDict[this.blocks.blockList[blk].name]); break; } this.errorMsg(_('Block does not support incrementing.'), blk); } }; this._runFromBlockNow = function (that, turtle, blk, isflow, receivedArg, queueStart) { // Run a stack of blocks, beginning with blk. // Sometimes we don't want to unwind the entire queue. if (queueStart === undefined) { queueStart = 0; } // (1) Evaluate any arguments (beginning with connection[1]); var args = []; if (that.blocks.blockList[blk].protoblock.args > 0) { for (var i = 1; i < that.blocks.blockList[blk].protoblock.args + 1; i++) { if (that.blocks.blockList[blk].protoblock.dockTypes[i] === 'in' && that.blocks.blockList[blk].connections[i] == null){ console.log('skipping null inflow args'); } else { args.push(that.parseArg(that, turtle, that.blocks.blockList[blk].connections[i], blk, receivedArg)); } } } // (2) Run function associated with the block; if (that.blocks.blockList[blk].isValueBlock()) { var nextFlow = null; } else { // All flow blocks have a nextFlow, but it can be null // (i.e., end of a flow). if (that.backward[turtle].length > 0) { // We only run backwards in the "first generation" children. if (that.blocks.blockList[last(that.backward[turtle])].name === 'backward') { var c = 1; } else { var c = 2; } if (!that.blocks.sameGeneration(that.blocks.blockList[last(that.backward[turtle])].connections[c], blk)) { var nextFlow = last(that.blocks.blockList[blk].connections); } else { var nextFlow = that.blocks.blockList[blk].connections[0]; if (that.blocks.blockList[nextFlow].name === 'action' || that.blocks.blockList[nextFlow].name === 'backward') { nextFlow = null; } else { if (!that.blocks.sameGeneration(that.blocks.blockList[last(that.backward[turtle])].connections[c], nextFlow)) { var nextFlow = last(that.blocks.blockList[blk].connections); } else { var nextFlow = that.blocks.blockList[blk].connections[0]; } } } } else { var nextFlow = last(that.blocks.blockList[blk].connections); } if (nextFlow === -1) { nextFlow = null; } var queueBlock = new Queue(nextFlow, 1, blk, receivedArg); if (nextFlow != null) { // This could be the last block that.turtles.turtleList[turtle].queue.push(queueBlock); } } // Some flow blocks have childflows, e.g., repeat. var childFlow = null; var childFlowCount = 0; var actionArgs = []; if (that.blocks.visible) { that.blocks.highlight(blk, false); } switch (that.blocks.blockList[blk].name) { case 'dispatch': // Dispatch an event. if (args.length === 1) { // If the event is not in the event list, add it. if (!(args[0] in that.eventList)) { var event = new Event(args[0]); that.eventList[args[0]] = event; } that.stage.dispatchEvent(args[0]); } break; case 'listen': if (args.length === 2) { if (!(args[1] in that.actions)) { that.errorMsg(NOACTIONERRORMSG, blk, args[1]); that.stopTurtle = true; } else { var __listener = function (event) { if (that.turtles.turtleList[turtle].running) { var queueBlock = new Queue(that.actions[args[1]], 1, blk); that.parentFlowQueue[turtle].push(blk); that.turtles.turtleList[turtle].queue.push(queueBlock); } else { // Since the turtle has stopped // running, we need to run the stack // from here. if (isflow) { that._runFromBlockNow(that, turtle, that.actions[args[1]], isflow, receivedArg); } else { that._runFromBlock(that, turtle, that.actions[args[1]], isflow, receivedArg); } } }; // If there is already a listener, remove it // before adding the new one. that._setListener(turtle, args[0], __listener); } } break; case 'start': case 'drum': if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } break; case 'nameddo': var name = that.blocks.blockList[blk].privateData; if (name in that.actions) { if (!that.justCounting[turtle]) { lilypondLineBreak(that, turtle); } if (that.backward[turtle].length > 0) { childFlow = that.blocks.findBottomBlock(that.actions[name]); var actionBlk = that.blocks.findTopBlock(that.actions[name]); that.backward[turtle].push(actionBlk); var listenerName = '_backward_action_' + turtle + '_' + blk; var nextBlock = this.blocks.blockList[actionBlk].connections[2]; if (nextBlock == null) { that.backward[turtle].pop(); } else { that.endOfClampSignals[turtle][nextBlock] = [listenerName]; } var __listener = function (event) { that.backward[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { childFlow = that.actions[name]; } childFlowCount = 1; } else { that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; // If we clicked on an action block, treat it like a do // block. case 'action': case 'do': if (args.length > 0) { if (args[0] in that.actions) { if (!that.justCounting[turtle]) { lilypondLineBreak(that, turtle); } childFlow = that.actions[args[0]]; childFlowCount = 1; } else { console.log('action ' + args[0] + ' not found'); that.errorMsg(NOACTIONERRORMSG, blk, args[0]); that.stopTurtle = true; } } break; case 'nameddoArg': var name = that.blocks.blockList[blk].privateData; while(actionArgs.length > 0) { actionArgs.pop(); } if (that.blocks.blockList[blk].argClampSlots.length > 0) { for (var i = 0; i < that.blocks.blockList[blk].argClampSlots.length; i++){ var t = (that.parseArg(that, turtle, that.blocks.blockList[blk].connections[i + 1], blk, receivedArg)); actionArgs.push(t); } } if (name in that.actions) { if (!that.justCounting[turtle]) { lilypondLineBreak(that, turtle); } if (that.backward[turtle].length > 0) { childFlow = that.blocks.findBottomBlock(that.actions[name]); var actionBlk = that.blocks.findTopBlock(that.actions[name]); that.backward[turtle].push(actionBlk); var listenerName = '_backward_action_' + turtle + '_' + blk; var nextBlock = this.blocks.blockList[actionBlk].connections[2]; if (nextBlock == null) { that.backward[turtle].pop(); } else { that.endOfClampSignals[turtle][nextBlock] = [listenerName]; } var __listener = function (event) { that.backward[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { childFlow = that.actions[name] } childFlowCount = 1; } else{ that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; case 'doArg': while(actionArgs.length > 0) { actionArgs.pop(); } if (that.blocks.blockList[blk].argClampSlots.length > 0) { for (var i = 0; i < that.blocks.blockList[blk].argClampSlots.length; i++){ var t = (that.parseArg(that, turtle, that.blocks.blockList[blk].connections[i + 2], blk, receivedArg)); actionArgs.push(t); } } if (args.length >= 1) { if (args[0] in that.actions) { if (!that.justCounting[turtle]) { lilypondLineBreak(that, turtle); } actionName = args[0]; childFlow = that.actions[args[0]]; childFlowCount = 1; } else { that.errorMsg(NOACTIONERRORMSG, blk, args[0]); that.stopTurtle = true; } } break; case 'forever': if (args.length === 1) { childFlow = args[0]; // If we are running in non-interactive mode, we // need to put a bounds on "forever". if (that.suppressOutput[turtle]) { childFlowCount = 10; } else { childFlowCount = -1; } } break; case 'hidden': case 'hiddennoflow': // Hidden block is used at end of clamps and actions to // trigger listeners. break; case 'break': that._doBreak(turtle); // Since we pop the queue, we need to unhighlight our // parent. var parentBlk = that.blocks.blockList[blk].connections[0]; if (parentBlk != null) { that.unhightlightQueue[turtle].push(parentBlk); } break; case 'wait': if (args.length === 1) { that._doWait(turtle, args[0]); } break; case 'print': if (!that.inStatusMatrix) { if (args.length === 1) { if (args[0] !== null) { that.textMsg(args[0].toString()); } } } break; case 'speak': if (args.length === 1) { if (that.meSpeak) { var text = args[0]; var new_text = ""; for (var i = 0; i < text.length; i++){ if ((text[i] >= 'a' && text[i] <= 'z') || (text[i] >= 'A' && text[i] <= 'Z') || text[i] === ',' || text[i] === '.' || text[i] === ' ') new_text += text[i]; } that.meSpeak.speak(new_text); } } break; case 'repeat': if (args.length === 2) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (args[0] <= 0) { that.errorMsg(POSNUMBER,blk); } else { childFlow = args[1]; childFlowCount = Math.floor(args[0]); } } break; case 'clamp': if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } break; case 'until': // Similar to 'while' if (args.length === 2) { // Queue the child flow. childFlow = args[1]; childFlowCount = 1; if (!args[0]) { // We will add the outflow of the until block // each time through, so we pop it off so as // to not accumulate multiple copies. var queueLength = that.turtles.turtleList[turtle].queue.length; if (queueLength > 0) { if (that.turtles.turtleList[turtle].queue[queueLength - 1].parentBlk === blk) { that.turtles.turtleList[turtle].queue.pop(); } } // Requeue. var parentBlk = that.blocks.blockList[blk].connections[0]; var queueBlock = new Queue(blk, 1, parentBlk); that.parentFlowQueue[turtle].push(parentBlk); that.turtles.turtleList[turtle].queue.push(queueBlock); } else { // Since an until block was requeued each // time, we need to flush the queue of all but // the last one, otherwise the child of the // until block is executed multiple times. var queueLength = that.turtles.turtleList[turtle].queue.length; for (var i = queueLength - 1; i > 0; i--) { if (that.turtles.turtleList[turtle].queue[i].parentBlk === blk) { that.turtles.turtleList[turtle].queue.pop(); } } } } break; case 'waitFor': if (args.length === 1) { if (!args[0]) { // Requeue. var parentBlk = that.blocks.blockList[blk].connections[0]; var queueBlock = new Queue(blk, 1, parentBlk); that.parentFlowQueue[turtle].push(parentBlk); that.turtles.turtleList[turtle].queue.push(queueBlock); that._doWait(0.05); } else { // Since a wait for block was requeued each // time, we need to flush the queue of all but // the last one, otherwise the child of the // while block is executed multiple times. var queueLength = that.turtles.turtleList[turtle].queue.length; for (var i = queueLength - 1; i > 0; i--) { if (that.turtles.turtleList[turtle].queue[i].parentBlk === blk) { that.turtles.turtleList[turtle].queue.pop(); } } } } break; case 'if': if (args.length === 2) { if (args[0]) { childFlow = args[1]; childFlowCount = 1; } } break; case 'ifthenelse': if (args.length === 3) { if (args[0]) { childFlow = args[1]; childFlowCount = 1; } else { childFlow = args[2]; childFlowCount = 1; } } break; case 'while': // While is tricky because we need to recalculate // args[0] each time, so we requeue the While block // itself. if (args.length === 2) { if (args[0]) { // We will add the outflow of the while block // each time through, so we pop it off so as // to not accumulate multiple copies. var queueLength = that.turtles.turtleList[turtle].queue.length; if (queueLength > 0) { if (that.turtles.turtleList[turtle].queue[queueLength - 1].parentBlk === blk) { that.turtles.turtleList[turtle].queue.pop(); } } var parentBlk = that.blocks.blockList[blk].connections[0]; var queueBlock = new Queue(blk, 1, parentBlk); that.parentFlowQueue[turtle].push(parentBlk); that.turtles.turtleList[turtle].queue.push(queueBlock); // and queue the interior child flow. childFlow = args[1]; childFlowCount = 1; } else { // Since a while block was requeued each time, // we need to flush the queue of all but the // last one, otherwise the child of the while // block is executed multiple times. var queueLength = that.turtles.turtleList[turtle].queue.length; for (var i = queueLength - 1; i > 0; i--) { if (that.turtles.turtleList[turtle].queue[i].parentBlk === blk) { // if (that.turtles.turtleList[turtle].queue[i].blk === blk) { that.turtles.turtleList[turtle].queue.pop(); } } } } break; case 'storein': if (args.length === 2) { that.boxes[args[0]] = args[1]; } break; case 'incrementOne': var i = 1; case 'increment': // If the 2nd arg is not set, default to 1. if (args.length === 2) { var i = args[1]; } if (args.length >= 1) { var settingBlk = that.blocks.blockList[blk].connections[1]; that._blockSetter(settingBlk, args[0] + i, turtle); } break; case 'clear': that.svgBackground = true; that.turtles.turtleList[turtle].doClear(true, true); break; case 'setxy': if (args.length === 2) { if (typeof(args[0]) === 'string' || typeof(args[1]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push([args[0], args[1]]); } else { that.turtles.turtleList[turtle].doSetXY(args[0], args[1]); } } break; case 'arc': if (args.length === 2) { if (typeof(args[0]) === 'string' || typeof(args[1]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push([args[0], args[1]]); } else if (that.inNoteBlock[turtle] > 0) { if (!that.suppressOutput[turtle]) { var delta = args[0] / NOTEDIV; var listenerName = '_arc_' + turtle + '_' + that.whichNoteBlock[turtle]; var __listener = function (event) { that.turtles.turtleList[turtle].doArc(delta * that.dispatchFactor[turtle], args[1]); }; if (that.whichNoteBlock[turtle] in that.arcListener[turtle]) { that.arcListener[turtle][that.whichNoteBlock[turtle]].push(__listener); } else { that.arcListener[turtle][that.whichNoteBlock[turtle]] = [__listener]; } that.stage.addEventListener(listenerName, __listener, false); } } else { that.turtles.turtleList[turtle].doArc(args[0], args[1]); } } break; case 'bezier': if (args.length === 2) { if (typeof(args[0]) === 'string' || typeof(args[1]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else { that.turtles.turtleList[turtle].doBezier(that.cp1x[turtle], that.cp1y[turtle], that.cp2x[turtle], that.cp2y[turtle], args[0], args[1]); } } break; case 'controlpoint1': if (args.length === 2) { if (typeof(args[0]) === 'string' || typeof(args[1]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else { that.cp1x[turtle] = args[0]; that.cp1y[turtle] = args[1]; } } break; case 'controlpoint2': if (args.length === 2) { if (typeof(args[0]) === 'string' || typeof(args[1]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else { that.cp2x[turtle] = args[0]; that.cp2y[turtle] = args[1]; } } break; case 'return': if (args.length === 1) { that.returns.push(args[0]); } break; case 'returnToUrl': var URL = window.location.href; var urlParts; var outurl; if (URL.indexOf('?') > 0) { var urlParts = URL.split('?'); if (urlParts[1].indexOf('&') >0) { var newUrlParts = urlParts[1].split('&'); for (var i = 0; i < newUrlParts.length; i++) { if (newUrlParts[i].indexOf('=') > 0) { var tempargs = newUrlParts[i].split('='); switch (tempargs[0].toLowerCase()) { case 'outurl': outurl = tempargs[1]; break; } } } } } if (args.length === 1) { var jsonRet = {}; jsonRet['result'] = args[0]; var json= JSON.stringify(jsonRet); var xmlHttp = new XMLHttpRequest(); xmlHttp.open('POST',outurl, true); // Call a function when the state changes. xmlHttp.onreadystatechange = function () { if(xmlHttp.readyState === 4 && xmlHttp.status === 200) { alert(xmlHttp.responseText); } }; xmlHttp.send(json); } break; case 'forward': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inNoteBlock[turtle] > 0) { if (!that.suppressOutput[turtle]) { var dist = args[0] / NOTEDIV; var listenerName = '_forward_' + turtle + '_' + that.whichNoteBlock[turtle]; var __listener = function (event) { that.turtles.turtleList[turtle].doForward(dist * that.dispatchFactor[turtle]); }; if (that.whichNoteBlock[turtle] in that.forwardListener[turtle]) { that.forwardListener[turtle][that.whichNoteBlock[turtle]].push(__listener); } else { that.forwardListener[turtle][that.whichNoteBlock[turtle]] = [__listener]; } that.stage.addEventListener(listenerName, __listener, false); } } else { that.turtles.turtleList[turtle].doForward(args[0]); } } break; case 'back': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inNoteBlock[turtle] > 0) { if (!that.suppressOutput[turtle]) { var dist = -args[0] / NOTEDIV; var listenerName = '_forward_' + turtle + '_' + that.whichNoteBlock[turtle]; var __listener = function (event) { that.turtles.turtleList[turtle].doForward(dist * that.dispatchFactor[turtle]); }; if (that.whichNoteBlock[turtle] in that.forwardListener[turtle]) { that.forwardListener[turtle][that.whichNoteBlock[turtle]].push(__listener); } else { that.forwardListener[turtle][that.whichNoteBlock[turtle]] = [__listener]; } that.stage.addEventListener(listenerName, __listener, false); } } else { that.turtles.turtleList[turtle].doForward(-args[0]); } } break; case 'right': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inNoteBlock[turtle] > 0) { if (!that.suppressOutput[turtle]) { var delta = args[0] / NOTEDIV; var listenerName = '_right_' + turtle + '_' + that.whichNoteBlock[turtle]; var __listener = function (event) { that.turtles.turtleList[turtle].doRight(delta * that.dispatchFactor[turtle]); }; if (that.whichNoteBlock[turtle] in that.rightListener[turtle]) { that.rightListener[turtle][that.whichNoteBlock[turtle]].push(__listener); } else { that.rightListener[turtle][that.whichNoteBlock[turtle]] = [__listener]; } that.stage.addEventListener(listenerName, __listener, false); } } else { that.turtles.turtleList[turtle].doRight(args[0]); } } break; case 'left': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inNoteBlock[turtle] > 0) { if (!that.suppressOutput[turtle]) { var delta = -args[0] / NOTEDIV; var listenerName = '_right_' + turtle + '_' + that.whichNoteBlock[turtle]; var __listener = function (event) { that.turtles.turtleList[turtle].doRight(delta * that.dispatchFactor[turtle]); }; if (that.whichNoteBlock[turtle] in that.rightListener[turtle]) { that.rightListener[turtle][that.whichNoteBlock[turtle]].push(__listener); } else { that.rightListener[turtle][that.whichNoteBlock[turtle]] = [__listener]; } that.stage.addEventListener(listenerName, __listener, false); } } else { that.turtles.turtleList[turtle].doRight(-args[0]); } } break; case 'setheading': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetHeading(args[0]); } } break; case 'show': if (args.length === 2) { if (typeof(args[1]) === 'string') { var len = args[1].length; if (len === 14 && args[1].substr(0, 14) === CAMERAVALUE) { doUseCamera(args, that.turtles, turtle, false, that.cameraID, that.setCameraID, that.errorMsg); } else if (len === 13 && args[1].substr(0, 13) === VIDEOVALUE) { doUseCamera(args, that.turtles, turtle, true, that.cameraID, that.setCameraID, that.errorMsg); } else if (len > 10 && args[1].substr(0, 10) === 'data:image') { that.turtles.turtleList[turtle].doShowImage(args[0], args[1]); } else if (len > 8 && args[1].substr(0, 8) === 'https://') { that.turtles.turtleList[turtle].doShowURL(args[0], args[1]); } else if (len > 7 && args[1].substr(0, 7) === 'http://') { that.turtles.turtleList[turtle].doShowURL(args[0], args[1]); } else if (len > 7 && args[1].substr(0, 7) === 'file://') { that.turtles.turtleList[turtle].doShowURL(args[0], args[1]); } else { that.turtles.turtleList[turtle].doShowText(args[0], args[1]); } } else if (typeof(args[1]) === 'object' && that.blocks.blockList[that.blocks.blockList[blk].connections[2]].name === 'loadFile') { if (args[1]) { that.turtles.turtleList[turtle].doShowText(args[0], args[1][1]); } else { that.errorMsg(_('You must select a file.')); } } else { that.turtles.turtleList[turtle].doShowText(args[0], args[1]); } } break; case 'turtleshell': if (args.length === 2) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else { that.turtles.turtleList[turtle].doTurtleShell(args[0], args[1]); } } break; case 'setturtlename': var foundTargetTurtle = false; if (args.length === 2) { for (var i = 0; i < that.turtles.turtleList.length; i++) { if (that.turtles.turtleList[i].name === args[0]) { that.turtles.turtleList[i].rename(args[1]); foundTargetTurtle = true; break; } } } if (!foundTargetTurtle) { that.errorMsg('Could not find turtle ' + args[0], blk); } break; case 'startTurtle': var targetTurtle = that._getTargetTurtle(args); if (targetTurtle == null) { that.errorMsg('Cannot find turtle: ' + args[0], blk) } else { if (that.turtles.turtleList[targetTurtle].running) { that.errorMsg('Turtle is already running.', blk); break; } that.turtles.turtleList[targetTurtle].queue = []; that.turtles.turtleList[targetTurtle].running = true; that.parentFlowQueue[targetTurtle] = []; that.unhightlightQueue[targetTurtle] = []; that.parameterQueue[targetTurtle] = []; // Find the start block associated with this turtle. var foundStartBlock = false; for (var i = 0; i < that.blocks.blockList.length; i++) { if (that.blocks.blockList[i] === that.turtles.turtleList[targetTurtle].startBlock) { foundStartBlock = true; break; } } if (foundStartBlock) { that._runFromBlock(that, targetTurtle, i, isflow, receivedArg); } else { that.errorMsg('Cannot find start block for turtle: ' + args[0], blk) } } break; case 'stopTurtle': var targetTurtle = that._getTargetTurtle(args); if (targetTurtle == null) { that.errorMsg('Cannot find turtle: ' + args[0], blk) } else { that.turtles.turtleList[targetTurtle].queue = []; that.parentFlowQueue[targetTurtle] = []; that.unhightlightQueue[targetTurtle] = []; that.parameterQueue[targetTurtle] = []; that._doBreak(targetTurtle); } break; case 'setcolor': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetColor(args[0]); } } break; case 'setfont': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.turtles.turtleList[turtle].doSetFont(args[0]); } else { that.errorMsg(NOSTRINGERRORMSG, blk); that.stopTurtle = true; } } break; case 'sethue': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetHue(args[0]); } } break; case 'setshade': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetValue(args[0]); } } break; case 'settranslucency': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { args[0] %= 101; var alpha = 1.0 - (args[0] / 100); that.turtles.turtleList[turtle].doSetPenAlpha(alpha); } } break; case 'setgrey': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetChroma(args[0]); } } break; case 'setpensize': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else { that.turtles.turtleList[turtle].doSetPensize(args[0]); } } break; case 'fill': that.turtles.turtleList[turtle].doStartFill(); childFlow = args[0]; childFlowCount = 1; var listenerName = '_fill_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.turtles.turtleList[turtle].doEndFill(); }; that._setListener(turtle, listenerName, __listener); break; // Deprecated case 'beginfill': that.turtles.turtleList[turtle].doStartFill(); break; // Deprecated case 'endfill': that.turtles.turtleList[turtle].doEndFill(); break; case 'hollowline': that.turtles.turtleList[turtle].doStartHollowLine(); childFlow = args[0]; childFlowCount = 1; var listenerName = '_hollowline_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.turtles.turtleList[turtle].doEndHollowLine(); }; that._setListener(turtle, listenerName, __listener); break; // Deprecated case 'beginhollowline': that.turtles.turtleList[turtle].doStartHollowLine(); break; // Deprecated case 'endhollowline': that.turtles.turtleList[turtle].doEndHollowLine(); break; case 'fillscreen': if (args.length === 3) { var hue = that.turtles.turtleList[turtle].color; var value = that.turtles.turtleList[turtle].value; var chroma = that.turtles.turtleList[turtle].chroma; that.turtles.turtleList[turtle].doSetHue(args[0]); that.turtles.turtleList[turtle].doSetValue(args[1]); that.turtles.turtleList[turtle].doSetChroma(args[2]); that.setBackgroundColor(turtle); that.turtles.turtleList[turtle].doSetHue(hue); that.turtles.turtleList[turtle].doSetValue(value); that.turtles.turtleList[turtle].doSetChroma(chroma); } break; case 'nobackground': that.svgBackground = false; break; case 'background': that.setBackgroundColor(turtle); break; case 'penup': that.turtles.turtleList[turtle].doPenUp(); break; case 'pendown': that.turtles.turtleList[turtle].doPenDown(); break; case 'openProject': url = args[0]; function ValidURL(str) { var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string '(\\#[-a-z\\d_]*)?$','i'); // fragment locator if(!pattern.test(str)) { that.errorMsg('Please enter a valid URL.'); return false; } else { return true; } }; if (ValidURL(url)) { var win = window.open(url, '_blank') if (win) { // Browser has allowed it to be opened. win.focus(); } else { // Broswer has blocked it. alert('Please allow popups for this site'); } } break; case 'vspace': break; case 'playback': var sound = new Howl({ urls: [args[0]] }); that.sounds.push(sound); sound.play(); break; case 'stopplayback': for (var sound in that.sounds) { that.sounds[sound].stop(); } that.sounds = []; break; case 'stopvideocam': if (cameraID != null) { doStopVideoCam(that.cameraID, that.setCameraID); } break; case 'showblocks': that.showBlocks(); that.setTurtleDelay(DEFAULTDELAY); break; case 'hideblocks': that.hideBlocks(); that.setTurtleDelay(0); break; case 'savesvg': if (args.length === 1) { if (that.svgBackground) { that.svgOutput = ' ' + that.svgOutput; } doSaveSVG(that, args[0]); } break; case 'showHeap': if (!(turtle in that.turtleHeaps)) { that.turtleHeaps[turtle] = []; } that.textMsg(JSON.stringify(that.turtleHeaps[turtle])); break; case 'emptyHeap': that.turtleHeaps[turtle] = []; break; case 'push': if (args.length === 1) { if (!(turtle in that.turtleHeaps)) { that.turtleHeaps[turtle] = []; } that.turtleHeaps[turtle].push(args[0]); } break; case 'saveHeap': function downloadFile(filename, mimetype, content) { var download = document.createElement('a'); download.setAttribute('href', 'data:' + mimetype + ';charset-utf-8,' + content); download.setAttribute('download', filename); document.body.appendChild(download); download.click(); document.body.removeChild(download); }; if (args[0] && turtle in that.turtleHeaps) { downloadFile(args[0], 'text/json', JSON.stringify(that.turtleHeaps[turtle])); } break; case 'loadHeap': var block = that.blocks.blockList[blk]; if (turtle in that.turtleHeaps) { var oldHeap = that.turtleHeaps[turtle]; } else { var oldHeap = []; } var c = block.connections[1]; if (c != null && that.blocks.blockList[c].name === 'loadFile') { if (args.length !== 1) { that.errorMsg(_('You must select a file.')); } else { try { that.turtleHeaps[turtle] = JSON.parse(that.blocks.blockList[c].value[1]); if (!Array.isArray(that.turtleHeaps[turtle])) { throw 'is not array'; } } catch (e) { that.turtleHeaps[turtle] = oldHeap; that.errorMsg(_('The file you selected does not contain a valid heap.')); } } } else { that.errorMsg(_('The loadHeap block needs a loadFile block.')) } break; case 'loadHeapFromApp': var data = []; var url = args[1]; var name = args [0] var xmlHttp = new XMLHttpRequest(); xmlHttp.open('GET', url, false ); xmlHttp.send(); if (xmlHttp.readyState === 4 && xmlHttp.status === 200){ console.log(xmlHttp.responseText); try { var data = JSON.parse(xmlHttp.responseText); } catch (e) { console.log(e); that.errorMsg(_('Error parsing JSON data:') + e); } } else if (xmlHttp.readyState === 4 && xmlHttp.status !== 200) { console.log('fetched the wrong page or network error...'); that.errorMsg(_('404: Page not found')); break; } else { that.errorMsg('xmlHttp.readyState: ' + xmlHttp.readyState); break; } if (name in that.turtleHeaps){ var oldHeap = turtleHeaps[turtle]; } else { var oldHeap = []; } that.turtleHeaps[name] = data; break; case 'saveHeapToApp': var name = args[0]; var url = args[1]; if (name in that.turtleHeaps) { var data = JSON.stringify(that.turtleHeaps[name]); var xmlHttp = new XMLHttpRequest(); xmlHttp.open('POST', url, true); xmlHttp.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); xmlHttp.send(data); } else { that.errorMsg(_('turtleHeaps does not contain a valid heap for') + ' ' + name); } break; case 'setHeapEntry': if (args.length === 2) { if (!(turtle in that.turtleHeaps)) { that.turtleHeaps[turtle] = []; } var idx = Math.floor(args[0]); if (idx < 1) { that.errorMsg(_('Index must be > 0.')) } // If index > heap length, grow the heap. while (that.turtleHeaps[turtle].length < idx) { that.turtleHeaps[turtle].push(null); } that.turtleHeaps[turtle][idx - 1] = args[1]; } break; // Actions for music-related blocks case 'savelilypond': if (args.length === 1) { saveLilypondOutput(that, args[0]); } break; case 'setmasterbpm': if (args.length === 1 && typeof(args[0] === 'number')) { if (args[0] < 30) { that.errorMsg(_('Beats per minute must be > 30.')) that._masterBPM = 30; } else if (args[0] > 1000) { that.errorMsg(_('Maximum beats per minute is 1000.')) that._masterBPM = 1000; } else { that._masterBPM = args[0]; } that.defaultBPMFactor = TONEBPM / this._masterBPM; } if (this.inTempo) { that.tempo.BPMBlocks.push(blk); var bpmnumberblock = that.blocks.blockList[blk].connections[1] that.tempo.BPMs.push(that.blocks.blockList[bpmnumberblock].text.text); } break; case 'setbpm': if (args.length === 2 && typeof(args[0] === 'number')) { if (args[0] < 30) { that.errorMsg(_('Beats per minute must be > 30.')) var bpm = 30; } else if (args[0] > 1000) { that.errorMsg(_('Maximum beats per minute is 1000.')) var bpm = 1000; } else { var bpm = args[0]; } that.bpm[turtle].push(bpm); childFlow = args[1]; childFlowCount = 1; var listenerName = '_bpm_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.bpm[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } break; // Deprecated case 'setkey': if (args.length === 1) { that.keySignature[turtle] = args[0]; } break; case 'setkey2': if (args.length === 2) { var modename = 'major'; for (var i = 0; i < MODENAMES.length; i++) { if (MODENAMES[i][0] === args[1]) { modename = MODENAMES[i][1]; that._modeBlock = that.blocks.blockList[blk].connections[2]; console.log(modename); break; } } that.keySignature[turtle] = args[0] + ' ' + modename; } break; case 'rhythmruler': console.log("running rhythmruler"); childFlow = args[1]; childFlowCount = 1; if (that.rhythmRuler == null) { that.rhythmRuler = new RhythmRuler(); } // To do: restore previous state that.rhythmRuler.Rulers = []; that.rhythmRuler.Drums = []; that.inRhythmRuler = true; var listenerName = '_rhythmruler_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.rhythmRuler.init(that); }; that._setListener(turtle, listenerName, __listener); break; case 'pitchstaircase': if (that.pitchStaircase == null) { that.pitchStaircase = new PitchStaircase(); } that.pitchStaircase.Stairs = []; childFlow = args[0]; childFlowCount = 1; that.inPitchStaircase = true; var listenerName = '_pitchstaircase_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.pitchStaircase.init(that); that.inPitchStaircase = false; }; that._setListener(turtle, listenerName, __listener); break; case 'tempo': console.log("running Tempo"); childFlow = args[0]; childFlowCount = 1; if (that.tempo == null) { that.tempo = new Tempo(); } that.inTempo = true; that.tempo.BPMblocks = []; that.tempo.BPMs = []; var listenerName = '_tempo_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.tempo.init(that); }; that._setListener(turtle, listenerName, __listener); break; case 'pitchslider': if (that.pitchSlider == null) { that.pitchSlider = new PitchSlider(); } that.pitchSlider.Sliders = []; childFlow = args[0]; childFlowCount = 1; that.inPitchSlider = true; var listenerName = '_pitchslider_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.pitchSlider.init(that); that.inPitchSlider = false; }; that._setListener(turtle, listenerName, __listener); break; case 'pitchdrummatrix': if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } if (that.pitchDrumMatrix == null) { that.pitchDrumMatrix = new PitchDrumMatrix(); } that.inPitchDrumMatrix = true; that.pitchDrumMatrix.rowLabels = []; that.pitchDrumMatrix.rowArgs = []; that.pitchDrumMatrix.drums = []; that.pitchDrumMatrix.clearBlocks(); var listenerName = '_pitchdrummatrix_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (that.pitchDrumMatrix.drums.length === 0 || that.pitchDrumMatrix.rowLabels.length === 0) { that.errorMsg(_('You must have at least one pitch block and one drum block in the matrix.'), blk); } else { // Process queued up rhythms. that.pitchDrumMatrix.init(that); that.pitchDrumMatrix.makeClickable(); } }; that._setListener(turtle, listenerName, __listener); break; case 'modewidget': if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } if (that.modeWidget == null) { that.modeWidget = new ModeWidget(); } var listenerName = '_modewidget_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { console.log(that.keySignature[turtle]); that.modeWidget.init(that, that._modeBlock); } that._setListener(turtle, listenerName, __listener); break; case 'status': if (that.statusMatrix == null) { that.statusMatrix = new StatusMatrix(); } that.statusMatrix.init(that); that.statusFields = []; if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } that.inStatusMatrix = true; var listenerName = '_status_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.statusMatrix.init(that); that.inStatusMatrix = false; } that._setListener(turtle, listenerName, __listener); break; case 'matrix': if (args.length === 1) { childFlow = args[0]; childFlowCount = 1; } that.inMatrix = true; if (that.pitchTimeMatrix == null) { that.pitchTimeMatrix = new PitchTimeMatrix(); } that.pitchTimeMatrix.rowLabels = []; that.pitchTimeMatrix.rowArgs = []; that.pitchTimeMatrix.graphicsBlocks = []; that.pitchTimeMatrix.clearBlocks(); that.tupletRhythms = []; that.tupletParams = []; that.addingNotesToTuplet = false; var listenerName = '_matrix_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (that.tupletRhythms.length === 0 || that.pitchTimeMatrix.rowLabels.length === 0) { that.errorMsg(_('You must have at least one pitch block and one rhythm block in the matrix.'), blk); } else { // Process queued up rhythms. that.pitchTimeMatrix.init(that); for (var i = 0; i < that.tupletRhythms.length; i++) { // We have two cases: (1) notes in a tuplet; // and (2) rhythm block outside of a // tuplet. Rhythm blocks in a tuplet are // converted to notes. switch (that.tupletRhythms[i][0]) { case 'notes': case 'simple': var tupletParam = [that.tupletParams[that.tupletRhythms[i][1]]]; tupletParam.push([]); for (var j = 2; j < that.tupletRhythms[i].length; j++) { tupletParam[1].push(that.tupletRhythms[i][j]); } that.pitchTimeMatrix.addTuplet(tupletParam); break; default: that.pitchTimeMatrix.addNotes(that.tupletRhythms[i][1], that.tupletRhythms[i][2]); break; } } that.pitchTimeMatrix.makeClickable(); } }; that._setListener(turtle, listenerName, __listener); break; case 'invert1': if (typeof(args[2]) === 'number') { if (args[2] % 2 === 0){ args[2] = 'even'; } else { args[2] = 'odd'; } } if (args[2] === _('even')) { args2 = 'even'; } if (args[2] === _('odd')) { args2 = 'odd'; } if (args[2] === 'even' || args[2] === 'odd'){ var octave = calcOctave(that.currentOctaves[turtle], args[1]); that.invertList[turtle].push([args[0], octave, args[2]]); } else { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } childFlow = args[3]; childFlowCount = 1; var listenerName = '_invert_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.invertList[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); break; case 'invert2': case 'invert': // Deprecated if (that.blocks.blockList[blk].name === 'invert') { that.invertList[turtle].push([args[0], args[1], 'even']); } else { that.invertList[turtle].push([args[0], args[1], 'odd']); } childFlow = args[2]; childFlowCount = 1; var listenerName = '_invert_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.invertList[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); break; case 'backward': that.backward[turtle].push(blk); // Set child to bottom block inside clamp childFlow = that.blocks.findBottomBlock(args[0]); childFlowCount = 1; var listenerName = '_backward_' + turtle + '_' + blk; var nextBlock = this.blocks.blockList[blk].connections[1]; if (nextBlock == null) { that.backward[turtle].pop(); } else { that.endOfClampSignals[turtle][nextBlock] = [listenerName]; } var __listener = function (event) { that.backward[turtle].pop(); // Since a backward block was requeued each // time, we need to flush it from the queue. // that.turtles.turtleList[turtle].queue.pop(); }; that._setListener(turtle, listenerName, __listener); break; case 'rest2': that.notePitches[turtle].push('rest'); that.noteOctaves[turtle].push(4); that.noteCents[turtle].push(0); that.noteHertz[turtle].push(0); if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; break; case 'steppitch': // Similar to pitch but calculated from previous note played. if (that.inNoteBlock[turtle] === 0) { that.errorMsg(_('The Step Pitch Block must be used inside of a Note Block.'), blk); that.stopTurtle = true; break; } if (typeof(args[0]) !== 'number') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; break; } if (that.lastNotePlayed[turtle] == null) { that.errorMsg('The Step Pitch Block must be preceded by a Pitch Block.', blk); that.stopTurtle = true; break; } function addPitch(note, octave, cents) { if (that.drumStyle[turtle].length > 0) { var drumname = last(that.drumStyle[turtle]); var note2 = that.getNote(note, octave, transposition, that.keySignature[turtle]); that.pitchDrumTable[turtle][note2[0] + note2[1]] = drumname; } that.notePitches[turtle].push(note); that.noteOctaves[turtle].push(octave); that.noteCents[turtle].push(cents); if (cents !== 0) { that.noteHertz[turtle].push(pitchToFrequency(note, octave, cents, that.keySignature[turtle])); } else { that.noteHertz[turtle].push(0); } } var len = that.lastNotePlayed[turtle][0].length; if (args[0] >= 1) { var n = Math.floor(args[0]); var value = getStepSizeUp(that.keySignature[turtle], that.lastNotePlayed[turtle][0].slice(0, len - 1)); var noteObj = that.getNote(that.lastNotePlayed[turtle][0].slice(0, len - 1), parseInt(that.lastNotePlayed[turtle][0].slice(len - 1)), value, that.keySignature[turtle]); for (var i = 1; i < n; i++) { var value = getStepSizeUp(that.keySignature[turtle], noteObj[0]); noteObj = that.getNote(noteObj[0], noteObj[1], value, that.keySignature[turtle]); } } else if (args[0] <= -1) { var n = -Math.ceil(args[0]); value = getStepSizeDown(that.keySignature[turtle], that.lastNotePlayed[turtle][0].slice(0, len - 1)); var noteObj = that.getNote(that.lastNotePlayed[turtle][0].slice(0, len - 1), parseInt(that.lastNotePlayed[turtle][0].slice(len - 1)), value, that.keySignature[turtle]); for (var i = 1; i < n; i++) { var value = getStepSizeDown(that.keySignature[turtle], noteObj[0]); noteObj = that.getNote(noteObj[0], noteObj[1], value, that.keySignature[turtle]); } } else { // Repeat last pitch played. var noteObj = that.getNote(that.lastNotePlayed[turtle][0].slice(0, len - 1), parseInt(that.lastNotePlayed[turtle][0].slice(len - 1)), 0, that.keySignature[turtle]); } var delta = 0; if (!(that.invertList[turtle].length === 0)) { var len = that.invertList[turtle].length; var note1 = that.getNote(noteObj[0], noteObj[1], 0, that.keySignature[turtle]); var num1 = getNumber(note1[0], note1[1]); for (var i = len - 1; i > -1; i--) { var note2 = that.getNote(that.invertList[turtle][i][0], that.invertList[turtle][i][1], 0, that.keySignature[turtle]); var num2 = getNumber(note2[0], note2[1]); // var a = getNumNote(num1, 0); if (that.invertList[turtle][i][2] === 'even') { delta += num2 - num1; } else { // odd delta += num2 - num1 + 0.5; } num1 += 2 * delta; } } addPitch(noteObj[0], noteObj[1], 0); if (turtle in that.intervals && that.intervals[turtle].length > 0) { for (var i = 0; i < that.intervals[turtle].length; i++) { var ii = getInterval(that.intervals[turtle][i], that.keySignature[turtle], noteObj[0]); var noteObj2 = that.getNote(noteObj[0], noteObj[1], ii, that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } } if (turtle in that.perfect && that.perfect[turtle].length > 0) { var noteObj2 = that.getNote(noteObj[0], noteObj[1], calcPerfect(last(that.perfect[turtle])), that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } if (turtle in that.diminished && that.diminished[turtle].length > 0) { var noteObj2 = that.getNote(noteObj[0], noteObj[1], calcDiminished(last(that.diminished[turtle])), that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } if (turtle in that.augmented && that.augmented[turtle].length > 0) { var noteObj2 = that.getNote(noteObj[0], noteObj[1], calcAugmented(last(that.augmented[turtle])), that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } if (turtle in that.major && that.major[turtle].length > 0) { var noteObj2 = that.getNote(noteObj[0], noteObj[1], calcMajor(last(that.major[turtle])), that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } if (turtle in that.minor && that.minor[turtle].length > 0) { var noteObj2 = that.getNote(noteObj[0], noteObj[1], calcMinor(last(that.minor[turtle])), that.keySignature[turtle]); addPitch(noteObj2[0], noteObj2[1], 0); } if (turtle in that.transposition) { that.noteTranspositions[turtle].push(that.transposition[turtle] + 2 * delta); } else { that.noteTranspositions[turtle].push(2 * delta); } if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; break; case 'playdrum': if (args.length !== 1 || args[0] == null) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } if (typeof(args[0]) !== 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; break; } var drumname = 'kick'; if (args[0].slice(0, 4) === 'http') { drumname = args[0]; } else { for (var drum in DRUMNAMES) { if (DRUMNAMES[drum][0] === args[0]) { drumname = DRUMNAMES[drum][1]; break; } else if (DRUMNAMES[drum][1] === args[0]) { drumname = args[0]; break; } } } // If we are in a setdrum clamp, override the drum name. if (that.drumStyle[turtle].length > 0) { drumname = last(that.drumStyle[turtle]); } if (that.inPitchDrumMatrix) { that.pitchDrumMatrix.drums.push(drumname); that.pitchDrumMatrix.addColBlock(blk); if (that.drumBlocks.indexOf(blk) === -1) { that.drumBlocks.push(blk); } } else if (that.inMatrix) { that.pitchTimeMatrix.rowLabels.push(drumname); that.pitchTimeMatrix.rowArgs.push(-1); that.pitchTimeMatrix.addRowBlock(blk); if (that.drumBlocks.indexOf(blk) === -1) { that.drumBlocks.push(blk); } } else if (that.inNoteBlock[turtle] > 0) { that.noteDrums[turtle].push(drumname); } else { that.errorMsg(_('Drum Block: Did you mean to use a Note block?'), blk); break; } if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; break; case 'setpitchnumberoffset': if (args.length !== 2 || args[0] == null || args[1] == null) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } var octave = Math.floor(calcOctave(that.currentOctaves[turtle], args[1])); that.pitchNumberOffset = pitchToNumber(args[0], octave, that.keySignature[turtle]); break; case 'pitchnumber': case 'scaledegree': case 'pitch': if (that.blocks.blockList[blk].name == 'pitchnumber') { if (args.length !== 1 || args[0] == null) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } if (typeof(args[0]) !== 'number') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; break; } // In number to pitch we assume A0 == 0. Here we // assume that C4 == 0, so we need an offset of 39. var obj = numberToPitch(Math.floor(args[0] + that.pitchNumberOffset)); note = obj[0]; octave = obj[1]; cents = 0; } else { if (args.length !== 2 || args[0] == null || args[1] == null) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } /* if (typeof(args[1]) !== 'number') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; break; } */ if (typeof(args[0]) === 'number' && that.blocks.blockList[blk].name == 'pitch') { // We interpret numbers two different ways: // (1) a positive integer between 1 and 12 is taken to be // a moveable solfege, e.g., 1 == do; 2 == re... // (2) if frequency is input, ignore octave (args[1]). // Negative numbers will throw an error. if (args[0] < 1) { note = '?'; // throws an error } else if (args[0] < 13) { // moveable solfege note = scaleDegreeToPitch(that.keySignature[turtle], Math.floor(args[0])); var octave = Math.floor(calcOctave(that.currentOctaves[turtle], args[1])); var cents = 0; } else if (args[0] < A0 || args[0] > C8) { note = '?'; // throws an error } else { var obj = frequencyToPitch(args[0]); var note = obj[0]; var octave = obj[1]; var cents = obj[2]; } if (note === '?') { that.errorMsg(INVALIDPITCH, blk); that.stopTurtle = true; break; } } else if (typeof(args[0]) === 'number' && that.blocks.blockList[blk].name == 'scaledegree') { if (args[0] < 0) { var neg = true; args[0] = -args[0]; } else { var neg = false; } if (args[0] == 0) { note = '?'; // throws an error } else { var obj = keySignatureToMode(that.keySignature[turtle]); var modeLength = MUSICALMODES[obj[1]].length; var scaleDegree = Math.floor(args[0] - 1) % modeLength; scaleDegree += 1; if (neg) { // -1, 4 --> do 4; -2, 4 --> ti 3; -8, 4 --> do 3 if (scaleDegree > 1) { scaleDegree = modeLength - scaleDegree + 2; } note = scaleDegreeToPitch(that.keySignature[turtle], scaleDegree); var deltaOctave = Math.floor((args[0] + modeLength - 2) / modeLength); var octave = Math.floor(calcOctave(that.currentOctaves[turtle], args[1])) - deltaOctave; } else { // 1, 4 --> do 4; 2, 4 --> re 4; 8, 4 --> do 5 note = scaleDegreeToPitch(that.keySignature[turtle], scaleDegree); var deltaOctave = Math.floor((args[0] - 1) / modeLength); var octave = Math.floor(calcOctave(that.currentOctaves[turtle], args[1])) + deltaOctave; } var cents = 0; } if (note === '?') { that.errorMsg(INVALIDPITCH, blk); that.stopTurtle = true; break; } } else { var cents = 0; var note = args[0]; if (calcOctave(that.currentOctaves[turtle], args[1]) < 0) { console.log('minimum allowable octave is 0'); var octave = 0; } else if (calcOctave(that.currentOctaves[turtle], args[1]) > 10) { // Humans can only hear 10 octaves. console.log('clipping octave at 10'); var octave = 10; } else { // Octave must be a whole number. var octave = Math.floor(calcOctave(that.currentOctaves[turtle], args[1])); } that.getNote(note, octave, 0, that.keySignature[turtle]); if (!that.validNote) { that.errorMsg(INVALIDPITCH, blk); that.stopTurtle = true; break; } } } var delta = 0; if (that.inPitchDrumMatrix) { if (note.toLowerCase() !== 'rest') { that.pitchDrumMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } } if (!(that.invertList[turtle].length === 0)) { var delta = 0; var len = that.invertList[turtle].length; // Get an anchor note and its corresponding // number, which is used to calculate delta. var note1 = that.getNote(note, octave, 0, that.keySignature[turtle]); var num1 = that.getNumber(note1[0], note1[1]); for (var i = len - 1; i > -1; i--) { // Note from which delta is calculated. var note2 = that.getNote(that.invertList[turtle][i][0], that.invertList[turtle][i][1], 0, that.keySignature[turtle]); var num2 = getNumber(note2[0], note2[1]); // var a = getNumNote(num1, 0); if (that.invertList[turtle][i][2] === 'even') { delta += num2 - num1; } else { // odd delta += num2 - num1 + 0.5; } num1 += 2 * delta; } } if (that.duplicateFactor[turtle].length > 0) { var duplicateFactor = that.duplicateFactor[turtle]; } else { var duplicateFactor = 1; } for (var i = 0; i < duplicateFactor; i++) { // Apply transpositions var transposition = 2 * delta; if (turtle in that.transposition) { transposition += that.transposition[turtle]; } var nnote = that.getNote(note, octave, transposition, that.keySignature[turtle]); if (noteIsSolfege(note)) { nnote[0] = getSolfege(nnote[0]); } if (that.drumStyle[turtle].length > 0) { that.pitchDrumMatrix.drums.push(last(that.drumStyle[turtle])); } else { that.pitchDrumMatrix.rowLabels.push(nnote[0]); that.pitchDrumMatrix.rowArgs.push(nnote[1]); } } } else if (that.inMatrix) { if (note.toLowerCase() !== 'rest') { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } } if (!(that.invertList[turtle].length === 0)) { var delta = 0; var len = that.invertList[turtle].length; // Get an anchor note and its corresponding // number, which is used to calculate delta. var note1 = that.getNote(note, octave, 0, that.keySignature[turtle]); var num1 = that.getNumber(note1[0], note1[1]); for (var i = len - 1; i > -1; i--) { // Note from which delta is calculated. var note2 = that.getNote(that.invertList[turtle][i][0], that.invertList[turtle][i][1], 0, that.keySignature[turtle]); var num2 = getNumber(note2[0], note2[1]); // var a = getNumNote(num1, 0); if (that.invertList[turtle][i][2] === 'even') { delta += num2 - num1; } else { // odd delta += num2 - num1 + 0.5; } num1 += 2 * delta; } } if (that.duplicateFactor[turtle].length > 0) { var duplicateFactor = that.duplicateFactor[turtle]; } else { var duplicateFactor = 1; } for (var i = 0; i < duplicateFactor; i++) { // Apply transpositions var transposition = 2 * delta; if (turtle in that.transposition) { transposition += that.transposition[turtle]; } var nnote = that.getNote(note, octave, transposition, that.keySignature[turtle]); if (noteIsSolfege(note)) { nnote[0] = getSolfege(nnote[0]); } // If we are in a setdrum clamp, override the pitch. if (that.drumStyle[turtle].length > 0) { that.pitchTimeMatrix.rowLabels.push(last(that.drumStyle[turtle])); that.pitchTimeMatrix.rowArgs.push(-1); } else { that.pitchTimeMatrix.rowLabels.push(nnote[0]); that.pitchTimeMatrix.rowArgs.push(nnote[1]); } } } else if (that.inNoteBlock[turtle] > 0) { function addPitch(note, octave, cents) { if (that.drumStyle[turtle].length > 0) { var drumname = last(that.drumStyle[turtle]); var note2 = that.getNote(note, octave, transposition, that.keySignature[turtle]); that.pitchDrumTable[turtle][note2[0]+note2[1]] = drumname; } that.notePitches[turtle].push(note); that.noteOctaves[turtle].push(octave); that.noteCents[turtle].push(cents); if (cents !== 0) { that.noteHertz[turtle].push(pitchToFrequency(note, octave, cents, that.keySignature[turtle])); } else { that.noteHertz[turtle].push(0); } } if (!(that.invertList[turtle].length === 0)) { var len = that.invertList[turtle].length; var note1 = that.getNote(note, octave, 0, that.keySignature[turtle]); var num1 = getNumber(note1[0], note1[1]); for (var i = len - 1; i > -1; i--) { var note2 = that.getNote(that.invertList[turtle][i][0], that.invertList[turtle][i][1], 0, that.keySignature[turtle]); var num2 = getNumber(note2[0], note2[1]); // var a = getNumNote(num1, 0); if (that.invertList[turtle][i][2] === 'even') { delta += num2 - num1; } else { // odd delta += num2 - num1 + 0.5; } num1 += 2 * delta; } } addPitch(note, octave, cents); if (turtle in that.intervals && that.intervals[turtle].length > 0) { for (var i = 0; i < that.intervals[turtle].length; i++) { var ii = getInterval(that.intervals[turtle][i], that.keySignature[turtle], note); var noteObj = that.getNote(note, octave, ii, that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } } if (turtle in that.perfect && that.perfect[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcPerfect(last(that.perfect[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } if (turtle in that.diminished && that.diminished[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcDiminished(last(that.diminished[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } if (turtle in that.augmented && that.augmented[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcAugmented(last(that.augmented[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } if (turtle in that.major && that.major[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcMajor(last(that.major[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } if (turtle in that.minor && that.minor[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcMinor(last(that.minor[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents); } if (turtle in that.transposition) { that.noteTranspositions[turtle].push(that.transposition[turtle] + 2 * delta); } else { that.noteTranspositions[turtle].push(2 * delta); } if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; } else if (that.drumStyle[turtle].length > 0) { var drumname = last(that.drumStyle[turtle]); var note2 = that.getNote(note, octave, transposition, that.keySignature[turtle]); that.pitchDrumTable[turtle][note2[0]+note2[1]] = drumname; } else if (that.inPitchStaircase) { var frequency = pitchToFrequency(args[0], calcOctave(that.currentOctaves[turtle], args[1]), 0, that.keySignature[turtle]); var note = that.getNote(args[0], calcOctave(that.currentOctaves[turtle], args[1]), 0, that.keySignature[turtle]); var flag = 0; for (var i = 0 ; i < that.pitchStaircase.Stairs.length; i++) { if (that.pitchStaircase.Stairs[i][2] < parseFloat(frequency)) { that.pitchStaircase.Stairs.splice(i, 0, [note[0], note[1], parseFloat(frequency), 1, 1]); flag = 1; break; } if (that.pitchStaircase.Stairs[i][2] === parseFloat(frequency)) { that.pitchStaircase.Stairs.splice(i, 1, [note[0], note[1], parseFloat(frequency), 1, 1]); flag = 1; break; } } if (flag === 0) { that.pitchStaircase.Stairs.push([note[0], note[1], parseFloat(frequency), 1, 1]); } } else { that.errorMsg(_('Pitch Block: Did you mean to use a Note block?'), blk); } break; case 'rhythm2': case 'rhythm': if (args.length < 2 || typeof(args[0]) !== 'number' || typeof(args[1]) !== 'number' || args[0] < 1 || args[1] <= 0) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } if (that.blocks.blockList[blk].name === 'rhythm2') { var noteBeatValue = 1 / args[1]; } else { var noteBeatValue = args[1]; } if (that.inMatrix) { that.pitchTimeMatrix.addColBlock(blk, args[0]); for (var i = 0; i < args[0]; i++) { that._processNote(noteBeatValue, blk, turtle); } } else if (that.inRhythmRuler) { // Temporary work-around to #272. if (that.rhythmRulerMeasure === null) { that.rhythmRulerMeasure = args[0] * args[1]; } else if (that.rhythmRulerMeasure != (args[0] * args[1])) { that.errorMsg('Rhythm Ruler imbalance.', blk); that.stopTurtle = true; } // Since there maybe more than one instance of the // same drum, e.g., if a repeat is used, we look from // end of the list instead of the beginning of the // list. var drumIndex = -1; for (var i = 0; i < that.rhythmRuler.Drums.length; i++) { var j = that.rhythmRuler.Drums.length - i - 1; if (that.rhythmRuler.Drums[j] === that._currentDrumBlock) { drumIndex = j; break; } } if (drumIndex !== -1) { for (var i = 0; i < args[0]; i++) { that.rhythmRuler.Rulers[drumIndex][0].push(noteBeatValue); } } } else { that.errorMsg(_('Rhythm Block: Did you mean to use a Matrix block?'), blk); } break; // 𝅝 𝅗𝅥 𝅘𝅥 𝅘𝅥𝅮 𝅘𝅥𝅯 𝅘𝅥𝅰 𝅘𝅥𝅱 𝅘𝅥𝅲 case 'wholeNote': that._processNote(1, blk, turtle); break; case 'halfNote': that._processNote(2, blk, turtle); break; case 'quarterNote': that._processNote(4, blk, turtle); break; case 'eighthNote': that._processNote(8, blk, turtle); break; case 'sixteenthNote': that._processNote(16, blk, turtle); break; case 'thirtysecondNote': that._processNote(32, blk, turtle); break; case 'sixtyfourthNote': that._processNote(64, blk, turtle); break; case 'meter': // See Issue #337 break; case 'osctime': case 'newnote': case 'note': // We queue up the child flow of the note clamp and // once all of the children are run, we trigger a // _playnote_ event, then wait for the note to play. // The note can be specified by pitch or synth blocks. // The osctime block specifies the duration in // milleseconds while the note block specifies // duration as a beat value. Note: we should consider // the use of the global timer in Tone.js for more // accuracy. // A note can contain multiple pitch blocks to create // a chord. The chord is accumuated in these arrays, // which are used when we play the note. that.oscList[turtle] = []; that.noteBeat[turtle] = []; that.notePitches[turtle] = []; that.noteOctaves[turtle] = []; that.noteCents[turtle] = []; that.noteHertz[turtle] = []; that.noteTranspositions[turtle] = []; that.noteBeatValues[turtle] = []; that.noteDrums[turtle] = []; // Ensure that note duration is positive. if (args[0] < 0) { that.errorMsg(_('Note value must be greater than 0'), blk); var noteBeatValue = 0; } else { if (that.blocks.blockList[blk].name === 'newnote') { var noteBeatValue = 1 / args[0]; } else { var noteBeatValue = args[0]; } } that.inNoteBlock[turtle] += 1; that.whichNoteBlock[turtle] = blk; childFlow = args[1]; childFlowCount = 1; var listenerName = '_playnote_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that._processNote(noteBeatValue, blk, turtle); that.inNoteBlock[turtle] -= 1; that.pitchBlocks = []; that.drumBlocks = []; }; that._setListener(turtle, listenerName, __listener); break; case 'rhythmicdot': // Dotting a note will increase its play time by // a(2 - 1/2^n) var currentDotFactor = 2 - (1 / Math.pow(2, that.dotCount[turtle])); that.beatFactor[turtle] *= currentDotFactor; that.dotCount[turtle] += 1; var newDotFactor = 2 - (1 / Math.pow(2, that.dotCount[turtle])); that.beatFactor[turtle] /= newDotFactor; childFlow = args[0]; childFlowCount = 1; var listenerName = '_dot_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { var currentDotFactor = 2 - (1 / Math.pow(2, that.dotCount[turtle])); that.beatFactor[turtle] *= currentDotFactor; that.dotCount[turtle] -= 1; var newDotFactor = 2 - (1 / Math.pow(2, that.dotCount[turtle])); that.beatFactor[turtle] /= newDotFactor; }; that._setListener(turtle, listenerName, __listener); break; case 'crescendo': if (args.length > 1 && args[0] !== 0) { that.crescendoDelta[turtle].push(args[0]); that.crescendoVolume[turtle].push(last(that.polyVolume[turtle])); that.crescendoInitialVolume[turtle].push(last(that.polyVolume[turtle])); that.polyVolume[turtle].push(last(that.crescendoVolume[turtle])); childFlow = args[1]; childFlowCount = 1; var listenerName = '_crescendo_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.crescendoDelta[turtle].pop(); that.crescendoVolume[turtle].pop(); that.polyVolume[turtle].pop(); that.crescendoInitialVolume[turtle].pop(); if (!that.justCounting[turtle]) { lilypondEndCrescendo(that, turtle); } }; that._setListener(turtle, listenerName, __listener); } break; case 'darbuka': case 'clang': case 'bottle': case 'duck': case 'snare': case 'hihat': case 'tom': case 'kick': case 'pluck': case 'triangle1': case 'slap': case 'frogs': case 'fingercymbals': case 'cup': case 'cowbell': case 'splash': case 'ridebell': case 'floortom': case 'crash': case 'chine': case 'dog': case 'cat': case 'clap': case 'bubbles': case 'cricket': that.drumStyle[turtle].push(that.blocks.blockList[blk].name); childFlow = args[0]; childFlowCount = 1; var listenerName = '_drum_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.drumStyle[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); break; case 'setdrum': var drumname = 'kick'; for (var drum in DRUMNAMES) { if (DRUMNAMES[drum][0] === args[0]) { drumname = DRUMNAMES[drum][1]; } else if (DRUMNAMES[drum][1] === args[0]) { drumname = args[0]; } } that.drumStyle[turtle].push(drumname); childFlow = args[1]; childFlowCount = 1; var listenerName = '_setdrum_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.drumStyle[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); if (that.inRhythmRuler) { that._currentDrumBlock = blk; that.rhythmRuler.Drums.push(blk); that.rhythmRuler.Rulers.push([[],[]]); } break; case 'setvoice': var voicename = null; for (var voice in VOICENAMES) { if (VOICENAMES[voice][0] === args[0]) { voicename = VOICENAMES[voice][1]; } else if (VOICENAMES[voice][1] === args[0]) { voicename = args[0]; } } // Maybe it is a drum? if (voicename == null) { for (var drum in DRUMNAMES) { if (DRUMNAMES[drum][0] === args[0]) { voicename = DRUMNAMES[drum][1]; } else if (DRUMNAMES[drum][1] === args[0]) { voicename = args[0]; } } } if (voicename == null) { that.errorMsg(NOINPUTERRORMSG, blk); childFlow = args[1]; childFlowCount = 1; } else { that.voices[turtle].push(voicename); childFlow = args[1]; childFlowCount = 1; var listenerName = '_setvoice_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.voices[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } break; case 'vibrato': var intensity = args[0]; var rate = args[1]; if (intensity < 1 || intensity > 100) { that.errorMsg(_('Vibrato intensity must be between 1 and 100.'), blk); that.stopTurtle = true; } if (rate <= 0) { that.errorMsg(_('Vibrato rate must be greater than 0.'), blk); that.stopTurtle = true; } childFlow = args[2]; childFlowCount = 1; that.vibratoIntensity[turtle].push(intensity / 100); that.vibratoRate[turtle].push(Math.floor(Math.pow(rate, -1))); var listenerName = '_vibrato_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.vibratoIntensity[turtle].pop(); that.vibratoRate[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); break; case 'interval': if (typeof(args[0]) !== 'number') { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } if (args[0] > 0) { var i = Math.floor(args[0]); } else { var i = Math.ceil(args[0]); } if (i !== 0) { that.intervals[turtle].push(i); var listenerName = '_interval_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.intervals[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } childFlow = args[1]; childFlowCount = 1; break; case 'perfectx': if (args[0] < 0) { var interval = mod12(-args[0]); } else { var interval = mod12(args[0]); } var deltaOctave = calcOctaveInterval(args[1]); if ([1, 4, 5, 8].indexOf(interval) !== -1) { that.perfect[turtle].push([args[0], deltaOctave]); var listenerName = '_perfect_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.perfect[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Perfect Block must be 1, 4, 5, or 8'), blk); } childFlow = args[2]; childFlowCount = 1; break; case 'perfect': // Deprecated var mod12Arg = mod12(args[0]); if ([1, 4, 5, 8].indexOf(mod12Arg) !== -1) { that.perfect[turtle].push([args[0], 0]); var listenerName = '_perfect_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.perfect[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Perfect Block must be 1, 4, 5, or 8'), blk); } childFlow = args[1]; childFlowCount = 1; break; case 'diminishedx': if (args[0] < 0) { var interval = mod12(-args[0]); } else { var interval = mod12(args[0]); } var deltaOctave = calcOctaveInterval(args[1]); if ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(interval) !== -1) { that.diminished[turtle].push([args[0], deltaOctave]); var listenerName = '_diminished_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.diminished[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Diminished Block must be 1, 2, 3, 4, 5, 6, 7, or 8'), blk); } childFlow = args[2]; childFlowCount = 1; break; case 'diminished': // Deprecated var mod12Arg = mod12(args[0]); if ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(mod12Arg) !== -1) { that.diminished[turtle].push([args[0], 0]); var listenerName = '_diminished_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.diminished[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Diminished Block must be 1, 2, 3, 4, 5, 6, 7, or 8'), blk); } childFlow = args[1]; childFlowCount = 1; break; case 'augmentedx': if (args[0] < 0) { var interval = mod12(-args[0]); } else { var interval = mod12(args[0]); } var deltaOctave = calcOctaveInterval(args[1]); if ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(interval) !== -1) { that.augmented[turtle].push([args[0], deltaOctave]); var listenerName = '_augmented_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.augmented[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Augmented Block must be 1, 2, 3, 4, 5, 6, 7, or 8'), blk); } childFlow = args[2]; childFlowCount = 1; break; case 'augmented': // Deprecated var mod12Arg = mod12(args[0]); if ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(mod12Arg) !== -1) { that.augmented[turtle].push([args[0], 0]); var listenerName = '_tritone_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.augmented[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Augmented Block must be 1, 2, 3, 4, 5, 6, 7, or 8'), blk); } childFlow = args[1]; childFlowCount = 1; break; case 'majorx': if (args[0] < 0) { var interval = mod12(-args[0]); } else { var interval = mod12(args[0]); } var deltaOctave = calcOctaveInterval(args[1]); if ([2, 3, 6, 7].indexOf(interval) !== -1) { that.major[turtle].push([args[0], deltaOctave]); var listenerName = '_major_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.major[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Major Block must be 2, 3, 6, or 7'), blk); } childFlow = args[2]; childFlowCount = 1; break; case 'major': // Deprecated var mod12Arg = mod12(args[0]); var octaveArg = args[1]; if ([2, 3, 6, 7].indexOf(mod12Arg) !== -1) { that.major[turtle].push([args[0], 0]); var listenerName = '_major_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.major[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Major Block must be 2, 3, 6, or 7'), blk); } childFlow = args[1]; childFlowCount = 1; break; case 'minorx': if (args[0] < 0) { var interval = mod12(-args[0]); } else { var interval = mod12(args[0]); } var deltaOctave = calcOctaveInterval(args[1]); if ([2, 3, 6, 7].indexOf(interval) !== -1) { that.minor[turtle].push([args[0], deltaOctave]); var listenerName = '_minor_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.minor[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Minor Block must be 2, 3, 6, or 7'), blk); } childFlow = args[2]; childFlowCount = 1; break; case 'minor': // Deprecated var mod12Arg = mod12(args[0]); if ([2, 3, 6, 7].indexOf(mod12Arg) !== -1) { that.minor[turtle].push([args[0], 0]); var listenerName = '_minor_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.minor[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } else { that.errorMsg(_('Input to Minor Block must be 2, 3, 6, or 7'), blk); } childFlow = args[1]; childFlowCount = 1; break; case 'newstaccato': case 'staccato': if (args.length > 1) { if (that.blocks.blockList[blk].name === 'newstaccato') { that.staccato[turtle].push(1 / args[0]); } else { that.staccato[turtle].push(args[0]); } childFlow = args[1]; childFlowCount = 1; var listenerName = '_staccato_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.staccato[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } break; case 'newslur': case 'slur': if (args.length > 1) { if (that.blocks.blockList[blk].name === 'newslur') { that.staccato[turtle].push(-1 / args[0]); } else { that.staccato[turtle].push(-args[0]); } if (!that.justCounting[turtle]) { lilypondBeginSlur(that, turtle); } childFlow = args[1]; childFlowCount = 1; var listenerName = '_staccato_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.staccato[turtle].pop(); if (!that.justCounting[turtle]) { lilypondEndSlur(that, turtle); } }; that._setListener(turtle, listenerName, __listener); } break; case 'drift': that.drift[turtle] += 1; childFlow = args[0]; childFlowCount = 1; var listenerName = '_drift_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.drift[turtle] -= 1; }; that._setListener(turtle, listenerName, __listener); break; case 'tie': // Tie notes together in pairs. that.tie[turtle] = true; that.tieNote[turtle] = []; that.tieCarryOver[turtle] = 0; childFlow = args[0]; childFlowCount = 1; var listenerName = '_tie_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.tie[turtle] = false; // If tieCarryOver > 0, we have one more note to // play. if (that.tieCarryOver[turtle] > 0) { if (!that.justCounting[turtle]) { // Remove the note from the Lilypond list. for (var i = 0; i < that.notePitches[turtle].length; i++) { lilypondRemoveTie(that, turtle); } } var noteValue = that.tieCarryOver[turtle]; that.tieCarryOver[turtle] = 0; that._processNote(noteValue, blk, turtle); } that.tieNote[turtle] = []; }; that._setListener(turtle, listenerName, __listener); break; case 'newswing': case 'newswing2': case 'swing': // Grab a bit from the next note to give to the current note. if (that.blocks.blockList[blk].name === 'newswing2') { that.swing[turtle].push(1 / args[0]); that.swingTarget[turtle].push(1 / args[1]); childFlow = args[2]; } else if (that.blocks.blockList[blk].name === 'newswing') { that.swing[turtle].push(1 / args[0]); that.swingTarget[turtle].push(null); childFlow = args[1]; } else { that.swing[turtle].push(args[0]); that.swingTarget[turtle].push(null); childFlow = args[1]; } that.swingCarryOver[turtle] = 0; childFlowCount = 1; var listenerName = '_swing_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.swingTarget[turtle].pop(); that.swing[turtle].pop(); that.swingCarryOver[turtle] = 0; }; that._setListener(turtle, listenerName, __listener); break; case 'duplicatenotes': var factor = args[0]; if (factor === 0) { that.errorMsg(ZERODIVIDEERRORMSG, blk); that.stopTurtle = true; } else { that.duplicateFactor[turtle] *= factor; childFlow = args[1]; childFlowCount = 1; var listenerName = '_duplicate_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.duplicateFactor[turtle] /= factor; }; that._setListener(turtle, listenerName, __listener); } break; case 'skipnotes': var factor = args[0]; if (factor === 0) { that.errorMsg(ZERODIVIDEERRORMSG, blk); that.stopTurtle = true; } else { that.skipFactor[turtle] *= factor; childFlow = args[1]; childFlowCount = 1; var listenerName = '_skip_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.skipFactor[turtle] /= factor; if (that.skipFactor[turtle] === 1) { that.skipIndex[turtle] = 0; } }; that._setListener(turtle, listenerName, __listener); } break; case 'multiplybeatfactor': var factor = args[0]; if (factor === 0) { that.errorMsg(ZERODIVIDEERRORMSG, blk); that.stopTurtle = true; } else { that.beatFactor[turtle] *= factor; childFlow = args[1]; childFlowCount = 1; var listenerName = '_multiplybeat_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.beatFactor[turtle] /= factor; }; that._setListener(turtle, listenerName, __listener); } break; case 'dividebeatfactor': var factor = args[0]; if (factor === 0) { that.errorMsg(ZERODIVIDEERRORMSG, blk); that.stopTurtle = true; } else { that.beatFactor[turtle] /= factor; childFlow = args[1]; childFlowCount = 1; var listenerName = '_dividebeat_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.beatFactor[turtle] *= factor; }; that._setListener(turtle, listenerName, __listener); } break; case 'settransposition': var transValue = args[0]; if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] -= transValue; } else { that.transposition[turtle] += transValue; } childFlow = args[1]; childFlowCount = 1; var listenerName = '_transposition_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] += transValue; } else { that.transposition[turtle] -= transValue; } }; that._setListener(turtle, listenerName, __listener); break; case 'sharp': if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] -= 1; } else { that.transposition[turtle] += 1; } childFlow = args[0]; childFlowCount = 1; var listenerName = '_sharp_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] += 1; } else { that.transposition[turtle] -= 1; } }; that._setListener(turtle, listenerName, __listener); break; case 'flat': if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] += 1; } else { that.transposition[turtle] -= 1; } childFlow = args[0]; childFlowCount = 1; var listenerName = '_flat_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (!(that.invertList[turtle].length === 0)) { that.transposition[turtle] -= 1; } else { that.transposition[turtle] += 1; } }; that._setListener(turtle, listenerName, __listener); break; case 'articulation': if (args.length === 2 && typeof(args[0]) === 'number' && args[0] > 0) { var newVolume = last(that.polyVolume[turtle]) * (100 + args[0]) / 100; if (newVolume > 100) { console.log('articulated volume exceeds 100%. clipping'); newVolume = 100; } that.polyVolume[turtle].push(newVolume); that._setSynthVolume(newVolume, turtle); if (!that.justCounting[turtle]) { lilypondBeginArticulation(that, turtle); } childFlow = args[1]; childFlowCount = 1; var listenerName = '_articulation_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.polyVolume[turtle].pop(); if (!that.justCounting[turtle]) { lilypondEndArticulation(that, turtle); } }; that._setListener(turtle, listenerName, __listener); } break; case 'setnotevolume2': if (args.length === 2 && typeof(args[0]) === 'number') { that.polyVolume[turtle].push(args[0]); that._setSynthVolume(args[0], turtle); childFlow = args[1]; childFlowCount = 1; var listenerName = '_volume_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { that.polyVolume[turtle].pop(); }; that._setListener(turtle, listenerName, __listener); } break; // Deprecated case 'setnotevolume': if (args.length === 1) { if (typeof(args[0]) === 'string') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; } else { that._setSynthVolume(args[0], turtle); } } break; // Deprecated P5 tone generator replaced by macro. case 'tone': break; case 'tone2': if (_THIS_IS_TURTLE_BLOCKS_) { if (typeof(that.turtleOscs[turtle]) === 'undefined') { that.turtleOscs[turtle] = new p5.TriOsc(); } osc = that.turtleOscs[turtle]; osc.stop(); osc.start(); osc.amp(0); osc.freq(args[0]); osc.fade(0.5, 0.2); setTimeout(function(osc) { osc.fade(0, 0.2); }, args[1], osc); } break; case 'hertz': if (args.length !== 1 || args[0] == null) { that.errorMsg(NOINPUTERRORMSG, blk); that.stopTurtle = true; break; } if (typeof(args[0]) !== 'number') { that.errorMsg(NANERRORMSG, blk); that.stopTurtle = true; break; } var obj = frequencyToPitch(args[0]); var note = obj[0]; var octave = obj[1]; var cents = obj[2]; var delta = 0; if (note === '?') { that.errorMsg(INVALIDPITCH, blk); that.stopTurtle = true; } else if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inNoteBlock[turtle] > 0) { function addPitch(note, octave, cents, frequency) { if (that.drumStyle[turtle].length > 0) { var drumname = last(that.drumStyle[turtle]); var note2 = that.getNote(note, octave, transposition, that.keySignature[turtle]); that.pitchDrumTable[turtle][note2[0] + note2[1]] = drumname; } that.notePitches[turtle].push(note); that.noteOctaves[turtle].push(octave); that.noteCents[turtle].push(cents); that.noteHertz[turtle].push(frequency); } if (!(that.invertList[turtle].length === 0)) { var len = that.invertList[turtle].length; var note1 = that.getNote(note, octave, 0, that.keySignature[turtle]); var num1 = getNumber(note1[0], note1[1]); for (var i = len - 1; i > -1; i--) { var note2 = that.getNote(that.invertList[turtle][i][0], that.invertList[turtle][i][1], 0, that.keySignature[turtle]); var num2 = getNumber(note2[0], note2[1]); // var a = getNumNote(num1, 0); if (that.invertList[turtle][i][2] === 'even') { delta += num2 - num1; } else { // odd delta += num2 - num1 + 0.5; } num1 += 2 * delta; } } addPitch(note, octave, cents, args[0]); if (turtle in that.intervals && that.intervals[turtle].length > 0) { for (var i = 0; i < that.intervals[turtle].length; i++) { var ii = getInterval(that.intervals[turtle][i], that.keySignature[turtle], note); var noteObj = that.getNote(note, octave, ii, that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } } if (turtle in that.perfect && that.perfect[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcPerfect(last(that.perfect[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } if (turtle in that.diminished && that.diminished[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcDiminished(last(that.diminished[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } if (turtle in that.augmented && that.augmented[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcAugmented(last(that.augmented[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } if (turtle in that.major && that.major[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcMajor(last(that.major[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } if (turtle in that.minor && that.minor[turtle].length > 0) { var noteObj = that.getNote(note, octave, calcMinor(last(that.minor[turtle])), that.keySignature[turtle]); addPitch(noteObj[0], noteObj[1], cents, 0); } if (turtle in that.transposition) { that.noteTranspositions[turtle].push(that.transposition[turtle] + 2 * delta); } else { that.noteTranspositions[turtle].push(2 * delta); } if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; } else if (that.inPitchStaircase) { var frequency = args[0]; var note = frequencyToPitch(args[0]); var flag = 0; for (var i = 0 ; i < that.pitchStaircase.Stairs.length; i++) { if (that.pitchStaircase.Stairs[i][2] < parseFloat(frequency)) { that.pitchStaircase.Stairs.splice(i, 0, [note[0], note[1], parseFloat(frequency)]); flag = 1; break; } if (that.pitchStaircase.Stairs[i][2] === parseFloat(frequency)) { that.pitchStaircase.Stairs.splice(i, 1, [note[0], note[1], parseFloat(frequency)]); flag = 1; break; } } if (flag === 0) { that.pitchStaircase.Stairs.push([note[0], note[1], parseFloat(frequency)]); } } else if (that.inPitchSlider) { that.pitchSlider.Sliders.push([args[0], 0, 0]); } else { that.errorMsg(_('Hertz Block: Did you mean to use a Note block?'), blk); } break; // deprecated case 'triangle': case 'sine': case 'square': case 'sawtooth': if (args.length === 1) { var obj = frequencyToPitch(args[0]); // obj[2] is cents if (that.inMatrix) { that.pitchTimeMatrix.addRowBlock(blk); if (that.pitchBlocks.indexOf(blk) === -1) { that.pitchBlocks.push(blk); } that.pitchTimeMatrix.rowLabels.push(that.blocks.blockList[blk].name); that.pitchTimeMatrix.rowArgs.push(args[0]); } else if (that.inPitchSlider) { that.pitchSlider.Sliders.push([args[0], 0, 0]); } else { that.oscList[turtle].push(that.blocks.blockList[blk].name); // We keep track of pitch and octave for notation purposes. that.notePitches[turtle].push(obj[0]); that.noteOctaves[turtle].push(obj[1]); that.noteCents[turtle].push(obj[2]); if (obj[2] !== 0) { that.noteHertz[turtle].push(pitchToFrequency(obj[0], obj[1], obj[2], that.keySignature[turtle])); } else { that.noteHertz[turtle].push(0); } if (turtle in that.transposition) { that.noteTranspositions[turtle].push(that.transposition[turtle]); } else { that.noteTranspositions[turtle].push(0); } if (turtle in that.beatFactor) { that.noteBeatValues[turtle].push(that.beatFactor[turtle]); } else { that.noteBeatValues[turtle].push(1); } that.pushedNote[turtle] = true; } } break; // Deprecated case 'playfwd': that.pitchTimeMatrix.playDirection = 1; that._runFromBlock(that, turtle, args[0]); break; // Deprecated case 'playbwd': that.pitchTimeMatrix.playDirection = -1; that._runFromBlock(that, turtle, args[0]); break; case 'stuplet': if (that.inMatrix) { that.pitchTimeMatrix.addColBlock(blk, args[0]); var noteBeatValue = (1 / args[1]) * that.beatFactor[turtle]; if (that.tuplet === true) { // The simple-tuplet block is inside. for (var i = 0; i < args[0]; i++) { if (!that.addingNotesToTuplet) { that.tupletRhythms.push(['notes', 0]); that.addingNotesToTuplet = true; } that._processNote(noteBeatValue, blk, turtle); } } else { that.tupletParams.push([1, noteBeatValue]); var obj = ['simple', 0]; for (var i = 0; i < args[0]; i++) { obj.push((1 / args[1]) * that.beatFactor[turtle]); } that.tupletRhythms.push(obj); } } break; case 'tuplet2': case 'tuplet3': if (that.inMatrix) { if (that.blocks.blockList[blk].name === 'tuplet3') { that.tupletParams.push([args[0], (1 / args[1]) * that.beatFactor[turtle]]); } else { that.tupletParams.push([args[0], args[1] * that.beatFactor[turtle]]); } that.tuplet = true; that.addingNotesToTuplet = false; } else { that.errorMsg(_('Tuplet Block: Did you mean to use a Matrix block?'), blk); } childFlow = args[2]; childFlowCount = 1; var listenerName = '_tuplet_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (that.inMatrix) { that.tuplet = false; that.addingNotesToTuplet = false; } else { } }; that._setListener(turtle, listenerName, __listener); break; case 'tuplet4': if (that.inMatrix) { that.tupletParams.push([1, (1 / args[0]) * that.beatFactor[turtle]]); that.tuplet = true; that.addingNotesToTuplet = false; } else { that.errorMsg(_('Tuplet Block: Did you mean to use a Matrix block?'), blk); } childFlow = args[1]; childFlowCount = 1; var listenerName = '_tuplet_' + turtle; that._setDispatchBlock(blk, turtle, listenerName); var __listener = function (event) { if (that.inMatrix) { that.tuplet = false; that.addingNotesToTuplet = false; } else { } }; that._setListener(turtle, listenerName, __listener); break; default: if (that.blocks.blockList[blk].name in that.evalFlowDict) { eval(that.evalFlowDict[that.blocks.blockList[blk].name]); } else { // Could be an arg block, so we need to print its value. if (that.blocks.blockList[blk].isArgBlock()) { args.push(that.parseArg(that, turtle, blk)); console.log('block: ' + blk + ' turtle: ' + turtle); console.log('block name: ' + that.blocks.blockList[blk].name); console.log('block value: ' + that.blocks.blockList[blk].value); if (that.blocks.blockList[blk].value == null) { that.textMsg('null block value'); } else { that.textMsg(that.blocks.blockList[blk].value.toString()); } } else { that.errorMsg('I do not know how to ' + that.blocks.blockList[blk].name + '.', blk); } that.stopTurtle = true; } break; } // (3) Queue block below the current block. // Is the block in a queued clamp? if (blk in that.endOfClampSignals[turtle]) { for (var i = that.endOfClampSignals[turtle][blk].length - 1; i >= 0; i--) { var signal = that.endOfClampSignals[turtle][blk][i]; if (signal != null) { that.stage.dispatchEvent(that.endOfClampSignals[turtle][blk][i]); // Mark issued signals as null that.endOfClampSignals[turtle][blk][i] = null; } } var cleanSignals = []; for (var i = 0; i < that.endOfClampSignals[turtle][blk].length; i++) { if (that.endOfClampSignals[turtle][blk][i] != null) { cleanSignals.push(that.endOfClampSignals[turtle][blk][i]); } } that.endOfClampSignals[turtle][blk] = cleanSignals; } if (docById('statusDiv').style.visibility === 'visible') { that.statusMatrix.updateAll(); } // If there is a child flow, queue it. if (childFlow != null) { if (that.blocks.blockList[blk].name==='doArg' || that.blocks.blockList[blk].name==='nameddoArg') { var queueBlock = new Queue(childFlow, childFlowCount, blk, actionArgs); } else { var queueBlock = new Queue(childFlow, childFlowCount, blk, receivedArg); } // We need to keep track of the parent block to the child // flow so we can unlightlight the parent block after the // child flow completes. that.parentFlowQueue[turtle].push(blk); that.turtles.turtleList[turtle].queue.push(queueBlock); } var nextBlock = null; var parentBlk = null; // Run the last flow in the queue. if (that.turtles.turtleList[turtle].queue.length > queueStart) { nextBlock = last(that.turtles.turtleList[turtle].queue).blk; parentBlk = last(that.turtles.turtleList[turtle].queue).parentBlk; passArg = last(that.turtles.turtleList[turtle].queue).args; // Since the forever block starts at -1, it will never === 1. if (last(that.turtles.turtleList[turtle].queue).count === 1) { // Finished child so pop it off the queue. that.turtles.turtleList[turtle].queue.pop(); } else { // Decrement the counter for repeating that flow. last(that.turtles.turtleList[turtle].queue).count -= 1; } } if (nextBlock != null) { if (parentBlk !== blk) { // The wait block waits waitTimes longer than other // blocks before it is unhighlighted. if (that.turtleDelay === TURTLESTEP) { that.unhighlightStepQueue[turtle] = blk; } else { setTimeout(function () { if (that.blocks.visible) { that.blocks.unhighlight(blk); } }, that.turtleDelay + that.waitTimes[turtle]); } } if ((that.backward[turtle].length > 0 && that.blocks.blockList[blk].connections[0] == null) || (that.backward[turtle].length === 0 && last(that.blocks.blockList[blk].connections) == null)) { // If we are at the end of the child flow, queue the // unhighlighting of the parent block to the flow. if (that.parentFlowQueue[turtle].length > 0 && that.turtles.turtleList[turtle].queue.length > 0 && last(that.turtles.turtleList[turtle].queue).parentBlk !== last(that.parentFlowQueue[turtle])) { that.unhightlightQueue[turtle].push(last(that.parentFlowQueue[turtle])); // that.unhightlightQueue[turtle].push(that.parentFlowQueue[turtle].pop()); } else if (that.unhightlightQueue[turtle].length > 0) { // The child flow is finally complete, so unhighlight. setTimeout(function () { if (that.blocks.visible) { that.blocks.unhighlight(that.unhightlightQueue[turtle].pop()); } else { that.unhightlightQueue[turtle].pop(); } }, that.turtleDelay); } } // We don't update parameter blocks when running full speed. if (that.turtleDelay !== 0) { for (var pblk in that.parameterQueue[turtle]) { that._updateParameterBlock(that, turtle, that.parameterQueue[turtle][pblk]); } } if (isflow){ that._runFromBlockNow(that, turtle, nextBlock, isflow, passArg, queueStart); } else{ that._runFromBlock(that, turtle, nextBlock, isflow, passArg); } } else { // Make sure any unissued signals are dispatched. for (var b in that.endOfClampSignals[turtle]) { for (var i = 0; i < that.endOfClampSignals[turtle][b].length; i++) { if (that.endOfClampSignals[turtle][b][i] != null) { that.stage.dispatchEvent(that.endOfClampSignals[turtle][b][i]); } } } if (turtle in that.forwardListener) { for (var b in that.forwardListener[turtle]) { for (var i = 0; i < this.forwardListener[turtle][b].length; i++) { that.stage.removeEventListener('_forward_' + turtle, that.forwardListener[turtle][b][i], false); } } } if (turtle in that.rightListener) { for (var b in that.rightListener[turtle]) { for (var i = 0; i < this.rightListener[turtle][b].length; i++) { that.stage.removeEventListener('_right_' + turtle, that.rightListener[turtle][b][i], false); } } } if (turtle in that.arcListener) { for (var b in that.arcListener[turtle]) { for (var i = 0; i < this.arcListener[turtle][b].length; i++) { that.stage.removeEventListener('_arc_' + turtle, that.arcListener[turtle][b][i], false); } } } // Make sure SVG path is closed. that.turtles.turtleList[turtle].closeSVG(); // Mark the turtle as not running. that.turtles.turtleList[turtle].running = false; if (!that.turtles.running() && queueStart === 0) { that.onStopTurtle(); } // Because flow can come from calc blocks, we are not // ensured that the turtle is really finished running // yet. Hence the timeout. __checkLilypond = function () { if (!that.turtles.running() && queueStart === 0 && that.suppressOutput[turtle] && !that.justCounting[turtle]) { console.log('saving lilypond output: ' + that.lilypondStaging); saveLilypondOutput(that, _('My Project') + '.ly'); that.suppressOutput[turtle] = false; that.checkingLilypond = false; that.runningLilypond = false; // Reset cursor. document.body.style.cursor = 'default'; } else if (that.suppressOutput[turtle]) { setTimeout(function () { __checkLilypond(); }, 250); } }; if (!that.turtles.running() && queueStart === 0 && that.suppressOutput[turtle] && !that.justCounting[turtle]) { if (!that.checkingLilypond) { that.checkingLilypond = true; setTimeout(function () { __checkLilypond(); }, 250); } } // Nothing else to do... so cleaning up. if (that.turtles.turtleList[turtle].queue.length === 0 || blk !== last(that.turtles.turtleList[turtle].queue).parentBlk) { setTimeout(function () { if (that.blocks.visible) { that.blocks.unhighlight(blk); } }, that.turtleDelay); } // Unhighlight any parent blocks still highlighted. for (var b in that.parentFlowQueue[turtle]) { if (that.blocks.visible) { that.blocks.unhighlight(that.parentFlowQueue[turtle][b]); } } // Make sure the turtles are on top. var i = that.stage.getNumChildren() - 1; that.stage.setChildIndex(that.turtles.turtleList[turtle].container, i); that.refreshCanvas(); for (var arg in that.evalOnStopList) { eval(that.evalOnStopList[arg]); } } clearTimeout(this.saveTimeout); var me = this; this.saveTimeout = setTimeout(function () { // Save at the end to save an image // console.log('in saveTimeout'); me.saveLocally(); }, DEFAULTDELAY * 1.5); }; this._setSynthVolume = function (vol, turtle) { if (vol > 100) { vol = 100; } else if (vol < 0) { vol = 0; } if (_THIS_IS_MUSIC_BLOCKS_) { this.synth.setVolume(vol); } }; this._processNote = function (noteValue, blk, turtle) { if (this.bpm[turtle].length > 0) { var bpmFactor = TONEBPM / last(this.bpm[turtle]); } else { var bpmFactor = TONEBPM / this._masterBPM; } if (this.blocks.blockList[blk].name === 'osctime') { // Convert msecs to note value. if (noteValue == 0) { var noteBeatValue = 0; } else { var noteBeatValue = (bpmFactor * 1000) / noteValue; } } else { var noteBeatValue = noteValue; } // How best to expose this in the UI? What units? this.notesPlayed[turtle] += (1 / (noteValue * this.beatFactor[turtle])); var vibratoRate = 0; var vibratoValue = 0; var vibratoIntensity = 0; var doVibrato = false; if (this.vibratoRate[turtle].length > 0) { vibratoRate = last(this.vibratoRate[turtle]); vibratoIntensity = last(this.vibratoIntensity[turtle]); doVibrato = true; } var carry = 0; if (this.crescendoDelta[turtle].length === 0) { this._setSynthVolume(last(this.polyVolume[turtle]), turtle); } if (this.inMatrix) { if (this.inNoteBlock[turtle] > 0) { this.pitchTimeMatrix.addColBlock(blk, 1); for (var i = 0; i < this.pitchBlocks.length; i++) { this.pitchTimeMatrix.addNode(this.pitchBlocks[i], blk, 0); } for (var i = 0; i < this.drumBlocks.length; i++) { this.pitchTimeMatrix.addNode(this.drumBlocks[i], blk, 0); } } noteBeatValue *= this.beatFactor[turtle]; if (this.tuplet === true) { if (this.addingNotesToTuplet) { var i = this.tupletRhythms.length - 1; this.tupletRhythms[i].push(noteBeatValue); } else { this.tupletRhythms.push(['notes', this.tupletParams.length - 1, noteBeatValue]); this.addingNotesToTuplet = true; } } else { this.tupletRhythms.push(['', 1, noteBeatValue]); } } else { // We start the music clock as the first note is being // played. if (this.firstNoteTime == null && !this.suppressOutput[turtle]) { var d = new Date(); this.firstNoteTime = d.getTime(); } // Calculate a lag: In case this turtle has fallen behind, // we need to catch up. var d = new Date(); var elapsedTime = (d.getTime() - this.firstNoteTime) / 1000; if (this.drift[turtle] === 0) { // How far behind is this turtle lagging? var turtleLag = elapsedTime - this.turtleTime[turtle]; } else { // When we are "drifting", we don't bother with lag. var turtleLag = 0; } // If we are in a tie, depending upon parity, we either // add the duration from the previous note to the current // note, or we cache the duration and set the wait to // zero. FIXME: Will not work when using dup and skip. if (this.tie[turtle]) { // We need to check to see if we are tying together // similar notes. if (this.tieCarryOver[turtle] > 0) { var match = true; if (this.tieNote[turtle].length !== this.notePitches[turtle].length) { match = false; } else { for (var i = 0; i < this.tieNote[turtle].length; i++) { if (this.tieNote[turtle][i][0] != this.notePitches[turtle][i]) { match = false; break; } if (this.tieNote[turtle][i][1] != this.noteOctaves[turtle][i]) { match = false; break; } } } if (!match) { var tmpBeatValue = this.tieCarryOver[turtle]; this.tieCarryOver[turtle] = 0; this.tie[turtle] = false; // Save the current note. var saveNote = []; for (var i = 0; i < this.notePitches[turtle].length; i++) { saveNote.push([this.notePitches[turtle][i], this.noteOctaves[turtle][i], this.noteCents[turtle][i], this.noteHertz[turtle][i]]); } // Swap in the previous note. this.notePitches[turtle] = []; this.noteOctaves[turtle] = []; this.noteCents[turtle] = []; this.noteHertz[turtle] = []; for (var i = 0; i < this.tieNote[turtle].length; i++) { this.notePitches[turtle].push(this.tieNote[turtle][i][0]); this.noteOctaves[turtle].push(this.tieNote[turtle][i][1]); this.noteCents[turtle].push(this.tieNote[turtle][i][2]); this.noteHertz[turtle].push(this.tieNote[turtle][i][3]); } this.tieNote[turtle] = []; if (!this.justCounting[turtle]) { // Remove the note from the Lilypond list. for (var i = 0; i < this.notePitches[turtle].length; i++) { lilypondRemoveTie(this, turtle); } } this._processNote(tmpBeatValue, blk, turtle); // Restore the current note. this.tie[turtle] = true; this.notePitches[turtle] = []; this.noteOctaves[turtle] = []; this.noteCents[turtle] = []; this.noteHertz[turtle] = []; for (var i = 0; i < saveNote.length; i++) { this.notePitches[turtle].push(saveNote[i][0]); this.noteOctaves[turtle].push(saveNote[i][1]); this.noteCents[turtle].push(saveNote[i][2]); this.noteHertz[turtle].push(saveNote[i][3]); } } } if (this.tieCarryOver[turtle] === 0) { this.tieNote[turtle] = []; this.tieCarryOver[turtle] = noteBeatValue; for (var i = 0; i < this.notePitches[turtle].length; i++) { this.tieNote[turtle].push([this.notePitches[turtle][i], this.noteOctaves[turtle][i], this.noteCents[turtle][i], this.noteHertz[turtle][i]]); } noteBeatValue = 0; } else { carry = this.tieCarryOver[turtle]; noteBeatValue = 1 / ((1 / noteBeatValue) + (1 / this.tieCarryOver[turtle])); this.tieCarryOver[turtle] = 0; } } // If we are in a swing, depending upon parity, we either // add the duration from the current note or we substract // duration from the next note. Swing is triggered by an // initial notevalue. When that notevalue is encountered // again, the swing terminates, e.g., 8->4->4->4->8 // 8->4->4->4->8 // FIXME: Will not work when using dup and skip. // FIXME: Could behave weirdly with tie. if (this.swing[turtle].length > 0) { // Deprecated // newswing2 takes the target as an argument if (last(this.swingTarget[turtle]) == null) { // When we start a swing we need to keep track of // the initial beat value. this.swingTarget[turtle][this.swingTarget[turtle].length - 1] = noteBeatValue; } var swingValue = last(this.swing[turtle]); // If this notevalue matches the target, either we are // starting a swing or ending a swing. if (noteBeatValue === last(this.swingTarget[turtle])) { if (this.swingCarryOver[turtle] === 0) { noteBeatValue = 1 / ((1 / noteBeatValue) + (1 / swingValue)); this.swingCarryOver[turtle] = swingValue; } else { if (noteBeatValue === swingValue) { noteBeatValue = 0; } else { noteBeatValue = 1 / ((1 / noteBeatValue) - (1 / swingValue)); } this.swingCarryOver[turtle] = 0; } if (noteBeatValue < 0) { noteBeatValue = 0; } } } // Duration is the duration of the note to be // played. doWait sets the wait time for the turtle before // the next block is executed. var duration = noteBeatValue * this.beatFactor[turtle]; // beat value if (duration > 0 && !this.suppressOutput[turtle]) { this.turtleTime[turtle] += ((bpmFactor / duration) + (this.noteDelay / 1000)) * this.duplicateFactor[turtle]; } if (!this.suppressOutput[turtle]) { if (duration > 0) { this._doWait(turtle, Math.max(((bpmFactor / duration) + (this.noteDelay / 1000)) * this.duplicateFactor[turtle] - turtleLag, 0)); } } var waitTime = 0; for (var j = 0; j < this.duplicateFactor[turtle]; j++) { if (this.skipFactor[turtle] > 1 && this.skipIndex[turtle] % this.skipFactor[turtle] > 0) { this.skipIndex[turtle] += 1; // Lessen delay time by one note since we are // skipping a note. if (duration > 0) { this.waitTimes[turtle] -= ((bpmFactor / duration) + (this.noteDelay / 1000)) * 1000; if (this.waitTimes[turtle] < 0) { this.waitTimes[turtle] = 0; } } continue; } if (this.skipFactor[turtle] > 1) { this.skipIndex[turtle] += 1; } if (!this.suppressOutput[turtle]) { if (j > 0) { if (duration > 0) { waitTime += (bpmFactor * 1000 / duration); } } } else { waitTime = 0; } __playnote = function (that) { var notes = []; var drums = []; var insideChord = -1; if ((that.notePitches[turtle].length + that.oscList[turtle].length) > 1) { if (turtle in that.lilypondStaging && !that.justCounting[turtle]) { var insideChord = that.lilypondStaging[turtle].length + 1; } else { var insideChord = 1; } } that.noteBeat[turtle] = noteBeatValue; // Do not process a note if its duration is equal // to infinity or NaN. if (!isFinite(duration)) { return; } // Use the beatValue of the first note in // the group since there can only be one. if (that.staccato[turtle].length > 0) { var staccatoBeatValue = last(that.staccato[turtle]); if (staccatoBeatValue < 0) { // slur var beatValue = bpmFactor / ((noteBeatValue) * that.noteBeatValues[turtle][0]) + bpmFactor / (-staccatoBeatValue * that.noteBeatValues[turtle][0]); } else if (staccatoBeatValue > noteBeatValue) { // staccato var beatValue = bpmFactor / (staccatoBeatValue * that.noteBeatValues[turtle][0]); } else { var beatValue = bpmFactor / (noteBeatValue * that.noteBeatValues[turtle][0]); } } else { var beatValue = bpmFactor / (noteBeatValue * that.noteBeatValues[turtle][0]); } if (doVibrato) { vibratoValue = beatValue * (duration / vibratoRate); } that._dispatchTurtleSignals(turtle, beatValue, blk, noteBeatValue); // Process pitches if (that.notePitches[turtle].length > 0) { for (var i = 0; i < that.notePitches[turtle].length; i++) { if (that.notePitches[turtle][i] === 'rest') { note = 'R'; } else { var noteObj = that.getNote(that.notePitches[turtle][i], that.noteOctaves[turtle][i], that.noteTranspositions[turtle][i], that.keySignature[turtle]); // If the cents for this note != 0, then // we need to convert to frequency and add // in the cents. if (that.noteCents[turtle][i] !== 0) { if (that.noteHertz[turtle][i] !== 0 && that.noteTranspositions[turtle][i] === 0) { var note = that.noteHertz[turtle][i]; } else { var note = Math.floor(pitchToFrequency(noteObj[0], noteObj[1], that.noteCents[turtle][i], that.keySignature[turtle])); } } else { var note = noteObj[0] + noteObj[1]; } } if (note !== 'R') { notes.push(note); } if (duration > 0) { if (carry > 0) { if (i === 0 && !that.justCounting[turtle]) { lilypondInsertTie(that, turtle); } originalDuration = 1 / ((1 / duration) - (1 / carry)); } else { originalDuration = duration; } if (!that.justCounting[turtle]) { updateLilypondNotation(that, note, originalDuration, turtle, insideChord); } } else if (that.tieCarryOver[turtle] > 0) { if (!that.justCounting[turtle]) { updateLilypondNotation(that, note, that.tieCarryOver[turtle], turtle, insideChord); } } else { // console.log('duration == ' + duration + ' and tieCarryOver === 0 and drift is ' + drift); } } if (!that.justCounting[turtle]) { console.log("notes to play " + notes + ' ' + noteBeatValue); } else { console.log("notes to count " + notes + ' ' + noteBeatValue); } if (!that.suppressOutput[turtle]) { that.turtles.turtleList[turtle].blink(duration,last(that.polyVolume[turtle])); } if (notes.length > 0) { var len = notes[0].length; // Deprecated if (typeof(notes[i]) === 'string') { that.currentNotes[turtle] = notes[0].slice(0, len - 1); that.currentOctaves[turtle] = parseInt(notes[0].slice(len - 1)); } if (that.turtles.turtleList[turtle].drum) { for (var i = 0; i < notes.length; i++) { notes[i] = notes[i].replace(/♭/g, 'b').replace(/♯/g, '#'); // 'C2'; // Remove pitch } } else { for (var i = 0; i < notes.length; i++) { if (typeof(notes[i]) === 'string') { notes[i] = notes[i].replace(/♭/g, 'b').replace(/♯/g, '#'); } } } if (!that.suppressOutput[turtle] && duration > 0) { if (_THIS_IS_MUSIC_BLOCKS_) { if (that.oscList[turtle].length > 0) { if (notes.length > 1) { that.errorMsg(last(that.oscList[turtle]) + ': ' + _('synth cannot play chords.'), blk); } that.synth.trigger(notes, beatValue, last(that.oscList[turtle]), [vibratoIntensity, vibratoValue]); } else if (that.drumStyle[turtle].length > 0) { that.synth.trigger(notes, beatValue, last(that.drumStyle[turtle]), []); } else if (that.turtles.turtleList[turtle].drum) { that.synth.trigger(notes, beatValue, 'drum', []); } else { // Look for any notes in the chord that might be in the pitchDrumTable. for (var d = 0; d < notes.length; d++) { if (notes[d] in that.pitchDrumTable[turtle]) { that.synth.trigger(notes[d], beatValue, that.pitchDrumTable[turtle][notes[d]], []); } else if (turtle in that.voices && last(that.voices[turtle])) { that.synth.trigger(notes[d], beatValue, last(that.voices[turtle]), [vibratoIntensity, vibratoValue]); } else { that.synth.trigger(notes[d], beatValue, 'default', [vibratoIntensity, vibratoValue]); } } } that.synth.start(); } } that.lastNotePlayed[turtle] = [notes[0], noteBeatValue]; that.noteStatus[turtle] = [notes, noteBeatValue]; } } // Process drums if (that.noteDrums[turtle].length > 0) { for (var i = 0; i < that.noteDrums[turtle].length; i++) { drums.push(that.noteDrums[turtle][i]); } // console.log("drums to play " + drums + ' ' + noteBeatValue); if (!that.suppressOutput[turtle] && duration > 0) { if (_THIS_IS_MUSIC_BLOCKS_) { for (var i = 0; i < drums.length; i++) { if (that.drumStyle[turtle].length > 0) { that.synth.trigger(['C2'], beatValue, last(that.drumStyle[turtle]), []); } else { that.synth.trigger(['C2'], beatValue, drums[i], []); } } } } } // Ensure note value block unhighlights after note plays. setTimeout(function () { if (that.blocks.visible) { that.blocks.unhighlight(blk); } }, beatValue * 1000); }; if (waitTime === 0 || this.suppressOutput[turtle]) { __playnote(this); } else { var that = this; setTimeout(function () { __playnote(that); }, waitTime + this.noteDelay); } if (this.crescendoDelta[turtle].length > 0) { if (last(this.crescendoVolume[turtle]) === last(this.crescendoInitialVolume[turtle]) && !this.justCounting[turtle]) { lilypondBeginCrescendo(this, turtle, last(this.crescendoDelta[turtle])); } var len = this.crescendoVolume[turtle].length this.crescendoVolume[turtle][len - 1] += this.crescendoDelta[turtle][len - 1]; this._setSynthVolume(this.crescendoVolume[turtle][len - 1], turtle); var len2 = this.polyVolume[turtle].length; this.polyVolume[turtle][len2 - 1] = this.crescendoVolume[turtle][len - 1]; } } this.pushedNote[turtle] = false; } }; this._dispatchTurtleSignals = function (turtle, beatValue, blk, noteBeatValue) { // When turtle commands (forward, right, arc) are inside of Notes, // they are progressive. var that = this; var stepTime = beatValue * 1000 / (NOTEDIV + 4); // We want to update the turtle graphics every 50ms with a note. // FIXME: Do this more efficiently if (stepTime > 200) { that.dispatchFactor[turtle] = 0.25; } else if (stepTime > 100) { that.dispatchFactor[turtle] = 0.5; } else if (stepTime > 50) { that.dispatchFactor[turtle] = 1; } else if (stepTime > 25) { that.dispatchFactor[turtle] = 2; } else if (stepTime > 12.5) { that.dispatchFactor[turtle] = 4; } else { that.dispatchFactor[turtle] = 8; } for (var t = 0; t < (NOTEDIV / that.dispatchFactor[turtle]); t++) { setTimeout(function () { if (turtle in that.forwardListener && blk in that.forwardListener[turtle]) { that.stage.dispatchEvent('_forward_' + turtle + '_' + blk); } if (turtle in that.rightListener && blk in that.rightListener[turtle]) { that.stage.dispatchEvent('_right_' + turtle + '_' + blk); } if (turtle in that.arcListener && blk in that.arcListener[turtle]) { that.stage.dispatchEvent('_arc_' + turtle + '_' + blk); } // (NOTEDIV + 4) is a workaround so that the graphics // finish a bit ahead of the note in order t minimize the // risk that there is overlap with the next note scheduled // to trigger. }, t * stepTime * that.dispatchFactor[turtle]); } setTimeout(function () { if (turtle in that.forwardListener && blk in that.forwardListener[turtle]) { for (var i = 0; i < that.forwardListener[turtle][blk].length; i++) { that.stage.removeEventListener('_forward_' + turtle + '_' + blk, that.forwardListener[turtle][blk][i], false); } delete that.forwardListener[turtle][blk]; } if (turtle in that.rightListener && blk in that.rightListener[turtle]) { for (var i = 0; i < that.rightListener[turtle][blk].length; i++) { that.stage.removeEventListener('_right_' + turtle + '_' + blk, that.rightListener[turtle][blk][i], false); } delete that.rightListener[turtle][blk]; } if (turtle in that.arcListener && blk in that.arcListener[turtle]) { for (var i = 0; i < that.arcListener[turtle][blk].length; i++) { that.stage.removeEventListener('_arc_' + turtle + '_' + blk, that.arcListener[turtle][blk][i], false); } delete that.arcListener[turtle][blk]; } }, beatValue * 1000); }; this._setListener = function (turtle, listenerName, listener) { if (listenerName in this.turtles.turtleList[turtle].listeners) { this.stage.removeEventListener(listenerName, this.turtles.turtleList[turtle].listeners[listenerName], false); } this.turtles.turtleList[turtle].listeners[listenerName] = listener; this.stage.addEventListener(listenerName, listener, false); }; this._setDispatchBlock = function (blk, turtle, listenerName) { if (this.backward[turtle].length > 0) { if (this.blocks.blockList[last(this.backward[turtle])].name === 'backward') { var c = 1; } else { var c = 2; } if (this.blocks.sameGeneration(this.blocks.blockList[last(this.backward[turtle])].connections[c], blk)) { var nextBlock = this.blocks.blockList[blk].connections[0]; this.endOfClampSignals[turtle][nextBlock] = [listenerName]; } else { var nextBlock = null; } } else { var nextBlock = last(this.blocks.blockList[blk].connections); } if (nextBlock == null) { return; } this.endOfClampSignals[turtle][nextBlock] = [listenerName]; }; this._getTargetTurtle = function (args) { // The target turtle name can be a string or an int. Make // sure there is a turtle by this name and then find the // associated start block. var targetTurtle = args[0]; // We'll compare the names as strings. if (typeof(targetTurtle) === 'number') { targetTurtle = targetTurtle.toString(); } for (var i = 0; i < this.turtles.turtleList.length; i++) { var turtleName = this.turtles.turtleList[i].name; if (typeof(turtleName) === 'number') { turtleName = turtleName.toString(); } if (turtleName === targetTurtle) { return i; } } return null; }; this._loopBlock = function (name) { return ['forever', 'repeat', 'while', 'until'].indexOf(name) !== -1; }; this._doBreak = function (turtle) { // Look for a parent loopBlock in queue and set its count to 1. var parentLoopBlock = null; var loopBlkIdx = -1; var queueLength = this.turtles.turtleList[turtle].queue.length; for (var i = queueLength - 1; i > -1; i--) { if (this._loopBlock(this.blocks.blockList[this.turtles.turtleList[turtle].queue[i].blk].name)) { // while or until loopBlkIdx = this.turtles.turtleList[turtle].queue[i].blk; parentLoopBlock = this.blocks.blockList[loopBlkIdx]; // Flush the parent from the queue. this.turtles.turtleList[turtle].queue.pop(); break; } else if (this._loopBlock(this.blocks.blockList[this.turtles.turtleList[turtle].queue[i].parentBlk].name)) { // repeat or forever loopBlkIdx = this.turtles.turtleList[turtle].queue[i].parentBlk; parentLoopBlock = this.blocks.blockList[loopBlkIdx]; // Flush the parent from the queue. this.turtles.turtleList[turtle].queue.pop(); break; } } if (parentLoopBlock == null) { // In this case, we flush the child flow. this.turtles.turtleList[turtle].queue.pop(); return; } // For while and until, we need to add any childflow from the // parent to the queue. if (parentLoopBlock.name === 'while' || parentLoopBlock.name === 'until') { var childFlow = last(parentLoopBlock.connections); if (childFlow != null) { var queueBlock = new Queue(childFlow, 1, loopBlkIdx); // We need to keep track of the parent block to the // child flow so we can unlightlight the parent block // after the child flow completes. this.parentFlowQueue[turtle].push(loopBlkIdx); this.turtles.turtleList[turtle].queue.push(queueBlock); } } }; this.parseArg = function (that, turtle, blk, parentBlk, receivedArg) { // Retrieve the value of a block. if (blk == null) { that.errorMsg('Missing argument.', parentBlk); that.stopTurtle = true; return null } if (that.blocks.blockList[blk].protoblock.parameter) { if (turtle in that.parameterQueue) { if (that.parameterQueue[turtle].indexOf(blk) === -1) { that.parameterQueue[turtle].push(blk); } } else { // console.log('turtle ' + turtle + ' has no parameterQueue'); } } if (that.blocks.blockList[blk].isValueBlock()) { if (that.blocks.blockList[blk].name === 'number' && typeof(that.blocks.blockList[blk].value) === 'string') { try { that.blocks.blockList[blk].value = Number(that.blocks.blockList[blk].value); } catch (e) { console.log(e); } } return that.blocks.blockList[blk].value; } else if (that.blocks.blockList[blk].isArgBlock() || that.blocks.blockList[blk].isArgClamp() || that.blocks.blockList[blk].isArgFlowClampBlock()) { switch (that.blocks.blockList[blk].name) { case 'loudness': if (_THIS_IS_TURTLE_BLOCKS_) { try { // DEBUGGING P5 MIC if (!that.mic.enabled) { that.mic.start(); that.blocks.blockList[blk].value = 0; } else { that.blocks.blockList[blk].value = Math.round(that.mic.getLevel() * 1000); } } catch (e) { // MORE DEBUGGING console.log(e); that.mic.start(); that.blocks.blockList[blk].value = Math.round(that.mic.getLevel() * 1000); } } else { var values = that.analyser.analyse(); var sum = 0; for(var k=0; k= Number(name)){ var value = action_args[Number(name)-1]; that.blocks.blockList[blk].value = value; }else { that.errorMsg('Invalid argument',blk); that.stopTurtle = true; } return that.blocks.blockList[blk].value; break; case 'box': var cblk = that.blocks.blockList[blk].connections[1]; var name = that.parseArg(that, turtle, cblk, blk, receivedArg); if (name in that.boxes) { that.blocks.blockList[blk].value = that.boxes[name]; } else { that.errorMsg(NOBOXERRORMSG, blk, name); that.stopTurtle = true; that.blocks.blockList[blk].value = null; } break; case 'turtlename': that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].name; break; case 'namedbox': var name = that.blocks.blockList[blk].privateData; if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, that.blocks.blockList[blk].name]); } else if (!that.updatingStatusMatrix) { if (name in that.boxes) { that.blocks.blockList[blk].value = that.boxes[name]; } else { that.errorMsg(NOBOXERRORMSG, blk, name); that.stopTurtle = true; that.blocks.blockList[blk].value = null; } } break; case 'namedarg' : var name = that.blocks.blockList[blk].privateData; var action_args = receivedArg; // If an action block with an arg is clicked, // the arg will have no value. if (action_args == null) { that.errorMsg('Invalid argument', blk); that.stopTurtle = true; that.blocks.blockList[blk].value = null; return; } if (action_args.length >= Number(name)) { var value = action_args[Number(name)-1]; that.blocks.blockList[blk].value = value; } else { that.errorMsg('Invalid argument', blk); } return that.blocks.blockList[blk].value; break; case 'sqrt': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, that.blocks.blockList[blk].name]); } else { var cblk = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk, blk, receivedArg); if (a < 0) { that.errorMsg(NOSQRTERRORMSG, blk); that.stopTurtle = true; a = -a; } that.blocks.blockList[blk].value = that._doSqrt(a); } break; case 'int': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'int']); } else { var cblk = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk, blk, receivedArg); that.blocks.blockList[blk].value = Math.floor(a); } break; case 'mod': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'mod']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doMod(a, b); } break; case 'not': var cblk = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk, blk, receivedArg); that.blocks.blockList[blk].value = !a; break; case 'greater': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = (Number(a) > Number(b)); break; case 'equal': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = (a === b); break; case 'less': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); var result = (Number(a) < Number(b)); that.blocks.blockList[blk].value = result; break; case 'random': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doRandom(a, b); break; case 'oneOf': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doOneOf(a, b); break; case 'plus': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'plus']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doPlus(a, b); } break; case 'multiply': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'multiply']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doMultiply(a, b); } break; case 'power': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'power']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doPower(a, b); } break; case 'divide': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'divide']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doDivide(a, b); } break; case 'minus': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'minus']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = that._doMinus(a, b); } break; case 'neg': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'neg']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); that.blocks.blockList[blk].value = that._doMinus(0, a); } break; case 'toascii': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'toascii']); } else { var cblk1 = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); that.blocks.blockList[blk].value = String.fromCharCode(a); } break; case 'myclick': console.log('[click' + that.turtles.turtleList[turtle].name + ']'); that.blocks.blockList[blk].value = 'click' + that.turtles.turtleList[turtle].name; break; case 'heading': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'heading']); } else { that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].orientation; } break; case 'x': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'x']); } else { that.blocks.blockList[blk].value = that.turtles.screenX2turtleX(that.turtles.turtleList[turtle].container.x); } break; case 'y': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'y']); } else { that.blocks.blockList[blk].value = that.turtles.screenY2turtleY(that.turtles.turtleList[turtle].container.y); } break; case 'xturtle': case 'yturtle': var cblk = that.blocks.blockList[blk].connections[1]; var targetTurtle = that.parseArg(that, turtle, cblk, blk, receivedArg); for (var i = 0; i < that.turtles.turtleList.length; i++) { var thisTurtle = that.turtles.turtleList[i]; if (targetTurtle === thisTurtle.name) { if (that.blocks.blockList[blk].name === 'yturtle') { that.blocks.blockList[blk].value = that.turtles.screenY2turtleY(thisTurtle.container.y); } else { that.blocks.blockList[blk].value = that.turtles.screenX2turtleX(thisTurtle.container.x); } break; } } if (i === that.turtles.turtleList.length) { that.errorMsg('Could not find turtle ' + targetTurtle, blk); that.blocks.blockList[blk].value = 0; } break; case 'bpmfactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'bpm']); } else if (that.bpm[turtle].length > 0) { that.blocks.blockList[blk].value = last(that.bpm[turtle]); } else { that.blocks.blockList[blk].value = that._masterBPM; } break; case 'staccatofactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'staccato']); } else if (that.staccato[turtle].length > 0) { that.blocks.blockList[blk].value = last(that.staccato[turtle]); } else { that.blocks.blockList[blk].value = 0; } break; case 'slurfactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'slur']); } else if (that.staccato[turtle].length > 0) { that.blocks.blockList[blk].value = -last(that.staccato[turtle]); } else { that.blocks.blockList[blk].value = 0; } break; case 'key': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'key']); } else { that.blocks.blockList[blk].value = that.keySignature[turtle]; } break; case 'consonantstepsizeup': if (that.lastNotePlayed[turtle] !== null) { var len = that.lastNotePlayed[turtle][0].length; that.blocks.blockList[blk].value = getStepSizeUp(that.keySignature[turtle], that.lastNotePlayed[turtle][0].slice(0, len - 1)); } else { that.blocks.blockList[blk].value = getStepSizeUp(that.keySignature[turtle], 'A'); } break; case 'consonantstepsizedown': if (that.lastNotePlayed[turtle] !== null) { var len = that.lastNotePlayed[turtle][0].length; that.blocks.blockList[blk].value = getStepSizeDown(that.keySignature[turtle], that.lastNotePlayed[turtle][0].slice(0, len - 1)); } else { that.blocks.blockList[blk].value = getStepSizeDown(that.keySignature[turtle], 'A'); } break; case 'transpositionfactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'transposition']); } else { that.blocks.blockList[blk].value = that.transposition[turtle]; } break; case 'duplicatefactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'duplicate']); } else { that.blocks.blockList[blk].value = that.duplicateFactor[turtle]; } break; case 'skipfactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'skip']); } else { that.blocks.blockList[blk].value = that.skipFactor[turtle]; } break; case 'notevolumefactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'volume']); } else { that.blocks.blockList[blk].value = last(that.polyVolume[turtle]); } break; case 'elapsednotes': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'elapsednotes']); } else { that.blocks.blockList[blk].value = that.notesPlayed[turtle]; } break; case 'beatfactor': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'beatfactor']); } else { that.blocks.blockList[blk].value = that.beatFactor[turtle]; } break; case 'number2pitch': case 'number2octave': var cblk = that.blocks.blockList[blk].connections[1]; var num = that.parseArg(that, turtle, cblk, blk, receivedArg); if (num != null && typeof(num) === 'number') { var obj = numberToPitch(num + that.pitchNumberOffset); if (that.blocks.blockList[blk].name === 'number2pitch') { that.blocks.blockList[blk].value = obj[0]; } else { that.blocks.blockList[blk].value = obj[1]; } } else { that.errorMsg('Invalid argument', blk); that.stopTurtle = true; } break; case 'turtlepitch': var value = null; var cblk = that.blocks.blockList[blk].connections[1]; var targetTurtle = that.parseArg(that, turtle, cblk, blk, receivedArg); for (var i = 0; i < that.turtles.turtleList.length; i++) { var thisTurtle = that.turtles.turtleList[i]; if (targetTurtle === thisTurtle.name) { if (that.lastNotePlayed[turtle] !== null) { var len = that.lastNotePlayed[turtle][0].length; var pitch = that.lastNotePlayed[turtle][0].slice(0, len - 1); var octave = parseInt(that.lastNotePlayed[turtle][0].slice(len - 1)); var obj = [pitch, octave]; } else if(that.notePitches[i].length > 0) { var obj = that.getNote(that.notePitches[i][0], that.noteOctaves[i][0], that.noteTranspositions[i][0], that.keySignature[i]); } else { console.log('Could not find a note for turtle ' + turtle); var obj = ['C', 0]; } value = pitchToNumber(obj[0], obj[1], that.keySignature[turtle]) - that.pitchNumberOffset; that.blocks.blockList[blk].value = value; } } if (value == null) { that.errorMsg('Could not find turtle ' + targetTurtle, blk); that.blocks.blockList[blk].value = 0; } break; case 'turtlenote': case 'turtlenote2': var value = null; var cblk = that.blocks.blockList[blk].connections[1]; var targetTurtle = that.parseArg(that, turtle, cblk, blk, receivedArg); for (var i = 0; i < that.turtles.turtleList.length; i++) { var thisTurtle = that.turtles.turtleList[i]; if (targetTurtle === thisTurtle.name) { if (that.lastNotePlayed[turtle] !== null) { value = that.lastNotePlayed[turtle][1]; } else if(that.notePitches[i].length > 0) { value = that.noteBeat[i]; } else { console.log('Could not find a note for turtle ' + turtle); value = -1; } if (that.blocks.blockList[blk].name === 'turtlenote') { that.blocks.blockList[blk].value = value; } else if (value !== 0) { that.blocks.blockList[blk].value = 1 / value; } else { that.blocks.blockList[blk].value = 0; } } } if (value == null) { that.errorMsg('Could not find turtle ' + targetTurtle, blk); that.blocks.blockList[blk].value = -1; } break; // Deprecated case 'currentnote': that.blocks.blockList[blk].value = that.currentNotes[turtle]; break; // Deprecated case 'currentoctave': that.blocks.blockList[blk].value = that.currentOctaves[turtle]; break; case 'color': case 'hue': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'color']); } else { that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].color; } break; case 'shade': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'shade']); } else { that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].value; } break; case 'grey': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'grey']); } else { that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].chroma; } break; case 'pensize': if (that.inStatusMatrix && that.blocks.blockList[that.blocks.blockList[blk].connections[0]].name === 'print') { that.statusFields.push([blk, 'pensize']); } else { that.blocks.blockList[blk].value = that.turtles.turtleList[turtle].stroke; } break; case 'and': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = a && b; break; case 'or': var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var a = that.parseArg(that, turtle, cblk1, blk, receivedArg); var b = that.parseArg(that, turtle, cblk2, blk, receivedArg); that.blocks.blockList[blk].value = a || b; break; case 'time': var d = new Date(); that.blocks.blockList[blk].value = (d.getTime() - that.time) / 1000; break; case 'hspace': var cblk = that.blocks.blockList[blk].connections[1]; var v = that.parseArg(that, turtle, cblk, blk, receivedArg); that.blocks.blockList[blk].value = v; break; case 'mousex': that.blocks.blockList[blk].value = that.getStageX(); break; case 'mousey': that.blocks.blockList[blk].value = that.getStageY(); break; case 'mousebutton': that.blocks.blockList[blk].value = that.getStageMouseDown(); break; case 'keyboard': that.lastKeyCode = that.getCurrentKeyCode(); that.blocks.blockList[blk].value = that.lastKeyCode; that.clearCurrentKeyCode(); break; case 'getred': case 'getgreen': case 'getblue': var colorString = that.turtles.turtleList[turtle].canvasColor; // 'rgba(255,0,49,1)' or '#ff0031' if (colorString[0] === "#") { colorString = hex2rgb(colorString.split("#")[1]); } var obj = colorString.split('('); var obj = obj[1].split(','); switch (that.blocks.blockList[blk].name) { case 'getred': that.blocks.blockList[blk].value = parseInt(Number(obj[0]) / 2.55); break; case 'getgreen': that.blocks.blockList[blk].value = parseInt(Number(obj[1]) / 2.55); break; case 'getblue': that.blocks.blockList[blk].value = parseInt(Number(obj[2]) / 2.55); break; } break; case 'getcolorpixel': var wasVisible = that.turtles.turtleList[turtle].container.visible; that.turtles.turtleList[turtle].container.visible = false; var x = that.turtles.turtleList[turtle].container.x; var y = that.turtles.turtleList[turtle].container.y; that.refreshCanvas(); var ctx = that.canvas.getContext('2d'); var imgData = ctx.getImageData(x, y, 1, 1).data; var color = searchColors(imgData[0], imgData[1], imgData[2]); if (imgData[3] === 0) { color = body.style.background.substring(body.style.background.indexOf('(') + 1, body.style.background.lastIndexOf(')')).split(/,\s*/), color = searchColors(color[0], color[1], color[2]); } that.blocks.blockList[blk].value = color; if (wasVisible) { that.turtles.turtleList[turtle].container.visible = true; } break; case 'loadFile': // No need to do anything here. break; case 'tofrequency': if (_THIS_IS_MUSIC_BLOCKS_) { var block = that.blocks.blockList[blk]; var cblk1 = that.blocks.blockList[blk].connections[1]; var cblk2 = that.blocks.blockList[blk].connections[2]; var note = that.parseArg(that, turtle, cblk1, blk, receivedArg); var octave = Math.floor(calcOctave(that.currentOctaves[turtle], that.parseArg(that, turtle, cblk2, blk, receivedArg))); block.value = Math.round(pitchToFrequency(note, octave, 0, that.keySignature[turtle])); } else { const NOTENAMES = ['A', 'B♭', 'B', 'C', 'D♭', 'D', 'E♭', 'E', 'F', 'G♭', 'G', 'A♭']; const NOTECONVERSION = {'A♯': 'B♭', 'C♯': 'D♭', 'D♯': 'E♭', 'F♯': 'G♭', 'G♯': 'A♭'}; var block = that.blocks.blockList[blk]; var cblk = block.connections[1]; var noteName = that.parseArg(that, turtle, cblk, blk, receivedArg); if (typeof(noteName) === 'string') { noteName = noteName.replace('b', '♭'); noteName = noteName.replace('#', '♯'); if (noteName in NOTECONVERSION) { noteName = NOTECONVERSION[noteName]; } var idx = NOTENAMES.indexOf(noteName); if (idx === -1) { this.errorMsg(_('Note name must be one of A, A♯, B♭, B, C, C♯, D♭, D, D♯, E♭, E, F, F♯, G♭, G, G♯ or A♭.')); block.value = 440; } else { var cblk = block.connections[2]; var octave = Math.floor(that.parseArg(that, turtle, cblk, blk, receivedArg)); if (octave < 1) { octave = 1; } if (idx > 2) { octave -= 1; // New octave starts on C } var i = octave * 12 + idx; block.value = 27.5 * Math.pow(1.05946309435929, i); } } else { block.value = 440 * Math.pow(2, (noteName - 69) / 12); } } break; case 'pop': var block = that.blocks.blockList[blk]; if (turtle in that.turtleHeaps && that.turtleHeaps[turtle].length > 0) { block.value = that.turtleHeaps[turtle].pop(); } else { that.errorMsg(_('empty heap')); block.value = null; } break; case 'indexHeap': var block = that.blocks.blockList[blk]; var cblk = that.blocks.blockList[blk].connections[1]; var a = that.parseArg(that, turtle, cblk, blk, receivedArg); if (!(turtle in that.turtleHeaps)) { that.turtleHeaps[turtle] = []; } // If index > heap length, grow the heap. while (that.turtleHeaps[turtle].length < a) { that.turtleHeaps[turtle].push(null); } block.value = that.turtleHeaps[turtle][a - 1]; break; case 'heapLength': var block = that.blocks.blockList[blk]; if (!(turtle in that.turtleHeaps)) { that.turtleHeaps[turtle] = []; } // console.log(that.turtleHeaps[turtle].length); block.value = that.turtleHeaps[turtle].length; break; case 'heapEmpty': var block = that.blocks.blockList[blk]; if (turtle in that.turtleHeaps) { block.value = (that.turtleHeaps[turtle].length === 0); } else { block.value = true; } break; case 'notecounter': var saveCountingStatus = that.justCounting[turtle]; var saveSuppressStatus = that.suppressOutput[turtle]; that.suppressOutput[turtle] = true; that.justCounting[turtle] = true; var actionArgs = []; var saveNoteCount = that.notesPlayed[turtle]; var cblk = that.blocks.blockList[blk].connections[1]; if (cblk == null) { that.blocks.blockList[blk].value = 0; } else { that.turtles.turtleList[turtle].running = true; that._runFromBlockNow(that, turtle, cblk, true, actionArgs, that.turtles.turtleList[turtle].queue.length); that.blocks.blockList[blk].value = that.notesPlayed[turtle] - saveNoteCount; that.notesPlayed[turtle] = saveNoteCount; } that.justCounting[turtle] = saveCountingStatus; that.suppressOutput[turtle] = saveSuppressStatus; break; case 'calc': var actionArgs = []; var cblk = that.blocks.blockList[blk].connections[1]; var name = that.parseArg(that, turtle, cblk, blk, receivedArg); actionArgs = receivedArg; // that.getBlockAtStartOfArg(blk); if (name in that.actions) { that.turtles.turtleList[turtle].running = true; that._runFromBlockNow(that, turtle, that.actions[name], true, actionArgs, that.turtles.turtleList[turtle].queue.length); that.blocks.blockList[blk].value = that.returns.shift(); } else { that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; case 'namedcalc': var name = that.blocks.blockList[blk].privateData; var actionArgs = []; actionArgs = receivedArg; // that.getBlockAtStartOfArg(blk); if (name in that.actions) { that.turtles.turtleList[turtle].running = true; that._runFromBlockNow(that, turtle, that.actions[name], true, actionArgs, that.turtles.turtleList[turtle].queue.length); that.blocks.blockList[blk].value = that.returns.shift(); } else { that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; case 'calcArg': var actionArgs = []; // that.getBlockAtStartOfArg(blk); if (that.blocks.blockList[blk].argClampSlots.length > 0) { for (var i = 0; i < that.blocks.blockList[blk].argClampSlots.length; i++){ var t = (that.parseArg(that, turtle, that.blocks.blockList[blk].connections[i + 2], blk, receivedArg)); actionArgs.push(t); } } var cblk = that.blocks.blockList[blk].connections[1]; var name = that.parseArg(that, turtle, cblk, blk, receivedArg); if (name in that.actions) { that.turtles.turtleList[turtle].running = true; that._runFromBlockNow(that, turtle, that.actions[name], true, actionArgs, that.turtles.turtleList[turtle].queue.length); that.blocks.blockList[blk].value = that.returns.pop(); } else { that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; case 'namedcalcArg': var name = that.blocks.blockList[blk].privateData; var actionArgs = []; // that.getBlockAtStartOfArg(blk); if (that.blocks.blockList[blk].argClampSlots.length > 0) { for (var i = 0; i < that.blocks.blockList[blk].argClampSlots.length; i++){ var t = (that.parseArg(that, turtle, that.blocks.blockList[blk].connections[i + 1], blk, receivedArg)); actionArgs.push(t); } } if (name in that.actions) { // Just run the stack. that.turtles.turtleList[turtle].running = true; that._runFromBlockNow(that, turtle, that.actions[name], true, actionArgs, that.turtles.turtleList[turtle].queue.length); that.blocks.blockList[blk].value = that.returns.pop(); } else { that.errorMsg(NOACTIONERRORMSG, blk, name); that.stopTurtle = true; } break; case 'doArg': return blk; break; case 'nameddoArg': return blk; break; case 'returnValue': if (that.returns.length > 0) { that.blocks.blockList[blk].value = that.returns.pop(); } else { console.log('WARNING: No return value.'); that.blocks.blockList[blk].value = 0; } break; default: if (that.blocks.blockList[blk].name in that.evalArgDict) { eval(that.evalArgDict[that.blocks.blockList[blk].name]); } else { console.log('ERROR: I do not know how to ' + that.blocks.blockList[blk].name); } break; } return that.blocks.blockList[blk].value; } else { return blk; } }; this._doWait = function (turtle, secs) { this.waitTimes[turtle] = Number(secs) * 1000; }; // Math functions this._doRandom = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Math.floor(Math.random() * (Number(b) - Number(a) + 1) + Number(a)); }; this._doOneOf = function (a, b) { if (Math.random() < 0.5) { return a; } else { return b; } }; this._doMod = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Number(a) % Number(b); }; this._doSqrt = function (a) { if (typeof(a) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Math.sqrt(Number(a)); }; this._doPlus = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { if (typeof(a) === 'string') { var aString = a; } else { var aString = a.toString(); } if (typeof(b) === 'string') { var bString = b; } else { var bString = b.toString(); } return aString + bString; } else { return Number(a) + Number(b); } }; this._doMinus = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Number(a) - Number(b); }; this._doMultiply = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Number(a) * Number(b); }; this._doPower = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } return Math.pow(a, b); }; this._doDivide = function (a, b) { if (typeof(a) === 'string' || typeof(b) === 'string') { this.errorMsg(NANERRORMSG); this.stopTurtle = true; return 0; } if (Number(b) === 0) { this.errorMsg(ZERODIVIDEERRORMSG); this.stopTurtle = true; return 0; } else { return Number(a) / Number(b); } }; this.setBackgroundColor = function (turtle) { /// Change body background in DOM to current color. if (turtle === -1) { var c = platformColor.background; } else { var c = this.turtles.turtleList[turtle].canvasColor; } docById('myCanvas').style.background = c; this.svgOutput = ''; }; this.setCameraID = function (id) { this.cameraID = id; }; this.hideBlocks = function () { // Hide all the blocks. this.blocks.hide(); this.refreshCanvas(); }; this.showBlocks = function () { // Show all the blocks. this.blocks.show(); this.blocks.bringToTop(); this.refreshCanvas(); }; this.getNote = function (solfege, octave, transposition, keySignature) { this.validNote = true; var sharpFlat = false; octave = Math.round(octave); transposition = Math.round(transposition); if (typeof(solfege) === 'number') { solfege = solfege.toString(); } // Check for double flat or double sharp. var len = solfege.length; if (len > 2) { var lastTwo = solfege.slice(len - 2); if (lastTwo === 'bb' || lastTwo === '♭♭') { solfege = solfege.slice(0, len - 1); transposition -= 1; } else if (lastTwo === '##' || lastTwo === '♯♯') { solfege = solfege.slice(0, len - 1); transposition += 1; } else if (lastTwo === '#b' || lastTwo === '♯♭' || lastTwo === 'b#' || lastTwo === '♭♯') { // Not sure this could occur... but just in case. solfege = solfege.slice(0, len - 2); } } // Already a note? No need to convert from solfege. if (solfege in BTOFLAT) { solfege = BTOFLAT[solfege]; } else if (solfege in STOSHARP) { solfege = STOSHARP[solfege]; } if (solfege in EXTRATRANSPOSITIONS) { octave += EXTRATRANSPOSITIONS[solfege][1]; note = EXTRATRANSPOSITIONS[solfege][0]; } else if (NOTESSHARP.indexOf(solfege.toUpperCase()) !== -1) { note = solfege.toUpperCase(); } else if (NOTESFLAT.indexOf(solfege) !== -1) { note = solfege; } else if (NOTESFLAT2.indexOf(solfege) !== -1) { // Convert to uppercase, e.g., d♭ -> D♭. note = NOTESFLAT[notesFlat2.indexOf(solfege)]; } else { // Not a note, so convert from Solfege. // Could be mi#4 (from matrix) or mi# (from note). if (solfege.substr(-1) === '>') { // Read octave and solfege from HTML octave = parseInt(solfege.slice(solfege.indexOf('>') + 1, solfege.indexOf('/') - 1)); solfege = solfege.substr(0, solfege.indexOf('<')); } if(['#', '♯', '♭', 'b'].indexOf(solfege.substr(-1)) !== -1) { sharpFlat = true; } if (!keySignature) { keySignature = 'C'; } var obj = getScaleAndHalfSteps(keySignature); var thisScale = obj[0]; var halfSteps = obj[1]; var myKeySignature = obj[2]; var mode = obj[3]; // Ensure it is a valid key signature. offset = thisScale.indexOf(myKeySignature); if (offset === -1) { console.log('WARNING: Key ' + myKeySignature + ' not found in ' + thisScale + '. Using default of C'); var offset = 0; var thisScale = NOTESSHARP; } if (sharpFlat) { if (solfege.substr(-1) === '#') { offset += 1; } else if (solfege.substr(-1) === '♯') { offset += 1; } else if (solfege.substr(-1) === '♭') { offset -= 1; } else if(solfege.substr(-1) === 'b') { offset -= 1; } } // Reverse any i18n // solfnotes_ is used in the interface for i18n //.TRANS: the note names must be separated by single spaces var solfnotes_ = _('ti la sol fa mi re do').split(' '); if (solfnotes_.indexOf(solfege.substr(0, 2).toLowerCase()) !== -1) { var solfegePart = SOLFNOTES[solfnotes_.indexOf(solfege.substr(0, 2).toLowerCase())]; } else if (solfnotes_.indexOf(solfege.substr(0, 3).toLowerCase()) !== -1) { var solfegePart = SOLFNOTES[solfnotes_.indexOf(solfege.substr(0, 3).toLowerCase())]; } else { var solfegePart = solfege.substr(0, 2).toLowerCase(); } if (solfege.toLowerCase().substr(0, 4) === 'rest') { return ['R', '']; } else if (halfSteps.indexOf(solfegePart) !== -1) { var index = halfSteps.indexOf(solfegePart) + offset; if (index > 11) { index -= 12; octave += 1; } note = thisScale[index]; } else { console.log('WARNING: Note ' + solfege + ' not found in ' + halfSteps + '. Returning REST'); // this.validNote = false; this.errorMsg(INVALIDPITCH, null); return ['R', '']; } if (note in EXTRATRANSPOSITIONS) { octave += EXTRATRANSPOSITIONS[note][1]; note = EXTRATRANSPOSITIONS[note][0]; } } if (transposition && transposition !== 0) { if (transposition < 0) { deltaOctave = -Math.floor(-transposition / 12); deltaNote = -(-transposition % 12); } else { deltaOctave = Math.floor(transposition / 12); deltaNote = transposition % 12; } octave += deltaOctave; if (NOTESSHARP.indexOf(note) !== -1) { i = NOTESSHARP.indexOf(note); i += deltaNote; if (i < 0) { i += 12; octave -= 1; } else if (i > 11) { i -= 12; octave += 1; } note = NOTESSHARP[i]; } else if (NOTESFLAT.indexOf(note) !== -1) { i = NOTESFLAT.indexOf(note); i += deltaNote; if (i < 0) { i += 12; octave -= 1; } else if (i > 11) { i -= 12; octave += 1; } note = NOTESFLAT[i]; } else { console.log('note not found? ' + note); } } if (octave < 0) { return [note, 0]; } else if (octave > 10) { return [note, 10]; } else { return [note, octave]; } }; };