From 79a99aa7e17c02bc8718407164420180dcf947e1 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 10 Feb 2015 02:50:37 -0500 Subject: [PATCH 01/68] add clone function add function to clone a turtle. --- jquery-turtle.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 691726a..e23bd8b 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6444,6 +6444,34 @@ var turtlefn = { }); return this; }), + clone: wrapcommand('clone', 0, + ["clone() clones the turtle. "+ + "Creates another turtle, puts it in the sameplace and rotation "+ + "as this turtle, and gives it the same pen. "], + function clone(cc) { + t2 = new Turtle(); + t2.hide() + sync(t2, this); + this.plan(function(j, elem) { //t2.plan doesn't work here for some subtle reason - + //doesn't pick up turtle's properties correctly. + //but turtle.plan means t2 doesn't refer to the right turtle + //by the time the plan comes around. + // can pass turtles as arguments to plan? + cc.appear(j); + var pos = this.css('turtlePosition'), + rot = this.css('turtleRotation'), + pen = this.css('turtlePenStyle'), + pendown = this.css('turtlePenDown') + t2.css({turtlePosition: pos, turtleRotation:rot, + turtlePenStyle:pen, turtlePenDown:pendown}); + t2.show(); + cc.resolve(j); + }); + t2.plan(function(j, elem) { // j does not apply here? + sync(t2, this); // otherwise things get added to t2's queue before it has 'appeared' + }); + return t2; + }), pen: wrapcommand('pen', 1, ["pen(color, size) Selects a pen. " + "Chooses a color and/or size for the pen: " + @@ -9712,6 +9740,7 @@ try { screen.width >= 800 && screen.height >= 600 && parent && parent.ide); } catch(e) {} +var panel = true; // TODO: get rid of var see; // defined below. var paneltitle = ''; var logconsole = null; From bd76d72c9894f76cb33ba94a108bd9a01f420c8f Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 10 Feb 2015 17:04:42 -0500 Subject: [PATCH 02/68] new copy/clone method - first draft --- jquery-turtle.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index e23bd8b..30846b4 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6452,24 +6452,18 @@ var turtlefn = { t2 = new Turtle(); t2.hide() sync(t2, this); - this.plan(function(j, elem) { //t2.plan doesn't work here for some subtle reason - - //doesn't pick up turtle's properties correctly. - //but turtle.plan means t2 doesn't refer to the right turtle - //by the time the plan comes around. - // can pass turtles as arguments to plan? - cc.appear(j); + this.plan(function(othert) { //t2.plan doesn't work here - + //always applies to the most recent clone at the time of execution of plan! + //cc.appear(j); var pos = this.css('turtlePosition'), rot = this.css('turtleRotation'), pen = this.css('turtlePenStyle'), - pendown = this.css('turtlePenDown') - t2.css({turtlePosition: pos, turtleRotation:rot, + pendown = this.css('turtlePenDown') //should just copy all the things? + othert.css({turtlePosition: pos, turtleRotation:rot, turtlePenStyle:pen, turtlePenDown:pendown}); - t2.show(); - cc.resolve(j); - }); - t2.plan(function(j, elem) { // j does not apply here? - sync(t2, this); // otherwise things get added to t2's queue before it has 'appeared' - }); + othert.show(); + //cc.resolve(j); + }, [t2]); // pass in our current clone, otherwise things get applied to the wrong clone return t2; }), pen: wrapcommand('pen', 1, From fb34189cd3bf7a5cf016c491699f786e6ebb2847 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 10 Feb 2015 17:50:46 -0500 Subject: [PATCH 03/68] fixes for copy rename from clone to copy, use jquery's clone, copy over all the css (theoretically) and turtle data. reorder things a bit. --- jquery-turtle.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 30846b4..c528864 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6444,26 +6444,32 @@ var turtlefn = { }); return this; }), - clone: wrapcommand('clone', 0, - ["clone() clones the turtle. "+ - "Creates another turtle, puts it in the sameplace and rotation "+ - "as this turtle, and gives it the same pen. "], + copy: wrapcommand('copy', 0, + ["copy() makes a new turtle that is a copy of this turtle."], function clone(cc) { - t2 = new Turtle(); - t2.hide() - sync(t2, this); + t2 = this.clone().insertAfter(this); + t2.hide(); this.plan(function(othert) { //t2.plan doesn't work here - //always applies to the most recent clone at the time of execution of plan! //cc.appear(j); - var pos = this.css('turtlePosition'), - rot = this.css('turtleRotation'), - pen = this.css('turtlePenStyle'), - pendown = this.css('turtlePenDown') //should just copy all the things? - othert.css({turtlePosition: pos, turtleRotation:rot, - turtlePenStyle:pen, turtlePenDown:pendown}); + + olddata = getTurtleData(this); + newdata = getTurtleData(othert); + for (k in olddata) { newdata[k] = olddata[k]; } + + othert.attr('style', this.attr('style')); + // Theoretically, I shouldn't have to do the following, since + // that should have gotten copied over with the other attributes. + // But nope. + var pendown = this.css('turtlePenDown'), + pen = this.css('turtlePenStyle') + othert.css({turtlePenStyle:pen, turtlePenDown:pendown}) + othert.show(); + //cc.resolve(j); }, [t2]); // pass in our current clone, otherwise things get applied to the wrong clone + sync(t2, this); return t2; }), pen: wrapcommand('pen', 1, From 47d55f0f72c79ff33f56fa311ef411a5d7f65899 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 10 Feb 2015 17:57:39 -0500 Subject: [PATCH 04/68] removed panel again --- jquery-turtle.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index c528864..e21ba63 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9740,7 +9740,6 @@ try { screen.width >= 800 && screen.height >= 600 && parent && parent.ide); } catch(e) {} -var panel = true; // TODO: get rid of var see; // defined below. var paneltitle = ''; var logconsole = null; From 0c6c454c02a765a66092eb5b6fbe285631cb14ed Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 17 Feb 2015 18:59:37 -0500 Subject: [PATCH 05/68] Make t2 local variable Now the callbacks work properly without having to pass the second turtle! Also, rename actual copy function. --- jquery-turtle.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index e21ba63..e83c924 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6446,29 +6446,29 @@ var turtlefn = { }), copy: wrapcommand('copy', 0, ["copy() makes a new turtle that is a copy of this turtle."], - function clone(cc) { - t2 = this.clone().insertAfter(this); + function copy(cc) { + var t2 = this.clone().insertAfter(this); t2.hide(); - this.plan(function(othert) { //t2.plan doesn't work here - + this.plan(function(j, elem) { //t2.plan doesn't work here - //always applies to the most recent clone at the time of execution of plan! - //cc.appear(j); + cc.appear(j); olddata = getTurtleData(this); - newdata = getTurtleData(othert); + newdata = getTurtleData(t2); for (k in olddata) { newdata[k] = olddata[k]; } - othert.attr('style', this.attr('style')); + t2.attr('style', this.attr('style')); // Theoretically, I shouldn't have to do the following, since // that should have gotten copied over with the other attributes. // But nope. var pendown = this.css('turtlePenDown'), pen = this.css('turtlePenStyle') - othert.css({turtlePenStyle:pen, turtlePenDown:pendown}) + t2.css({turtlePenStyle:pen, turtlePenDown:pendown}) - othert.show(); + t2.show(); - //cc.resolve(j); - }, [t2]); // pass in our current clone, otherwise things get applied to the wrong clone + cc.resolve(j); + }); // pass in our current clone, otherwise things get applied to the wrong clone sync(t2, this); return t2; }), From ccae62ecd02b115a75ec98b528179f1f5aa84432 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 3 Mar 2015 18:08:11 -0500 Subject: [PATCH 06/68] add new function name to globals test --- test/globals.html | 1 + 1 file changed, 1 insertion(+) diff --git a/test/globals.html b/test/globals.html index 244c50b..cca74b9 100644 --- a/test/globals.html +++ b/test/globals.html @@ -38,6 +38,7 @@ "cg", "ct", "canvas", + "copy", "sizexy", "tick", "forever", From d000d86ef2f80aa1e944ddbcd7dd9f9ede1ec8a1 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 3 Mar 2015 18:34:17 -0500 Subject: [PATCH 07/68] Added copying the turtle's canvas --- jquery-turtle.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index e83c924..26acc61 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6453,17 +6453,27 @@ var turtlefn = { //always applies to the most recent clone at the time of execution of plan! cc.appear(j); + //copy over turtle data: olddata = getTurtleData(this); newdata = getTurtleData(t2); for (k in olddata) { newdata[k] = olddata[k]; } + // copy over style attributes: t2.attr('style', this.attr('style')); // Theoretically, I shouldn't have to do the following, since // that should have gotten copied over with the other attributes. // But nope. + var pendown = this.css('turtlePenDown'), - pen = this.css('turtlePenStyle') - t2.css({turtlePenStyle:pen, turtlePenDown:pendown}) + pen = this.css('turtlePenStyle'); + t2.css({turtlePenStyle:pen, turtlePenDown:pendown}); + + // copy the canvas: + t2.canvas().width = this.canvas().width; + t2.canvas().height = this.canvas().height; + + newCanvasContext = t2.canvas().getContext('2d'); + newCanvasContext.drawImage(this.canvas(), 0, 0) t2.show(); From a739f3680dbffe52193be370e08599f9dbe36bca Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 3 Mar 2015 19:53:49 -0500 Subject: [PATCH 08/68] Copy more types of things! all css properties, all attributes(although those may be redundant at this point) --- jquery-turtle.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 26acc61..98b3ea4 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6460,13 +6460,19 @@ var turtlefn = { // copy over style attributes: t2.attr('style', this.attr('style')); - // Theoretically, I shouldn't have to do the following, since - // that should have gotten copied over with the other attributes. - // But nope. - var pendown = this.css('turtlePenDown'), - pen = this.css('turtlePenStyle'); - t2.css({turtlePenStyle:pen, turtlePenDown:pendown}); + // copy each thing listed in css hooks: + for(property in $.cssHooks) { + var value = this.css(property); + t2.css(property, value); + } + + // copy attributes, just in case: + var attrs = this.prop("attributes"); + //console.log(attrs) + for(i in attrs) { + t2.attr(attrs[i].name, attrs[i].value); + } // copy the canvas: t2.canvas().width = this.canvas().width; From 08d6353325f448ce5909ab742fc045479acacbbc Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 15 Apr 2015 16:57:15 -0400 Subject: [PATCH 09/68] Easy image printer. --- jquery-turtle.js | 34 ++++++++++++++++++++++++++++++++++ test/globals.html | 1 + test/img.html | 19 +++++++++++++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 73683cc..e585e4f 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2651,6 +2651,13 @@ function apiUrl(url, topdir) { } return result; } +// Creates an image url from a potentially short name. +function imgUrl(url) { + if (/\//.test(url)) { return url; } + url = '/img/' + url; + if (isPencilHost(window.location.hostname)) { return url; } + return '//pencil.io' + url; +} // Retrieves the pencil code login cookie, if there is one. function loginCookie() { if (!document.cookie) return null; @@ -7869,6 +7876,12 @@ var dollar_turtle_methods = { "Each nested array is a row: " + "table [[1,2,3],[4,5,6]]"], doOutput, prepareTable), + img: wrapglobalcommand('img', + ["img(url) Writes an image with the given address. " + + "Any URL can be provided. A name without slashes will be " + + "treated as '/img/name'." + + "t = img 'tree'"], + doOutput, prepareImage), random: wrapraw('random', ["random(n) Random non-negative integer less than n: " + "write random 10", @@ -9379,6 +9392,27 @@ function prepareInput(name, callback, numeric) { }; } +////////////////////////////////////////////////////////////////////////// +// IMAGE PRINTER +////////////////////////////////////////////////////////////////////////// + +// Simplify creation of images. +function prepareImage(url, options) { + if ($.isNumeric(options)) { + options = { height: options }; + } + var result = $(''); + if (url) { + result.attr('src', imgUrl(url)); + } + if (options) { + result.css(options); + } + return { + result: result + }; +} + ////////////////////////////////////////////////////////////////////////// // TABLE PRINTER ////////////////////////////////////////////////////////////////////////// diff --git a/test/globals.html b/test/globals.html index 244c50b..21039e2 100644 --- a/test/globals.html +++ b/test/globals.html @@ -61,6 +61,7 @@ "random", "hatch", "button", + "img", "table", "rgb", "rgba", diff --git a/test/img.html b/test/img.html index da97633..3f3d734 100644 --- a/test/img.html +++ b/test/img.html @@ -2,17 +2,25 @@ + - +
+ + + + +
+ From 37e7f6f68a287963c2a7a56318dc1432e1c31774 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 21 Apr 2015 16:35:29 -0400 Subject: [PATCH 11/68] Make sure canvas(es) exist before doing something to them --- jquery-turtle.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 98b3ea4..9435f55 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6475,11 +6475,15 @@ var turtlefn = { } // copy the canvas: - t2.canvas().width = this.canvas().width; - t2.canvas().height = this.canvas().height; + var t2canvas = t2.canvas(); + var tcanvas = this.canvas(); + if(t2canvas && tcanvas) { + t2canvas.width = tcanvas.width; + t2canvas.height = tcanvas.height; + newCanvasContext = t2canvas.getContext('2d'); + newCanvasContext.drawImage(tcanvas, 0, 0) + } - newCanvasContext = t2.canvas().getContext('2d'); - newCanvasContext.drawImage(this.canvas(), 0, 0) t2.show(); From 4eedd4e022ab0281ff0c7fdcad7ec92ac4036c2c Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 21 Apr 2015 17:05:53 -0400 Subject: [PATCH 12/68] add timeout to sprite copy test, so it passes in grunt --- jquery-turtle.js | 1 - test/copy.html | 21 ++++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9435f55..0417a02 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6484,7 +6484,6 @@ var turtlefn = { newCanvasContext.drawImage(tcanvas, 0, 0) } - t2.show(); cc.resolve(j); diff --git a/test/copy.html b/test/copy.html index 1c3857b..c6d6e26 100644 --- a/test/copy.html +++ b/test/copy.html @@ -30,13 +30,20 @@ asyncTest("Make a red sprite and clone it.", function() { speed(Infinity); + home() s1 = new Sprite({color:'red'}); - s2 = s1.copy(); - s2.fd(200); - s1.remove(); - fd(200) - drawon(s2); - ok(touches(red)); - start(); + setTimeout(function(){ + s2 = s1.copy(); + s2.fd(200); + s1.remove(); + moveto(128, 72) + drawon(s2); + s2.clip(); + ok(touches(red)); + x = s2.imagedata(); + console.log(x.data[0]) + start(); + }, 10); + }); From 49cbbd4920630937db58768dbf95c1dc2b93c69a Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 21 Apr 2015 17:14:59 -0400 Subject: [PATCH 13/68] increase the timeout for the copy sprite test --- test/copy.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/copy.html b/test/copy.html index c6d6e26..8a57c26 100644 --- a/test/copy.html +++ b/test/copy.html @@ -43,7 +43,7 @@ x = s2.imagedata(); console.log(x.data[0]) start(); - }, 10); + }, 100); }); From 0360e30f96af992f8fad3a62ba221f54391c5047 Mon Sep 17 00:00:00 2001 From: yanamal Date: Tue, 21 Apr 2015 17:23:28 -0400 Subject: [PATCH 14/68] get rid of console.log --- test/copy.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/copy.html b/test/copy.html index 8a57c26..8b8e1c8 100644 --- a/test/copy.html +++ b/test/copy.html @@ -34,14 +34,13 @@ s1 = new Sprite({color:'red'}); setTimeout(function(){ s2 = s1.copy(); + s2.clip(); s2.fd(200); s1.remove(); moveto(128, 72) drawon(s2); - s2.clip(); ok(touches(red)); x = s2.imagedata(); - console.log(x.data[0]) start(); }, 100); From 683455950ff0020f772f060376cda61e8de1e8be Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 28 Apr 2015 08:37:22 -0400 Subject: [PATCH 15/68] Disconnect event handlers on stop. --- jquery-turtle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index e585e4f..f31c0b5 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7612,6 +7612,9 @@ var dollar_turtle_methods = { $('.turtleinput').prop('disabled', true); // Detach all event handlers on the window. $(window).off('.turtleevent'); + // Low-level detach all jQuery events + $('*').not('#_testpanel *').map( + function(i, e) { $._data(e, 'events', null) }); // Set a flag that will cause all commands to throw. interrupted = true; // Turn off the global tick interval timer. From 4b6dc78162a290b4646d4ccad2a19a2682d41ce0 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 5 May 2015 17:35:45 -0400 Subject: [PATCH 16/68] Remove some trailing spaces. --- jquery-turtle.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 5e0b27d..bbcba07 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6455,31 +6455,31 @@ var turtlefn = { function copy(cc) { var t2 = this.clone().insertAfter(this); t2.hide(); - this.plan(function(j, elem) { //t2.plan doesn't work here - - //always applies to the most recent clone at the time of execution of plan! + // t2.plan doesn't work here. + this.plan(function(j, elem) { cc.appear(j); - + //copy over turtle data: olddata = getTurtleData(this); newdata = getTurtleData(t2); for (k in olddata) { newdata[k] = olddata[k]; } - + // copy over style attributes: t2.attr('style', this.attr('style')); - + // copy each thing listed in css hooks: for(property in $.cssHooks) { var value = this.css(property); t2.css(property, value); } - + // copy attributes, just in case: var attrs = this.prop("attributes"); //console.log(attrs) for(i in attrs) { t2.attr(attrs[i].name, attrs[i].value); } - + // copy the canvas: var t2canvas = t2.canvas(); var tcanvas = this.canvas(); @@ -6489,9 +6489,9 @@ var turtlefn = { newCanvasContext = t2canvas.getContext('2d'); newCanvasContext.drawImage(tcanvas, 0, 0) } - + t2.show(); - + cc.resolve(j); }); // pass in our current clone, otherwise things get applied to the wrong clone sync(t2, this); From bf0e1f808361e21a7e3baf53e90977077bbd8888 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 17 May 2015 10:14:26 -0400 Subject: [PATCH 17/68] Show x y tool even without panel. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index bbcba07..7ead782 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9611,7 +9611,7 @@ debug.init(); // X Y coordinate showing support ////////////////////////////////////////////////////////////////////////// (function() { - if (!debug.ide || (debug.ide.getOptions && !debug.ide.getOptions().panel)) { + if (!debug.ide) { // Only show the X-Y tip if inside a debugging IDE. return; } From 9209b5bb05c347312aa7ef591dc8eed135013df5 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 18 May 2015 20:44:16 -0400 Subject: [PATCH 18/68] Upgrade libraries, and remove leaked global variables. --- Gruntfile.js | 13 ++++++++++++- jquery-turtle.js | 27 +++++++++++++-------------- package.json | 18 +++++++++--------- test/autoscroll.html | 1 + test/copy.html | 10 +++++----- test/inputoutput.html | 7 ++++--- test/pen.html | 1 + test/remove.html | 4 ++++ 8 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b2f1735..12f17ea 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,13 @@ +var serveNoDottedFiles = function(connect, options, middlewares) { + // Avoid leaking .git/.svn or other dotted files from test servers. + middlewares.unshift(function(req, res, next) { + if (req.url.indexOf('/.') < 0) { return next(); } + res.statusCode = 404 + res.end("Cannot GET " + req.url) + }); + return middlewares; +}; + module.exports = function(grunt) { "use strict"; @@ -21,7 +31,8 @@ module.exports = function(grunt) { connect: { testserver: { options: { - hostname: '0.0.0.0' + hostname: '0.0.0.0', + middleware: serveNoDottedFiles } } }, diff --git a/jquery-turtle.js b/jquery-turtle.js index 7ead782..c1df409 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2183,7 +2183,7 @@ function touchesPixel(elem, color) { } octx.restore(); // Now examine the results and look for alpha > 0%. - data = octx.getImageData(0, 0, w, h).data; + var data = octx.getImageData(0, 0, w, h).data; if (!rgba || rgba[3] == 0) { // Handle the "looking for any color" and "transparent" cases. var wantcolor = !rgba; @@ -6411,7 +6411,7 @@ var turtlefn = { if (!nlocalxy) { nlocalxy = computePositionAsLocalOffset(elem, targetpos); } - dir = radiansToDegrees(Math.atan2(-nlocalxy[0], -nlocalxy[1])); + var dir = radiansToDegrees(Math.atan2(-nlocalxy[0], -nlocalxy[1])); ts = readTurtleTransform(elem, true); if (!(limit === null)) { r = convertToRadians(ts.rot); @@ -6460,40 +6460,39 @@ var turtlefn = { cc.appear(j); //copy over turtle data: - olddata = getTurtleData(this); - newdata = getTurtleData(t2); - for (k in olddata) { newdata[k] = olddata[k]; } + var olddata = getTurtleData(this); + var newdata = getTurtleData(t2); + for (var k in olddata) { newdata[k] = olddata[k]; } // copy over style attributes: t2.attr('style', this.attr('style')); // copy each thing listed in css hooks: - for(property in $.cssHooks) { + for (var property in $.cssHooks) { var value = this.css(property); t2.css(property, value); } // copy attributes, just in case: var attrs = this.prop("attributes"); - //console.log(attrs) - for(i in attrs) { + for (var i in attrs) { t2.attr(attrs[i].name, attrs[i].value); } // copy the canvas: var t2canvas = t2.canvas(); var tcanvas = this.canvas(); - if(t2canvas && tcanvas) { + if (t2canvas && tcanvas) { t2canvas.width = tcanvas.width; t2canvas.height = tcanvas.height; - newCanvasContext = t2canvas.getContext('2d'); + var newCanvasContext = t2canvas.getContext('2d'); newCanvasContext.drawImage(tcanvas, 0, 0) } t2.show(); cc.resolve(j); - }); // pass in our current clone, otherwise things get applied to the wrong clone + }); // pass in our current clone, otherwise things apply to the wrong clone sync(t2, this); return t2; }), @@ -10695,14 +10694,14 @@ function transparentHull(image, threshold) { if (!threshold) threshold = 0; c.width = image.width; c.height = image.height; - ctx = c.getContext('2d'); + var ctx = c.getContext('2d'); ctx.drawImage(image, 0, 0); return transparentCanvasHull(c, threshold); } function transparentCanvasHull(canvas, threshold) { var ctx = canvas.getContext('2d'); - data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; var hull = []; var intthresh = 256 * threshold; var first, last, prevfirst = Infinity, prevlast = -1; @@ -10734,7 +10733,7 @@ function transparentCanvasHull(canvas, threshold) { function eraseOutsideHull(canvas, hull) { var ctx = canvas.getContext('2d'), w = canvas.width, - h = canvas.height + h = canvas.height, j = 0; ctx.save(); // Erase everything outside clipping region. diff --git a/package.json b/package.json index 907a6ff..1e5e71a 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,14 @@ }, "homepage": "https://github.com/PencilCode/jquery-turtle", "devDependencies": { - "grunt": "0.4.2", - "grunt-bowercopy": "0.4.1", - "grunt-cli": "0.1.11", - "grunt-contrib-uglify": "0.2.7", - "grunt-contrib-qunit": "0.3.0", - "grunt-release": "0.6.0", - "phantomjs": "1.9.12", - "grunt-contrib-connect": "~0.6.0", - "grunt-contrib-watch": "~0.5.3" + "grunt": "latest", + "grunt-bowercopy": "latest", + "grunt-cli": "latest", + "grunt-contrib-uglify": "latest", + "grunt-contrib-qunit": "latest", + "grunt-release": "latest", + "phantomjs": "latest", + "grunt-contrib-connect": "latest", + "grunt-contrib-watch": "latest" } } diff --git a/test/autoscroll.html b/test/autoscroll.html index 81ff91b..f9cf865 100644 --- a/test/autoscroll.html +++ b/test/autoscroll.html @@ -26,6 +26,7 @@ if (/phantom/i.test(navigator.userAgent)) { // PhantomJS doesn't correctly deal with full-window scrolling. // https://github.com/ariya/phantomjs/issues/10619 + delete parent.window.dotest; start(); } else { var urect = fw.$('.last').get(0).getBoundingClientRect(); diff --git a/test/copy.html b/test/copy.html index 8b8e1c8..d659aea 100644 --- a/test/copy.html +++ b/test/copy.html @@ -13,8 +13,8 @@ asyncTest("Copy a turtle, then move the original away from the copy.", function() { speed(Infinity); moveto(random(position)); - newt = turtle.copy(); - tpos = newt.getxy(); + var newt = turtle.copy(); + var tpos = newt.getxy(); deepEqual(turtle.getxy(), newt.getxy()); fd(100); notDeepEqual(turtle.getxy(), newt.getxy()); @@ -31,16 +31,16 @@ asyncTest("Make a red sprite and clone it.", function() { speed(Infinity); home() - s1 = new Sprite({color:'red'}); + var s1 = new Sprite({color:'red'}); setTimeout(function(){ - s2 = s1.copy(); + var s2 = s1.copy(); s2.clip(); s2.fd(200); s1.remove(); moveto(128, 72) drawon(s2); ok(touches(red)); - x = s2.imagedata(); + var x = s2.imagedata(); start(); }, 100); diff --git a/test/inputoutput.html b/test/inputoutput.html index 74035f4..a29c622 100644 --- a/test/inputoutput.html +++ b/test/inputoutput.html @@ -8,11 +8,11 @@ eval($.turtle()); module("Input/Output test."); asyncTest("Draws a square changing speed along the way.", function() { - w = write('hello!'); + var w = write('hello!'); equal($('div').last().text(), 'hello!'); type('AWESOME'); equal($('pre').last().text(), 'AWESOME'); - t = table([[1,2,3]]); + var t = table([[1,2,3]]); var td = $('td'); equal(td.length, 3); equal(td.eq(0).text(), "1"); @@ -22,7 +22,8 @@ setTimeout(function() { $('input').val('Amy').change(); }, 1); - r = read('name?', function(t) { + var b; + var r = read('name?', function(t) { equal(t, 'Amy'); setTimeout(function() { $('button').click(); diff --git a/test/pen.html b/test/pen.html index 837f014..59a6e8a 100644 --- a/test/pen.html +++ b/test/pen.html @@ -172,6 +172,7 @@ ok(touches(pink)); ok(touches(transparent)); dot(); + delete window.buggy; start(); }); }); diff --git a/test/remove.html b/test/remove.html index 6050a37..98e7166 100644 --- a/test/remove.html +++ b/test/remove.html @@ -33,6 +33,10 @@ ok(completed); equal(t.parent().length, 0); equal(typeof(window.turtle), 'undefined'); + // Restore globals to an initial state. + $.turtle({ids: false}); + window.turtle = $('#turtle'); + equal(typeof(window.turtle), 'object'); start(); }); }); From 5006e3c1d1a8c394e785b4356c1dab70e5266494 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 19 May 2015 18:51:53 -0400 Subject: [PATCH 19/68] log should cause the 'see' test panel to show up When not running in the IDE, errors and other log messages previously spewed to the body of the document. Now, instead, these cause the test panel to pop up on-demand. If nothing is logged, the test panel remains hidden. --- jquery-turtle.js | 244 ++++++++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 108 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index c1df409..347901c 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -368,6 +368,7 @@ THE SOFTWARE. ////////////////////////////////////////////////////////////////////////// var undefined = void 0, + _window = this, __hasProp = {}.hasOwnProperty, rootjQuery = jQuery(function() {}), interrupted = false, @@ -752,8 +753,8 @@ function getElementTranslation(elem) { // Reads out the 2x3 transform matrix of the given element. function readTransformMatrix(elem) { - var ts = (window.getComputedStyle ? - window.getComputedStyle(elem)[transform] : + var ts = (_window.getComputedStyle ? + _window.getComputedStyle(elem)[transform] : $.css(elem, 'transform')); if (!ts || ts === 'none') { return null; @@ -782,7 +783,7 @@ function readTransformOrigin(elem, wh) { elem.style[name] = swapout[name]; } } - var gcs = (window.getComputedStyle ? window.getComputedStyle(elem) : null), + var gcs = (_window.getComputedStyle ? _window.getComputedStyle(elem) : null), origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); if (hidden) { for (name in swapout) { @@ -981,12 +982,12 @@ function getTurtleOrigin(elem, inverseParent, extra) { function wh() { // Quirks-mode compatible window height. - return window.innerHeight || $(window).height(); + return _window.innerHeight || $(_window).height(); } function ww() { // Quirks-mode compatible window width. - return window.innerWidth || $(window).width(); + return _window.innerWidth || $(_window).width(); } function dh() { @@ -1013,7 +1014,7 @@ function getPageGbcr(elem) { return makeGbcrLTWH(elem.pageX, elem.pageY, 0, 0); } else if ($.isWindow(elem)) { return makeGbcrLTWH( - $(window).scrollLeft(), $(window).scrollTop(), ww(), wh()); + $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9) { return makeGbcrLTWH(0, 0, dw(), dh()); } else if (!('getBoundingClientRect' in elem)) { @@ -1057,10 +1058,10 @@ function polyMatchesGbcr(poly, gbcr) { function readPageGbcr() { var raw = this.getBoundingClientRect(); return { - top: raw.top + window.pageYOffset, - bottom: raw.bottom + window.pageYOffset, - left: raw.left + window.pageXOffset, - right: raw.right + window.pageXOffset, + top: raw.top + _window.pageYOffset, + bottom: raw.bottom + _window.pageYOffset, + left: raw.left + _window.pageXOffset, + right: raw.right + _window.pageXOffset, width: raw.width, height: raw.height }; @@ -1145,7 +1146,7 @@ function convertLocalXyToPageCoordinates(elem, localxy) { function getCenterInPageCoordinates(elem) { if ($.isWindow(elem)) { return getRoundedCenterLTWH( - $(window).scrollLeft(), $(window).scrollTop(), ww(), wh()); + $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9 || elem == document.body) { return getRoundedCenterLTWH(0, 0, dw(), dh()); } @@ -1180,7 +1181,7 @@ function polyToVectorsOffset(poly, offset) { function getCornersInPageCoordinates(elem, untransformed) { if ($.isWindow(elem)) { return getStraightRectLTWH( - $(window).scrollLeft(), $(window).scrollTop(), ww(), wh()); + $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9) { return getStraightRectLTWH(0, 0, dw(), dh()); } @@ -1227,7 +1228,7 @@ function scrollWindowToDocumentPosition(pos, limit) { b = $('body'), dw = b.width(), dh = b.height(), - w = $(window); + w = $(_window); if (tx > dw - ww2) { tx = dw - ww2; } if (tx < ww2) { tx = ww2; } if (ty > dh - wh2) { ty = dh - wh2; } @@ -1634,7 +1635,7 @@ function getTurtleDrawingCanvas() { surface.insertBefore(globalDrawing.canvas, surface.firstChild); resizecanvas(); pollbodysize(resizecanvas); - $(window).resize(resizecanvas); + $(_window).resize(resizecanvas); return globalDrawing.canvas; } @@ -1682,8 +1683,8 @@ function sizexy() { // Using innerHeight || $(window).height() deals with quirks-mode. var b = $('body'); return [ - Math.max(b.outerWidth(true), window.innerWidth || $(window).width()), - Math.max(b.outerHeight(true), window.innerHeight || $(window).height()) + Math.max(b.outerWidth(true), _window.innerWidth || $(_window).width()), + Math.max(b.outerHeight(true), _window.innerHeight || $(_window).height()) ]; } @@ -2031,7 +2032,6 @@ function flushPenState(elem, state, corner) { if (path[0].length) { path[0].length = 0; } if (corner) { if (!style) { - if (window.buggy) console.trace('clearing the retracing path'); // pen null will clear the retracing path too. if (corners.length > 1) corners.length = 1; if (corners[0].length) corners[0].length = 0; @@ -2645,7 +2645,7 @@ function apiUrl(url, topdir) { result = link.protocol + '//' + link.host + '/' + topdir + '/' + link.pathname.replace(/\/[^\/]*(?:\/|$)/, '') + link.search + link.hash; } - } else if (isPencilHost(window.location.hostname)) { + } else if (isPencilHost(_window.location.hostname)) { // Proxy offdomain requests to avoid CORS issues. result = '/proxy/' + result; } @@ -2655,7 +2655,7 @@ function apiUrl(url, topdir) { function imgUrl(url) { if (/\//.test(url)) { return url; } url = '/img/' + url; - if (isPencilHost(window.location.hostname)) { return url; } + if (isPencilHost(_window.location.hostname)) { return url; } return '//pencil.io' + url; } // Retrieves the pencil code login cookie, if there is one. @@ -3082,7 +3082,7 @@ var Webcam = (function(_super) { $(v).off('play.capture' + k); next(); }); - v.src = window.URL.createObjectURL(stream); + v.src = _window.URL.createObjectURL(stream); } }, function() { next(); @@ -3398,14 +3398,14 @@ function focusWindowIfFirst() { try { // If we are in a frame with access to a parent with an activeElement, // then try to blur it (as is common inside the pencilcode IDE). - window.parent.document.activeElement.blur(); + _window.parent.document.activeElement.blur(); } catch (e) {} - window.focus(); + _window.focus(); } // Construction of keyCode names. var keyCodeName = (function() { - var ua = typeof window !== 'undefined' ? window.navigator.userAgent : '', + var ua = typeof _window !== 'undefined' ? _window.navigator.userAgent : '', isOSX = /OS X/.test(ua), isOpera = /Opera/.test(ua), maybeFirefox = !/like Gecko/.test(ua) && !isOpera, @@ -3585,9 +3585,9 @@ var pressedKey = (function() { resetPressedState(); for (var name in eventMap) { if (turnon) { - window.addEventListener(name, eventMap[name], true); + _window.addEventListener(name, eventMap[name], true); } else { - window.removeEventListener(name, eventMap[name]); + _window.removeEventListener(name, eventMap[name]); } } } @@ -3831,7 +3831,7 @@ function getGlobalInstrument() { // Tests for the presence of HTML5 Web Audio (or webkit's version). function isAudioPresent() { - return !!(window.AudioContext || window.webkitAudioContext); + return !!(_window.AudioContext || _window.webkitAudioContext); } // All our audio funnels through the same AudioContext with a @@ -3842,7 +3842,7 @@ function getAudioTop() { if (!isAudioPresent()) { return null; } - var ac = new (window.AudioContext || window.webkitAudioContext); + var ac = new (_window.AudioContext || _window.webkitAudioContext); getAudioTop.audioTop = { ac: ac, wavetable: makeWavetable(ac), @@ -5378,7 +5378,7 @@ var Instrument = (function() { o.type = wavename; } } catch(e) { - if (window.console) { window.console.log(e); } + if (_window.console) { _window.console.log(e); } // If unrecognized, just use square. // TODO: support "noise" or other wave shapes. o.type = 'square'; @@ -5661,7 +5661,7 @@ function globalhelp(obj) { helpwrite('This is an unassigned value.'); return helpok; } - if (obj === window) { + if (obj === _window) { helpwrite('The global window object represents the browser window.'); return helpok; } @@ -5681,7 +5681,7 @@ function globalhelp(obj) { helplist = []; for (var name in helptable) { if (helptable[name].helptext && helptable[name].helptext.length && - (!(name in window) || typeof(window[name]) == 'function')) { + (!(name in _window) || typeof(_window[name]) == 'function')) { helplist.push(name); } } @@ -5910,7 +5910,7 @@ function wrapwindowevent(name, helptext) { : null; if (forKey) { focusWindowIfFirst(); } if (fn == null && typeof(d) == 'function') { fn = d; d = null; } - $(window).on(name + '.turtleevent', null, d, !filter ? fn : function(e) { + $(_window).on(name + '.turtleevent', null, d, !filter ? fn : function(e) { if (interrupted) return; if ($(e.target).closest(filter).length) { return; } return fn.apply(this, arguments); @@ -5919,7 +5919,7 @@ function wrapwindowevent(name, helptext) { } function windowhasturtleevent() { - var events = $._data(window, 'events'); + var events = $._data(_window, 'events'); if (!events) return false; for (var type in events) { var entries = events[type]; @@ -6807,7 +6807,7 @@ var turtlefn = { complete(); }, 250); } catch (e) { - console.log(e); + if (_window.console) { _window.console.log(e); } complete(); } }); @@ -6973,7 +6973,7 @@ var turtlefn = { if (state.drawOnCanvas) { sync(elem, state.drawOnCanvas); } - if (!canvas || canvas === window) { + if (!canvas || canvas === _window) { state.drawOnCanvas = null; } else if (canvas.jquery && $.isFunction(canvas.canvas)) { state.drawOnCanvas = canvas.canvas(); @@ -7080,7 +7080,7 @@ var turtlefn = { this.plan(function(j, elem) { cc.appear(j); if ($.isWindow(elem) || elem.nodeType === 9) { - window.location.reload(); + _window.location.reload(); cc.resolve(j); return; } @@ -7657,7 +7657,7 @@ var dollar_turtle_methods = { // Disable all input. $('.turtleinput').prop('disabled', true); // Detach all event handlers on the window. - $(window).off('.turtleevent'); + $(_window).off('.turtleevent'); // Low-level detach all jQuery events $('*').not('#_testpanel *').map( function(i, e) { $._data(e, 'events', null) }); @@ -8115,7 +8115,7 @@ var dollar_turtle_methods = { ["loadscript(url, callback) Loads Javascript or Coffeescript from " + "the given URL, calling callback when done."], function loadscript(url, callback) { - if (window.CoffeeScript && /\.(?:coffee|cs)$/.test(url)) { + if (_window.CoffeeScript && /\.(?:coffee|cs)$/.test(url)) { CoffeeScript.load(url, callback); } else { $.getScript(url, callback); @@ -8271,18 +8271,18 @@ $.turtle = function turtle(id, options) { if (!('see' in options) || options.see) { exportsee(); exportedsee = true; - if (window.addEventListener) { - window.addEventListener('error', see); + if (_window.addEventListener) { + _window.addEventListener('error', see); } else { - window.onerror = see; + _window.onerror = see; } // Set up an alias. - window.log = see; + _window.log = see; } // Copy $.turtle.* functions into global namespace. if (!('functions' in options) || options.functions) { - window.printpage = window.print; - $.extend(window, dollar_turtle_methods); + _window.printpage = _window.print; + $.extend(_window, dollar_turtle_methods); } // Set default turtle speed globaldefaultspeed(('defaultspeed' in options) ? @@ -8323,7 +8323,7 @@ $.turtle = function turtle(id, options) { if (!('ids' in options) || options.ids) { turtleids(options.idprefix); if (selector && id) { - window[id] = selector; + _window[id] = selector; } } // Set up test console. @@ -8349,7 +8349,7 @@ $.turtle = function turtle(id, options) { } // Return an eval loop hook string if 'see' is exported. if (exportedsee) { - if (window.CoffeeScript) { + if (_window.CoffeeScript) { return "see.init(eval(see.cs))"; } else { return see.here; @@ -8392,9 +8392,9 @@ function copyhelp(method, fname, extrahelp, globalfn) { function globalizeMethods(thisobj, fnames) { var replaced = []; for (var fname in fnames) { - if (fnames.hasOwnProperty(fname) && !(fname in window)) { + if (fnames.hasOwnProperty(fname) && !(fname in _window)) { replaced.push(fname); - window[fname] = (function(fname) { + _window[fname] = (function(fname) { var method = thisobj[fname], target = thisobj; return copyhelp(method, fname, extrahelp, (function globalized() { /* Use parentheses to call a function */ @@ -8408,7 +8408,7 @@ function globalizeMethods(thisobj, fnames) { function clearGlobalTurtle() { global_turtle = null; for (var j = 0; j < global_turtle_methods.length; ++j) { - delete window[global_turtle_methods[j]]; + delete _window[global_turtle_methods[j]]; } global_turtle_methods.length = 0; } @@ -8423,10 +8423,10 @@ $.cleanData = function(elems) { state.stream.stop(); } // Undefine global variablelem. - if (elem.id && window[elem.id] && window[elem.id].jquery && - window[elem.id].length === 1 && - window[elem.id][0] === elem) { - delete window[elem.id]; + if (elem.id && _window[elem.id] && _window[elem.id].jquery && + _window[elem.id].length === 1 && + _window[elem.id][0] === elem) { + delete _window[elem.id]; } // Clear global turtlelem. if (elem === global_turtle) { @@ -8821,9 +8821,9 @@ function nameToImg(name, defaultshape) { var hostname = absoluteUrlObject(name).hostname; // Use proxy to load image if the image is offdomain but the page is on // a pencil host (with a proxy). - if (!isPencilHost(hostname) && isPencilHost(window.location.hostname)) { - name = window.location.protocol + '//' + - window.location.host + '/proxy/' + absoluteUrl(name); + if (!isPencilHost(hostname) && isPencilHost(_window.location.hostname)) { + name = _window.location.protocol + '//' + + _window.location.host + '/proxy/' + absoluteUrl(name); } return { url: name, @@ -8907,8 +8907,8 @@ function hatchone(name, container, defaultshape) { if (isID) { result.attr('id', name); // Update global variable unless there is a conflict. - if (attaching_ids && !window.hasOwnProperty(name)) { - window[name] = result; + if (attaching_ids && !_window.hasOwnProperty(name)) { + _window[name] = result; } } // Move it to the center of the document and export the name as a global. @@ -9026,11 +9026,11 @@ function globaltick(rps, fn) { rps = 1; } if (tickinterval) { - window.clearInterval(tickinterval); + _window.clearInterval(tickinterval); tickinterval = null; } if (fn && rps) { - tickinterval = window.setInterval( + tickinterval = _window.setInterval( function() { // Set default speed to Infinity within tick(). try { @@ -9059,7 +9059,7 @@ function turtleids(prefix) { prefix = ''; } $('[id]').each(function(j, item) { - window[prefix + item.id] = $('#' + item.id); + _window[prefix + item.id] = $('#' + item.id); }); attaching_ids = true; } @@ -9072,7 +9072,7 @@ function turtleevents(prefix) { prefix = 'last'; } if (eventsaver) { - $(window).off($.map(eventfn, function(x,k) { return k; }).join(' '), + $(_window).off($.map(eventfn, function(x,k) { return k; }).join(' '), eventsaver); } if (prefix || prefix === '') { @@ -9084,20 +9084,20 @@ function turtleevents(prefix) { } for (j = 0; j < names.length; ++j) { var name = names[j]; - old = window[name], prop; + old = _window[name], prop; if (old && old.__proto__ === e.__proto__) { for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } } else { - window[name] = e; + _window[name] = e; } } }); - window[prefix + 'mouse'] = new $.Event(); + _window[prefix + 'mouse'] = new $.Event(); for (var k in eventfn) { - window[prefix + k] = new $.Event(); + _window[prefix + k] = new $.Event(); } - $(window).on($.map(eventfn, function(x,k) { return k; }).join(' '), + $(_window).on($.map(eventfn, function(x,k) { return k; }).join(' '), eventsaver); } } @@ -9109,15 +9109,15 @@ function turtleevents(prefix) { function autoScrollAfter(f) { var slop = 10, seen = autoScrollBottomSeen(), - stick = ($(window).height() + $(window).scrollTop() + slop >= + stick = ($(_window).height() + $(_window).scrollTop() + slop >= $('html').outerHeight(true)); f(); if (stick) { - var scrollPos = $(window).scrollTop(), + var scrollPos = $(_window).scrollTop(), advancedScrollPos = Math.min(seen, - $('html').outerHeight(true) - $(window).height()); + $('html').outerHeight(true) - $(_window).height()); if (advancedScrollPos > scrollPos) { - $(window).scrollTop(advancedScrollPos); + $(_window).scrollTop(advancedScrollPos); } } } @@ -9136,7 +9136,7 @@ function autoScrollBottomSeen() { var offset = $('body').offset(); var doctop = offset ? offset.top : 8; autoScrollState.bottomSeen = Math.min( - $(window).height() + $(window).scrollTop(), + $(_window).height() + $(_window).scrollTop(), $('body').height() + doctop); } return autoScrollState.bottomSeen; @@ -9145,9 +9145,9 @@ function autoScrollBottomSeen() { // location after running the passed function. (E.g., to allow focusing // a control without autoscrolling.) function undoScrollAfter(f) { - var scrollPos = $(window).scrollTop(); + var scrollPos = $(_window).scrollTop(); f(); - $(window).scrollTop(scrollPos); + $(_window).scrollTop(scrollPos); } ////////////////////////////////////////////////////////////////////////// @@ -9574,7 +9574,7 @@ var debug = { try { if (parent && parent.ide) { this.ide = parent.ide; - this.ide.bindframe(window); + this.ide.bindframe(_window); this.attached = true; } } catch(e) { @@ -9582,8 +9582,8 @@ var debug = { this.attached = false; } if (this.attached) { - if (window.addEventListener) { - window.addEventListener('error', function(event) { + if (_window.addEventListener) { + _window.addEventListener('error', function(event) { // An error event will highlight the error line. debug.reportEvent('error', [event]); }); @@ -9637,7 +9637,7 @@ debug.init(); } var linestart = null, linecanvas = null, lineend = null, xa = 0, ya = 0, xb = 0, yb = 0, xt, yt, dr, ar; - $(window).on('mousedown mouseup mousemove keydown', function(e) { + $(_window).on('mousedown mouseup mousemove keydown', function(e) { if (e.type == 'keydown') { if (e.which < 27) return; if (linecanvas) linecanvas.remove(); @@ -9764,16 +9764,16 @@ debug.init(); var pos = { left: '', top: '', right: '', bottom: '' }; if (p.pageX + 5 < s.pageX) { pos.left = Math.max( - p.pageX - $(window).scrollLeft() - location.outerWidth() - 5, 2); + p.pageX - $(_window).scrollLeft() - location.outerWidth() - 5, 2); } else { - pos.left = Math.min(p.pageX - $(window).scrollLeft() + 5, + pos.left = Math.min(p.pageX - $(_window).scrollLeft() + 5, $(document).width() - location.outerWidth() - 2); } if (p.pageY + 5 < s.pageY) { pos.top = Math.max( - p.pageY - $(window).scrollTop() - location.outerHeight() - 5, 2); + p.pageY - $(_window).scrollTop() - location.outerHeight() - 5, 2); } else { - pos.top = Math.min(p.pageY - $(window).scrollTop() + 5, + pos.top = Math.min(p.pageY - $(_window).scrollTop() + 5, $(document).height() - location.outerHeight() - 2); } location.css(pos); @@ -9815,14 +9815,15 @@ var linestyle = 'position:relative;display:block;font-family:monospace;' + var logdepth = 5; var autoscroll = false; var logelement = 'body'; -var panel = false; +var panel = 'auto'; try { // show panel by default if framed inside a an ide, // and if the screen is big enough (i.e., omit mobile clients). - panel = (window.self !== window.top && - screen.width >= 800 && screen.height >= 600 && - parent && parent.ide && parent.ide.getOptions().panel); + if (_window.self !== _window.top && + screen.width >= 800 && screen.height >= 600 && + parent && parent.ide) { panel = parent.ide.getOptions().panel; } } catch(e) {} +var consolelog = panel; var see; // defined below. var paneltitle = ''; var logconsole = null; @@ -9830,10 +9831,10 @@ var uselocalstorage = '_loghistory'; var panelheight = 50; var currentscope = ''; var scopes = { - '': { e: window.eval, t: window }, - top: { e: window.eval, t: window } + '': { e: _window.eval, t: _window }, + top: { e: _window.eval, t: _window } }; -var coffeescript = window.CoffeeScript; +var coffeescript = _window.CoffeeScript; var seejs = '(function(){return eval(arguments[0]);})'; function init(options) { @@ -9861,13 +9862,17 @@ function init(options) { if ('coffee' in options) { coffeescript = options.coffee; } if ('abbreviate' in options) { abbreviate = options.abbreviate; } if ('consolehook' in options) { consolehook = options.consolehook; } + if ('consolelog' in options) { consolelog = options.consolelog; } if ('noconflict' in options) { noconflict(options.noconflict); } if (panel) { // panel overrides element and autoscroll. logelement = '#_testlog'; autoscroll = '#_testscroll'; - pulljQuery(tryinitpanel); + if (panel === true) { + startinitpanel(); + } } + if (consolelog === true) { initconsolelog(); } return scope(); } @@ -9900,7 +9905,7 @@ var initialvardecl = new RegExp( function barecs(s) { // Compile coffeescript in bare mode. - var compiler = coffeescript || window.CoffeeScript; + var compiler = coffeescript || _window.CoffeeScript; var compiled = compiler.compile(s, {bare:1}); if (compiled) { // Further strip top-level var decls out of the coffeescript so @@ -9927,22 +9932,22 @@ function exportsee() { see.js = seejs; see.cs = '(function(){return eval(' + seepkg + '.barecs(arguments[0]));})'; see.version = version; - window[seepkg] = see; + _window[seepkg] = see; } function noteoldvalue(name) { return { name: name, - has: window.hasOwnProperty(name), - value: window[name] + has: _window.hasOwnProperty(name), + value: _window[name] }; } function restoreoldvalue(old) { if (!old.has) { - delete window[old.name]; + delete _window[old.name]; } else { - window[old.name] = old.value; + _window[old.name] = old.value; } } @@ -10006,7 +10011,7 @@ var queue = []; see = function see() { if (logconsole && typeof(logconsole.log) == 'function') { - logconsole.log.apply(window.console, arguments); + logconsole.log.apply(_window.console, arguments); } var args = Array.prototype.slice.call(arguments); queue.push(''); @@ -10355,12 +10360,12 @@ function expand(prefix, obj, depth, output) { } } function initlogcss() { - if (!addedcss && !window.document.getElementById('_logcss')) { - var style = window.document.createElement('style'); + if (!addedcss && !_window.document.getElementById('_logcss')) { + var style = _window.document.createElement('style'); style.id = '_logcss'; style.innerHTML = (linestyle ? 'samp._log{' + linestyle + '}' : '') + logcss; - window.document.head.appendChild(style); + _window.document.head.appendChild(style); addedcss = true; } } @@ -10417,7 +10422,7 @@ function flushqueue() { var elt = aselement(logelement, null); if (elt && elt.appendChild && queue.length) { initlogcss(); - var temp = window.document.createElement('samp'); + var temp = _window.document.createElement('samp'); temp.innerHTML = queue.join(''); queue.length = 0; var complete = stickscroll(); @@ -10427,6 +10432,9 @@ function flushqueue() { complete(); } if (!retrying && queue.length) { + if (panel == 'auto') { + startinitpanel(); + } retrying = setTimeout(function() { timer = null; flushqueue(); }, 100); } else if (retrying && !queue.length) { clearTimeout(retrying); @@ -10438,6 +10446,7 @@ function flushqueue() { // TEST PANEL SUPPORT // --------------------------------------------------------------------- var addedpanel = false; +var initpanelstarted = false; var inittesttimer = null; var abbreviate = [{}.undefined]; var consolehook = null; @@ -10462,7 +10471,7 @@ function promptcaret(color) { return ''; } function getSelectedText(){ - if(window.getSelection) { return window.getSelection().toString(); } + if(_window.getSelection) { return _window.getSelection().toString(); } else if(document.getSelection) { return document.getSelection(); } else if(document.selection) { return document.selection.createRange().text; } @@ -10478,7 +10487,7 @@ function readlocalstorage() { } var state = { height: panelheight, history: [] }, result; try { - result = window.JSON.parse(window.localStorage[uselocalstorage]); + result = _window.JSON.parse(_window.localStorage[uselocalstorage]); } catch(e) { result = noLocalStorage || {}; } @@ -10509,14 +10518,32 @@ function updatelocalstorage(state) { } if (changed) { try { - window.localStorage[uselocalstorage] = window.JSON.stringify(stored); + _window.localStorage[uselocalstorage] = _window.JSON.stringify(stored); } catch(e) { noLocalStorage = stored; } } } function wheight() { - return window.innerHeight || $(window).height(); + return _window.innerHeight || $(_window).height(); +} +function initconsolelog() { + try { + if (consolelog && _window.console && !_window.console._log && + 'function' == typeof _window.console.log) { + var _log = _window.console._log = _window.console.log; + _window_.console.log = function log() { + _log.apply(this, arguments); + see.apply(this, arguments); + } + } + } catch(e) { } +} +function startinitpanel() { + if (!initpanelstarted) { + initpanelstarted = true; + pulljQuery(tryinitpanel); + } } function tryinitpanel() { if (addedpanel) { @@ -10529,7 +10556,8 @@ function tryinitpanel() { } $('#_testpanel').show(); } else { - if (!window.document.getElementById('_testlog') && window.document.body) { + if (!_window.document.getElementById('_testlog') && _window.document.body) { + initconsolelog(); initlogcss(); var state = readlocalstorage(); var titlehtml = (paneltitle ? formattitle(paneltitle) : ''); @@ -10659,7 +10687,7 @@ function tryinitpanel() { } if (e.type == 'mouseup' || e.type == 'blur' || e.type == 'mousemove' && e.which != dragwhich) { - $(window).off('mousemove mouseup blur', dragfunc); + $(_window).off('mousemove mouseup blur', dragfunc); if (document.releaseCapture) { document.releaseCapture(); } if ($('#_testpanel').height() != state.height) { state.height = $('#_testpanel').height(); @@ -10667,7 +10695,7 @@ function tryinitpanel() { } } }; - $(window).on('mousemove mouseup blur', dragfunc); + $(_window).on('mousemove mouseup blur', dragfunc); return false; }); $('#_testpanel').on('mouseup', function(e) { @@ -10762,4 +10790,4 @@ function scalePolygon(poly, sx, sy, tx, ty) { } } -})(jQuery); +}).call(this, this.jQuery); From 2ed5be9996b3f0dca95e864cd83f4826f76b36fa Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 24 May 2015 13:25:43 -0400 Subject: [PATCH 20/68] Update to latest musical.js. --- jquery-turtle.js | 1060 +++++++++++++++++++++++----------------------- 1 file changed, 537 insertions(+), 523 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 347901c..11569e0 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -368,7 +368,7 @@ THE SOFTWARE. ////////////////////////////////////////////////////////////////////////// var undefined = void 0, - _window = this, + global = this, __hasProp = {}.hasOwnProperty, rootjQuery = jQuery(function() {}), interrupted = false, @@ -753,8 +753,8 @@ function getElementTranslation(elem) { // Reads out the 2x3 transform matrix of the given element. function readTransformMatrix(elem) { - var ts = (_window.getComputedStyle ? - _window.getComputedStyle(elem)[transform] : + var ts = (global.getComputedStyle ? + global.getComputedStyle(elem)[transform] : $.css(elem, 'transform')); if (!ts || ts === 'none') { return null; @@ -783,7 +783,7 @@ function readTransformOrigin(elem, wh) { elem.style[name] = swapout[name]; } } - var gcs = (_window.getComputedStyle ? _window.getComputedStyle(elem) : null), + var gcs = (global.getComputedStyle ? global.getComputedStyle(elem) : null), origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); if (hidden) { for (name in swapout) { @@ -982,12 +982,12 @@ function getTurtleOrigin(elem, inverseParent, extra) { function wh() { // Quirks-mode compatible window height. - return _window.innerHeight || $(_window).height(); + return global.innerHeight || $(global).height(); } function ww() { // Quirks-mode compatible window width. - return _window.innerWidth || $(_window).width(); + return global.innerWidth || $(global).width(); } function dh() { @@ -1014,7 +1014,7 @@ function getPageGbcr(elem) { return makeGbcrLTWH(elem.pageX, elem.pageY, 0, 0); } else if ($.isWindow(elem)) { return makeGbcrLTWH( - $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); + $(global).scrollLeft(), $(global).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9) { return makeGbcrLTWH(0, 0, dw(), dh()); } else if (!('getBoundingClientRect' in elem)) { @@ -1058,10 +1058,10 @@ function polyMatchesGbcr(poly, gbcr) { function readPageGbcr() { var raw = this.getBoundingClientRect(); return { - top: raw.top + _window.pageYOffset, - bottom: raw.bottom + _window.pageYOffset, - left: raw.left + _window.pageXOffset, - right: raw.right + _window.pageXOffset, + top: raw.top + global.pageYOffset, + bottom: raw.bottom + global.pageYOffset, + left: raw.left + global.pageXOffset, + right: raw.right + global.pageXOffset, width: raw.width, height: raw.height }; @@ -1146,7 +1146,7 @@ function convertLocalXyToPageCoordinates(elem, localxy) { function getCenterInPageCoordinates(elem) { if ($.isWindow(elem)) { return getRoundedCenterLTWH( - $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); + $(global).scrollLeft(), $(global).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9 || elem == document.body) { return getRoundedCenterLTWH(0, 0, dw(), dh()); } @@ -1181,7 +1181,7 @@ function polyToVectorsOffset(poly, offset) { function getCornersInPageCoordinates(elem, untransformed) { if ($.isWindow(elem)) { return getStraightRectLTWH( - $(_window).scrollLeft(), $(_window).scrollTop(), ww(), wh()); + $(global).scrollLeft(), $(global).scrollTop(), ww(), wh()); } else if (elem.nodeType === 9) { return getStraightRectLTWH(0, 0, dw(), dh()); } @@ -1228,7 +1228,7 @@ function scrollWindowToDocumentPosition(pos, limit) { b = $('body'), dw = b.width(), dh = b.height(), - w = $(_window); + w = $(global); if (tx > dw - ww2) { tx = dw - ww2; } if (tx < ww2) { tx = ww2; } if (ty > dh - wh2) { ty = dh - wh2; } @@ -1635,7 +1635,7 @@ function getTurtleDrawingCanvas() { surface.insertBefore(globalDrawing.canvas, surface.firstChild); resizecanvas(); pollbodysize(resizecanvas); - $(_window).resize(resizecanvas); + $(global).resize(resizecanvas); return globalDrawing.canvas; } @@ -1683,8 +1683,8 @@ function sizexy() { // Using innerHeight || $(window).height() deals with quirks-mode. var b = $('body'); return [ - Math.max(b.outerWidth(true), _window.innerWidth || $(_window).width()), - Math.max(b.outerHeight(true), _window.innerHeight || $(_window).height()) + Math.max(b.outerWidth(true), global.innerWidth || $(global).width()), + Math.max(b.outerHeight(true), global.innerHeight || $(global).height()) ]; } @@ -2645,7 +2645,7 @@ function apiUrl(url, topdir) { result = link.protocol + '//' + link.host + '/' + topdir + '/' + link.pathname.replace(/\/[^\/]*(?:\/|$)/, '') + link.search + link.hash; } - } else if (isPencilHost(_window.location.hostname)) { + } else if (isPencilHost(global.location.hostname)) { // Proxy offdomain requests to avoid CORS issues. result = '/proxy/' + result; } @@ -2655,7 +2655,7 @@ function apiUrl(url, topdir) { function imgUrl(url) { if (/\//.test(url)) { return url; } url = '/img/' + url; - if (isPencilHost(_window.location.hostname)) { return url; } + if (isPencilHost(global.location.hostname)) { return url; } return '//pencil.io' + url; } // Retrieves the pencil code login cookie, if there is one. @@ -3082,7 +3082,7 @@ var Webcam = (function(_super) { $(v).off('play.capture' + k); next(); }); - v.src = _window.URL.createObjectURL(stream); + v.src = global.URL.createObjectURL(stream); } }, function() { next(); @@ -3398,14 +3398,14 @@ function focusWindowIfFirst() { try { // If we are in a frame with access to a parent with an activeElement, // then try to blur it (as is common inside the pencilcode IDE). - _window.parent.document.activeElement.blur(); + global.parent.document.activeElement.blur(); } catch (e) {} - _window.focus(); + global.focus(); } // Construction of keyCode names. var keyCodeName = (function() { - var ua = typeof _window !== 'undefined' ? _window.navigator.userAgent : '', + var ua = typeof global !== 'undefined' ? global.navigator.userAgent : '', isOSX = /OS X/.test(ua), isOpera = /Opera/.test(ua), maybeFirefox = !/like Gecko/.test(ua) && !isOpera, @@ -3585,9 +3585,9 @@ var pressedKey = (function() { resetPressedState(); for (var name in eventMap) { if (turnon) { - _window.addEventListener(name, eventMap[name], true); + global.addEventListener(name, eventMap[name], true); } else { - _window.removeEventListener(name, eventMap[name]); + global.removeEventListener(name, eventMap[name]); } } } @@ -3829,9 +3829,11 @@ function getGlobalInstrument() { return global_instrument; } +// Beginning of musical.js copy + // Tests for the presence of HTML5 Web Audio (or webkit's version). function isAudioPresent() { - return !!(_window.AudioContext || _window.webkitAudioContext); + return !!(global.AudioContext || global.webkitAudioContext); } // All our audio funnels through the same AudioContext with a @@ -3842,7 +3844,7 @@ function getAudioTop() { if (!isAudioPresent()) { return null; } - var ac = new (_window.AudioContext || _window.webkitAudioContext); + var ac = new (global.AudioContext || global.webkitAudioContext); getAudioTop.audioTop = { ac: ac, wavetable: makeWavetable(ac), @@ -3887,6 +3889,48 @@ function audioCurrentStartTime() { return atop.currentStart; } +// Converts a midi note number to a frequency in Hz. +function midiToFrequency(midi) { + return 440 * Math.pow(2, (midi - 69) / 12); +} +// Some constants. +var noteNum = + {C:0,D:2,E:4,F:5,G:7,A:9,B:11,c:12,d:14,e:16,f:17,g:19,a:21,b:23}; +var accSym = + { '^':1, '': 0, '=':0, '_':-1 }; +var noteName = + ['C', '^C', 'D', '_E', 'E', 'F', '^F', 'G', '_A', 'A', '_B', 'B', + 'c', '^c', 'd', '_e', 'e', 'f', '^f', 'g', '_a', 'a', '_b', 'b']; +// Converts a frequency in Hz to the closest midi number. +function frequencyToMidi(freq) { + return Math.round(69 + Math.log(freq / 440) * 12 / Math.LN2); +} +// Converts an ABC pitch (such as "^G,,") to a midi note number. +function pitchToMidi(pitch) { + var m = /^(\^+|_+|=|)([A-Ga-g])([,']*)$/.exec(pitch); + if (!m) { return null; } + var octave = m[3].replace(/,/g, '').length - m[3].replace(/'/g, '').length; + var semitone = + noteNum[m[2]] + accSym[m[1].charAt(0)] * m[1].length + 12 * octave; + return semitone + 60; // 60 = midi code middle "C". +} +// Converts a midi number to an ABC notation pitch. +function midiToPitch(midi) { + var index = ((midi - 72) % 12); + if (midi > 60 || index != 0) { index += 12; } + var octaves = Math.round((midi - index - 60) / 12), + result = noteName[index]; + while (octaves != 0) { + result += octaves > 0 ? "'" : ","; + octaves += octaves > 0 ? -1 : 1; + } + return result; +} +// Converts an ABC pitch to a frequency in Hz. +function pitchToFrequency(pitch) { + return midiToFrequency(pitchToMidi(pitch)); +} + // All further details of audio handling are encapsulated in the Instrument // class, which knows how to synthesize a basic timbre; how to play and // schedule a tone; and how to parse and sequence a song written in ABC @@ -4583,150 +4627,402 @@ var Instrument = (function() { } }; - // Parses an ABC file to an object with the following structure: - // { - // X: value from the X: lines in header (\n separated for multiple values) - // V: value from the V:myname lines that appear before K: - // (etc): for all the one-letter header-names. - // K: value from the K: lines in header. - // tempo: Q: line parsed as beatsecs - // timbre: ... I:timbre line as parsed by makeTimbre - // voice: { - // myname: { // voice with id "myname" - // V: value from the V:myname lines (from the body) - // stems: [...] as parsed by parseABCstems - // } - // } - // } - // ABC files are idiosyncratic to parse: the written specifications - // do not necessarily reflect the defacto standard implemented by - // ABC content on the web. This implementation is designed to be - // practical, working on content as it appears on the web, and only - // using the written standard as a guideline. - var ABCheader = /^([A-Za-z]):\s*(.*)$/; - function parseABCFile(str) { - var lines = str.split('\n'), - result = { - voice: {} - }, - context = result, timbre, - j, k, header, stems, key = {}, accent = {}, voiceid, out; - // Shifts context to a voice with the given id given. If no id - // given, then just sticks with the current voice. If the current - // voice is unnamed and empty, renames the current voice. - function startVoiceContext(id) { - id = id || ''; - if (!id && context !== result) { - return; - } - if (result.voice.hasOwnProperty(id)) { - // Resume a named voice. - context = result.voice[id]; - accent = context.accent; - } else { - // Start a new voice. - context = { id: id, accent: { slurred: 0 } }; - result.voice[id] = context; - accent = context.accent; + + // The default sound is a square wave with a pretty quick decay to zero. + var defaultTimbre = Instrument.defaultTimbre = { + wave: 'square', // Oscillator type. + gain: 0.1, // Overall gain at maximum attack. + attack: 0.002, // Attack time at the beginning of a tone. + decay: 0.4, // Rate of exponential decay after attack. + decayfollow: 0, // Amount of decay shortening for higher notes. + sustain: 0, // Portion of gain to sustain indefinitely. + release: 0.1, // Release time after a tone is done. + cutoff: 0, // Low-pass filter cutoff frequency. + cutfollow: 0, // Cutoff adjustment, a multiple of oscillator freq. + resonance: 0, // Low-pass filter resonance. + detune: 0 // Detune factor for a second oscillator. + }; + + // Norrmalizes a timbre object by making a copy that has exactly + // the right set of timbre fields, defaulting when needed. + // A timbre can specify any of the fields of defaultTimbre; any + // unspecified fields are treated as they are set in defaultTimbre. + function makeTimbre(options, atop) { + if (!options) { + options = {}; + } + if (typeof(options) == 'string') { + // Abbreviation: name a wave to get a default timbre for that wave. + options = { wave: options }; + } + var result = {}, key, + wt = atop && atop.wavetable && atop.wavetable[options.wave]; + for (key in defaultTimbre) { + if (options.hasOwnProperty(key)) { + result[key] = options[key]; + } else if (wt && wt.defs && wt.defs.hasOwnProperty(key)) { + result[key] = wt.defs[key]; + } else{ + result[key] = defaultTimbre[key]; } } - // For picking a default voice, looks for the first voice name. - function firstVoiceName() { - if (result.V) { - return result.V.split(/\s+/)[0]; - } else { - return ''; + return result; + } + + var whiteNoiseBuf = null; + function getWhiteNoiseBuf() { + if (whiteNoiseBuf == null) { + var ac = getAudioTop().ac, + bufferSize = 2 * ac.sampleRate, + whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), + output = whiteNoiseBuf.getChannelData(0); + for (var i = 0; i < bufferSize; i++) { + output[i] = Math.random() * 2 - 1; } } - // ABC files are parsed one line at a time. - for (j = 0; j < lines.length; ++j) { - // First, check to see if the line is a header line. - header = ABCheader.exec(lines[j]); - if (header) { - // The following headers are recognized and processed. - switch(header[1]) { - case 'V': - // A V: header switches voices if in the body. - // If in the header, then it is just advisory. - if (context !== result) { - startVoiceContext(header[2].split(' ')[0]); + return whiteNoiseBuf; + } + + // This utility function creates an oscillator at the given frequency + // and the given wavename. It supports lookups in a static wavetable, + // defined right below. + function makeOscillator(atop, wavename, freq) { + if (wavename == 'noise') { + var whiteNoise = atop.ac.createBufferSource(); + whiteNoise.buffer = getWhiteNoiseBuf(); + whiteNoise.loop = true; + return whiteNoise; + } + var wavetable = atop.wavetable, o = atop.ac.createOscillator(), + k, pwave, bwf, wf; + try { + if (wavetable.hasOwnProperty(wavename)) { + // Use a customized wavetable. + pwave = wavetable[wavename].wave; + if (wavetable[wavename].freq) { + bwf = 0; + // Look for a higher-frequency variant. + for (k in wavetable[wavename].freq) { + wf = Number(k); + if (freq > wf && wf > bwf) { + bwf = wf; + pwave = wavetable[wavename].freq[bwf]; } - break; - case 'M': - parseMeter(header[2], context); - break; - case 'L': - parseUnitNote(header[2], context); - break; - case 'Q': - parseTempo(header[2], context); - break; + } } - // All headers (including unrecognized ones) are - // just accumulated as properties. Repeated header - // lines are accumulated as multiline properties. - if (context.hasOwnProperty(header[1])) { - context[header[1]] += '\n' + header[2]; + if (!o.setPeriodicWave && o.setWaveTable) { + // The old API name: Safari 7 still uses this. + o.setWaveTable(pwave); } else { - context[header[1]] = header[2]; - } - // The K header is special: it should be the last one - // before the voices and notes begin. - if (header[1] == 'K' && context === result) { - key = keysig(header[2]); - startVoiceContext(firstVoiceName()); + // The new API name. + o.setPeriodicWave(pwave); } - } else if (/^\s*(?:%.*)?$/.test(lines[j])) { - // Skip blank and comment lines. - continue; } else { - // A non-blank non-header line should have notes. - voiceid = peekABCVoice(lines[j]); - if (voiceid) { - // If it declares a voice id, respect it. - startVoiceContext(voiceid); - } else { - // Otherwise, start a default voice. - if (context === result) { - startVoiceContext(firstVoiceName()); + o.type = wavename; + } + } catch(e) { + if (window.console) { window.console.log(e); } + // If unrecognized, just use square. + // TODO: support "noise" or other wave shapes. + o.type = 'square'; + } + o.frequency.value = freq; + return o; + } + + // Accepts either an ABC pitch or a midi number and converts to midi. + Instrument.pitchToMidi = function(n) { + if (typeof(n) == 'string') { return pitchToMidi(n); } + return n; + } + + // Accepts either an ABC pitch or a midi number and converts to ABC pitch. + Instrument.midiToPitch = function(n) { + if (typeof(n) == 'number') { return midiToPitch(n); } + return n; + } + + return Instrument; +})(); + +// Parses an ABC file to an object with the following structure: +// { +// X: value from the X: lines in header (\n separated for multiple values) +// V: value from the V:myname lines that appear before K: +// (etc): for all the one-letter header-names. +// K: value from the K: lines in header. +// tempo: Q: line parsed as beatsecs +// timbre: ... I:timbre line as parsed by makeTimbre +// voice: { +// myname: { // voice with id "myname" +// V: value from the V:myname lines (from the body) +// stems: [...] as parsed by parseABCstems +// } +// } +// } +// ABC files are idiosyncratic to parse: the written specifications +// do not necessarily reflect the defacto standard implemented by +// ABC content on the web. This implementation is designed to be +// practical, working on content as it appears on the web, and only +// using the written standard as a guideline. +var ABCheader = /^([A-Za-z]):\s*(.*)$/; +var ABCtoken = /(?:\[[A-Za-z]:[^\]]*\])|\s+|%[^\n]*|![^\s!:|\[\]]*!|\+[^+|!]*\+|[_<>@^]?"[^"]*"|\[|\]|>+|<+|(?:(?:\^+|_+|=|)[A-Ga-g](?:,+|'+|))|\(\d+(?::\d+){0,2}|\d*\/\d+|\d+\/?|\/+|[xzXZ]|\[?\|\]?|:?\|:?|::|./g; +function parseABCFile(str) { + var lines = str.split('\n'), + result = {}, + context = result, timbre, + j, k, header, stems, key = {}, accent = { slurred: 0 }, voiceid, out; + // ABC files are parsed one line at a time. + for (j = 0; j < lines.length; ++j) { + // First, check to see if the line is a header line. + header = ABCheader.exec(lines[j]); + if (header) { + handleInformation(header[1], header[2].trim()); + } else if (/^\s*(?:%.*)?$/.test(lines[j])) { + // Skip blank and comment lines. + continue; + } else { + // Parse the notes. + parseABCNotes(lines[j]); + } + } + var infer = ['unitnote', 'unitbeat', 'tempo']; + if (result.voice) { + out = []; + for (j in result.voice) { + if (result.voice[j].stems && result.voice[j].stems.length) { + // Calculate times for all the tied notes. This happens at the end + // because in principle, the first note of a song could be tied all + // the way through to the last note. + processTies(result.voice[j].stems); + // Bring up inferred tempo values from voices if not specified + // in the header. + for (k = 0; k < infer.length; ++k) { + if (!(infer[k] in result) && (infer[k] in result.voice[j])) { + result[infer[k]] = result.voice[j][infer[k]]; } } - // Parse the notes. - stems = parseABCNotes(lines[j], key, accent); - if (stems && stems.length) { - // Push the line of stems into the voice. - if (!('stems' in context)) { context.stems = []; } - context.stems.push.apply(context.stems, stems); + // Remove this internal state variable; + delete result.voice[j].accent; + } else { + out.push(j); + } + } + // Delete any voices that had no stems. + for (j = 0; j < out.length; ++j) { + delete result.voice[out[j]]; + } + } + return result; + + + //////////////////////////////////////////////////////////////////////// + // Parsing helper functions below. + //////////////////////////////////////////////////////////////////////// + + + // Processes header fields such as V: voice, which may appear at the + // top of the ABC file, or in the ABC body in a [V:voice] directive. + function handleInformation(field, value) { + // The following headers are recognized and processed. + switch(field) { + case 'V': + // A V: header switches voices if in the body. + // If in the header, then it is just advisory. + if (context !== result) { + startVoiceContext(value.split(' ')[0]); } + break; + case 'M': + parseMeter(value, context); + break; + case 'L': + parseUnitNote(value, context); + break; + case 'Q': + parseTempo(value, context); + break; + } + // All headers (including unrecognized ones) are + // just accumulated as properties. Repeated header + // lines are accumulated as multiline properties. + if (context.hasOwnProperty(field)) { + context[field] += '\n' + value; + } else { + context[field] = value; + } + // The K header is special: it should be the last one + // before the voices and notes begin. + if (field == 'K') { + key = keysig(value); + if (context === result) { + startVoiceContext(firstVoiceName()); } } - var infer = ['unitnote', 'unitbeat', 'tempo']; - if (result.voice) { - out = []; - for (j in result.voice) { - if (result.voice[j].stems && result.voice[j].stems.length) { - // Calculate times for all the tied notes. This happens at the end - // because in principle, the first note of a song could be tied all - // the way through to the last note. - processTies(result.voice[j].stems); - // Bring up inferred tempo values from voices if not specified - // in the header. - for (k = 0; k < infer.length; ++k) { - if (!(infer[k] in result) && (infer[k] in result.voice[j])) { - result[infer[k]] = result.voice[j][infer[k]]; + } + + // Shifts context to a voice with the given id given. If no id + // given, then just sticks with the current voice. If the current + // voice is unnamed and empty, renames the current voice. + function startVoiceContext(id) { + id = id || ''; + if (!id && context !== result) { + return; + } + if (!result.voice) { + result.voice = {}; + } + if (result.voice.hasOwnProperty(id)) { + // Resume a named voice. + context = result.voice[id]; + accent = context.accent; + } else { + // Start a new voice. + context = { id: id, accent: { slurred: 0 } }; + result.voice[id] = context; + accent = context.accent; + } + } + + // For picking a default voice, looks for the first voice name. + function firstVoiceName() { + if (result.V) { + return result.V.split(/\s+/)[0]; + } else { + return ''; + } + } + + // Parses a single line of ABC notes (i.e., not a header line). + // + // We process an ABC song stream by dividing it into tokens, each of + // which is a pitch, duration, or special decoration symbol; then + // we process each decoration individually, and we process each + // stem as a group using parseStem. + // The structure of a single ABC note is something like this: + // + // NOTE -> STACCATO? PITCH DURATION? TIE? + // + // I.e., it always has a pitch, and it is prefixed by some optional + // decorations such as a (.) staccato marking, and it is suffixed by + // an optional duration and an optional tie (-) marking. + // + // A stem is either a note or a bracketed series of notes, followed + // by duration and tie. + // + // STEM -> NOTE OR '[' NOTE * ']' DURAITON? TIE? + // + // Then a song is just a sequence of stems interleaved with other + // decorations such as dynamics markings and measure delimiters. + function parseABCNotes(str) { + var tokens = str.match(ABCtoken), parsed = null, + index = 0, dotted = 0, beatlet = null, t; + if (!tokens) { + return null; + } + while (index < tokens.length) { + // Ignore %comments and !markings! + if (/^[\s%]/.test(tokens[index])) { index++; continue; } + // Handle inline [X:...] information fields + if (/^\[[A-Za-z]:[^\]]*\]$/.test(tokens[index])) { + handleInformation( + tokens[index].substring(1, 2), + tokens[index].substring(3, tokens[index].length - 1).trim() + ); + index++; + continue; + } + // Handled dotted notation abbreviations. + if (//.test(tokens[index])) { + dotted = tokens[index++].length; + continue; + } + if (/^\(\d+(?::\d+)*/.test(tokens[index])) { + beatlet = parseBeatlet(tokens[index++]); + continue; + } + if (/^[!+].*[!+]$/.test(tokens[index])) { + parseDecoration(tokens[index++], accent); + continue; + } + if (/^.?".*"$/.test(tokens[index])) { + // Ignore double-quoted tokens (chords and general text annotations). + index++; + continue; + } + if (/^[()]$/.test(tokens[index])) { + if (tokens[index++] == '(') { + accent.slurred += 1; + } else { + accent.slurred -= 1; + if (accent.slurred <= 0) { + accent.slurred = 0; + if (context.stems && context.stems.length >= 1) { + // The last notes in a slur are not slurred. + slurStem(context.stems[context.stems.length - 1], false); } } + } + continue; + } + // Handle measure markings by clearing accidentals. + if (/\|/.test(tokens[index])) { + for (t in accent) { + if (t.length == 1) { + // Single-letter accent properties are note accidentals. + delete accent[t]; + } + } + index++; + continue; + } + parsed = parseStem(tokens, index, key, accent); + // Skip unparsable bits + if (parsed === null) { + index++; + continue; + } + // Process a parsed stem. + if (beatlet) { + scaleStem(parsed.stem, beatlet.time); + beatlet.count -= 1; + if (!beatlet.count) { + beatlet = null; + } + } + // If syncopated with > or < notation, shift part of a beat + // between this stem and the previous one. + if (dotted && context.stems && context.stems.length) { + if (dotted > 0) { + t = (1 - Math.pow(0.5, dotted)) * parsed.stem.time; } else { - out.push(j); + t = (Math.pow(0.5, -dotted) - 1) * + context.stems[context.stems.length - 1].time; } + syncopateStem(context.stems[context.stems.length - 1], t); + syncopateStem(parsed.stem, -t); + } + dotted = 0; + // Slur all the notes contained within a strem. + if (accent.slurred) { + slurStem(parsed.stem, true); } - // Delete any voices that had no stems. - for (j = 0; j < out.length; ++j) { - delete result.voice[out[j]]; + // Start a default voice if we're not in a voice yet. + if (context === result) { + startVoiceContext(firstVoiceName()); } + if (!('stems' in context)) { context.stems = []; } + // Add the stem to the sequence of stems for this voice. + context.stems.push(parsed.stem); + // Advance the parsing index since a stem is multiple tokens. + index = parsed.index; } - return result; } + // Parse M: lines. "3/4" is 3/4 time and "C" is 4/4 (common) time. function parseMeter(mline, beatinfo) { var d = /^C/.test(mline) ? 4/4 : durationToTime(mline); @@ -4811,7 +5107,7 @@ var Instrument = (function() { // Supports the whole range of scale systems listed in the ABC spec. function keysig(keyname) { if (!keyname) { return {}; } - var key, sigcodes = { + var kkey, sigcodes = { // Major 'c#':7, 'f#':6, 'b':5, 'e':4, 'a':3, 'd':2, 'g':1, 'c':0, 'f':-1, 'bb':-2, 'eb':-3, 'ab':-4, 'db':-5, 'gb':-6, 'cb':-7, @@ -4843,19 +5139,19 @@ var Instrument = (function() { var scale = k.match(/maj|min|mix|dor|phr|lyd|loc|m/); if (scale) { if (scale == 'maj') { - key = k.substr(0, scale.index); + kkey = k.substr(0, scale.index); } else if (scale == 'min') { - key = k.substr(0, scale.index + 1); + kkey = k.substr(0, scale.index + 1); } else { - key = k.substr(0, scale.index + scale[0].length); + kkey = k.substr(0, scale.index + scale[0].length); } } else { - key = /^[a-g][#b]?/.exec(k) || ''; + kkey = /^[a-g][#b]?/.exec(k) || ''; } - var result = accidentals(sigcodes[key]); - var extras = keyname.substr(key.length).match(/(_+|=|\^+)[a-g]/ig); + var result = accidentals(sigcodes[kkey]); + var extras = keyname.substr(kkey.length).match(/(_+|=|\^+)[a-g]/ig); if (extras) { - for (j = 0; j < extras.length; ++j) { + for (var j = 0; j < extras.length; ++j) { var note = extras[j].charAt(extras[j].length - 1).toUpperCase(); if (extras[j].charAt(0) == '=') { delete result[note]; @@ -4866,133 +5162,6 @@ var Instrument = (function() { } return result; } - // Peeks and looks for a prefix of the form [V:voiceid]. - function peekABCVoice(line) { - var match = /^\[V:([^\]\s]*)\]/.exec(line); - if (!match) return null; - return match[1]; - } - // Parses a single line of ABC notes (i.e., not a header line). - // - // We process an ABC song stream by dividing it into tokens, each of - // which is a pitch, duration, or special decoration symbol; then - // we process each decoration individually, and we process each - // stem as a group using parseStem. - // The structure of a single ABC note is something like this: - // - // NOTE -> STACCATO? PITCH DURATION? TIE? - // - // I.e., it always has a pitch, and it is prefixed by some optional - // decorations such as a (.) staccato marking, and it is suffixed by - // an optional duration and an optional tie (-) marking. - // - // A stem is either a note or a bracketed series of notes, followed - // by duration and tie. - // - // STEM -> NOTE OR '[' NOTE * ']' DURAITON? TIE? - // - // Then a song is just a sequence of stems interleaved with other - // decorations such as dynamics markings and measure delimiters. - var ABCtoken = /(?:^\[V:[^\]\s]*\])|\s+|%[^\n]*|![^\s!:|\[\]]*!|\+[^+|!]*\+|[_<>@^]?"[^"]*"|\[|\]|>+|<+|(?:(?:\^+|_+|=|)[A-Ga-g](?:,+|'+|))|\(\d+(?::\d+){0,2}|\d*\/\d+|\d+\/?|\/+|[xzXZ]|\[?\|\]?|:?\|:?|::|./g; - function parseABCNotes(str, key, accent) { - var tokens = str.match(ABCtoken), result = [], parsed = null, - index = 0, dotted = 0, beatlet = null, t; - if (!tokens) { - return null; - } - while (index < tokens.length) { - // Ignore %comments and !markings! - if (/^[\s%]/.test(tokens[index])) { index++; continue; } - if (/^\[V:\S*\]$/.test(tokens[index])) { - // Voice id from [V:id] is handled in peekABCVoice. - index++; - continue; - } - // Handled dotted notation abbreviations. - if (//.test(tokens[index])) { - dotted = tokens[index++].length; - continue; - } - if (/^\(\d+(?::\d+)*/.test(tokens[index])) { - beatlet = parseBeatlet(tokens[index++]); - continue; - } - if (/^[!+].*[!+]$/.test(tokens[index])) { - parseDecoration(tokens[index++], accent); - continue; - } - if (/^.?".*"$/.test(tokens[index])) { - // Ignore double-quoted tokens (chords and general text annotations). - index++; - continue; - } - if (/^[()]$/.test(tokens[index])) { - if (tokens[index++] == '(') { - accent.slurred += 1; - } else { - accent.slurred -= 1; - if (accent.slurred <= 0) { - accent.slurred = 0; - if (result.length >= 1) { - // The last notes in a slur are not slurred. - slurStem(result[result.length - 1], false); - } - } - } - continue; - } - // Handle measure markings by clearing accidentals. - if (/\|/.test(tokens[index])) { - for (t in accent) { - if (t.length == 1) { - // Single-letter accent properties are note accidentals. - delete accent[t]; - } - } - index++; - continue; - } - parsed = parseStem(tokens, index, key, accent); - // Skip unparsable bits - if (parsed === null) { - index++; - continue; - } - // Process a parsed stem. - if (beatlet) { - scaleStem(parsed.stem, beatlet.time); - beatlet.count -= 1; - if (!beatlet.count) { - beatlet = null; - } - } - // If syncopated with > or < notation, shift part of a beat - // between this stem and the previous one. - if (dotted && result.length) { - if (dotted > 0) { - t = (1 - Math.pow(0.5, dotted)) * parsed.stem.time; - } else { - t = (Math.pow(0.5, -dotted) - 1) * result[result.length - 1].time; - } - syncopateStem(result[result.length - 1], t); - syncopateStem(parsed.stem, -t); - } - dotted = 0; - // Slur all the notes contained within a strem. - if (accent.slurred) { - slurStem(parsed.stem, true); - } - // Add the stem to the sequence of stems for this voice. - result.push(parsed.stem); - // Advance the parsing index since a stem is multiple tokens. - index = parsed.index; - } - return result; - } // Additively adjusts the beats for a stem and the contained notes. function syncopateStem(stem, t) { var j, note, stemtime = stem.time, newtime = stemtime + t; @@ -5225,47 +5394,6 @@ var Instrument = (function() { } return stripNatural(pitch); } - // Converts a midi note number to a frequency in Hz. - function midiToFrequency(midi) { - return 440 * Math.pow(2, (midi - 69) / 12); - } - // Some constants. - var noteNum = - {C:0,D:2,E:4,F:5,G:7,A:9,B:11,c:12,d:14,e:16,f:17,g:19,a:21,b:23}; - var accSym = - { '^':1, '': 0, '=':0, '_':-1 }; - var noteName = - ['C', '^C', 'D', '_E', 'E', 'F', '^F', 'G', '_A', 'A', '_B', 'B', - 'c', '^c', 'd', '_e', 'e', 'f', '^f', 'g', '_a', 'a', '_b', 'b']; - // Converts a frequency in Hz to the closest midi number. - function frequencyToMidi(freq) { - return Math.round(69 + Math.log(freq / 440) * 12 / Math.LN2); - } - // Converts an ABC pitch (such as "^G,,") to a midi note number. - function pitchToMidi(pitch) { - var m = /^(\^+|_+|=|)([A-Ga-g])([,']*)$/.exec(pitch); - if (!m) { return null; } - var octave = m[3].replace(/,/g, '').length - m[3].replace(/'/g, '').length; - var semitone = - noteNum[m[2]] + accSym[m[1].charAt(0)] * m[1].length + 12 * octave; - return semitone + 60; // 60 = midi code middle "C". - } - // Converts a midi number to an ABC notation pitch. - function midiToPitch(midi) { - var index = ((midi - 72) % 12); - if (midi > 60 || index != 0) { index += 12; } - var octaves = Math.round((midi - index - 60) / 12), - result = noteName[index]; - while (octaves != 0) { - result += octaves > 0 ? "'" : ","; - octaves += octaves > 0 ? -1 : 1; - } - return result; - } - // Converts an ABC pitch to a frequency in Hz. - function pitchToFrequency(pitch) { - return midiToFrequency(pitchToMidi(pitch)); - } // Converts an ABC duration to a number (e.g., "/3"->0.333 or "11/2"->1.5). function durationToTime(duration) { var m = /^(\d*)(?:\/(\d*))?$|^(\/+)$/.exec(duration), n, d, i = 0, ilen; @@ -5284,123 +5412,7 @@ var Instrument = (function() { } return i + (n / d); } - - // The default sound is a square wave with a pretty quick decay to zero. - var defaultTimbre = Instrument.defaultTimbre = { - wave: 'square', // Oscillator type. - gain: 0.1, // Overall gain at maximum attack. - attack: 0.002, // Attack time at the beginning of a tone. - decay: 0.4, // Rate of exponential decay after attack. - decayfollow: 0, // Amount of decay shortening for higher notes. - sustain: 0, // Portion of gain to sustain indefinitely. - release: 0.1, // Release time after a tone is done. - cutoff: 0, // Low-pass filter cutoff frequency. - cutfollow: 0, // Cutoff adjustment, a multiple of oscillator freq. - resonance: 0, // Low-pass filter resonance. - detune: 0 // Detune factor for a second oscillator. - }; - - // Norrmalizes a timbre object by making a copy that has exactly - // the right set of timbre fields, defaulting when needed. - // A timbre can specify any of the fields of defaultTimbre; any - // unspecified fields are treated as they are set in defaultTimbre. - function makeTimbre(options, atop) { - if (!options) { - options = {}; - } - if (typeof(options) == 'string') { - // Abbreviation: name a wave to get a default timbre for that wave. - options = { wave: options }; - } - var result = {}, key, - wt = atop && atop.wavetable && atop.wavetable[options.wave]; - for (key in defaultTimbre) { - if (options.hasOwnProperty(key)) { - result[key] = options[key]; - } else if (wt && wt.defs && wt.defs.hasOwnProperty(key)) { - result[key] = wt.defs[key]; - } else{ - result[key] = defaultTimbre[key]; - } - } - return result; - } - - var whiteNoiseBuf = null; - function getWhiteNoiseBuf() { - if (whiteNoiseBuf == null) { - var ac = getAudioTop().ac, - bufferSize = 2 * ac.sampleRate, - whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), - output = whiteNoiseBuf.getChannelData(0); - for (var i = 0; i < bufferSize; i++) { - output[i] = Math.random() * 2 - 1; - } - } - return whiteNoiseBuf; - } - - // This utility function creates an oscillator at the given frequency - // and the given wavename. It supports lookups in a static wavetable, - // defined right below. - function makeOscillator(atop, wavename, freq) { - if (wavename == 'noise') { - var whiteNoise = atop.ac.createBufferSource(); - whiteNoise.buffer = getWhiteNoiseBuf(); - whiteNoise.loop = true; - return whiteNoise; - } - var wavetable = atop.wavetable, o = atop.ac.createOscillator(), - k, pwave, bwf, wf; - try { - if (wavetable.hasOwnProperty(wavename)) { - // Use a customized wavetable. - pwave = wavetable[wavename].wave; - if (wavetable[wavename].freq) { - bwf = 0; - // Look for a higher-frequency variant. - for (k in wavetable[wavename].freq) { - wf = Number(k); - if (freq > wf && wf > bwf) { - bwf = wf; - pwave = wavetable[wavename].freq[bwf]; - } - } - } - if (!o.setPeriodicWave && o.setWaveTable) { - // The old API name: Safari 7 still uses this. - o.setWaveTable(pwave); - } else { - // The new API name. - o.setPeriodicWave(pwave); - } - } else { - o.type = wavename; - } - } catch(e) { - if (_window.console) { _window.console.log(e); } - // If unrecognized, just use square. - // TODO: support "noise" or other wave shapes. - o.type = 'square'; - } - o.frequency.value = freq; - return o; - } - - // Accepts either an ABC pitch or a midi number and converts to midi. - Instrument.pitchToMidi = function(n) { - if (typeof(n) == 'string') { return pitchToMidi(n); } - return n; - } - - // Accepts either an ABC pitch or a midi number and converts to ABC pitch. - Instrument.midiToPitch = function(n) { - if (typeof(n) == 'number') { return midiToPitch(n); } - return n; - } - - return Instrument; -})(); +} // wavetable is a table of names for nonstandard waveforms. // The table maps names to objects that have wave: and freq: @@ -5493,13 +5505,15 @@ function makeWavetable(ac) { // TODO: this approach attenuates low notes too much - // this should be fixed. defs: { wave: 'piano', gain: 0.5, - attack: 0.002, decay: 0.4, sustain: 0.005, release: 0.1, + attack: 0.002, decay: 0.25, sustain: 0.03, release: 0.1, decayfollow: 0.7, - cutoff: 800, cutfollow: 0.1, resonance: 1, detune: 1.001 } + cutoff: 800, cutfollow: 0.1, resonance: 1, detune: 0.9994 } } }); } +// End of musical.js copy. + ////////////////////////////////////////////////////////////////////////// // SYNC, REMOVE SUPPORT @@ -5661,7 +5675,7 @@ function globalhelp(obj) { helpwrite('This is an unassigned value.'); return helpok; } - if (obj === _window) { + if (obj === global) { helpwrite('The global window object represents the browser window.'); return helpok; } @@ -5681,7 +5695,7 @@ function globalhelp(obj) { helplist = []; for (var name in helptable) { if (helptable[name].helptext && helptable[name].helptext.length && - (!(name in _window) || typeof(_window[name]) == 'function')) { + (!(name in global) || typeof(global[name]) == 'function')) { helplist.push(name); } } @@ -5910,7 +5924,7 @@ function wrapwindowevent(name, helptext) { : null; if (forKey) { focusWindowIfFirst(); } if (fn == null && typeof(d) == 'function') { fn = d; d = null; } - $(_window).on(name + '.turtleevent', null, d, !filter ? fn : function(e) { + $(global).on(name + '.turtleevent', null, d, !filter ? fn : function(e) { if (interrupted) return; if ($(e.target).closest(filter).length) { return; } return fn.apply(this, arguments); @@ -5919,7 +5933,7 @@ function wrapwindowevent(name, helptext) { } function windowhasturtleevent() { - var events = $._data(_window, 'events'); + var events = $._data(global, 'events'); if (!events) return false; for (var type in events) { var entries = events[type]; @@ -6807,7 +6821,7 @@ var turtlefn = { complete(); }, 250); } catch (e) { - if (_window.console) { _window.console.log(e); } + if (global.console) { global.console.log(e); } complete(); } }); @@ -6973,7 +6987,7 @@ var turtlefn = { if (state.drawOnCanvas) { sync(elem, state.drawOnCanvas); } - if (!canvas || canvas === _window) { + if (!canvas || canvas === global) { state.drawOnCanvas = null; } else if (canvas.jquery && $.isFunction(canvas.canvas)) { state.drawOnCanvas = canvas.canvas(); @@ -7080,7 +7094,7 @@ var turtlefn = { this.plan(function(j, elem) { cc.appear(j); if ($.isWindow(elem) || elem.nodeType === 9) { - _window.location.reload(); + global.location.reload(); cc.resolve(j); return; } @@ -7657,7 +7671,7 @@ var dollar_turtle_methods = { // Disable all input. $('.turtleinput').prop('disabled', true); // Detach all event handlers on the window. - $(_window).off('.turtleevent'); + $(global).off('.turtleevent'); // Low-level detach all jQuery events $('*').not('#_testpanel *').map( function(i, e) { $._data(e, 'events', null) }); @@ -8115,7 +8129,7 @@ var dollar_turtle_methods = { ["loadscript(url, callback) Loads Javascript or Coffeescript from " + "the given URL, calling callback when done."], function loadscript(url, callback) { - if (_window.CoffeeScript && /\.(?:coffee|cs)$/.test(url)) { + if (global.CoffeeScript && /\.(?:coffee|cs)$/.test(url)) { CoffeeScript.load(url, callback); } else { $.getScript(url, callback); @@ -8271,18 +8285,18 @@ $.turtle = function turtle(id, options) { if (!('see' in options) || options.see) { exportsee(); exportedsee = true; - if (_window.addEventListener) { - _window.addEventListener('error', see); + if (global.addEventListener) { + global.addEventListener('error', see); } else { - _window.onerror = see; + global.onerror = see; } // Set up an alias. - _window.log = see; + global.log = see; } // Copy $.turtle.* functions into global namespace. if (!('functions' in options) || options.functions) { - _window.printpage = _window.print; - $.extend(_window, dollar_turtle_methods); + global.printpage = global.print; + $.extend(global, dollar_turtle_methods); } // Set default turtle speed globaldefaultspeed(('defaultspeed' in options) ? @@ -8323,7 +8337,7 @@ $.turtle = function turtle(id, options) { if (!('ids' in options) || options.ids) { turtleids(options.idprefix); if (selector && id) { - _window[id] = selector; + global[id] = selector; } } // Set up test console. @@ -8349,7 +8363,7 @@ $.turtle = function turtle(id, options) { } // Return an eval loop hook string if 'see' is exported. if (exportedsee) { - if (_window.CoffeeScript) { + if (global.CoffeeScript) { return "see.init(eval(see.cs))"; } else { return see.here; @@ -8392,9 +8406,9 @@ function copyhelp(method, fname, extrahelp, globalfn) { function globalizeMethods(thisobj, fnames) { var replaced = []; for (var fname in fnames) { - if (fnames.hasOwnProperty(fname) && !(fname in _window)) { + if (fnames.hasOwnProperty(fname) && !(fname in global)) { replaced.push(fname); - _window[fname] = (function(fname) { + global[fname] = (function(fname) { var method = thisobj[fname], target = thisobj; return copyhelp(method, fname, extrahelp, (function globalized() { /* Use parentheses to call a function */ @@ -8408,7 +8422,7 @@ function globalizeMethods(thisobj, fnames) { function clearGlobalTurtle() { global_turtle = null; for (var j = 0; j < global_turtle_methods.length; ++j) { - delete _window[global_turtle_methods[j]]; + delete global[global_turtle_methods[j]]; } global_turtle_methods.length = 0; } @@ -8423,10 +8437,10 @@ $.cleanData = function(elems) { state.stream.stop(); } // Undefine global variablelem. - if (elem.id && _window[elem.id] && _window[elem.id].jquery && - _window[elem.id].length === 1 && - _window[elem.id][0] === elem) { - delete _window[elem.id]; + if (elem.id && global[elem.id] && global[elem.id].jquery && + global[elem.id].length === 1 && + global[elem.id][0] === elem) { + delete global[elem.id]; } // Clear global turtlelem. if (elem === global_turtle) { @@ -8821,9 +8835,9 @@ function nameToImg(name, defaultshape) { var hostname = absoluteUrlObject(name).hostname; // Use proxy to load image if the image is offdomain but the page is on // a pencil host (with a proxy). - if (!isPencilHost(hostname) && isPencilHost(_window.location.hostname)) { - name = _window.location.protocol + '//' + - _window.location.host + '/proxy/' + absoluteUrl(name); + if (!isPencilHost(hostname) && isPencilHost(global.location.hostname)) { + name = global.location.protocol + '//' + + global.location.host + '/proxy/' + absoluteUrl(name); } return { url: name, @@ -8907,8 +8921,8 @@ function hatchone(name, container, defaultshape) { if (isID) { result.attr('id', name); // Update global variable unless there is a conflict. - if (attaching_ids && !_window.hasOwnProperty(name)) { - _window[name] = result; + if (attaching_ids && !global.hasOwnProperty(name)) { + global[name] = result; } } // Move it to the center of the document and export the name as a global. @@ -9026,11 +9040,11 @@ function globaltick(rps, fn) { rps = 1; } if (tickinterval) { - _window.clearInterval(tickinterval); + global.clearInterval(tickinterval); tickinterval = null; } if (fn && rps) { - tickinterval = _window.setInterval( + tickinterval = global.setInterval( function() { // Set default speed to Infinity within tick(). try { @@ -9059,7 +9073,7 @@ function turtleids(prefix) { prefix = ''; } $('[id]').each(function(j, item) { - _window[prefix + item.id] = $('#' + item.id); + global[prefix + item.id] = $('#' + item.id); }); attaching_ids = true; } @@ -9072,7 +9086,7 @@ function turtleevents(prefix) { prefix = 'last'; } if (eventsaver) { - $(_window).off($.map(eventfn, function(x,k) { return k; }).join(' '), + $(global).off($.map(eventfn, function(x,k) { return k; }).join(' '), eventsaver); } if (prefix || prefix === '') { @@ -9084,20 +9098,20 @@ function turtleevents(prefix) { } for (j = 0; j < names.length; ++j) { var name = names[j]; - old = _window[name], prop; + old = global[name], prop; if (old && old.__proto__ === e.__proto__) { for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } } else { - _window[name] = e; + global[name] = e; } } }); - _window[prefix + 'mouse'] = new $.Event(); + global[prefix + 'mouse'] = new $.Event(); for (var k in eventfn) { - _window[prefix + k] = new $.Event(); + global[prefix + k] = new $.Event(); } - $(_window).on($.map(eventfn, function(x,k) { return k; }).join(' '), + $(global).on($.map(eventfn, function(x,k) { return k; }).join(' '), eventsaver); } } @@ -9109,15 +9123,15 @@ function turtleevents(prefix) { function autoScrollAfter(f) { var slop = 10, seen = autoScrollBottomSeen(), - stick = ($(_window).height() + $(_window).scrollTop() + slop >= + stick = ($(global).height() + $(global).scrollTop() + slop >= $('html').outerHeight(true)); f(); if (stick) { - var scrollPos = $(_window).scrollTop(), + var scrollPos = $(global).scrollTop(), advancedScrollPos = Math.min(seen, - $('html').outerHeight(true) - $(_window).height()); + $('html').outerHeight(true) - $(global).height()); if (advancedScrollPos > scrollPos) { - $(_window).scrollTop(advancedScrollPos); + $(global).scrollTop(advancedScrollPos); } } } @@ -9136,7 +9150,7 @@ function autoScrollBottomSeen() { var offset = $('body').offset(); var doctop = offset ? offset.top : 8; autoScrollState.bottomSeen = Math.min( - $(_window).height() + $(_window).scrollTop(), + $(global).height() + $(global).scrollTop(), $('body').height() + doctop); } return autoScrollState.bottomSeen; @@ -9145,9 +9159,9 @@ function autoScrollBottomSeen() { // location after running the passed function. (E.g., to allow focusing // a control without autoscrolling.) function undoScrollAfter(f) { - var scrollPos = $(_window).scrollTop(); + var scrollPos = $(global).scrollTop(); f(); - $(_window).scrollTop(scrollPos); + $(global).scrollTop(scrollPos); } ////////////////////////////////////////////////////////////////////////// @@ -9574,7 +9588,7 @@ var debug = { try { if (parent && parent.ide) { this.ide = parent.ide; - this.ide.bindframe(_window); + this.ide.bindframe(global); this.attached = true; } } catch(e) { @@ -9582,8 +9596,8 @@ var debug = { this.attached = false; } if (this.attached) { - if (_window.addEventListener) { - _window.addEventListener('error', function(event) { + if (global.addEventListener) { + global.addEventListener('error', function(event) { // An error event will highlight the error line. debug.reportEvent('error', [event]); }); @@ -9637,7 +9651,7 @@ debug.init(); } var linestart = null, linecanvas = null, lineend = null, xa = 0, ya = 0, xb = 0, yb = 0, xt, yt, dr, ar; - $(_window).on('mousedown mouseup mousemove keydown', function(e) { + $(global).on('mousedown mouseup mousemove keydown', function(e) { if (e.type == 'keydown') { if (e.which < 27) return; if (linecanvas) linecanvas.remove(); @@ -9764,16 +9778,16 @@ debug.init(); var pos = { left: '', top: '', right: '', bottom: '' }; if (p.pageX + 5 < s.pageX) { pos.left = Math.max( - p.pageX - $(_window).scrollLeft() - location.outerWidth() - 5, 2); + p.pageX - $(global).scrollLeft() - location.outerWidth() - 5, 2); } else { - pos.left = Math.min(p.pageX - $(_window).scrollLeft() + 5, + pos.left = Math.min(p.pageX - $(global).scrollLeft() + 5, $(document).width() - location.outerWidth() - 2); } if (p.pageY + 5 < s.pageY) { pos.top = Math.max( - p.pageY - $(_window).scrollTop() - location.outerHeight() - 5, 2); + p.pageY - $(global).scrollTop() - location.outerHeight() - 5, 2); } else { - pos.top = Math.min(p.pageY - $(_window).scrollTop() + 5, + pos.top = Math.min(p.pageY - $(global).scrollTop() + 5, $(document).height() - location.outerHeight() - 2); } location.css(pos); @@ -9819,7 +9833,7 @@ var panel = 'auto'; try { // show panel by default if framed inside a an ide, // and if the screen is big enough (i.e., omit mobile clients). - if (_window.self !== _window.top && + if (global.self !== global.top && screen.width >= 800 && screen.height >= 600 && parent && parent.ide) { panel = parent.ide.getOptions().panel; } } catch(e) {} @@ -9831,10 +9845,10 @@ var uselocalstorage = '_loghistory'; var panelheight = 50; var currentscope = ''; var scopes = { - '': { e: _window.eval, t: _window }, - top: { e: _window.eval, t: _window } + '': { e: global.eval, t: global }, + top: { e: global.eval, t: global } }; -var coffeescript = _window.CoffeeScript; +var coffeescript = global.CoffeeScript; var seejs = '(function(){return eval(arguments[0]);})'; function init(options) { @@ -9905,7 +9919,7 @@ var initialvardecl = new RegExp( function barecs(s) { // Compile coffeescript in bare mode. - var compiler = coffeescript || _window.CoffeeScript; + var compiler = coffeescript || global.CoffeeScript; var compiled = compiler.compile(s, {bare:1}); if (compiled) { // Further strip top-level var decls out of the coffeescript so @@ -9932,22 +9946,22 @@ function exportsee() { see.js = seejs; see.cs = '(function(){return eval(' + seepkg + '.barecs(arguments[0]));})'; see.version = version; - _window[seepkg] = see; + global[seepkg] = see; } function noteoldvalue(name) { return { name: name, - has: _window.hasOwnProperty(name), - value: _window[name] + has: global.hasOwnProperty(name), + value: global[name] }; } function restoreoldvalue(old) { if (!old.has) { - delete _window[old.name]; + delete global[old.name]; } else { - _window[old.name] = old.value; + global[old.name] = old.value; } } @@ -10011,7 +10025,7 @@ var queue = []; see = function see() { if (logconsole && typeof(logconsole.log) == 'function') { - logconsole.log.apply(_window.console, arguments); + logconsole.log.apply(global.console, arguments); } var args = Array.prototype.slice.call(arguments); queue.push(''); @@ -10360,12 +10374,12 @@ function expand(prefix, obj, depth, output) { } } function initlogcss() { - if (!addedcss && !_window.document.getElementById('_logcss')) { - var style = _window.document.createElement('style'); + if (!addedcss && !global.document.getElementById('_logcss')) { + var style = global.document.createElement('style'); style.id = '_logcss'; style.innerHTML = (linestyle ? 'samp._log{' + linestyle + '}' : '') + logcss; - _window.document.head.appendChild(style); + global.document.head.appendChild(style); addedcss = true; } } @@ -10422,7 +10436,7 @@ function flushqueue() { var elt = aselement(logelement, null); if (elt && elt.appendChild && queue.length) { initlogcss(); - var temp = _window.document.createElement('samp'); + var temp = global.document.createElement('samp'); temp.innerHTML = queue.join(''); queue.length = 0; var complete = stickscroll(); @@ -10471,7 +10485,7 @@ function promptcaret(color) { return ''; } function getSelectedText(){ - if(_window.getSelection) { return _window.getSelection().toString(); } + if(global.getSelection) { return global.getSelection().toString(); } else if(document.getSelection) { return document.getSelection(); } else if(document.selection) { return document.selection.createRange().text; } @@ -10487,7 +10501,7 @@ function readlocalstorage() { } var state = { height: panelheight, history: [] }, result; try { - result = _window.JSON.parse(_window.localStorage[uselocalstorage]); + result = global.JSON.parse(global.localStorage[uselocalstorage]); } catch(e) { result = noLocalStorage || {}; } @@ -10518,21 +10532,21 @@ function updatelocalstorage(state) { } if (changed) { try { - _window.localStorage[uselocalstorage] = _window.JSON.stringify(stored); + global.localStorage[uselocalstorage] = global.JSON.stringify(stored); } catch(e) { noLocalStorage = stored; } } } function wheight() { - return _window.innerHeight || $(_window).height(); + return global.innerHeight || $(global).height(); } function initconsolelog() { try { - if (consolelog && _window.console && !_window.console._log && - 'function' == typeof _window.console.log) { - var _log = _window.console._log = _window.console.log; - _window_.console.log = function log() { + if (consolelog && global.console && !global.console._log && + 'function' == typeof global.console.log) { + var _log = global.console._log = global.console.log; + global_.console.log = function log() { _log.apply(this, arguments); see.apply(this, arguments); } @@ -10556,7 +10570,7 @@ function tryinitpanel() { } $('#_testpanel').show(); } else { - if (!_window.document.getElementById('_testlog') && _window.document.body) { + if (!global.document.getElementById('_testlog') && global.document.body) { initconsolelog(); initlogcss(); var state = readlocalstorage(); @@ -10687,7 +10701,7 @@ function tryinitpanel() { } if (e.type == 'mouseup' || e.type == 'blur' || e.type == 'mousemove' && e.which != dragwhich) { - $(_window).off('mousemove mouseup blur', dragfunc); + $(global).off('mousemove mouseup blur', dragfunc); if (document.releaseCapture) { document.releaseCapture(); } if ($('#_testpanel').height() != state.height) { state.height = $('#_testpanel').height(); @@ -10695,7 +10709,7 @@ function tryinitpanel() { } } }; - $(_window).on('mousemove mouseup blur', dragfunc); + $(global).on('mousemove mouseup blur', dragfunc); return false; }); $('#_testpanel').on('mouseup', function(e) { From 44ef3e31ced75e95d50454ffc5cc5f4961f27175 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 May 2015 09:52:32 -0400 Subject: [PATCH 21/68] Latest musical.js. --- jquery-turtle.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 11569e0..d15672e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3970,6 +3970,7 @@ var Instrument = (function() { } } + Instrument.timeOffset = 0.0625;// Seconds to delay all audiable timing. Instrument.dequeueTime = 0.5; // Seconds before an event to reexamine queue. Instrument.bufferSecs = 2; // Seconds ahead to put notes in WebAudio. Instrument.toneLength = 1; // Default duration of a tone. @@ -4106,7 +4107,7 @@ var Instrument = (function() { // node graph for the tone generators and filters for the tone. Instrument.prototype._makeSound = function(record) { var timbre = record.timbre || this._timbre, - starttime = record.time, + starttime = record.time + Instrument.timeOffset, releasetime = starttime + record.duration, attacktime = Math.min(releasetime, starttime + timbre.attack), decaytime = timbre.decay * @@ -4183,12 +4184,13 @@ var Instrument = (function() { // Truncates a sound previously scheduled by _makeSound by using // cancelScheduledValues and directly ramping down to zero. // Can only be used to shorten a sound. - Instrument.prototype._truncateSound = function(record, releasetime) { - if (releasetime < record.time + record.duration) { - record.duration = Math.max(0, releasetime - record.time); + Instrument.prototype._truncateSound = function(record, truncatetime) { + if (truncatetime < record.time + record.duration) { + record.duration = Math.max(0, truncatetime - record.time); if (record.gainNode) { var timbre = record.timbre || this._timbre, - starttime = record.time, + starttime = record.time + Instrument.timeOffset, + releasetime = truncatetime + Instrument.timeOffset, attacktime = Math.min(releasetime, starttime + timbre.attack), decaytime = timbre.decay * Math.pow(440 / record.frequency, timbre.decayfollow), From 92eb0667a469a6cc5fcb7b4e8b3c6a2d3eba6584 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 28 May 2015 10:05:11 -0400 Subject: [PATCH 22/68] Fix instrument unit test. --- test/instrument.html | 116 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/test/instrument.html b/test/instrument.html index 286258d..c0b9ff4 100644 --- a/test/instrument.html +++ b/test/instrument.html @@ -467,64 +467,64 @@ "on57-3845", "on60-3845", "on63-3845", - "off54-5805", - "off57-5805", - "off60-5805", - "off63-5805", - "on55-5805", - "on59-5805", - "on62-5805", - "off55-6743", - "off59-6743", - "off62-6743", - "on44-7227", - "off44-7680", - "on56-7696", - "on59-7696", - "on62-7696", - "on65-7696", - "off56-10358", - "off59-10358", - "off62-10358", - "off65-10358", - "on56-10358", - "on59-10358", - "on62-10358", - "on65-10358", - "off56-10546", - "off59-10546", - "off62-10546", - "off65-10546", - "on55-10562", - "on59-10562", - "on62-10562", - "on67-10562", - "off55-11281", - "off59-11281", - "off62-11281", - "off67-11281", - "on53-11281", - "on59-11281", - "on62-11281", - "on68-11281", - "off53-11500", - "off59-11500", - "off62-11500", - "off68-11500", - "on51-11531", - "on59-11531", - "on62-11531", - "on68-11531", - "off59-13491", - "off62-13491", - "off68-13491", - "on60-13491", - "on63-13491", - "on67-13491", - "off51-14413", - "off60-14413", - "off63-14413", - "off67-14413" + "off54-5804", + "off57-5804", + "off60-5804", + "off63-5804", + "on55-5804", + "on59-5804", + "on62-5804", + "off55-6742", + "off59-6742", + "off62-6742", + "on44-7226", + "off44-7679", + "on56-7695", + "on59-7695", + "on62-7695", + "on65-7695", + "off56-10357", + "off59-10357", + "off62-10357", + "off65-10357", + "on56-10357", + "on59-10357", + "on62-10357", + "on65-10357", + "off56-10545", + "off59-10545", + "off62-10545", + "off65-10545", + "on55-10576", + "on59-10576", + "on62-10576", + "on67-10576", + "off55-11279", + "off59-11279", + "off62-11279", + "off67-11279", + "on53-11295", + "on59-11295", + "on62-11295", + "on68-11295", + "off53-11514", + "off59-11514", + "off62-11514", + "off68-11514", + "on51-11530", + "on59-11530", + "on62-11530", + "on68-11530", + "off59-13474", + "off62-13474", + "off68-13474", + "on60-13474", + "on63-13474", + "on67-13474", + "off51-14412", + "off60-14412", + "off63-14412", + "off67-14412" ]); ins.remove(); start(); From 2f51211abe6d0bf14cf4b02f57973650f5f3fa9c Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 11 Jun 2015 11:40:36 -0400 Subject: [PATCH 23/68] Special treatment of load/save for json files. --- jquery-turtle.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 11569e0..3750fbc 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7833,11 +7833,16 @@ var dollar_turtle_methods = { var val; $.ajax(apiUrl(url, 'load'), { async: !!cb, complete: function(xhr) { try { - val = JSON.parse(xhr.responseText); + val = xhr.responseObject = JSON.parse(xhr.responseText); if (typeof(val.data) == 'string' && typeof(val.file) == 'string') { val = val.data; + if (/\.json(?:$|\?|\#)/.test(url)) { + try { val = JSON.parse(val); } catch(e) {} + } } else if ($.isArray(val.list) && typeof(val.directory) == 'string') { val = val.list; + } else if (val.error) { + val = null; } } catch(e) { if (val == null && xhr && xhr.responseText) { @@ -7856,6 +7861,9 @@ var dollar_turtle_methods = { function(url, data, cb) { if (!url) throw new Error('Missing url for save'); var payload = { }, url = apiUrl(url, 'save'), key; + if (/\.json(?:$|\?|\#)/.test(url)) { + data = JSON.stringify(data, null, 2); + } if (typeof(data) == 'string' || typeof(data) == 'number') { payload.data = data; } else { From bb00f804b3782aea72c669e379388f4ed7ade939 Mon Sep 17 00:00:00 2001 From: calistenson Date: Tue, 16 Jun 2015 10:14:45 -0400 Subject: [PATCH 24/68] Added a reportEvent to seeeval --- jquery-turtle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index d50043f..7633e73 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9920,6 +9920,7 @@ function seeeval(scope, code) { if (scopes[scope].e) { ef = scopes[scope].e; } if (scopes[scope].t) { et = scopes[scope].t; } } + debug.reportEvent("seeeval", [scope, code]); return ef.call(et, code); } From e511e0705eb3ed848403fc1dee333a0dec02892a Mon Sep 17 00:00:00 2001 From: Bob Cassels Date: Wed, 17 Jun 2015 03:06:30 -0400 Subject: [PATCH 25/68] Rename degree trig functions; fix accuracy; add radian trig functions; add tests. --- jquery-turtle.js | 108 +++++++++++++++++++++++++++++++----- test/globals.html | 7 +++ test/numeric_functions.html | 47 ++++++++++++++++ 3 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 test/numeric_functions.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 7633e73..0c58411 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8047,41 +8047,119 @@ var dollar_turtle_methods = { ["abs(x) The absolute value of x. " + "see abs -5"], Math.abs), acos: wrapraw('acos', - ["acos(degreees) Trigonometric arccosine, in degrees. " + + ["acos(radians) Trigonometric arccosine, in radians. " + "see acos 0.5"], - function acos(x) { return roundEpsilon(Math.acos(x) * 180 / Math.PI); } + function acos(x) { return Math.acos(x); } ), asin: wrapraw('asin', - ["asin(degreees) Trigonometric arcsine, in degrees. " + + ["asin(radians) Trigonometric arcsine, in radians. " + "see asin 0.5"], - function asin(x) { return roundEpsilon(Math.asin(x) * 180 / Math.PI); } + function asin(x) { return Math.asin(x); } ), atan: wrapraw('atan', - ["atan(degreees) Trigonometric arctangent, in degrees. " + + ["atan(radians) Trigonometric arctangent, in radians. " + "see atan 0.5"], - function atan(x) { return roundEpsilon(Math.atan(x) * 180 / Math.PI); } + function atan(x) { return Math.atan(x); } ), atan2: wrapraw('atan2', - ["atan2(degreees) Trigonometric two-argument arctangent, " + - "in degrees. see atan -1, 0"], + ["atan2(radians) Trigonometric two-argument arctangent, " + + "in radians. see atan -1, 0"], function atan2(x, y) { - return roundEpsilon(Math.atan2(x, y) * 180 / Math.PI); + return Math.atan2(x, y); }), cos: wrapraw('cos', - ["cos(degreees) Trigonometric cosine, in degrees. " + + ["cos(radians) Trigonometric cosine, in radians. " + "see cos 45"], - function cos(x) { return roundEpsilon(Math.cos((x % 360) * Math.PI / 180)); } + function cos(x) { return Math.cos((x % 360) * Math.PI / 180); } ), sin: wrapraw('sin', - ["sin(degreees) Trigonometric sine, in degrees. " + + ["sin(radians) Trigonometric sine, in radians. " + "see sin 45"], - function sin(x) { return roundEpsilon(Math.sin((x % 360) * Math.PI / 180)); } + function sin(x) { return Math.sin((x % 360) * Math.PI / 180); } ), tan: wrapraw('tan', - ["tan(degreees) Trigonometric tangent, in degrees. " + + ["tan(radians) Trigonometric tangent, in radians. " + "see tan 45"], - function tan(x) { return roundEpsilon(Math.tan((x % 360) * Math.PI / 180)); } + function tan(x) { return Math.tan((x % 360) * Math.PI / 180); } ), + acosd: wrapraw('acosd', + ["acosd(degrees) Trigonometric arccosine, in degrees. " + + "see acosd 0.5"], + function acosd(x) { return Math.acos(x); } + ), + asind: wrapraw('asind', + ["asind(degrees) Trigonometric arcsine, in degrees. " + + "see asind 0.5"], + function asind(x) { return Math.asin(x); } + ), + atand: wrapraw('atand', + ["atand(degrees) Trigonometric arctangent, in degrees. " + + "see atand 0.5"], + function atand(x) { return Math.atan(x); } + ), + atan2d: wrapraw('atan2d', + ["atan2d(degrees) Trigonometric two-argument arctangent, " + + "in degrees. see atand -1, 0"], + function atan2d(x, y) { + return Math.atan2(x, y); + }), + cosd: wrapraw('cosd', + ["cosd(degrees) Trigonometric cosine, in degrees. " + + "see cosd 45"], + function cosd(x) { + x = x % 360; + if (x % 30 === 0) { + switch ((x < 0) ? x + 360 : x) { + case 0: return 1; + case 60: return .5; + case 90: return 0; + case 120: return -.5; + case 180: return -1; + case 240: return -.5; + case 270: return 0; + case 300: return .5; + } + } + return Math.cos(x / 180 * Math.PI); + }), + sind: wrapraw('sind', + ["sind(degrees) Trigonometric sine, in degrees. " + + "see sind 45"], + function sind(x) { + x = x % 360; + if (x % 30 === 0) { + switch ((x < 0) ? x + 360 : x) { + case 0: return 0; + case 30: return .5; + case 90: return 1; + case 150: return .5; + case 180: return 0; + case 210: return -.5; + case 270: return -1; + case 330: return -.5 + } + } + return Math.sin(x / 180 * Math.PI); + }), + tand: wrapraw('tand', + ["tand(degrees) Trigonometric tangent, in degrees. " + + "see tand 45"], + function tand(x) { + x = x % 360; + if (x % 45 === 0) { + switch ((x < 0) ? x + 360 : x) { + case 0: return 0; + case 45: return 1; + case 90: return Infinity; + case 135: return -1; + case 180: return 0; + case 225: return 1; + case 270: return Infinity; + case 315: return -1 + } + } + return Math.tan(x / 180 * Math.PI); + }), ceil: wrapraw('ceil', ["ceil(x) Round up. " + "see ceil 1.9"], Math.ceil), diff --git a/test/globals.html b/test/globals.html index 0376880..6f5b8c6 100644 --- a/test/globals.html +++ b/test/globals.html @@ -86,6 +86,13 @@ "cos", "sin", "tan", + "acosd", + "asind", + "atand", + "atan2d", + "cosd", + "sind", + "tand", "ceil", "floor", "round", diff --git a/test/numeric_functions.html b/test/numeric_functions.html new file mode 100644 index 0000000..368e893 --- /dev/null +++ b/test/numeric_functions.html @@ -0,0 +1,47 @@ + + + + + +
+ From a278ee91f73ae0cb36671b640e7c2d7c99014931 Mon Sep 17 00:00:00 2001 From: Bob Cassels Date: Fri, 19 Jun 2015 23:11:46 -0400 Subject: [PATCH 26/68] Replace atan2 with 2-arg atan; implement inverse trig functions. --- jquery-turtle.js | 126 +++++++++++++++++++++--------------- test/globals.html | 2 - test/numeric_functions.html | 32 ++++++++- 3 files changed, 105 insertions(+), 55 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 0c58411..d375efa 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7628,6 +7628,8 @@ $.fn.extend(turtlefn); var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7" +function modulo(n, m) { return (+n % (m = +m) + m) % m; } + var eventfn = { click:1, dblclick:1, mouseup:1, mousedown:1, mousemove:1 }; function global_turtle_animating() { @@ -8047,77 +8049,97 @@ var dollar_turtle_methods = { ["abs(x) The absolute value of x. " + "see abs -5"], Math.abs), acos: wrapraw('acos', - ["acos(radians) Trigonometric arccosine, in radians. " + + ["acos(x) Trigonometric arccosine, in radians. " + "see acos 0.5"], function acos(x) { return Math.acos(x); } ), asin: wrapraw('asin', - ["asin(radians) Trigonometric arcsine, in radians. " + + ["asin(y) Trigonometric arcsine, in radians. " + "see asin 0.5"], - function asin(x) { return Math.asin(x); } + function asin(y) { return Math.asin(y); } ), atan: wrapraw('atan', - ["atan(radians) Trigonometric arctangent, in radians. " + + ["atan(y, x = 1) Trigonometric arctangent, in radians. " + "see atan 0.5"], - function atan(x) { return Math.atan(x); } + function atan(x, y) { return Math.atan2(x, (y == undefined) ? 1 : y); } ), - atan2: wrapraw('atan2', - ["atan2(radians) Trigonometric two-argument arctangent, " + - "in radians. see atan -1, 0"], - function atan2(x, y) { - return Math.atan2(x, y); - }), cos: wrapraw('cos', ["cos(radians) Trigonometric cosine, in radians. " + - "see cos 45"], - function cos(x) { return Math.cos((x % 360) * Math.PI / 180); } + "see cos 0"], + function cos(x) { return Math.cos(x); } ), sin: wrapraw('sin', ["sin(radians) Trigonometric sine, in radians. " + - "see sin 45"], - function sin(x) { return Math.sin((x % 360) * Math.PI / 180); } + "see sin 0"], + function sin(x) { return Math.sin(x); } ), tan: wrapraw('tan', ["tan(radians) Trigonometric tangent, in radians. " + - "see tan 45"], - function tan(x) { return Math.tan((x % 360) * Math.PI / 180); } + "see tan 0"], + function tan(x) { return Math.tan(x); } ), + + // For degree versions of trig functions, make sure we return exact + // results when possible. The set of values we have to consider is + // fortunately very limited. See "Rational Values of Trigonometric + // Functions." http://www.jstor.org/stable/2304540 + acosd: wrapraw('acosd', - ["acosd(degrees) Trigonometric arccosine, in degrees. " + + ["acosd(x) Trigonometric arccosine, in degrees. " + "see acosd 0.5"], - function acosd(x) { return Math.acos(x); } - ), + function acosd(x) { + switch (x) { + case 1: return 0; + case .5: return 60; + case 0: return 90; + case -.5: return 120; + case -1: return 180; + } + return Math.acos(x) * 180 / Math.PI; + }), asind: wrapraw('asind', - ["asind(degrees) Trigonometric arcsine, in degrees. " + + ["asind(x) Trigonometric arcsine, in degrees. " + "see asind 0.5"], - function asind(x) { return Math.asin(x); } - ), + function asind(x) { + switch (x) { + case 1: return 90; + case .5: return 30; + case 0: return 0; + case -.5: return -30; + case -1: return -90; + } + return Math.asin(x) * 180 / Math.PI; + }), atand: wrapraw('atand', - ["atand(degrees) Trigonometric arctangent, in degrees. " + - "see atand 0.5"], - function atand(x) { return Math.atan(x); } - ), - atan2d: wrapraw('atan2d', - ["atan2d(degrees) Trigonometric two-argument arctangent, " + - "in degrees. see atand -1, 0"], - function atan2d(x, y) { - return Math.atan2(x, y); + ["atand(y, x = 1) Trigonometric arctangent, " + + "in degrees. see atand -1, 0/mark>"], + function atand(y, x) { + if (x == undefined) { x = 1; } + if (y == 0) { + return (x == 0) ? NaN : ((x > 0) ? 0 : 180); + } else if (x == 0) { + return (y > 0) ? Infinity : -Infinity; + } else if (abs(y) == abs(x)) { + return (y > 0) ? ((x > 0) ? 45 : 135) : + ((x > 0) ? -45 : -135); + } + return Math.atan2(y, x) * 180 / Math.PI; }), cosd: wrapraw('cosd', ["cosd(degrees) Trigonometric cosine, in degrees. " + "see cosd 45"], function cosd(x) { - x = x % 360; + x = modulo(x, 360); if (x % 30 === 0) { switch ((x < 0) ? x + 360 : x) { - case 0: return 1; - case 60: return .5; - case 90: return 0; + case 0: return 1; + case 60: return .5; + case 90: return 0; case 120: return -.5; - case 180: return -1; + case 180: return -1; case 240: return -.5; - case 270: return 0; - case 300: return .5; + case 270: return 0; + case 300: return .5; } } return Math.cos(x / 180 * Math.PI); @@ -8126,17 +8148,17 @@ var dollar_turtle_methods = { ["sind(degrees) Trigonometric sine, in degrees. " + "see sind 45"], function sind(x) { - x = x % 360; + x = modulo(x, 360); if (x % 30 === 0) { switch ((x < 0) ? x + 360 : x) { - case 0: return 0; - case 30: return .5; - case 90: return 1; - case 150: return .5; - case 180: return 0; + case 0: return 0; + case 30: return .5; + case 90: return 1; + case 150: return .5; + case 180: return 0; case 210: return -.5; - case 270: return -1; - case 330: return -.5 + case 270: return -1; + case 330: return -.5; } } return Math.sin(x / 180 * Math.PI); @@ -8145,16 +8167,16 @@ var dollar_turtle_methods = { ["tand(degrees) Trigonometric tangent, in degrees. " + "see tand 45"], function tand(x) { - x = x % 360; + x = modulo(x, 360); if (x % 45 === 0) { switch ((x < 0) ? x + 360 : x) { - case 0: return 0; - case 45: return 1; - case 90: return Infinity; + case 0: return 0; + case 45: return 1; + case 90: return Infinity; case 135: return -1; case 180: return 0; case 225: return 1; - case 270: return Infinity; + case 270: return -Infinity; case 315: return -1 } } diff --git a/test/globals.html b/test/globals.html index 6f5b8c6..7ab6874 100644 --- a/test/globals.html +++ b/test/globals.html @@ -82,14 +82,12 @@ "acos", "asin", "atan", - "atan2", "cos", "sin", "tan", "acosd", "asind", "atand", - "atan2d", "cosd", "sind", "tand", diff --git a/test/numeric_functions.html b/test/numeric_functions.html index 368e893..49ee2df 100644 --- a/test/numeric_functions.html +++ b/test/numeric_functions.html @@ -11,6 +11,9 @@ ok(sin(0) === 0); ok(cos(0) === 1); ok(tan(0) === 0); + ok(asin(0) === 0); + ok(acos(1) === 0); + ok(atan(0) === 0); ok(sind(0) === 0); ok(sind(30) === .5); @@ -37,9 +40,36 @@ ok(tand(135) === -1); ok(tand(180) === 0); ok(tand(225) === 1); - ok(tand(270) === Infinity); + ok(tand(270) === -Infinity); ok(tand(315) === -1); ok(tand(360) === 0); + + ok(asind(0) === 0); + ok(asind(.5) === 30); + ok(asind(1) === 90); + ok(asind(-.5) === -30); + ok(asind(-1) === -90); + + ok(acosd(1) === 0); + ok(acosd(.5) === 60); + ok(acosd(0) === 90); + ok(acosd(-.5) === 120); + ok(acosd(-1) === 180); + + ok(atand(0) === 0); + ok(atand(1) === 45); + ok(atand(Infinity) === 90); + ok(atand(-1) === -45); + ok(atand(0, 1) === 0); + ok(atand(1, 1) === 45); + ok(atand(Infinity, 1) === 90); + ok(atand(-1, 1) === -45); + ok(atand(0, -1) === 180); + ok(atand(1, -1) === 135); + ok(atand(-Infinity) === -90); + ok(atand(-Infinity, 1) === -90); + ok(atand(-1, -1) === -135); + done(function() { start(); }); From 8c2fe217e3905127f27971c04cee699280dde69d Mon Sep 17 00:00:00 2001 From: Bob Cassels Date: Fri, 19 Jun 2015 23:18:10 -0400 Subject: [PATCH 27/68] Fix indentation, arg names for atan. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d375efa..10b64fd 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8061,7 +8061,7 @@ var dollar_turtle_methods = { atan: wrapraw('atan', ["atan(y, x = 1) Trigonometric arctangent, in radians. " + "see atan 0.5"], - function atan(x, y) { return Math.atan2(x, (y == undefined) ? 1 : y); } + function atan(y, x) { return Math.atan2(y, (x == undefined) ? 1 : x); } ), cos: wrapraw('cos', ["cos(radians) Trigonometric cosine, in radians. " + From cf0efe37da90c2e93de1b1a7132811e86ed25568 Mon Sep 17 00:00:00 2001 From: Bob Cassels Date: Fri, 19 Jun 2015 23:59:01 -0400 Subject: [PATCH 28/68] Use simple form for sin, cos, etc. radians versions. --- jquery-turtle.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 10b64fd..8ec182f 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8050,14 +8050,10 @@ var dollar_turtle_methods = { "see abs -5"], Math.abs), acos: wrapraw('acos', ["acos(x) Trigonometric arccosine, in radians. " + - "see acos 0.5"], - function acos(x) { return Math.acos(x); } - ), + "see acos 0.5"], Math.acos), asin: wrapraw('asin', ["asin(y) Trigonometric arcsine, in radians. " + - "see asin 0.5"], - function asin(y) { return Math.asin(y); } - ), + "see asin 0.5"], Math.asin), atan: wrapraw('atan', ["atan(y, x = 1) Trigonometric arctangent, in radians. " + "see atan 0.5"], @@ -8065,19 +8061,13 @@ var dollar_turtle_methods = { ), cos: wrapraw('cos', ["cos(radians) Trigonometric cosine, in radians. " + - "see cos 0"], - function cos(x) { return Math.cos(x); } - ), + "see cos 0"], Math.cos), sin: wrapraw('sin', ["sin(radians) Trigonometric sine, in radians. " + - "see sin 0"], - function sin(x) { return Math.sin(x); } - ), + "see sin 0"], Math.sin), tan: wrapraw('tan', ["tan(radians) Trigonometric tangent, in radians. " + - "see tan 0"], - function tan(x) { return Math.tan(x); } - ), + "see tan 0"], Math.tan), // For degree versions of trig functions, make sure we return exact // results when possible. The set of values we have to consider is From b3c3f68462b4ea77202e45674dd94f51d944e350 Mon Sep 17 00:00:00 2001 From: Bob Cassels Date: Sat, 20 Jun 2015 00:11:45 -0400 Subject: [PATCH 29/68] Make convertToRadians a tiny bit more accurate. Move modulo. --- jquery-turtle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 8ec182f..62fe284 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1487,6 +1487,8 @@ function writeTurtleTransform(ts) { return result.join(' '); } +function modulo(n, m) { return (+n % (m = +m) + m) % m; } + function radiansToDegrees(r) { var d = r * 180 / Math.PI; if (d > 180) { d -= 360; } @@ -1494,7 +1496,7 @@ function radiansToDegrees(r) { } function convertToRadians(d) { - return d * Math.PI / 180; + return d / 180 * Math.PI; } function normalizeRotation(x) { @@ -7628,8 +7630,6 @@ $.fn.extend(turtlefn); var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7" -function modulo(n, m) { return (+n % (m = +m) + m) % m; } - var eventfn = { click:1, dblclick:1, mouseup:1, mousedown:1, mousemove:1 }; function global_turtle_animating() { From 2c5062982f4c1bf54917c05ee8a6744c6892be2c Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 30 Jun 2015 23:53:29 -0400 Subject: [PATCH 30/68] Remove deadlock between drawon and sync. --- jquery-turtle.js | 3 --- test/drawon.html | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 7633e73..86f789e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6986,9 +6986,6 @@ var turtlefn = { return this.plan(function(j, elem) { cc.appear(j); var state = getTurtleData(elem); - if (state.drawOnCanvas) { - sync(elem, state.drawOnCanvas); - } if (!canvas || canvas === global) { state.drawOnCanvas = null; } else if (canvas.jquery && $.isFunction(canvas.canvas)) { diff --git a/test/drawon.html b/test/drawon.html index 2609810..de61837 100644 --- a/test/drawon.html +++ b/test/drawon.html @@ -81,4 +81,18 @@ start(); }); }); + +asyncTest("Drawon does not deadlock with sync.", function() { + speed(100); + var trt = new Sprite(); + drawon(trt); + dot(red, 100); + drawon(); + sync(turtle, trt); + trt.bk(100); + trt.done(function() { + ok(touches(red)); + start(); + }); +}); From b2ff0e293eec536ca2efa67a59cb476c978d954d Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 7 Jul 2015 04:38:29 -0400 Subject: [PATCH 31/68] Fix piano rendering on Safari. --- jquery-turtle.js | 6 ++++-- test/drawon.html | 2 +- test/piano.html | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 test/piano.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 86f789e..71d47d0 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8739,7 +8739,9 @@ function createRectangleShape(width, height, subpixels) { subpixels = 1; } return (function(color) { - var c = getOffscreenCanvas(width, height); + var c = document.createElement('canvas'); + c.width = width; + c.height = height; var ctx = c.getContext('2d'); if (!color) { color = "rgba(128,128,128,0.125)"; @@ -8761,7 +8763,7 @@ function createRectangleShape(width, height, subpixels) { css.imageRendering = 'pixelated'; } return { - url: c.toDataURL(), + img: c, css: css }; }); diff --git a/test/drawon.html b/test/drawon.html index de61837..bf68802 100644 --- a/test/drawon.html +++ b/test/drawon.html @@ -10,7 +10,7 @@ asyncTest("Draws on another turtle.", function() { speed(Infinity); css({zIndex:1}); - var B = hatch('1000x1000/5'); + var B = hatch({width: 1000, height: 1000, subpixel: 5}); drawon(B); ok(!touches(blue)); ok(touches(transparent)); diff --git a/test/piano.html b/test/piano.html new file mode 100644 index 0000000..c85681b --- /dev/null +++ b/test/piano.html @@ -0,0 +1,51 @@ + + + + + +
+ + + + From f98a3b9a10a91c70fc74228c5daae38a6ca3d3b6 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 11 Jul 2015 07:26:56 -0400 Subject: [PATCH 32/68] Fix clearRect arithmetic. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 71d47d0..ebd5e7c 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9730,7 +9730,7 @@ debug.init(); ang = Math.atan2(dx, dy) / Math.PI * 180; if (linestart) { c.save(); - c.clearRect(xa - 10, ya - 10, xb + 10, yb + 10); + c.clearRect(xa - 10, ya - 10, xb - xa + 20, yb - ya + 20); xa = xb = s.pageX; ya = yb = s.pageY; // Draw a dot From bc8762182b8666308b43a735418fbc3c91689b94 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 11 Jul 2015 08:34:00 -0400 Subject: [PATCH 33/68] Add experimental typebox function. --- jquery-turtle.js | 43 ++++++++++++++++++++++++++++++++++++++----- test/globals.html | 1 + 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index ebd5e7c..232df05 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7907,6 +7907,12 @@ var dollar_turtle_methods = { type: wrapglobalcommand('type', ["type(text) Types preformatted text like a typewriter. " + "type 'Hello!\n'"], plainTextPrint), + typebox: wrapglobalcommand('typebox', + ["typebox(clr) Types a colored box into the typewriter output. " + + "typebox red"], function(c, t) { + if (t == null && c != null && !isCSSColor(c)) { t = c; c = null; } + plainBoxPrint(c, t); + }), write: wrapglobalcommand('write', ["write(html) Writes a line of text. Arbitrary HTML may be written: " + "write 'Hello, world!'"], doOutput, function() { @@ -9180,20 +9186,47 @@ function undoScrollAfter(f) { ////////////////////////////////////////////////////////////////////////// // Simplify output of preformatted text inside a
.
+function getTrailingPre() {
+  var pre = document.body.lastChild;
+  if (!pre || pre.tagName != 'PRE') {
+    pre = document.createElement('pre');
+    document.body.appendChild(pre);
+  }
+  return pre;
+}
+
 function plainTextPrint() {
   var args = arguments;
   autoScrollAfter(function() {
-    var pre = document.body.lastChild;
-    if (!pre || pre.tagName != 'PRE') {
-      pre = document.createElement('pre');
-      document.body.appendChild(pre);
-    }
+    var pre = getTrailingPre();
     for (var j = 0; j < args.length; j++) {
       pre.appendChild(document.createTextNode(String(args[j])));
     }
   });
 }
 
+function plainBoxPrint(clr, text) {
+  var elem = $("
").css({ + display: 'inline-block', + verticalAlign: 'top', + textAlign: 'center', + height: '1.2em', + width: '1.2em', + maxWidth: '1.2em', + overflow: 'hidden' + }).appendTo(getTrailingPre()), finish = function() { + if (clr) { elem.css({background: clr}); } + if (text) { elem.text(text); } + }; + if (!global_turtle) { + finish(); + } else { + var turtle = $(global_turtle); + moveto.call(turtle, null, elem); + turtle.eq(0).plan(finish); + } +} + // Put this output on the screen. Called some time after prepareOutput // if the turtle is animating. function doOutput() { diff --git a/test/globals.html b/test/globals.html index 0376880..23b3d2d 100644 --- a/test/globals.html +++ b/test/globals.html @@ -55,6 +55,7 @@ "append", "write", "type", + "typebox", "read", "readnum", "readstr", From 7eead06e2189811868931ab45e6ca45be2c1d3bd Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 11 Jul 2015 09:03:07 -0400 Subject: [PATCH 34/68] Add typeline(). --- jquery-turtle.js | 7 ++++++- test/globals.html | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 232df05..1948fc8 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7908,11 +7908,16 @@ var dollar_turtle_methods = { ["type(text) Types preformatted text like a typewriter. " + "type 'Hello!\n'"], plainTextPrint), typebox: wrapglobalcommand('typebox', - ["typebox(clr) Types a colored box into the typewriter output. " + + ["typebox(clr) Draws a colored box as typewriter output. " + "typebox red"], function(c, t) { if (t == null && c != null && !isCSSColor(c)) { t = c; c = null; } plainBoxPrint(c, t); }), + typeline: wrapglobalcommand('typebox', + ["typeline() Same as type '\\n'. " + + "typeline()"], function(t) { + plainTextPrint((t || '') + '\n'); + }), write: wrapglobalcommand('write', ["write(html) Writes a line of text. Arbitrary HTML may be written: " + "write 'Hello, world!'"], doOutput, function() { diff --git a/test/globals.html b/test/globals.html index 23b3d2d..30d7d41 100644 --- a/test/globals.html +++ b/test/globals.html @@ -56,6 +56,7 @@ "write", "type", "typebox", + "typeline", "read", "readnum", "readstr", From 5c1b3786caa2abddf7e6535016414c740e9271c0 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 12 Jul 2015 12:42:12 -0500 Subject: [PATCH 35/68] Add test for random(). --- jquery-turtle.js | 5 ++++- package.json | 1 + test/random.html | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 test/random.html diff --git a/jquery-turtle.js b/jquery-turtle.js index 1948fc8..f5e4dfb 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8959,7 +8959,7 @@ function random(arg, arg2) { } return Math.floor(Math.random() * arg); } - if (typeof(arg) == 'object' && arg.length && arg.slice) { + if (typeof(arg) == 'object' && arg && arg.length && arg.slice) { return arg[Math.floor(Math.random() * arg.length)]; } if (arg == 'normal') { @@ -8986,6 +8986,9 @@ function random(arg, arg2) { if (arg == 'gray') { return 'hsl(0,0,' + Math.floor(Math.random() * 100) + '%)'; } + if (arg === true) { + return Math.random() >= 0.5; + } return Math.random(); } diff --git a/package.json b/package.json index 1e5e71a..208270b 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "grunt-contrib-qunit": "latest", "grunt-release": "latest", "phantomjs": "latest", + "seedrandom": "latest", "grunt-contrib-connect": "latest", "grunt-contrib-watch": "latest" } diff --git a/test/random.html b/test/random.html new file mode 100644 index 0000000..0d03405 --- /dev/null +++ b/test/random.html @@ -0,0 +1,25 @@ + + + + + + +
+ From 066bb883d8656793b4a9013ed9c7ac04f603ffed Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 18 Jul 2015 18:45:32 -0400 Subject: [PATCH 36/68] Adjust timeout up to 20 secs. --- jquery-turtle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index f5e4dfb..d8ab736 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7542,7 +7542,7 @@ function checkForHungLoop(fname) { }, 0); return; } - // Timeout after which we interrupt the program: 6 seconds. + // Timeout after which we interrupt the program. if (now - hangStartTime > $.turtle.hangtime) { if (see.visible()) { see.html('Oops: program ' + @@ -8287,9 +8287,9 @@ $.turtle = function turtle(id, options) { if (!globalDrawing.ctx && ('subpixel' in options)) { globalDrawing.subpixel = parseInt(options.subpixel); } - // Set up hung-browser timeout, default 10 seconds. + // Set up hung-browser timeout, default 20 seconds. $.turtle.hangtime = ('hangtime' in options) ? - parseFloat(options.hangtime) : 10000; + parseFloat(options.hangtime) : 20000; // Set up global events. if (!('events' in options) || options.events) { From da88c6cf5cdf0e10981f0aeafb7cc1174a8865e0 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 21 Jul 2015 12:18:23 -0400 Subject: [PATCH 37/68] Don't reattach IDE if already attached. --- jquery-turtle.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index d8ab736..161dda6 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9637,8 +9637,10 @@ var debug = { init: function initdebug() { try { if (parent && parent.ide) { - this.ide = parent.ide; - this.ide.bindframe(global); + if (this.ide !== parent.ide) { + this.ide = parent.ide; + this.ide.bindframe(global); + } this.attached = true; } } catch(e) { From 96a6595819c72fd79aae1964d4d5a2ed433a3d9b Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 22 Jul 2015 15:24:18 -0400 Subject: [PATCH 38/68] Add save.loginCookie() accessor. --- jquery-turtle.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 161dda6..0c35846 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8194,6 +8194,8 @@ function pollSendRecv(name) { deprecate(dollar_turtle_methods, 'defaultspeed', 'speed'); +dollar_turtle_methods.save.loginCookie = loginCookie; + var helpok = {}; var colors = [ From 37b9b8f05f2c586dd9349b1b99bda101a62b8ad7 Mon Sep 17 00:00:00 2001 From: David Bau Date: Wed, 22 Jul 2015 16:54:22 -0400 Subject: [PATCH 39/68] Fix typo in console.log intercept. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 0c35846..bd278ed 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -10601,7 +10601,7 @@ function initconsolelog() { if (consolelog && global.console && !global.console._log && 'function' == typeof global.console.log) { var _log = global.console._log = global.console.log; - global_.console.log = function log() { + global.console.log = function log() { _log.apply(this, arguments); see.apply(this, arguments); } From e6f686f8d0b43f924d59ac022aabb46d3c28e4f6 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 31 Jul 2015 14:36:13 -0400 Subject: [PATCH 40/68] Undeprecate slide. --- jquery-turtle.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index bd278ed..dd49209 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -43,7 +43,7 @@ for color in [red, gold, green, blue] lt 360 / sides pen null fd 40 - move 40, -160 + slide 40, -160
[Try an interactive demo (CoffeeScript syntax) here.]( @@ -62,8 +62,8 @@ $(q).fd(100) // Forward relative motion in local coordinates. $(q).bk(50) // Back. $(q).rt(90) // Right turn. Optional second arg is turning radius. $(q).lt(45) // Left turn. Optional second arg is turning radius. -$(q).move(x, y) // Move right by x while moving forward by y. -$(q).jump(x, y) // Like move, but without drawing. +$(q).slide(x, y) // Move right by x while moving forward by y. +$(q).leap(x, y) // Like slide, but without drawing. $(q).moveto({pageX:x,pageY:y} | [x,y]) // Absolute motion on page. $(q).jumpto({pageX:x,pageY:y} | [x,y]) // Like moveto, without drawing. $(q).turnto(direction || position) // Absolute direction adjustment. @@ -6045,7 +6045,7 @@ function fdbk(cc, amount) { // CARTESIAN MOVEMENT FUNCTIONS ////////////////////////////////////////////////////////////////////////// -function move(cc, x, y) { +function slide(cc, x, y) { if ($.isArray(x)) { y = x[1]; x = x[0]; @@ -6358,9 +6358,9 @@ var turtlefn = { bk: wrapcommand('bk', 1, ["bk(pixels) Back. Moves in reverse by some pixels: " + "bk 100"], fdbk), - move: wrapcommand('move', 1, + slide: wrapcommand('slide', 1, ["move(x, y) Slides right x and forward y pixels without turning: " + - "move 50, 100"], move), + "slide 50, 100"], slide), movexy: wrapcommand('movexy', 1, ["movexy(x, y) Changes graphing coordinates by x and y: " + "movexy 50, 100"], movexy), @@ -6371,8 +6371,8 @@ var turtlefn = { "or an object on the page (see pagexy): " + "moveto lastmousemove"], moveto), jump: wrapcommand('jump', 1, - ["jump(x, y) Move without drawing (compare to move): " + - "jump 0, 50"], makejump(move)), + ["jump(x, y) Move without drawing (compare to slide): " + + "jump 0, 50"], makejump(slide)), jumpxy: wrapcommand('jumpxy', 1, ["jumpxy(x, y) Move without drawing (compare to movexy): " + "jump 0, 50"], makejump(movexy)), @@ -7603,7 +7603,7 @@ function deprecate(map, oldname, newname) { __extends(map[oldname], map[newname]); } } -deprecate(turtlefn, 'slide', 'move'); +deprecate(turtlefn, 'move', 'slide'); deprecate(turtlefn, 'direct', 'plan'); deprecate(turtlefn, 'enclosedby', 'inside'); deprecate(turtlefn, 'bearing', 'direction'); @@ -10488,7 +10488,7 @@ function stickscroll() { } } function flushqueue() { - var elt = aselement(logelement, null); + var elt = aselement(logelement, null), child; if (elt && elt.appendChild && queue.length) { initlogcss(); var temp = global.document.createElement('samp'); @@ -10504,7 +10504,7 @@ function flushqueue() { if (panel == 'auto') { startinitpanel(); } - retrying = setTimeout(function() { timer = null; flushqueue(); }, 100); + retrying = setTimeout(function() { retrying = null; flushqueue(); }, 100); } else if (retrying && !queue.length) { clearTimeout(retrying); retrying = null; From 9937e3c47d88e54ce022e2852dbaf660518511f5 Mon Sep 17 00:00:00 2001 From: David Bau Date: Fri, 31 Jul 2015 15:14:00 -0400 Subject: [PATCH 41/68] Rename #field to #origin. Then things work! --- jquery-turtle.js | 2 +- test/{field.html => origin.html} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename test/{field.html => origin.html} (85%) diff --git a/jquery-turtle.js b/jquery-turtle.js index dd49209..3e2d6d1 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -1569,7 +1569,7 @@ function createSurfaceAndField() { pointerEvents: 'none', overflow: 'hidden' }).addClass('turtlefield'); - $(field).attr('id', 'field') + $(field).attr('id', 'origin') .css({ position: 'absolute', display: 'inline-block', diff --git a/test/field.html b/test/origin.html similarity index 85% rename from test/field.html rename to test/origin.html index 1674406..0f0534e 100644 --- a/test/field.html +++ b/test/origin.html @@ -7,7 +7,7 @@ + + + + +
+ From d4793973c3972f3e384d5a2262fff4d825fc7be3 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 24 Sep 2015 12:08:09 -0400 Subject: [PATCH 49/68] Improve the debug event protocol: bind only if ide says it's ok. --- jquery-turtle.js | 14 +++++--------- test/debugevents.html | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 0b6fc22..892563a 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9834,18 +9834,14 @@ function autoArgs(arguments, start, map) { ////////////////////////////////////////////////////////////////////////// var debug = { init: function initdebug() { + if (this.ide) return; // Don't re-initialize debug. try { - if (parent && parent.ide) { - if (this.ide !== parent.ide) { - this.ide = parent.ide; - this.ide.bindframe(global); - } + if (parent && parent.ide && parent.ide.bindframe && + parent.ide.bindframe(global, parent)) { + this.ide = parent.ide; this.attached = true; } - } catch(e) { - this.ide = null; - this.attached = false; - } + } catch(e) { } if (this.attached) { if (global.addEventListener) { global.addEventListener('error', function(event) { diff --git a/test/debugevents.html b/test/debugevents.html index 64d3a57..b63142f 100644 --- a/test/debugevents.html +++ b/test/debugevents.html @@ -64,6 +64,7 @@ window.ide = { bindframe: function() { verify('bindframe'); + return true; }, reportEvent: function(eventname, args) { verify([eventname, args[0]]); From 72945b16c69a96d18b2cb6c11484747d50574429 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 24 Sep 2015 15:33:24 -0400 Subject: [PATCH 50/68] Make interrupt robust when AudioContext is closed. --- jquery-turtle.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 892563a..1c2508e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -3899,11 +3899,16 @@ function resetAudio() { atop.out = null; atop.currentStart = null; } - var dcn = atop.ac.createDynamicsCompressor(); - dcn.ratio = 16; - dcn.attack = 0.0005; - dcn.connect(atop.ac.destination); - atop.out = dcn; + // If resetting due to interrupt after AudioContext closed, this can fail. + try { + var dcn = atop.ac.createDynamicsCompressor(); + dcn.ratio = 16; + dcn.attack = 0.0005; + dcn.connect(atop.ac.destination); + atop.out = dcn; + } catch (e) { + getAudioTop.audioTop = null; + } } } From 189612044a106365832a87cdfdcbcbc4b84bea00 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 12 Oct 2015 21:27:52 -0400 Subject: [PATCH 51/68] Implementation of readvoice: voice recognition on Chrome. --- jquery-turtle.js | 69 ++++++++++++++++++++++++++++++++++++++++------- test/globals.html | 1 + 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 1c2508e..fa02719 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -7729,7 +7729,7 @@ $.fn.extend(turtlefn); // * Sets up a global "hatch" function to make a new turtle. ////////////////////////////////////////////////////////////////////////// -var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7" +var turtleGIFUrl = "data:image/gif;base64,R0lGODlhKAAwAPIFAAAAAAFsOACSRTCuSICAgP///wAAAAAAACH5BAlkAAYAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAKAAwAAAD72i6zATEgBCAebHpzUnxhDAMAvhxKOoV3ziuZyo3RO26dTbvgXj/gsCO9ysOhENZz+gKJmcUkmA6PSKfSqrWieVtuU+KGNXbXofLEZgR/VHCgdua4isGz9mbmM6U7/94BmlyfUZ1fhqDhYuGgYqMkCOBgo+RfWsNlZZ3ewIpcZaIYaF6XaCkR6aokqqrk0qrqVinpK+fsbZkuK2ouRy0ob4bwJbCibthh6GYebGcY7/EsWqTbdNG1dd9jnXPyk2d38y0Z9Yub2yA6AvWPYk+zEnkv6xdCoPuw/X2gLqy9vJIGAN4b8pAgpQOIlzI8EkCACH5BAlkAAYALAAAAAAoADAAAAPuaLrMBMSAEIB5senNSfGEMAwC+HEo6hXfOK5nKjdE7bp1Nu+BeP+CwI73Kw6EQ1nP6AomZxSSYDo9Ip9KqtaJ5W25Xej3qqGYsdEfZbMcgZXtYpActzLMeLOP6c7f3nVNfEZ7TXSFg4lyZAYBio+LZYiQfHMbc3iTlG9ilGpdjp4ujESiI6RQpqegqkesqqhKrbEpoaa0KLaiuBy6nrxss6+3w7tomo+cDXmBnsoLza2nsb7SN2tl1nyozVOZTJhxysxnd9XYCrrAtT7KQaPruavBo2HQ8xrvffaN+GV5/JbE45fOG8Ek5Q4qXHgwAQA7"; var eventfn = { click:1, dblclick:1, mouseup:1, mousedown:1, mousemove:1 }; @@ -8039,12 +8039,16 @@ var dollar_turtle_methods = { readnum: wrapglobalcommand('readnum', ["readnum(html, fn) Reads numeric input. Only numbers allowed: " + "readnum 'Amount?', (v) -> write 'Tip: ' + (0.15 * v)"], - doOutput, function readnum(a, b) { return prepareInput(a, b, 1); }), + doOutput, function readnum(a, b) { return prepareInput(a, b, 'number'); }), readstr: wrapglobalcommand('readstr', ["readstr(html, fn) Reads text input. Never " + "converts input to a number: " + "readstr 'Enter code', (v) -> write v.length + ' long'"], - doOutput, function readstr(a, b) { return prepareInput(a, b, -1); }), + doOutput, function readstr(a, b) { return prepareInput(a, b, 'text'); }), + readvoice: wrapglobalcommand('readvoice', + ["readvoice(html, fn) Reads voice input, if the browser supports it:" + + "readvoice 'Say something', (v) -> write v"], + doOutput, function readstr(a, b) { return prepareInput(a, b, 'voice'); }), menu: wrapglobalcommand('menu', ["menu(map) shows a menu of choices and calls a function " + "based on the user's choice: " + @@ -9635,18 +9639,24 @@ function prepareButton(name, callback) { // for creating an input box with a label, for one-shot input ////////////////////////////////////////////////////////////////////////// +var microphoneSvg = "data:image/svg+xml," + // Simplify $('body').append('' + label) and onchange hookup. -function prepareInput(name, callback, numeric) { +// Type can be 'auto', 'number', 'text', or 'voice' for slightly +// different interfaces. +function prepareInput(name, callback, type) { if ($.isFunction(name) && !callback) { callback = name; name = null; } + if (!type) { type = 'auto'; } name = $.isNumeric(name) || name ? name : '⇒'; var textbox = $('').css({margin:0, padding:0}), label = $('').append(textbox), debounce = null, - lastseen = textbox.val(); + lastseen = textbox.val(), + recognition = null; function dodebounce() { if (!debounce) { debounce = setTimeout(function() { debounce = null; }, 1000); @@ -9660,15 +9670,15 @@ function prepareInput(name, callback, numeric) { lastseen = val; textbox.remove(); label.append(val).css({display: 'table'}); - if (numeric > 0 || ( - numeric >= 0 && $.isNumeric(val) && ('' + parseFloat(val) == val))) { + if (type == 'number' || (type == 'auto' && + $.isNumeric(val) && ('' + parseFloat(val) == val))) { val = parseFloat(val); } label.prop('value', val); if (callback) { setTimeout(function() {callback.call(label, val); }, 0); } } function validate() { - if (numeric <= 0) return true; + if (type != 'numeric') return true; var val = textbox.val(), nval = val.replace(/[^0-9\.]/g, ''); if (val != nval || !$.isNumeric(nval)) { @@ -9682,19 +9692,58 @@ function prepareInput(name, callback, numeric) { if (!validate()) { return false; } newval(); } - if (numeric > 0 && (e.which >= 32 && e.which <= 127) && + if (type == 'voice' && recognition) { + recognition.abort(); + recognition = null; + } + if (type == 'numeric' && (e.which >= 32 && e.which <= 127) && (e.which < '0'.charCodeAt(0) || e.which > '9'.charCodeAt(0)) && (e.which != '.'.charCodeAt(0) || ~textbox.val().indexOf('.'))) { return false; } } + if (type == 'voice' && 'function' == typeof(global.webkitSpeechRecognition)) { + try { + recognition = new global.webkitSpeechRecognition(); + recognition.continuous = false; + recognition.interimResults = true; + textbox.css({backgroundColor: 'lightyellow', + color: 'gray', + backgroundImage: "url(" + microphoneSvg + ")", + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center'}); + recognition.onspeechstart = function() { + textbox.css({background: 'lightgreen'}); + }; + recognition.onend = function() { + textbox.css({color: '', backgroundColor: '', backgroundImage: '', + backgroundRepeat: '', backgroundPosition: ''}); + textbox.val(lastseen); + newval(); + }; + recognition.onresult = function(event) { + var text = event.results[0][0].transcript; + var confidence = event.results[0][0].confidence; + var shade = 128 - 128 * confidence; + if (event.results[0].isFinal) { + shade = 0; + lastseen = text; + } + textbox.css({color: componentColor('rgb', shade, shade, shade)}); + textbox.val(text); + }; + recognition.start(); + } catch (e) { + console.log(e); + } + } textbox.on('keypress keydown', key); textbox.on('change', newval); return { result: label, setup: function() { dodebounce(); - if (numeric < 0) { + if (type == 'text' || type == 'voice') { // Widen a "readstr" textbox to make it fill the line. var availwidth = label.parent().width(), freewidth = availwidth + label.offset().left - textbox.offset().left, diff --git a/test/globals.html b/test/globals.html index 5c760fc..f208663 100644 --- a/test/globals.html +++ b/test/globals.html @@ -60,6 +60,7 @@ "typeline", "read", "readnum", + "readvoice", "readstr", "menu", "random", From b98735d30a54c251defcbfb047bb9b53aa609e52 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 13 Oct 2015 06:55:10 -0400 Subject: [PATCH 52/68] Also allow nonprefixed SpeechRecognition api. --- jquery-turtle.js | 73 +++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index fa02719..19ab9cb 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9429,8 +9429,8 @@ function plainBoxPrint(clr, text) { maxWidth: '1.2em', overflow: 'hidden' }).appendTo(getTrailingPre()), finish = function() { - if (clr) { elem.css({background: clr}); } - if (text) { elem.text(text); } + if (clr != null) { elem.css({background: clr}); } + if (text != null) { elem.text(text); } }; if (!global_turtle) { finish(); @@ -9702,39 +9702,42 @@ function prepareInput(name, callback, type) { return false; } } - if (type == 'voice' && 'function' == typeof(global.webkitSpeechRecognition)) { - try { - recognition = new global.webkitSpeechRecognition(); - recognition.continuous = false; - recognition.interimResults = true; - textbox.css({backgroundColor: 'lightyellow', - color: 'gray', - backgroundImage: "url(" + microphoneSvg + ")", - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center'}); - recognition.onspeechstart = function() { - textbox.css({background: 'lightgreen'}); - }; - recognition.onend = function() { - textbox.css({color: '', backgroundColor: '', backgroundImage: '', - backgroundRepeat: '', backgroundPosition: ''}); - textbox.val(lastseen); - newval(); - }; - recognition.onresult = function(event) { - var text = event.results[0][0].transcript; - var confidence = event.results[0][0].confidence; - var shade = 128 - 128 * confidence; - if (event.results[0].isFinal) { - shade = 0; - lastseen = text; - } - textbox.css({color: componentColor('rgb', shade, shade, shade)}); - textbox.val(text); - }; - recognition.start(); - } catch (e) { - console.log(e); + if (type == 'voice') { + var SR = global.SpeechRecognition || global.webkitSpeechRecognition; + if ('function' == typeof(SR)) { + try { + recognition = new SR(); + recognition.continuous = false; + recognition.interimResults = true; + textbox.css({backgroundColor: 'lightyellow', + color: 'gray', + backgroundImage: "url(" + microphoneSvg + ")", + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center'}); + recognition.onspeechstart = function() { + textbox.css({background: 'lightgreen'}); + }; + recognition.onend = function() { + textbox.css({color: '', backgroundColor: '', backgroundImage: '', + backgroundRepeat: '', backgroundPosition: ''}); + textbox.val(lastseen); + newval(); + }; + recognition.onresult = function(event) { + var text = event.results[0][0].transcript; + var confidence = event.results[0][0].confidence; + var shade = 128 - 128 * confidence; + if (event.results[0].isFinal) { + shade = 0; + lastseen = text; + } + textbox.css({color: componentColor('rgb', shade, shade, shade)}); + textbox.val(text); + }; + recognition.start(); + } catch (e) { + console.log(e); + } } } textbox.on('keypress keydown', key); From 84edbca9e636cfbb825faa5c2ff763531ed343e8 Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Mon, 26 Oct 2015 19:22:27 -0700 Subject: [PATCH 53/68] Fixed language of 'say' command to british-english, fixed bug in which 'readvoice' command was executing out of order. --- jquery-turtle.js | 77 ++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 19ab9cb..9f4064d 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6886,6 +6886,7 @@ var turtlefn = { var msg = new SpeechSynthesisUtterance(words); msg.addEventListener('end', complete); msg.addEventListener('error', complete); + msg.lang = 'en-GB'; speechSynthesis.speak(msg); pollTimer = setInterval(function() { // Chrome speech synthesis fails to deliver an 'end' event @@ -9702,44 +9703,6 @@ function prepareInput(name, callback, type) { return false; } } - if (type == 'voice') { - var SR = global.SpeechRecognition || global.webkitSpeechRecognition; - if ('function' == typeof(SR)) { - try { - recognition = new SR(); - recognition.continuous = false; - recognition.interimResults = true; - textbox.css({backgroundColor: 'lightyellow', - color: 'gray', - backgroundImage: "url(" + microphoneSvg + ")", - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center'}); - recognition.onspeechstart = function() { - textbox.css({background: 'lightgreen'}); - }; - recognition.onend = function() { - textbox.css({color: '', backgroundColor: '', backgroundImage: '', - backgroundRepeat: '', backgroundPosition: ''}); - textbox.val(lastseen); - newval(); - }; - recognition.onresult = function(event) { - var text = event.results[0][0].transcript; - var confidence = event.results[0][0].confidence; - var shade = 128 - 128 * confidence; - if (event.results[0].isFinal) { - shade = 0; - lastseen = text; - } - textbox.css({color: componentColor('rgb', shade, shade, shade)}); - textbox.val(text); - }; - recognition.start(); - } catch (e) { - console.log(e); - } - } - } textbox.on('keypress keydown', key); textbox.on('change', newval); return { @@ -9755,6 +9718,44 @@ function prepareInput(name, callback, type) { marginwidth = textbox.outerWidth(true) - textbox.width(); textbox.width(desiredwidth - marginwidth); } + if (type == 'voice') { + var SR = global.SpeechRecognition || global.webkitSpeechRecognition; + if ('function' == typeof(SR)) { + try { + recognition = new SR(); + recognition.continuous = false; + recognition.interimResults = true; + textbox.css({backgroundColor: 'lightyellow', + color: 'gray', + backgroundImage: "url(" + microphoneSvg + ")", + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center'}); + recognition.onspeechstart = function() { + textbox.css({background: 'lightgreen'}); + }; + recognition.onend = function() { + textbox.css({color: '', backgroundColor: '', backgroundImage: '', + backgroundRepeat: '', backgroundPosition: ''}); + textbox.val(lastseen); + newval(); + }; + recognition.onresult = function(event) { + var text = event.results[0][0].transcript; + var confidence = event.results[0][0].confidence; + var shade = 128 - 128 * confidence; + if (event.results[0].isFinal) { + shade = 0; + lastseen = text; + } + textbox.css({color: componentColor('rgb', shade, shade, shade)}); + textbox.val(text); + }; + recognition.start(); + } catch (e) { + console.log(e); + } + } + } // Focus, but don't cause autoscroll to occur due to focus. undoScrollAfter(function() { textbox.focus(); }); } From fd5689961c87e6d68f57db7e257b6f29ad01c1af Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Tue, 27 Oct 2015 08:29:41 -0700 Subject: [PATCH 54/68] Added submit button to read and readnum, added type=number to readnum input box. --- jquery-turtle.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9f4064d..cc262a1 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9653,8 +9653,9 @@ function prepareInput(name, callback, type) { if (!type) { type = 'auto'; } name = $.isNumeric(name) || name ? name : '⇒'; var textbox = $('').css({margin:0, padding:0}), + button = $('').css({marginLeft:4}), label = $('').append(textbox), + '').append(textbox).append(button), debounce = null, lastseen = textbox.val(), recognition = null; @@ -9670,6 +9671,7 @@ function prepareInput(name, callback, type) { dodebounce(); lastseen = val; textbox.remove(); + button.remove(); label.append(val).css({display: 'table'}); if (type == 'number' || (type == 'auto' && $.isNumeric(val) && ('' + parseFloat(val) == val))) { @@ -9718,7 +9720,11 @@ function prepareInput(name, callback, type) { marginwidth = textbox.outerWidth(true) - textbox.width(); textbox.width(desiredwidth - marginwidth); } + if (type == 'number') { + textbox.attr('type', 'number'); + } if (type == 'voice') { + button.css({display:none}); var SR = global.SpeechRecognition || global.webkitSpeechRecognition; if ('function' == typeof(SR)) { try { From cf4d9317f89b805dfa8d6a40e843330e4367b828 Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 11:12:01 -0700 Subject: [PATCH 55/68] Added newval handler to button instead of text box blur. --- jquery-turtle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index cc262a1..ac528b6 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -9706,7 +9706,7 @@ function prepareInput(name, callback, type) { } } textbox.on('keypress keydown', key); - textbox.on('change', newval); + button.on('click', newval); return { result: label, setup: function() { From cf03900eb29f746ee61f4d5a232f5eff9f30b2de Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 21:34:18 -0700 Subject: [PATCH 56/68] Replaced the readvoice command with the listen command. --- jquery-turtle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 9f4064d..c44f712 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8046,9 +8046,9 @@ var dollar_turtle_methods = { "converts input to a number: " + "readstr 'Enter code', (v) -> write v.length + ' long'"], doOutput, function readstr(a, b) { return prepareInput(a, b, 'text'); }), - readvoice: wrapglobalcommand('readvoice', - ["readvoice(html, fn) Reads voice input, if the browser supports it:" + - "readvoice 'Say something', (v) -> write v"], + listen: wrapglobalcommand('listen', + ["listen(html, fn) Reads voice input, if the browser supports it:" + + "listen 'Say something', (v) -> write v"], doOutput, function readstr(a, b) { return prepareInput(a, b, 'voice'); }), menu: wrapglobalcommand('menu', ["menu(map) shows a menu of choices and calls a function " + From 3a71978450e101f033b7c74e21e3ae2164583461 Mon Sep 17 00:00:00 2001 From: Amit Deutsch Date: Wed, 28 Oct 2015 22:40:46 -0700 Subject: [PATCH 57/68] Fixed broken test. --- test/globals.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/globals.html b/test/globals.html index f208663..a19cb14 100644 --- a/test/globals.html +++ b/test/globals.html @@ -60,7 +60,7 @@ "typeline", "read", "readnum", - "readvoice", + "listen", "readstr", "menu", "random", From de2c752ebc0908693e84a4fa6d83ca039bdd233f Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 29 Oct 2015 02:47:21 -0400 Subject: [PATCH 58/68] Fix test. --- test/inputoutput.html | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/inputoutput.html b/test/inputoutput.html index a29c622..b3b33b4 100644 --- a/test/inputoutput.html +++ b/test/inputoutput.html @@ -20,14 +20,22 @@ equal(td.eq(2).text(), "3"); var clicked = 0; setTimeout(function() { - $('input').val('Amy').change(); + $('input').val('Amy'); + $('button').click(); }, 1); - var b; + var b, r2; var r = read('name?', function(t) { equal(t, 'Amy'); setTimeout(function() { + $('input').val('34'); + var e = jQuery.Event("keydown"); + e.which = 13; // Enter. + $('input').trigger(e); $('button').click(); }, 1); + r2 = readnum('count?', function(c) { + equal(c, 34); + }); b = button('click me!', function() { equal(equal($('button').last().text(), 'click me!')); equal(clicked++, 0); From 06dad3b5ea0a13b87768711dc32e36da44bdbc6b Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 06:50:19 -0500 Subject: [PATCH 59/68] Do not require /img/ for wear, and also add sync for newly written objects. --- jquery-turtle.js | 11 +++++++++-- test/wear.html | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index de99da4..506354e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -2689,7 +2689,7 @@ function imgUrl(url) { if (/\//.test(url)) { return url; } url = '/img/' + url; if (isPencilHost(global.location.hostname)) { return url; } - return '//pencil.io' + url; + return '//pencilcode.net' + url; } // Retrieves the pencil code login cookie, if there is one. function loginCookie() { @@ -5944,6 +5944,9 @@ function wrapglobalcommand(name, helptext, fn, fnfilter) { this.plan(cc.resolver(j)); }); cc.exit(); + if (early && early.result && early.result.constructor === jQuery) { + sync(animate, early.result); + } } else { cc = setupContinuation(null, name, arguments, argcount); fn.apply(early, arguments); @@ -9026,7 +9029,7 @@ function nameToImg(name, defaultshape) { // Deal with unquoted "tan" and "dot". name = name.helpname || name.name; } - if (name.constructor === $) { + if (name.constructor === jQuery) { // Unwrap jquery objects. if (!name.length) { return null; } name = name.get(0); @@ -9057,6 +9060,10 @@ function nameToImg(name, defaultshape) { if (shape) { return shape(color); } + // Default to '/img/' URLs if it doesn't match a well-known name. + if (!/\//.test(name)) { + name = imgUrl(name); + } // Parse URLs. if (/\//.test(name)) { var hostname = absoluteUrlObject(name).hostname; diff --git a/test/wear.html b/test/wear.html index eed8ddc..12a9d3f 100644 --- a/test/wear.html +++ b/test/wear.html @@ -24,6 +24,20 @@ }); }); +asyncTest("Wears an creative commons image.", function() { + var r = new Turtle(red); + r.speed(100); + tick(60, function() { + ok(r.width() != 20 || r.getxy()[1] == 0); + }); + r.wear('t-watermelon'); // no /img/ needed. + r.done(function() { + ok(r.width() != 20); + tick(null); + setTimeout(start, 0); + }); +}); + asyncTest("Wears and hittests different shapes.", function() { speed(Infinity); dot(red, 30); From f4ba15971301c6b11cbb0c71e61be99c14436912 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 07:36:23 -0500 Subject: [PATCH 60/68] Allow 'say' when there is no global turtle. --- jquery-turtle.js | 73 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index 506354e..bcf03f1 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -6427,6 +6427,39 @@ function drawArrowLine(c, w, x0, y0, x1, y1) { drawArrowHead(c, m); } +////////////////////////////////////////////////////////////////////////// +// VOICE SYNTHESIS +// Method for uttering words. +////////////////////////////////////////////////////////////////////////// +function utterSpeech(words, cb) { + var pollTimer = null; + function complete() { + if (pollTimer) { clearInterval(pollTimer); } + if (cb) { cb(); } + } + if (!global.speechSynthesis) { + console.log('No speech synthesis: ' + words); + complete(); + return; + } + try { + var msg = new global.SpeechSynthesisUtterance(words); + msg.addEventListener('end', complete); + msg.addEventListener('error', complete); + msg.lang = navigator.language || 'en-GB'; + global.speechSynthesis.speak(msg); + pollTimer = setInterval(function() { + // Chrome speech synthesis fails to deliver an 'end' event + // sometimes, so we also poll every 250ms. + if (global.speechSynthesis.pending || global.speechSynthesis.speaking) return; + complete(); + }, 250); + } catch (e) { + if (global.console) { global.console.log(e); } + complete(); + } +} + ////////////////////////////////////////////////////////////////////////// // TURTLE FUNCTIONS // Turtle methods to be registered as jquery instance methods. @@ -6876,35 +6909,13 @@ var turtlefn = { this.plan(function(j, elem) { cc.appear(j); this.queue(function(next) { - var finished = false, - pollTimer = null, - complete = function() { - if (finished) return; - clearInterval(pollTimer); - finished = true; + utterSpeech(words, function() { cc.resolve(j); next(); - }; - try { - var msg = new SpeechSynthesisUtterance(words); - msg.addEventListener('end', complete); - msg.addEventListener('error', complete); - msg.lang = 'en-GB'; - speechSynthesis.speak(msg); - pollTimer = setInterval(function() { - // Chrome speech synthesis fails to deliver an 'end' event - // sometimes, so we also poll every 250ms. - if (speechSynthesis.pending || speechSynthesis.speaking) return; - complete(); - }, 250); - } catch (e) { - if (global.console) { global.console.log(e); } - complete(); - } + }); }); }); return this; - }), play: wrapcommand('play', 1, ["play(notes) Play notes. Notes are specified in " + @@ -7863,6 +7874,20 @@ var dollar_turtle_methods = { function globalspeed(mps) { globaldefaultspeed(mps); }), + say: wrapraw('say', + ["say(words) Say something. Use English words." + + "say \"Let's go!\""], + function say(words) { + if (global_turtle) { + var sel = $(global_turtle); + sel.say.call(sel, words); + } else { + var cc = setupContinuation(null, 'say', arguments, 0); + cc.appear(null); + utterSpeech(words, function() { cc.resolve(null); }); + cc.exit(); + } + }), play: wrapraw('play', ["play(notes) Play notes. Notes are specified in " + "" + From 2a0dcce08464b3b6e729411b1c11d696e1cbb91c Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 09:01:52 -0500 Subject: [PATCH 61/68] Add test for speech synthesis. --- test/say.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/say.html diff --git a/test/say.html b/test/say.html new file mode 100644 index 0000000..a8aed1c --- /dev/null +++ b/test/say.html @@ -0,0 +1,25 @@ + + + + + +
+ + + + From 5565321dc105e6949e7483b46f3134d188fd7ca8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Thu, 19 Nov 2015 09:07:55 -0500 Subject: [PATCH 62/68] Fix global_turtle sync when writing new objects. --- jquery-turtle.js | 6 +++--- test/sync.html | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index bcf03f1..8465c7e 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -5944,15 +5944,15 @@ function wrapglobalcommand(name, helptext, fn, fnfilter) { this.plan(cc.resolver(j)); }); cc.exit(); - if (early && early.result && early.result.constructor === jQuery) { - sync(animate, early.result); - } } else { cc = setupContinuation(null, name, arguments, argcount); fn.apply(early, arguments); cc.exit(); } if (early) { + if (early.result && early.result.constructor === jQuery && global_turtle) { + sync(global_turtle, early.result); + } return early.result; } }; diff --git a/test/sync.html b/test/sync.html index 87e4d1a..4808c51 100644 --- a/test/sync.html +++ b/test/sync.html @@ -46,4 +46,22 @@ start(); }); }); + +asyncTest("Moves a written object and verifies that sync works.", function() { + speed(10); + var w = write('hello'); + w.bk(100); + sync(w, turtle); + w.plan(function() { + // When w is done moving, w2 should not have moved yet. + var xy2 = w2.getxy(); + var xy = w.getxy(); + ok(xy2[1] > xy[1]); + }); + var w2 = write('hello2'); + w2.bk(100); + done(function() { + start(); + }); +}); From 89e724b6054d3aac6eb2b142144a3b54f133f23a Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 30 Nov 2015 07:39:54 -0500 Subject: [PATCH 63/68] Add $.turtle.colors. --- jquery-turtle.js | 1 + test/random.html | 1 + 2 files changed, 2 insertions(+) diff --git a/jquery-turtle.js b/jquery-turtle.js index 8465c7e..da2d879 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -8626,6 +8626,7 @@ $.turtle = function turtle(id, options) { }; $.extend($.turtle, dollar_turtle_methods); +$.turtle.colors = colors; function seehelphook(text, result) { // Also, check the command to look for (non-CoffeeScript) help requests. diff --git a/test/random.html b/test/random.html index 0d03405..4e6e289 100644 --- a/test/random.html +++ b/test/random.html @@ -20,6 +20,7 @@ equal(random([1,2,3]), 3); equal(random(), 0.9520654011140274); equal(random(null), 0.006122341186296597); + equal($.turtle.colors.indexOf('red'), 120); start(); }); From 157acf35a1166b9fc2df8adf840a62175c2230a3 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Sat, 9 Jan 2016 16:12:56 +0100 Subject: [PATCH 64/68] Fix and clean up the code to avoid compiler errors and warnings. --- jquery-turtle.js | 75 +++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/jquery-turtle.js b/jquery-turtle.js index da2d879..033294b 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -772,7 +772,7 @@ function readTransformMatrix(elem) { // Reads out the css transformOrigin property, if present. function readTransformOrigin(elem, wh) { var hidden = ($.css(elem, 'display') === 'none'), - swapout, old, name, gcs, origin; + swapout, old, name; if (hidden) { // IE GetComputedStyle doesn't give pixel values for transformOrigin // unless the element is unhidden. @@ -783,13 +783,13 @@ function readTransformOrigin(elem, wh) { elem.style[name] = swapout[name]; } } - var gcs = (global.getComputedStyle ? global.getComputedStyle(elem) : null), - origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); + var gcs = (global.getComputedStyle ? global.getComputedStyle(elem) : null); if (hidden) { for (name in swapout) { elem.style[name] = old[name]; } } + var origin = (gcs && gcs[transformOrigin] || $.css(elem, 'transformOrigin')); if (origin && origin.indexOf('%') < 0) { return $.map(origin.split(' '), parseFloat); } @@ -960,7 +960,7 @@ function getTurtleOrigin(elem, inverseParent, extra) { { position: "absolute", visibility: "hidden", display: "block" } : {}, substTransform = swapout[transform] = (inverseParent ? 'matrix(' + $.map(inverseParent, cssNum).join(', ') + ', 0, 0)' : 'none'), - old = {}, name, gbcr, transformOrigin, result; + old = {}, name, gbcr, transformOrigin; for (name in swapout) { old[name] = elem.style[name]; elem.style[name] = swapout[name]; @@ -1250,7 +1250,7 @@ function scrollWindowToDocumentPosition(pos, limit) { if (tx < ww2) { tx = ww2; } if (ty > dh - wh2) { ty = dh - wh2; } if (ty < wh2) { ty = wh2; } - targ = { pageX: tx, pageY: ty }; + var targ = { pageX: tx, pageY: ty }; if ($.isNumeric(limit)) { targ = limitMovement(w.origin(), targ, limit); } @@ -2178,8 +2178,7 @@ function touchesPixel(elem, color) { h = (bb.bottom - bb.top), osc = getOffscreenCanvas(w, h), octx = osc.getContext('2d'), - rgba = rgbaForColor(color), - j = 1, k, data; + j = 1, k; if (!c || c.length < 3 || !w || !h) { return false; } octx.drawImage(canvas, bb.left, bb.top, w, h, 0, 0, w, h); @@ -2726,7 +2725,8 @@ var stablyLoadedImages = {}; // @param css is a dictionary of css props to set when the image is loaded. // @param cb is an optional callback, called after the loading is done. function setImageWithStableOrigin(elem, url, css, cb) { - var record, urlobj = absoluteUrlObject(url), url = urlobj.href; + var record, urlobj = absoluteUrlObject(url); + url = urlobj.href; // The data-loading attr will always reflect the last URL requested. elem.setAttribute('data-loading', url); if (url in stablyLoadedImages) { @@ -3032,7 +3032,7 @@ var Pencil = (function(_super) { } // The pencil is a sprite that just defaults to zero size. var context = canvas ? canvas.parentElement : null; - var settings = { width: 0, height: 0, color: transparent }; + var settings = { width: 0, height: 0, color: 'transparent' }; Pencil.__super__.constructor.call(this, settings, context); // Set the pencil to hidden, infinite speed, // and drawing on the specifed canvas. @@ -3072,7 +3072,7 @@ var Webcam = (function(_super) { function Webcam(opts, context) { var attrs = "", hassrc = false, hasautoplay = false, hasdims = false; if ($.isPlainObject(opts)) { - for (key in opts) { + for (var key in opts) { attrs += ' ' + key + '="' + escapeHtml(opts[key]) + '"'; } hassrc = ('src' in opts); @@ -3317,12 +3317,12 @@ var Piano = (function(_super) { // Converts a midi number to a white key position (black keys round left). function wcp(n) { - return floor((n + 7) / 12 * 7); + return Math.floor((n + 7) / 12 * 7); }; // Converts from a white key position to a midi number. function mcp(n) { - return ceil(n / 7 * 12) - 7; + return Math.ceil(n / 7 * 12) - 7; }; // True if midi #n is a black key. @@ -4517,7 +4517,7 @@ var Instrument = (function() { if (key in given) { timbre[key] = given[key]; } else { - timbre[key] = defaulTimbre[key]; + timbre[key] = defaultTimbre[key]; } } } @@ -5206,7 +5206,6 @@ function parseABCFile(str) { function syncopateStem(stem, t) { var j, note, stemtime = stem.time, newtime = stemtime + t; stem.time = newtime; - syncopateStem for (j = 0; j < stem.notes.length; ++j) { note = stem.notes[j]; // Only adjust a note's duration if it matched the stem's duration. @@ -7116,8 +7115,8 @@ var turtlefn = { // For defaults, copy inline styles of the turtle itself except for // properties in the following list (these are the properties used to // make the turtle look like a turtle). - for (var j = 0; j < currentStyles.length; ++j) { - var styleProperty = currentStyles[j]; + for (var j2 = 0; j2 < currentStyles.length; ++j2) { + var styleProperty = currentStyles[j2]; if (/^(?:width|height|opacity|background-image|background-size)$/.test( styleProperty) || /transform/.test(styleProperty)) { continue; @@ -7535,7 +7534,6 @@ var turtlefn = { callback.apply(this, arguments); } }); - sync = null; }), plan: wrapraw('plan', ["plan(fn) Runs fn in the animation queue. For planning logic: " + @@ -7958,7 +7956,6 @@ var dollar_turtle_methods = { callback.apply(this, arguments); } }); - sync = null; }), load: wrapraw('load', ["load(url, cb) Loads data from the url and passes it to cb. " + @@ -7994,7 +7991,8 @@ var dollar_turtle_methods = { "save 'intro', 'pen gold, 20\\nfd 100\\n'"], function(url, data, cb) { if (!url) throw new Error('Missing url for save'); - var payload = { }, url = apiUrl(url, 'save'), key; + var payload = { }, key; + url = apiUrl(url, 'save'); if (/\.json(?:$|\?|\#)/.test(url)) { data = JSON.stringify(data, null, 2); } @@ -8254,7 +8252,7 @@ var dollar_turtle_methods = { return (x == 0) ? NaN : ((x > 0) ? 0 : 180); } else if (x == 0) { return (y > 0) ? Infinity : -Infinity; - } else if (abs(y) == abs(x)) { + } else if (Math.abs(y) == Math.abs(x)) { return (y > 0) ? ((x > 0) ? 45 : 135) : ((x > 0) ? -45 : -135); } @@ -8608,12 +8606,6 @@ $.turtle = function turtle(id, options) { seeopt.height = options.panelheight; } see.init(seeopt); - if (wrotebody) { - /* - see.html('Turtle script should be inside body ' + - '- wrote a <body>'); - */ - } // Return an eval loop hook string if 'see' is exported. if (exportedsee) { if (global.CoffeeScript) { @@ -9355,13 +9347,12 @@ function turtleevents(prefix) { if (prefix || prefix === '') { eventsaver = (function(e) { // Keep the old instance if possible. - var names = [prefix + e.type], j, old, name, prop; + var names = [prefix + e.type], j; if ((e.originalEvent || e) instanceof MouseEvent) { names.push(prefix + 'mouse'); } for (j = 0; j < names.length; ++j) { - var name = names[j]; - old = global[name], prop; + var name = names[j], old = global[name], prop; if (old && old.__proto__ === e.__proto__) { for (prop in old) { if (old.hasOwnProperty(prop)) delete old[prop]; } for (prop in e) { if (e.hasOwnProperty(prop)) old[prop] = e[prop]; } @@ -9757,7 +9748,7 @@ function prepareInput(name, callback, type) { textbox.attr('type', 'number'); } if (type == 'voice') { - button.css({display:none}); + button.css({display: 'none'}); var SR = global.SpeechRecognition || global.webkitSpeechRecognition; if ('function' == typeof(SR)) { try { @@ -9892,40 +9883,41 @@ function componentColor(t, args) { return t + '(' + Array.prototype.join.call(args, ',') + ')'; } -function autoArgs(arguments, start, map) { +function autoArgs(args, start, map) { var j = 0; var taken = []; var result = {}; for (var key in map) { var pattern = map[key]; - for (j = start; j < arguments.length; ++j) { + for (j = start; j < args.length; ++j) { if (~taken.indexOf(j)) continue; if (pattern == '*') { break; - } else if (pattern instanceof RegExp && pattern.test(arguments[j])) { + } else if (pattern instanceof RegExp && pattern.test(args[j])) { break; - } else if (pattern instanceof Function && pattern(arguments[j])) { + } else if (pattern instanceof Function && pattern(args[j])) { break; - } else if (pattern == typeof arguments[j]) { + } else if (pattern == typeof args[j]) { break; } } - if (j < arguments.length) { + if (j < args.length) { taken.push(j); - result[key] = arguments[j]; + result[key] = args[j]; } } - if (taken.length + start < arguments.length) { + if (taken.length + start < args.length) { var extra = []; - for (j = start; j < arguments.length; ++j) { + for (j = start; j < args.length; ++j) { if (~taken.indexOf(j)) continue; - extra.push(arguments[j]); + extra.push(args[j]); } result.extra = extra; } return result; } + ////////////////////////////////////////////////////////////////////////// // DEBUGGING SUPPORT ////////////////////////////////////////////////////////////////////////// @@ -10036,7 +10028,7 @@ debug.init(); c = cnv.getContext('2d'), relative = false, p = lineend || e, - s, html, dx, dy, dd, dir, ang; + html, dx, dy, dd, dir, ang; if (linestart && 'function' == typeof(linestart.pagexy)) { var xy = linestart.getxy(), s = linestart.pagexy(); s.x = xy[0]; @@ -10763,7 +10755,6 @@ function aselement(s, def) { default: return s; } - return null; } function stickscroll() { var stick = false, a = aselement(autoscroll, null); From 04503f35404c3a709d13b3e8b3e2bb7a5f4a4c4c Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Wed, 13 Jan 2016 21:11:19 +0100 Subject: [PATCH 65/68] Fixed smaller issues and removed jquery-turtle.min.js from .gitignore. --- .gitignore | 1 - LICENSE.txt | 2 +- README.md | 2 +- jquery-turtle.js | 24 ++++++++++-------------- jquery-turtle.min.js | 5 +++++ 5 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 jquery-turtle.min.js diff --git a/.gitignore b/.gitignore index c40cd56..a349cbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .svn node_modules -jquery-turtle.min.js diff --git a/LICENSE.txt b/LICENSE.txt index fca7187..f997494 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -2,7 +2,7 @@ jQuery-turtle version 2.0 LICENSE (MIT): -Copyright (c) 2013 Pencil Code Foundation, Google, and other contributors. +Copyright (c) 2013 Pencil Code Foundation, Google Inc., and other contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 91bace6..8af96ab 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ element. License (MIT) ------------- -Copyright (c) 2014 Pencil Code, Google, and other contributors +Copyright (c) 2014 Pencil Code, Google Inc., and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/jquery-turtle.js b/jquery-turtle.js index 033294b..b09f1ad 100644 --- a/jquery-turtle.js +++ b/jquery-turtle.js @@ -4709,16 +4709,13 @@ var Instrument = (function() { return result; } - var whiteNoiseBuf = null; function getWhiteNoiseBuf() { - if (whiteNoiseBuf == null) { - var ac = getAudioTop().ac, - bufferSize = 2 * ac.sampleRate, - whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), - output = whiteNoiseBuf.getChannelData(0); - for (var i = 0; i < bufferSize; i++) { - output[i] = Math.random() * 2 - 1; - } + var ac = getAudioTop().ac, + bufferSize = 2 * ac.sampleRate, + whiteNoiseBuf = ac.createBuffer(1, bufferSize, ac.sampleRate), + output = whiteNoiseBuf.getChannelData(0); + for (var i = 0; i < bufferSize; i++) { + output[i] = Math.random() * 2 - 1; } return whiteNoiseBuf; } @@ -7323,7 +7320,7 @@ var turtlefn = { if (typeof val != 'object' || !$.isNumeric(val.width) || !$.isNumeric(val.height) || !($.isArray(val.data) || val.data instanceof Uint8ClampedArray || - val.data instanceof Unit8Array)) { + val.data instanceof Uint8Array)) { return; } var imdat = ctx.createImageData( @@ -7917,7 +7914,7 @@ var dollar_turtle_methods = { sel.tone.apply(sel, arguments); } else { var instrument = getGlobalInstrument(); - instrument.play.apply(instrument, args); + instrument.play.apply(instrument); } }), silence: wrapraw('silence', @@ -8489,14 +8486,12 @@ var colors = [ $.turtle = function turtle(id, options) { var exportedsee = false; - if (!arguments.length) { - id = 'turtle'; - } if (arguments.length == 1 && typeof(id) == 'object' && id && !id.hasOwnProperty('length')) { options = id; id = 'turtle'; } + id = id || 'turtle'; options = options || {}; if ('turtle' in options) { id = options.turtle; @@ -8615,6 +8610,7 @@ $.turtle = function turtle(id, options) { } } } + return $('#' + id); }; $.extend($.turtle, dollar_turtle_methods); diff --git a/jquery-turtle.min.js b/jquery-turtle.min.js new file mode 100644 index 0000000..2718667 --- /dev/null +++ b/jquery-turtle.min.js @@ -0,0 +1,5 @@ +(function(a){function b(b,c){if(Ne>5)Oe.push({elem:b,qname:c});else{for(Ne+=1,a.dequeue(b,c);Oe.length>0;){var d=Oe.shift();a.dequeue(d.elem,d.qname),Vc()}Ne-=1}}function c(a,b){function c(){this.constructor=a}for(var d in b)Je.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a}function d(b){var c,d,e=b.charAt(0).toUpperCase()+b.slice(1),f=["Moz","Webkit","O","ms"],g=document.createElement("div");if(b in g.style)d=b;else for(var h=0;h<0?'"'+a+'"':"'"+a+"'":a}var c=[];for(var d in a)a.hasOwnProperty(d)&&c.push(d+":"+b(a[d])+";");return c.join(" ")}function h(a){return a}function i(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1]];return 6==a.length&&(c[0]+=a[4],c[1]+=a[5]),c}function j(a,b){var c=[a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1],a[0]*b[2]+a[2]*b[3],a[1]*b[2]+a[3]*b[3]],d=6==a.length;return 6==b.length?(c.push(a[0]*b[4]+a[2]*b[5]+(d?a[4]:0)),c.push(a[1]*b[4]+a[3]*b[5]+(d?a[5]:0))):d&&(c.push(a[4]),c.push(a[5])),c}function k(a){return Math.abs(a)>1e-12}function l(a){return!(k(a[1])||k(a[2])||k(1-a[0])||k(1-a[3]))}function m(a){if(l(a))return[1,0,0,1];var b=u(a);return k(b[2])?j(o(-b[3]),j(p(1/b[1],1/b[2]),o(-b[0]))):null}function n(a){var b=m(a);if(4==a.length)return b;var c=i(b,[-a[4],-a[5]]);return b.push(c[0]),b.push(c[1]),b}function o(a){var b=Math.cos(a),c=Math.sin(a);return[b,c,-c,b]}function p(a,b){return 1==arguments.length&&(a=b),[a,0,0,b]}function q(a,b){return[a[0]+b[0],a[1]+b[1]]}function r(a,b){return[a[0]-b[0],a[1]-b[1]]}function s(a,b){return[a[0]*b,a[1]*b]}function t(a,b,c){return q(i(a,r(b,c)),c)}function u(a){var b,c,d=a[0]*a[0]+a[1]*a[1],e=a[0]*a[2]+a[1]*a[3],f=a[2]*a[2]+a[3]*a[3],g=-.5*Math.atan2(2*e,d-f),h=Math.cos(g),i=Math.sin(g),j=a[0]*h-a[2]*i,k=a[1]*h-a[3]*i,l=Math.atan2(k,j),m=Math.cos(l),n=Math.sin(l),o=(a[1]*i+a[3]*h)*m-(a[0]*i+a[2]*h)*n,p=(a[0]*h-a[2]*i)*m+(a[1]*h-a[3]*i)*n;return g<-Math.PI/4?(g+=Math.PI/2,c=p,b=o,l-=Math.PI/2):(b=p,c=o),l>Math.PI&&(l-=2*Math.PI),[l,b,c,g]}function v(a,b){var c=(b-a)/2,d=Math.cos(c),e=Math.sin(c),f=d,g=-e,h=1+f*d+g*e,i=f*e-g*d,j=i&&4/3*(Math.sqrt(2*h)-h)/i,k=f-j*g,l=g+j*f,m=k,n=-l,o=c+a,p=Math.cos(o),q=Math.sin(o);return[[k*p-l*q,k*q+l*p],[m*p-n*q,m*q+n*p],[Math.cos(b),Math.sin(b)]]}function w(a){var b=ra(a,!1);if(b)return[b.tx,b.ty];var c=x(a);return c?[c[4],c[5]]:[0,0]}function x(b){var c=Ie.getComputedStyle?Ie.getComputedStyle(b)[Pe]:a.css(b,"transform");if(!c||"none"===c)return null;var d=/^matrix\(([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+),\s*([\-+.\de]+)(?:px)?,\s*([\-+.\de]+)(?:px)?\)$/.exec(c);return d?[parseFloat(d[1]),parseFloat(d[2]),parseFloat(d[3]),parseFloat(d[4]),parseFloat(d[5]),parseFloat(d[6])]:A(c)}function y(b,c){var d,e,f,g="none"===a.css(b,"display");if(g){d={position:"absolute",visibility:"hidden",display:"block"},e={};for(f in d)e[f]=b.style[f],b.style[f]=d[f]}var h=Ie.getComputedStyle?Ie.getComputedStyle(b):null;if(g)for(f in d)b.style[f]=e[f];var i=h&&h[Qe]||a.css(b,"transformOrigin");if(i&&i.indexOf("%")<0)return a.map(i.split(" "),parseFloat);if(c)return[c[0]/2,c[1]/2];var j=a(b);return[j.width()/2,j.height()/2]}function z(a){for(var b,c=[1,0,0,1];null!==a;)b=x(a),b&&!l(b)&&(c=j(b,c)),a=a.parentElement;return c.slice(0,4)}function A(b){var c=[1,0,0,1],d=[],e=[],f=/(?:^\s*|)(\w*)\s*\(([^)]*)\)\s*/g,g=b.replace(f,function(b){return d.push(b[1].toLowerCase()),e.push(a.map(b[2].split(","),function(a){var b=a.trim().toLowerCase();return{num:parseFloat(b),unit:b.replace(/^[+-.\de]*/,"")}})),""});if(g)return null;for(var h=d.length-1;h>=0;--h){var i,k,l,m=null,n=d[h],o=e[h];if("matrix"==n)o.length>=6&&(m=[o[0].num,o[1].num,o[2].num,o[3].num,o[4].num,o[5].num]);else if("rotate"==n)1==o.length&&(i=wa(o[0]),k=Math.cos(i),l=Math.sin(i),m=[k,-l,k,l]);else if("translate"==n||"translatex"==n||"translatey"==n){var p=0,q=0;if(o.length>=1){if(o[0].unit&&"px"!=o[0].unit)return null;if("translate"==n||"translatex"==n?p=o[0].num:"translatey"==n&&(q=o[0].num),"translate"==n&&o.length>=2){if(o[1].unit&&"px"!=o[1].unit)return null;q=o[1].num}m=[0,0,0,0,p,q]}}else if("scale"==n||"scalex"==n||"scaley"==n){var r=1,s=1;o.length>=1&&("scale"==n||"scalex"==n?r=o[0].num:"scaley"==n&&(s=o[0].num),"scale"==n&&o.length>=2&&(s=o[1].num),m=[r,0,0,s,0,0])}else{if("skew"!=n&&"skewx"!=n&&"skewy"!=n)return null;var t=0,u=0;o.length>=1&&("skew"==n||"skewx"==n?t=Math.tan(wa(o[0])):"skewy"==n&&(u=Math.tan(wa(o[0]))),"skew"==n&&o.length>=2&&(u=Math.tan(wa(o[0]))),m=[1,u,t,1,0,0])}c=j(c,m)}return c}function B(a,b,c){if(0>=c)return a;var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=d*d+e*e;if(c*c>=f)return b;var g=c/Math.sqrt(f);return{pageX:a.pageX+g*d,pageY:a.pageY+g*e}}function C(a,b,c){if(0>=c)b=a;else if(180>c){var d=xa(b-a);d>c?b=a+c:-c>d&&(b=a-c)}return xa(b)}function D(a,b,c,d){return{pageX:Math.floor(a+c/2),pageY:Math.floor(b+d/2)}}function E(a,b,c,d){var e=a+c,f=b+d;return[{pageX:a,pageY:b},{pageX:a,pageY:f},{pageX:e,pageY:f},{pageX:e,pageY:b}]}function F(a){if(!/e[\-+]/.exec(a))return a;var b=a.replace(/(?:\d+(?:\.\d*)?|\.\d+)e[\-+]\d+/g,function(a){return sa(parseFloat(a))});return b}function G(b,c,d){var e=a.data(b,"turtleData");if(e&&e.quickhomeorigin&&e.down&&e.style&&!d&&b.classList&&b.classList.contains("turtle"))return e.quickhomeorigin;var f,g,h,i="none"===a.css(b,"display"),j=i?{position:"absolute",visibility:"hidden",display:"block"}:{},k=(j[Pe]=c?"matrix("+a.map(c,sa).join(", ")+", 0, 0)":"none",{});for(f in j)k[f]=b.style[f],b.style[f]=j[f];g=N(b),h=y(b,[g.width,g.height]);for(f in j)b.style[f]=F(k[f]);d&&(d.gbcr=g,d.localorigin=h);var l=q([g.left,g.top],h);return e&&e.down&&e.style&&(e.quickhomeorigin=l),l}function H(){return Ie.innerHeight||a(Ie).height()}function I(){return Ie.innerWidth||a(Ie).width()}function J(){return document.body?a(document).height():document.height}function K(){return document.body?a(document).width():document.width}function L(a){return a.offsetHeight<=0&&a.offsetWidth<=0}function M(a,b,c,d){return{left:a,top:b,right:a+c,bottom:b+d,width:c,height:d}}function N(b){return mb(b)?M(b.pageX,b.pageY,0,0):a.isWindow(b)?M(a(Ie).scrollLeft(),a(Ie).scrollTop(),I(),H()):9===b.nodeType?M(0,0,K(),J()):"getBoundingClientRect"in b?T.apply(b):M(0,0,0,0)}function O(a,b,c){var d=Math.max(0,Math.max(c.top-a.pageY,a.pageY-c.bottom)),e=Math.max(0,Math.max(c.left-a.pageX,a.pageX-c.right));return e*e+d*d>b}function P(a,b,c){var d=Math.max(c.bottom-a.pageY,a.pageY-c.top),e=Math.max(c.right-a.pageX,a.pageX-c.left);return b>e*e+d*d}function Q(a,b){return b.right=a.top&&b.bottom<=a.bottom&&b.left>=a.left&&b.right<=a.right}function S(a,b){return 4===a.length&&a[0].pageX===b.left&&a[0].pageY===b.top&&a[1].pageX===b.left&&a[1].pageY===b.bottom&&a[2].pageX===b.right&&a[2].pageY===b.bottom&&a[3].pageX===b.right&&a[3].pageY===b.top}function T(){var a=this.getBoundingClientRect();return{top:a.top+Ie.pageYOffset,bottom:a.bottom+Ie.pageYOffset,left:a.left+Ie.pageXOffset,right:a.right+Ie.pageXOffset,width:a.width,height:a.height}}function U(b,c,d,e,f){var g,h,j,k,l=z(b.parentElement),n=m(l),o=G(b,n);if(n){if(a.isNumeric(d)&&(j=w(b),g=q(i(l,j),o),h={pageX:g[0],pageY:g[1]},c=B(h,c,d)),k=i(n,r([c.pageX,c.pageY],o)),e||f){var p=Gc(b);k[0]+=e*p,k[1]-=f*p}return sa(k[0])+" "+sa(k[1])}}function V(a){var b=a.offsetParent;return b?b:document}function W(b,c){c||(c=a(V(b)).pagexy());var d=z(b.parentElement),e=m(d),f=G(b,e),g=ra(b,!0),h=e&&i(e,r([c.pageX,c.pageY],f)),j=1/Gc(b);if(e)return[(g.tx-h[0])*j,(h[1]-g.ty)*j]}function X(b,c){var d,e,f=z(b.parentElement),g=(ra(b,!0),a(V(b)).pagexy()),h=Gc(b),j=[];for(e=0;ei-f&&(d=i-f),f>d&&(d=f),e>j-g&&(e=j-g),g>e&&(e=g);var l={pageX:d,pageY:e};a.isNumeric(c)&&(l=B(k.origin(),l,c)),k.scrollLeft(l.pageX-f),k.scrollTop(l.pageY-g)}function ca(a,b,c){var d=b.pageX-a.pageX,e=b.pageY-a.pageY,f=c.pageX-a.pageX,g=c.pageY-a.pageY;return f*e-d*g}function da(a,b,c){var d=c.pageX-a.pageX,e=c.pageY-a.pageY;return d*b.pageY-b.pageX*e}function ea(a,b){if(b.length<=0)return!1;if(1==b.length)return b[0].pageX==a.pageX&&b[0].pageY==a.pageY;var c=ca(a,b[b.length-1],b[0]);if(0===c)return!0;var d=c>0;if(2==b.length)return!1;for(var e=1;e0!=d)return!1}return!0}function fa(a,b){return{pageX:a.pageX-b.pageX,pageY:a.pageY-b.pageY}}function ga(a,b,c,d){var e,f,g=fa(c,b);for(e=0;e0?1:0>a?-1:0}function ia(a){if(a.length<=2)return 0;var b=ca(a[a.length-1],a[0],a[1]);if(0!==b)return ha(b);for(var c=1;c1&&1!=ha(ca(a[a.length-2],a[a.length-1],b));)a.pop();return a.length&&d(a[a.length-1],b)||a.push(b),a}function c(a,b,c){for(var d=0;db.pageX?1:a.pageYb.pageY?1:0}a.sort(e);var f=c(a,[],b),g=c(a.reverse(),[],b);return f.concat(g.slice(1,-1))}function ma(b){if(!b)return null;if(a.isArray(b))return b;for(var c=a.map(b.trim().split(/\s+/),parseFloat),d=[],e=0;e+1180&&(b-=360),b}function wa(a){return a/180*Math.PI}function xa(a){return Math.abs(a)>180&&(a%=360,a>180?a-=360:-180>=a&&(a+=360)),a}function ya(a){return Math.abs(a)>=720&&(a=a%360+(a>0?360:-360)),a}function za(){return Re.field||Ba(),Re.field}function Aa(){return Re.surface||Ba(),Re.surface}function Ba(){var b=document.createElement("samp"),c=document.createElement("samp"),d=Math.floor(I()/2),e=Math.floor(H()/2);a(b).css({position:"absolute",display:"inline-block",top:0,left:0,width:"100%",height:"100%",font:"inherit",zIndex:-1,transformOrigin:d+"px "+e+"px",pointerEvents:"none",overflow:"hidden"}).addClass("turtlefield"),a(c).attr("id","origin").css({position:"absolute",display:"inline-block",top:e,left:d,width:"100%",height:"0",font:"inherit",transformOrigin:"0px 0px",pointerEvents:"all",turtleSpeed:1/0}).appendTo(b),Re.surface=b,Re.field=c,Ca(),Sb()}function Ca(){document.body?(null==a("html").attr("style")&&a("html").css("min-height","100%"),a(Re.surface).prependTo("body"),Rb()):a(document).ready(Ca)}function Da(a){return a.drawOnCanvas||(a.drawOnCanvas=Fa()),a.drawOnCanvas}function Ea(b){var c=a.data(b,"turtleData");return c?c.drawOnCanvas?c.drawOnCanvas:Re.canvas:null}function Fa(){if(Re.canvas)return Re.canvas;var b=Aa();return Re.canvas=document.createElement("canvas"),a(Re.canvas).css({"z-index":-1}),b.insertBefore(Re.canvas,b.firstChild),Ja(),Ha(Ja),a(Ie).resize(Ja),Re.canvas}function Ga(a,b){return Re.offscreen&&Re.offscreen.width===a&&Re.offscreen.height===b?(Re.offscreen.getContext("2d").clearRect(0,0,a,b),Re.offscreen):(Re.offscreen||(Re.offscreen=document.createElement("canvas")),Re.offscreen.width=a,Re.offscreen.height=b,Re.offscreen)}function Ha(b){var c=a("body"),d=c.width(),e=c.height(),f=function(){(c.width()!=d||c.height()!=e)&&(b(),d=c.width(),e=c.height())};Re.timer&&clearInterval(Re.timer),Re.timer=setInterval(f,250)}function Ia(){var b=a("body");return[Math.max(b.outerWidth(!0),Ie.innerWidth||a(Ie).width()),Math.max(b.outerHeight(!0),Ie.innerHeight||a(Ie).height())]}function Ja(){if(Re.canvas){var b,c=Ia(),d=c[0],e=c[1],f=Re.canvas.width,g=Re.canvas.height,h=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(d/100))*Re.subpixel,i=Math.max(Math.min(2e3,Math.max(200,f)),100*Math.ceil(e/100))*Re.subpixel;a(Re.surface).css({width:d+"px",height:e+"px"}),(f!=h||g!=i)&&(b=document.createElement("canvas"),b.width=Math.min(f,h),b.height=Math.min(g,i),b.getContext("2d").drawImage(Re.canvas,0,0),Re.canvas.width=h,Re.canvas.height=i,Re.canvas.getContext("2d").drawImage(b,0,0),a(Re.canvas).css({width:h/Re.subpixel,height:i/Re.subpixel}))}}function Ka(a,b){if(!a)return null;if(a&&"function"==typeof a&&(a.helpname||a.name)&&(a=a.helpname||a.name),a=String(a),a.trim&&(a=a.trim()),!a||"none"===a)return null;if("path"===a||"fill"===a)return{savePath:!0};var c=!1;/^erase\b/.test(a)&&(a=a.replace(/^erase\b/,"white; globalCompositeOperation:destination-out"),c=!0);var d=f(a,b);return c&&(d.eraseMode=!0),d}function La(a){return a?g(a):"none"}function Ma(a){return"down"==a||a===!0?!0:"up"==a||a===!1?!1:He}function Na(a){return a?"down":"up"}function Oa(b){var c=a.data(b,"turtleData");return c||(c=a.data(b,"turtleData",{style:null,corners:[[]],path:[[]],down:!1,speed:"turtle",easing:"swing",turningRadius:0,drawOnCanvas:null,quickpagexy:null,quickhomeorigin:null,oldscale:1,instrument:null,stream:null})),c}function Pa(b){var c=a.data(b,"turtleData");return c?c.turningRadius:0}function Qa(){return{get:function(a,b,c){return sa(Pa(a))+"px"},set:function(a,b){var c=parseFloat(b);if(!isNaN(c)&&(Oa(a).turningRadius=c,a.style.turtleTurningRadius=""+sa(c)+"px",0===c)){var d=ra(a,!1);d&&(d.rot>180||d.rot<=-180)&&(d.rot=xa(d.rot),a.style[Pe]=ta(d))}}}}function Ra(){return{get:function(a,b,c){return La(Oa(a).style)},set:function(a,b){var c=Ka(b,"strokeStyle"),d=Oa(a);d.style&&(d.style=null,bb(a,d,!0)),d.style=c,a.style.turtlePenStyle=La(c),bb(a,d,!0)}}}function Sa(){return{get:function(a,b,c){return Na(Oa(a).down)},set:function(a,b){var c=Ma(b);if(c!==He){var d=Oa(a);c!=d.down&&(d.down=c,d.quickpagexy=null,d.quickhomeorigin=null,a.style.turtlePenDown=Na(c),bb(a,d,!0))}}}}function Ta(a,b){return 0===Math.round(a.pageX-b.pageX)&&0===Math.round(a.pageY-b.pageY)}function Ua(a,b){return 0===Math.round(1e3*(a.pageX-b.pageX))&&0===Math.round(1e3*(a.pageY-b.pageY))}function Va(a,b){return Ta(a,b)&&0===Math.round(a.pageX-b.pageX1)&&0===Math.round(a.pageY-b.pageY1)&&0===Math.round(b.pageX2-b.pageX)&&0===Math.round(b.pageY2-b.pageY)}function Wa(a){var b=1e3*a,c=Math.round(b);return Math.abs(c-b)1){h=b[j],f=h.length>2&&Ta(h[0],h[h.length-1])&&!Ta(h[0],h[Math.floor(h.length/2)]),g=f&&!("pageX2"in h[h.length-1]);var k=h[0].pageX,l=h[0].pageY;i.moveTo(k,l);for(var m=1;m1&&(d.length=1),d[0].length&&(d[0].length=0),void(c&&(e?f.length&&f[0].length&&(1==f[0].length?f[0].length=0:f.unshift([])):(f.length>1&&(f.length=1),f[0].length&&(f[0].length=0))));if(c||!e.savePath){var g=Y(a);if(c&&(g.corner=!0,ab(f[0],g)),!e.savePath){ab(d[0],g);var h=Kc(a);$a(Da(b),b.path,e,h,2)}}}}function cb(b,c){var d=Oa(b);d.style&&(c=a.extend({},d.style,c));var e=Kc(b);$a(Da(d),d.corners,c,e,1)}function db(b){if((!b||/\bcanvas\b/.test(b))&&Re.canvas){var c=Re.canvas.getContext("2d");c.save(),c.setTransform(1,0,0,1,0,0),c.clearRect(0,0,Re.canvas.width,Re.canvas.height),c.restore()}if((!b||/\bturtles\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtle").not(".turtlefield");pf&&(d=d.not(pf)),d.remove()}if((!b||/\blabels\b/.test(b))&&Re.surface){var d=a(Re.surface).find(".turtlelabel").not(".turtlefield");d.remove()}(!b||/\btext\b/.test(b))&&a("body").contents().not(".turtlefield").remove()}function eb(a,b){if(!a||a.length<1)return null;for(var c=1,d={left:Math.floor(a[0].pageX),top:Math.floor(a[0].pageY),right:Math.ceil(a[0].pageX),bottom:Math.ceil(a[0].pageY)};c=c[3]/2)return!0}else{var o=!c;for(m=0;m0==o)return!0}return!1}function hb(a,b,c){if(b.img)("CANVAS"==a[0].tagName||a[0].tagName==b.img.tagName)&&Kb(b.img,a[0],b.css);else if("IMG"==a[0].tagName||"CANVAS"==a[0].tagName)Hb(a[0],b.url,b.css,c),c=null;else{var d={backgroundImage:"url("+b.url+")",backgroundRepeat:"no-repeat",backgroundPosition:"center"};b.css.width&&b.css.height&&(d.backgroundSize=b.css.width+"px "+b.css.height+"px"),a.css(d)}c&&c()}function ib(b,c,d){var e,f=ra(b,!0),g=f&&wa(f.rot),h=Gc(b),i=f&&c*h,j=f&&(d||0)*h,k=-Math.cos(g)*i,l=Math.sin(g)*i,m=a.data(b,"turtleData");f&&(d&&(k+=Math.sin(g)*j,l+=Math.cos(g)*j),m&&(e=m.quickpagexy)&&(m.quickpagexy={pageX:e.pageX+l,pageY:e.pageY+k}),f.tx+=l,f.ty+=k,b.style[Pe]=ta(f),bb(b,m,!0))}function jb(b,c,d){var e,f=ra(b,!0),g=a.data(b,"turtleData");f&&(g&&(e=g.quickpagexy)&&(g.quickpagexy={pageX:e.pageX+c,pageY:e.pageY-d}),f.tx+=c,f.ty-=d,b.style[Pe]=ta(f),bb(b,g,!0))}function kb(a,b){var c=ra(a,!0);c&&(c.rot+=b,a.style[Pe]=ta(c))}function lb(a,b,c){var d=ra(a,!0);if(d){var e=Gc(a),f=wa(d.rot),g=b*e,h=(c||0)*e,i=-Math.cos(f)*g,j=Math.sin(f)*g;return h&&(i+=Math.sin(f)*h,j+=Math.cos(f)*h),sa(d.tx+j)+" "+sa(d.ty+i)}}function mb(b){return b&&a.isNumeric(b.pageX)&&a.isNumeric(b.pageY)}function nb(){return{get:function(a,b,c){return Oa(a).speed},set:function(b,c){(a.isNumeric(c)&&!(0>=c)||c in a.fx.speeds||""+c=="Infinity")&&(Oa(b).speed=""+c)}}}function ob(){return{get:function(a,b,c){return Oa(a).easing},set:function(b,c){c in a.easing&&(Oa(b).easing=c)}}}function pb(b,c){var d=a.data(b,"turtleData");return c=c||Df,d?a.isNumeric(d.speed)||"Infinity"==d.speed?1e3/d.speed:"turtle"==d.speed&&c?0:d.speed:c?0:"turtle"}function qb(b){var c=a.data(b,"turtleData");return c?c.easing:null}function rb(){return{get:function(a,b,c){var d=ra(a,b),e=y(a);if(d){var f=wa(d.rot),g=Math.cos(f),h=Math.sin(f),i=Gc(a);return sa(((d.tx+e[0])*h-(d.ty+e[1])*g)/i)+"px"}},set:function(b,c){var d,e=ra(b,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},f=y(b),g=Gc(b),h=parseFloat(c)*g,i=wa(e.rot),j=Math.cos(i),k=Math.sin(i),l=(e.tx+f[0])*j+(e.ty+f[1])*k,m=l*j+h*k-f[0],n=l*k-h*j-f[1],o=a.data(b,"turtleData");o&&(d=o.quickpagexy)&&(o.quickpagexy={pageX:d.pageX+(m-e.tx),pageY:d.pageY+(n-e.ty)}),e.tx=m,e.ty=n,b.style[Pe]=ta(e),bb(b,o)}}}function sb(b,c,d,e){return{get:function(a,c,e){var f=ra(a,c);return f?f[b]+d:void 0},set:function(d,f){var g,h=ra(d,!0)||{tx:0,ty:0,rot:0,sx:1,sy:1,twi:0},i={displace:e},j=a.data(d,"turtleData"),k=h.tx,l=h.ty;h[b]=c(f,d,h,i),d.style[Pe]=ta(h),i.displace?(j&&(g=j.quickpagexy)&&(j.quickpagexy={pageX:g.pageX+(h.tx-k),pageY:g.pageY+(h.ty-l)}),bb(d,j)):Z(d)}}}function tb(a,b,c){var d=ya(b-a),e=d>0?c:-c,f=wa(a),g=[Math.cos(f)*e,Math.sin(f)*e],h=wa(b);return{delta:d,sradius:e,dc:g,dx:g[0]-Math.cos(h)*e,dy:g[1]-Math.sin(h)*e}}function ub(a,b,c,d,e,f){var g,h,j,k,l,m,n,o,p,r,t,u=tb(c,d,e),w=u.sradius,x=u.dc;for(n=1,o=u.delta,p=Math.abs(u.delta),p>45&&(n=Math.ceil(p/45),o=u.delta/n),r=[];--n>=0;)g=0===n?d:c+o,h=wa(c+180),j=wa(g+180),r.push.apply(r,v(h,j)),c=g;for(t=[],k=0;k2||(j.length>=1&&(i[c]=parseFloat(j[0])),j.length>=2?i[d]=parseFloat(j[1]):e?i[d]=0:i[d]=i[c],b.style[Pe]=ta(i),e?(k&&(h=k.quickpagexy)&&(k.quickpagexy={pageX:h.pageX+(i.tx-l),pageY:h.pageY+(i.ty-m)}),bb(b,k)):Z(b))}}}function Ab(a){return Se.href=a,Se}function Bb(a){return Ab(a).href}function Cb(a){return/(?:^|\.)pencil(?:code)?\./i.test(a)}function Db(a){var b=Ab(null==a?"":a).hostname,c=/^(\w+)\.pencil(?:code)?\./i.exec(b);return c?c[1]:null}function Eb(a,b){var c=Ab(null==a?"":a),d=c.href;return Cb(c.hostname)?/^\/(?:edit|home|code|load|save)(?:\/|$)/.test(c.pathname)&&(d=c.protocol+"//"+c.host+"/"+b+"/"+c.pathname.replace(/\/[^\/]*(?:\/|$)/,"")+c.search+c.hash):Cb(Ie.location.hostname)&&(d="/proxy/"+d),d}function Fb(a){return/\//.test(a)?a:(a="/img/"+a,Cb(Ie.location.hostname)?a:"//pencilcode.net"+a)}function Gb(){if(!document.cookie)return null;for(var a=document.cookie.split(/;\s*/),b=0;b0&&b.height>0)try{e=c.getContext("2d"),e.clearRect(0,0,b.width,b.height),e.drawImage(b,0,0)}catch(i){}}else c.src=b.src;d&&g.css(d);var j=y(c);if(b&&!d.turtleHull)try{var k=De(b);Ge(k,parseFloat(g.css("width"))/b.width,parseFloat(g.css("height"))/b.height,-j[0],-j[1]),g.css("turtleHull",k)}catch(i){}Lb(c,f,j)}function Lb(b,c,d){var e=a(b);if(e.hasClass("turtle")&&(d[0]!=c[0]||d[1]!=c[1]))if("absolute"==e.css("position")&&/px$/.test(e.css("left"))&&/px$/.test(e.css("top")))e.css("left",parseFloat(e.css("left"))+c[0]-d[0]),e.css("top",parseFloat(e.css("top"))+c[1]-d[1]);else{var f=ra(b,!0);f.tx+=c[0]-d[0],f.ty+=c[1]-d[1],b.style[Pe]=ta(f)}}function Mb(b,c,d,e,f){var g,h,i,j,k;return e===He&&f===He?(g=a(d),g.length?(h=g[0],i=N(h),S(_(h),i)?b.filter(function(){var b=N(this);return c===(R(i,b)||!Q(i,b)&&a(this).inside(h))}):b.filter(function(){return c===a(this).inside(h)})):[]):(j=a.isNumeric(e)&&a.isNumeric(f)?[e,f]:e,a.isArray(j)&&(j=X(b[0]||document.body,[j])[0]),"touch"===d?mb(j)?b.filter(function(){return c===a(this).touches(j)}):(g=a(j),i=N(g[0]),S(_(g[0]),i)?b.filter(function(){var a=N(this);return c===(!Q(i,a)&&(R(i,a)||g.touches(this)))}):b.filter(function(){return c===g.touches(this)})):(k=d*d,b.filter(function(){var a=N(this);if(O(j,k,a))return!c;if(P(j,k,a))return c;var b=Y(this),d=j.pageX-b.pageX,e=j.pageY-b.pageY;return c===k>=d*d+e*e})))}function Nb(){if(!Ze){Ze=!0;try{Ie.parent.document.activeElement.blur()}catch(a){}Ie.focus()}}function Ob(b,c,d,e,f){for(var g=e.split(/\s+/),h=0;h<0?void 0:c.apply(this,arguments)};c.guid&&(d.guid=c.guid),a.handler=d}}function Wb(){Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keydown keyup",Tb),Ob(a.event.fixHooks,"filter",a.event.keyHooks,"keypress",Ub),Ob(a.event.special,"add",{},"keydown keyup keypress",Vb)}function Xb(){return{get:function(a,b,c){return g(Zb(a).getTimbre())},set:function(a,b){Zb(a).setTimbre(f(b,"wave"))}}}function Yb(){return{get:function(a,b,c){return Zb(a).getVolume()},set:function(a,b){Zb(a).setVolume(parseFloat(b))}}}function Zb(b){var c=Oa(b);if(c.instrument)return c.instrument;c.instrument=new ef("piano");var d=a(b);return c.instrument.on("noteon",function(b){var c=a.Event("noteon");c.midi=b.midi,d.trigger(c)}),c.instrument.on("noteoff",function(b){var c=a.Event("noteoff");c.midi=b.midi,d.trigger(c)}),c.instrument}function $b(){return af||(af=new ef),af}function _b(){return!(!Ie.AudioContext&&!Ie.webkitAudioContext)}function ac(){if(ac.audioTop)return ac.audioTop;if(!_b())return null;var a=new(Ie.AudioContext||Ie.webkitAudioContext);return ac.audioTop={ac:a,wavetable:jc(a),out:null,currentStart:null},bc(),ac.audioTop}function bc(){if(ac.audioTop){var a=ac.audioTop;a.out&&(a.out.disconnect(),a.out=null,a.currentStart=null);try{var b=a.ac.createDynamicsCompressor();b.ratio=16,b.attack=5e-4,b.connect(a.ac.destination),a.out=b}catch(c){ac.audioTop=null}}}function cc(){var a=ac();return null!=a.currentStart?a.currentStart:(a.currentStart=Math.max(.25,a.ac.currentTime),setTimeout(function(){a.currentStart=null},0),a.currentStart)}function dc(a){return 440*Math.pow(2,(a-69)/12)}function ec(a){return Math.round(69+12*Math.log(a/440)/Math.LN2)}function fc(a){var b=/^(\^+|_+|=|)([A-Ga-g])([,']*)$/.exec(a);if(!b)return null;var c=b[3].replace(/,/g,"").length-b[3].replace(/'/g,"").length,d=bf[b[2]]+cf[b[1].charAt(0)]*b[1].length+12*c;return d+60}function gc(a){var b=(a-72)%12;(a>60||0!=b)&&(b+=12);for(var c=Math.round((a-b-60)/12),d=df[b];0!=c;)d+=c>0?"'":",",c+=c>0?-1:1;return d}function hc(a){return dc(fc(a))}function ic(a){function b(a,b){switch(a){case"V":A!==z&&c(b.split(" ")[0]);break;case"M":f(b,A);break;case"L":g(b,A);break;case"Q":h(b,A)}A.hasOwnProperty(a)?A[a]+="\n"+b:A[a]=b,"K"==a&&(B=k(b),A===z&&c(d()))}function c(a){a=a||"",(a||A===z)&&(z.voice||(z.voice={}),z.voice.hasOwnProperty(a)?(A=z.voice[a],C=A.accent):(A={id:a,accent:{slurred:0}},z.voice[a]=A,C=A.accent))}function d(){return z.V?z.V.split(/\s+/)[0]:""}function e(a){var e,f=a.match(gf),g=null,h=0,i=0,j=null;if(!f)return null;for(;h/.test(f[h]))i=f[h++].length;else if(/^\(\d+(?::\d+)*/.test(f[h]))j=o(f[h++]);else if(/^[!+].*[!+]$/.test(f[h]))p(f[h++],C);else if(/^.?".*"$/.test(f[h]))h++;else if(/^[()]$/.test(f[h]))"("==f[h++]?C.slurred+=1:(C.slurred-=1,C.slurred<=0&&(C.slurred=0,A.stems&&A.stems.length>=1&&m(A.stems[A.stems.length-1],!1)));else if(/\|/.test(f[h])){for(e in C)1==e.length&&delete C[e];h++}else g=q(f,h,B,C),null!==g?(j&&(n(g.stem,j.time),j.count-=1,j.count||(j=null)),i&&A.stems&&A.stems.length&&(e=i>0?(1-Math.pow(.5,i))*g.stem.time:(Math.pow(.5,-i)-1)*A.stems[A.stems.length-1].time,l(A.stems[A.stems.length-1],e),l(g.stem,-e)),i=0,C.slurred&&m(g.stem,!0),A===z&&c(d()),"stems"in A||(A.stems=[]),A.stems.push(g.stem),h=g.index):h++}function f(a,b){var c=/^C/.test(a)?1:t(a);c&&(b.unitnote||(.75>c?b.unitnote=1/16:b.unitnote=1/8))}function g(a,b){var c=t(a);c&&(b.unitnote=c)}function h(a,b){var c,d=a.split(/\s+|=/),e=null,f=null;for(c=0;c=0||/^[1-4]$/.test(d[c])?e=e||t(d[c]):f=f||Number(d[c]);e&&(b.unitbeat=e),f&&(b.tempo=f)}function i(a){var b,c,d,e,f,g={};for(c=0;c0)for(b=0;a>b&&7>b;++b)d[c.charAt(b)]="^";else for(b=0;b>a&&b>-7;--b)d[c.charAt(6+b)]="_";return d}function k(a){if(!a)return{};var b,c={"c#":7,"f#":6,b:5,e:4,a:3,d:2,g:1,c:0,f:-1,bb:-2,eb:-3,ab:-4,db:-5,gb:-6,cb:-7,"a#m":7,"d#m":6,"g#m":5,"c#m":4,"f#m":3,bm:2,em:1,am:0,dm:-1,gm:-2,cm:-3,fm:-4,bbm:-5,ebm:-6,abm:-7,"g#mix":7,"c#mix":6,"f#mix":5,bmix:4,emix:3,amix:2,dmix:1,gmix:0,cmix:-1,fmix:-2,bbmix:-3,ebmix:-4,abmix:-5,dbmix:-6,gbmix:-7,"d#dor":7,"g#dor":6,"c#dor":5,"f#dor":4,bdor:3,edor:2,ador:1,ddor:0,gdor:-1,cdor:-2,fdor:-3,bbdor:-4,ebdor:-5,abdor:-6,dbdor:-7,"e#phr":7,"a#phr":6,"d#phr":5,"g#phr":4,"c#phr":3,"f#phr":2,bphr:1,ephr:0,aphr:-1,dphr:-2,gphr:-3,cphr:-4,fphr:-5,bbphr:-6,ebphr:-7,"f#lyd":7,blyd:6,elyd:5,alyd:4,dlyd:3,glyd:2,clyd:1,flyd:0,bblyd:-1,eblyd:-2,ablyd:-3,dblyd:-4,gblyd:-5,cblyd:-6,fblyd:-7,"b#loc":7,"e#loc":6,"a#loc":5,"d#loc":4,"g#loc":3,"c#loc":2,"f#loc":1,bloc:0,eloc:-1,aloc:-2,dloc:-3,gloc:-4,cloc:-5,floc:-6,bbloc:-7},d=a.replace(/\s+/g,"").toLowerCase().substr(0,5),e=d.match(/maj|min|mix|dor|phr|lyd|loc|m/);b=e?"maj"==e?d.substr(0,e.index):"min"==e?d.substr(0,e.index+1):d.substr(0,e.index+e[0].length):/^[a-g][#b]?/.exec(d)||"";var f=j(c[b]),g=a.substr(b.length).match(/(_+|=|\^+)[a-g]/gi);if(g)for(var h=0;h<2))switch(a=a.substring(1,a.length-1)){case"pppp":case"ppp":b.dynamics=.2;break;case"pp":b.dynamics=.4;break;case"p":b.dynamics=.6;break;case"mp":b.dynamics=.8;break;case"mf":b.dynamics=1;break;case"f":b.dynamics=1.2;break;case"ff":b.dynamics=1.4;break;case"fff":case"ffff":b.dynamics=1.5}}function q(a,b,c,d){var e,f,g,h,i=[],j="",k=!1,l=null,m=1/0;if(bf&&(j=e,m=f),b0&&"="==a.charAt(0)?a.substr(1):a}function s(a,b,c){var d,e=/^(\^+|_+|=|)([A-Ga-g])(.*)$/.exec(a);return e?(d=e[2].toUpperCase(),e[1].length>0?(c[d]=e[1],r(a)):r(c.hasOwnProperty(d)?c[d]+e[2]+e[3]:b.hasOwnProperty(d)?b[d]+e[2]+e[3]:a)):a}function t(a){var b,c,d,e=/^(\d*)(?:\/(\d*))?$|^(\/+)$/.exec(a),f=0;if(e){if(e[3])return Math.pow(.5,e[3].length);if(c=e[2]?parseFloat(e[2]):/\//.test(a)?2:1,d=0,b=e[1]?parseFloat(e[1]):1,e[2])for(;d+1c;)d+=1,f=parseFloat(e[1].substring(0,d)),b=parseFloat(e[1].substring(d));return f+b/c}}var u,v,w,x,y=a.split("\n"),z={},A=z,B={},C={slurred:0};for(u=0;uc;++c)e[c]=b.real[c],f[c]=b.imag[c];try{return a.createPeriodicWave(e,f)}catch(g){}try{return a.createWaveTable(e,f)}catch(g){}return null}function d(a,b,c){var d,e,f={real:[],imag:[]},g=a.real.length;for(d=0;g>d;++d)e=Math.log(b[Math.min(d,b.length-1)]),f.real.push(a.real[d]*Math.exp(c*e)),f.imag.push(a.imag[d]*Math.exp(c*e));return f}var e,f,g,h,i,j,k={};for(e in b)if(f=b[e],j=c(f)){if(i={wave:j},f.mult)for(h=b[e].freq,i.freq={},g=0;gc;++c)b[c]&&(b[c].constructor===a?e.push.apply(e,b[c].toArray()):a.isArray(b[c])?e.push.apply(e,b[c]):e.push(b[c]));return{elts:a.unique(e),completion:d}}function lc(){function b(){var a,b=g;for(g=null,f&&f(),a=0;a1)for(c=0;c")}function oc(b){var c,d,e=a.extend({},sf,hf,tf);if(b&&!a.isArray(b.helptext)&&b in e&&(b=e[b]),b&&a.isArray(b.helptext)&&b.helptext.length){for(d=0;d/g,'<$1 style="border:1px solid black;text-decoration:none;word-break:keep-all;white-space:nowrap">').replace(/<(mark)>/g,'<$1 style="border:1px solid blue;color:blue;text-decoration:none;word-break:keep-all;white-space:nowrap;cursor:pointer;" onclick="see.enter($(this).text())">'))}return vf}if("number"==typeof b)return nc("Equal to the number "+b+"."),vf;if("boolean"==typeof b)return nc("Equal to the boolean value "+b+"."),vf;if(null===b)return nc("The special null value represents the absence of a value."),vf;if(b===He)return nc("This is an unassigned value."),vf;if(b===Ie)return nc("The global window object represents the browser window."),vf;if(b===document)return nc("The HTML document running the program."),vf;if(b===jQuery)return nc('The jQuery function. Read about it at
jquery.com.'),vf;if(b&&b!=oc)return nc("No help available for "+b),vf;c=[];for(var g in e)!e[g].helptext||!e[g].helptext.length||g in Ie&&"function"!=typeof Ie[g]||c.push(g);return c.sort(function(a,b){return a.length!=b.length?a.length-b.length:b>a?-1:a>b?1:0}),nc("help available for: "+c.map(function(a){return''+a+""}).join(" ")),vf}function pc(a){return 1==a.length&&qc(a[0])&&a[0]}function qc(b){var c;return b&&0==a.queue(b).length&&(!b.parentElement||!b.parentElement.style.transform)&&(0===(c=pb(b))||0===a.fx.speeds[c])}function rc(b,c){var d;if(null==c){if(qc(b))return;d=pb(b)}else d=1e3*c;var e=a(b);d>0&&e.delay(d)}function sc(a,b){if(b=b||0,a.length<=b)return null;var c=a[a.length-1];return"function"!=typeof c||c.helpname?null:c}function tc(b,c,d,e){function f(d,e){if(null!=d){var f=b&&b[d];e&&f&&bb(f,a.data(f,"turtleData"),!0),Hf.reportEvent("resolve",[c,m,j,d,f])}0==--k&&h&&(l?(Me+=1,setTimeout(function(){Me-=1,h()},0)):h())}function g(a){null!=a&&Hf.reportEvent("appear",[c,m,j,a,b&&b[a],i])}var h=sc(d,e),i=h?Array.prototype.slice.call(d,0,d.length-1):d,j=b?b.length||0:0,k=j+1,l=!0,m=Hf.nextId();return Hf.reportEvent("enter",[c,m,j,i]),{name:c,args:i,appear:g,resolve:f,resolver:function(a,b){return function(){f(a,b)}},exit:function(){Hf.reportEvent("exit",[c,m,j,i]),f(null),l=!1}}}function uc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var d,f=tc(this,b,arguments,c),g=[f].concat(a.makeArray(f.args));try{d=e.apply(this,g)}finally{f.exit()}return d};return zc(b,d,f)}function vc(a,b,c){var d=function(){if(Vc(a),Le)throw new Error(a+" interrupted");return Wc(a,this),c.apply(this,arguments)};return zc(a,b,d)}function wc(b,c,d,e){var f=function(){if(Vc(b),Le)throw new Error(b+" interrupted");var c=null,f=0,g=pf;if(e&&(c=e.apply(null,arguments),f=arguments.length,g=Yc()),g){var h=a(pf).eq(0),i=arguments,j=tc(h,b,arguments,f);h.plan(function(a,b){j.appear(a),d.apply(c,i),this.plan(j.resolver(a))}),j.exit()}else j=tc(null,b,arguments,f),d.apply(c,arguments),j.exit();return c?(c.result&&c.result.constructor===jQuery&&pf&&lc(pf,c.result),c.result):void 0};return zc(b,c,f)}function xc(b,c){return zc(b,c,function(c,d){var e=/^key/.test(b),f=/^mouse|click$/.test(b),g=f?"input,button":e?"textarea,input:not([type]),input[type=text],input[type=password]":null;e&&Nb(),null==d&&"function"==typeof c&&(d=c,c=null),a(Ie).on(b+".turtleevent",null,c,g?function(b){return Le||a(b.target).closest(g).length?void 0:d.apply(this,arguments)}:d)})}function yc(){var b=a._data(Ie,"events");if(!b)return!1;for(var c in b)for(var d=b[c],e=0;eb?-c:c,l=null;i.style&&i.down&&(l=function(){var a=Y(h),c=ra(h,!0),d=z(h.parentElement);return function(){ub(i.corners[0],a,c.rot,c.rot+(e?-b:b),k*(i.oldscale?c.sy:1),d)}}()),i.turningRadius=k,this.animate({turtleRotation:g+sa(b)+"deg"},pb(h,f),qb(h)),this.plan(function(){l&&l(),i.turningRadius=j,a.resolve(d,!0)})}),this)}function Bc(a,b){null==b&&(b=100),"bk"===a.name&&(b=-b);var c,d=Df;return(c=pc(this))?(a.appear(0),ib(c,b,0),a.resolve(0,!0),this):(this.plan(function(c,e){a.appear(c),this.animate({turtleForward:"+="+sa(b||0)+"px"},pb(e,d),qb(e),a.resolver(c,!0))}),this)}function Cc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e=Df;return this.plan(function(a,f){b&&b.appear(a),this.animate({turtlePosition:lb(f,d,c)},pb(f,e),qb(f),b&&b.resolver(a,!0))}),this}function Dc(b,c,d){a.isArray(c)&&(d=c[1],c=c[0]),d||(d=0),c||(c=0);var e,f=Df;return(e=pc(this))?(b&&b.appear(0),jb(e,c,d),b&&b.resolve(0),this):(this.plan(function(a,e){b&&b.appear(a);var g=w(e);this.animate({turtlePosition:sa(g[0]+c)+" "+sa(g[1]-d)},pb(e,f),qb(e),b&&b.resolver(a,!0))}),this)}function Ec(b,c,d){var e=c,f=0,g=0,h=null,i=Df;return a.isNumeric(e)&&a.isNumeric(d)?(f=parseFloat(e),g=parseFloat(d),e=null,h=null):a.isArray(e)?(f=e[0],g=e[1],e=null,h=d):a.isNumeric(d)&&(h=d),this.plan(function(c,d){var j=e;if(null===j&&(j=a(V(d)).pagexy()),j&&!mb(j))try{j=a(j).pagexy()}catch(k){return}return j&&mb(j)?a.isWindow(d)?(b&&b.appear(c),ba(j,h),void(b&&b.resolve(c))):void(9!==d.nodeType&&(b&&b.appear(c),this.animate({turtlePosition:U(d,j,h,f,g)},pb(d,i),qb(d),b&&b.resolver(c,!0)))):void 0}),this}function Fc(a){return function(b,c,d){return this.plan(function(e,f){b.appear(e);var g=this.css("turtlePenDown");this.css({turtlePenDown:"up"}),a.call(this,null,c,d),this.plan(function(){this.css({turtlePenDown:g}),b.resolve(e,!0)})}),this}}function Gc(b){var c=a.data(b,"turtleData");return c&&null!=c.oldscale?c.oldscale:1}function Hc(a,b,c){Jc.call(this,!0,a,b,c)}function Ic(a,b,c){Jc.call(this,!1,a,b,c)}function Jc(b,c,d,e){e===He&&(e=d),d&&e||(d=e=1);var f=Df;return this.plan(function(g,h){if(b&&(Oa(h).oldscale*=e),c.appear(g),a.isWindow(h)||9===h.nodeType)return void c.resolve(g);var i=a.map(a.css(h,"turtleScale").split(" "),parseFloat);1===i.length&&i.push(i[0]),i[0]*=d,i[1]*=e,this.animate({turtleScale:a.map(i,sa).join(" ")},pb(h,f),qb(h),c.resolver(g))}),this}function Kc(a,b){var c=z(a.parentElement),d=l(c),e=d?1:u(c)[1];return e*Gc(a)}function Lc(b){var c=Df;return function(d,e,f){if(a.isNumeric(e)){var g=e;e=f,f=g}return null==f&&(f=8.8),this.plan(function(g,h){var i=Oa(h),j=i.style;e||(e=j&&(j.fillStyle||j.strokeStyle)||"black"),d.appear(g);var k=this.pagexy(),l=ra(h,!0),m=Ka(e,"fillStyle"),n=Da(i),o=Kc(h),p=f*o,q=Math.max(0,p-2),r=p+(m.eraseMode?2:0),s=/rgba|hsla/.test(m.fillStyle);null==m.lineWidth&&j&&j.lineWidth&&(m.lineWidth=j.lineWidth),pc(this)?(b(n,k,r,l.rot,m,!0),d.resolve(g)):this.queue(function(e){a({radius:0}).animate({radius:q},{duration:pb(h,c),step:function(){s||b(n,k,this.radius,l.rot,m,!1)},complete:function(){b(n,k,r,l.rot,m,!0),d.resolve(g),e()}})})}),this}}function Mc(a,b,c,d,e){var f=a.getContext("2d");f.save(),Xa(f,e),c===1/0?(f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height)):(Za(f,a),f.beginPath(),f.arc(b.pageX,b.pageY,c/2,0,2*Math.PI,!1),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()),f.restore()}function Nc(a,b,c,d,e){var f=a.getContext("2d");if(f.save(),Xa(f,e),c===1/0)f.setTransform(1,0,0,1,0,0),f.fillRect(0,0,a.width,a.height);else{var g=Math.sin((d+45)/180*Math.PI),h=Math.cos((d+45)/180*Math.PI),i=c*h/Math.SQRT2,j=c*g/Math.SQRT2;Za(f,a),f.beginPath(),f.moveTo(b.pageX-i,b.pageY-j),f.lineTo(b.pageX-j,b.pageY+i),f.lineTo(b.pageX+i,b.pageY+j),f.lineTo(b.pageX+j,b.pageY-i),f.closePath(),f.fill(),e.strokeStyle&&f.stroke()}f.restore()}function Oc(a,b,c,d,e,f){var g=a.getContext("2d");if(g.save(),Xa(g,e),!e.strokeStyle&&e.fillStyle&&(g.strokeStyle=e.fillStyle),c!==1/0){var h=Math.sin(d/180*Math.PI),i=-Math.cos(d/180*Math.PI),j=e.lineWidth||1.62,k=b.pageX+c*h,l=b.pageY+c*i,m=Pc(j,k,l,h,i),n=c-m.hs,o=n*h,p=n*i;Za(g,a),n>0&&(g.beginPath(),g.moveTo(b.pageX,b.pageY),g.lineTo(b.pageX+o,b.pageY+p),g.stroke()),f&&Qc(g,m)}g.restore()}function Pc(a,b,c,d,e){var f=Math.max(1.25*a,a+2),g=2*f,h=g-f/2;return{hs:h,x1:b,y1:c,xm:b-d*h,ym:c-e*h,x2:b-e*f-d*g,y2:c+d*f-e*g,x3:b+e*f-d*g,y3:c-d*f-e*g}}function Qc(a,b){a.beginPath(),a.moveTo(b.x2,b.y2),a.lineTo(b.x1,b.y1),a.lineTo(b.x3,b.y3),a.quadraticCurveTo(b.xm,b.ym,b.x2,b.y2),a.closePath(),a.fill()}function Rc(a,b,c,d,e,f){var g=e-c,h=f-d,i=Math.sqrt(g*g+h*h),j=g/i,k=h/i,l=Pc(b,e,f,j,k);i>l.hs&&(a.beginPath(),a.moveTo(c,d),a.lineTo(l.xm,l.ym),a.lineWidth=b,a.stroke()),Qc(a,l)}function Sc(a,b){function c(){d&&clearInterval(d),b&&b()}var d=null;if(!Ie.speechSynthesis)return console.log("No speech synthesis: "+a),void c();try{var e=new Ie.SpeechSynthesisUtterance(a);e.addEventListener("end",c),e.addEventListener("error",c),e.lang=navigator.language||"en-GB",Ie.speechSynthesis.speak(e),d=setInterval(function(){Ie.speechSynthesis.pending||Ie.speechSynthesis.speaking||c()},250)}catch(f){Ie.console&&Ie.console.log(f),c()}}function Tc(){a.each(["toggle","show","hide"],function(b,c){var d=a.fn[c];a.fn[c]=function(b,c,e){var f=arguments;!f.length&&this.hasClass("turtle")&&(this.length>1||!a._data(this[0],"fxshow"))&&(f=[0]),d.apply(this,f)}})}function Uc(c,d){if(d||(d="fx"),"IMG"==c.tagName&&c.src&&!c.complete){var e=a.queue(c,d);0==e.length&&(a.queue(c,d,function(a){Ib(c,null,a)}),b(c,d))}}function Vc(b){if(!(a.turtle.hangtime==1/0||kf++<100)){kf=0;var c=(new Date).getTime();return mf?void(c-mf>a.turtle.hangtime&&(Sf.visible()&&Sf.html('Oops: program interrupted because it was hanging the browser. Try reducing the number of repetitions. Or try using await done defer() or tick to make an animation.'),a.turtle.interrupt("hung"))):(mf=c,clearTimeout(lf),void(lf=setTimeout(function(){clearTimeout(lf),lf=null,mf=null},0)))}}function Wc(b,c){if(!a.turtle.nowarn){var d,e=!0;for(d=0;e&&d=100&&(e=!1);e||(jf[b]||(jf[b]=1,Sf.visible()?Sf.html('Oops: '+b+' may not return useful results when motion is queued. Try speed Infinity or await done defer() first.'):console.warn(b+' may not return useful results when motion is queued. Try "speed Infinity" or "await done defer()".')),c.finish())}}function Xc(a,b,d){a[b]=function(){return b in jf||(Sf.html(''+b+" deprecated. Use "+d+"."),jf[b]=1),a[d].apply(this,arguments)},a[d].__super__&&c(a[b],a[d])}function Yc(){return pf&&a.queue(pf).length>0}function Zc(a){if(null===uf.pollTimer){var b=uf.sent[a],c=uf.waiting[a];c&&c.length&&b&&b.length&&(uf.pollTimer=setTimeout(function(){uf.pollTimer=null,c&&c.length&&b&&b.length&&(c.shift().apply(null,b.shift()),Zc(a))},0))}}function $c(a,b){if("function"!=typeof b&&"undefined"!=typeof b||!/^\w+\s*$/.test(a)){if("undefined"==typeof b&&/^help\s+\S+$/.test(a))return oc(/^help\s+(\S+)$/.exec(a)[1]),!0}else{if(b&&b.helptext)return oc(b),!0;if(a in tf)return oc(a),!0}return!1}function _c(a,b,c,d){return a.helptext?d.helptext=a.helptext:b in c&&(d.helptext=c[b].helptext),d.method=a,d.helpname=b,d}function ad(a,b){var c=[];for(var d in b)!b.hasOwnProperty(d)||d in Ie||(c.push(d),Ie[d]=function(b){var c=a[b],d=a;return _c(c,b,tf,function(){return c.apply(d,arguments)})}(d));return c}function bd(){pf=null;for(var a=0;a=0;i--)null===h[i]?(i--,c.moveTo(d-h[i][0],e+h[i][1])):c.lineTo(d-h[i][0],e+h[i][1])}return c.lineWidth=1.1,c.strokeStyle="rgba(255,255,255,0.75)",c.stroke(),c.beginPath(),c.arc(d,e,15.5,0,2*Math.PI,!1),c.closePath(),c.strokeStyle="rgba(0,0,0,0.4)",c.stroke(),b.toDataURL()}function gd(a){var b=Ga(40,48),c=b.getContext("2d");return c.beginPath(),c.moveTo(0,48),c.lineTo(20,0),c.lineTo(40,48),c.lineTo(20,36),c.closePath(),c.fillStyle=a,c.fill(),b.toDataURL()}function hd(a){var b=Ga(40,40),c=b.getContext("2d");return c.beginPath(),c.arc(20,20,18,-5*Math.PI/2,-Math.PI/2),c.closePath(),c.lineTo(20,20),c.stroke(),c.strokeStyle=a,c.lineWidth=4,c.stroke(),b.toDataURL()}function id(a,b){b=b||12;var c=Ga(b,b),d=c.getContext("2d"),e=b/2;return d.beginPath(),d.arc(e,e,e,0,2*Math.PI),d.closePath(),d.fillStyle=a,d.fill(),c.toDataURL()}function jd(a){function b(){e.moveTo(19.5,43),e.lineTo(20.5,43),e.lineTo(21.5,43.5),e.lineTo(25.5,36.2),e.lineTo(24,35.5),e.lineTo(23,35.5),e.lineTo(20.5,36.5),e.lineTo(19.5,36.5),e.lineTo(17,35.5),e.lineTo(16,35.5),e.lineTo(14.5,36.2),e.lineTo(18.5,43.5),e.closePath()}function c(){e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath()}var d=Ga(40,48),e=d.getContext("2d");return e.beginPath(),b(),e.fillStyle="#ffcb6b",e.fill(),e.beginPath(),c(),e.fillStyle="#d1ebff",e.fill(),e.beginPath(),e.moveTo(19.5,48),e.lineTo(13,36),e.lineTo(13,1),e.lineTo(14,0),e.lineTo(26,0),e.lineTo(27,1),e.lineTo(27,36),e.lineTo(20.5,48),e.closePath(),b(),e.moveTo(25.5,12),e.lineTo(25.5,8),e.lineTo(14.5,8),e.lineTo(14.5,12),e.closePath(),e.fillStyle=a,e.fill(),d.toDataURL()}function kd(a,b,c){return c||(c=1),function(d){var e=document.createElement("canvas");e.width=a,e.height=b;var f=e.getContext("2d");d||(d="rgba(128,128,128,0.125)"),"transparent"!=d&&(f.fillStyle=d,f.fillRect(0,0,a,b));var g=a/c,h=b/c,i={width:g,height:h,transformOrigin:g/2+"px + "+h/2+"px",opacity:1};return 1>c&&(i.imageRendering="pixelated"),{img:e,css:i}}}function ld(a){if(!a)return null;if(a in yf)return yf[a];var b=a.match(/^(\d+)x(\d+)(?:\/(\d+))?$/);return b?kd(parseFloat(b[1]),parseFloat(b[2]),b[3]&&parseFloat(b[3])):null}function md(a,b){var c=a.width||a.height||256,d=a.height||a.width||256,e=a.subpixel||1/(a.scale||1),f=a.color||"transparent",g=kd(c,d,e);return g(f)}function nd(b,c){if(!b)return null;if(a.isPlainObject(b))return md(b,c);if(a.isFunction(b)&&(b.helpname||b.name)&&(b=b.helpname||b.name),b.constructor===jQuery){if(!b.length)return null;b=b.get(0)}if(b.tagName)return"CANVAS"!=b.tagName&&"IMG"!=b.tagName&&"VIDEO"!=b.tagName?null:{img:b,css:{opacity:1}};var d=b.toString().trim().split(/\s+/),e=null,f=null;if(d.length&&(f=ld(d[d.length-1]),f&&d.pop()),d.length&&cd(d.join(" "))&&(e=d.join(" "),d.length=0),!f&&e&&(f=ld(c)),f)return f(e);if(/\//.test(b)||(b=Fb(b)),/\//.test(b)){var g=Ab(b).hostname;return!Cb(g)&&Cb(Ie.location.hostname)&&(b=Ie.location.protocol+"//"+Ie.location.host+"/proxy/"+Bb(b)),{url:b,css:{transformOrigin:"50% 50%",opacity:1}}}return null}function od(a){return null==a?"":String(a).replace(/[&<>"]/g,function(a){return zf[a]})}function pd(b,c,d){var e=b&&/^[a-zA-Z]\w*$/.exec(b),f=b&&/^<.*>$/.exec(b),g=nd(b,d)||null==b&&nd(d);e&&a("#"+b).length&&(e=!1);var h;g?(h=a(""),hb(h,g)):h=a(f?b:"
"+od(b)+"
"),b&&"object"==typeof b&&("id"in b&&h.attr("id",b.id),"class"in b&&h.addClass(b["class"])),h.css({position:"absolute",display:"table",top:0,left:0}),(!c||9==c.nodeType||a.isWindow(c))&&(c=za()),h.appendTo(c);var i=y(h[0]);return h.css({top:-i[1],left:-i[0]}),h.css({turtlePosition:U(h[0],a(c).pagexy(),null,0,0),turtleRotation:0,turtleScale:1}),h.addClass("turtle"),e&&(h.attr("id",b),rf&&!Ie.hasOwnProperty(b)&&(Ie[b]=h)),h}function qd(a,b){if("number"==typeof a)return a=Math.ceil(a),"number"==typeof b?(b=Math.ceil(b),Math.floor(Math.random()*(b-a)+a)):Math.floor(Math.random()*a);if("object"==typeof a&&a&&a.length&&a.slice)return a[Math.floor(Math.random()*a.length)];if("normal"==a){var c,d,e,f,g;do c=Math.random(),d=1.7156*(Math.random()-.5),e=c-.449871,f=Math.abs(d)+.386595,g=e*e+f*(.196*f-.25472*e);while(g>.27597&&(g>.27846||d*d>-4*Math.log(c)*c*c));return d/c}return"position"==a?{pageX:qd(K()+1),pageY:qd(J()+1)}:"color"==a?"hsl("+Math.floor(360*Math.random())+",100%,50%)":"gray"==a?"hsl(0,0,"+Math.floor(100*Math.random())+"%)":a===!0?Math.random()>=.5:Math.random()}function rd(b,c){c||"function"!=typeof b||(c=b,b=30);var d=null,e=Math.max(Math.floor(1e3/Math.max(1/86400,b)),0);if(Yc()){var f=a(pf);f.plan(function(){d=c})}else d=c;var g={fn:c,timer:setInterval(function(){if(d)try{Df++,Bf.push(g),d()}finally{Df--,Bf.pop(g)}},e)};return Af.push(g),g.timer}function sd(a){for(var b=[],c=0;c0?1e3/b:0)}function wd(b){b===He&&(b=""),a("[id]").each(function(c,d){Ie[b+d.id]=a("#"+d.id)}),rf=!0}function xd(b){if(b===He&&(b="last"),Ef&&a(Ie).off(a.map(of,function(a,b){return b}).join(" "),Ef),b||""===b){Ef=function(a){var c,d=[b+a.type];for((a.originalEvent||a)instanceof MouseEvent&&d.push(b+"mouse"),c=0;c=a("html").outerHeight(!0);if(b(),e){var f=a(Ie).scrollTop(),g=Math.min(d,a("html").outerHeight(!0)-a(Ie).height());g>f&&a(Ie).scrollTop(g)}}function zd(){if(!Ff.timer){Ff.timer=setTimeout(function(){Ff.timer=null},0);var b=a("body").offset(),c=b?b.top:8;Ff.bottomSeen=Math.min(a(Ie).height()+a(Ie).scrollTop(),a("body").height()+c)}return Ff.bottomSeen}function Ad(b){var c=a(Ie).scrollTop();b(),a(Ie).scrollTop(c)}function Bd(){var a=document.body.lastChild;return a&&"PRE"==a.tagName||(a=document.createElement("pre"),document.body.appendChild(a)),a}function Cd(){var a=arguments;yd(function(){for(var b=Bd(),c=0;c").css({display:"inline-block",verticalAlign:"top",textAlign:"center",height:"1.2em",width:"1.2em",maxWidth:"1.2em",overflow:"hidden"}).appendTo(Bd()),e=function(){null!=b&&d.css({background:b}),null!=c&&d.text(c)};if(pf){var f=a(pf);Ec.call(f,null,d),f.eq(0).plan(e)}else e()}function Ed(){var a=this;yd(function(){a.result.appendTo("body"),a.setup&&a.setup()})}function Fd(b,c){var d="<"+c+' style="display:table">',e="";if(b===He||null===b)return{result:a(d+"
"+e)};if(b.jquery||b instanceof Element&&(b=a(b)))return{result:b};var f=null;return b=""+b,/^\s*<.*>\s*$/.test(b)&&(f=a(b)),(null==f||1!=f.length||1!=f[0].nodeType)&&(f=a(d+b+e)),{result:f}}function Gd(b,c){function d(b){return function(d){j||m&&"change"==d.type||(j=!0,a(this).prop("checked",!0),i.find("input[type=radio]").prop("disabled",!0),c(b))}}function e(a){return function(){j||(l=a,f())}}function f(a){a||(m+=1,setTimeout(function(){m-=1},0),i.find("input").eq(l).prop("checked",!0)),i.find("input").eq(l).focus()}function g(b,c){a.isFunction(b)&&(b=(k+1).toString());var f=a.isFunction(c)||null==c?b:c,g=a('').attr("value",f).on("change click",d(c)),h=a('