diff --git a/examples/p5js/grid24x8_clouds.html b/examples/p5js/grid24x8_clouds.html new file mode 100644 index 0000000000000000000000000000000000000000..f010525730c044e024e89d28960e91de1d9d31d0 --- /dev/null +++ b/examples/p5js/grid24x8_clouds.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>grid24x8z_clouds</title> + <script src="libraries/p5.js" type="text/javascript"></script> + + <script src="libraries/p5.dom.js" type="text/javascript"></script> + <script src="libraries/p5.sound.js" type="text/javascript"></script> + <script src="libraries/opc.js" type="text/javascript"></script> + <script src="p5js_sketches/grid24x8_clouds.js" type="text/javascript"></script> + + <style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style> + </head> + <body> + </body> +</html> diff --git a/examples/p5js/grid8x8_dot.html b/examples/p5js/grid8x8_dot.html new file mode 100644 index 0000000000000000000000000000000000000000..08971382da2d3f15477d1158e2280e48f3666d1d --- /dev/null +++ b/examples/p5js/grid8x8_dot.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>grid8x8_dot</title> + <script src="libraries/p5.js" type="text/javascript"></script> + <script src="libraries/p5.dom.js" type="text/javascript"></script> + <script src="libraries/p5.sound.js" type="text/javascript"></script> + <script src="libraries/opc.js" type="text/javascript"></script> + <script src="p5js_sketches/grid8x8_dot.js" type="text/javascript"></script> + + <style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style> + </head> + <body> + </body> +</html> \ No newline at end of file diff --git a/examples/p5js/grid8x8_noise_simple.html b/examples/p5js/grid8x8_noise_simple.html new file mode 100644 index 0000000000000000000000000000000000000000..7305990623c684259fa6f98dec56d567e6552ea3 --- /dev/null +++ b/examples/p5js/grid8x8_noise_simple.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>grid8x8_dot</title> + <script src="libraries/p5.js" type="text/javascript"></script> + <script src="libraries/p5.dom.js" type="text/javascript"></script> + <script src="libraries/p5.sound.js" type="text/javascript"></script> + <script src="libraries/opc.js" type="text/javascript"></script> + <script src="p5js_sketches/grid8x8_simple_noise.js" type="text/javascript"></script> + + <style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style> + </head> + <body> + </body> +</html> \ No newline at end of file diff --git a/examples/p5js/grid8x8_orbits.html b/examples/p5js/grid8x8_orbits.html new file mode 100644 index 0000000000000000000000000000000000000000..56f89f2ab9f28161f843df6379db35d0f217be35 --- /dev/null +++ b/examples/p5js/grid8x8_orbits.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>Grid 8x8 Orbits</title> + <script src="libraries/p5.js" type="text/javascript"></script> + + <script src="libraries/p5.dom.js" type="text/javascript"></script> + <script src="libraries/p5.sound.js" type="text/javascript"></script> + <script src="libraries/opc.js" type="text/javascript"></script> + <script src="p5js_sketches/grid8x8_orbits.js" type="text/javascript"></script> + + <style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style> + </head> + <body> + </body> +</html> diff --git a/examples/p5js/images/dot.png b/examples/p5js/images/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..eee5297756163e940bc7036a51557a4d07df5ffa Binary files /dev/null and b/examples/p5js/images/dot.png differ diff --git a/examples/p5js/images/flames.jpeg b/examples/p5js/images/flames.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f91518b6a0f5054eb33f60f97a80bc5935a90de0 Binary files /dev/null and b/examples/p5js/images/flames.jpeg differ diff --git a/examples/p5js/images/greenDot.png b/examples/p5js/images/greenDot.png new file mode 100644 index 0000000000000000000000000000000000000000..fb79d40215ba868d865984b91b0f60f95cd5e00b Binary files /dev/null and b/examples/p5js/images/greenDot.png differ diff --git a/examples/p5js/images/purpleDot.png b/examples/p5js/images/purpleDot.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1b93d3f298fc425a9128e9999c47844dc525ff Binary files /dev/null and b/examples/p5js/images/purpleDot.png differ diff --git a/examples/p5js/libraries/opc.js b/examples/p5js/libraries/opc.js new file mode 100644 index 0000000000000000000000000000000000000000..f1daaaefe848501a25c42a0110aa52f5bb011c7f --- /dev/null +++ b/examples/p5js/libraries/opc.js @@ -0,0 +1,160 @@ +/* + * Simple Open Pixel Control client for P5js, + * designed to sample each LED's color from some point on the canvas. + * + * Micah Elizabeth Scott, 2013 + * Ported to P5js by Matthew I. Kessler + * This file is released into the public domain. + */ + +/* + *!!THIS SCRIPT MUST BE LOADED IN HTML BEFORE THE DRAW SCRIPT!! + * + *Example for HTML head: + *<script src="libraries/opc.js" type="text/javascript"></script> + *<script src="strip64_flames.js" type="text/javascript"></script> + */ + +// Arrays for pixels[]'s locations to send rgb values to fcServer. +var pixelLocationsRed = []; +var pixelLocationsGre = []; +var pixelLocationsBlu = []; + +// Arrays for to map pixels on screen. +var ledXpoints = []; +var ledYpoints = []; + +// Enable locations on screen. +var enableShowLocations; + +//New WebSocket. +var socket; +function socketSetup(WebSocketAddress) { + socket = new WebSocket(WebSocketAddress); + enableShowLocations = true; +} + +// Set the location of a single LED. +function led(index, x, y) { + loadPixels(); + if (pixelLocationsRed === null) { + pixelLocationsRed.length = index + 1; + pixelLocationsGre.length = index + 1; + pixelLocationsBlu.length = index + 1; + ledXpoints.length = index + 1; + ledYpoints.length = index + 1; + } else if (index >= pixelLocationsRed.length) { + pixelLocationsRed.length = index + 1; + pixelLocationsGre.length = index + 1; + pixelLocationsBlu.length = index + 1; + ledXpoints.length = index + 1; + ledYpoints.length = index + 1; + } + //Store pixel[] map to color arrays. + var pixelD = pixelDensity(); + var idx = pixelD*pixelD*4*y*width+x*pixelD*4; + pixelLocationsRed[index] = (idx); + pixelLocationsGre[index] = (idx + 1); + pixelLocationsBlu[index] = (idx + 2); + //Store x,y to draw points for pixel locations + ledXpoints[index] = x; + ledYpoints[index] = y; +} + +// Set the location of several LEDs arranged in a strip. +// Angle is in radians, measured clockwise from +X. +// (x,y) is the center of the strip. +function ledStrip(index, count, x, y, spacing, angle, reversed) { + var s = sin(angle); + var c = cos(angle); + for (var i = 0; i < count; i++) { + led( + reversed ? (index + count - 1 - i) * 1 : (index + i) * 1, + //floor() These must be integers. round() causes lag + floor((x + (i - (count - 1) / 2.0) * spacing * c + 0.5) * 1), + floor((y + (i - (count - 1) / 2.0) * spacing * s + 0.5) * 1)); + } +} + +// Set the locations of a ring of LEDs. The center of the ring is at (x, y), +// with "radius" pixels between the center and each LED. The first LED is at +// the indicated angle, in radians, measured clockwise from +X. +function ledRing(index, count, x, y, radius, angle) { + for (var i = 0; i < count; i++) { + var a = angle + i * 2 * PI / count; + led( + index + i, + floor((x - radius * cos(a) + 0.5)), + floor((y - radius * sin(a) + 0.5)) + ); + } +} + +// Set the location of several LEDs arranged in a grid. The first strip is +// at 'angle', measured in radians clockwise from +X. +// (x,y) is the center of the grid. +function ledGrid(index, stripLength, numStrips, x, y, ledSpacing, stripSpacing, angle, zigzag) { + var s = sin(angle + HALF_PI); + var c = cos(angle + HALF_PI); + for (var i = 0; i < numStrips; i++) { + ledStrip( + index + stripLength * i, + stripLength, + x + (i - (numStrips - 1) / 2.0) * stripSpacing * c, + y + (i - (numStrips - 1) / 2.0) * stripSpacing * s, + ledSpacing, + angle, + zigzag && (i % 2) == 1); + } +} + +// Set the location of 64 LEDs arranged in a uniform 8x8 grid. +// (x,y) is the center of the grid. +function ledGrid8x8(index, x, y, spacing, angle, zigzag) { + ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag); +} + +//Called in function draw(){...} on last line. +function drawFrame() { + if (pixelLocationsRed === null) { + // No pixels defined yet + return; + } + var leds = pixelLocationsRed.length; + var packet = new Uint8ClampedArray(4 + leds * 3); + + if (socket.readyState != 1 /* OPEN */ ) { + // The server connection isn't open. Nothing to do. + return; + } + + if (socket.bufferedAmount > packet.length) { + // The network is lagging, and we still haven't sent the previous frame. + // Don't flood the network, it will just make us laggy. + // If fcserver is running on the same computer, it should always be able + // to keep up with the frames we send, so we shouldn't reach this point. + return; + } + + // Dest position in our packet. Start right after the header. + var dest = 4; + loadPixels(); + + // Sample and send the center pixel of each LED + for (var led = 0; led < leds; led++) { + var i = led; + packet[dest++] = pixels[pixelLocationsRed[i]]; + packet[dest++] = pixels[pixelLocationsGre[i]]; + packet[dest++] = pixels[pixelLocationsBlu[i]]; + } + socket.send(packet.buffer); + + //draw pixel locations on screen if enabled + if (showPixelLocations === true) { + for (i = 0; i < leds; i++) { + stroke(127); + //offset x+1 and y+1 so we don't send the dots to the fc Server + point(ledXpoints[i]+1, ledYpoints[i]+1); + } + } +} \ No newline at end of file diff --git a/examples/p5js/libraries/p5.dom.js b/examples/p5js/libraries/p5.dom.js new file mode 100644 index 0000000000000000000000000000000000000000..d4b84602c0ba7943a6d70a1d9500b935d6e8fe8e --- /dev/null +++ b/examples/p5js/libraries/p5.dom.js @@ -0,0 +1,2034 @@ +/*! p5.dom.js v0.2.9 March 3, 2016 */ +/** + * <p>The web is much more than just canvas and p5.dom makes it easy to interact + * with other HTML5 objects, including text, hyperlink, image, input, video, + * audio, and webcam.</p> + * <p>There is a set of creation methods, DOM manipulation methods, and + * an extended p5.Element that supports a range of HTML elements. See the + * <a href="https://github.com/processing/p5.js/wiki/Beyond-the-canvas"> + * beyond the canvas tutorial</a> for a full overview of how this addon works. + * + * <p>Methods and properties shown in black are part of the p5.js core, items in + * blue are part of the p5.dom library. You will need to include an extra file + * in order to access the blue functions. See the + * <a href="http://p5js.org/libraries/#using-a-library">using a library</a> + * section for information on how to include this library. p5.dom comes with + * <a href="http://p5js.org/download">p5 complete</a> or you can download the single file + * <a href="https://raw.githubusercontent.com/lmccart/p5.js/master/lib/addons/p5.dom.js"> + * here</a>.</p> + * <p>See <a href="https://github.com/processing/p5.js/wiki/Beyond-the-canvas">tutorial: beyond the canvas</a> + * for more info on how to use this libary.</a> + * + * @module p5.dom + * @submodule p5.dom + * @for p5.dom + * @main + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) + define('p5.dom', ['p5'], function (p5) { (factory(p5));}); + else if (typeof exports === 'object') + factory(require('../p5')); + else + factory(root['p5']); +}(this, function (p5) { +// ============================================================================= +// p5 additions +// ============================================================================= + + /** + * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' + * prefixes to specify an ID or class respectively, and none for a tag) and returns it as + * a p5.Element. If a class or tag name is given with more than 1 element, + * only the first element will be returned. + * The DOM node itself can be accessed with .elt. + * Returns null if none found. You can also specify a container to search within. + * + * @method select + * @param {String} name id, class, or tag name of element to search for + * @param {String} [container] id, p5.Element, or HTML element to search within + * @return {Object/p5.Element|Null} p5.Element containing node found + * @example + * <div ><code class='norender'> + * function setup() { + * createCanvas(100,100); + * //translates canvas 50px down + * select('canvas').position(100, 100); + * } + * </code></div> + * <div ><code class='norender'> + * // these are all valid calls to select() + * var a = select('#moo'); + * var b = select('#blah', '#myContainer'); + * var c = select('#foo', b); + * var d = document.getElementById('beep'); + * var e = select('p', d); + * </code></div> + * + */ + p5.prototype.select = function (e, p) { + var res = null; + var container = getContainer(p); + if (e[0] === '.'){ + e = e.slice(1); + res = container.getElementsByClassName(e); + if (res.length) { + res = res[0]; + } else { + res = null; + } + }else if (e[0] === '#'){ + e = e.slice(1); + res = container.getElementById(e); + }else { + res = container.getElementsByTagName(e); + if (res.length) { + res = res[0]; + } else { + res = null; + } + } + if (res) { + return wrapElement(res); + } else { + return null; + } + }; + + /** + * Searches the page for elements with the given class or tag name (using the '.' prefix + * to specify a class and no prefix for a tag) and returns them as p5.Elements + * in an array. + * The DOM node itself can be accessed with .elt. + * Returns an empty array if none found. + * You can also specify a container to search within. + * + * @method selectAll + * @param {String} name class or tag name of elements to search for + * @param {String} [container] id, p5.Element, or HTML element to search within + * @return {Array} Array of p5.Elements containing nodes found + * @example + * <div ><code class='norender'> + * function setup() { + * createButton('btn'); + * createButton('2nd btn'); + * createButton('3rd btn'); + * var buttons = selectAll('button'); + * + * for (var i = 0; i < buttons.length; i++){ + * buttons[i].size(100,100); + * } + * } + * </code></div> + * <div ><code class='norender'> + * // these are all valid calls to selectAll() + * var a = selectAll('.moo'); + * var b = selectAll('div'); + * var c = selectAll('button', '#myContainer'); + * var d = select('#container'); + * var e = selectAll('p', d); + * var f = document.getElementById('beep'); + * var g = select('.blah', f); + * </code></div> + * + */ + p5.prototype.selectAll = function (e, p) { + var arr = []; + var res; + var container = getContainer(p); + if (e[0] === '.'){ + e = e.slice(1); + res = container.getElementsByClassName(e); + } else { + res = container.getElementsByTagName(e); + } + if (res) { + for (var j = 0; j < res.length; j++) { + var obj = wrapElement(res[j]); + arr.push(obj); + } + } + return arr; + }; + + /** + * Helper function for select and selectAll + */ + function getContainer(p) { + var container = document; + if (typeof p === 'string' && p[0] === '#'){ + p = p.slice(1); + container = document.getElementById(p) || document; + } else if (p instanceof p5.Element){ + container = p.elt; + } else if (p instanceof HTMLElement){ + container = p; + } + return container; + } + + /** + * Helper function for getElement and getElements. + */ + function wrapElement(elt) { + if(elt.tagName === "INPUT" && elt.type === "checkbox") { + var converted = new p5.Element(elt); + converted.checked = function(){ + if (arguments.length === 0){ + return this.elt.checked; + } else if(arguments[0]) { + this.elt.checked = true; + } else { + this.elt.checked = false; + } + return this; + }; + return converted; + } else if (elt.tagName === "VIDEO" || elt.tagName === "AUDIO") { + return new p5.MediaElement(elt); + } else { + return new p5.Element(elt); + } + } + + /** + * Removes all elements created by p5, except any canvas / graphics + * elements created by createCanvas or createGraphics. + * Event handlers are removed, and element is removed from the DOM. + * @method removeElements + * @example + * <div class='norender'><code> + * function setup() { + * createCanvas(100, 100); + * createDiv('this is some text'); + * createP('this is a paragraph'); + * } + * function mousePressed() { + * removeElements(); // this will remove the div and p, not canvas + * } + * </code></div> + * + */ + p5.prototype.removeElements = function (e) { + for (var i=0; i<this._elements.length; i++) { + if (!(this._elements[i].elt instanceof HTMLCanvasElement)) { + this._elements[i].remove(); + } + } + }; + + /** + * Helpers for create methods. + */ + function addElement(elt, pInst, media) { + var node = pInst._userNode ? pInst._userNode : document.body; + node.appendChild(elt); + var c = media ? new p5.MediaElement(elt) : new p5.Element(elt); + pInst._elements.push(c); + return c; + } + + /** + * Creates a <div></div> element in the DOM with given inner HTML. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createDiv + * @param {String} html inner HTML for element created + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var myDiv; + * function setup() { + * myDiv = createDiv('this is some text'); + * } + * </code></div> + */ + + /** + * Creates a <p></p> element in the DOM with given inner HTML. Used + * for paragraph length text. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createP + * @param {String} html inner HTML for element created + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var myP; + * function setup() { + * myP = createP('this is some text'); + * } + * </code></div> + */ + + /** + * Creates a <span></span> element in the DOM with given inner HTML. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createSpan + * @param {String} html inner HTML for element created + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var mySpan; + * function setup() { + * mySpan = createSpan('this is some text'); + * } + * </code></div> + */ + var tags = ['div', 'p', 'span']; + tags.forEach(function(tag) { + var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1); + p5.prototype[method] = function(html) { + var elt = document.createElement(tag); + elt.innerHTML = typeof html === undefined ? "" : html; + return addElement(elt, this); + } + }); + + /** + * Creates an <img /> element in the DOM with given src and + * alternate text. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createImg + * @param {String} src src path or url for image + * @param {String} [alt] alternate text to be used if image does not load + * @param {Function} [successCallback] callback to be called once image data is loaded + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var img; + * function setup() { + * img = createImg('http://p5js.org/img/asterisk-01.png'); + * } + * </code></div> + */ + p5.prototype.createImg = function() { + var elt = document.createElement('img'); + var args = arguments; + var self; + var setAttrs = function(){ + self.width = elt.offsetWidth; + self.height = elt.offsetHeight; + if (args.length > 1 && typeof args[1] === 'function'){ + self.fn = args[1]; + self.fn(); + }else if (args.length > 1 && typeof args[2] === 'function'){ + self.fn = args[2]; + self.fn(); + } + }; + elt.src = args[0]; + if (args.length > 1 && typeof args[1] === 'string'){ + elt.alt = args[1]; + } + elt.onload = function(){ + setAttrs(); + } + self = addElement(elt, this); + return self; + }; + + /** + * Creates an <a></a> element in the DOM for including a hyperlink. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createA + * @param {String} href url of page to link to + * @param {String} html inner html of link element to display + * @param {String} [target] target where new link should open, + * could be _blank, _self, _parent, _top. + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var myLink; + * function setup() { + * myLink = createA('http://p5js.org/', 'this is a link'); + * } + * </code></div> + */ + p5.prototype.createA = function(href, html, target) { + var elt = document.createElement('a'); + elt.href = href; + elt.innerHTML = html; + if (target) elt.target = target; + return addElement(elt, this); + }; + + /** INPUT **/ + + + /** + * Creates a slider <input></input> element in the DOM. + * Use .size() to set the display length of the slider. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createSlider + * @param {Number} min minimum value of the slider + * @param {Number} max maximum value of the slider + * @param {Number} [value] default value of the slider + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div><code> + * var slider; + * function setup() { + * slider = createSlider(0, 255, 100); + * slider.position(10, 10); + * slider.style('width', '80px'); + * } + * + * function draw() { + * var val = slider.value(); + * background(val); + * } + * </code></div> + */ + p5.prototype.createSlider = function(min, max, value, step) { + var elt = document.createElement('input'); + elt.type = 'range'; + elt.min = min; + elt.max = max; + if (step) elt.step = step; + if (typeof(value) === "number") elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates a <button></button> element in the DOM. + * Use .size() to set the display size of the button. + * Use .mousePressed() to specify behavior on press. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createButton + * @param {String} label label displayed on the button + * @param {String} [value] value of the button + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var button; + * function setup() { + * createCanvas(100, 100); + * background(0); + * button = createButton('click me'); + * button.position(19, 19); + * button.mousePressed(changeBG); + * } + * + * function changeBG() { + * var val = random(255); + * background(val); + * } + * </code></div> + */ + p5.prototype.createButton = function(label, value) { + var elt = document.createElement('button'); + elt.innerHTML = label; + elt.value = value; + if (value) elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates a checkbox <input></input> element in the DOM. + * Calling .checked() on a checkbox returns if it is checked or not + * + * @method createCheckbox + * @param {String} [label] label displayed after checkbox + * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false.Unchecked if no value given + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var checkbox; + * + * function setup() { + * checkbox = createCheckbox('label', false); + * checkbox.changed(myCheckedEvent); + * } + * + * function myCheckedEvent() { + * if (this.checked()) { + * console.log("Unchecking!"); + * } else { + * console.log("Checking!"); + * } + * + * </code></div> + */ + p5.prototype.createCheckbox = function() { + var elt = document.createElement('input'); + elt.type = 'checkbox'; + //checkbox must be wrapped in p5.Element before label so that label appears after + var self = addElement(elt, this); + self.checked = function(){ + if (arguments.length === 0){ + return self.elt.checked; + }else if(arguments[0]){ + self.elt.checked = true; + }else{ + self.elt.checked = false; + } + return self; + }; + this.value = function(val){ + self.value = val; + return this; + }; + if (arguments[0]){ + var ran = Math.random().toString(36).slice(2); + var label = document.createElement('label'); + elt.setAttribute('id', ran); + label.htmlFor = ran; + self.value(arguments[0]); + label.appendChild(document.createTextNode(arguments[0])); + addElement(label, this); + } + if (arguments[1]){ + elt.checked = true; + } + return self; + }; + + /** + * Creates a dropdown menu <select></select> element in the DOM. + * @method createSelect + * @param {boolean} [multiple] [true if dropdown should support multiple selections] + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div><code> + * var sel; + * + * function setup() { + * textAlign(CENTER); + * background(200); + * sel = createSelect(); + * sel.position(10, 10); + * sel.option('pear'); + * sel.option('kiwi'); + * sel.option('grape'); + * sel.changed(mySelectEvent); + * } + * + * function mySelectEvent() { + * var item = sel.value(); + * background(200); + * text("it's a "+item+"!", 50, 50); + * } + * </code></div> + */ + p5.prototype.createSelect = function(mult) { + var elt = document.createElement('select'); + if (mult){ + elt.setAttribute('multiple', 'true'); + } + var self = addElement(elt, this); + self.option = function(name, value){ + var opt = document.createElement('option'); + opt.innerHTML = name; + if (arguments.length > 1) + opt.value = value; + else + opt.value = name; + elt.appendChild(opt); + }; + self.selected = function(value){ + var arr = []; + if (arguments.length > 0){ + for (var i = 0; i < this.elt.length; i++){ + if (value.toString() === this.elt[i].value){ + this.elt.selectedIndex = i; + } + } + return this; + }else{ + if (mult){ + for (var i = 0; i < this.elt.selectedOptions.length; i++){ + arr.push(this.elt.selectedOptions[i].value); + } + return arr; + }else{ + return this.elt.value; + } + } + }; + return self; + }; + + /** + * Creates a radio button <input></input> element in the DOM. + * + * @method createRadio + * @param {String} [divId] the id and name of the created div and input field respectively + * @return {Object/p5.Element} pointer to p5.Element holding created node + */ + p5.prototype.createRadio = function() { + var radios = document.querySelectorAll("input[type=radio]"); + var count = 0; + if(radios.length > 1){ + console.log(radios,radios[0].name); + var length = radios.length; + var prev=radios[0].name; + var current = radios[1].name; + count=1; + for(var i = 1; i < length; i++ ){ + current = radios[i].name; + if(prev != current){ + count++; + } + prev = current; + } + } + else if (radios.length == 1){ + count = 1; + } + var elt = document.createElement('div'); + var self = addElement(elt, this); + var times = -1; + self.option = function(name, value){ + var opt = document.createElement('input'); + opt.type = 'radio'; + opt.innerHTML = name; + if (arguments.length > 1) + opt.value = value; + else + opt.value = name; + opt.setAttribute('name',"defaultradio"+count); + elt.appendChild(opt); + if (name){ + times++; + var ran = Math.random().toString(36).slice(2); + var label = document.createElement('label'); + opt.setAttribute('id', "defaultradio"+count+"-"+times); + label.htmlFor = "defaultradio"+count+"-"+times; + label.appendChild(document.createTextNode(name)); + elt.appendChild(label); + } + return opt; + }; + self.selected = function(){ + var length = this.elt.childNodes.length; + if(arguments[0]) { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].value == arguments[0]) + this.elt.childNodes[i].checked = true; + } + return this; + } else { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].checked == true) + return this.elt.childNodes[i].value; + } + } + }; + self.value = function(){ + var length = this.elt.childNodes.length; + if(arguments[0]) { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].value == arguments[0]) + this.elt.childNodes[i].checked = true; + } + return this; + } else { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].checked == true) + return this.elt.childNodes[i].value; + } + return ""; + } + }; + return self + }; + + /** + * Creates an <input></input> element in the DOM for text input. + * Use .size() to set the display length of the box. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createInput + * @param {Number} [value] default value of the input box + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * function setup(){ + * var inp = createInput(''); + * inp.input(myInputEvent); + * } + * + * function myInputEvent(){ + * console.log('you are typing: ', this.value()); + * } + * + * </code></div> + */ + p5.prototype.createInput = function(value) { + var elt = document.createElement('input'); + elt.type = 'text'; + if (value) elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates an <input></input> element in the DOM of type 'file'. + * This allows users to select local files for use in a sketch. + * + * @method createFileInput + * @param {Function} [callback] callback function for when a file loaded + * @param {String} [multiple] optional to allow multiple files selected + * @return {Object/p5.Element} pointer to p5.Element holding created DOM element + */ + p5.prototype.createFileInput = function(callback, multiple) { + + // Is the file stuff supported? + if (window.File && window.FileReader && window.FileList && window.Blob) { + // Yup, we're ok and make an input file selector + var elt = document.createElement('input'); + elt.type = 'file'; + + // If we get a second argument that evaluates to true + // then we are looking for multiple files + if (multiple) { + // Anything gets the job done + elt.multiple = 'multiple'; + } + + // Function to handle when a file is selected + // We're simplifying life and assuming that we always + // want to load every selected file + function handleFileSelect(evt) { + // These are the files + var files = evt.target.files; + // Load each one and trigger a callback + for (var i = 0; i < files.length; i++) { + var f = files[i]; + var reader = new FileReader(); + function makeLoader(theFile) { + // Making a p5.File object + var p5file = new p5.File(theFile); + return function(e) { + p5file.data = e.target.result; + callback(p5file); + }; + }; + reader.onload = makeLoader(f); + + // Text or data? + // This should likely be improved + if (f.type.indexOf('text') > -1) { + reader.readAsText(f); + } else { + reader.readAsDataURL(f); + } + } + } + + // Now let's handle when a file was selected + elt.addEventListener('change', handleFileSelect, false); + return addElement(elt, this); + } else { + console.log('The File APIs are not fully supported in this browser. Cannot create element.'); + } + }; + + + /** VIDEO STUFF **/ + + function createMedia(pInst, type, src, callback) { + var elt = document.createElement(type); + + // allow src to be empty + var src = src || ''; + if (typeof src === 'string') { + src = [src]; + } + for (var i=0; i<src.length; i++) { + var source = document.createElement('source'); + source.src = src[i]; + elt.appendChild(source); + } + if (typeof callback !== 'undefined') { + var callbackHandler = function() { + callback(); + elt.removeEventListener('canplaythrough', callbackHandler); + } + elt.addEventListener('canplaythrough', callbackHandler); + } + + var c = addElement(elt, pInst, true); + c.loadedmetadata = false; + // set width and height onload metadata + elt.addEventListener('loadedmetadata', function() { + c.width = elt.videoWidth; + c.height = elt.videoHeight; + c.loadedmetadata = true; + }); + + return c; + } + /** + * Creates an HTML5 <video> element in the DOM for simple playback + * of audio/video. Shown by default, can be hidden with .hide() + * and drawn into canvas using video(). Appends to the container + * node if one is specified, otherwise appends to body. The first parameter + * can be either a single string path to a video file, or an array of string + * paths to different formats of the same video. This is useful for ensuring + * that your video can play across different browsers, as each supports + * different formats. See <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats">this + * page</a> for further information about supported formats. + * + * @method createVideo + * @param {String|Array} src path to a video file, or array of paths for + * supporting different browsers + * @param {Object} [callback] callback function to be called upon + * 'canplaythrough' event fire, that is, when the + * browser can play the media, and estimates that + * enough data has been loaded to play the media + * up to its end without having to stop for + * further buffering of content + * @return {Object/p5.Element} pointer to video p5.Element + */ + p5.prototype.createVideo = function(src, callback) { + return createMedia(this, 'video', src, callback); + }; + + /** AUDIO STUFF **/ + + /** + * Creates a hidden HTML5 <audio> element in the DOM for simple audio + * playback. Appends to the container node if one is specified, + * otherwise appends to body. The first parameter + * can be either a single string path to a audio file, or an array of string + * paths to different formats of the same audio. This is useful for ensuring + * that your audio can play across different browsers, as each supports + * different formats. See <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats">this + * page for further information about supported formats</a>. + * + * @method createAudio + * @param {String|Array} src path to an audio file, or array of paths for + * supporting different browsers + * @param {Object} [callback] callback function to be called upon + * 'canplaythrough' event fire, that is, when the + * browser can play the media, and estimates that + * enough data has been loaded to play the media + * up to its end without having to stop for + * further buffering of content + * @return {Object/p5.Element} pointer to audio p5.Element + */ + p5.prototype.createAudio = function(src, callback) { + return createMedia(this, 'audio', src, callback); + }; + + + /** CAMERA STUFF **/ + + p5.prototype.VIDEO = 'video'; + p5.prototype.AUDIO = 'audio'; + + navigator.getUserMedia = navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + + /** + * Creates a new <video> element that contains the audio/video feed + * from a webcam. This can be drawn onto the canvas using video(). More + * specific properties of the stream can be passing in a Constraints object. + * See the + * <a href="http://w3c.github.io/mediacapture-main/getusermedia.html">W3C + * spec</a> for possible properties. Note that not all of these are supported + * by all browsers. + * + * @method createCapture + * @param {String|Constant|Object} type type of capture, either VIDEO or + * AUDIO if none specified, default both, + * or a Constraints object + * @param {Function} callback function to be called once + * stream has loaded + * @return {Object/p5.Element} capture video p5.Element + * @example + * <div class='norender'><code> + * var capture; + * + * function setup() { + * createCanvas(480, 120); + * capture = createCapture(VIDEO); + * } + * + * function draw() { + * image(capture, 0, 0, width, width*capture.height/capture.width); + * filter(INVERT); + * } + * </code></div> + * <div class='norender'><code> + * function setup() { + * createCanvas(480, 120); + * var constraints = { + * video: { + * mandatory: { + * minWidth: 1280, + * minHeight: 720 + * }, + * optional: [ + * { maxFrameRate: 10 } + * ] + * }, + * audio: true + * }; + * createCapture(constraints, function(stream) { + * console.log(stream); + * }); + * } + * </code></div> + */ + p5.prototype.createCapture = function() { + var useVideo = true; + var useAudio = true; + var constraints; + var cb; + for (var i=0; i<arguments.length; i++) { + if (arguments[i] === p5.prototype.VIDEO) { + useAudio = false; + } else if (arguments[i] === p5.prototype.AUDIO) { + useVideo = false; + } else if (typeof arguments[i] === 'object') { + constraints = arguments[i]; + } else if (typeof arguments[i] === 'function') { + cb = arguments[i]; + } + } + + if (navigator.getUserMedia) { + var elt = document.createElement('video'); + + if (!constraints) { + constraints = {video: useVideo, audio: useAudio}; + } + + navigator.getUserMedia(constraints, function(stream) { + elt.src = window.URL.createObjectURL(stream); + elt.onloadedmetadata = function(e) { + elt.play(); + if (cb) { + cb(stream); + } + }; + }, function(e) { console.log(e); }); + } else { + throw 'getUserMedia not supported in this browser'; + } + var c = addElement(elt, this, true); + c.loadedmetadata = false; + // set width and height onload metadata + elt.addEventListener('loadedmetadata', function() { + c.width = elt.videoWidth; + c.height = elt.videoHeight; + c.loadedmetadata = true; + }); + return c; + }; + + /** + * Creates element with given tag in the DOM with given content. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createElement + * @param {String} tag tag for the new element + * @param {String} [content] html content to be inserted into the element + * @return {Object/p5.Element} pointer to p5.Element holding created node + * @example + * <div class='norender'><code> + * var h2 = createElement('h2','im an h2 p5.element!'); + * </code></div> + */ + p5.prototype.createElement = function(tag, content) { + var elt = document.createElement(tag); + if (typeof content !== 'undefined') { + elt.innerHTML = content; + } + return addElement(elt, this); + }; + + +// ============================================================================= +// p5.Element additions +// ============================================================================= + /** + * + * Adds specified class to the element. + * + * @for p5.Element + * @method addClass + * @param {String} class name of class to add + * @return {Object/p5.Element} + * @example + * <div class='norender'><code> + * var div = createDiv('div'); + * div.addClass('myClass'); + * </code></div> + */ + p5.Element.prototype.addClass = function(c) { + if (this.elt.className) { + // PEND don't add class more than once + //var regex = new RegExp('[^a-zA-Z\d:]?'+c+'[^a-zA-Z\d:]?'); + //if (this.elt.className.search(/[^a-zA-Z\d:]?hi[^a-zA-Z\d:]?/) === -1) { + this.elt.className = this.elt.className+' '+c; + //} + } else { + this.elt.className = c; + } + return this; + } + + /** + * + * Removes specified class from the element. + * + * @method removeClass + * @param {String} class name of class to remove + * @return {Object/p5.Element} + */ + p5.Element.prototype.removeClass = function(c) { + var regex = new RegExp('(?:^|\\s)'+c+'(?!\\S)'); + this.elt.className = this.elt.className.replace(regex, ''); + this.elt.className = this.elt.className.replace(/^\s+|\s+$/g, ""); //prettify (optional) + return this; + } + + /** + * + * Attaches the element as a child to the parent specified. + * Accepts either a string ID, DOM node, or p5.Element. + * If no argument is specified, an array of children DOM nodes is returned. + * + * @method child + * @param {String|Object|p5.Element} [child] the ID, DOM node, or p5.Element + * to add to the current element + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * var div1 = createDiv('this is the child'); + * div0.child(div1); // use p5.Element + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * var div1 = createDiv('this is the child'); + * div1.id('apples'); + * div0.child('apples'); // use id + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * var elt = document.getElementById('myChildDiv'); + * div0.child(elt); // use element from page + * </code></div> + */ + p5.Element.prototype.child = function(c) { + if (c === null){ + return this.elt.childNodes + } + if (typeof c === 'string') { + if (c[0] === '#') { + c = c.substring(1); + } + c = document.getElementById(c); + } else if (c instanceof p5.Element) { + c = c.elt; + } + this.elt.appendChild(c); + return this; + }; + + /** + * Centers a p5 Element either vertically, horizontally, + * or both, relative to its parent or according to + * the body if the Element has no parent. If no argument is passed + * the Element is aligned both vertically and horizontally. + * + * @param {String} align passing 'vertical', 'horizontal' aligns element accordingly + * @return {Object/p5.Element} pointer to p5.Element + * @example + * <div><code> + * function setup() { + * var div = createDiv('').size(10,10); + * div.style('background-color','orange'); + * div.center(); + * + * } + * </code></div> + */ + p5.Element.prototype.center = function(align) { + var style = this.elt.style.display; + var hidden = this.elt.style.display === 'none'; + var parentHidden = this.parent().style.display === 'none'; + var pos = { x : this.elt.offsetLeft, y : this.elt.offsetTop }; + + if (hidden) this.show(); + + this.elt.style.display = 'block'; + this.position(0,0); + + if (parentHidden) this.parent().style.display = 'block'; + + var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth); + var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight); + var y = pos.y; + var x = pos.x; + + if (align === 'both' || align === undefined){ + this.position(wOffset/2, hOffset/2); + }else if (align === 'horizontal'){ + this.position(wOffset/2, y); + }else if (align === 'vertical'){ + this.position(x, hOffset/2); + } + + this.style('display', style); + + if (hidden) this.hide(); + + if (parentHidden) this.parent().style.display = 'none'; + + return this; + }; + + /** + * + * If an argument is given, sets the inner HTML of the element, + * replacing any existing html. If no arguments are given, returns + * the inner HTML of the element. + * + * @for p5.Element + * @method html + * @param {String} [html] the HTML to be placed inside the element + * @return {Object/p5.Element|String} + * @example + * <div class='norender'><code> + * var div = createDiv('').size(100,100); + * div.style('background-color','orange'); + * div.html('hi'); + * </code></div> + */ + p5.Element.prototype.html = function(html) { + if (typeof html !== 'undefined') { + this.elt.innerHTML = html; + return this; + } else { + return this.elt.innerHTML; + } + }; + + /** + * + * Sets the position of the element relative to (0, 0) of the + * window. Essentially, sets position:absolute and left and top + * properties of style. If no arguments given returns the x and y position + * of the element in an object. + * + * @method position + * @param {Number} [x] x-position relative to upper left of window + * @param {Number} [y] y-position relative to upper left of window + * @return {Object/p5.Element} + * @example + * <div><code class='norender'> + * function setup() { + * var cnv = createCanvas(100, 100); + * // positions canvas 50px to the right and 100px + * // below upper left corner of the window + * cnv.position(50, 100); + * } + * </code></div> + */ + p5.Element.prototype.position = function() { + if (arguments.length === 0){ + return { 'x' : this.elt.offsetLeft , 'y' : this.elt.offsetTop }; + }else{ + this.elt.style.position = 'absolute'; + this.elt.style.left = arguments[0]+'px'; + this.elt.style.top = arguments[1]+'px'; + this.x = arguments[0]; + this.y = arguments[1]; + return this; + } + }; + + /* Helper method called by p5.Element.style() */ + p5.Element.prototype._translate = function(){ + this.elt.style.position = 'absolute'; + // save out initial non-translate transform styling + var transform = ''; + if (this.elt.style.transform) { + transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); + transform = transform.replace(/translate[X-Z]?\(.*\)/g, ''); + } + if (arguments.length === 2) { + this.elt.style.transform = 'translate('+arguments[0]+'px, '+arguments[1]+'px)'; + } else if (arguments.length > 2) { + this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)'; + if (arguments.length === 3) { + this.elt.parentElement.style.perspective = '1000px'; + } else { + this.elt.parentElement.style.perspective = arguments[3]+'px'; + } + } + // add any extra transform styling back on end + this.elt.style.transform += transform; + return this; + }; + + /* Helper method called by p5.Element.style() */ + p5.Element.prototype._rotate = function(){ + // save out initial non-rotate transform styling + var transform = ''; + if (this.elt.style.transform) { + var transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); + transform = transform.replace(/rotate[X-Z]?\(.*\)/g, ''); + } + + if (arguments.length === 1){ + this.elt.style.transform = 'rotate('+arguments[0]+'deg)'; + }else if (arguments.length === 2){ + this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)'; + }else if (arguments.length === 3){ + this.elt.style.transform = 'rotateX('+arguments[0]+'deg)'; + this.elt.style.transform += 'rotateY('+arguments[1]+'deg)'; + this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)'; + } + // add remaining transform back on + this.elt.style.transform += transform; + return this; + }; + + /** + * Sets the given style (css) property (1st arg) of the element with the + * given value (2nd arg). If a single argument is given, .style() + * returns the value of the given property; however, if the single argument + * is given in css syntax ('text-align:center'), .style() sets the css + * appropriatly. .style() also handles 2d and 3d css transforms. If + * the 1st arg is 'rotate', 'translate', or 'position', the following arguments + * accept Numbers as values. ('translate', 10, 100, 50); + * + * @method style + * @param {String} property property to be set + * @param {String|Number|p5.Color} [value] value to assign to property + * @param {String|Number} [value] value to assign to property (rotate/translate) + * @param {String|Number} [value] value to assign to property (rotate/translate) + * @param {String|Number} [value] value to assign to property (translate) + * @return {String|Object/p5.Element} value of property, if no value is specified + * or p5.Element + * @example + * <div><code class="norender"> + * var myDiv = createDiv("I like pandas."); + * myDiv.style("font-size", "18px"); + * myDiv.style("color", "#ff0000"); + * </code></div> + * <div><code class="norender"> + * var col = color(25,23,200,50); + * var button = createButton("button"); + * button.style("background-color", col); + * button.position(10, 10); + * </code></div> + * <div><code class="norender"> + * var myDiv = createDiv("I like lizards."); + * myDiv.style("position", 20, 20); + * myDiv.style("rotate", 45); + * </code></div> + * <div><code class="norender"> + * var myDiv; + * function setup() { + * background(200); + * myDiv = createDiv("I like gray."); + * myDiv.position(20, 20); + * } + * + * function draw() { + * myDiv.style("font-size", mouseX+"px"); + * } + * </code></div> + */ + p5.Element.prototype.style = function(prop, val) { + var self = this; + + if (val instanceof p5.Color) { + val = 'rgba(' + val.levels[0] + ',' + val.levels[1] + ',' + val.levels[2] + ',' + val.levels[3]/255 + ')' + } + + if (typeof val === 'undefined') { + if (prop.indexOf(':') === -1) { + var styles = window.getComputedStyle(self.elt); + var style = styles.getPropertyValue(prop); + return style; + } else { + var attrs = prop.split(';'); + for (var i = 0; i < attrs.length; i++) { + var parts = attrs[i].split(':'); + if (parts[0] && parts[1]) { + this.elt.style[parts[0].trim()] = parts[1].trim(); + } + } + } + } else { + if (prop === 'rotate' || prop === 'translate' || prop === 'position'){ + var trans = Array.prototype.shift.apply(arguments); + var f = this[trans] || this['_'+trans]; + f.apply(this, arguments); + } else { + this.elt.style[prop] = val; + if (prop === 'width' || prop === 'height' || prop === 'left' || prop === 'top') { + var numVal = val.replace(/\D+/g, ''); + this[prop] = parseInt(numVal, 10); // pend: is this necessary? + } + } + } + return this; + }; + + + /** + * + * Adds a new attribute or changes the value of an existing attribute + * on the specified element. If no value is specified, returns the + * value of the given attribute, or null if attribute is not set. + * + * @method attribute + * @param {String} attr attribute to set + * @param {String} [value] value to assign to attribute + * @return {String|Object/p5.Element} value of attribute, if no value is + * specified or p5.Element + * @example + * <div class="norender"><code> + * var myDiv = createDiv("I like pandas."); + * myDiv.attribute("align", "center"); + * </code></div> + */ + p5.Element.prototype.attribute = function(attr, value) { + if (typeof value === 'undefined') { + return this.elt.getAttribute(attr); + } else { + this.elt.setAttribute(attr, value); + return this; + } + }; + + + /** + * Either returns the value of the element if no arguments + * given, or sets the value of the element. + * + * @method value + * @param {String|Number} [value] + * @return {String|Object/p5.Element} value of element if no value is specified or p5.Element + * @example + * <div class='norender'><code> + * // gets the value + * var inp; + * function setup() { + * inp = createInput(''); + * } + * + * function mousePressed() { + * print(inp.value()); + * } + * </code></div> + * <div class='norender'><code> + * // sets the value + * var inp; + * function setup() { + * inp = createInput('myValue'); + * } + * + * function mousePressed() { + * inp.value("myValue"); + * } + * </code></div> + */ + p5.Element.prototype.value = function() { + if (arguments.length > 0) { + this.elt.value = arguments[0]; + return this; + } else { + if (this.elt.type === 'range') { + return parseFloat(this.elt.value); + } + else return this.elt.value; + } + }; + + /** + * + * Shows the current element. Essentially, setting display:block for the style. + * + * @method show + * @return {Object/p5.Element} + * @example + * <div class='norender'><code> + * var div = createDiv('div'); + * div.attribute("display", "none"); + * div.show(); // turns display to block + * </code></div> + */ + p5.Element.prototype.show = function() { + this.elt.style.display = 'block'; + return this; + }; + + /** + * Hides the current element. Essentially, setting display:none for the style. + * + * @method hide + * @return {Object/p5.Element} + * @example + * <div class='norender'><code> + * var div = createDiv('this is a div'); + * div.hide(); + * </code></div> + */ + p5.Element.prototype.hide = function() { + this.elt.style.display = 'none'; + return this; + }; + + /** + * + * Sets the width and height of the element. AUTO can be used to + * only adjust one dimension. If no arguments given returns the width and height + * of the element in an object. + * + * @method size + * @param {Number} [w] width of the element + * @param {Number} [h] height of the element + * @return {Object/p5.Element} + * @example + * <div class='norender'><code> + * var div = createDiv('this is a div'); + * div.size(100, 100); + * </code></div> + */ + p5.Element.prototype.size = function(w, h) { + if (arguments.length === 0){ + return { 'width' : this.elt.offsetWidth , 'height' : this.elt.offsetHeight }; + }else{ + var aW = w; + var aH = h; + var AUTO = p5.prototype.AUTO; + if (aW !== AUTO || aH !== AUTO) { + if (aW === AUTO) { + aW = h * this.width / this.height; + } else if (aH === AUTO) { + aH = w * this.height / this.width; + } + // set diff for cnv vs normal div + if (this.elt instanceof HTMLCanvasElement) { + var j = {}; + var k = this.elt.getContext('2d'); + for (var prop in k) { + j[prop] = k[prop]; + } + this.elt.setAttribute('width', aW * this._pInst._pixelDensity); + this.elt.setAttribute('height', aH * this._pInst._pixelDensity); + this.elt.setAttribute('style', 'width:' + aW + 'px; height:' + aH + 'px'); + this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity); + for (var prop in j) { + this.elt.getContext('2d')[prop] = j[prop]; + } + } else { + this.elt.style.width = aW+'px'; + this.elt.style.height = aH+'px'; + this.elt.width = aW; + this.elt.height = aH; + this.width = aW; + this.height = aH; + } + + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; + + if (this._pInst) { // main canvas associated with p5 instance + if (this._pInst._curElement.elt === this.elt) { + this._pInst._setProperty('width', this.elt.offsetWidth); + this._pInst._setProperty('height', this.elt.offsetHeight); + } + } + } + return this; + } + }; + + /** + * Removes the element and deregisters all listeners. + * @method remove + * @example + * <div class='norender'><code> + * var myDiv = createDiv('this is some text'); + * myDiv.remove(); + * </code></div> + */ + p5.Element.prototype.remove = function() { + // deregister events + for (var ev in this._events) { + this.elt.removeEventListener(ev, this._events[ev]); + } + if (this.elt.parentNode) { + this.elt.parentNode.removeChild(this.elt); + } + delete(this); + }; + + + +// ============================================================================= +// p5.MediaElement additions +// ============================================================================= + + + /** + * Extends p5.Element to handle audio and video. In addition to the methods + * of p5.Element, it also contains methods for controlling media. It is not + * called directly, but p5.MediaElements are created by calling createVideo, + * createAudio, and createCapture. + * + * @class p5.MediaElement + * @constructor + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + */ + p5.MediaElement = function(elt, pInst) { + p5.Element.call(this, elt, pInst); + + var self = this; + this.elt.crossOrigin = 'anonymous'; + + this._prevTime = 0; + this._cueIDCounter = 0; + this._cues = []; + this._pixelDensity = 1; + + /** + * Path to the media element source. + * + * @property src + * @return {String} src + */ + Object.defineProperty(self, 'src', { + get: function() { + var firstChildSrc = self.elt.children[0].src; + var srcVal = self.elt.src === window.location.href ? '' : self.elt.src; + var ret = firstChildSrc === window.location.href ? srcVal : firstChildSrc; + return ret; + }, + set: function(newValue) { + for (var i = 0; i < self.elt.children.length; i++) { + self.elt.removeChild(self.elt.children[i]); + } + var source = document.createElement('source'); + source.src = newValue; + elt.appendChild(source); + self.elt.src = newValue; + }, + }); + + // private _onended callback, set by the method: onended(callback) + self._onended = function() {}; + self.elt.onended = function() { + self._onended(self); + } + }; + p5.MediaElement.prototype = Object.create(p5.Element.prototype); + + + + + /** + * Play an HTML5 media element. + * + * @method play + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.play = function() { + if (this.elt.currentTime === this.elt.duration) { + this.elt.currentTime = 0; + } + + if (this.elt.readyState > 1) { + this.elt.play(); + } else { + // in Chrome, playback cannot resume after being stopped and must reload + this.elt.load(); + this.elt.play(); + } + return this; + }; + + /** + * Stops an HTML5 media element (sets current time to zero). + * + * @method stop + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.stop = function() { + this.elt.pause(); + this.elt.currentTime = 0; + return this; + }; + + /** + * Pauses an HTML5 media element. + * + * @method pause + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.pause = function() { + this.elt.pause(); + return this; + }; + + /** + * Set 'loop' to true for an HTML5 media element, and starts playing. + * + * @method loop + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.loop = function() { + this.elt.setAttribute('loop', true); + this.play(); + return this; + }; + /** + * Set 'loop' to false for an HTML5 media element. Element will stop + * when it reaches the end. + * + * @method noLoop + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.noLoop = function() { + this.elt.setAttribute('loop', false); + return this; + }; + + + /** + * Set HTML5 media element to autoplay or not. + * + * @method autoplay + * @param {Boolean} autoplay whether the element should autoplay + * @return {Object/p5.Element} + */ + p5.MediaElement.prototype.autoplay = function(val) { + this.elt.setAttribute('autoplay', val); + return this; + }; + + /** + * Sets volume for this HTML5 media element. If no argument is given, + * returns the current volume. + * + * @param {Number} [val] volume between 0.0 and 1.0 + * @return {Number|p5.MediaElement} current volume or p5.MediaElement + * @method volume + */ + p5.MediaElement.prototype.volume = function(val) { + if (typeof val === 'undefined') { + return this.elt.volume; + } else { + this.elt.volume = val; + } + }; + + /** + * If no arguments are given, returns the current playback speed of the + * element. The speed parameter sets the speed where 2.0 will play the + * element twice as fast, 0.5 will play at half the speed, and -1 will play + * the element in normal speed in reverse.(Note that not all browsers support + * backward playback and even if they do, playback might not be smooth.) + * + * @method speed + * @param {Number} [speed] speed multiplier for element playback + * @return {Number|Object/p5.MediaElement} current playback speed or p5.MediaElement + */ + p5.MediaElement.prototype.speed = function(val) { + if (typeof val === 'undefined') { + return this.elt.playbackRate; + } else { + this.elt.playbackRate = val; + } + }; + + /** + * If no arguments are given, returns the current time of the element. + * If an argument is given the current time of the element is set to it. + * + * @method time + * @param {Number} [time] time to jump to (in seconds) + * @return {Number|Object/p5.MediaElement} current time (in seconds) + * or p5.MediaElement + */ + p5.MediaElement.prototype.time = function(val) { + if (typeof val === 'undefined') { + return this.elt.currentTime; + } else { + this.elt.currentTime = val; + } + }; + + /** + * Returns the duration of the HTML5 media element. + * + * @method duration + * @return {Number} duration + */ + p5.MediaElement.prototype.duration = function() { + return this.elt.duration; + }; + p5.MediaElement.prototype.pixels = []; + p5.MediaElement.prototype.loadPixels = function() { + if (!this.canvas) { + this.canvas = document.createElement('canvas'); + this.drawingContext = this.canvas.getContext('2d'); + } + if (this.loadedmetadata) { // wait for metadata for w/h + if (this.canvas.width !== this.elt.videoWidth) { + this.canvas.width = this.elt.videoWidth; + this.canvas.height = this.elt.videoHeight; + this.width = this.canvas.width; + this.height = this.canvas.height; + } + this.drawingContext.drawImage(this.elt, 0, 0, this.canvas.width, this.canvas.height); + p5.Renderer2D.prototype.loadPixels.call(this); + } + return this; + } + p5.MediaElement.prototype.updatePixels = function(x, y, w, h){ + if (this.loadedmetadata) { // wait for metadata + p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); + } + return this; + } + p5.MediaElement.prototype.get = function(x, y, w, h){ + if (this.loadedmetadata) { // wait for metadata + return p5.Renderer2D.prototype.get.call(this, x, y, w, h); + } else return [0, 0, 0, 255]; + }; + p5.MediaElement.prototype.set = function(x, y, imgOrCol){ + if (this.loadedmetadata) { // wait for metadata + p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); + } + }; + /** + * Schedule an event to be called when the audio or video + * element reaches the end. If the element is looping, + * this will not be called. The element is passed in + * as the argument to the onended callback. + * + * @method onended + * @param {Function} callback function to call when the + * soundfile has ended. The + * media element will be passed + * in as the argument to the + * callback. + * @return {Object/p5.MediaElement} + * @example + * <div><code> + * function setup() { + * audioEl = createAudio('assets/beat.mp3'); + * audioEl.showControls(true); + * audioEl.onended(sayDone); + * } + * + * function sayDone(elt) { + * alert('done playing ' + elt.src ); + * } + * </code></div> + */ + p5.MediaElement.prototype.onended = function(callback) { + this._onended = callback; + return this; + }; + + + /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/ + + /** + * Send the audio output of this element to a specified audioNode or + * p5.sound object. If no element is provided, connects to p5's master + * output. That connection is established when this method is first called. + * All connections are removed by the .disconnect() method. + * + * This method is meant to be used with the p5.sound.js addon library. + * + * @method connect + * @param {AudioNode|p5.sound object} audioNode AudioNode from the Web Audio API, + * or an object from the p5.sound library + */ + p5.MediaElement.prototype.connect = function(obj) { + var audioContext, masterOutput; + + // if p5.sound exists, same audio context + if (typeof p5.prototype.getAudioContext === 'function') { + audioContext = p5.prototype.getAudioContext(); + masterOutput = p5.soundOut.input; + } else { + try { + audioContext = obj.context; + masterOutput = audioContext.destination + } catch(e) { + throw 'connect() is meant to be used with Web Audio API or p5.sound.js' + } + } + + // create a Web Audio MediaElementAudioSourceNode if none already exists + if (!this.audioSourceNode) { + this.audioSourceNode = audioContext.createMediaElementSource(this.elt); + + // connect to master output when this method is first called + this.audioSourceNode.connect(masterOutput); + } + + // connect to object if provided + if (obj) { + if (obj.input) { + this.audioSourceNode.connect(obj.input); + } else { + this.audioSourceNode.connect(obj); + } + } + + // otherwise connect to master output of p5.sound / AudioContext + else { + this.audioSourceNode.connect(masterOutput); + } + + }; + + /** + * Disconnect all Web Audio routing, including to master output. + * This is useful if you want to re-route the output through + * audio effects, for example. + * + * @method disconnect + */ + p5.MediaElement.prototype.disconnect = function() { + if (this.audioSourceNode) { + this.audioSourceNode.disconnect(); + } else { + throw 'nothing to disconnect'; + } + }; + + + /*** SHOW / HIDE CONTROLS ***/ + + /** + * Show the default MediaElement controls, as determined by the web browser. + * + * @method showControls + */ + p5.MediaElement.prototype.showControls = function() { + // must set style for the element to show on the page + this.elt.style['text-align'] = 'inherit'; + this.elt.controls = true; + }; + + /** + * Hide the default mediaElement controls. + * + * @method hideControls + */ + p5.MediaElement.prototype.hideControls = function() { + this.elt.controls = false; + }; + + /*** SCHEDULE EVENTS ***/ + + /** + * Schedule events to trigger every time a MediaElement + * (audio/video) reaches a playback cue point. + * + * Accepts a callback function, a time (in seconds) at which to trigger + * the callback, and an optional parameter for the callback. + * + * Time will be passed as the first parameter to the callback function, + * and param will be the second parameter. + * + * + * @method addCue + * @param {Number} time Time in seconds, relative to this media + * element's playback. For example, to trigger + * an event every time playback reaches two + * seconds, pass in the number 2. This will be + * passed as the first parameter to + * the callback function. + * @param {Function} callback Name of a function that will be + * called at the given time. The callback will + * receive time and (optionally) param as its + * two parameters. + * @param {Object} [value] An object to be passed as the + * second parameter to the + * callback function. + * @return {Number} id ID of this cue, + * useful for removeCue(id) + * @example + * <div><code> + * function setup() { + * background(255,255,255); + * + * audioEl = createAudio('assets/beat.mp3'); + * audioEl.showControls(); + * + * // schedule three calls to changeBackground + * audioEl.addCue(0.5, changeBackground, color(255,0,0) ); + * audioEl.addCue(1.0, changeBackground, color(0,255,0) ); + * audioEl.addCue(2.5, changeBackground, color(0,0,255) ); + * audioEl.addCue(3.0, changeBackground, color(0,255,255) ); + * audioEl.addCue(4.2, changeBackground, color(255,255,0) ); + * audioEl.addCue(5.0, changeBackground, color(255,255,0) ); + * } + * + * function changeBackground(val) { + * background(val); + * } + * </code></div> + */ + p5.MediaElement.prototype.addCue = function(time, callback, val) { + var id = this._cueIDCounter++; + + var cue = new Cue(callback, time, id, val); + this._cues.push(cue); + + if (!this.elt.ontimeupdate) { + this.elt.ontimeupdate = this._onTimeUpdate.bind(this); + } + + return id; + }; + + /** + * Remove a callback based on its ID. The ID is returned by the + * addCue method. + * + * @method removeCue + * @param {Number} id ID of the cue, as returned by addCue + */ + p5.MediaElement.prototype.removeCue = function(id) { + for (var i = 0; i < this._cues.length; i++) { + var cue = this._cues[i]; + if (cue.id === id) { + this.cues.splice(i, 1); + } + } + + if (this._cues.length === 0) { + this.elt.ontimeupdate = null + } + }; + + /** + * Remove all of the callbacks that had originally been scheduled + * via the addCue method. + * + * @method clearCues + */ + p5.MediaElement.prototype.clearCues = function() { + this._cues = []; + this.elt.ontimeupdate = null; + }; + + // private method that checks for cues to be fired if events + // have been scheduled using addCue(callback, time). + p5.MediaElement.prototype._onTimeUpdate = function() { + var playbackTime = this.time(); + + for (var i = 0 ; i < this._cues.length; i++) { + var callbackTime = this._cues[i].time; + var val = this._cues[i].val; + + + if (this._prevTime < callbackTime && callbackTime <= playbackTime) { + + // pass the scheduled callbackTime as parameter to the callback + this._cues[i].callback(val); + } + + } + + this._prevTime = playbackTime; + }; + + + // Cue inspired by JavaScript setTimeout, and the + // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org + var Cue = function(callback, time, id, val) { + this.callback = callback; + this.time = time; + this.id = id; + this.val = val; + }; + +// ============================================================================= +// p5.File +// ============================================================================= + + + /** + * Base class for a file + * Using this for createFileInput + * + * @class p5.File + * @constructor + * @param {File} file File that is wrapped + * @param {Object} [pInst] pointer to p5 instance + */ + p5.File = function(file, pInst) { + /** + * Underlying File object. All normal File methods can be called on this. + * + * @property file + */ + this.file = file; + + this._pInst = pInst; + + // Splitting out the file type into two components + // This makes determining if image or text etc simpler + var typeList = file.type.split('/'); + /** + * File type (image, text, etc.) + * + * @property type + */ + this.type = typeList[0]; + /** + * File subtype (usually the file extension jpg, png, xml, etc.) + * + * @property subtype + */ + this.subtype = typeList[1]; + /** + * File name + * + * @property name + */ + this.name = file.name; + /** + * File size + * + * @property size + */ + this.size = file.size; + + /** + * URL string containing image data. + * + * @property data + */ + this.data = undefined; + }; + +})); diff --git a/examples/p5js/libraries/p5.js b/examples/p5js/libraries/p5.js new file mode 100644 index 0000000000000000000000000000000000000000..43d7136f8cbe364d0ec9e256f2971bf19d12e774 --- /dev/null +++ b/examples/p5js/libraries/p5.js @@ -0,0 +1,29765 @@ +/*! p5.js v0.4.23 March 04, 2016 */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.p5 = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ + +},{}],2:[function(_dereq_,module,exports){ +// Run-time checking of preconditions. + +'use strict'; + +// Precondition function that checks if the given predicate is true. +// If not, it will throw an error. +exports.argument = function(predicate, message) { + if (!predicate) { + throw new Error(message); + } +}; + +// Precondition function that checks if the given assertion is true. +// If not, it will throw an error. +exports.assert = exports.argument; + +},{}],3:[function(_dereq_,module,exports){ +// Drawing utility functions. + +'use strict'; + +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +exports.line = line; + +},{}],4:[function(_dereq_,module,exports){ +// Glyph encoding + +'use strict'; + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +// This is the encoding used for fonts created from scratch. +// It loops through all glyphs and finds the appropriate unicode value. +// Since it's linear time, other encodings will be faster. +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.charCodeAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } else { + return null; + } +}; + +function CmapEncoding(cmap) { + this.cmap = cmap; +} + +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.charCodeAt(0)] || 0; +}; + +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.charCodeAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +function GlyphNames(post) { + var i; + switch (post.version) { + case 1: + this.names = exports.standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < exports.standardNames.length) { + this.names[i] = exports.standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - exports.standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + this.names[i] = exports.standardNames[i + post.glyphNameIndex[i]]; + } + + break; + case 3: + this.names = []; + break; + } +} + +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNames(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (i = 0; i < font.glyphs.length; i += 1) { + glyph = font.glyphs.get(i); + if (font.cffEncoding) { + glyph.name = font.cffEncoding.charset[i]; + } else { + glyph.name = font.glyphNames.glyphIndexToName(i); + } + } +} + +exports.cffStandardStrings = cffStandardStrings; +exports.cffStandardEncoding = cffStandardEncoding; +exports.cffExpertEncoding = cffExpertEncoding; +exports.standardNames = standardNames; +exports.DefaultEncoding = DefaultEncoding; +exports.CmapEncoding = CmapEncoding; +exports.CffEncoding = CffEncoding; +exports.GlyphNames = GlyphNames; +exports.addGlyphNames = addGlyphNames; + +},{}],5:[function(_dereq_,module,exports){ +// The Font object + +'use strict'; + +var path = _dereq_('./path'); +var sfnt = _dereq_('./tables/sfnt'); +var encoding = _dereq_('./encoding'); +var glyphset = _dereq_('./glyphset'); + +// A Font represents a loaded OpenType font file. +// It contains a set of glyphs and methods to draw text on a drawing context, +// or to get a path representing the text. +function Font(options) { + options = options || {}; + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.familyName = options.familyName || ' '; + this.styleName = options.styleName || ' '; + this.designer = options.designer || ' '; + this.designerURL = options.designerURL || ' '; + this.manufacturer = options.manufacturer || ' '; + this.manufacturerURL = options.manufacturerURL || ' '; + this.license = options.license || ' '; + this.licenseURL = options.licenseURL || ' '; + this.version = options.version || 'Version 0.1'; + this.description = options.description || ' '; + this.copyright = options.copyright || ' '; + this.trademark = options.trademark || ' '; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.supported = true; + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new encoding.DefaultEncoding(this); + this.tables = {}; +} + +// Check if the font has a glyph for the given character. +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; + +// Convert the given character to a single glyph index. +// Note that this function assumes that there is a one-to-one mapping between +// the given character and a glyph; for complex scripts this might not be the case. +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; + +// Convert the given character to a single Glyph object. +// Note that this function assumes that there is a one-to-one mapping between +// the given character and a glyph; for complex scripts this might not be the case. +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +// Convert the given text to a list of Glyph objects. +// Note that there is no strict one-to-one mapping between characters and +// glyphs, so the list of returned glyphs can be larger or smaller than the +// length of the given string. +Font.prototype.stringToGlyphs = function(s) { + var glyphs = []; + for (var i = 0; i < s.length; i += 1) { + var c = s[i]; + glyphs.push(this.charToGlyph(c)); + } + + return glyphs; +}; + +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; + +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nametoGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); +}; + +// Retrieve the value of the kerning pair between the left glyph (or its index) +// and the right glyph (or its index). If no kerning pair is found, return 0. +// The kerning value gets added to the advance width when calculating the spacing +// between glyphs. +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.getGposKerningValue; + return gposKerning ? gposKerning(leftGlyph, rightGlyph) : + (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); +}; + +// Helper function that invokes the given callback for each glyph in the given text. +// The callback gets `(glyph, x, y, fontSize, options)`. +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + if (!this.supported) { + return; + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = options || {}; + var kerning = options.kerning === undefined ? true : options.kerning; + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback(glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (kerning && i < glyphs.length - 1) { + var kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + } +}; + +// Create a Path object that represents the given text. +// +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +// +// Returns a Path object. +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new path.Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize); + fullPath.extend(glyphPath); + }); + + return fullPath; +}; + +// Draw the text on the given drawing context. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; + +// Draw the points of all glyphs in the text. +// On-curve points will be drawn in blue, off-curve points will be drawn in red. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; + +// Draw lines indicating important font measurements for all glyphs in the text. +// Black lines indicate the origin of the coordinate system (point 0,0). +// Blue lines indicate the glyph bounding box. +// Green line indicates the advance width of the glyph. +// +// ctx - A 2D drawing context, like Canvas. +// text - The text to create. +// x - Horizontal position of the beginning of the text. (default: 0) +// y - Vertical position of the *baseline* of the text. (default: 0) +// fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) +// Options is an optional object that contains: +// - kerning - Whether to take kerning information into account. (default: true) +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; + +// Validate +Font.prototype.validate = function() { + var warnings = []; + var _this = this; + + function assert(predicate, message) { + if (!predicate) { + warnings.push(message); + } + } + + function assertStringAttribute(attrName) { + assert(_this[attrName] && _this[attrName].trim().length > 0, 'No ' + attrName + ' specified.'); + } + + // Identification information + assertStringAttribute('familyName'); + assertStringAttribute('weightName'); + assertStringAttribute('manufacturer'); + assertStringAttribute('copyright'); + assertStringAttribute('version'); + + // Dimension information + assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); +}; + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; + +Font.prototype.toBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; +}; + +// Initiate a download of the OpenType font. +Font.prototype.download = function() { + var fileName = this.familyName.replace(/\s/g, '') + '-' + this.styleName + '.otf'; + var buffer = this.toBuffer(); + + window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + window.requestFileSystem(window.TEMPORARY, buffer.byteLength, function(fs) { + fs.root.getFile(fileName, {create: true}, function(fileEntry) { + fileEntry.createWriter(function(writer) { + var dataView = new DataView(buffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + writer.write(blob); + + writer.addEventListener('writeend', function() { + // Navigating to the file will download it. + location.href = fileEntry.toURL(); + }, false); + }); + }); + }, + + function(err) { + throw err; + }); +}; + +exports.Font = Font; + +},{"./encoding":4,"./glyphset":7,"./path":10,"./tables/sfnt":25}],6:[function(_dereq_,module,exports){ +// The Glyph object + +'use strict'; + +var check = _dereq_('./check'); +var draw = _dereq_('./draw'); +var path = _dereq_('./path'); + +function getPathDefinition(glyph, path) { + var _path = path || { commands: [] }; + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; +} + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannnot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if (options.xMin) { + this.xMin = options.xMin; + } + + if (options.yMin) { + this.yMin = options.yMin; + } + + if (options.xMax) { + this.xMax = options.xMax; + } + + if (options.yMax) { + this.yMax = options.yMax; + } + + if (options.advanceWidth) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; + +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); +}; + +// Convert the glyph to a Path we can draw on a drawing context. +// +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.getPath = function(x, y, fontSize) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var scale = 1 / this.path.unitsPerEm * fontSize; + var p = new path.Path(); + var commands = this.path.commands; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), + x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), + x + (cmd.x2 * scale), y + (-cmd.y2 * scale), + x + (cmd.x * scale), y + (-cmd.y * scale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; +}; + +// Split the glyph into contours. +// This function is here for backwards compatibility, and to +// provide raw access to the TrueType glyph outlines. +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +// Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: 0 + }; + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; + +// Draw the glyph on the given context. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.draw = function(ctx, x, y, fontSize) { + this.getPath(x, y, fontSize).draw(ctx); +}; + +// Draw the points of the glyph. +// On-curve points will be drawn in blue, off-curve points will be drawn in red. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + + function drawCircles(l, x, y, scale) { + var PI_SQ = Math.PI * 2; + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, PI_SQ, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; + +// Draw lines indicating important font measurements. +// Black lines indicate the origin of the coordinate system (point 0,0). +// Blue lines indicate the glyph bounding box. +// Green line indicates the advance width of the glyph. +// +// ctx - The drawing context. +// x - Horizontal position of the glyph. (default: 0) +// y - Vertical position of the *baseline* of the glyph. (default: 0) +// fontSize - Font size, in pixels (default: 72). +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; + +exports.Glyph = Glyph; + +},{"./check":2,"./draw":3,"./path":10}],7:[function(_dereq_,module,exports){ +// The GlyphSet object + +'use strict'; + +var _glyph = _dereq_('./glyph'); + +// A GlyphSet represents all glyphs available in the font, but modelled using +// a deferred glyph loader, for retrieving glyphs only once they are absolutely +// necessary, to keep the memory footprint down. +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + this.glyphs[i] = glyphs[i]; + } + } + + this.length = (glyphs && glyphs.length) || 0; +} + +GlyphSet.prototype.get = function(index) { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + return this.glyphs[index]; +}; + +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; + +function glyphLoader(font, index) { + return new _glyph.Glyph({index: index, font: font}); +} + +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + */ + +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new _glyph.Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new _glyph.Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +exports.GlyphSet = GlyphSet; +exports.glyphLoader = glyphLoader; +exports.ttfGlyphLoader = ttfGlyphLoader; +exports.cffGlyphLoader = cffGlyphLoader; + +},{"./glyph":6}],8:[function(_dereq_,module,exports){ +// opentype.js +// https://github.com/nodebox/opentype.js +// (c) 2015 Frederik De Bleser +// opentype.js may be freely distributed under the MIT license. + +/* global ArrayBuffer, DataView, Uint8Array, XMLHttpRequest */ + +'use strict'; + +var encoding = _dereq_('./encoding'); +var _font = _dereq_('./font'); +var glyph = _dereq_('./glyph'); +var parse = _dereq_('./parse'); +var path = _dereq_('./path'); + +var cmap = _dereq_('./tables/cmap'); +var cff = _dereq_('./tables/cff'); +var glyf = _dereq_('./tables/glyf'); +var gpos = _dereq_('./tables/gpos'); +var head = _dereq_('./tables/head'); +var hhea = _dereq_('./tables/hhea'); +var hmtx = _dereq_('./tables/hmtx'); +var kern = _dereq_('./tables/kern'); +var loca = _dereq_('./tables/loca'); +var maxp = _dereq_('./tables/maxp'); +var _name = _dereq_('./tables/name'); +var os2 = _dereq_('./tables/os2'); +var post = _dereq_('./tables/post'); + +// File loaders ///////////////////////////////////////////////////////// + +// Convert a Node.js Buffer to an ArrayBuffer +function toArrayBuffer(buffer) { + var arrayBuffer = new ArrayBuffer(buffer.length); + var data = new Uint8Array(arrayBuffer); + for (var i = 0; i < buffer.length; i += 1) { + data[i] = buffer[i]; + } + + return arrayBuffer; +} + +function loadFromFile(path, callback) { + var fs = _dereq_('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, toArrayBuffer(buffer)); + }); +} + +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.status !== 200) { + return callback('Font could not be loaded: ' + request.statusText); + } + + return callback(null, request.response); + }; + + request.send(); +} + +// Public API /////////////////////////////////////////////////////////// + +// Parse the OpenType file data (as an ArrayBuffer) and return a Font object. +// If the file could not be parsed (most likely because it contains Postscript outlines) +// we return an empty Font object with the `supported` flag set to `false`. +function parseBuffer(buffer) { + var indexToLocFormat; + var hmtxOffset; + var glyfOffset; + var locaOffset; + var cffOffset; + var kernOffset; + var gposOffset; + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + + var font = new _font.Font(); + var data = new DataView(buffer, 0); + + var version = parse.getFixed(data, 0); + if (version === 1.0) { + font.outlinesFormat = 'truetype'; + } else { + version = parse.getTag(data, 0); + if (version === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType version ' + version); + } + } + + var numTables = parse.getUShort(data, 4); + + // Offset into the table records. + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 8); + switch (tag) { + case 'cmap': + font.tables.cmap = cmap.parse(data, offset); + font.encoding = new encoding.CmapEncoding(font.tables.cmap); + if (!font.encoding) { + font.supported = false; + } + + break; + case 'head': + font.tables.head = head.parse(data, offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + font.tables.hhea = hhea.parse(data, offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxOffset = offset; + break; + case 'maxp': + font.tables.maxp = maxp.parse(data, offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + font.tables.name = _name.parse(data, offset); + font.familyName = font.tables.name.fontFamily; + font.styleName = font.tables.name.fontSubfamily; + break; + case 'OS/2': + font.tables.os2 = os2.parse(data, offset); + break; + case 'post': + font.tables.post = post.parse(data, offset); + font.glyphNames = new encoding.GlyphNames(font.tables.post); + break; + case 'glyf': + glyfOffset = offset; + break; + case 'loca': + locaOffset = offset; + break; + case 'CFF ': + cffOffset = offset; + break; + case 'kern': + kernOffset = offset; + break; + case 'GPOS': + gposOffset = offset; + break; + } + p += 16; + } + + if (glyfOffset && locaOffset) { + var shortVersion = indexToLocFormat === 0; + var locaTable = loca.parse(data, locaOffset, font.numGlyphs, shortVersion); + font.glyphs = glyf.parse(data, glyfOffset, locaTable, font); + hmtx.parse(data, hmtxOffset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); + encoding.addGlyphNames(font); + } else if (cffOffset) { + cff.parse(data, cffOffset, font); + encoding.addGlyphNames(font); + } else { + font.supported = false; + } + + if (font.supported) { + if (kernOffset) { + font.kerningPairs = kern.parse(data, kernOffset); + } else { + font.kerningPairs = {}; + } + + if (gposOffset) { + gpos.parse(data, gposOffset, font); + } + } + + return font; +} + +// Asynchronously load the font from a URL or a filesystem. When done, call the callback +// with two arguments `(err, font)`. The `err` will be null on success, +// the `font` is a Font object. +// +// We use the node.js callback convention so that +// opentype.js can integrate with frameworks like async.js. +function load(url, callback) { + var isNode = typeof window === 'undefined'; + var loadFn = isNode ? loadFromFile : loadFromUrl; + loadFn(url, function(err, arrayBuffer) { + if (err) { + return callback(err); + } + + var font = parseBuffer(arrayBuffer); + if (!font.supported) { + return callback('Font is not supported (is this a Postscript font?)'); + } + + return callback(null, font); + }); +} + +exports._parse = parse; +exports.Font = _font.Font; +exports.Glyph = glyph.Glyph; +exports.Path = path.Path; +exports.parse = parseBuffer; +exports.load = load; + +},{"./encoding":4,"./font":5,"./glyph":6,"./parse":9,"./path":10,"./tables/cff":12,"./tables/cmap":13,"./tables/glyf":14,"./tables/gpos":15,"./tables/head":16,"./tables/hhea":17,"./tables/hmtx":18,"./tables/kern":19,"./tables/loca":20,"./tables/maxp":21,"./tables/name":22,"./tables/os2":23,"./tables/post":24,"fs":1}],9:[function(_dereq_,module,exports){ +// Parsing utility functions + +'use strict'; + +// Retrieve an unsigned byte from the DataView. +exports.getByte = function getByte(dataView, offset) { + return dataView.getUint8(offset); +}; + +exports.getCard8 = exports.getByte; + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +exports.getUShort = function(dataView, offset) { + return dataView.getUint16(offset, false); +}; + +exports.getCard16 = exports.getUShort; + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +exports.getShort = function(dataView, offset) { + return dataView.getInt16(offset, false); +}; + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +exports.getULong = function(dataView, offset) { + return dataView.getUint32(offset, false); +}; + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +exports.getFixed = function(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +}; + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +exports.getTag = function(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +}; + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +exports.getOffset = function(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +}; + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +exports.getBytes = function(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +}; + +// Convert the list of bytes to a string. +exports.bytesToString = function(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +}; + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; + +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseCard8 = Parser.prototype.parseByte; + +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseULong = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseFixed = function() { + var v = exports.getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = exports.getUShort(dataView, offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +Parser.prototype.parseLongDateTime = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset + 4); + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseFixed = function() { + var v = exports.getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v / 65536; +}; + +Parser.prototype.parseVersion = function() { + var major = exports.getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // This returns the correct number if minor = 0xN000 where N is 0-9 + var minor = exports.getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + return major + minor / 0x1000 / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; +}; + +exports.Parser = Parser; + +},{}],10:[function(_dereq_,module,exports){ +// Geometric objects + +'use strict'; + +// A bézier path containing a set of path commands similar to a SVG path. +// Paths can be drawn on a context using `draw`. +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} + +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; + +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; + +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; + +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; + +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; + +// Add the given path or list of commands to the commands of this path. +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; + +// Draw the path to a 2D context. +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } +}; + +// Convert the Path to a string of path data instructions +// See http://www.w3.org/TR/SVG/paths.html#PathData +// Parameters: +// - decimalPlaces: The amount of decimal places for floating-point values (default: 2) +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; +}; + +// Convert the path to a SVG <path> element, as a string. +// Parameters: +// - decimalPlaces: The amount of decimal places for floating-point values (default: 2) +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '<path d="'; + svg += this.toPathData(decimalPlaces); + svg += '"'; + if (this.fill & this.fill !== 'black') { + if (this.fill === null) { + svg += ' fill="none"'; + } else { + svg += ' fill="' + this.fill + '"'; + } + } + + if (this.stroke) { + svg += ' stroke="' + this.stroke + '" stroke-width="' + this.strokeWidth + '"'; + } + + svg += '/>'; + return svg; +}; + +exports.Path = Path; + +},{}],11:[function(_dereq_,module,exports){ +// Table metadata + +'use strict'; + +var check = _dereq_('./check'); +var encode = _dereq_('./types').encode; +var sizeOf = _dereq_('./types').sizeOf; + +function Table(tableName, fields, options) { + var i; + for (i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (i = 0; i < optionKeys.length; i += 1) { + var k = optionKeys[i]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } +} + +Table.prototype.sizeOf = function() { + var v = 0; + for (var i = 0; i < this.fields.length; i += 1) { + var field = this.fields[i]; + var value = this[field.name]; + if (value === undefined) { + value = field.value; + } + + if (typeof value.sizeOf === 'function') { + v += value.sizeOf(); + } else { + var sizeOfFunction = sizeOf[field.type]; + check.assert(typeof sizeOfFunction === 'function', 'Could not find sizeOf function for field' + field.name); + v += sizeOfFunction(value); + } + } + + return v; +}; + +Table.prototype.encode = function() { + return encode.TABLE(this); +}; + +exports.Table = Table; + +},{"./check":2,"./types":26}],12:[function(_dereq_,module,exports){ +// The `CFF` table contains the glyph outlines in PostScript format. +// https://www.microsoft.com/typography/OTSPEC/cff.htm +// http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/cff.pdf +// http://download.microsoft.com/download/8/0/1/801a191c-029d-4af3-9642-555f6fe514ee/type2.pdf + +'use strict'; + +var encoding = _dereq_('../encoding'); +var glyphset = _dereq_('../glyphset'); +var parse = _dereq_('../parse'); +var path = _dereq_('../path'); +var table = _dereq_('../table'); + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + //var i, objectOffset, endOffset; + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var i; + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (i = 0; i < offsets.length - 1; i += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; +} + +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); +} + +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); +} + +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value; + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key)) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; +} + +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); +} + +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = encoding.cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; +} + +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + var value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + + newDict[m.name] = value; + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + {name: 'fontMatrix', op: 1207, type: ['real', 'real', 'real', 'real', 'real', 'real'], value: [0.001, 0, 0, 0.001, 0, 0]}, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var i; + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (i = 0; i <= count; i += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (i = 0; i <= count; i += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var i; + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (i = 0; i < nRanges; i += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new encoding.CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new path.Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var width = font.defaultWidthX; + var open = false; + var x = 0; + var y = 0; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + font.subrsBias; + subrCode = font.subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + font.nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictData = new DataView(new Uint8Array(topDictIndex.objects[0]).buffer); + var topDict = parseCFFTopDict(topDictData, stringIndex.objects); + font.tables.cff.topDict = topDict; + + var privateDictOffset = start + topDict['private'][1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { // Standard encoding + font.cffEncoding = new encoding.CffEncoding(encoding.cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { // Expert encoding + font.cffEncoding = new encoding.CffEncoding(encoding.cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } +} + +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = encoding.cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + encoding.cffStandardStrings.length; + } else { + sid = encoding.cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; +} + +function makeHeader() { + return new table.Table('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} + +function makeNameIndex(fontNames) { + var t = new table.Table('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; +} + +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; +} + +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Table('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} + +function makeTopDictIndex(topDict) { + var t = new table.Table('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} + +function makeStringIndex(strings) { + var t = new table.Table('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; +} + +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Table('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} + +function makeCharsets(glyphNames, strings) { + var t = new table.Table('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx; + var dy; + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: _13 * x + _23 * cmd.x1, + y1: _13 * y + _23 * cmd.y1, + x2: _13 * cmd.x + _23 * cmd.x1, + y2: _13 * cmd.y + _23 * cmd.y1 + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Table('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; +} + +function makePrivateDict(attrs, strings) { + var t = new table.Table('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makePrivateDictIndex(privateDict) { + var t = new table.Table('Private DICT INDEX', [ + {name: 'privateDicts', type: 'INDEX', value: []} + ]); + t.privateDicts = [{name: 'privateDict_0', type: 'TABLE', value: privateDict}]; + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'TABLE'}, + {name: 'nameIndex', type: 'TABLE'}, + {name: 'topDictIndex', type: 'TABLE'}, + {name: 'stringIndex', type: 'TABLE'}, + {name: 'globalSubrIndex', type: 'TABLE'}, + {name: 'charsets', type: 'TABLE'}, + {name: 'charStringsIndex', type: 'TABLE'}, + {name: 'privateDictIndex', type: 'TABLE'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + var privateDict = makePrivateDict(privateAttrs, strings); + t.privateDictIndex = makePrivateDictIndex(privateDict); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +exports.parse = parseCFFTable; +exports.make = makeCFFTable; + +},{"../encoding":4,"../glyphset":7,"../parse":9,"../path":10,"../table":11}],13:[function(_dereq_,module,exports){ +// The `cmap` table stores the mappings from characters to glyphs. +// https://www.microsoft.com/typography/OTSPEC/cmap.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var i; + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 3" table. This is a Windows format. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (i = 0; i < cmap.numTables; i += 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if (platformId === 3 && (encodingId === 1 || encodingId === 0)) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support, so return null. + // This font will be marked as unsupported. + return null; + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + check.argument(cmap.format === 4, 'Only format 4 cmap tables are supported.'); + + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (i = 0; i < segCount - 1; i += 1) { + var glyphIndex; + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } + + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0 + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +function makeCmapTable(glyphs) { + var i; + var t = new table.Table('cmap', [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: 1}, + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: 12}, + {name: 'format', type: 'USHORT', value: 4}, + {name: 'length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function(a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount; + segCount = t.segments.length; + t.segCountX2 = segCount * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + return t; +} + +exports.parse = parseCmapTable; +exports.make = makeCmapTable; + +},{"../check":2,"../parse":9,"../table":11}],14:[function(_dereq_,module,exports){ +// The `glyf` table describes the glyphs in TrueType outline format. +// http://www.microsoft.com/typography/otspec/glyf.htm + +'use strict'; + +var check = _dereq_('../check'); +var glyphset = _dereq_('../glyphset'); +var parse = _dereq_('../parse'); +var path = _dereq_('../path'); + +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph.xMin = p.parseShort(); + glyph.yMin = p.parseShort(); + glyph.xMax = p.parseShort(); + glyph.yMax = p.parseShort(); + var flags; + var flag; + if (glyph.numberOfContours > 0) { + var i; + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (i = 0; i < glyph.instructionLength; i += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i) >= 0; + points.push(point); + } + + var px = 0; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = points[i]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (i = 0; i < numberOfCoordinates; i += 1) { + flag = flags[i]; + point = points[i]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // The arguments are bytes + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + } +} + +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} + +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new path.Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + for (var i = 0; i < contours.length; i += 1) { + var contour = contours[i]; + var firstPt = contour[0]; + var lastPt = contour[contour.length - 1]; + var curvePt; + var realFirstPoint; + if (firstPt.onCurve) { + curvePt = null; + // The first point will be consumed by the moveTo command, + // so skip it in the loop. + realFirstPoint = true; + } else { + if (lastPt.onCurve) { + // If the first point is off-curve and the last point is on-curve, + // start at the last point. + firstPt = lastPt; + } else { + // If both first and last points are off-curve, start at their middle. + firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 }; + } + + curvePt = firstPt; + // The first point is synthesized, so don't skip the real first point. + realFirstPoint = false; + } + + p.moveTo(firstPt.x, firstPt.y); + + for (var j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) { + var pt = contour[j]; + var prevPt = j === 0 ? firstPt : contour[j - 1]; + if (prevPt.onCurve && pt.onCurve) { + // This is a straight line. + p.lineTo(pt.x, pt.y); + } else if (prevPt.onCurve && !pt.onCurve) { + curvePt = pt; + } else if (!prevPt.onCurve && !pt.onCurve) { + var midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 }; + p.quadraticCurveTo(prevPt.x, prevPt.y, midPt.x, midPt.y); + curvePt = pt; + } else if (!prevPt.onCurve && pt.onCurve) { + // Previous point off-curve, this point on-curve. + p.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y); + curvePt = null; + } else { + throw new Error('Invalid state.'); + } + } + + if (firstPt !== lastPt) { + // Connect the last and first points + if (curvePt) { + p.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y); + } else { + p.lineTo(firstPt.x, firstPt.y); + } + } + } + + p.closePath(); + return p; +} + +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + if (componentGlyph.points) { + var transformedPoints = transformPoints(componentGlyph.points, component); + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + var i; + + // The last element of the loca table is invalid. + for (i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; +} + +exports.parse = parseGlyfTable; + +},{"../check":2,"../glyphset":7,"../parse":9,"../path":10}],15:[function(_dereq_,module,exports){ +// The `GPOS` table contains kerning pairs, among other things. +// https://www.microsoft.com/typography/OTSPEC/gpos.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); + +// Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables. +// These lists are unused by now, this function is just the basis for a real parsing. +function parseTaggedListTable(data, start) { + var p = new parse.Parser(data, start); + var n = p.parseUShort(); + var list = []; + for (var i = 0; i < n; i++) { + list[p.parseTag()] = { offset: p.parseUShort() }; + } + + return list; +} + +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// Format 1 is a simple list of glyph ids, +// Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea. +function parseCoverageTable(data, start) { + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + if (format === 1) { + return p.parseUShortList(count); + } + else if (format === 2) { + var coverage = []; + for (; count--;) { + var begin = p.parseUShort(); + var end = p.parseUShort(); + var index = p.parseUShort(); + for (var i = begin; i <= end; i++) { + coverage[index++] = i; + } + } + + return coverage; + } +} + +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// Returns a function that gets a class value from a glyph ID. +function parseClassDefTable(data, start) { + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + if (format === 1) { + // Format 1 specifies a range of consecutive glyph indices, one class per glyph ID. + var startGlyph = p.parseUShort(); + var glyphCount = p.parseUShort(); + var classes = p.parseUShortList(glyphCount); + return function(glyphID) { + return classes[glyphID - startGlyph] || 0; + }; + } + else if (format === 2) { + // Format 2 defines multiple groups of glyph indices that belong to the same class. + var rangeCount = p.parseUShort(); + var startGlyphs = []; + var endGlyphs = []; + var classValues = []; + for (var i = 0; i < rangeCount; i++) { + startGlyphs[i] = p.parseUShort(); + endGlyphs[i] = p.parseUShort(); + classValues[i] = p.parseUShort(); + } + + return function(glyphID) { + var l = 0; + var r = startGlyphs.length - 1; + while (l < r) { + var c = (l + r + 1) >> 1; + if (glyphID < startGlyphs[c]) { + r = c - 1; + } else { + l = c; + } + } + + if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) { + return classValues[l] || 0; + } + + return 0; + }; + } +} + +// Parse a pair adjustment positioning subtable, format 1 or format 2 +// The subtable is returned in the form of a lookup function. +function parsePairPosSubTable(data, start) { + var p = new parse.Parser(data, start); + // This part is common to format 1 and format 2 subtables + var format = p.parseUShort(); + var coverageOffset = p.parseUShort(); + var coverage = parseCoverageTable(data, start + coverageOffset); + // valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph + // Only valueFormat1=4 and valueFormat2=0 is supported. + var valueFormat1 = p.parseUShort(); + var valueFormat2 = p.parseUShort(); + var value1; + var value2; + if (valueFormat1 !== 4 || valueFormat2 !== 0) return; + var sharedPairSets = {}; + if (format === 1) { + // Pair Positioning Adjustment: Format 1 + var pairSetCount = p.parseUShort(); + var pairSet = []; + // Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index + var pairSetOffsets = p.parseOffset16List(pairSetCount); + for (var firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) { + var pairSetOffset = pairSetOffsets[firstGlyph]; + var sharedPairSet = sharedPairSets[pairSetOffset]; + if (!sharedPairSet) { + // Parse a pairset table in a pair adjustment subtable format 1 + sharedPairSet = {}; + p.relativeOffset = pairSetOffset; + var pairValueCount = p.parseUShort(); + for (; pairValueCount--;) { + var secondGlyph = p.parseUShort(); + if (valueFormat1) value1 = p.parseShort(); + if (valueFormat2) value2 = p.parseShort(); + // We only support valueFormat1 = 4 and valueFormat2 = 0, + // so value1 is the XAdvance and value2 is empty. + sharedPairSet[secondGlyph] = value1; + } + } + + pairSet[coverage[firstGlyph]] = sharedPairSet; + } + + return function(leftGlyph, rightGlyph) { + var pairs = pairSet[leftGlyph]; + if (pairs) return pairs[rightGlyph]; + }; + } + else if (format === 2) { + // Pair Positioning Adjustment: Format 2 + var classDef1Offset = p.parseUShort(); + var classDef2Offset = p.parseUShort(); + var class1Count = p.parseUShort(); + var class2Count = p.parseUShort(); + var getClass1 = parseClassDefTable(data, start + classDef1Offset); + var getClass2 = parseClassDefTable(data, start + classDef2Offset); + + // Parse kerning values by class pair. + var kerningMatrix = []; + for (var i = 0; i < class1Count; i++) { + var kerningRow = kerningMatrix[i] = []; + for (var j = 0; j < class2Count; j++) { + if (valueFormat1) value1 = p.parseShort(); + if (valueFormat2) value2 = p.parseShort(); + // We only support valueFormat1 = 4 and valueFormat2 = 0, + // so value1 is the XAdvance and value2 is empty. + kerningRow[j] = value1; + } + } + + // Convert coverage list to a hash + var covered = {}; + for (i = 0; i < coverage.length; i++) covered[coverage[i]] = 1; + + // Get the kerning value for a specific glyph pair. + return function(leftGlyph, rightGlyph) { + if (!covered[leftGlyph]) return; + var class1 = getClass1(leftGlyph); + var class2 = getClass2(rightGlyph); + var kerningRow = kerningMatrix[class1]; + + if (kerningRow) { + return kerningRow[class2]; + } + }; + } +} + +// Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables). +function parseLookupTable(data, start) { + var p = new parse.Parser(data, start); + var lookupType = p.parseUShort(); + var lookupFlag = p.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + var subTableCount = p.parseUShort(); + var subTableOffsets = p.parseOffset16List(subTableCount); + var table = { + lookupType: lookupType, + lookupFlag: lookupFlag, + markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1 + }; + // LookupType 2, Pair adjustment + if (lookupType === 2) { + var subtables = []; + for (var i = 0; i < subTableCount; i++) { + subtables.push(parsePairPosSubTable(data, start + subTableOffsets[i])); + } + // Return a function which finds the kerning values in the subtables. + table.getKerningValue = function(leftGlyph, rightGlyph) { + for (var i = subtables.length; i--;) { + var value = subtables[i](leftGlyph, rightGlyph); + if (value !== undefined) return value; + } + + return 0; + }; + } + + return table; +} + +// Parse the `GPOS` table which contains, among other things, kerning pairs. +// https://www.microsoft.com/typography/OTSPEC/gpos.htm +function parseGposTable(data, start, font) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseFixed(); + check.argument(tableVersion === 1, 'Unsupported GPOS table version.'); + + // ScriptList and FeatureList - ignored for now + parseTaggedListTable(data, start + p.parseUShort()); + // 'kern' is the feature we are looking for. + parseTaggedListTable(data, start + p.parseUShort()); + + // LookupList + var lookupListOffset = p.parseUShort(); + p.relativeOffset = lookupListOffset; + var lookupCount = p.parseUShort(); + var lookupTableOffsets = p.parseOffset16List(lookupCount); + var lookupListAbsoluteOffset = start + lookupListOffset; + for (var i = 0; i < lookupCount; i++) { + var table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]); + if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue; + } +} + +exports.parse = parseGposTable; + +},{"../check":2,"../parse":9}],16:[function(_dereq_,module,exports){ +// The `head` table contains global information about the font. +// https://www.microsoft.com/typography/OTSPEC/head.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); // 50 + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: 0}, + {name: 'modified', type: 'LONGDATETIME', value: 0}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +exports.parse = parseHeadTable; +exports.make = makeHeadTable; + +},{"../check":2,"../parse":9,"../table":11}],17:[function(_dereq_,module,exports){ +// The `hhea` table contains information for horizontal layout. +// https://www.microsoft.com/typography/OTSPEC/hhea.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +exports.parse = parseHheaTable; +exports.make = makeHheaTable; + +},{"../parse":9,"../table":11}],18:[function(_dereq_,module,exports){ +// The `hmtx` table contains the horizontal metrics for all glyphs. +// https://www.microsoft.com/typography/OTSPEC/hmtx.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; +} + +exports.parse = parseHmtxTable; +exports.make = makeHmtxTable; + +},{"../parse":9,"../table":11}],19:[function(_dereq_,module,exports){ +// The `kern` table contains kerning pairs. +// Note that some fonts use the GPOS OpenType layout table to specify kerning. +// https://www.microsoft.com/typography/OTSPEC/kern.htm + +'use strict'; + +var check = _dereq_('../check'); +var parse = _dereq_('../parse'); + +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var pairs = {}; + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + check.argument(tableVersion === 0, 'Unsupported kern table version.'); + // Skip nTables. + p.skip('uShort', 1); + var subTableVersion = p.parseUShort(); + check.argument(subTableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subTableLength, subTableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + + return pairs; +} + +exports.parse = parseKernTable; + +},{"../check":2,"../parse":9}],20:[function(_dereq_,module,exports){ +// The `loca` table stores the offsets to the locations of the glyphs in the font. +// https://www.microsoft.com/typography/OTSPEC/loca.htm + +'use strict'; + +var parse = _dereq_('../parse'); + +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; +} + +exports.parse = parseLocaTable; + +},{"../parse":9}],21:[function(_dereq_,module,exports){ +// The `maxp` table establishes the memory requirements for the font. +// We need it just to get the number of glyphs in the font. +// https://www.microsoft.com/typography/OTSPEC/maxp.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +exports.parse = parseMaxpTable; +exports.make = makeMaxpTable; + +},{"../parse":9,"../table":11}],22:[function(_dereq_,module,exports){ +// The `name` naming table. +// https://www.microsoft.com/typography/OTSPEC/name.htm + +'use strict'; + +var encode = _dereq_('../types').encode; +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'licence', // 13 + 'licenceURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +// Parse the naming `name` table +// Only Windows Unicode English names are supported. +// Format 1 additional fields are not supported +function parseNameTable(data, start) { + var name = {}; + var p = new parse.Parser(data, start); + name.format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + var unknownCount = 0; + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID]; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + // platformID - encodingID - languageID standard combinations : + // 1 - 0 - 0 : Macintosh, Roman, English + // 3 - 1 - 0x409 : Windows, Unicode BMP (UCS-2), en-US + if (platformID === 3 && encodingID === 1 && languageID === 0x409) { + var codePoints = []; + var length = byteLength / 2; + for (var j = 0; j < length; j++, offset += 2) { + codePoints[j] = parse.getShort(data, stringOffset + offset); + } + + var str = String.fromCharCode.apply(null, codePoints); + if (property) { + name[property] = str; + } + else { + unknownCount++; + name['unknown' + unknownCount] = str; + } + } + + } + + if (name.format === 1) { + name.langTagCount = p.parseUShort(); + } + + return name; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Table('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +function addMacintoshNameRecord(t, recordID, s, offset) { + // Macintosh, Roman, English + var stringBytes = encode.STRING(s); + t.records.push(makeNameRecord(1, 0, 0, recordID, stringBytes.length, offset)); + t.strings.push(stringBytes); + offset += stringBytes.length; + return offset; +} + +function addWindowsNameRecord(t, recordID, s, offset) { + // Windows, Unicode BMP (UCS-2), US English + var utf16Bytes = encode.UTF16(s); + t.records.push(makeNameRecord(3, 1, 0x0409, recordID, utf16Bytes.length, offset)); + t.strings.push(utf16Bytes); + offset += utf16Bytes.length; + return offset; +} + +function makeNameTable(options) { + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: 0}, + {name: 'stringOffset', type: 'USHORT', value: 0} + ]); + t.records = []; + t.strings = []; + var offset = 0; + var i; + var s; + // Add Macintosh records first + for (i = 0; i < nameTableNames.length; i += 1) { + if (options[nameTableNames[i]] !== undefined) { + s = options[nameTableNames[i]]; + offset = addMacintoshNameRecord(t, i, s, offset); + } + } + // Then add Windows records + for (i = 0; i < nameTableNames.length; i += 1) { + if (options[nameTableNames[i]] !== undefined) { + s = options[nameTableNames[i]]; + offset = addWindowsNameRecord(t, i, s, offset); + } + } + + t.count = t.records.length; + t.stringOffset = 6 + t.count * 12; + for (i = 0; i < t.records.length; i += 1) { + t.fields.push({name: 'record_' + i, type: 'TABLE', value: t.records[i]}); + } + + for (i = 0; i < t.strings.length; i += 1) { + t.fields.push({name: 'string_' + i, type: 'LITERAL', value: t.strings[i]}); + } + + return t; +} + +exports.parse = parseNameTable; +exports.make = makeNameTable; + +},{"../parse":9,"../table":11,"../types":26}],23:[function(_dereq_,module,exports){ +// The `OS/2` table contains metrics required in OpenType fonts. +// https://www.microsoft.com/typography/OTSPEC/os2.htm + +'use strict'; + +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +exports.unicodeRanges = unicodeRanges; +exports.getUnicodeRange = getUnicodeRange; +exports.parse = parseOS2Table; +exports.make = makeOS2Table; + +},{"../parse":9,"../table":11}],24:[function(_dereq_,module,exports){ +// The `post` table stores additional PostScript information, such as glyph names. +// https://www.microsoft.com/typography/OTSPEC/post.htm + +'use strict'; + +var encoding = _dereq_('../encoding'); +var parse = _dereq_('../parse'); +var table = _dereq_('../table'); + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + var i; + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = encoding.standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] >= encoding.standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (i = 0; i < post.numberOfGlyphs; i++) { + post.offset[i] = p.parseChar(); + } + + break; + } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} + +exports.parse = parsePostTable; +exports.make = makePostTable; + +},{"../encoding":4,"../parse":9,"../table":11}],25:[function(_dereq_,module,exports){ +// The `sfnt` wrapper provides organization for the tables in the font. +// It is the top-level data structure in a font. +// https://www.microsoft.com/typography/OTSPEC/otff.htm +// Recommendations for creating OpenType Fonts: +// http://www.microsoft.com/typography/otspec140/recom.htm + +'use strict'; + +var check = _dereq_('../check'); +var table = _dereq_('../table'); + +var cmap = _dereq_('./cmap'); +var cff = _dereq_('./cff'); +var head = _dereq_('./head'); +var hhea = _dereq_('./hhea'); +var hmtx = _dereq_('./hmtx'); +var maxp = _dereq_('./maxp'); +var _name = _dereq_('./name'); +var os2 = _dereq_('./os2'); +var post = _dereq_('./post'); + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Table('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'TABLE', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'TABLE', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} + +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + if (firstCharIndex > unicode || firstCharIndex === null) { + firstCharIndex = unicode; + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') continue; + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender !== undefined ? font.ascender : globals.yMax; + globals.descender = font.descender !== undefined ? font.descender : globals.yMin; + + var headTable = head.make({ + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usWeightClass: 500, // Medium FIXME Make this configurable + usWidthClass: 5, // Medium (normal) FIXME Make this configurable + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.ascender, + usWinDescent: -globals.descender, + sxHeight: metricsForChar(font, 'xyvw', {yMax: 0}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usBreakChar: font.hasChar(' ') ? 32 : 0 // Use space as the break character, if available. + }); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var fullName = font.familyName + ' ' + font.styleName; + var postScriptName = font.familyName.replace(/\s/g, '') + '-' + font.styleName; + var nameTable = _name.make({ + copyright: font.copyright, + fontFamily: font.familyName, + fontSubfamily: font.styleName, + uniqueID: font.manufacturer + ':' + fullName, + fullName: fullName, + version: font.version, + postScriptName: postScriptName, + trademark: font.trademark, + manufacturer: font.manufacturer, + designer: font.designer, + description: font.description, + manufacturerURL: font.manufacturerURL, + designerURL: font.designerURL, + license: font.license, + licenseURL: font.licenseURL, + preferredFamily: font.familyName, + preferredSubfamily: font.styleName + }); + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.version, + fullName: fullName, + familyName: font.familyName, + weightName: font.styleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm + }); + // Order the tables according to the the OpenType specification 1.4. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (i = 0; i < tableFields.length; i += 1) { + if (tableFields[i].name === 'head table') { + tableFields[i].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +exports.computeCheckSum = computeCheckSum; +exports.make = makeSfntTable; +exports.fontToTable = fontToSfntTable; + +},{"../check":2,"../table":11,"./cff":12,"./cmap":13,"./head":16,"./hhea":17,"./hmtx":18,"./maxp":21,"./name":22,"./os2":23,"./post":24}],26:[function(_dereq_,module,exports){ +// Data types used in the OpenType font file. +// All OpenType fonts use Motorola-style byte ordering (Big Endian) + +/* global WeakMap */ + +'use strict'; + +var check = _dereq_('./check'); + +var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 +var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 + +var decode = {}; +var encode = {}; +var sizeOf = {}; + +// Return a function that always returns the same value. +function constant(v) { + return function() { + return v; + }; +} + +// OpenType data types ////////////////////////////////////////////////////// + +// Convert an 8-bit unsigned integer to a list of 1 byte. +encode.BYTE = function(v) { + check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; + +sizeOf.BYTE = constant(1); + +// Convert a 8-bit signed integer to a list of 1 byte. +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; + +sizeOf.BYTE = constant(1); + +// Convert an ASCII string to a list of bytes. +encode.CHARARRAY = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b.push(v.charCodeAt(i)); + } + + return b; +}; + +sizeOf.CHARARRAY = function(v) { + return v.length; +}; + +// Convert a 16-bit unsigned integer to a list of 2 bytes. +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.USHORT = constant(2); + +// Convert a 16-bit signed integer to a list of 2 bytes. +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.SHORT = constant(2); + +// Convert a 24-bit unsigned integer to a list of 3 bytes. +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.UINT24 = constant(3); + +// Convert a 32-bit unsigned integer to a list of 4 bytes. +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.ULONG = constant(4); + +// Convert a 32-bit unsigned integer to a list of 4 bytes. +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.LONG = constant(4); + +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; + +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; + +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; + +// FIXME Implement LONGDATETIME +encode.LONGDATETIME = function() { + return [0, 0, 0, 0, 0, 0, 0, 0]; +}; + +sizeOf.LONGDATETIME = constant(8); + +// Convert a 4-char tag to a list of 4 bytes. +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; + +sizeOf.TAG = constant(4); + +// CFF data types /////////////////////////////////////////////////////////// + +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; + +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; + +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; + +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; + +// Convert a numeric operand or charstring number to a variable-size list of bytes. +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } +}; + +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; + +// Convert a signed number between -32768 and +32767 to a three-byte value. +// This ensures we always use three bytes, but is not the most compact format. +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.NUMBER16 = constant(2); + +// Convert a signed number between -(2^31) and +(2^31-1) to a four-byte value. +// This is useful if you want to be sure you always use four bytes, +// at the expense of wasting a few bytes for smaller numbers. +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +sizeOf.NUMBER32 = constant(4); + +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + var i; + var ii; + for (i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (i = 0, ii = nibbles.length; i < ii; i += 2) { + out.push(parseInt(nibbles.substr(i, 2), 16)); + } + + return out; +}; + +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; + +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; + +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; + +// Convert a ASCII string to a list of UTF16 bytes. +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + b.push(0); + b.push(v.charCodeAt(i)); + } + + return b; +}; + +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; + +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +encode.INDEX = function(l) { + var i; + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // dataSize, i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + var dataSize = 0; + for (i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + dataSize += v.length; + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(dataSize) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (i = 0; i < offsets.length; i += 1) { + var encodedOffset = offsetEncoder(offsets[i]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; + +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; + +// Convert an object to a CFF DICT structure. +// The keys should be numeric. +// The values should be objects containing name / type / value. +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; +}; + +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; + +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; + +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; +}; + +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; + +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); +// Convert a list of CharString operations to bytes. +encode.CHARSTRING = function(ops) { + if (wmm && wmm.has(ops)) { + return wmm.get(ops); + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; +}; + +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; + +// Utility functions //////////////////////////////////////////////////////// + +// Convert an object containing name / type / value to bytes. +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; + +// Convert a table object to bytes. +// A table contains a list of fields containing the metadata (name, type and default value). +// The table itself has the field values set as attributes. +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + d = d.concat(bytes); + } + + return d; +}; + +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; + +sizeOf.LITERAL = function(v) { + return v.length; +}; + +exports.decode = decode; +exports.encode = encode; +exports.sizeOf = sizeOf; + +},{"./check":2}],27:[function(_dereq_,module,exports){ +/*! + * Reqwest! A general purpose XHR connection manager + * license MIT (c) Dustin Diaz 2014 + * https://github.com/ded/reqwest + */ + +!function (name, context, definition) { + if (typeof module != 'undefined' && module.exports) module.exports = definition() + else if (typeof define == 'function' && define.amd) define(definition) + else context[name] = definition() +}('reqwest', this, function () { + + var win = window + , doc = document + , httpsRe = /^http/ + , protocolRe = /(^\w+):\/\// + , twoHundo = /^(20\d|1223)$/ //http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request + , byTag = 'getElementsByTagName' + , readyState = 'readyState' + , contentType = 'Content-Type' + , requestedWith = 'X-Requested-With' + , head = doc[byTag]('head')[0] + , uniqid = 0 + , callbackPrefix = 'reqwest_' + (+new Date()) + , lastValue // data stored by the most recent JSONP callback + , xmlHttpRequest = 'XMLHttpRequest' + , xDomainRequest = 'XDomainRequest' + , noop = function () {} + + , isArray = typeof Array.isArray == 'function' + ? Array.isArray + : function (a) { + return a instanceof Array + } + + , defaultHeaders = { + 'contentType': 'application/x-www-form-urlencoded' + , 'requestedWith': xmlHttpRequest + , 'accept': { + '*': 'text/javascript, text/html, application/xml, text/xml, */*' + , 'xml': 'application/xml, text/xml' + , 'html': 'text/html' + , 'text': 'text/plain' + , 'json': 'application/json, text/javascript' + , 'js': 'application/javascript, text/javascript' + } + } + + , xhr = function(o) { + // is it x-domain + if (o['crossOrigin'] === true) { + var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null + if (xhr && 'withCredentials' in xhr) { + return xhr + } else if (win[xDomainRequest]) { + return new XDomainRequest() + } else { + throw new Error('Browser does not support cross-origin requests') + } + } else if (win[xmlHttpRequest]) { + return new XMLHttpRequest() + } else { + return new ActiveXObject('Microsoft.XMLHTTP') + } + } + , globalSetupOptions = { + dataFilter: function (data) { + return data + } + } + + function succeed(r) { + var protocol = protocolRe.exec(r.url); + protocol = (protocol && protocol[1]) || window.location.protocol; + return httpsRe.test(protocol) ? twoHundo.test(r.request.status) : !!r.request.response; + } + + function handleReadyState(r, success, error) { + return function () { + // use _aborted to mitigate against IE err c00c023f + // (can't read props on aborted request objects) + if (r._aborted) return error(r.request) + if (r._timedOut) return error(r.request, 'Request is aborted: timeout') + if (r.request && r.request[readyState] == 4) { + r.request.onreadystatechange = noop + if (succeed(r)) success(r.request) + else + error(r.request) + } + } + } + + function setHeaders(http, o) { + var headers = o['headers'] || {} + , h + + headers['Accept'] = headers['Accept'] + || defaultHeaders['accept'][o['type']] + || defaultHeaders['accept']['*'] + + var isAFormData = typeof FormData === 'function' && (o['data'] instanceof FormData); + // breaks cross-origin requests with legacy browsers + if (!o['crossOrigin'] && !headers[requestedWith]) headers[requestedWith] = defaultHeaders['requestedWith'] + if (!headers[contentType] && !isAFormData) headers[contentType] = o['contentType'] || defaultHeaders['contentType'] + for (h in headers) + headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h]) + } + + function setCredentials(http, o) { + if (typeof o['withCredentials'] !== 'undefined' && typeof http.withCredentials !== 'undefined') { + http.withCredentials = !!o['withCredentials'] + } + } + + function generalCallback(data) { + lastValue = data + } + + function urlappend (url, s) { + return url + (/\?/.test(url) ? '&' : '?') + s + } + + function handleJsonp(o, fn, err, url) { + var reqId = uniqid++ + , cbkey = o['jsonpCallback'] || 'callback' // the 'callback' key + , cbval = o['jsonpCallbackName'] || reqwest.getcallbackPrefix(reqId) + , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') + , match = url.match(cbreg) + , script = doc.createElement('script') + , loaded = 0 + , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 + + if (match) { + if (match[3] === '?') { + url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name + } else { + cbval = match[3] // provided callback func name + } + } else { + url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em + } + + win[cbval] = generalCallback + + script.type = 'text/javascript' + script.src = url + script.async = true + if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { + // need this for IE due to out-of-order onreadystatechange(), binding script + // execution to an event listener gives us control over when the script + // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html + script.htmlFor = script.id = '_reqwest_' + reqId + } + + script.onload = script.onreadystatechange = function () { + if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { + return false + } + script.onload = script.onreadystatechange = null + script.onclick && script.onclick() + // Call the user callback with the last value stored and clean up values and scripts. + fn(lastValue) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + + // Add the script to the DOM head + head.appendChild(script) + + // Enable JSONP timeout + return { + abort: function () { + script.onload = script.onreadystatechange = null + err({}, 'Request is aborted: timeout', {}) + lastValue = undefined + head.removeChild(script) + loaded = 1 + } + } + } + + function getRequest(fn, err) { + var o = this.o + , method = (o['method'] || 'GET').toUpperCase() + , url = typeof o === 'string' ? o : o['url'] + // convert non-string objects to query-string form unless o['processData'] is false + , data = (o['processData'] !== false && o['data'] && typeof o['data'] !== 'string') + ? reqwest.toQueryString(o['data']) + : (o['data'] || null) + , http + , sendWait = false + + // if we're working on a GET request and we have data then we should append + // query string to end of URL and not post data + if ((o['type'] == 'jsonp' || method == 'GET') && data) { + url = urlappend(url, data) + data = null + } + + if (o['type'] == 'jsonp') return handleJsonp(o, fn, err, url) + + // get the xhr from the factory if passed + // if the factory returns null, fall-back to ours + http = (o.xhr && o.xhr(o)) || xhr(o) + + http.open(method, url, o['async'] === false ? false : true) + setHeaders(http, o) + setCredentials(http, o) + if (win[xDomainRequest] && http instanceof win[xDomainRequest]) { + http.onload = fn + http.onerror = err + // NOTE: see + // http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e + http.onprogress = function() {} + sendWait = true + } else { + http.onreadystatechange = handleReadyState(this, fn, err) + } + o['before'] && o['before'](http) + if (sendWait) { + setTimeout(function () { + http.send(data) + }, 200) + } else { + http.send(data) + } + return http + } + + function Reqwest(o, fn) { + this.o = o + this.fn = fn + + init.apply(this, arguments) + } + + function setType(header) { + // json, javascript, text/plain, text/html, xml + if (header.match('json')) return 'json' + if (header.match('javascript')) return 'js' + if (header.match('text')) return 'html' + if (header.match('xml')) return 'xml' + } + + function init(o, fn) { + + this.url = typeof o == 'string' ? o : o['url'] + this.timeout = null + + // whether request has been fulfilled for purpose + // of tracking the Promises + this._fulfilled = false + // success handlers + this._successHandler = function(){} + this._fulfillmentHandlers = [] + // error handlers + this._errorHandlers = [] + // complete (both success and fail) handlers + this._completeHandlers = [] + this._erred = false + this._responseArgs = {} + + var self = this + + fn = fn || function () {} + + if (o['timeout']) { + this.timeout = setTimeout(function () { + timedOut() + }, o['timeout']) + } + + if (o['success']) { + this._successHandler = function () { + o['success'].apply(o, arguments) + } + } + + if (o['error']) { + this._errorHandlers.push(function () { + o['error'].apply(o, arguments) + }) + } + + if (o['complete']) { + this._completeHandlers.push(function () { + o['complete'].apply(o, arguments) + }) + } + + function complete (resp) { + o['timeout'] && clearTimeout(self.timeout) + self.timeout = null + while (self._completeHandlers.length > 0) { + self._completeHandlers.shift()(resp) + } + } + + function success (resp) { + var type = o['type'] || resp && setType(resp.getResponseHeader('Content-Type')) // resp can be undefined in IE + resp = (type !== 'jsonp') ? self.request : resp + // use global data filter on response text + var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) + , r = filteredResponse + try { + resp.responseText = r + } catch (e) { + // can't assign this in IE<=8, just ignore + } + if (r) { + switch (type) { + case 'json': + try { + resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') + } catch (err) { + return error(resp, 'Could not parse JSON in response', err) + } + break + case 'js': + resp = eval(r) + break + case 'html': + resp = r + break + case 'xml': + resp = resp.responseXML + && resp.responseXML.parseError // IE trololo + && resp.responseXML.parseError.errorCode + && resp.responseXML.parseError.reason + ? null + : resp.responseXML + break + } + } + + self._responseArgs.resp = resp + self._fulfilled = true + fn(resp) + self._successHandler(resp) + while (self._fulfillmentHandlers.length > 0) { + resp = self._fulfillmentHandlers.shift()(resp) + } + + complete(resp) + } + + function timedOut() { + self._timedOut = true + self.request.abort() + } + + function error(resp, msg, t) { + resp = self.request + self._responseArgs.resp = resp + self._responseArgs.msg = msg + self._responseArgs.t = t + self._erred = true + while (self._errorHandlers.length > 0) { + self._errorHandlers.shift()(resp, msg, t) + } + complete(resp) + } + + this.request = getRequest.call(this, success, error) + } + + Reqwest.prototype = { + abort: function () { + this._aborted = true + this.request.abort() + } + + , retry: function () { + init.call(this, this.o, this.fn) + } + + /** + * Small deviation from the Promises A CommonJs specification + * http://wiki.commonjs.org/wiki/Promises/A + */ + + /** + * `then` will execute upon successful requests + */ + , then: function (success, fail) { + success = success || function () {} + fail = fail || function () {} + if (this._fulfilled) { + this._responseArgs.resp = success(this._responseArgs.resp) + } else if (this._erred) { + fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._fulfillmentHandlers.push(success) + this._errorHandlers.push(fail) + } + return this + } + + /** + * `always` will execute whether the request succeeds or fails + */ + , always: function (fn) { + if (this._fulfilled || this._erred) { + fn(this._responseArgs.resp) + } else { + this._completeHandlers.push(fn) + } + return this + } + + /** + * `fail` will execute when the request fails + */ + , fail: function (fn) { + if (this._erred) { + fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) + } else { + this._errorHandlers.push(fn) + } + return this + } + , 'catch': function (fn) { + return this.fail(fn) + } + } + + function reqwest(o, fn) { + return new Reqwest(o, fn) + } + + // normalize newline variants according to spec -> CRLF + function normalize(s) { + return s ? s.replace(/\r?\n/g, '\r\n') : '' + } + + function serial(el, cb) { + var n = el.name + , t = el.tagName.toLowerCase() + , optCb = function (o) { + // IE gives value="" even where there is no value attribute + // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 + if (o && !o['disabled']) + cb(n, normalize(o['attributes']['value'] && o['attributes']['value']['specified'] ? o['value'] : o['text'])) + } + , ch, ra, val, i + + // don't serialize elements that are disabled or without a name + if (el.disabled || !n) return + + switch (t) { + case 'input': + if (!/reset|button|image|file/i.test(el.type)) { + ch = /checkbox/i.test(el.type) + ra = /radio/i.test(el.type) + val = el.value + // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here + ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) + } + break + case 'textarea': + cb(n, normalize(el.value)) + break + case 'select': + if (el.type.toLowerCase() === 'select-one') { + optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) + } else { + for (i = 0; el.length && i < el.length; i++) { + el.options[i].selected && optCb(el.options[i]) + } + } + break + } + } + + // collect up all form elements found from the passed argument elements all + // the way down to child elements; pass a '<form>' or form fields. + // called with 'this'=callback to use for serial() on each element + function eachFormElement() { + var cb = this + , e, i + , serializeSubtags = function (e, tags) { + var i, j, fa + for (i = 0; i < tags.length; i++) { + fa = e[byTag](tags[i]) + for (j = 0; j < fa.length; j++) serial(fa[j], cb) + } + } + + for (i = 0; i < arguments.length; i++) { + e = arguments[i] + if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) + serializeSubtags(e, [ 'input', 'select', 'textarea' ]) + } + } + + // standard query string style serialization + function serializeQueryString() { + return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) + } + + // { 'name': 'value', ... } style serialization + function serializeHash() { + var hash = {} + eachFormElement.apply(function (name, value) { + if (name in hash) { + hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) + hash[name].push(value) + } else hash[name] = value + }, arguments) + return hash + } + + // [ { name: 'name', value: 'value' }, ... ] style serialization + reqwest.serializeArray = function () { + var arr = [] + eachFormElement.apply(function (name, value) { + arr.push({name: name, value: value}) + }, arguments) + return arr + } + + reqwest.serialize = function () { + if (arguments.length === 0) return '' + var opt, fn + , args = Array.prototype.slice.call(arguments, 0) + + opt = args.pop() + opt && opt.nodeType && args.push(opt) && (opt = null) + opt && (opt = opt.type) + + if (opt == 'map') fn = serializeHash + else if (opt == 'array') fn = reqwest.serializeArray + else fn = serializeQueryString + + return fn.apply(null, args) + } + + reqwest.toQueryString = function (o, trad) { + var prefix, i + , traditional = trad || false + , s = [] + , enc = encodeURIComponent + , add = function (key, value) { + // If value is a function, invoke it and return its value + value = ('function' === typeof value) ? value() : (value == null ? '' : value) + s[s.length] = enc(key) + '=' + enc(value) + } + // If an array was passed in, assume that it is an array of form elements. + if (isArray(o)) { + for (i = 0; o && i < o.length; i++) add(o[i]['name'], o[i]['value']) + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for (prefix in o) { + if (o.hasOwnProperty(prefix)) buildParams(prefix, o[prefix], traditional, add) + } + } + + // spaces should be + according to spec + return s.join('&').replace(/%20/g, '+') + } + + function buildParams(prefix, obj, traditional, add) { + var name, i, v + , rbracket = /\[\]$/ + + if (isArray(obj)) { + // Serialize array item. + for (i = 0; obj && i < obj.length; i++) { + v = obj[i] + if (traditional || rbracket.test(prefix)) { + // Treat each array item as a scalar. + add(prefix, v) + } else { + buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add) + } + } + } else if (obj && obj.toString() === '[object Object]') { + // Serialize object item. + for (name in obj) { + buildParams(prefix + '[' + name + ']', obj[name], traditional, add) + } + + } else { + // Serialize scalar item. + add(prefix, obj) + } + } + + reqwest.getcallbackPrefix = function () { + return callbackPrefix + } + + // jQuery and Zepto compatibility, differences can be remapped here so you can call + // .ajax.compat(options, callback) + reqwest.compat = function (o, fn) { + if (o) { + o['type'] && (o['method'] = o['type']) && delete o['type'] + o['dataType'] && (o['type'] = o['dataType']) + o['jsonpCallback'] && (o['jsonpCallbackName'] = o['jsonpCallback']) && delete o['jsonpCallback'] + o['jsonp'] && (o['jsonpCallback'] = o['jsonp']) + } + return new Reqwest(o, fn) + } + + reqwest.ajaxSetup = function (options) { + options = options || {} + for (var k in options) { + globalSetupOptions[k] = options[k] + } + } + + return reqwest +}); + +},{}],28:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule 3D Primitives + * @for p5 + * @requires core + * @requires p5.Geometry3D + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +_dereq_('./p5.Geometry3D'); + +/** + * Draw a plane with given a width and height + * @method plane + * @param {Number} width width of the plane + * @param {Number} height height of the plane + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a plane with width 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * plane(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.plane = function(width, height){ + + width = width || 50; + height = height || 50; + + //details for plane are highly optional + var detailX = typeof arguments[2] === Number ? arguments[2] : 1; + var detailY = typeof arguments[3] === Number ? arguments[3] : 1; + + var gId = 'plane|'+width+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createPlane = function(u, v){ + var x = 2 * width * u - width; + var y = 2 * height * v - height; + var z = 0; + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createPlane, detailX, detailY); + + var obj = geometry3d.generateObj(); + + this._renderer.initBuffer(gId, [obj]); + + } + + this._renderer.drawBuffer(gId); + +}; + +/** + * Draw a sphere with given raduis + * @method sphere + * @param {Number} radius radius of circle + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150, it may crash the browser. + * @example + * <div> + * <code> + * // draw a sphere with radius 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.sphere = function(radius, detail){ + + radius = radius || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'sphere|'+radius+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createSphere = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = Math.PI * v - Math.PI / 2; + var x = radius * Math.cos(phi) * Math.sin(theta); + var y = radius * Math.sin(phi); + var z = radius * Math.cos(phi) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createSphere, detailX, detailY); + + var obj = geometry3d.generateObj(true, true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw an ellipsoid with given radius + * @method ellipsoid + * @param {Number} radiusx xradius of circle + * @param {Number} radiusy yradius of circle + * @param {Number} radiusz zradius of circle + * @param {Number} [detail] Number of segments. + * The more segments, the smoother the + * geometry (default is 24). Avoid detail + * number above 150. It may crash the + * browser. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // draw an ellipsoid with radius 200, 300 and 400 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * ellipsoid(200,300,400); + * } + * </code> + * </div> + */ +p5.prototype.ellipsoid = function(radiusx, radiusy, radiusz, detail){ + + radiusx = radiusx || 50; + radiusy = radiusy || 50; + radiusz = radiusz || 50; + + var detailX = detail || 24; + var detailY = detail || 24; + + var gId = 'ellipsoid|'+radiusx+'|'+radiusy+ + '|'+radiusz+'|'+detailX+'|'+detailY; + + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createEllipsoid = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = Math.PI * v - Math.PI / 2; + var x = radiusx * Math.cos(phi) * Math.sin(theta); + var y = radiusy * Math.sin(phi); + var z = radiusz * Math.cos(phi) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createEllipsoid, detailX, detailY); + + var obj = geometry3d.generateObj(true, true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw a cylinder with given radius and height + * @method cylinder + * @param {Number} radius radius of the surface + * @param {Number} height height of the cylinder + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a spinning cylinder with radius 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * cylinder(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.cylinder = function(radius, height, detail){ + + radius = radius || 50; + height = height || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'cylinder|'+radius+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createCylinder = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(theta); + var y = 2 * height * v - height; + var z = radius * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createCylinder, detailX, detailY); + var obj = geometry3d.generateObj(true); + + var createTop = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(-theta); + var y = height; + var z = radius * Math.cos(theta); + if(v === 0){ + return new p5.Vector(0, height, 0); + } + else{ + return new p5.Vector(x, y, z); + } + }; + + var geometry3d1 = new p5.Geometry3D(); + geometry3d1.parametricGeometry( + createTop, detailX, 1); + var obj1 = geometry3d1.generateObj(); + + var createBottom = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * Math.sin(theta); + var y = -height; + var z = radius * Math.cos(theta); + if(v === 0){ + return new p5.Vector(0, -height, 0); + }else{ + return new p5.Vector(x, y, z); + } + }; + + var geometry3d2 = new p5.Geometry3D(); + geometry3d2.parametricGeometry( + createBottom, detailX, 1); + var obj2 = geometry3d2.generateObj(); + + + this._renderer.initBuffer(gId, [obj, obj1, obj2]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + + +/** + * Draw a cone with given radius and height + * @method cone + * @param {Number} radius radius of the bottom surface + * @param {Number} height height of the cone + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @example + * <div> + * <code> + * //draw a spinning cone with radius 200 and height 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * cone(200, 200); + * } + * </code> + * </div> + */ +p5.prototype.cone = function(radius, height, detail){ + + radius = radius || 50; + height = height || 50; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'cone|'+radius+'|'+height+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createCone = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * (1 - v) * Math.sin(theta); + var y = 2 * height * v - height; + var z = radius * (1 - v) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createCone, detailX, detailY); + var obj = geometry3d.generateObj(true); + + var geometry3d1 = new p5.Geometry3D(); + var createBottom = function(u, v){ + var theta = 2 * Math.PI * u; + var x = radius * (1 - v) * Math.sin(-theta); + var y = -height; + var z = radius * (1 - v) * Math.cos(theta); + return new p5.Vector(x, y, z); + }; + + geometry3d1.parametricGeometry( + createBottom, detailX, 1); + var obj1 = geometry3d1.generateObj(); + + this._renderer.initBuffer(gId, [obj, obj1]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + + +/** + * Draw a torus with given radius and tube radius + * @method torus + * @param {Number} radius radius of the whole ring + * @param {Number} tubeRadius radius of the tube + * @param {Number} [detail] number of segments, + * the more segments the smoother geometry + * default is 24. Avoid detail number above + * 150. It may crash the browser. + * @example + * <div> + * <code> + * //draw a spinning torus with radius 200 and tube radius 60 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * torus(200, 60); + * } + * </code> + * </div> + */ +p5.prototype.torus = function(radius, tubeRadius, detail){ + + radius = radius || 50; + tubeRadius = tubeRadius || 10; + + var detailX = detail || 24; + var detailY = detail || 16; + + var gId = 'torus|'+radius+'|'+tubeRadius+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createTorus = function(u, v){ + var theta = 2 * Math.PI * u; + var phi = 2 * Math.PI * v; + var x = (radius + tubeRadius * Math.cos(phi)) * Math.cos(theta); + var y = (radius + tubeRadius * Math.cos(phi)) * Math.sin(theta); + var z = tubeRadius * Math.sin(phi); + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry(createTorus, detailX, detailY); + + var obj = geometry3d.generateObj(true); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; +}; + +/** + * Draw a box with given width, height and depth + * @method box + * @param {Number} width width of the box + * @param {Number} height height of the box + * @param {Number} depth depth of the box + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //draw a spinning box with width, height and depth 200 + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.box = function(width, height, depth){ + + width = width || 50; + height = height || width; + depth = depth || width; + + //details for box are highly optional + var detailX = typeof arguments[3] === Number ? arguments[3] : 1; + var detailY = typeof arguments[4] === Number ? arguments[4] : 1; + + var gId = 'cube|'+width+'|'+height+'|'+depth+'|'+detailX+'|'+detailY; + + if(!this._renderer.geometryInHash(gId)){ + + var geometry3d = new p5.Geometry3D(); + + var createPlane1 = function(u, v){ + var x = 2 * width * u - width; + var y = 2 * height * v - height; + var z = depth; + return new p5.Vector(x, y, z); + }; + var createPlane2 = function(u, v){ + var x = 2 * width * ( 1 - u ) - width; + var y = 2 * height * v - height; + var z = -depth; + return new p5.Vector(x, y, z); + }; + var createPlane3 = function(u, v){ + var x = 2 * width * ( 1 - u ) - width; + var y = height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane4 = function(u, v){ + var x = 2 * width * u - width; + var y = -height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane5 = function(u, v){ + var x = width; + var y = 2 * height * u - height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + var createPlane6 = function(u, v){ + var x = -width; + var y = 2 * height * ( 1 - u ) - height; + var z = 2 * depth * v - depth; + return new p5.Vector(x, y, z); + }; + + geometry3d.parametricGeometry( + createPlane1, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane2, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane3, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane4, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane5, detailX, detailY, geometry3d.vertices.length); + geometry3d.parametricGeometry( + createPlane6, detailX, detailY, geometry3d.vertices.length); + + var obj = geometry3d.generateObj(); + + this._renderer.initBuffer(gId, [obj]); + } + + this._renderer.drawBuffer(gId); + + return this; + +}; + +module.exports = p5; +},{"../core/core":48,"./p5.Geometry3D":34}],29:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Camera + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Sets camera position + * @method camera + * @param {Number} x camera postion value on x axis + * @param {Number} y camera postion value on y axis + * @param {Number} z camera postion value on z axis + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * //move the camera away from the plane by a sin wave + * camera(0, 0, sin(frameCount * 0.01) * 100); + * plane(120, 120); + * } + * </code> + * </div> + */ +p5.prototype.camera = function(x, y, z){ + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'camera', + args, + ['Number', 'Number', 'Number'] + ); + //what it manipulates is the model view matrix + this._renderer.translate(-x, -y, -z); +}; + +/** + * Sets perspective camera + * @method perspective + * @param {Number} fovy camera frustum vertical field of view, + * from bottom to top of view, in degrees + * @param {Number} aspect camera frustum aspect ratio + * @param {Number} near frustum near plane length + * @param {Number} far frustum far plane length + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //drag mouse to toggle the world! + * //you will see there's a vanish point + * function setup(){ + * createCanvas(100, 100, WEBGL); + * perspective(60 / 180 * PI, width/height, 0.1, 100); + * } + * function draw(){ + * background(200); + * orbitControl(); + * for(var i = -1; i < 2; i++){ + * for(var j = -2; j < 3; j++){ + * push(); + * translate(i*160, 0, j*160); + * box(40, 40, 40); + * pop(); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.perspective = function(fovy,aspect,near,far) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'perspective', + args, + ['Number', 'Number', 'Number', 'Number'] + ); + this._renderer.uPMatrix = p5.Matrix.identity(); + this._renderer.uPMatrix.perspective(fovy,aspect,near,far); + this._renderer._setCamera = true; +}; + +/** + * Setup ortho camera + * @method ortho + * @param {Number} left camera frustum left plane + * @param {Number} right camera frustum right plane + * @param {Number} bottom camera frustum bottom plane + * @param {Number} top camera frustum top plane + * @param {Number} near camera frustum near plane + * @param {Number} far camera frustum far plane + * @return {p5} the p5 object + * @example + * <div> + * <code> + * //drag mouse to toggle the world! + * //there's no vanish point + * function setup(){ + * createCanvas(100, 100, WEBGL); + * ortho(-width/2, width/2, height/2, -height/2, 0.1, 100); + * } + * function draw(){ + * background(200); + * orbitControl(); + * for(var i = -1; i < 2; i++){ + * for(var j = -2; j < 3; j++){ + * push(); + * translate(i*160, 0, j*160); + * box(40, 40, 40); + * pop(); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.ortho = function(left,right,bottom,top,near,far) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'ortho', + args, + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ); + left /= this.width; + right /= this.width; + top /= this.height; + bottom /= this.height; + this._renderer.uPMatrix = p5.Matrix.identity(); + this._renderer.uPMatrix.ortho(left,right,bottom,top,near,far); + this._renderer._setCamera = true; +}; + +module.exports = p5; + +},{"../core/core":48}],30:[function(_dereq_,module,exports){ +//@TODO: documentation of immediate mode + +'use strict'; + +var p5 = _dereq_('../core/core'); + +////////////////////////////////////////////// +// _primitives2D in 3D space +////////////////////////////////////////////// + +p5.Renderer3D.prototype._primitives2D = function(arr){ + this._setDefaultCamera(); + var gl = this.GL; + var shaderProgram = this._getColorVertexShader(); + + //create vertice buffer + var vertexPositionBuffer = this.verticeBuffer; + gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); + + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + //create vertexcolor buffer + var vertexColorBuffer = this.colorBuffer; + gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); + var color = this._getCurColor(); + var colors = []; + for(var i = 0; i < arr.length / 3; i++){ + colors = colors.concat(color); + } + + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); + gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, + 4, gl.FLOAT, false, 0, 0); + + //matrix + var mId = 'vertexColorVert|vertexColorFrag'; + this.setMatrixUniforms(mId); +}; + +p5.Renderer3D.prototype.point = function(x, y, z){ + var gl = this.GL; + this._primitives2D([x, y, z]); + gl.drawArrays(gl.POINTS, 0, 1); + return this; +}; + +p5.Renderer3D.prototype.line = function(x1, y1, z1, x2, y2, z2){ + var gl = this.GL; + this._primitives2D([x1, y1, z1, x2, y2, z2]); + gl.drawArrays(gl.LINES, 0, 2); + return this; +}; + +p5.Renderer3D.prototype.triangle = function +(x1, y1, z1, x2, y2, z2, x3, y3, z3){ + var gl = this.GL; + this._primitives2D([x1, y1, z1, x2, y2, z2, x3, y3, z3]); + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + return this; +}; + +//@TODO: how to define the order of 4 points +p5.Renderer3D.prototype.quad = function +(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4){ + var gl = this.GL; + this._primitives2D( + [x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4]); + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + return this; +}; + +p5.Renderer3D.prototype.beginShape = function(mode){ + this.shapeMode = mode; + this.verticeStack = []; + return this; +}; + +p5.Renderer3D.prototype.vertex = function(x, y, z){ + this.verticeStack.push(x, y, z); + return this; +}; + +p5.Renderer3D.prototype.endShape = function(){ + var gl = this.GL; + this._primitives2D(this.verticeStack); + this.verticeStack = []; + + switch(this.shapeMode){ + case 'POINTS': + gl.drawArrays(gl.POINTS, 0, 1); + break; + case 'LINES': + gl.drawArrays(gl.LINES, 0, 2); + break; + case 'TRIANGLES': + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + break; + case 'TRIANGLE_STRIP': + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + break; + default: + this._strokeCheck(); + gl.drawArrays(gl.TRIANGLES, 0, 3); + break; + } + return this; +}; + +//@TODO: figure out how to actually do stroke on shapes in 3D +p5.Renderer3D.prototype._strokeCheck = function(){ + if(this.drawMode === 'stroke'){ + throw new Error( + 'stroke for shapes in 3D not yet implemented, use fill for now :(' + ); + } +}; + +//@TODO +p5.Renderer3D.prototype.strokeWeight = function() { + throw new Error('strokeWeight for 3d not yet implemented'); +}; + +////////////////////////////////////////////// +// COLOR +////////////////////////////////////////////// + +p5.Renderer3D.prototype.fill = function(r, g, b, a) { + var color = this._pInst.color.apply(this, arguments); + var colorNormalized = color._array; + this.curColor = colorNormalized; + this.drawMode = 'fill'; + return this; +}; + +p5.Renderer3D.prototype.stroke = function(r, g, b, a) { + var color = this._pInst.color.apply(this, arguments); + var colorNormalized = color._array; + this.curColor = colorNormalized; + this.drawMode = 'stroke'; + return this; +}; + +p5.Renderer3D.prototype._getColorVertexShader = function(){ + var gl = this.GL; + var mId = 'vertexColorVert|vertexColorFrag'; + var shaderProgram; + + if(!this.materialInHash(mId)){ + shaderProgram = + this.initShaders('vertexColorVert', 'vertexColorFrag', true); + this.mHash[mId] = shaderProgram; + shaderProgram.vertexColorAttribute = + gl.getAttribLocation(shaderProgram, 'aVertexColor'); + gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); + }else{ + shaderProgram = this.mHash[mId]; + } + return shaderProgram; +}; + +module.exports = p5.Renderer3D; + +},{"../core/core":48}],31:[function(_dereq_,module,exports){ +'use strict'; + +var p5 = _dereq_('../core/core'); + +//@TODO: implement full orbit controls including +//pan, zoom, quaternion rotation, etc. +p5.prototype.orbitControl = function(){ + if(this.mouseIsPressed){ + this.rotateY((this.mouseX - this.width / 2) / (this.width / 2)); + this.rotateX((this.mouseY - this.height / 2) / (this.width / 2)); + } + return this; +}; + +module.exports = p5; +},{"../core/core":48}],32:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Lights + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Creates an ambient light with a color + * @method ambientLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(150); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.ambientLight = function(v1, v2, v3, a){ + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uAmbientColor = gl.getUniformLocation( + shaderProgram, + 'uAmbientColor[' + this._renderer.ambientLightCount + ']'); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform3f( shaderProgram.uAmbientColor, + colors[0], colors[1], colors[2]); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.ambientLightCount ++; + shaderProgram.uAmbientLightCount = + gl.getUniformLocation(shaderProgram, 'uAmbientLightCount'); + gl.uniform1i(shaderProgram.uAmbientLightCount, + this._renderer.ambientLightCount); + + return this; +}; + +/** + * Creates a directional light with a color and a direction + * @method directionalLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @param {Number|p5.Vector} x x axis direction or a p5.Vector + * @param {Number} [y] optional: y axis direction + * @param {Number} [z] optional: z axis direction + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * //move your mouse to change light direction + * var dirX = (mouseX / width - 0.5) *2; + * var dirY = (mouseY / height - 0.5) *(-2); + * directionalLight(250, 250, 250, dirX, dirY, 0.25); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.directionalLight = function(v1, v2, v3, a, x, y, z) { + // TODO(jgessner): Find an example using this and profile it. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'directionalLight', + // args, + // [ + // //rgbaxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //rgbxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //caxyz + // ['Number', 'Number', 'Number', 'Number', 'Number'], + // //cxyz + // ['Number', 'Number', 'Number', 'Number'], + // ['String', 'Number', 'Number', 'Number'], + // ['Array', 'Number', 'Number', 'Number'], + // ['Object', 'Number', 'Number', 'Number'], + // //rgbavector + // ['Number', 'Number', 'Number', 'Number', 'Object'], + // //rgbvector + // ['Number', 'Number', 'Number', 'Object'], + // //cavector + // ['Number', 'Number', 'Object'], + // //cvector + // ['Number', 'Object'], + // ['String', 'Object'], + // ['Array', 'Object'], + // ['Object', 'Object'] + // ] + // ); + + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uDirectionalColor = gl.getUniformLocation( + shaderProgram, + 'uDirectionalColor[' + this._renderer.directionalLightCount + ']'); + + //@TODO: check parameters number + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, [v1, v2, v3]); + var colors = color._array; + + gl.uniform3f( shaderProgram.uDirectionalColor, + colors[0], colors[1], colors[2]); + + var _x, _y, _z; + + if(typeof arguments[arguments.length-1] === 'number'){ + _x = arguments[arguments.length-3]; + _y = arguments[arguments.length-2]; + _z = arguments[arguments.length-1]; + + }else{ + try{ + _x = arguments[arguments.length-1].x; + _y = arguments[arguments.length-1].y; + _z = arguments[arguments.length-1].z; + } + catch(error){ + throw error; + } + } + + shaderProgram.uLightingDirection = gl.getUniformLocation( + shaderProgram, + 'uLightingDirection[' + this._renderer.directionalLightCount + ']'); + gl.uniform3f( shaderProgram.uLightingDirection, _x, _y, _z); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.directionalLightCount ++; + shaderProgram.uDirectionalLightCount = + gl.getUniformLocation(shaderProgram, 'uDirectionalLightCount'); + gl.uniform1i(shaderProgram.uDirectionalLightCount, + this._renderer.directionalLightCount); + + return this; +}; + +/** + * Creates a point light with a color and a light position + * @method pointLight + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @param {Number|p5.Vector} x x axis position or a p5.Vector + * @param {Number} [y] optional: y axis position + * @param {Number} [z] optional: z axis position + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * //move your mouse to change light position + * var locY = (mouseY / height - 0.5) *(-2); + * var locX = (mouseX / width - 0.5) *2; + * //to set the light position, + * //think of the world's coordinate as: + * // -1,1 -------- 1,1 + * // | | + * // | | + * // | | + * // -1,-1---------1,-1 + * pointLight(250, 250, 250, locX, locY, 0); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.pointLight = function(v1, v2, v3, a, x, y, z) { + // TODO(jgessner): Find an example using this and profile it. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'pointLight', + // arguments, + // [ + // //rgbaxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //rgbxyz + // ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + // //caxyz + // ['Number', 'Number', 'Number', 'Number', 'Number'], + // //cxyz + // ['Number', 'Number', 'Number', 'Number'], + // ['String', 'Number', 'Number', 'Number'], + // ['Array', 'Number', 'Number', 'Number'], + // ['Object', 'Number', 'Number', 'Number'], + // //rgbavector + // ['Number', 'Number', 'Number', 'Number', 'Object'], + // //rgbvector + // ['Number', 'Number', 'Number', 'Object'], + // //cavector + // ['Number', 'Number', 'Object'], + // //cvector + // ['Number', 'Object'], + // ['String', 'Object'], + // ['Array', 'Object'], + // ['Object', 'Object'] + // ] + // ); + + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader( + 'lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uPointLightColor = gl.getUniformLocation( + shaderProgram, + 'uPointLightColor[' + this._renderer.pointLightCount + ']'); + + //@TODO: check parameters number + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, [v1, v2, v3]); + var colors = color._array; + + gl.uniform3f( shaderProgram.uPointLightColor, + colors[0], colors[1], colors[2]); + + var _x, _y, _z; + + if(typeof arguments[arguments.length-1] === 'number'){ + _x = arguments[arguments.length-3]; + _y = arguments[arguments.length-2]; + _z = arguments[arguments.length-1]; + + }else{ + try{ + _x = arguments[arguments.length-1].x; + _y = arguments[arguments.length-1].y; + _z = arguments[arguments.length-1].z; + } + catch(error){ + throw error; + } + } + + shaderProgram.uPointLightLocation = gl.getUniformLocation( + shaderProgram, + 'uPointLightLocation[' + this._renderer.pointLightCount + ']'); + gl.uniform3f( shaderProgram.uPointLightLocation, _x, _y, _z); + + //in case there's no material color for the geometry + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + gl.uniform4f( shaderProgram.uMaterialColor, 1, 1, 1, 1); + + this._renderer.pointLightCount ++; + shaderProgram.uPointLightCount = + gl.getUniformLocation(shaderProgram, 'uPointLightCount'); + gl.uniform1i(shaderProgram.uPointLightCount, + this._renderer.pointLightCount); + + return this; +}; + +module.exports = p5; + +},{"../core/core":48}],33:[function(_dereq_,module,exports){ +/** + * @module Lights, Camera + * @submodule Material + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +//require('./p5.Texture'); + +/** + * Normal material for geometry + * @method normalMaterial + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(0); + * normalMaterial(); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.normalMaterial = function(){ + this._renderer._getShader('normalVert', 'normalFrag'); + return this; +}; + +/** + * Texture for geometry + * @method texture + * @return {p5} the p5 object + * @example + * <div> + * <code> + * var img; + * function setup(){ + * createCanvas(100, 100, WEBGL); + * img = loadImage("assets/laDefense.jpg"); + * } + * + * function draw(){ + * background(0); + * rotateZ(frameCount * 0.01); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * //pass image as texture + * texture(img); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.texture = function(image){ + var gl = this._renderer.GL; + var shaderProgram = this._renderer._getShader('lightVert', + 'lightTextureFrag'); + gl.useProgram(shaderProgram); + if (image instanceof p5.Image) { + //check if image is already used as texture + if(!image.isTexture){ + //createTexture and set isTexture to true + var tex = gl.createTexture(); + image.createTexture(tex); + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); + image._setProperty('isTexture', true); + } + //otherwise we're good to bind texture without creating + //a new one on the gl + else { + //TODO + } + image.loadPixels(); + var data = new Uint8Array(image.pixels); + gl.texImage2D(gl.TEXTURE_2D, 0, + gl.RGBA, image.width, image.height, + 0, gl.RGBA, gl.UNSIGNED_BYTE, data); + } + //if param is a video + else if (image instanceof p5.MediaElement){ + if(!image.loadedmetadata) {return;} + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, + gl.UNSIGNED_BYTE, image.elt); + } + else { + //@TODO handle following cases: + //- 2D canvas (p5 inst) + } + if (_isPowerOf2(image.width) && _isPowerOf2(image.height)) { + gl.generateMipmap(gl.TEXTURE_2D); + } else { + //@TODO this is problematic + //image.width = _nextHighestPOT(image.width); + //image.height = _nextHighestPOT(image.height); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, + gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + //this is where we'd activate multi textures + //eg. gl.activeTexture(gl.TEXTURE0 + (unit || 0)); + //but for now we just have a single texture. + //@TODO need to extend this functionality + //gl.activeTexture(gl.TEXTURE0 + 0); + //gl.bindTexture(gl.TEXTURE_2D, tex); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'uSampler'), 0); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), true); + return this; +}; + +/** + * Helper functions; Checks whether val is a pot + * more info on power of 2 here: + * https://www.opengl.org/wiki/NPOT_Texture + * @param {Number} value + * @return {Boolean} + */ +function _isPowerOf2 (value){ + return (value & (value - 1)) === 0; +} + +/** + * returns the next highest power of 2 value + * @param {Number} value [description] + * @return {Number} [description] + */ +// function _nextHighestPOT (value){ +// --value; +// for (var i = 1; i < 32; i <<= 1) { +// value = value | value >> i; +// } +// return value + 1; +// } + +/** + * Basic material for geometry with a given color + * @method basicMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(0); + * basicMaterial(250, 0, 0); + * rotateX(frameCount * 0.01); + * rotateY(frameCount * 0.01); + * rotateZ(frameCount * 0.01); + * box(200, 200, 200); + * } + * </code> + * </div> + */ +p5.prototype.basicMaterial = function(v1, v2, v3, a){ + var gl = this._renderer.GL; + + var shaderProgram = this._renderer._getShader('normalVert', 'basicFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f( shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + return this; + +}; + +/** + * Ambient material for geometry with a given color + * @method ambientMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity +* @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(100); + * pointLight(250, 250, 250, 100, 100, 0); + * ambientMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.ambientMaterial = function(v1, v2, v3, a) { + var gl = this._renderer.GL; + var shaderProgram = + this._renderer._getShader('lightVert', 'lightTextureFrag'); + + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f(shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + shaderProgram.uSpecular = gl.getUniformLocation( + shaderProgram, 'uSpecular' ); + gl.uniform1i(shaderProgram.uSpecular, false); + + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), false); + + return this; +}; + +/** + * Specular material for geometry with a given color + * @method specularMaterial + * @param {Number|Array|String|p5.Color} v1 gray value, + * red or hue value (depending on the current color mode), + * or color Array, or CSS color string + * @param {Number} [v2] optional: green or saturation value + * @param {Number} [v3] optional: blue or brightness value + * @param {Number} [a] optional: opacity + * @return {p5} the p5 object + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * function draw(){ + * background(0); + * ambientLight(100); + * pointLight(250, 250, 250, 100, 100, 0); + * specularMaterial(250); + * sphere(200); + * } + * </code> + * </div> + */ +p5.prototype.specularMaterial = function(v1, v2, v3, a) { + var gl = this._renderer.GL; + var shaderProgram = + this._renderer._getShader('lightVert', 'lightTextureFrag'); + gl.uniform1i(gl.getUniformLocation(shaderProgram, 'isTexture'), false); + gl.useProgram(shaderProgram); + shaderProgram.uMaterialColor = gl.getUniformLocation( + shaderProgram, 'uMaterialColor' ); + + var color = this._renderer._pInst.color.apply( + this._renderer._pInst, arguments); + var colors = color._array; + + gl.uniform4f(shaderProgram.uMaterialColor, + colors[0], colors[1], colors[2], colors[3]); + + shaderProgram.uSpecular = gl.getUniformLocation( + shaderProgram, 'uSpecular' ); + gl.uniform1i(shaderProgram.uSpecular, true); + + return this; +}; + +module.exports = p5; + +},{"../core/core":48}],34:[function(_dereq_,module,exports){ +//some of the functions are adjusted from Three.js(http://threejs.org) + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * p5 Geometry3D class + */ +p5.Geometry3D = function(){ + //an array holding every vertice + //each vertex is a p5.Vector + this.vertices = []; + //an array holding each normals for each vertice + //each normal is a p5.Vector + this.vertexNormals = []; + //an array holding each three indecies of vertices that form a face + //[[0, 1, 2], [1, 2, 3], ...] + this.faces = []; + //an array holding every noraml for each face + //each faceNormal is a p5.Vector + //[[p5.Vector, p5.Vector, p5.Vector],[p5.Vector, p5.Vector, p5.Vector],...] + this.faceNormals = []; + //an array of array holding uvs (group according to faces) + //[[[0, 0], [1, 0], [1, 0]],...] + this.uvs = []; +}; + +/** + * generate geometriy with parametric method + * @param {Function} func callback function for how to generate geometry + * @param {Number} detailX number of vertices on horizontal surface + * @param {Number} detailY number of vertices on horizontal surface + * @param {Number} offset offset of vertices index + */ +p5.Geometry3D.prototype.parametricGeometry = function +//@TODO: put func as the last parameters +(func, detailX, detailY, offset){ + + var i, j, p; + var u, v; + offset = offset || 0; + this.detailX = detailX; + this.detailY = detailY; + + var sliceCount = detailX + 1; + for (i = 0; i <= detailY; i++){ + v = i / detailY; + for (j = 0; j <= detailX; j++){ + u = j / detailX; + p = func(u, v); + this.vertices.push(p); + } + } + + var a, b, c, d; + var uva, uvb, uvc, uvd; + + for (i = 0; i < detailY; i++){ + for (j = 0; j < detailX; j++){ + a = i * sliceCount + j + offset; + b = i * sliceCount + j + 1 + offset; + c = (i + 1)* sliceCount + j + 1 + offset; + d = (i + 1)* sliceCount + j + offset; + + uva = [j/detailX, i/detailY]; + uvb = [(j + 1)/ detailX, i/detailY]; + uvc = [(j + 1)/ detailX, (i + 1)/detailY]; + uvd = [j/detailX, (i + 1)/detailY]; + + this.faces.push([a, b, d]); + this.uvs.push([uva, uvb, uvd]); + + this.faces.push([b, c, d]); + this.uvs.push([uvb, uvc, uvd]); + } + } +}; + +/** + * compute faceNormals for a geometry + */ +p5.Geometry3D.prototype.computeFaceNormals = function(){ + + var cb = new p5.Vector(); + var ab = new p5.Vector(); + + for (var f = 0; f < this.faces.length; f++){ + var face = this.faces[f]; + var vA = this.vertices[face[0]]; + var vB = this.vertices[face[1]]; + var vC = this.vertices[face[2]]; + + p5.Vector.sub(vC, vB, cb); + p5.Vector.sub(vA, vB, ab); + + var normal = p5.Vector.cross(ab, cb); + normal.normalize(); + normal.mult(-1); + this.faceNormals[f] = normal; + } + +}; + +/** + * compute vertexNormals for a geometry + */ +p5.Geometry3D.prototype.computeVertexNormals = function (){ + + var v, f, face, faceNormal, vertices; + var vertexNormals = []; + + vertices = new Array(this.vertices.length); + for (v = 0; v < this.vertices.length; v++) { + vertices[v] = new p5.Vector(); + } + + for (f = 0; f < this.faces.length; f++) { + face = this.faces[f]; + faceNormal = this.faceNormals[f]; + + vertices[face[0]].add(faceNormal); + vertices[face[1]].add(faceNormal); + vertices[face[2]].add(faceNormal); + } + + for (v = 0; v < this.vertices.length; v++) { + vertices[v].normalize(); + } + + for (f = 0; f < this.faces.length; f++) { + face = this.faces[f]; + vertexNormals[f] = []; + vertexNormals[f][0]= vertices[face[0]].copy(); + vertexNormals[f][1]= vertices[face[1]].copy(); + vertexNormals[f][2]= vertices[face[2]].copy(); + } + + for (f = 0; f < this.faces.length; f++){ + face = this.faces[f]; + faceNormal = this.faceNormals[f]; + this.vertexNormals[face[0]] = vertexNormals[f][0]; + this.vertexNormals[face[1]] = vertexNormals[f][1]; + this.vertexNormals[face[2]] = vertexNormals[f][2]; + } + +}; + +p5.Geometry3D.prototype.averageNormals = function() { + + for(var i = 0; i <= this.detailY; i++){ + var offset = this.detailX + 1; + var temp = p5.Vector + .add(this.vertexNormals[i*offset], + this.vertexNormals[i*offset + this.detailX]); + temp = p5.Vector.div(temp, 2); + this.vertexNormals[i*offset] = temp; + this.vertexNormals[i*offset + this.detailX] = temp; + } +}; + +p5.Geometry3D.prototype.averagePoleNormals = function() { + + //average the north pole + var sum = new p5.Vector(0, 0, 0); + for(var i = 0; i < this.detailX; i++){ + sum.add(this.vertexNormals[i]); + } + sum = p5.Vector.div(sum, this.detailX); + + for(i = 0; i < this.detailX; i++){ + this.vertexNormals[i] = sum; + } + + //average the south pole + sum = new p5.Vector(0, 0, 0); + for(i = this.vertices.length - 1; + i > this.vertices.length - 1 - this.detailX; i--){ + sum.add(this.vertexNormals[i]); + } + sum = p5.Vector.div(sum, this.detailX); + + for(i = this.vertices.length - 1; + i > this.vertices.length - 1 - this.detailX; i--){ + this.vertexNormals[i] = sum; + } +}; + +/** + * [generateUV description] + * @param {Array} faces [description] + * @param {Array} uvs [description] + */ +p5.Geometry3D.prototype.generateUV = function(faces, uvs){ + + faces = flatten(faces); + uvs = flatten(uvs); + var arr = []; + faces.forEach(function(item, index){ + arr[item] = uvs[index]; + }); + return flatten(arr); +}; + + +/** + * generate an object containing information needed to create buffer + */ +p5.Geometry3D.prototype.generateObj = function(average, sphere){ + + this.computeFaceNormals(); + this.computeVertexNormals(); + + if(average){ + this.averageNormals(); + } + + if(sphere){ + this.averagePoleNormals(); + } + + var obj = { + vertices: turnVectorArrayIntoNumberArray(this.vertices), + vertexNormals: turnVectorArrayIntoNumberArray(this.vertexNormals), + uvs: this.generateUV(this.faces, this.uvs), + faces: flatten(this.faces), + len: this.faces.length * 3 + }; + return obj; +}; + +/** + * turn a two dimensional array into one dimensional array + * @param {Array} arr 2-dimensional array + * @return {Array} 1-dimensional array + * [[1, 2, 3],[4, 5, 6]] -> [1, 2, 3, 4, 5, 6] + */ +function flatten(arr){ + return arr.reduce(function(a, b){ + return a.concat(b); + }); +} + +/** + * turn an array of Vector into a one dimensional array of numbers + * @param {Array} arr an array of p5.Vector + * @return {Array]} a one dimensional array of numbers + * [p5.Vector(1, 2, 3), p5.Vector(4, 5, 6)] -> + * [1, 2, 3, 4, 5, 6] + */ +function turnVectorArrayIntoNumberArray(arr){ + return flatten(arr.map(function(item){ + return [item.x, item.y, item.z]; + })); +} + +module.exports = p5.Geometry3D; +},{"../core/core":48}],35:[function(_dereq_,module,exports){ +/** +* @requires constants +* @todo see methods below needing further implementation. +* future consideration: implement SIMD optimizations +* when browser compatibility becomes available +* https://developer.mozilla.org/en-US/docs/Web/JavaScript/ +* Reference/Global_Objects/SIMD +*/ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('../math/polargeometry'); +var constants = _dereq_('../core/constants'); +var GLMAT_ARRAY_TYPE = ( + typeof Float32Array !== 'undefined') ? + Float32Array : Array; + +/** + * A class to describe a 4x4 matrix + * for model and view matrix manipulation in the p5js webgl renderer. + * class p5.Matrix + * @constructor + * @param {Array} [mat4] array literal of our 4x4 matrix + */ +p5.Matrix = function() { + // This is how it comes in with createMatrix() + if(arguments[0] instanceof p5) { + // save reference to p5 if passed in + this.p5 = arguments[0]; + this.mat4 = arguments[1] || new GLMAT_ARRAY_TYPE([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + // This is what we'll get with new p5.Matrix() + // a mat4 identity matrix + } else { + this.mat4 = arguments[0] || new GLMAT_ARRAY_TYPE([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + } + return this; +}; + +/** + * Sets the x, y, and z component of the vector using two or three separate + * variables, the data from a p5.Matrix, or the values from a float array. + * + * @param {p5.Matrix|Array} [inMatrix] the input p5.Matrix or + * an Array of length 16 + */ +p5.Matrix.prototype.set = function (inMatrix) { + if (inMatrix instanceof p5.Matrix) { + this.mat4 = inMatrix.mat4; + return this; + } + else if (inMatrix instanceof GLMAT_ARRAY_TYPE) { + this.mat4 = inMatrix; + return this; + } + return this; +}; + +/** + * Gets a copy of the vector, returns a p5.Matrix object. + * + * @return {p5.Matrix} the copy of the p5.Matrix object + */ +p5.Matrix.prototype.get = function () { + return new p5.Matrix(this.mat4); +}; + +/** + * return a copy of a matrix + * @return {p5.Matrix} the result matrix + */ +p5.Matrix.prototype.copy = function(){ + var copied = new p5.Matrix(); + copied.mat4[0] = this.mat4[0]; + copied.mat4[1] = this.mat4[1]; + copied.mat4[2] = this.mat4[2]; + copied.mat4[3] = this.mat4[3]; + copied.mat4[4] = this.mat4[4]; + copied.mat4[5] = this.mat4[5]; + copied.mat4[6] = this.mat4[6]; + copied.mat4[7] = this.mat4[7]; + copied.mat4[8] = this.mat4[8]; + copied.mat4[9] = this.mat4[9]; + copied.mat4[10] = this.mat4[10]; + copied.mat4[11] = this.mat4[11]; + copied.mat4[12] = this.mat4[12]; + copied.mat4[13] = this.mat4[13]; + copied.mat4[14] = this.mat4[14]; + copied.mat4[15] = this.mat4[15]; + return copied; +}; + +/** + * return an identity matrix + * @return {p5.Matrix} the result matrix + */ +p5.Matrix.identity = function(){ + return new p5.Matrix(); +}; + +/** + * transpose according to a given matrix + * @param {p5.Matrix | Typed Array} a the matrix to be based on to transpose + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.transpose = function(a){ + var a01, a02, a03, a12, a13, a23; + if(a instanceof p5.Matrix){ + a01 = a.mat4[1]; + a02 = a.mat4[2]; + a03 = a.mat4[3]; + a12 = a.mat4[6]; + a13 = a.mat4[7]; + a23 = a.mat4[11]; + + this.mat4[0] = a.mat4[0]; + this.mat4[1] = a.mat4[4]; + this.mat4[2] = a.mat4[8]; + this.mat4[3] = a.mat4[12]; + this.mat4[4] = a01; + this.mat4[5] = a.mat4[5]; + this.mat4[6] = a.mat4[9]; + this.mat4[7] = a.mat4[13]; + this.mat4[8] = a02; + this.mat4[9] = a12; + this.mat4[10] = a.mat4[10]; + this.mat4[11] = a.mat4[14]; + this.mat4[12] = a03; + this.mat4[13] = a13; + this.mat4[14] = a23; + this.mat4[15] = a.mat4[15]; + + }else if(a instanceof GLMAT_ARRAY_TYPE){ + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a12 = a[6]; + a13 = a[7]; + a23 = a[11]; + + this.mat4[0] = a[0]; + this.mat4[1] = a[4]; + this.mat4[2] = a[8]; + this.mat4[3] = a[12]; + this.mat4[4] = a01; + this.mat4[5] = a[5]; + this.mat4[6] = a[9]; + this.mat4[7] = a[13]; + this.mat4[8] = a02; + this.mat4[9] = a12; + this.mat4[10] = a[10]; + this.mat4[11] = a[14]; + this.mat4[12] = a03; + this.mat4[13] = a13; + this.mat4[14] = a23; + this.mat4[15] = a[15]; + } + return this; +}; + +/** + * invert matrix according to a give matrix + * @param {p5.Matrix or Typed Array} a the matrix to be based on to invert + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.invert = function(a){ + var a00, a01, a02, a03, a10, a11, a12, a13, + a20, a21, a22, a23, a30, a31, a32, a33; + if(a instanceof p5.Matrix){ + a00 = a.mat4[0]; + a01 = a.mat4[1]; + a02 = a.mat4[2]; + a03 = a.mat4[3]; + a10 = a.mat4[4]; + a11 = a.mat4[5]; + a12 = a.mat4[6]; + a13 = a.mat4[7]; + a20 = a.mat4[8]; + a21 = a.mat4[9]; + a22 = a.mat4[10]; + a23 = a.mat4[11]; + a30 = a.mat4[12]; + a31 = a.mat4[13]; + a32 = a.mat4[14]; + a33 = a.mat4[15]; + }else if(a instanceof GLMAT_ARRAY_TYPE){ + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; + a30 = a[12]; + a31 = a[13]; + a32 = a[14]; + a33 = a[15]; + } + var b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + // Calculate the determinant + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - + b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + this.mat4[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + this.mat4[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + this.mat4[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + this.mat4[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + this.mat4[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + this.mat4[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + this.mat4[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + this.mat4[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + this.mat4[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + this.mat4[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + this.mat4[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + this.mat4[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + this.mat4[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + this.mat4[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + this.mat4[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + this.mat4[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + + return this; +}; + +/** + * inspired by Toji's mat4 determinant + * @return {Number} Determinant of our 4x4 matrix + */ +p5.Matrix.prototype.determinant = function(){ + var d00 = (this.mat4[0] * this.mat4[5]) - (this.mat4[1] * this.mat4[4]), + d01 = (this.mat4[0] * this.mat4[6]) - (this.mat4[2] * this.mat4[4]), + d02 = (this.mat4[0] * this.mat4[7]) - (this.mat4[3] * this.mat4[4]), + d03 = (this.mat4[1] * this.mat4[6]) - (this.mat4[2] * this.mat4[5]), + d04 = (this.mat4[1] * this.mat4[7]) - (this.mat4[3] * this.mat4[5]), + d05 = (this.mat4[2] * this.mat4[7]) - (this.mat4[3] * this.mat4[6]), + d06 = (this.mat4[8] * this.mat4[13]) - (this.mat4[9] * this.mat4[12]), + d07 = (this.mat4[8] * this.mat4[14]) - (this.mat4[10] * this.mat4[12]), + d08 = (this.mat4[8] * this.mat4[15]) - (this.mat4[11] * this.mat4[12]), + d09 = (this.mat4[9] * this.mat4[14]) - (this.mat4[10] * this.mat4[13]), + d10 = (this.mat4[9] * this.mat4[15]) - (this.mat4[11] * this.mat4[13]), + d11 = (this.mat4[10] * this.mat4[15]) - (this.mat4[11] * this.mat4[14]); + + // Calculate the determinant + return d00 * d11 - d01 * d10 + d02 * d09 + + d03 * d08 - d04 * d07 + d05 * d06; +}; + +/** + * multiply two mat4s + * @param {p5.Matrix | Array} multMatrix The matrix we want to multiply by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.mult = function(multMatrix){ + var _dest = new GLMAT_ARRAY_TYPE(16); + var _src = new GLMAT_ARRAY_TYPE(16); + + if(multMatrix instanceof p5.Matrix) { + _src = multMatrix.mat4; + } + else if(multMatrix instanceof GLMAT_ARRAY_TYPE){ + _src = multMatrix; + } + + // each row is used for the multiplier + var b0 = this.mat4[0], b1 = this.mat4[1], + b2 = this.mat4[2], b3 = this.mat4[3]; + _dest[0] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[1] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[2] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[3] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[4]; + b1 = this.mat4[5]; + b2 = this.mat4[6]; + b3 = this.mat4[7]; + _dest[4] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[5] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[6] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[7] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[8]; + b1 = this.mat4[9]; + b2 = this.mat4[10]; + b3 = this.mat4[11]; + _dest[8] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[9] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[10] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[11] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + b0 = this.mat4[12]; + b1 = this.mat4[13]; + b2 = this.mat4[14]; + b3 = this.mat4[15]; + _dest[12] = b0*_src[0] + b1*_src[4] + b2*_src[8] + b3*_src[12]; + _dest[13] = b0*_src[1] + b1*_src[5] + b2*_src[9] + b3*_src[13]; + _dest[14] = b0*_src[2] + b1*_src[6] + b2*_src[10] + b3*_src[14]; + _dest[15] = b0*_src[3] + b1*_src[7] + b2*_src[11] + b3*_src[15]; + + this.mat4 = _dest; + + return this; +}; + +/** + * scales a p5.Matrix by scalars or a vector + * @param {p5.Vector | Array } + * vector to scale by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.scale = function() { + var x,y,z; + var args = new Array(arguments.length); + for(var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + //if our 1st arg is a type p5.Vector + if (args[0] instanceof p5.Vector){ + x = args[0].x; + y = args[0].y; + z = args[0].z; + } + //otherwise if it's an array + else if (args[0] instanceof Array){ + x = args[0][0]; + y = args[0][1]; + z = args[0][2]; + } + var _dest = new GLMAT_ARRAY_TYPE(16); + _dest[0] = this.mat4[0] * x; + _dest[1] = this.mat4[1] * x; + _dest[2] = this.mat4[2] * x; + _dest[3] = this.mat4[3] * x; + _dest[4] = this.mat4[4] * y; + _dest[5] = this.mat4[5] * y; + _dest[6] = this.mat4[6] * y; + _dest[7] = this.mat4[7] * y; + _dest[8] = this.mat4[8] * z; + _dest[9] = this.mat4[9] * z; + _dest[10] = this.mat4[10] * z; + _dest[11] = this.mat4[11] * z; + _dest[12] = this.mat4[12]; + _dest[13] = this.mat4[13]; + _dest[14] = this.mat4[14]; + _dest[15] = this.mat4[15]; + + this.mat4 = _dest; + return this; +}; + +/** + * rotate our Matrix around an axis by the given angle. + * @param {Number} a The angle of rotation in radians + * @param {p5.Vector | Array} axis the axis(es) to rotate around + * @return {p5.Matrix} this + * inspired by Toji's gl-matrix lib, mat4 rotation + */ +p5.Matrix.prototype.rotate = function(a, axis){ + var x, y, z, _a, len; + + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + _a = polarGeometry.degreesToRadians(a); + } + } + else { + _a = a; + } + if (axis instanceof p5.Vector) { + x = axis.x; + y = axis.y; + z = axis.z; + } + else if (axis instanceof Array) { + x = axis[0]; + y = axis[1]; + z = axis[2]; + } + + len = Math.sqrt(x * x + y * y + z * z); + x *= (1/len); + y *= (1/len); + z *= (1/len); + + var a00 = this.mat4[0]; + var a01 = this.mat4[1]; + var a02 = this.mat4[2]; + var a03 = this.mat4[3]; + var a10 = this.mat4[4]; + var a11 = this.mat4[5]; + var a12 = this.mat4[6]; + var a13 = this.mat4[7]; + var a20 = this.mat4[8]; + var a21 = this.mat4[9]; + var a22 = this.mat4[10]; + var a23 = this.mat4[11]; + + //sin,cos, and tan of respective angle + var sA = Math.sin(_a); + var cA = Math.cos(_a); + var tA = 1 - cA; + // Construct the elements of the rotation matrix + var b00 = x * x * tA + cA; + var b01 = y * x * tA + z * sA; + var b02 = z * x * tA - y * sA; + var b10 = x * y * tA - z * sA; + var b11 = y * y * tA + cA; + var b12 = z * y * tA + x * sA; + var b20 = x * z * tA + y * sA; + var b21 = y * z * tA - x * sA; + var b22 = z * z * tA + cA; + + // rotation-specific matrix multiplication + this.mat4[0] = a00 * b00 + a10 * b01 + a20 * b02; + this.mat4[1] = a01 * b00 + a11 * b01 + a21 * b02; + this.mat4[2] = a02 * b00 + a12 * b01 + a22 * b02; + this.mat4[3] = a03 * b00 + a13 * b01 + a23 * b02; + this.mat4[4] = a00 * b10 + a10 * b11 + a20 * b12; + this.mat4[5] = a01 * b10 + a11 * b11 + a21 * b12; + this.mat4[6] = a02 * b10 + a12 * b11 + a22 * b12; + this.mat4[7] = a03 * b10 + a13 * b11 + a23 * b12; + this.mat4[8] = a00 * b20 + a10 * b21 + a20 * b22; + this.mat4[9] = a01 * b20 + a11 * b21 + a21 * b22; + this.mat4[10] = a02 * b20 + a12 * b21 + a22 * b22; + this.mat4[11] = a03 * b20 + a13 * b21 + a23 * b22; + + return this; +}; + +/** + * @todo finish implementing this method! + * translates + * @param {Array} v vector to translate by + * @return {p5.Matrix} this + */ +p5.Matrix.prototype.translate = function(v){ + var x = v[0], + y = v[1], + z = v[2] || 0; + this.mat4[12] = + this.mat4[0] * x +this.mat4[4] * y +this.mat4[8] * z +this.mat4[12]; + this.mat4[13] = + this.mat4[1] * x +this.mat4[5] * y +this.mat4[9] * z +this.mat4[13]; + this.mat4[14] = + this.mat4[2] * x +this.mat4[6] * y +this.mat4[10] * z +this.mat4[14]; + this.mat4[15] = + this.mat4[3] * x +this.mat4[7] * y +this.mat4[11] * z +this.mat4[15]; +}; + +p5.Matrix.prototype.rotateX = function(a){ + this.rotate(a, [1,0,0]); +}; +p5.Matrix.prototype.rotateY = function(a){ + this.rotate(a, [0,1,0]); +}; +p5.Matrix.prototype.rotateZ = function(a){ + this.rotate(a, [0,0,1]); +}; + +/** + * sets the perspective matrix + * @param {Number} fovy [description] + * @param {Number} aspect [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @return {void} + */ +p5.Matrix.prototype.perspective = function(fovy,aspect,near,far){ + + var f = 1.0 / Math.tan(fovy / 2), + nf = 1 / (near - far); + + this.mat4[0] = f / aspect; + this.mat4[1] = 0; + this.mat4[2] = 0; + this.mat4[3] = 0; + this.mat4[4] = 0; + this.mat4[5] = f; + this.mat4[6] = 0; + this.mat4[7] = 0; + this.mat4[8] = 0; + this.mat4[9] = 0; + this.mat4[10] = (far + near) * nf; + this.mat4[11] = -1; + this.mat4[12] = 0; + this.mat4[13] = 0; + this.mat4[14] = (2 * far * near) * nf; + this.mat4[15] = 0; + + return this; + +}; + +/** + * sets the ortho matrix + * @param {Number} left [description] + * @param {Number} right [description] + * @param {Number} bottom [description] + * @param {Number} top [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @return {void} + */ +p5.Matrix.prototype.ortho = function(left,right,bottom,top,near,far){ + + var lr = 1 / (left - right), + bt = 1 / (bottom - top), + nf = 1 / (near - far); + this.mat4[0] = -2 * lr; + this.mat4[1] = 0; + this.mat4[2] = 0; + this.mat4[3] = 0; + this.mat4[4] = 0; + this.mat4[5] = -2 * bt; + this.mat4[6] = 0; + this.mat4[7] = 0; + this.mat4[8] = 0; + this.mat4[9] = 0; + this.mat4[10] = 2 * nf; + this.mat4[11] = 0; + this.mat4[12] = (left + right) * lr; + this.mat4[13] = (top + bottom) * bt; + this.mat4[14] = (far + near) * nf; + this.mat4[15] = 1; + + return this; +}; + +/** + * PRIVATE + */ +// matrix methods adapted from: +// https://developer.mozilla.org/en-US/docs/Web/WebGL/ +// gluPerspective +// +// function _makePerspective(fovy, aspect, znear, zfar){ +// var ymax = znear * Math.tan(fovy * Math.PI / 360.0); +// var ymin = -ymax; +// var xmin = ymin * aspect; +// var xmax = ymax * aspect; +// return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar); +// } + +//// +//// glFrustum +//// +//function _makeFrustum(left, right, bottom, top, znear, zfar){ +// var X = 2*znear/(right-left); +// var Y = 2*znear/(top-bottom); +// var A = (right+left)/(right-left); +// var B = (top+bottom)/(top-bottom); +// var C = -(zfar+znear)/(zfar-znear); +// var D = -2*zfar*znear/(zfar-znear); +// var frustrumMatrix =[ +// X, 0, A, 0, +// 0, Y, B, 0, +// 0, 0, C, D, +// 0, 0, -1, 0 +//]; +//return frustrumMatrix; +// } + +// function _setMVPMatrices(){ +////an identity matrix +////@TODO use the p5.Matrix class to abstract away our MV matrices and +///other math +//var _mvMatrix = +//[ +// 1.0,0.0,0.0,0.0, +// 0.0,1.0,0.0,0.0, +// 0.0,0.0,1.0,0.0, +// 0.0,0.0,0.0,1.0 +//]; + +module.exports = p5.Matrix; +},{"../core/constants":47,"../core/core":48,"../math/polargeometry":77}],36:[function(_dereq_,module,exports){ +'use strict'; + +var p5 = _dereq_('../core/core'); +var shader = _dereq_('./shader'); +_dereq_('../core/p5.Renderer'); +_dereq_('./p5.Matrix'); +var uMVMatrixStack = []; +var RESOLUTION = 1000; + +//@TODO should probably implement an override for these attributes +var attributes = { + alpha: true, + depth: true, + stencil: true, + antialias: false, + premultipliedAlpha: false, + preserveDrawingBuffer: false +}; + +/** + * 3D graphics class. Can also be used as an off-screen graphics buffer. + * A p5.Renderer3D object can be constructed + * + */ +p5.Renderer3D = function(elt, pInst, isMainCanvas) { + p5.Renderer.call(this, elt, pInst, isMainCanvas); + + try { + this.drawingContext = this.canvas.getContext('webgl', attributes) || + this.canvas.getContext('experimental-webgl', attributes); + if (this.drawingContext === null) { + throw new Error('Error creating webgl context'); + } else { + console.log('p5.Renderer3D: enabled webgl context'); + } + } catch (er) { + throw new Error(er); + } + + this.isP3D = true; //lets us know we're in 3d mode + this.GL = this.drawingContext; + var gl = this.GL; + gl.clearColor(1.0, 1.0, 1.0, 1.0); //background is initialized white + gl.clearDepth(1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + this._init(); + return this; +}; + +p5.Renderer3D.prototype = Object.create(p5.Renderer.prototype); + +p5.Renderer3D.prototype._applyDefaults = function() { + return this; +}; + +////////////////////////////////////////////// +// Setting +////////////////////////////////////////////// + +p5.Renderer3D.prototype._init = function(first_argument) { + var gl = this.GL; + //for our default matrices + this.initMatrix(); + this.initHash(); + //for immedidate mode + this.verticeStack = []; + this.verticeBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + //for camera + this._setCamera = false; + //for counting lights + this.ambientLightCount = 0; + this.directionalLightCount = 0; + this.pointLightCount = 0; +}; + +p5.Renderer3D.prototype._update = function() { + this.resetMatrix(); + this.translate(0, 0, -800); + this.ambientLightCount = 0; + this.directionalLightCount = 0; + this.pointLightCount = 0; + this.verticeStack = []; +}; + +/** + * [resize description] + * @param {[type]} w [description] + * @param {[tyoe]} h [description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.resize = function(w,h) { + var gl = this.GL; + p5.Renderer.prototype.resize.call(this, w, h); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); +}; + +/** + * [background description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.background = function() { + var gl = this.GL; + var _col = this._pInst.color.apply(this._pInst, arguments); + // gl.clearColor(0.0,0.0,0.0,1.0); + var _r = (_col.levels[0]) / 255; + var _g = (_col.levels[1]) / 255; + var _b = (_col.levels[2]) / 255; + var _a = (_col.levels[3]) / 255; + gl.clearColor(_r, _g, _b, _a); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); +}; + +//@TODO implement this +// p5.Renderer3D.prototype.clear = function() { +//@TODO +// }; + +////////////////////////////////////////////// +// SHADER +////////////////////////////////////////////// + +/** + * [initShaders description] + * @param {[type]} vertId [description] + * @param {[type]} fragId [description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.initShaders = function(vertId, fragId, immediateMode) { + var gl = this.GL; + //set up our default shaders by: + // 1. create the shader, + // 2. load the shader source, + // 3. compile the shader + var _vertShader = gl.createShader(gl.VERTEX_SHADER); + //load in our default vertex shader + gl.shaderSource(_vertShader, shader[vertId]); + gl.compileShader(_vertShader); + // if our vertex shader failed compilation? + if (!gl.getShaderParameter(_vertShader, gl.COMPILE_STATUS)) { + alert('Yikes! An error occurred compiling the shaders:' + + gl.getShaderInfoLog(_vertShader)); + return null; + } + + var _fragShader = gl.createShader(gl.FRAGMENT_SHADER); + //load in our material frag shader + gl.shaderSource(_fragShader, shader[fragId]); + gl.compileShader(_fragShader); + // if our frag shader failed compilation? + if (!gl.getShaderParameter(_fragShader, gl.COMPILE_STATUS)) { + alert('Darn! An error occurred compiling the shaders:' + + gl.getShaderInfoLog(_fragShader)); + return null; + } + + var shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, _vertShader); + gl.attachShader(shaderProgram, _fragShader); + gl.linkProgram(shaderProgram); + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + alert('Snap! Error linking shader program'); + } + //END SHADERS SETUP + + this._getLocation(shaderProgram, immediateMode); + + return shaderProgram; +}; + +p5.Renderer3D.prototype._getLocation = function(shaderProgram, immediateMode) { + var gl = this.GL; + gl.useProgram(shaderProgram); + shaderProgram.uResolution = + gl.getUniformLocation(shaderProgram, 'uResolution'); + gl.uniform1f(shaderProgram.uResolution, RESOLUTION); + + //vertex position Attribute + shaderProgram.vertexPositionAttribute = + gl.getAttribLocation(shaderProgram, 'aPosition'); + gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); + + //projection Matrix uniform + shaderProgram.uPMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'); + //model view Matrix uniform + shaderProgram.uMVMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'); + + //@TODO: figure out a better way instead of if statement + if(immediateMode === undefined){ + //vertex normal Attribute + shaderProgram.vertexNormalAttribute = + gl.getAttribLocation(shaderProgram, 'aNormal'); + gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute); + + //normal Matrix uniform + shaderProgram.uNMatrixUniform = + gl.getUniformLocation(shaderProgram, 'uNormalMatrix'); + + //texture coordinate Attribute + shaderProgram.textureCoordAttribute = + gl.getAttribLocation(shaderProgram, 'aTexCoord'); + gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); + + shaderProgram.samplerUniform = + gl.getUniformLocation(shaderProgram, 'uSampler'); + } +}; + +p5.Renderer3D.prototype.setMatrixUniforms = function(shaderKey) { + var gl = this.GL; + var shaderProgram = this.mHash[shaderKey]; + + gl.useProgram(shaderProgram); + + gl.uniformMatrix4fv( + shaderProgram.uPMatrixUniform, + false, this.uPMatrix.mat4); + + gl.uniformMatrix4fv( + shaderProgram.uMVMatrixUniform, + false, this.uMVMatrix.mat4); + + this.uNMatrix = new p5.Matrix(); + this.uNMatrix.invert(this.uMVMatrix); + this.uNMatrix.transpose(this.uNMatrix); + + gl.uniformMatrix4fv( + shaderProgram.uNMatrixUniform, + false, this.uNMatrix.mat4); +}; +////////////////////////////////////////////// +// GET CURRENT | for shader and color +////////////////////////////////////////////// +p5.Renderer3D.prototype._getShader = function(vertId, fragId, immediateMode) { + var mId = vertId+ '|' + fragId; + //create it and put it into hashTable + if(!this.materialInHash(mId)){ + var shaderProgram = this.initShaders(vertId, fragId, immediateMode); + this.mHash[mId] = shaderProgram; + } + this.curShaderId = mId; + + return this.mHash[this.curShaderId]; +}; + +p5.Renderer3D.prototype._getCurShaderId = function(){ + //if it's not defined yet + if(this.curShaderId === undefined){ + //default shader: normalMaterial() + var mId = 'normalVert|normalFrag'; + var shaderProgram = this.initShaders('normalVert', 'normalFrag'); + this.mHash[mId] = shaderProgram; + this.curShaderId = mId; + } + + return this.curShaderId; +}; + +p5.Renderer3D.prototype._getCurColor = function() { + //default color: gray + if(this.curColor === undefined) { + this.curColor = [0.5, 0.5, 0.5, 1.0]; + } + return this.curColor; +}; + +////////////////////////////////////////////// +// HASH | for material and geometry +////////////////////////////////////////////// + +p5.Renderer3D.prototype.initHash = function(){ + this.gHash = {}; + this.mHash = {}; +}; + +p5.Renderer3D.prototype.geometryInHash = function(gId){ + return this.gHash[gId] !== undefined; +}; + +p5.Renderer3D.prototype.materialInHash = function(mId){ + return this.mHash[mId] !== undefined; +}; + +////////////////////////////////////////////// +// MATRIX +////////////////////////////////////////////// + +p5.Renderer3D.prototype.initMatrix = function(){ + this.uMVMatrix = new p5.Matrix(); + this.uPMatrix = new p5.Matrix(); + this.uNMatrix = new p5.Matrix(); +}; + +p5.Renderer3D.prototype.resetMatrix = function() { + this.uMVMatrix = p5.Matrix.identity(); + //this.uPMatrix = p5.Matrix.identity(); +}; + +//detect if user didn't set the camera +//then call this function below +p5.Renderer3D.prototype._setDefaultCamera = function(){ + if(!this._setCamera){ + var _w = this.width; + var _h = this.height; + this.uPMatrix = p5.Matrix.identity(); + this.uPMatrix.perspective(60 / 180 * Math.PI, _w / _h, 0.1, 100); + this._setCamera = true; + } +}; + +/** + * [translate description] + * @param {[type]} x [description] + * @param {[type]} y [description] + * @param {[type]} z [description] + * @return {[type]} [description] + * @todo implement handle for components or vector as args + */ +p5.Renderer3D.prototype.translate = function(x, y, z) { + //@TODO: figure out how to fit the resolution + x = x / RESOLUTION; + y = -y / RESOLUTION; + z = z / RESOLUTION; + this.uMVMatrix.translate([x,y,z]); + return this; +}; + +/** + * Scales the Model View Matrix by a vector + * @param {Number | p5.Vector | Array} x [description] + * @param {Number} [y] y-axis scalar + * @param {Number} [z] z-axis scalar + * @return {this} [description] + */ +p5.Renderer3D.prototype.scale = function(x,y,z) { + this.uMVMatrix.scale([x,y,z]); + return this; +}; + +/** + * [rotate description] + * @param {Number} rad angle in radians + * @param {p5.Vector | Array} axis axis to rotate around + * @return {p5.Renderer3D} [description] + */ +p5.Renderer3D.prototype.rotate = function(rad, axis){ + this.uMVMatrix.rotate(rad, axis); + return this; +}; + +/** + * [rotateX description] + * @param {Number} rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateX = function(rad) { + this.uMVMatrix.rotateX(rad); + return this; +}; + +/** + * [rotateY description] + * @param {Number} rad rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateY = function(rad) { + this.uMVMatrix.rotateY(rad); + return this; +}; + +/** + * [rotateZ description] + * @param {Number} rad rad radians to rotate + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.rotateZ = function(rad) { + this.uMVMatrix.rotateZ(rad); + return this; +}; + +/** + * pushes a copy of the model view matrix onto the + * MV Matrix stack. + * NOTE to self: could probably make this more readable + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.push = function() { + uMVMatrixStack.push(this.uMVMatrix.copy()); +}; + +/** + * [pop description] + * @return {[type]} [description] + */ +p5.Renderer3D.prototype.pop = function() { + if (uMVMatrixStack.length === 0) { + throw new Error('Invalid popMatrix!'); + } + this.uMVMatrix = uMVMatrixStack.pop(); +}; + +module.exports = p5.Renderer3D; + +},{"../core/core":48,"../core/p5.Renderer":54,"./p5.Matrix":35,"./shader":38}],37:[function(_dereq_,module,exports){ +//retained mode is used by rendering 3d_primitives + +'use strict'; + +var p5 = _dereq_('../core/core'); +var hashCount = 0; + +/** + * createBuffer + * @param {String} gId key of the geometry object + * @param {Array} arr array holding bject containing geometry information + */ +p5.Renderer3D.prototype.createBuffer = function(gId, arr) { + + hashCount ++; + if(hashCount > 1000){ + var key = Object.keys(this.gHash)[0]; + delete this.gHash[key]; + hashCount --; + } + + var gl = this.GL; + this.gHash[gId] = {}; + this.gHash[gId].len = []; + this.gHash[gId].vertexBuffer = []; + this.gHash[gId].normalBuffer = []; + this.gHash[gId].uvBuffer = []; + this.gHash[gId].indexBuffer =[]; + + arr.forEach(function(obj){ + this.gHash[gId].len.push(obj.len); + this.gHash[gId].vertexBuffer.push(gl.createBuffer()); + this.gHash[gId].normalBuffer.push(gl.createBuffer()); + this.gHash[gId].uvBuffer.push(gl.createBuffer()); + this.gHash[gId].indexBuffer.push(gl.createBuffer()); + }.bind(this)); +}; + +/** + * initBuffer description + * @param {String} gId key of the geometry object + * @param {Array} arr array holding bject containing geometry information + */ +p5.Renderer3D.prototype.initBuffer = function(gId, arr) { + this._setDefaultCamera(); + var gl = this.GL; + this.createBuffer(gId, arr); + + var shaderProgram = this.mHash[this._getCurShaderId()]; + + arr.forEach(function(obj, i){ + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].vertexBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.vertices), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].normalBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.vertexNormals), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.vertexNormalAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].uvBuffer[i]); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array(obj.uvs), gl.STATIC_DRAW); + gl.vertexAttribPointer( + shaderProgram.textureCoordAttribute, + 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gHash[gId].indexBuffer[i]); + gl.bufferData + (gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(obj.faces), gl.STATIC_DRAW); + }.bind(this)); +}; + +/** + * drawBuffer + * @param {String} gId key of the geometery object + */ +p5.Renderer3D.prototype.drawBuffer = function(gId) { + this._setDefaultCamera(); + var gl = this.GL; + var shaderKey = this._getCurShaderId(); + var shaderProgram = this.mHash[shaderKey]; + + this.gHash[gId].len.forEach(function(d, i){ + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].vertexBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.vertexPositionAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].normalBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.vertexNormalAttribute, + 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.gHash[gId].uvBuffer[i]); + gl.vertexAttribPointer( + shaderProgram.textureCoordAttribute, + 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gHash[gId].indexBuffer[i]); + + this.setMatrixUniforms(shaderKey); + + gl.drawElements( + gl.TRIANGLES, this.gHash[gId].len[i], + gl.UNSIGNED_SHORT, 0); + }.bind(this)); +}; + +module.exports = p5.Renderer3D; +},{"../core/core":48}],38:[function(_dereq_,module,exports){ + + +module.exports = { + vertexColorVert: + "attribute vec3 aPosition;\nattribute vec4 aVertexColor;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform float uResolution;\n\nvarying vec4 vColor;\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition / uResolution * vec3(1.0, -1.0, 1.0), 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vColor = aVertexColor;\n}", + vertexColorFrag: + "precision mediump float;\nvarying vec4 vColor;\nvoid main(void) {\n gl_FragColor = vColor;\n}", + normalVert: + "attribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat4 uNormalMatrix;\nuniform float uResolution;\n\nvarying vec3 vVertexNormal;\nvarying highp vec2 vVertTexCoord;\n\nvoid main(void) {\n vec4 positionVec4 = vec4(aPosition / uResolution, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n vVertexNormal = vec3( uNormalMatrix * vec4( aNormal, 1.0 ) );\n vVertTexCoord = aTexCoord;\n}", + normalFrag: + "precision mediump float;\nvarying vec3 vVertexNormal;\nvoid main(void) {\n gl_FragColor = vec4(vVertexNormal, 1.0);\n}", + basicFrag: + "precision mediump float;\nvarying vec3 vVertexNormal;\nuniform vec4 uMaterialColor;\nvoid main(void) {\n gl_FragColor = uMaterialColor;\n}", + lightVert: + "attribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aTexCoord;\n\nuniform mat4 uModelViewMatrix;\nuniform mat4 uProjectionMatrix;\nuniform mat4 uNormalMatrix;\nuniform float uResolution;\nuniform int uAmbientLightCount;\nuniform int uDirectionalLightCount;\nuniform int uPointLightCount;\n\nuniform vec3 uAmbientColor[8];\nuniform vec3 uLightingDirection[8];\nuniform vec3 uDirectionalColor[8];\nuniform vec3 uPointLightLocation[8];\nuniform vec3 uPointLightColor[8];\nuniform bool uSpecular;\n\nvarying vec3 vVertexNormal;\nvarying vec2 vVertTexCoord;\nvarying vec3 vLightWeighting;\n\nvec3 ambientLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 directionalLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 pointLightFactor = vec3(0.0, 0.0, 0.0);\nvec3 pointLightFactor2 = vec3(0.0, 0.0, 0.0);\n\nvoid main(void){\n\n vec4 positionVec4 = vec4(aPosition / uResolution, 1.0);\n gl_Position = uProjectionMatrix * uModelViewMatrix * positionVec4;\n\n vec3 vertexNormal = vec3( uNormalMatrix * vec4( aNormal, 1.0 ) );\n vVertexNormal = vertexNormal;\n vVertTexCoord = aTexCoord;\n\n vec4 mvPosition = uModelViewMatrix * vec4(aPosition / uResolution, 1.0);\n vec3 eyeDirection = normalize(-mvPosition.xyz);\n\n float shininess = 32.0;\n float specularFactor = 2.0;\n float diffuseFactor = 0.3;\n\n for(int i = 0; i < 8; i++){\n if(uAmbientLightCount == i) break;\n ambientLightFactor += uAmbientColor[i];\n }\n\n for(int j = 0; j < 8; j++){\n if(uDirectionalLightCount == j) break;\n vec3 dir = uLightingDirection[j];\n float directionalLightWeighting = max(dot(vertexNormal, dir), 0.0);\n directionalLightFactor += uDirectionalColor[j] * directionalLightWeighting;\n }\n\n for(int k = 0; k < 8; k++){\n if(uPointLightCount == k) break;\n vec3 loc = uPointLightLocation[k];\n //loc = loc / uResolution;\n vec3 lightDirection = normalize(loc - mvPosition.xyz);\n\n float directionalLightWeighting = max(dot(vertexNormal, lightDirection), 0.0);\n pointLightFactor += uPointLightColor[k] * directionalLightWeighting;\n\n //factor2 for specular\n vec3 reflectionDirection = reflect(-lightDirection, vertexNormal);\n float specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);\n\n pointLightFactor2 += uPointLightColor[k] * (specularFactor * specularLightWeighting\n + directionalLightWeighting * diffuseFactor);\n }\n \n if(!uSpecular){\n vLightWeighting = ambientLightFactor + directionalLightFactor + pointLightFactor;\n }else{\n vLightWeighting = ambientLightFactor + directionalLightFactor + pointLightFactor2;\n }\n\n}", + lightTextureFrag: + "precision mediump float;\n\nuniform vec4 uMaterialColor;\nuniform sampler2D uSampler;\nuniform bool isTexture;\n\nvarying vec3 vLightWeighting;\nvarying highp vec2 vVertTexCoord;\n\nvoid main(void) {\n if(!isTexture){\n gl_FragColor = vec4(vec3(uMaterialColor.rgb * vLightWeighting), uMaterialColor.a);\n }else{\n vec4 textureColor = texture2D(uSampler, vVertTexCoord);\n if(vLightWeighting == vec3(0., 0., 0.)){\n gl_FragColor = textureColor;\n }else{\n gl_FragColor = vec4(vec3(textureColor.rgb * vLightWeighting), textureColor.a); \n }\n }\n}" +}; +},{}],39:[function(_dereq_,module,exports){ + +'use strict'; + +var p5 = _dereq_('./core/core'); +_dereq_('./color/p5.Color'); +_dereq_('./core/p5.Element'); +_dereq_('./typography/p5.Font'); +_dereq_('./core/p5.Graphics'); +_dereq_('./core/p5.Renderer2D'); + +_dereq_('./image/p5.Image'); +_dereq_('./math/p5.Vector'); +_dereq_('./io/p5.TableRow'); +_dereq_('./io/p5.Table'); + +_dereq_('./color/creating_reading'); +_dereq_('./color/setting'); +_dereq_('./core/constants'); +_dereq_('./utilities/conversion'); +_dereq_('./utilities/array_functions'); +_dereq_('./utilities/string_functions'); +_dereq_('./core/environment'); +_dereq_('./image/image'); +_dereq_('./image/loading_displaying'); +_dereq_('./image/pixels'); +_dereq_('./io/files'); +_dereq_('./events/keyboard'); +_dereq_('./events/acceleration'); //john +_dereq_('./events/mouse'); +_dereq_('./utilities/time_date'); +_dereq_('./events/touch'); +_dereq_('./math/math'); +_dereq_('./math/calculation'); +_dereq_('./math/random'); +_dereq_('./math/noise'); +_dereq_('./math/trigonometry'); +_dereq_('./core/rendering'); +_dereq_('./core/2d_primitives'); + +_dereq_('./core/attributes'); +_dereq_('./core/curves'); +_dereq_('./core/vertex'); +_dereq_('./core/structure'); +_dereq_('./core/transform'); +_dereq_('./typography/attributes'); +_dereq_('./typography/loading_displaying'); + +_dereq_('./3d/p5.Renderer3D'); +_dereq_('./3d/p5.Geometry3D'); +_dereq_('./3d/retainedMode3D'); +_dereq_('./3d/immediateMode3D'); +_dereq_('./3d/3d_primitives'); +_dereq_('./3d/p5.Matrix'); +_dereq_('./3d/material'); +_dereq_('./3d/light'); +_dereq_('./3d/shader'); +_dereq_('./3d/camera'); +_dereq_('./3d/interaction'); + +/** + * _globalInit + * + * TODO: ??? + * if sketch is on window + * assume "global" mode + * and instantiate p5 automatically + * otherwise do nothing + * + * @return {Undefined} + */ +var _globalInit = function() { + if (!window.PHANTOMJS && !window.mocha) { + // If there is a setup or draw function on the window + // then instantiate p5 in "global" mode + if((window.setup && typeof window.setup === 'function') || + (window.draw && typeof window.draw === 'function')) { + new p5(); + } + } +}; + +// TODO: ??? +if (document.readyState === 'complete') { + _globalInit(); +} else { + window.addEventListener('load', _globalInit , false); +} + +module.exports = p5; +},{"./3d/3d_primitives":28,"./3d/camera":29,"./3d/immediateMode3D":30,"./3d/interaction":31,"./3d/light":32,"./3d/material":33,"./3d/p5.Geometry3D":34,"./3d/p5.Matrix":35,"./3d/p5.Renderer3D":36,"./3d/retainedMode3D":37,"./3d/shader":38,"./color/creating_reading":41,"./color/p5.Color":42,"./color/setting":43,"./core/2d_primitives":44,"./core/attributes":45,"./core/constants":47,"./core/core":48,"./core/curves":49,"./core/environment":50,"./core/p5.Element":52,"./core/p5.Graphics":53,"./core/p5.Renderer2D":55,"./core/rendering":56,"./core/structure":58,"./core/transform":59,"./core/vertex":60,"./events/acceleration":61,"./events/keyboard":62,"./events/mouse":63,"./events/touch":64,"./image/image":66,"./image/loading_displaying":67,"./image/p5.Image":68,"./image/pixels":69,"./io/files":70,"./io/p5.Table":71,"./io/p5.TableRow":72,"./math/calculation":73,"./math/math":74,"./math/noise":75,"./math/p5.Vector":76,"./math/random":78,"./math/trigonometry":79,"./typography/attributes":80,"./typography/loading_displaying":81,"./typography/p5.Font":82,"./utilities/array_functions":83,"./utilities/conversion":84,"./utilities/string_functions":85,"./utilities/time_date":86}],40:[function(_dereq_,module,exports){ +/** + * module Conversion + * submodule Color Conversion + * @for p5 + * @requires core + */ + +'use strict'; + +/** + * Conversions adapted from <http://www.easyrgb.com/math.html>. + * + * In these functions, hue is always in the range [0,1); all other components + * are in the range [0,1]. 'Brightness' and 'value' are used interchangeably. + */ + +var p5 = _dereq_('../core/core'); +p5.ColorConversion = {}; + +/** + * Convert an HSBA array to HSLA. + */ +p5.ColorConversion._hsbaToHSLA = function(hsba) { + var hue = hsba[0]; + var sat = hsba[1]; + var val = hsba[2]; + + // Calculate lightness. + var li = (2 - sat) * val / 2; + + // Convert saturation. + if (li !== 0) { + if (li === 1) { + sat = 0; + } else if (li < 0.5) { + sat = sat / (2 - sat); + } else { + sat = sat * val / (2 - li * 2); + } + } + + // Hue and alpha stay the same. + return [hue, sat, li, hsba[3]]; +}; + +/** + * Convert an HSBA array to RGBA. + */ +p5.ColorConversion._hsbaToRGBA = function(hsba) { + var hue = hsba[0] * 6; // We will split hue into 6 sectors. + var sat = hsba[1]; + var val = hsba[2]; + + var RGBA = []; + + if (sat === 0) { + RGBA = [val, val, val, hsba[3]]; // Return early if grayscale. + } else { + var sector = Math.floor(hue); + var tint1 = val * (1 - sat); + var tint2 = val * (1 - sat * (hue - sector)); + var tint3 = val * (1 - sat * (1 + sector - hue)); + var red, green, blue; + if (sector === 0) { // Red to yellow. + red = val; + green = tint3; + blue = tint1; + } else if (sector === 1) { // Yellow to green. + red = tint2; + green = val; + blue = tint1; + } else if (sector === 2) { // Green to cyan. + red = tint1; + green = val; + blue = tint3; + } else if (sector === 3) { // Cyan to blue. + red = tint1; + green = tint2; + blue = val; + } else if (sector === 4) { // Blue to magenta. + red = tint3; + green = tint1; + blue = val; + } else { // Magenta to red. + red = val; + green = tint1; + blue = tint2; + } + RGBA = [red, green, blue, hsba[3]]; + } + + return RGBA; +}; + +/** + * Convert an HSLA array to HSBA. + */ +p5.ColorConversion._hslaToHSBA = function(hsla) { + var hue = hsla[0]; + var sat = hsla[1]; + var li = hsla[2]; + + // Calculate brightness. + var val; + if (li < 0.5) { + val = (1 + sat) * li; + } else { + val = li + sat - li * sat; + } + + // Convert saturation. + sat = 2 * (val - li) / val; + + // Hue and alpha stay the same. + return [hue, sat, val, hsla[3]]; +}; + +/** + * Convert an HSLA array to RGBA. + * + * We need to change basis from HSLA to something that can be more easily be + * projected onto RGBA. We will choose hue and brightness as our first two + * components, and pick a convenient third one ('zest') so that we don't need + * to calculate formal HSBA saturation. + */ +p5.ColorConversion._hslaToRGBA = function(hsla){ + var hue = hsla[0] * 6; // We will split hue into 6 sectors. + var sat = hsla[1]; + var li = hsla[2]; + + var RGBA = []; + + if (sat === 0) { + RGBA = [li, li, li, hsla[3]]; // Return early if grayscale. + } else { + + // Calculate brightness. + var val; + if (li < 0.5) { + val = (1 + sat) * li; + } else { + val = li + sat - li * sat; + } + + // Define zest. + var zest = 2 * li - val; + + // Implement projection (project onto green by default). + var hzvToRGB = function(hue, zest, val) { + if (hue < 0) { // Hue must wrap to allow projection onto red and blue. + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + if (hue < 1) { // Red to yellow (increasing green). + return (zest + (val - zest) * hue); + } else if (hue < 3) { // Yellow to cyan (greatest green). + return val; + } else if (hue < 4) { // Cyan to blue (decreasing green). + return (zest + (val - zest) * (4 - hue)); + } else { // Blue to red (least green). + return zest; + } + }; + + // Perform projections, offsetting hue as necessary. + RGBA = [hzvToRGB(hue + 2, zest, val), + hzvToRGB(hue , zest, val), + hzvToRGB(hue - 2, zest, val), + hsla[3]]; + } + + return RGBA; +}; + +/** + * Convert an RGBA array to HSBA. + */ +p5.ColorConversion._rgbaToHSBA = function(rgba) { + var red = rgba[0]; + var green = rgba[1]; + var blue = rgba[2]; + + var val = Math.max(red, green, blue); + var chroma = val - Math.min(red, green, blue); + + var hue, sat; + if (chroma === 0) { // Return early if grayscale. + hue = 0; + sat = 0; + } + else { + sat = chroma / val; + if (red === val) { // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + } + + return [hue / 6, sat, val, rgba[3]]; +}; + +/** + * Convert an RGBA array to HSLA. + */ +p5.ColorConversion._rgbaToHSLA = function(rgba) { + var red = rgba[0]; + var green = rgba[1]; + var blue = rgba[2]; + + var val = Math.max(red, green, blue); + var min = Math.min(red, green, blue); + var li = val + min; // We will halve this later. + var chroma = val - min; + + var hue, sat; + if (chroma === 0) { // Return early if grayscale. + hue = 0; + sat = 0; + } else { + if (li < 1) { + sat = chroma / li; + } else { + sat = chroma / (2 - chroma); + } + if (red === val) { // Magenta to yellow. + hue = (green - blue) / chroma; + } else if (green === val) { // Yellow to cyan. + hue = 2 + (blue - red) / chroma; + } else if (blue === val) { // Cyan to magenta. + hue = 4 + (red - green) / chroma; + } + if (hue < 0) { // Confine hue to the interval [0, 1). + hue += 6; + } else if (hue >= 6) { + hue -= 6; + } + } + + return [hue / 6, sat, li / 2, rgba[3]]; +}; + +module.exports = p5.ColorConversion; + +},{"../core/core":48}],41:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Creating & Reading + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +_dereq_('./p5.Color'); + +/** + * Extracts the alpha value from a color or pixel array. + * + * @method alpha + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * c = color(0, 126, 255, 102); + * fill(c); + * rect(15, 15, 35, 70); + * value = alpha(c); // Sets 'value' to 102 + * fill(value); + * rect(50, 15, 35, 70); + * </code> + * </div> + */ +p5.prototype.alpha = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getAlpha(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the blue value from a color or pixel array. + * + * @method blue + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(175, 100, 220); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * blueValue = blue(c); // Get blue in 'c' + * println(blueValue); // Prints "220.0" + * fill(0, 0, blueValue); // Use 'blueValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + */ +p5.prototype.blue = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getBlue(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the HSB brightness value from a color or pixel array. + * + * @method brightness + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = brightness(c); // Sets 'value' to 255 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.brightness = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getBrightness(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Creates colors for storing in variables of the color datatype. The + * parameters are interpreted as RGB or HSB values depending on the + * current colorMode(). The default mode is RGB values from 0 to 255 + * and, therefore, the function call color(255, 204, 0) will return a + * bright yellow color. + * <br><br> + * Note that if only one value is provided to color(), it will be interpreted + * as a grayscale value. Add a second value, and it will be used for alpha + * transparency. When three values are specified, they are interpreted as + * either RGB or HSB values. Adding a fourth value applies alpha + * transparency. If a single string parameter is provided it will be + * interpreted as a CSS-compatible color string. + * + * Colors are stored as Numbers or Arrays. + * + * @method color + * @param {Number|String} v1 gray value or red or hue value relative to + * the current color range, or a color string + * @param {Number} [v2] gray value or green or saturation value + * relative to the current color range (or + * alpha value if first param is gray value) + * @param {Number} [v3] gray value or blue or brightness value + * relative to the current color range + * @param {Number} [alpha] alpha value relative to current color range + * @return {Array} resulting color + * + * @example + * <div> + * <code> + * var c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * rect(30, 20, 55, 55); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * var c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * ellipse(25, 25, 80, 80); // Draw left circle + * + * // Using only one value with color() + * // generates a grayscale value. + * var c = color(65); // Update 'c' with grayscale value + * fill(c); // Use updated 'c' as fill color + * ellipse(75, 75, 80, 80); // Draw right circle + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG & CSS colors may be used, + * var c = color('magenta'); + * fill(c); // Use 'c' as fill color + * noStroke(); // Don't draw a stroke around shapes + * rect(20, 20, 60, 60); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // as can hex color codes: + * noStroke(); // Don't draw a stroke around shapes + * var c = color('#0f0'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('#00ff00'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // RGB and RGBA color strings are also supported: + * // these all set to the same color (solid blue) + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('rgb(0,0,255)'); + * fill(c); // Use 'c' as fill color + * rect(10, 10, 35, 35); // Draw rectangle + * + * c = color('rgb(0%, 0%, 100%)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 35, 35); // Draw rectangle + * + * c = color('rgba(0, 0, 255, 1)'); + * fill(c); // Use updated 'c' as fill color + * rect(10, 55, 35, 35); // Draw rectangle + * + * c = color('rgba(0%, 0%, 100%, 1)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 55, 35, 35); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // HSL color is also supported and can be specified + * // by value + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('hsl(160, 100%, 50%)'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('hsla(160, 100%, 50%, 0.5)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * // HSB color is also supported and can be specified + * // by value + * var c; + * noStroke(); // Don't draw a stroke around shapes + * c = color('hsb(160, 100%, 50%)'); + * fill(c); // Use 'c' as fill color + * rect(0, 10, 45, 80); // Draw rectangle + * + * c = color('hsba(160, 100%, 50%, 0.5)'); + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw rectangle + * </code> + * </div> + * + * <div> + * <code> + * var c; // Declare color 'c' + * noStroke(); // Don't draw a stroke around shapes + * + * // If no colorMode is specified, then the + * // default of RGB with scale of 0-255 is used. + * c = color(50, 55, 100); // Create a color for 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(0, 10, 45, 80); // Draw left rect + * + * colorMode(HSB, 100); // Use HSB with scale of 0-100 + * c = color(50, 55, 100); // Update 'c' with new color + * fill(c); // Use updated 'c' as fill color + * rect(55, 10, 45, 80); // Draw right rect + * </code> + * </div> + */ +p5.prototype.color = function() { + if (arguments[0] instanceof p5.Color) { + return arguments[0]; // Do nothing if argument is already a color object. + } else if (arguments[0] instanceof Array) { + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments[0]); + } else { + return new p5.Color(this._renderer, arguments[0]); + } + } else { + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments); + } else { + return new p5.Color(this._renderer, arguments); + } + } +}; + +/** + * Extracts the green value from a color or pixel array. + * + * @method green + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(20, 75, 200); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * greenValue = green(c); // Get green in 'c' + * println(greenValue); // Print "75.0" + * fill(0, greenValue, 0); // Use 'greenValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + */ +p5.prototype.green = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getGreen(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the hue value from a color or pixel array. + * + * Hue exists in both HSB and HSL. This function will return the + * HSB-normalized hue when supplied with an HSB color object (or when supplied + * with a pixel array while the color mode is HSB), but will default to the + * HSL-normalized hue otherwise. (The values will only be different if the + * maximum hue setting for each system is different.) + * + * @method hue + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = hue(c); // Sets 'value' to "0" + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.hue = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getHue(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Blends two colors to find a third color somewhere between them. The amt + * parameter is the amount to interpolate between the two values where 0.0 + * equal to the first color, 0.1 is very near the first color, 0.5 is halfway + * in between, etc. An amount below 0 will be treated as 0. Likewise, amounts + * above 1 will be capped at 1. This is different from the behavior of lerp(), + * but necessary because otherwise numbers outside the range will produce + * strange and unexpected colors. + * <br><br> + * The way that colours are interpolated depends on the current color mode. + * + * @method lerpColor + * @param {Array/Number} c1 interpolate from this color + * @param {Array/Number} c2 interpolate to this color + * @param {Number} amt number between 0 and 1 + * @return {Array/Number} interpolated color + * @example + * <div> + * <code> + * colorMode(RGB); + * stroke(255); + * background(51); + * from = color(218, 165, 32); + * to = color(72, 61, 139); + * colorMode(RGB); // Try changing to HSB. + * interA = lerpColor(from, to, .33); + * interB = lerpColor(from, to, .66); + * fill(from); + * rect(10, 20, 20, 60); + * fill(interA); + * rect(30, 20, 20, 60); + * fill(interB); + * rect(50, 20, 20, 60); + * fill(to); + * rect(70, 20, 20, 60); + * </code> + * </div> + */ +p5.prototype.lerpColor = function(c1, c2, amt) { + var mode = this._renderer._colorMode; + var maxes = this._renderer._colorMaxes; + var l0, l1, l2, l3; + var fromArray, toArray; + + if (mode === constants.RGB) { + fromArray = c1.levels.map(function(level) { + return level / 255; + }); + toArray = c2.levels.map(function(level) { + return level / 255; + }); + } else if (mode === constants.HSB) { + c1._getBrightness(); // Cache hsba so it definitely exists. + c2._getBrightness(); + fromArray = c1.hsba; + toArray = c2.hsba; + } else if (mode === constants.HSL) { + c1._getLightness(); // Cache hsla so it definitely exists. + c2._getLightness(); + fromArray = c1.hsla; + toArray = c2.hsla; + } else { + throw new Error (mode + 'cannot be used for interpolation.'); + } + + // Prevent extrapolation. + amt = Math.max(Math.min(amt, 1), 0); + + // Perform interpolation. + l0 = this.lerp(fromArray[0], toArray[0], amt); + l1 = this.lerp(fromArray[1], toArray[1], amt); + l2 = this.lerp(fromArray[2], toArray[2], amt); + l3 = this.lerp(fromArray[3], toArray[3], amt); + + // Scale components. + l0 *= maxes[mode][0]; + l1 *= maxes[mode][1]; + l2 *= maxes[mode][2]; + l3 *= maxes[mode][3]; + + return this.color(l0, l1, l2, l3); +}; + +/** + * Extracts the HSL lightness value from a color or pixel array. + * + * @method lightness + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSL); + * c = color(156, 100, 50, 1); + * fill(c); + * rect(15, 20, 35, 60); + * value = lightness(c); // Sets 'value' to 50 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.lightness = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getLightness(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the red value from a color or pixel array. + * + * @method red + * @param {Object} obj p5.Color object or pixel array + * @example + * <div> + * <code> + * c = color(255, 204, 0); // Define color 'c' + * fill(c); // Use color variable 'c' as fill color + * rect(15, 20, 35, 60); // Draw left rectangle + * + * redValue = red(c); // Get red in 'c' + * println(redValue); // Print "255.0" + * fill(redValue, 0, 0); // Use 'redValue' in new fill + * rect(50, 20, 35, 60); // Draw right rectangle + * </code> + * </div> + * + * <div> + * <code> + * colorMode(RGB, 255); + * var c = color(127, 255, 0); + * colorMode(RGB, 1); + * var myColor = red(c); + * print(myColor); + * </code> + * </div> + */ +p5.prototype.red = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getRed(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +/** + * Extracts the saturation value from a color or pixel array. + * + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object (or when supplied + * with a pixel array while the color mode is HSB), but will default to the + * HSL saturation otherwise. + * + * @method saturation + * @param {Object} color p5.Color object or pixel array + * @example + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 255); + * c = color(0, 126, 255); + * fill(c); + * rect(15, 20, 35, 60); + * value = saturation(c); // Sets 'value' to 126 + * fill(value); + * rect(50, 20, 35, 60); + * </code> + * </div> + */ +p5.prototype.saturation = function(c) { + if (c instanceof p5.Color || c instanceof Array) { + return this.color(c)._getSaturation(); + } else { + throw new Error('Needs p5.Color or pixel array as argument.'); + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./p5.Color":42}],42:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Creating & Reading + * @for p5 + * @requires core + * @requires constants + * @requires color_conversion + */ + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +var color_conversion = _dereq_('./color_conversion'); + +/** + * We define colors to be immutable objects. Each color stores the color mode + * and level maxes that applied at the time of its construction. These are + * used to interpret the input arguments and to format the output e.g. when + * saturation() is requested. + * + * Internally we store an array representing the ideal RGBA values in floating + * point form, normalized from 0 to 1. From this we calculate the closest + * screen color (RGBA levels from 0 to 255) and expose this to the renderer. + * + * We also cache normalized, floating point components of the color in various + * representations as they are calculated. This is done to prevent repeating a + * conversion that has already been performed. + * + * @class p5.Color + * @constructor + */ +p5.Color = function(renderer, vals) { + + // Record color mode and maxes at time of construction. + this.mode = renderer._colorMode; + this.maxes = renderer._colorMaxes; + + // Calculate normalized RGBA values. + if (this.mode !== constants.RGB && + this.mode !== constants.HSL && + this.mode !== constants.HSB) { + throw new Error(this.mode + ' is an invalid colorMode.'); + } else { + this._array = p5.Color._parseInputs.apply(renderer, vals); + } + + // Expose closest screen color. + this.levels = this._array.map(function(level) { + return Math.round(level * 255); + }); + + return this; +}; + +p5.Color.prototype.toString = function() { + var a = this.levels; + a[3] = this._array[3]; // String representation uses normalized alpha. + return 'rgba('+a[0]+','+a[1]+','+a[2]+','+ a[3] +')'; +}; + +p5.Color.prototype._getAlpha = function() { + return this._array[3] * this.maxes[this.mode][3]; +}; + +p5.Color.prototype._getBlue = function() { + return this._array[2] * this.maxes[constants.RGB][2]; +}; + +p5.Color.prototype._getBrightness = function() { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[2] * this.maxes[constants.HSB][2]; +}; + +p5.Color.prototype._getGreen = function() { + return this._array[1] * this.maxes[constants.RGB][1]; +}; + +/** + * Hue is the same in HSB and HSL, but the maximum value may be different. + * This function will return the HSB-normalized saturation when supplied with + * an HSB color object, but will default to the HSL-normalized saturation + * otherwise. + */ +p5.Color.prototype._getHue = function() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[0] * this.maxes[constants.HSB][0]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[0] * this.maxes[constants.HSL][0]; + } +}; + +p5.Color.prototype._getLightness = function() { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[2] * this.maxes[constants.HSL][2]; +}; + +p5.Color.prototype._getRed = function() { + return this._array[0] * this.maxes[constants.RGB][0]; +}; + +/** + * Saturation is scaled differently in HSB and HSL. This function will return + * the HSB saturation when supplied with an HSB color object, but will default + * to the HSL saturation otherwise. + */ +p5.Color.prototype._getSaturation = function() { + if (this.mode === constants.HSB) { + if (!this.hsba) { + this.hsba = color_conversion._rgbaToHSBA(this._array); + } + return this.hsba[1] * this.maxes[constants.HSB][1]; + } else { + if (!this.hsla) { + this.hsla = color_conversion._rgbaToHSLA(this._array); + } + return this.hsla[1] * this.maxes[constants.HSL][1]; + } +}; + +/** + * CSS named colors. + */ +var namedColors = { + aliceblue: '#f0f8ff', + antiquewhite: '#faebd7', + aqua: '#00ffff', + aquamarine: '#7fffd4', + azure: '#f0ffff', + beige: '#f5f5dc', + bisque: '#ffe4c4', + black: '#000000', + blanchedalmond: '#ffebcd', + blue: '#0000ff', + blueviolet: '#8a2be2', + brown: '#a52a2a', + burlywood: '#deb887', + cadetblue: '#5f9ea0', + chartreuse: '#7fff00', + chocolate: '#d2691e', + coral: '#ff7f50', + cornflowerblue: '#6495ed', + cornsilk: '#fff8dc', + crimson: '#dc143c', + cyan: '#00ffff', + darkblue: '#00008b', + darkcyan: '#008b8b', + darkgoldenrod: '#b8860b', + darkgray: '#a9a9a9', + darkgreen: '#006400', + darkgrey: '#a9a9a9', + darkkhaki: '#bdb76b', + darkmagenta: '#8b008b', + darkolivegreen: '#556b2f', + darkorange: '#ff8c00', + darkorchid: '#9932cc', + darkred: '#8b0000', + darksalmon: '#e9967a', + darkseagreen: '#8fbc8f', + darkslateblue: '#483d8b', + darkslategray: '#2f4f4f', + darkslategrey: '#2f4f4f', + darkturquoise: '#00ced1', + darkviolet: '#9400d3', + deeppink: '#ff1493', + deepskyblue: '#00bfff', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1e90ff', + firebrick: '#b22222', + floralwhite: '#fffaf0', + forestgreen: '#228b22', + fuchsia: '#ff00ff', + gainsboro: '#dcdcdc', + ghostwhite: '#f8f8ff', + gold: '#ffd700', + goldenrod: '#daa520', + gray: '#808080', + green: '#008000', + greenyellow: '#adff2f', + grey: '#808080', + honeydew: '#f0fff0', + hotpink: '#ff69b4', + indianred: '#cd5c5c', + indigo: '#4b0082', + ivory: '#fffff0', + khaki: '#f0e68c', + lavender: '#e6e6fa', + lavenderblush: '#fff0f5', + lawngreen: '#7cfc00', + lemonchiffon: '#fffacd', + lightblue: '#add8e6', + lightcoral: '#f08080', + lightcyan: '#e0ffff', + lightgoldenrodyellow: '#fafad2', + lightgray: '#d3d3d3', + lightgreen: '#90ee90', + lightgrey: '#d3d3d3', + lightpink: '#ffb6c1', + lightsalmon: '#ffa07a', + lightseagreen: '#20b2aa', + lightskyblue: '#87cefa', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#b0c4de', + lightyellow: '#ffffe0', + lime: '#00ff00', + limegreen: '#32cd32', + linen: '#faf0e6', + magenta: '#ff00ff', + maroon: '#800000', + mediumaquamarine: '#66cdaa', + mediumblue: '#0000cd', + mediumorchid: '#ba55d3', + mediumpurple: '#9370db', + mediumseagreen: '#3cb371', + mediumslateblue: '#7b68ee', + mediumspringgreen: '#00fa9a', + mediumturquoise: '#48d1cc', + mediumvioletred: '#c71585', + midnightblue: '#191970', + mintcream: '#f5fffa', + mistyrose: '#ffe4e1', + moccasin: '#ffe4b5', + navajowhite: '#ffdead', + navy: '#000080', + oldlace: '#fdf5e6', + olive: '#808000', + olivedrab: '#6b8e23', + orange: '#ffa500', + orangered: '#ff4500', + orchid: '#da70d6', + palegoldenrod: '#eee8aa', + palegreen: '#98fb98', + paleturquoise: '#afeeee', + palevioletred: '#db7093', + papayawhip: '#ffefd5', + peachpuff: '#ffdab9', + peru: '#cd853f', + pink: '#ffc0cb', + plum: '#dda0dd', + powderblue: '#b0e0e6', + purple: '#800080', + red: '#ff0000', + rosybrown: '#bc8f8f', + royalblue: '#4169e1', + saddlebrown: '#8b4513', + salmon: '#fa8072', + sandybrown: '#f4a460', + seagreen: '#2e8b57', + seashell: '#fff5ee', + sienna: '#a0522d', + silver: '#c0c0c0', + skyblue: '#87ceeb', + slateblue: '#6a5acd', + slategray: '#708090', + slategrey: '#708090', + snow: '#fffafa', + springgreen: '#00ff7f', + steelblue: '#4682b4', + tan: '#d2b48c', + teal: '#008080', + thistle: '#d8bfd8', + tomato: '#ff6347', + turquoise: '#40e0d0', + violet: '#ee82ee', + wheat: '#f5deb3', + white: '#ffffff', + whitesmoke: '#f5f5f5', + yellow: '#ffff00', + yellowgreen: '#9acd32' +}; + +/** + * These regular expressions are used to build up the patterns for matching + * viable CSS color strings: fragmenting the regexes in this way increases the + * legibility and comprehensibility of the code. + * + * Note that RGB values of .9 are not parsed by IE, but are supported here for + * color string consistency. + */ +var WHITESPACE = /\s*/; // Match zero or more whitespace characters. +var INTEGER = /(\d{1,3})/; // Match integers: 79, 255, etc. +var DECIMAL = /((?:\d+(?:\.\d+)?)|(?:\.\d+))/; // Match 129.6, 79, .9, etc. +var PERCENT = new RegExp(DECIMAL.source + '%'); // Match 12.9%, 79%, .9%, etc. + +/** + * Full color string patterns. The capture groups are necessary. + */ +var colorPatterns = { + // Match colors in format #XXX, e.g. #416. + HEX3: /^#([a-f0-9])([a-f0-9])([a-f0-9])$/i, + + // Match colors in format #XXXXXX, e.g. #b4d455. + HEX6: /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i, + + // Match colors in format rgb(R, G, B), e.g. rgb(255, 0, 128). + RGB: new RegExp([ + '^rgb\\(', + INTEGER.source, + ',', + INTEGER.source, + ',', + INTEGER.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R%, G%, B%), e.g. rgb(100%, 0%, 28.9%). + RGB_PERCENT: new RegExp([ + '^rgb\\(', + PERCENT.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R, G, B, A), e.g. rgb(255, 0, 128, 0.25). + RGBA: new RegExp([ + '^rgba\\(', + INTEGER.source, + ',', + INTEGER.source, + ',', + INTEGER.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format rgb(R%, G%, B%, A), e.g. rgb(100%, 0%, 28.9%, 0.5). + RGBA_PERCENT: new RegExp([ + '^rgba\\(', + PERCENT.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsla(H, S%, L%), e.g. hsl(100, 40%, 28.9%). + HSL: new RegExp([ + '^hsl\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsla(H, S%, L%, A), e.g. hsla(100, 40%, 28.9%, 0.5). + HSLA: new RegExp([ + '^hsla\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsb(H, S%, B%), e.g. hsb(100, 40%, 28.9%). + HSB: new RegExp([ + '^hsb\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + '\\)$' + ].join(WHITESPACE.source), 'i'), + + // Match colors in format hsba(H, S%, B%, A), e.g. hsba(100, 40%, 28.9%, 0.5). + HSBA: new RegExp([ + '^hsba\\(', + INTEGER.source, + ',', + PERCENT.source, + ',', + PERCENT.source, + ',', + DECIMAL.source, + '\\)$' + ].join(WHITESPACE.source), 'i') +}; + +/** + * For a number of different inputs, returns a color formatted as [r, g, b, a] + * arrays, with each component normalized between 0 and 1. + * + * @param {Array-like} args An 'array-like' object that represents a list of + * arguments + * @return {Array} a color formatted as [r, g, b, a] + * Example: + * input ==> output + * g ==> [g, g, g, 255] + * g,a ==> [g, g, g, a] + * r, g, b ==> [r, g, b, 255] + * r, g, b, a ==> [r, g, b, a] + * [g] ==> [g, g, g, 255] + * [g, a] ==> [g, g, g, a] + * [r, g, b] ==> [r, g, b, 255] + * [r, g, b, a] ==> [r, g, b, a] + * @example + * <div> + * <code> + * // todo + * </code> + * </div> + */ +p5.Color._parseInputs = function() { + var numArgs = arguments.length; + var mode = this._colorMode; + var maxes = this._colorMaxes; + var results = []; + + if (numArgs >= 3) { // Argument is a list of component values. + + results[0] = arguments[0] / maxes[mode][0]; + results[1] = arguments[1] / maxes[mode][1]; + results[2] = arguments[2] / maxes[mode][2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof arguments[3] === 'number') { + results[3] = arguments[3] / maxes[mode][3]; + } else { + results[3] = 1; + } + + // Constrain components to the range [0,1]. + results = results.map(function(value) { + return Math.max(Math.min(value, 1), 0); + }); + + // Convert to RGBA and return. + if (mode === constants.HSL) { + return color_conversion._hslaToRGBA(results); + } else if (mode === constants.HSB) { + return color_conversion._hsbaToRGBA(results); + } else { + return results; + } + + } else if (numArgs === 1 && typeof arguments[0] === 'string') { + + var str = arguments[0].trim().toLowerCase(); + + // Return if string is a named colour. + if (namedColors[str]) { + return p5.Color._parseInputs.apply(this, [namedColors[str]]); + } + + // Try RGBA pattern matching. + if (colorPatterns.HEX3.test(str)) { // #rgb + results = colorPatterns.HEX3.exec(str).slice(1).map(function(color) { + return parseInt(color + color, 16) / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.HEX6.test(str)) { // #rrggbb + results = colorPatterns.HEX6.exec(str).slice(1).map(function(color) { + return parseInt(color, 16) / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGB.test(str)) { // rgb(R,G,B) + results = colorPatterns.RGB.exec(str).slice(1).map(function(color) { + return color / 255; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGB_PERCENT.test(str)) { // rgb(R%,G%,B%) + results = colorPatterns.RGB_PERCENT.exec(str).slice(1) + .map(function(color) { + return parseFloat(color) / 100; + }); + results[3] = 1; + return results; + } else if (colorPatterns.RGBA.test(str)) { // rgba(R,G,B,A) + results = colorPatterns.RGBA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 3) { + return parseFloat(color); + } + return color / 255; + }); + return results; + } else if (colorPatterns.RGBA_PERCENT.test(str)) { // rgba(R%,G%,B%,A%) + results = colorPatterns.RGBA_PERCENT.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 3) { + return parseFloat(color); + } + return parseFloat(color) / 100; + }); + return results; + } + + // Try HSLA pattern matching. + if (colorPatterns.HSL.test(str)) { // hsl(H,S,L) + results = colorPatterns.HSL.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSLA.test(str)) { // hsla(H,S,L,A) + results = colorPatterns.HSLA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } + if (results.length) { + return color_conversion._hslaToRGBA(results); + } + + // Try HSBA pattern matching. + if (colorPatterns.HSB.test(str)) { // hsb(H,S,B) + results = colorPatterns.HSB.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + return parseInt(color, 10) / 100; + }); + results[3] = 1; + } else if (colorPatterns.HSBA.test(str)) { // hsba(H,S,B,A) + results = colorPatterns.HSBA.exec(str).slice(1) + .map(function(color, idx) { + if (idx === 0) { + return parseInt(color, 10) / 360; + } + else if (idx === 3) { + return parseFloat(color); + } + return parseInt(color, 10) / 100; + }); + } + if (results.length) { + return color_conversion._hsbaToRGBA(results); + } + + // Input did not match any CSS color pattern: default to white. + results = [1, 1, 1, 1]; + + } else if ((numArgs === 1 || numArgs === 2) && + typeof arguments[0] === 'number') { // 'Grayscale' mode. + + /** + * For HSB and HSL, interpret the gray level as a brightness/lightness + * value (they are equivalent when chroma is zero). For RGB, normalize the + * gray level according to the blue maximum. + */ + results[0] = arguments[0] / maxes[mode][2]; + results[1] = arguments[0] / maxes[mode][2]; + results[2] = arguments[0] / maxes[mode][2]; + + // Alpha may be undefined, so default it to 100%. + if (typeof arguments[1] === 'number') { + results[3] = arguments[1] / maxes[mode][3]; + } else { + results[3] = 1; + } + + // Constrain components to the range [0,1]. + results = results.map(function(value) { + return Math.max(Math.min(value, 1), 0); + }); + + } else { + throw new Error (arguments + 'is not a valid color representation.'); + } + + return results; +}; + +module.exports = p5.Color; + +},{"../core/constants":47,"../core/core":48,"./color_conversion":40}],43:[function(_dereq_,module,exports){ +/** + * @module Color + * @submodule Setting + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); +_dereq_('./p5.Color'); + +/** + * The background() function sets the color used for the background of the + * p5.js canvas. The default background is light gray. This function is + * typically used within draw() to clear the display window at the beginning + * of each frame, but it can be used inside setup() to set the background on + * the first frame of animation or if the background need only be set once. + * + * @method background + * @param {Number|String|p5.Color|p5.Image} v1 gray value, red or hue value + * (depending on the current + * color mode), color string, + * p5.Color, or p5.Image + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * background(51); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * background(255, 204, 0); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * background(255, 204, 100); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * background('red'); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * background('#fae'); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * background('#222222'); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * background('rgb(0,255,0)'); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * background('rgba(0,255,0, 0.25)'); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * background('rgb(100%,0%,10%)'); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * background('rgba(100%,0%,100%,0.5)'); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * background(color(0, 0, 255)); + * </code> + * </div> + */ +p5.prototype.background = function() { + if (arguments[0] instanceof p5.Image) { + this.image(arguments[0], 0, 0, this.width, this.height); + } else { + this._renderer.background.apply(this._renderer, arguments); + } + return this; +}; + +/** + * Clears the pixels within a buffer. This function only works on p5.Canvas + * objects created with the createCanvas() function; it won't work with the + * main display window. Unlike the main graphics context, pixels in + * additional graphics areas created with createGraphics() can be entirely + * or partially transparent. This function clears everything to make all of + * the pixels 100% transparent. + * + * @method clear + * @example + * <div> + * <code> + * // Clear the screen on mouse press. + * function setup() { + * createCanvas(100, 100); + * } + * + * function draw() { + * ellipse(mouseX, mouseY, 20, 20); + * } + * + * function mousePressed() { + * clear(); + * } + * </code> + * </div> + */ +p5.prototype.clear = function() { + this._renderer.clear(); + return this; +}; + +/** + * colorMode() changes the way p5.js interprets color data. By default, the + * parameters for fill(), stroke(), background(), and color() are defined by + * values between 0 and 255 using the RGB color model. This is equivalent to + * setting colorMode(RGB, 255). Setting colorMode(HSB) lets you use the HSB + * system instead. By default, this is colorMode(HSB, 360, 100, 100, 1). You + * can also use HSL. + * <br><br> + * Note: existing color objects remember the mode that they were created in, + * so you can change modes as you like without affecting their appearance. + * + * @method colorMode + * @param {Number|Constant} mode either RGB or HSB, corresponding to + * Red/Green/Blue and Hue/Saturation/Brightness + * (or Lightness) + * @param {Number|Constant} [max1] range for the red or hue depending on the + * current color mode, or range for all values + * @param {Number|Constant} [max2] range for the green or saturation depending + * on the current color mode + * @param {Number|Constant} [max3] range for the blue or brightness/lighntess + * depending on the current color mode + * @param {Number|Constant} [maxA] range for the alpha + * @example + * <div> + * <code> + * noStroke(); + * colorMode(RGB, 100); + * for (i = 0; i < 100; i++) { + * for (j = 0; j < 100; j++) { + * stroke(i, j, 0); + * point(i, j); + * } + * } + * </code> + * </div> + * + * <div> + * <code> + * noStroke(); + * colorMode(HSB, 100); + * for (i = 0; i < 100; i++) { + * for (j = 0; j < 100; j++) { + * stroke(i, j, 100); + * point(i, j); + * } + * } + * </code> + * </div> + * + * <div> + * <code> + * colorMode(RGB, 255); + * var c = color(127, 255, 0); + * + * colorMode(RGB, 1); + * var myColor = c._getRed(); + * text(myColor, 10, 10, 80, 80); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * colorMode(RGB, 255, 255, 255, 1); + * background(255); + * + * strokeWeight(4); + * stroke(255, 0 , 10, 0.3); + * ellipse(40, 40, 50, 50); + * ellipse(50, 50, 40, 40); + * </code> + * </div> + */ +p5.prototype.colorMode = function() { + if (arguments[0] === constants.RGB || + arguments[0] === constants.HSB || + arguments[0] === constants.HSL) { + + // Set color mode. + this._renderer._colorMode = arguments[0]; + + // Set color maxes. + var maxes = this._renderer._colorMaxes[this._renderer._colorMode]; + if (arguments.length === 2) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[1]; // Green + maxes[2] = arguments[1]; // Blue + maxes[3] = arguments[1]; // Alpha + } else if (arguments.length === 4) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[2]; // Green + maxes[2] = arguments[3]; // Blue + } else if (arguments.length === 5) { + maxes[0] = arguments[1]; // Red + maxes[1] = arguments[2]; // Green + maxes[2] = arguments[3]; // Blue + maxes[3] = arguments[4]; // Alpha + } + } + + return this; +}; + +/** + * Sets the color used to fill shapes. For example, if you run + * fill(204, 102, 0), all subsequent shapes will be filled with orange. This + * color is either specified in terms of the RGB or HSB color depending on + * the current colorMode(). (The default color space is RGB, with each value + * in the range from 0 to 255). + * <br><br> + * If a single string argument is provided, RGB, RGBA and Hex CSS color strings + * and all named color strings are supported. A p5 Color object can also be + * provided to set the fill color. + * + * @method fill + * @param {Number|Array|String|p5.Color} v1 gray value, red or hue value + * (depending on the current color + * mode), or color Array, or CSS + * color string + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * fill(51); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * fill(255, 204, 0); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * fill(255, 204, 100); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * fill('red'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * fill('#fae'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * fill('#222222'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * fill('rgb(0,255,0)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * fill('rgba(0,255,0, 0.25)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * fill('rgb(100%,0%,10%)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * fill('rgba(100%,0%,100%,0.5)'); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * fill(color(0, 0, 255)); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.fill = function() { + this._renderer._setProperty('_fillSet', true); + this._renderer._setProperty('_doFill', true); + this._renderer.fill.apply(this._renderer, arguments); + return this; +}; + +/** + * Disables filling geometry. If both noStroke() and noFill() are called, + * nothing will be drawn to the screen. + * + * @method noFill + * @example + * <div> + * <code> + * rect(15, 10, 55, 55); + * noFill(); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.noFill = function() { + this._renderer._setProperty('_doFill', false); + return this; +}; + +/** + * Disables drawing the stroke (outline). If both noStroke() and noFill() + * are called, nothing will be drawn to the screen. + * + * @method noStroke + * @example + * <div> + * <code> + * noStroke(); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.noStroke = function() { + this._renderer._setProperty('_doStroke', false); + return this; +}; + +/** + * Sets the color used to draw lines and borders around shapes. This color + * is either specified in terms of the RGB or HSB color depending on the + * current colorMode() (the default color space is RGB, with each value in + * the range from 0 to 255). + * <br><br> + * If a single string argument is provided, RGB, RGBA and Hex CSS color + * strings and all named color strings are supported. A p5 Color object + * can also be provided to set the stroke color. + * + * @method stroke + * @param {Number|Array|String|p5.Color} v1 gray value, red or hue value + * (depending on the current color + * mode), or color Array, or CSS + * color string + * @param {Number} [v2] green or saturation value + * (depending on the current + * color mode) + * @param {Number} [v3] blue or brightness value + * (depending on the current + * color mode) + * @param {Number} [a] opacity of the background + * + * @example + * <div> + * <code> + * // Grayscale integer value + * strokeWeight(4); + * stroke(51); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // R, G & B integer values + * stroke(255, 204, 0); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // H, S & B integer values + * colorMode(HSB); + * strokeWeight(4); + * stroke(255, 204, 100); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // Named SVG/CSS color string + * stroke('red'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // three-digit hexadecimal RGB notation + * stroke('#fae'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // six-digit hexadecimal RGB notation + * stroke('#222222'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGB notation + * stroke('rgb(0,255,0)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // integer RGBA notation + * stroke('rgba(0,255,0,0.25)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGB notation + * stroke('rgb(100%,0%,10%)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // percentage RGBA notation + * stroke('rgba(100%,0%,100%,0.5)'); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + * + * <div> + * <code> + * // p5 Color object + * stroke(color(0, 0, 255)); + * strokeWeight(4); + * rect(20, 20, 60, 60); + * </code> + * </div> + */ +p5.prototype.stroke = function() { + this._renderer._setProperty('_strokeSet', true); + this._renderer._setProperty('_doStroke', true); + this._renderer.stroke.apply(this._renderer, arguments); + return this; +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./p5.Color":42}],44:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule 2D Primitives + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +_dereq_('./error_helpers'); + +/** + * Draw an arc to the screen. If called with only a, b, c, d, start, and + * stop, the arc will be drawn as an open pie. If mode is provided, the arc + * will be drawn either open, as a chord, or as a pie as specified. The + * origin may be changed with the ellipseMode() function.<br><br> + * Note that drawing a full circle (ex: 0 to TWO_PI) will appear blank + * because 0 and TWO_PI are the same position on the unit circle. The + * best way to handle this is by using the ellipse() function instead + * to create a closed ellipse, and to use the arc() function + * only to draw parts of an ellipse. + * + * @method arc + * @param {Number} a x-coordinate of the arc's ellipse + * @param {Number} b y-coordinate of the arc's ellipse + * @param {Number} c width of the arc's ellipse by default + * @param {Number} d height of the arc's ellipse by default + * @param {Number} start angle to start the arc, specified in radians + * @param {Number} stop angle to stop the arc, specified in radians + * @param {String} [mode] optional parameter to determine the way of drawing + * the arc + * @return {Object} the p5 object + * @example + * <div> + * <code> + * arc(50, 55, 50, 50, 0, HALF_PI); + * noFill(); + * arc(50, 55, 60, 60, HALF_PI, PI); + * arc(50, 55, 70, 70, PI, PI+QUARTER_PI); + * arc(50, 55, 80, 80, PI+QUARTER_PI, TWO_PI); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, OPEN); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, CHORD); + * </code> + * </div> + * + * <div> + * <code> + * arc(50, 50, 80, 80, 0, PI+QUARTER_PI, PIE); + * </code> + * </div> + */ +p5.prototype.arc = function(x, y, w, h, start, stop, mode) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'arc', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'], + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'String' ] + ] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + if (this._angleMode === constants.DEGREES) { + start = this.radians(start); + stop = this.radians(stop); + } + + // Make all angles positive... + while (start < 0) { + start += constants.TWO_PI; + } + while (stop < 0) { + stop += constants.TWO_PI; + } + // ...and confine them to the interval [0,TWO_PI). + start %= constants.TWO_PI; + stop %= constants.TWO_PI; + + // Adjust angles to counter linear scaling. + if (start <= constants.HALF_PI) { + start = Math.atan(w / h * Math.tan(start)); + } else if (start > constants.HALF_PI && start <= 3 * constants.HALF_PI) { + start = Math.atan(w / h * Math.tan(start)) + constants.PI; + } else { + start = Math.atan(w / h * Math.tan(start)) + constants.TWO_PI; + } + if (stop <= constants.HALF_PI) { + stop = Math.atan(w / h * Math.tan(stop)); + } else if (stop > constants.HALF_PI && stop <= 3 * constants.HALF_PI) { + stop = Math.atan(w / h * Math.tan(stop)) + constants.PI; + } else { + stop = Math.atan(w / h * Math.tan(stop)) + constants.TWO_PI; + } + + // Exceed the interval if necessary in order to preserve the size and + // orientation of the arc. + if (start > stop) { + stop += constants.TWO_PI; + } + // p5 supports negative width and heights for ellipses + w = Math.abs(w); + h = Math.abs(h); + this._renderer.arc(x, y, w, h, start, stop, mode); + return this; +}; + +/** + * Draws an ellipse (oval) to the screen. An ellipse with equal width and + * height is a circle. By default, the first two parameters set the location, + * and the third and fourth parameters set the shape's width and height. The + * origin may be changed with the ellipseMode() function. + * + * @method ellipse + * @param {Number} a x-coordinate of the ellipse. + * @param {Number} b y-coordinate of the ellipse. + * @param {Number} c width of the ellipse. + * @param {Number} d height of the ellipse. + * @return {p5} the p5 object + * @example + * <div> + * <code> + * ellipse(56, 46, 55, 55); + * </code> + * </div> + */ +p5.prototype.ellipse = function(x, y, w, h) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'ellipse', + args, + ['Number', 'Number', 'Number', 'Number'] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + // p5 supports negative width and heights for ellipses + w = Math.abs(w); + h = Math.abs(h); + //@TODO add catch block here if this._renderer + //doesn't have the method implemented yet + this._renderer.ellipse(x, y, w, h); + return this; +}; +/** + * Draws a line (a direct path between two points) to the screen. The version + * of line() with four parameters draws the line in 2D. To color a line, use + * the stroke() function. A line cannot be filled, therefore the fill() + * function will not affect the color of a line. 2D lines are drawn with a + * width of one pixel by default, but this can be changed with the + * strokeWeight() function. + * + * @method line + * @param {Number} x1 the x-coordinate of the first point + * @param {Number} y1 the y-coordinate of the first point + * @param {Number} x2 the x-coordinate of the second point + * @param {Number} y2 the y-coordinate of the second point + * @return {p5} the p5 object + * @example + * <div> + * <code> + * line(30, 20, 85, 75); + * </code> + * </div> + * + * <div> + * <code> + * line(30, 20, 85, 20); + * stroke(126); + * line(85, 20, 85, 75); + * stroke(255); + * line(85, 75, 30, 75); + * </code> + * </div> + */ +////commented out original +// p5.prototype.line = function(x1, y1, x2, y2) { +// if (!this._renderer._doStroke) { +// return this; +// } +// if(this._renderer.isP3D){ +// } else { +// this._renderer.line(x1, y1, x2, y2); +// } +// }; +p5.prototype.line = function() { + if (!this._renderer._doStroke) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + //check whether we should draw a 3d line or 2d + if(this._renderer.isP3D){ + this._validateParameters( + 'line', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ] + ); + this._renderer.line( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5]); + } else { + this._validateParameters( + 'line', + args, + [ + ['Number', 'Number', 'Number', 'Number'], + ] + ); + this._renderer.line( + args[0], + args[1], + args[2], + args[3]); + } + return this; +}; + +/** + * Draws a point, a coordinate in space at the dimension of one pixel. + * The first parameter is the horizontal value for the point, the second + * value is the vertical value for the point. The color of the point is + * determined by the current stroke. + * + * @method point + * @param {Number} x the x-coordinate + * @param {Number} y the y-coordinate + * @return {p5} the p5 object + * @example + * <div> + * <code> + * point(30, 20); + * point(85, 20); + * point(85, 75); + * point(30, 75); + * </code> + * </div> + */ +p5.prototype.point = function() { + if (!this._renderer._doStroke) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + //check whether we should draw a 3d line or 2d + if(this._renderer.isP3D){ + this._validateParameters( + 'point', + args, + [ + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.point( + args[0], + args[1], + args[2] + ); + } else { + this._validateParameters( + 'point', + args, + [ + ['Number', 'Number'] + ] + ); + this._renderer.point( + args[0], + args[1] + ); + } + return this; +}; + + +/** + * Draw a quad. A quad is a quadrilateral, a four sided polygon. It is + * similar to a rectangle, but the angles between its edges are not + * constrained to ninety degrees. The first pair of parameters (x1,y1) + * sets the first vertex and the subsequent pairs should proceed + * clockwise or counter-clockwise around the defined shape. + * + * @method quad + * @param {Number} x1 the x-coordinate of the first point + * @param {Number} y1 the y-coordinate of the first point + * @param {Number} x2 the x-coordinate of the second point + * @param {Number} y2 the y-coordinate of the second point + * @param {Number} x3 the x-coordinate of the third point + * @param {Number} y3 the y-coordinate of the third point + * @param {Number} x4 the x-coordinate of the fourth point + * @param {Number} y4 the y-coordinate of the fourth point + * @return {p5} the p5 object + * @example + * <div> + * <code> + * quad(38, 31, 86, 20, 69, 63, 30, 76); + * </code> + * </div> + */ +p5.prototype.quad = function() { + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'quad', + args, + [ + [ 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number'] + ] + ); + this._renderer.quad( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7], + args[8], + args[9], + args[10], + args[11] + ); + } else { + this._validateParameters( + 'quad', + args, + [ + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ] + ); + this._renderer.quad( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7] + ); + } + return this; +}; + +/** +* Draws a rectangle to the screen. A rectangle is a four-sided shape with +* every angle at ninety degrees. By default, the first two parameters set +* the location of the upper-left corner, the third sets the width, and the +* fourth sets the height. The way these parameters are interpreted, however, +* may be changed with the rectMode() function. +* <br><br> +* The fifth, sixth, seventh and eighth parameters, if specified, +* determine corner radius for the top-right, top-left, lower-right and +* lower-left corners, respectively. An omitted corner radius parameter is set +* to the value of the previously specified radius value in the parameter list. +* +* @method rect +* @param {Number} x x-coordinate of the rectangle. +* @param {Number} y y-coordinate of the rectangle. +* @param {Number} w width of the rectangle. +* @param {Number} h height of the rectangle. +* @param {Number} [tl] optional radius of top-left corner. +* @param {Number} [tr] optional radius of top-right corner. +* @param {Number} [br] optional radius of bottom-right corner. +* @param {Number} [bl] optional radius of bottom-left corner. +* @return {p5} the p5 object. +* @example +* <div> +* <code> +* // Draw a rectangle at location (30, 20) with a width and height of 55. +* rect(30, 20, 55, 55); +* </code> +* </div> +* +* <div> +* <code> +* // Draw a rectangle with rounded corners, each having a radius of 20. +* rect(30, 20, 55, 55, 20); +* </code> +* </div> +* +* <div> +* <code> +* // Draw a rectangle with rounded corners having the following radii: +* // top-left = 20, top-right = 15, bottom-right = 10, bottom-left = 5. +* rect(30, 20, 55, 55, 20, 15, 10, 5) +* </code> +* </div> +*/ +p5.prototype.rect = function (x, y, w, h, tl, tr, br, bl) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rect', + args, + [ + ['Number', 'Number', 'Number', 'Number'], + ['Number', 'Number', 'Number', 'Number', 'Number'], + ['Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number'] + ] + ); + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return; + } + this._renderer.rect(x, y, w, h, tl, tr, br, bl); + return this; +}; + +/** +* A triangle is a plane created by connecting three points. The first two +* arguments specify the first point, the middle two arguments specify the +* second point, and the last two arguments specify the third point. +* +* @method triangle +* @param {Number} x1 x-coordinate of the first point +* @param {Number} y1 y-coordinate of the first point +* @param {Number} x2 x-coordinate of the second point +* @param {Number} y2 y-coordinate of the second point +* @param {Number} x3 x-coordinate of the third point +* @param {Number} y3 y-coordinate of the third point +* @return {p5} the p5 object +* @example +* <div> +* <code> +* triangle(30, 75, 58, 20, 86, 75); +* </code> +* </div> +*/ +p5.prototype.triangle = function() { + + if (!this._renderer._doStroke && !this._renderer._doFill) { + return this; + } + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'triangle', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number'] + ] + ); + this._renderer.triangle( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5], + args[6], + args[7], + args[8] + ); + } else { + this._validateParameters( + 'triangle', + args, + [ + ['Number', 'Number', 'Number', 'Number', 'Number', 'Number'] + ] + ); + this._renderer.triangle( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5] + ); + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48,"./error_helpers":51}],45:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Attributes + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * Modifies the location from which ellipses are drawn by changing the way + * in which parameters given to ellipse() are interpreted. + * <br><br> + * The default mode is ellipseMode(CENTER), which interprets the first two + * parameters of ellipse() as the shape's center point, while the third and + * fourth parameters are its width and height. + * <br><br> + * ellipseMode(RADIUS) also uses the first two parameters of ellipse() as + * the shape's center point, but uses the third and fourth parameters to + * specify half of the shapes's width and height. + * <br><br> + * ellipseMode(CORNER) interprets the first two parameters of ellipse() as + * the upper-left corner of the shape, while the third and fourth parameters + * are its width and height. + * <br><br> + * ellipseMode(CORNERS) interprets the first two parameters of ellipse() as + * the location of one corner of the ellipse's bounding box, and the third + * and fourth parameters as the location of the opposite corner. + * <br><br> + * The parameter must be written in ALL CAPS because Javascript is a + * case-sensitive language. + * + * @method ellipseMode + * @param {Number/Constant} mode either CENTER, RADIUS, CORNER, or CORNERS + * @return {p5} the p5 object + * @example + * <div> + * <code> + * ellipseMode(RADIUS); // Set ellipseMode to RADIUS + * fill(255); // Set fill to white + * ellipse(50, 50, 30, 30); // Draw white ellipse using RADIUS mode + * + * ellipseMode(CENTER); // Set ellipseMode to CENTER + * fill(100); // Set fill to gray + * ellipse(50, 50, 30, 30); // Draw gray ellipse using CENTER mode + * </code> + * </div> + * + * <div> + * <code> + * ellipseMode(CORNER); // Set ellipseMode is CORNER + * fill(255); // Set fill to white + * ellipse(25, 25, 50, 50); // Draw white ellipse using CORNER mode + * + * ellipseMode(CORNERS); // Set ellipseMode to CORNERS + * fill(100); // Set fill to gray + * ellipse(25, 25, 50, 50); // Draw gray ellipse using CORNERS mode + * </code> + * </div> + */ +p5.prototype.ellipseMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.RADIUS || + m === constants.CENTER) { + this._renderer._ellipseMode = m; + } + return this; +}; + +/** + * Draws all geometry with jagged (aliased) edges. Note that smooth() is + * active by default, so it is necessary to call noSmooth() to disable + * smoothing of geometry, images, and fonts. + * + * @method noSmooth + * @return {p5} the p5 object + * @example + * <div> + * <code> + * background(0); + * noStroke(); + * smooth(); + * ellipse(30, 48, 36, 36); + * noSmooth(); + * ellipse(70, 48, 36, 36); + * </code> + * </div> + */ +p5.prototype.noSmooth = function() { + this._renderer.noSmooth(); + return this; +}; + +/** + * Modifies the location from which rectangles are drawn by changing the way + * in which parameters given to rect() are interpreted. + * <br><br> + * The default mode is rectMode(CORNER), which interprets the first two + * parameters of rect() as the upper-left corner of the shape, while the + * third and fourth parameters are its width and height. + * <br><br> + * rectMode(CORNERS) interprets the first two parameters of rect() as the + * location of one corner, and the third and fourth parameters as the + * location of the opposite corner. + * <br><br> + * rectMode(CENTER) interprets the first two parameters of rect() as the + * shape's center point, while the third and fourth parameters are its + * width and height. + * <br><br> + * rectMode(RADIUS) also uses the first two parameters of rect() as the + * shape's center point, but uses the third and fourth parameters to specify + * half of the shapes's width and height. + * <br><br> + * The parameter must be written in ALL CAPS because Javascript is a + * case-sensitive language. + * + * @method rectMode + * @param {Number/Constant} mode either CORNER, CORNERS, CENTER, or RADIUS + * @return {p5} the p5 object + * @example + * <div> + * <code> + * rectMode(CORNER); // Default rectMode is CORNER + * fill(255); // Set fill to white + * rect(25, 25, 50, 50); // Draw white rect using CORNER mode + * + * rectMode(CORNERS); // Set rectMode to CORNERS + * fill(100); // Set fill to gray + * rect(25, 25, 50, 50); // Draw gray rect using CORNERS mode + * </code> + * </div> + * + * <div> + * <code> + * rectMode(RADIUS); // Set rectMode to RADIUS + * fill(255); // Set fill to white + * rect(50, 50, 30, 30); // Draw white rect using RADIUS mode + * + * rectMode(CENTER); // Set rectMode to CENTER + * fill(100); // Set fill to gray + * rect(50, 50, 30, 30); // Draw gray rect using CENTER mode + * </code> + * </div> + */ +p5.prototype.rectMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.RADIUS || + m === constants.CENTER) { + this._renderer._rectMode = m; + } + return this; +}; + +/** + * Draws all geometry with smooth (anti-aliased) edges. smooth() will also + * improve image quality of resized images. Note that smooth() is active by + * default; noSmooth() can be used to disable smoothing of geometry, + * images, and fonts. + * + * @method smooth + * @return {p5} the p5 object + * @example + * <div> + * <code> + * background(0); + * noStroke(); + * smooth(); + * ellipse(30, 48, 36, 36); + * noSmooth(); + * ellipse(70, 48, 36, 36); + * </code> + * </div> + */ +p5.prototype.smooth = function() { + this._renderer.smooth(); + return this; +}; + +/** + * Sets the style for rendering line endings. These ends are either squared, + * extended, or rounded, each of which specified with the corresponding + * parameters: SQUARE, PROJECT, and ROUND. The default cap is ROUND. + * + * @method strokeCap + * @param {Number/Constant} cap either SQUARE, PROJECT, or ROUND + * @return {p5} the p5 object + * @example + * <div> + * <code> + * strokeWeight(12.0); + * strokeCap(ROUND); + * line(20, 30, 80, 30); + * strokeCap(SQUARE); + * line(20, 50, 80, 50); + * strokeCap(PROJECT); + * line(20, 70, 80, 70); + * </code> + * </div> + */ +p5.prototype.strokeCap = function(cap) { + if (cap === constants.ROUND || + cap === constants.SQUARE || + cap === constants.PROJECT) { + this._renderer.strokeCap(cap); + } + return this; +}; + +/** + * Sets the style of the joints which connect line segments. These joints + * are either mitered, beveled, or rounded and specified with the + * corresponding parameters MITER, BEVEL, and ROUND. The default joint is + * MITER. + * + * @method strokeJoin + * @param {Number/Constant} join either MITER, BEVEL, ROUND + * @return {p5} the p5 object + * @example + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(MITER); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(BEVEL); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(10.0); + * strokeJoin(ROUND); + * beginShape(); + * vertex(35, 20); + * vertex(65, 50); + * vertex(35, 80); + * endShape(); + * </code> + * </div> + */ +p5.prototype.strokeJoin = function(join) { + if (join === constants.ROUND || + join === constants.BEVEL || + join === constants.MITER) { + this._renderer.strokeJoin(join); + } + return this; +}; + +/** + * Sets the width of the stroke used for lines, points, and the border + * around shapes. All widths are set in units of pixels. + * + * @method strokeWeight + * @param {Number} weight the weight (in pixels) of the stroke + * @return {p5} the p5 object + * @example + * <div> + * <code> + * strokeWeight(1); // Default + * line(20, 20, 80, 20); + * strokeWeight(4); // Thicker + * line(20, 40, 80, 40); + * strokeWeight(10); // Beastly + * line(20, 70, 80, 70); + * </code> + * </div> + */ +p5.prototype.strokeWeight = function(w) { + this._renderer.strokeWeight(w); + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],46:[function(_dereq_,module,exports){ +/** + * @requires constants + */ + +var constants = _dereq_('./constants'); + +module.exports = { + + modeAdjust: function(a, b, c, d, mode) { + if (mode === constants.CORNER) { + return { x: a, y: b, w: c, h: d }; + } else if (mode === constants.CORNERS) { + return { x: a, y: b, w: c-a, h: d-b }; + } else if (mode === constants.RADIUS) { + return { x: a-c, y: b-d, w: 2*c, h: 2*d }; + } else if (mode === constants.CENTER) { + return { x: a-c*0.5, y: b-d*0.5, w: c, h: d }; + } + }, + + arcModeAdjust: function(a, b, c, d, mode) { + if (mode === constants.CORNER) { + return { x: a+c*0.5, y: b+d*0.5, w: c, h: d }; + } else if (mode === constants.CORNERS) { + return { x: a, y: b, w: c+a, h: d+b }; + } else if (mode === constants.RADIUS) { + return { x: a, y: b, w: 2*c, h: 2*d }; + } else if (mode === constants.CENTER) { + return { x: a, y: b, w: c, h: d }; + } + } + +}; + + +},{"./constants":47}],47:[function(_dereq_,module,exports){ +/** + * @module Constants + * @submodule Constants + * @for p5 + */ + +var PI = Math.PI; + +module.exports = { + + // GRAPHICS RENDERER + P2D: 'p2d', + WEBGL: 'webgl', + + // ENVIRONMENT + ARROW: 'default', + CROSS: 'crosshair', + HAND: 'pointer', + MOVE: 'move', + TEXT: 'text', + WAIT: 'wait', + + // TRIGONOMETRY + + /** + * HALF_PI is a mathematical constant with the value + * 1.57079632679489661923. It is half the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property HALF_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, HALF_PI); + * </code></div> + * + */ + HALF_PI: PI / 2, + /** + * PI is a mathematical constant with the value + * 3.14159265358979323846. It is the ratio of the circumference + * of a circle to its diameter. It is useful in combination with + * the trigonometric functions sin() and cos(). + * + * @property PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, PI); + * </code></div> + */ + PI: PI, + /** + * QUARTER_PI is a mathematical constant with the value 0.7853982. + * It is one quarter the ratio of the circumference of a circle to + * its diameter. It is useful in combination with the trigonometric + * functions sin() and cos(). + * + * @property QUARTER_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, QUARTER_PI); + * </code></div> + * + */ + QUARTER_PI: PI / 4, + /** + * TAU is an alias for TWO_PI, a mathematical constant with the + * value 6.28318530717958647693. It is twice the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property TAU + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, TAU); + * </code></div> + * + */ + TAU: PI * 2, + /** + * TWO_PI is a mathematical constant with the value + * 6.28318530717958647693. It is twice the ratio of the + * circumference of a circle to its diameter. It is useful in + * combination with the trigonometric functions sin() and cos(). + * + * @property TWO_PI + * + * @example + * <div><code> + * arc(50, 50, 80, 80, 0, TWO_PI); + * </code></div> + * + */ + TWO_PI: PI * 2, + DEGREES: 'degrees', + RADIANS: 'radians', + + // SHAPE + CORNER: 'corner', + CORNERS: 'corners', + RADIUS: 'radius', + RIGHT: 'right', + LEFT: 'left', + CENTER: 'center', + TOP: 'top', + BOTTOM: 'bottom', + BASELINE: 'alphabetic', + POINTS: 'points', + LINES: 'lines', + TRIANGLES: 'triangles', + TRIANGLE_FAN: 'triangles_fan', + TRIANGLE_STRIP: 'triangles_strip', + QUADS: 'quads', + QUAD_STRIP: 'quad_strip', + CLOSE: 'close', + OPEN: 'open', + CHORD: 'chord', + PIE: 'pie', + PROJECT: 'square', // PEND: careful this is counterintuitive + SQUARE: 'butt', + ROUND: 'round', + BEVEL: 'bevel', + MITER: 'miter', + + // COLOR + RGB: 'rgb', + HSB: 'hsb', + HSL: 'hsl', + + // DOM EXTENSION + AUTO: 'auto', + + // INPUT + ALT: 18, + BACKSPACE: 8, + CONTROL: 17, + DELETE: 46, + DOWN_ARROW: 40, + ENTER: 13, + ESCAPE: 27, + LEFT_ARROW: 37, + OPTION: 18, + RETURN: 13, + RIGHT_ARROW: 39, + SHIFT: 16, + TAB: 9, + UP_ARROW: 38, + + // RENDERING + BLEND: 'normal', + ADD: 'lighter', + //ADD: 'add', // + //SUBTRACT: 'subtract', // + DARKEST: 'darken', + LIGHTEST: 'lighten', + DIFFERENCE: 'difference', + EXCLUSION: 'exclusion', + MULTIPLY: 'multiply', + SCREEN: 'screen', + REPLACE: 'source-over', + OVERLAY: 'overlay', + HARD_LIGHT: 'hard-light', + SOFT_LIGHT: 'soft-light', + DODGE: 'color-dodge', + BURN: 'color-burn', + + // FILTERS + THRESHOLD: 'threshold', + GRAY: 'gray', + OPAQUE: 'opaque', + INVERT: 'invert', + POSTERIZE: 'posterize', + DILATE: 'dilate', + ERODE: 'erode', + BLUR: 'blur', + + // TYPOGRAPHY + NORMAL: 'normal', + ITALIC: 'italic', + BOLD: 'bold', + + // TYPOGRAPHY-INTERNAL + _DEFAULT_TEXT_FILL: '#000000', + _DEFAULT_LEADMULT: 1.25, + _CTX_MIDDLE: 'middle', + + // VERTICES + LINEAR: 'linear', + QUADRATIC: 'quadratic', + BEZIER: 'bezier', + CURVE: 'curve', + + // DEFAULTS + _DEFAULT_STROKE: '#000000', + _DEFAULT_FILL: '#FFFFFF' + +}; + +},{}],48:[function(_dereq_,module,exports){ +/** + * @module Structure + * @submodule Structure + * @for p5 + * @requires constants + */ + +'use strict'; + +_dereq_('./shim'); + +// Core needs the PVariables object +var constants = _dereq_('./constants'); + +/** + * This is the p5 instance constructor. + * + * A p5 instance holds all the properties and methods related to + * a p5 sketch. It expects an incoming sketch closure and it can also + * take an optional node parameter for attaching the generated p5 canvas + * to a node. The sketch closure takes the newly created p5 instance as + * its sole argument and may optionally set preload(), setup(), and/or + * draw() properties on it for running a sketch. + * + * A p5 sketch can run in "global" or "instance" mode: + * "global" - all properties and methods are attached to the window + * "instance" - all properties and methods are bound to this p5 object + * + * @param {Function} sketch a closure that can set optional preload(), + * setup(), and/or draw() properties on the + * given p5 instance + * @param {HTMLElement|boolean} node element to attach canvas to, if a + * boolean is passed in use it as sync + * @param {boolean} [sync] start synchronously (optional) + * @return {p5} a p5 instance + */ +var p5 = function(sketch, node, sync) { + + if (arguments.length === 2 && typeof node === 'boolean') { + sync = node; + node = undefined; + } + + ////////////////////////////////////////////// + // PUBLIC p5 PROPERTIES AND METHODS + ////////////////////////////////////////////// + + + /** + * Called directly before setup(), the preload() function is used to handle + * asynchronous loading of external files. If a preload function is + * defined, setup() will wait until any load calls within have finished. + * Nothing besides load calls should be inside preload (loadImage, + * loadJSON, loadFont, loadStrings, etc). + * + * @method preload + * @example + * <div><code> + * var img; + * var c; + * function preload() { // preload() runs once + * img = loadImage('assets/laDefense.jpg'); + * } + * + * function setup() { // setup() waits until preload() is done + * img.loadPixels(); + * // get color of middle pixel + * c = img.get(img.width/2, img.height/2); + * } + * + * function draw() { + * background(c); + * image(img, 25, 25, 50, 50); + * } + * </code></div> + */ + + /** + * The setup() function is called once when the program starts. It's used to + * define initial environment properties such as screen size and background + * color and to load media such as images and fonts as the program starts. + * There can only be one setup() function for each program and it shouldn't + * be called again after its initial execution. + * <br><br> + * Note: Variables declared within setup() are not accessible within other + * functions, including draw(). + * + * @method setup + * @example + * <div><code> + * var a = 0; + * + * function setup() { + * background(0); + * noStroke(); + * fill(102); + * } + * + * function draw() { + * rect(a++%width, 10, 2, 80); + * } + * </code></div> + */ + + /** + * Called directly after setup(), the draw() function continuously executes + * the lines of code contained inside its block until the program is stopped + * or noLoop() is called. draw() is called automatically and should never be + * called explicitly. + * <br><br> + * It should always be controlled with noLoop(), redraw() and loop(). After + * noLoop() stops the code in draw() from executing, redraw() causes the + * code inside draw() to execute once, and loop() will cause the code + * inside draw() to resume executing continuously. + * <br><br> + * The number of times draw() executes in each second may be controlled with + * the frameRate() function. + * <br><br> + * There can only be one draw() function for each sketch, and draw() must + * exist if you want the code to run continuously, or to process events such + * as mousePressed(). Sometimes, you might have an empty call to draw() in + * your program, as shown in the above example. + * + * @method draw + * @example + * <div><code> + * var yPos = 0; + * function setup() { // setup() runs once + * frameRate(30); + * } + * function draw() { // draw() loops forever, until stopped + * background(204); + * yPos = yPos - 1; + * if (yPos < 0) { + * yPos = height; + * } + * line(0, yPos, width, yPos); + * } + * </code></div> + */ + + + ////////////////////////////////////////////// + // PRIVATE p5 PROPERTIES AND METHODS + ////////////////////////////////////////////// + + this._setupDone = false; + // for handling hidpi + this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1; + this._userNode = node; + this._curElement = null; + this._elements = []; + this._requestAnimId = 0; + this._preloadCount = 0; + this._isGlobal = false; + this._loop = true; + this._styles = []; + this._defaultCanvasSize = { + width: 100, + height: 100 + }; + this._events = { // keep track of user-events for unregistering later + 'mousemove': null, + 'mousedown': null, + 'mouseup': null, + 'dragend': null, + 'dragover': null, + 'click': null, + 'mouseover': null, + 'mouseout': null, + 'keydown': null, + 'keyup': null, + 'keypress': null, + 'touchstart': null, + 'touchmove': null, + 'touchend': null, + 'resize': null, + 'blur': null + }; + + if (window.DeviceOrientationEvent) { + this._events.deviceorientation = null; + } + if (window.DeviceMotionEvent && !window._isNodeWebkit) { + this._events.devicemotion = null; + } + + this._events.wheel = null; + + + this._loadingScreenId = 'p5_loading'; + + this._start = function () { + // Find node if id given + if (this._userNode) { + if (typeof this._userNode === 'string') { + this._userNode = document.getElementById(this._userNode); + } + } + + // Always create a default canvas. + // Later on if the user calls createCanvas, this default one + // will be replaced + this.createCanvas( + this._defaultCanvasSize.width, + this._defaultCanvasSize.height, + 'p2d', + true + ); + + var userPreload = this.preload || window.preload; // look for "preload" + if (userPreload) { + + // Setup loading screen + // Set loading scfeen into dom if not present + // Otherwise displays and removes user provided loading screen + var loadingScreen = document.getElementById(this._loadingScreenId); + if(!loadingScreen){ + loadingScreen = document.createElement('div'); + loadingScreen.innerHTML = 'Loading...'; + loadingScreen.style.position = 'absolute'; + loadingScreen.id = this._loadingScreenId; + var node = this._userNode || document.body; + node.appendChild(loadingScreen); + } + // var methods = this._preloadMethods; + for (var method in this._preloadMethods){ + // default to p5 if no object defined + this._preloadMethods[method] = this._preloadMethods[method] || p5; + var obj = this._preloadMethods[method]; + //it's p5, check if it's global or instance + if (obj === p5.prototype || obj === p5){ + obj = this._isGlobal ? window : this; + } + this._registeredPreloadMethods[method] = obj[method]; + obj[method] = this._wrapPreload(obj, method); + } + + userPreload(); + this._runIfPreloadsAreDone(); + } else { + this._setup(); + this._runFrames(); + this._draw(); + } + }.bind(this); + + this._runIfPreloadsAreDone = function(){ + var context = this._isGlobal ? window : this; + if (context._preloadCount === 0) { + var loadingScreen = document.getElementById(context._loadingScreenId); + if (loadingScreen) { + loadingScreen.parentNode.removeChild(loadingScreen); + } + context._setup(); + context._runFrames(); + context._draw(); + } + }; + + this._decrementPreload = function(){ + var context = this._isGlobal ? window : this; + context._setProperty('_preloadCount', context._preloadCount - 1); + context._runIfPreloadsAreDone(); + }; + + this._wrapPreload = function(obj, fnName){ + return function(){ + //increment counter + this._incrementPreload(); + //call original function + var args = Array.prototype.slice.call(arguments); + args.push(this._decrementPreload.bind(this)); + return this._registeredPreloadMethods[fnName].apply(obj, args); + }.bind(this); + }; + + this._incrementPreload = function(){ + var context = this._isGlobal ? window : this; + context._setProperty('_preloadCount', context._preloadCount + 1); + }; + + this._setup = function() { + + // return preload functions to their normal vals if switched by preload + var context = this._isGlobal ? window : this; + if (typeof context.preload === 'function') { + for (var f in this._preloadMethods) { + context[f] = this._preloadMethods[f][f]; + if (context[f] && this) { + context[f] = context[f].bind(this); + } + } + } + + // Short-circuit on this, in case someone used the library in "global" + // mode earlier + if (typeof context.setup === 'function') { + context.setup(); + } + + // // unhide hidden canvas that was created + // this.canvas.style.visibility = ''; + // this.canvas.className = this.canvas.className.replace('p5_hidden', ''); + + // unhide any hidden canvases that were created + var reg = new RegExp(/(^|\s)p5_hidden(?!\S)/g); + var canvases = document.getElementsByClassName('p5_hidden'); + for (var i = 0; i < canvases.length; i++) { + var k = canvases[i]; + k.style.visibility = ''; + k.className = k.className.replace(reg, ''); + } + this._setupDone = true; + + }.bind(this); + + this._draw = function () { + var now = window.performance.now(); + var time_since_last = now - this._lastFrameTime; + var target_time_between_frames = 1000 / this._targetFrameRate; + + // only draw if we really need to; don't overextend the browser. + // draw if we're within 5ms of when our next frame should paint + // (this will prevent us from giving up opportunities to draw + // again when it's really about time for us to do so). fixes an + // issue where the frameRate is too low if our refresh loop isn't + // in sync with the browser. note that we have to draw once even + // if looping is off, so we bypass the time delay if that + // is the case. + var epsilon = 5; + if (!this._loop || + time_since_last >= target_time_between_frames - epsilon) { + + //mandatory update values(matrixs and stack) for 3d + if(this._renderer.isP3D){ + this._renderer._update(); + } + + this._setProperty('frameCount', this.frameCount + 1); + this._updateMouseCoords(); + this._updateTouchCoords(); + this.redraw(); + this._frameRate = 1000.0/(now - this._lastFrameTime); + this._lastFrameTime = now; + } + + // get notified the next time the browser gives us + // an opportunity to draw. + if (this._loop) { + this._requestAnimId = window.requestAnimationFrame(this._draw); + } + }.bind(this); + + this._runFrames = function() { + if (this._updateInterval) { + clearInterval(this._updateInterval); + } + }.bind(this); + + this._setProperty = function(prop, value) { + this[prop] = value; + if (this._isGlobal) { + window[prop] = value; + } + }.bind(this); + + /** + * Removes the entire p5 sketch. This will remove the canvas and any + * elements created by p5.js. It will also stop the draw loop and unbind + * any properties or methods from the window global scope. It will + * leave a variable p5 in case you wanted to create a new p5 sketch. + * If you like, you can set p5 = null to erase it. + * @method remove + * @example + * <div class='norender'><code> + * function draw() { + * ellipse(50, 50, 10, 10); + * } + * + * function mousePressed() { + * remove(); // remove whole sketch on mouse press + * } + * </code></div> + */ + this.remove = function() { + if (this._curElement) { + + // stop draw + this._loop = false; + if (this._requestAnimId) { + window.cancelAnimationFrame(this._requestAnimId); + } + + // unregister events sketch-wide + for (var ev in this._events) { + window.removeEventListener(ev, this._events[ev]); + } + + // remove DOM elements created by p5, and listeners + for (var i=0; i<this._elements.length; i++) { + var e = this._elements[i]; + if (e.elt.parentNode) { + e.elt.parentNode.removeChild(e.elt); + } + for (var elt_ev in e._events) { + e.elt.removeEventListener(elt_ev, e._events[elt_ev]); + } + } + + // call any registered remove functions + var self = this; + this._registeredMethods.remove.forEach(function (f) { + if (typeof(f) !== 'undefined') { + f.call(self); + } + }); + + // remove window bound properties and methods + if (this._isGlobal) { + for (var p in p5.prototype) { + try { + delete window[p]; + } catch (x) { + window[p] = undefined; + } + } + for (var p2 in this) { + if (this.hasOwnProperty(p2)) { + try { + delete window[p2]; + } catch (x) { + window[p2] = undefined; + } + } + } + } + } + // window.p5 = undefined; + }.bind(this); + + + // attach constants to p5 instance + for (var k in constants) { + p5.prototype[k] = constants[k]; + } + + // If the user has created a global setup or draw function, + // assume "global" mode and make everything global (i.e. on the window) + if (!sketch) { + this._isGlobal = true; + // Loop through methods on the prototype and attach them to the window + for (var p in p5.prototype) { + if(typeof p5.prototype[p] === 'function') { + var ev = p.substring(2); + if (!this._events.hasOwnProperty(ev)) { + window[p] = p5.prototype[p].bind(this); + } + } else { + window[p] = p5.prototype[p]; + } + } + // Attach its properties to the window + for (var p2 in this) { + if (this.hasOwnProperty(p2)) { + window[p2] = this[p2]; + } + } + + } else { + // Else, the user has passed in a sketch closure that may set + // user-provided 'setup', 'draw', etc. properties on this instance of p5 + sketch(this); + } + + // Bind events to window (not using container div bc key events don't work) + + for (var e in this._events) { + var f = this['_on'+e]; + if (f) { + var m = f.bind(this); + window.addEventListener(e, m); + this._events[e] = m; + } + } + + var focusHandler = function() { + this._setProperty('focused', true); + }.bind(this); + var blurHandler = function() { + this._setProperty('focused', false); + }.bind(this); + window.addEventListener('focus', focusHandler); + window.addEventListener('blur', blurHandler); + this.registerMethod('remove', function() { + window.removeEventListener('focus', focusHandler); + window.removeEventListener('blur', blurHandler); + }); + + // TODO: ??? + + if (sync) { + this._start(); + } else { + if (document.readyState === 'complete') { + this._start(); + } else { + window.addEventListener('load', this._start.bind(this), false); + } + } +}; + + +// functions that cause preload to wait +// more can be added by using registerPreloadMethod(func) +p5.prototype._preloadMethods = { + loadJSON: p5.prototype, + loadImage: p5.prototype, + loadStrings: p5.prototype, + loadXML: p5.prototype, + loadShape: p5.prototype, + loadTable: p5.prototype, + loadFont: p5.prototype +}; + +p5.prototype._registeredMethods = { pre: [], post: [], remove: [] }; + +p5.prototype._registeredPreloadMethods = {}; + +p5.prototype.registerPreloadMethod = function(fnString, obj) { + // obj = obj || p5.prototype; + if (!p5.prototype._preloadMethods.hasOwnProperty(fnString)) { + p5.prototype._preloadMethods[fnString] = obj; + } +}; + +p5.prototype.registerMethod = function(name, m) { + if (!p5.prototype._registeredMethods.hasOwnProperty(name)) { + p5.prototype._registeredMethods[name] = []; + } + p5.prototype._registeredMethods[name].push(m); +}; + +module.exports = p5; + +},{"./constants":47,"./shim":57}],49:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Curves + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); + +_dereq_('./error_helpers'); + +var bezierDetail = 20; +var curveDetail = 20; + +/** + * Draws a cubic Bezier curve on the screen. These curves are defined by a + * series of anchor and control points. The first two parameters specify + * the first anchor point and the last two parameters specify the other + * anchor point, which become the first and last points on the curve. The + * middle parameters specify the two control points which define the shape + * of the curve. Approximately speaking, control points "pull" the curve + * towards them.<br /><br />Bezier curves were developed by French + * automotive engineer Pierre Bezier, and are commonly used in computer + * graphics to define gently sloping curves. See also curve(). + * + * @method bezier + * @param {Number} x1 x-coordinate for the first anchor point + * @param {Number} y1 y-coordinate for the first anchor point + * @param {Number} x2 x-coordinate for the first control point + * @param {Number} y2 y-coordinate for the first control point + * @param {Number} x3 x-coordinate for the second control point + * @param {Number} y3 y-coordinate for the second control point + * @param {Number} x4 x-coordinate for the second anchor point + * @param {Number} y4 y-coordinate for the second anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * stroke(255, 102, 0); + * line(85, 20, 10, 10); + * line(90, 90, 15, 80); + * stroke(0, 0, 0); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * </code> + * </div> + */ +p5.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'bezier', + args, + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ); + + if (!this._renderer._doStroke) { + return this; + } + this._renderer.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + return this; +}; + +/** + * Sets the resolution at which Beziers display. + * + * The default value is 20. + * + * @param {Number} detail resolution of the curves + * @return {Object} the p5 object + * @example + * <div> + * <code> + * background(204); + * bezierDetail(50); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * </code> + * </div> + */ +p5.prototype.bezierDetail = function(d) { + bezierDetail = d; + return this; +}; + +/** + * Evaluates the Bezier at position t for points a, b, c, d. + * The parameters a and d are the first and last points + * on the curve, and b and c are the control points. + * The final parameter t varies between 0 and 1. + * This can be done once with the x coordinates and a second time + * with the y coordinates to get the location of a bezier curve at t. + * + * @method bezierPoint + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the value of the Bezier at position t + * @example + * <div> + * <code> + * noFill(); + * x1 = 85, x2 = 10, x3 = 90, x4 = 15; + * y1 = 20, y2 = 10, y3 = 90, y4 = 80; + * bezier(x1, y1, x2, y2, x3, y3, x4, y4); + * fill(255); + * steps = 10; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = bezierPoint(x1, x2, x3, x4, t); + * y = bezierPoint(y1, y2, y3, y4, t); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + */ +p5.prototype.bezierPoint = function(a, b, c, d, t) { + var adjustedT = 1-t; + return Math.pow(adjustedT,3)*a + + 3*(Math.pow(adjustedT,2))*t*b + + 3*adjustedT*Math.pow(t,2)*c + + Math.pow(t,3)*d; +}; + +/** + * Evaluates the tangent to the Bezier at position t for points a, b, c, d. + * The parameters a and d are the first and last points + * on the curve, and b and c are the control points. + * The final parameter t varies between 0 and 1. + * + * @method bezierTangent + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the tangent at position t + * @example + * <div> + * <code> + * noFill(); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * steps = 6; + * fill(255); + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * // Get the location of the point + * x = bezierPoint(85, 10, 90, 15, t); + * y = bezierPoint(20, 10, 90, 80, t); + * // Get the tangent points + * tx = bezierTangent(85, 10, 90, 15, t); + * ty = bezierTangent(20, 10, 90, 80, t); + * // Calculate an angle from the tangent points + * a = atan2(ty, tx); + * a += PI; + * stroke(255, 102, 0); + * line(x, y, cos(a)*30 + x, sin(a)*30 + y); + * // The following line of code makes a line + * // inverse of the above line + * //line(x, y, cos(a)*-30 + x, sin(a)*-30 + y); + * stroke(0); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * bezier(85, 20, 10, 10, 90, 90, 15, 80); + * stroke(255, 102, 0); + * steps = 16; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = bezierPoint(85, 10, 90, 15, t); + * y = bezierPoint(20, 10, 90, 80, t); + * tx = bezierTangent(85, 10, 90, 15, t); + * ty = bezierTangent(20, 10, 90, 80, t); + * a = atan2(ty, tx); + * a -= HALF_PI; + * line(x, y, cos(a)*8 + x, sin(a)*8 + y); + * } + * </code> + * </div> + */ +p5.prototype.bezierTangent = function(a, b, c, d, t) { + var adjustedT = 1-t; + return 3*d*Math.pow(t,2) - + 3*c*Math.pow(t,2) + + 6*c*adjustedT*t - + 6*b*adjustedT*t + + 3*b*Math.pow(adjustedT,2) - + 3*a*Math.pow(adjustedT,2); +}; + +/** + * Draws a curved line on the screen between two points, given as the + * middle four parameters. The first two parameters are a control point, as + * if the curve came from this point even though it's not drawn. The last + * two parameters similarly describe the other control point. <br /><br /> + * Longer curves can be created by putting a series of curve() functions + * together or using curveVertex(). An additional function called + * curveTightness() provides control for the visual quality of the curve. + * The curve() function is an implementation of Catmull-Rom splines. + * + * @method curve + * @param {Number} x1 x-coordinate for the beginning control point + * @param {Number} y1 y-coordinate for the beginning control point + * @param {Number} x2 x-coordinate for the first point + * @param {Number} y2 y-coordinate for the first point + * @param {Number} x3 x-coordinate for the second point + * @param {Number} y3 y-coordinate for the second point + * @param {Number} x4 x-coordinate for the ending control point + * @param {Number} y4 y-coordinate for the ending control point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * stroke(255, 102, 0); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * stroke(0); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * stroke(255, 102, 0); + * curve(73, 24, 73, 61, 15, 65, 15, 65); + * </code> + * </div> + * <div> + * <code> + * // Define the curve points as JavaScript objects + * p1 = {x: 5, y: 26}, p2 = {x: 73, y: 24} + * p3 = {x: 73, y: 61}, p4 = {x: 15, y: 65} + * noFill(); + * stroke(255, 102, 0); + * curve(p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y) + * stroke(0); + * curve(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y) + * stroke(255, 102, 0); + * curve(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, p4.x, p4.y) + * </code> + * </div> + */ +p5.prototype.curve = function(x1, y1, x2, y2, x3, y3, x4, y4) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'curve', + args, + [ 'Number', 'Number', 'Number', 'Number', + 'Number', 'Number', 'Number', 'Number' ] + ); + + if (!this._renderer._doStroke) { + return; + } + this._renderer.curve(x1, y1, x2, y2, x3, y3, x4, y4); + return this; +}; + +/** + * Sets the resolution at which curves display. + * + * The default value is 20. + * + * @param {Number} resolution of the curves + * @return {Object} the p5 object + * @example + * <div> + * <code> + * background(204); + * curveDetail(20); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * </code> + * </div> + */ +p5.prototype.curveDetail = function(d) { + curveDetail = d; + return this; +}; + +/** + * Modifies the quality of forms created with curve() and curveVertex(). + * The parameter tightness determines how the curve fits to the vertex + * points. The value 0.0 is the default value for tightness (this value + * defines the curves to be Catmull-Rom splines) and the value 1.0 connects + * all the points with straight lines. Values within the range -5.0 and 5.0 + * will deform the curves but will leave them recognizable and as values + * increase in magnitude, they will continue to deform. + * + * @method curveTightness + * @param {Number} amount of deformation from the original vertices + * @return {Object} the p5 object + * @example + * <div> + * <code> + * // Move the mouse left and right to see the curve change + * + * function setup() { + * createCanvas(100, 100); + * noFill(); + * } + * + * function draw() { + * background(204); + * var t = map(mouseX, 0, width, -5, 5); + * curveTightness(t); + * beginShape(); + * curveVertex(10, 26); + * curveVertex(10, 26); + * curveVertex(83, 24); + * curveVertex(83, 61); + * curveVertex(25, 65); + * curveVertex(25, 65); + * endShape(); + * } + * </code> + * </div> + */ +p5.prototype.curveTightness = function (t) { + this._renderer._curveTightness = t; +}; + +/** + * Evaluates the curve at position t for points a, b, c, d. + * The parameter t varies between 0 and 1, a and d are points + * on the curve, and b and c are the control points. + * This can be done once with the x coordinates and a second time + * with the y coordinates to get the location of a curve at t. + * + * @method curvePoint + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} bezier value at position t + * @example + * <div> + * <code> + * noFill(); + * curve(5, 26, 5, 26, 73, 24, 73, 61); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * fill(255); + * ellipseMode(CENTER); + * steps = 6; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = curvePoint(5, 5, 73, 73, t); + * y = curvePoint(26, 26, 24, 61, t); + * ellipse(x, y, 5, 5); + * x = curvePoint(5, 73, 73, 15, t); + * y = curvePoint(26, 24, 61, 65, t); + * ellipse(x, y, 5, 5); + * } + * </code> + * </div> + */ +p5.prototype.curvePoint = function(a, b, c, d, t) { + var t3 = t*t*t, + t2 = t*t, + f1 = -0.5 * t3 + t2 - 0.5 * t, + f2 = 1.5 * t3 - 2.5 * t2 + 1.0, + f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t, + f4 = 0.5 * t3 - 0.5 * t2; + return a*f1 + b*f2 + c*f3 + d*f4; +}; + +/** + * Evaluates the tangent to the curve at position t for points a, b, c, d. + * The parameter t varies between 0 and 1, a and d are points on the curve, + * and b and c are the control points. + * + * @method curveTangent + * @param {Number} a coordinate of first point on the curve + * @param {Number} b coordinate of first control point + * @param {Number} c coordinate of second control point + * @param {Number} d coordinate of second point on the curve + * @param {Number} t value between 0 and 1 + * @return {Number} the tangent at position t + * @example + * <div> + * <code> + * noFill(); + * curve(5, 26, 73, 24, 73, 61, 15, 65); + * steps = 6; + * for (i = 0; i <= steps; i++) { + * t = i / steps; + * x = curvePoint(5, 73, 73, 15, t); + * y = curvePoint(26, 24, 61, 65, t); + * //ellipse(x, y, 5, 5); + * tx = curveTangent(5, 73, 73, 15, t); + * ty = curveTangent(26, 24, 61, 65, t); + * a = atan2(ty, tx); + * a -= PI/2.0; + * line(x, y, cos(a)*8 + x, sin(a)*8 + y); + * } + * </code> + * </div> + */ +p5.prototype.curveTangent = function(a, b,c, d, t) { + var t2 = t*t, + f1 = (-3*t2)/2 + 2*t - 0.5, + f2 = (9*t2)/2 - 5*t, + f3 = (-9*t2)/2 + 4*t + 0.5, + f4 = (3*t2)/2 - t; + return a*f1 + b*f2 + c*f3 + d*f4; +}; + +module.exports = p5; + +},{"./core":48,"./error_helpers":51}],50:[function(_dereq_,module,exports){ +/** + * @module Environment + * @submodule Environment + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var C = _dereq_('./constants'); + +var standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT]; + +p5.prototype._frameRate = 0; +p5.prototype._lastFrameTime = window.performance.now(); +p5.prototype._targetFrameRate = 60; + + +if (window.console && console.log) { + /** + * The print() function writes to the console area of your browser. + * This function is often helpful for looking at the data a program is + * producing. This function creates a new line of text for each call to + * the function. Individual elements can be + * separated with quotes ("") and joined with the addition operator (+). + * <br><br> + * While print() is similar to console.log(), it does not directly map to + * it in order to simulate easier to understand behavior than + * console.log(). Due to this, it is slower. For fastest results, use + * console.log(). + * + * @method print + * @param {Any} contents any combination of Number, String, Object, Boolean, + * Array to print + * @example + * <div><code class='norender'> + * var x = 10; + * print("The value of x is " + x); + * // prints "The value of x is 10" + * </code></div> + */ + // Converts passed args into a string and then parses that string to + // simulate synchronous behavior. This is a hack and is gross. + // Since this will not work on all objects, particularly circular + // structures, simply console.log() on error. + p5.prototype.print = function(args) { + try { + var newArgs = JSON.parse(JSON.stringify(args)); + console.log(newArgs); + } catch(err) { + console.log(args); + } + }; +} else { + p5.prototype.print = function() {}; +} + +p5.prototype.println = p5.prototype.print; + +/** + * The system variable frameCount contains the number of frames that have + * been displayed since the program started. Inside setup() the value is 0, + * after the first iteration of draw it is 1, etc. + * + * @property frameCount + * @example + * <div><code> + * function setup() { + * frameRate(30); + * textSize(20); + * textSize(30); + * textAlign(CENTER); + * } + * + * function draw() { + * background(200); + * text(frameCount, width/2, height/2); + * } + * </code></div> + */ +p5.prototype.frameCount = 0; + +/** + * Confirms if the window a p5.js program is in is "focused," meaning that + * the sketch will accept mouse or keyboard input. This variable is + * "true" if the window is focused and "false" if not. + * + * @property focused + * @example + * <div><code> + * // To demonstrate, put two windows side by side. + * // Click on the window that the p5 sketch isn't in! + * function draw() { + * if (focused) { // or "if (focused === true)" + * noStroke(); + * fill(0, 200, 0); + * ellipse(25, 25, 50, 50); + * } else { + * stroke(200,0,0); + * line(0, 0, 100, 100); + * line(100, 0, 0, 100); + * } + * } + * + * </code></div> + */ +p5.prototype.focused = (document.hasFocus()); + +/** + * Sets the cursor to a predefined symbol or an image, or makes it visible + * if already hidden. If you are trying to set an image as the cursor, the + * recommended size is 16x16 or 32x32 pixels. It is not possible to load an + * image as the cursor if you are exporting your program for the Web, and not + * all MODES work with all browsers. The values for parameters x and y must + * be less than the dimensions of the image. + * + * @method cursor + * @param {Number/Constant} type either ARROW, CROSS, HAND, MOVE, TEXT, or + * WAIT, or path for image + * @param {Number} [x] the horizontal active spot of the cursor + * @param {Number} [y] the vertical active spot of the cursor + * @example + * <div><code> + * // Move the mouse left and right across the image + * // to see the cursor change from a cross to a hand + * function draw() { + * line(width/2, 0, width/2, height); + * if (mouseX < 50) { + * cursor(CROSS); + * } else { + * cursor(HAND); + * } + * } + * </code></div> + */ +p5.prototype.cursor = function(type, x, y) { + var cursor = 'auto'; + var canvas = this._curElement.elt; + if (standardCursors.indexOf(type) > -1) { + // Standard css cursor + cursor = type; + } else if (typeof type === 'string') { + var coords = ''; + if (x && y && (typeof x === 'number' && typeof y === 'number')) { + // Note that x and y values must be unit-less positive integers < 32 + // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor + coords = x + ' ' + y; + } + if (type.substring(0, 6) !== 'http://') { + // Image (absolute url) + cursor = 'url(' + type + ') ' + coords + ', auto'; + } else if (/\.(cur|jpg|jpeg|gif|png|CUR|JPG|JPEG|GIF|PNG)$/.test(type)) { + // Image file (relative path) - Separated for performance reasons + cursor = 'url(' + type + ') ' + coords + ', auto'; + } else { + // Any valid string for the css cursor property + cursor = type; + } + } + canvas.style.cursor = cursor; +}; + +/** + * Specifies the number of frames to be displayed every second. For example, + * the function call frameRate(30) will attempt to refresh 30 times a second. + * If the processor is not fast enough to maintain the specified rate, the + * frame rate will not be achieved. Setting the frame rate within setup() is + * recommended. The default rate is 60 frames per second. This is the same as + * setFrameRate(val). + * <br><br> + * Calling frameRate() with no arguments returns the current framerate. This + * is the same as getFrameRate(). + * <br><br> + * Calling frameRate() with arguments that are not of the type numbers + * or are non positive also returns current framerate. + * + * @method frameRate + * @param {Number} [fps] number of frames to be displayed every second + * @return {Number} current frameRate + * @example + * + * <div><code> + * var rectX = 0; + * var fr = 30; //starting FPS + * var clr; + * + * function setup() { + * background(200); + * frameRate(fr); // Attempt to refresh at starting FPS + * clr = color(255,0,0); + * } + * + * function draw() { + * background(200); + * rectX = rectX += 1; // Move Rectangle + * + * if (rectX >= width) { // If you go off screen. + * if (fr == 30) { + * clr = color(0,0,255); + * fr = 10; + * frameRate(fr); // make frameRate 10 FPS + * } else { + * clr = color(255,0,0); + * fr = 30; + * frameRate(fr); // make frameRate 30 FPS + * } + * rectX = 0; + * } + * fill(clr); + * rect(rectX, 40, 20,20); + * } + * </div></code> + * + */ +p5.prototype.frameRate = function(fps) { + if (typeof fps !== 'number' || fps <= 0) { + return this._frameRate; + } else { + this._setProperty('_targetFrameRate', fps); + this._runFrames(); + return this; + } +}; +/** + * Returns the current framerate. + * + * @return {Number} current frameRate + */ +p5.prototype.getFrameRate = function() { + return this.frameRate(); +}; + +/** + * Specifies the number of frames to be displayed every second. For example, + * the function call frameRate(30) will attempt to refresh 30 times a second. + * If the processor is not fast enough to maintain the specified rate, the + * frame rate will not be achieved. Setting the frame rate within setup() is + * recommended. The default rate is 60 frames per second. + * + * Calling frameRate() with no arguments returns the current framerate. + * + * @param {Number} [fps] number of frames to be displayed every second + */ +p5.prototype.setFrameRate = function(fps) { + return this.frameRate(fps); +}; + +/** + * Hides the cursor from view. + * + * @method noCursor + * @example + * <div><code> + * function setup() { + * noCursor(); + * } + * + * function draw() { + * background(200); + * ellipse(mouseX, mouseY, 10, 10); + * } + * </code></div> + */ +p5.prototype.noCursor = function() { + this._curElement.elt.style.cursor = 'none'; +}; + + +/** + * System variable that stores the width of the entire screen display. This + * is used to run a full-screen program on any display size. + * + * @property displayWidth + * @example + * <div class="norender"><code> + * createCanvas(displayWidth, displayHeight); + * </code></div> + */ +p5.prototype.displayWidth = screen.width; + +/** + * System variable that stores the height of the entire screen display. This + * is used to run a full-screen program on any display size. + * + * @property displayHeight + * @example + * <div class="norender"><code> + * createCanvas(displayWidth, displayHeight); + * </code></div> + */ +p5.prototype.displayHeight = screen.height; + +/** + * System variable that stores the width of the inner window, it maps to + * window.innerWidth. + * + * @property windowWidth + * @example + * <div class="norender"><code> + * createCanvas(windowWidth, windowHeight); + * </code></div> + */ +p5.prototype.windowWidth = window.innerWidth; +/** + * System variable that stores the height of the inner window, it maps to + * window.innerHeight. + * + * @property windowHeight + * @example + * <div class="norender"><code> + * createCanvas(windowWidth, windowHeight); + * </code></div> + */ +p5.prototype.windowHeight = window.innerHeight; + +/** + * The windowResized() function is called once every time the browser window + * is resized. This is a good place to resize the canvas or do any other + * adjustements to accomodate the new window size. + * + * @method windowResized + * @example + * <div class="norender"><code> + * function setup() { + * createCanvas(windowWidth, windowHeight); + * } + * + * function draw() { + * background(0, 100, 200); + * } + * + * function windowResized() { + * resizeCanvas(windowWidth, windowHeight); + * } + * </code></div> + */ +p5.prototype._onresize = function(e){ + this._setProperty('windowWidth', window.innerWidth); + this._setProperty('windowHeight', window.innerHeight); + var context = this._isGlobal ? window : this; + var executeDefault; + if (typeof context.windowResized === 'function') { + executeDefault = context.windowResized(e); + if (executeDefault !== undefined && !executeDefault) { + e.preventDefault(); + } + } +}; + +/** + * System variable that stores the width of the drawing canvas. This value + * is set by the first parameter of the createCanvas() function. + * For example, the function call createCanvas(320, 240) sets the width + * variable to the value 320. The value of width defaults to 100 if + * createCanvas() is not used in a program. + * + * @property width + */ +p5.prototype.width = 0; + +/** + * System variable that stores the height of the drawing canvas. This value + * is set by the second parameter of the createCanvas() function. For + * example, the function call createCanvas(320, 240) sets the height + * variable to the value 240. The value of height defaults to 100 if + * createCanvas() is not used in a program. + * + * @property height + */ +p5.prototype.height = 0; + +/** + * If argument is given, sets the sketch to fullscreen or not based on the + * value of the argument. If no argument is given, returns the current + * fullscreen state. Note that due to browser restrictions this can only + * be called on user input, for example, on mouse press like the example + * below. + * + * @method fullscreen + * @param {Boolean} [val] whether the sketch should be in fullscreen mode + * or not + * @return {Boolean} current fullscreen state + * @example + * <div> + * <code> + * // Clicking in the box toggles fullscreen on and off. + * function setup() { + * background(200); + * } + * function mousePressed() { + * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { + * var fs = fullscreen(); + * fullscreen(!fs); + * } + * } + * </code> + * </div> + */ +p5.prototype.fullscreen = function(val) { + // no arguments, return fullscreen or not + if (typeof val === 'undefined') { + return document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement; + } else { // otherwise set to fullscreen or not + if (val) { + launchFullscreen(document.documentElement); + } else { + exitFullscreen(); + } + } +}; + +/** + * Sets the pixel scaling for high pixel density displays. By default + * pixel density is set to match display density, call pixelDensity(1) + * to turn this off. Calling pixelDensity() with no arguments returns + * the current pixel density of the sketch. + * + * + * @method pixelDensity + * @param {Number} [val] whether or how much the sketch should scale + * @returns {Number} current pixel density of the sketch + * @example + * <div> + * <code> + * function setup() { + * pixelDensity(1); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * pixelDensity(3.0); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.pixelDensity = function(val) { + if (typeof val === 'number') { + this._pixelDensity = val; + } else { + return this._pixelDensity; + } + this.resizeCanvas(this.width, this.height, true); +}; + +/** + * Returns the pixel density of the current display the sketch is running on. + * + * @method displayDensity + * @returns {Number} current pixel density of the display + * @example + * <div> + * <code> + * function setup() { + * var density = displayDensity(); + * pixelDensity(density); + * createCanvas(100, 100); + * background(200); + * ellipse(width/2, height/2, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.displayDensity = function() { + return window.devicePixelRatio; +}; + +function launchFullscreen(element) { + var enabled = document.fullscreenEnabled || + document.webkitFullscreenEnabled || + document.mozFullScreenEnabled || + document.msFullscreenEnabled; + if (!enabled) { + throw new Error('Fullscreen not enabled in this browser.'); + } + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } +} + +function exitFullscreen() { + if(document.exitFullscreen) { + document.exitFullscreen(); + } else if(document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if(document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } +} + + +/** + * Gets the current URL. + * @method getURL + * @return {String} url + * @example + * <div> + * <code> + * var url; + * var x = 100; + * + * function setup() { + * fill(0); + * noStroke(); + * url = getURL(); + * } + * + * function draw() { + * background(200); + * text(url, x, height/2); + * x--; + * } + * </code> + * </div> + */ +p5.prototype.getURL = function() { + return location.href; +}; +/** + * Gets the current URL path as an array. + * @method getURLPath + * @return {Array} path components + * @example + * <div class='norender'><code> + * function setup() { + * var urlPath = getURLPath(); + * for (var i=0; i<urlPath.length; i++) { + * text(urlPath[i], 10, i*20+20); + * } + * } + * </code></div> + */ +p5.prototype.getURLPath = function() { + return location.pathname.split('/').filter(function(v){return v!=='';}); +}; +/** + * Gets the current URL params as an Object. + * @method getURLParams + * @return {Object} URL params + * @example + * <div class='norender'> + * <code> + * // Example: http://p5js.org?year=2014&month=May&day=15 + * + * function setup() { + * var params = getURLParams(); + * text(params.day, 10, 20); + * text(params.month, 10, 40); + * text(params.year, 10, 60); + * } + * </code> + * </div> + */ +p5.prototype.getURLParams = function() { + var re = /[?&]([^&=]+)(?:[&=])([^&=]+)/gim; + var m; + var v={}; + while ((m = re.exec(location.search)) != null) { + if (m.index === re.lastIndex) { + re.lastIndex++; + } + v[m[1]]=m[2]; + } + return v; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],51:[function(_dereq_,module,exports){ +/** + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var doFriendlyWelcome = false; // TEMP until we get it all working LM + +// -- Borrowed from jQuery 1.11.3 -- +var class2type = {}; +var toString = class2type.toString; +var names = ['Boolean', 'Number', 'String', 'Function', + 'Array', 'Date', 'RegExp', 'Object', 'Error']; +for (var n=0; n<names.length; n++) { + class2type[ '[object ' + names[n] + ']' ] = names[n].toLowerCase(); +} +var getType = function( obj ) { + if ( obj == null ) { + return obj + ''; + } + return typeof obj === 'object' || typeof obj === 'function' ? + class2type[ toString.call(obj) ] || 'object' : + typeof obj; +}; +var isArray = Array.isArray || function( obj ) { + return getType(obj) === 'array'; +}; +var isNumeric =function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; +}; +// -- End borrow -- + +/** + * Checks the definition type against the argument type + * If any of these passes (in order), it matches: + * + * - p5.* definitions are checked with instanceof + * - Booleans are let through (everything is truthy or falsey) + * - Lowercase of the definition is checked against the js type + * - Number types are checked to see if they are numerically castable + */ +var numberTypes = ['Number', 'Integer', 'Number/Constant']; +function typeMatches(defType, argType, arg) { + if(defType.match(/^p5\./)) { + var parts = defType.split('.'); + return arg instanceof p5[parts[1]]; + } + return defType === 'Boolean' || // Anything is truthy, cover in Debug Guide + (defType.toLowerCase() === argType) || + (numberTypes.indexOf(defType) > -1 && isNumeric(arg)); +} + +/** + * Prints out a fancy, colorful message to the console log + * + * @param {String} message the words to be said + * @param {String} func the name of the function to link + * @param {Integer/Color String} color CSS color string or error type + * + * @return console logs + */ +// Wrong number of params, undefined param, wrong type +var PARAM_COUNT = 0; +var EMPTY_VAR = 1; +var WRONG_TYPE = 2; +var FILE_LOAD = 3; +// p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta +// See testColors below for all the color codes and names +var typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00']; +function report(message, func, color) { + if(doFriendlyWelcome){ + friendlyWelcome(); + doFriendlyWelcome =false; + } + if ('undefined' === getType(color)) { + color = '#B40033'; // dark magenta + } else if (getType(color) === 'number') { // Type to color + color = typeColors[color]; + } + // LM TEMP commenting this out until we get the whole system working + // if (func.substring(0,4) === 'load'){ + // console.log( + // '%c> p5.js says: '+message+'%c'+ + // '[https://github.com/processing/p5.js/wiki/Local-server]', + // 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';', + // 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';' + // ); + // } + // else{ + // console.log( + // '%c> p5.js says: '+message+'%c [http://p5js.org/reference/#p5/'+func+ + // ']', 'background-color:' + color + ';color:#FFF;', + // 'background-color:transparent;color:' + color +';' + // ); + // } +} + +/** + * Validate all the parameters of a function for number and type + * NOTE THIS FUNCTION IS TEMPORARILY DISABLED UNTIL FURTHER WORK + * AND UPDATES ARE IMPLEMENTED. -LMCCART + * + * @param {String} func name of function we're checking + * @param {Array} args pass of the JS default arguments array + * @param {Array} types List of types accepted ['Number', 'String, ...] OR + * a list of lists for each format: [ + * ['String', 'Number', 'Number'], + * ['String', 'Number', 'Number', 'Number', 'Number' + * ] + * + * @return console logs + */ +p5.prototype._validateParameters = function(func, args, types) { + if (!isArray(types[0])) { + types = [types]; + } + // Check number of parameters + // Example: "You wrote ellipse(X,X,X). ellipse was expecting 4 + // parameters. Try ellipse(X,X,X,X)." + var diff = Math.abs(args.length-types[0].length); + var message, tindex = 0; + for (var i=1, len=types.length; i<len; i++) { + var d = Math.abs(args.length-types[i].length); + if (d <= diff) { + tindex = i; + diff = d; + } + } + var symbol = 'X'; // Parameter placeholder + if(diff > 0) { + message = 'You wrote ' + func + '('; + // Concat an appropriate number of placeholders for call + if (args.length > 0) { + message += symbol + Array(args.length).join(',' + symbol); + } + message += '). ' + func + ' was expecting ' + types[tindex].length + + ' parameters. Try ' + func + '('; + // Concat an appropriate number of placeholders for definition + if (types[tindex].length > 0) { + message += symbol + Array(types[tindex].length).join(',' + symbol); + } + message += ').'; + // If multiple definitions + if (types.length > 1) { + message += ' ' + func + ' takes different numbers of parameters ' + + 'depending on what you want to do. Click this link to learn more: '; + } + report(message, func, PARAM_COUNT); + } + // Type checking + // Example: "It looks like ellipse received an empty variable in spot #2." + // Example: "ellipse was expecting a number for parameter #1, + // received "foo" instead." + for (var format=0; format<types.length; format++) { + for (var p=0; p < types[format].length && p < args.length; p++) { + var defType = types[format][p]; + var argType = getType(args[p]); + if ('undefined' === argType || null === argType) { + report('It looks like ' + func + + ' received an empty variable in spot #' + (p+1) + + '. If not intentional, this is often a problem with scope: ' + + '[link to scope].', func, EMPTY_VAR); + } else if (defType !== '*' && !typeMatches(defType, argType, args[p])) { + message = func + ' was expecting a ' + defType.toLowerCase() + + ' for parameter #' + (p+1) + ', received '; + // Wrap strings in quotes + message += 'string' === argType ? '"' + args[p] + '"' : args[p]; + message += ' instead.'; + // If multiple definitions + if (types.length > 1) { + message += ' ' + func + ' takes different numbers of parameters ' + + 'depending on what you want to do. ' + + 'Click this link to learn more:'; + } + report(message, func, WRONG_TYPE); + } + } + } +}; +/* + * NOTE THIS FUNCTION IS TEMPORARILY DISABLED UNTIL FURTHER WORK + * AND UPDATES ARE IMPLEMENTED. -LMCCART + */ +p5.prototype._validateParameters = function() { + return true; +}; + +var errorCases = { + '0': { + fileType: 'image', + method: 'loadImage', + message: ' hosting the image online,' + }, + '1': { + fileType: 'XML file', + method: 'loadXML' + }, + '2': { + fileType: 'table file', + method: 'loadTable' + }, + '3': { + fileType: 'text file', + method: 'loadStrings' + } +}; +p5._friendlyFileLoadError = function (errorType, filePath) { + var errorInfo = errorCases[ errorType ]; + var message = 'It looks like there was a problem' + + ' loading your ' + errorInfo.fileType + '.' + + ' Try checking if the file path%c [' + filePath + '] %cis correct,' + + (errorInfo.message || '') + ' or running a local server.'; + report(message, errorInfo.method, FILE_LOAD); +}; + +function friendlyWelcome() { + // p5.js brand - magenta: #ED225D + var astrixBgColor = 'transparent'; + var astrixTxtColor = '#ED225D'; + var welcomeBgColor = '#ED225D'; + var welcomeTextColor = 'white'; + console.log( + '%c _ \n'+ + ' /\\| |/\\ \n'+ + ' \\ ` \' / \n'+ + ' / , . \\ \n'+ + ' \\/|_|\\/ '+ + '\n\n%c> p5.js says: Welcome! '+ + 'This is your friendly debugger. ' + + 'To turn me off switch to using “p5.min.jsâ€.', + 'background-color:'+astrixBgColor+';color:' + astrixTxtColor +';', + 'background-color:'+welcomeBgColor+';color:' + welcomeTextColor +';' + ); +} + +/** + * Prints out all the colors in the color pallete with white text. + * For color blindness testing. + */ +/* function testColors() { + var str = 'A box of biscuits, a box of mixed biscuits and a biscuit mixer'; + report(str, 'println', '#ED225D'); // p5.js magenta + report(str, 'println', '#2D7BB6'); // p5.js blue + report(str, 'println', '#EE9900'); // p5.js orange + report(str, 'println', '#A67F59'); // p5.js light brown + report(str, 'println', '#704F21'); // p5.js gold + report(str, 'println', '#1CC581'); // auto cyan + report(str, 'println', '#FF6625'); // auto orange + report(str, 'println', '#79EB22'); // auto green + report(str, 'println', '#B40033'); // p5.js darkened magenta + report(str, 'println', '#084B7F'); // p5.js darkened blue + report(str, 'println', '#945F00'); // p5.js darkened orange + report(str, 'println', '#6B441D'); // p5.js darkened brown + report(str, 'println', '#2E1B00'); // p5.js darkened gold + report(str, 'println', '#008851'); // auto dark cyan + report(str, 'println', '#C83C00'); // auto dark orange + report(str, 'println', '#4DB200'); // auto dark green +} */ + +// This is a lazily-defined list of p5 symbols that may be +// misused by beginners at top-level code, outside of setup/draw. We'd like +// to detect these errors and help the user by suggesting they move them +// into setup/draw. +// +// For more details, see https://github.com/processing/p5.js/issues/1121. +var misusedAtTopLevelCode = null; +var FAQ_URL = 'https://github.com/processing/p5.js/wiki/' + + 'Frequently-Asked-Questions' + + '#why-cant-i-assign-variables-using-p5-functions-and-' + + 'variables-before-setup'; + +function defineMisusedAtTopLevelCode() { + var uniqueNamesFound = {}; + + var getSymbols = function(obj) { + return Object.getOwnPropertyNames(obj).filter(function(name) { + if (name[0] === '_') { + return false; + } + if (name in uniqueNamesFound) { + return false; + } + + uniqueNamesFound[name] = true; + + return true; + }).map(function(name) { + var type; + + if (typeof(obj[name]) === 'function') { + type = 'function'; + } else if (name === name.toUpperCase()) { + type = 'constant'; + } else { + type = 'variable'; + } + + return {name: name, type: type}; + }); + }; + + misusedAtTopLevelCode = [].concat( + getSymbols(p5.prototype), + // At present, p5 only adds its constants to p5.prototype during + // construction, which may not have happened at the time a + // ReferenceError is thrown, so we'll manually add them to our list. + getSymbols(_dereq_('./constants')) + ); + + // This will ultimately ensure that we report the most specific error + // possible to the user, e.g. advising them about HALF_PI instead of PI + // when their code misuses the former. + misusedAtTopLevelCode.sort(function(a, b) { + return b.name.length - a.name.length; + }); +} + +function helpForMisusedAtTopLevelCode(e, log) { + if (!log) { + log = console.log.bind(console); + } + + if (!misusedAtTopLevelCode) { + defineMisusedAtTopLevelCode(); + } + + // If we find that we're logging lots of false positives, we can + // uncomment the following code to avoid displaying anything if the + // user's code isn't likely to be using p5's global mode. (Note that + // setup/draw are more likely to be defined due to JS function hoisting.) + // + //if (!('setup' in window || 'draw' in window)) { + // return; + //} + + misusedAtTopLevelCode.some(function(symbol) { + // Note that while just checking for the occurrence of the + // symbol name in the error message could result in false positives, + // a more rigorous test is difficult because different browsers + // log different messages, and the format of those messages may + // change over time. + // + // For example, if the user uses 'PI' in their code, it may result + // in any one of the following messages: + // + // * 'PI' is undefined (Microsoft Edge) + // * ReferenceError: PI is undefined (Firefox) + // * Uncaught ReferenceError: PI is not defined (Chrome) + + if (e.message && e.message.indexOf(symbol.name) !== -1) { + log('%cDid you just try to use p5.js\'s ' + symbol.name + + (symbol.type === 'function' ? '() ' : ' ') + symbol.type + + '? If so, you may want to ' + + 'move it into your sketch\'s setup() function.\n\n' + + 'For more details, see: ' + FAQ_URL, + 'color: #B40033' /* Dark magenta */); + return true; + } + }); +} + +// Exposing this primarily for unit testing. +p5.prototype._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode; + +if (document.readyState !== 'complete') { + window.addEventListener('error', helpForMisusedAtTopLevelCode, false); + + // Our job is only to catch ReferenceErrors that are thrown when + // global (non-instance mode) p5 APIs are used at the top-level + // scope of a file, so we'll unbind our error listener now to make + // sure we don't log false positives later. + window.addEventListener('load', function() { + window.removeEventListener('error', helpForMisusedAtTopLevelCode, false); + }); +} + +module.exports = p5; + +},{"./constants":47,"./core":48}],52:[function(_dereq_,module,exports){ +/** + * @module DOM + * @submodule DOM + * @for p5.Element + */ + +var p5 = _dereq_('./core'); + +/** + * Base class for all elements added to a sketch, including canvas, + * graphics buffers, and other HTML elements. Methods in blue are + * included in the core functionality, methods in brown are added + * with the <a href="http://p5js.org/libraries/">p5.dom library</a>. + * It is not called directly, but p5.Element + * objects are created by calling createCanvas, createGraphics, + * or in the p5.dom library, createDiv, createImg, createInput, etc. + * + * @class p5.Element + * @constructor + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + */ +p5.Element = function(elt, pInst) { + /** + * Underlying HTML element. All normal HTML methods can be called on this. + * + * @property elt + */ + this.elt = elt; + this._pInst = pInst; + this._events = {}; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; +}; + +/** + * + * Attaches the element to the parent specified. A way of setting + * the container for the element. Accepts either a string ID, DOM + * node, or p5.Element. If no arguments given, parent node is returned. + * + * @method parent + * @param {String|Object} parent the ID, DOM node, or p5.Element + * of desired parent element + * @return {p5.Element} + * @example + * <div class="norender"><code> + * // in the html file: + * <div id="myContainer"></div> + * // in the js file: + * var cnv = createCanvas(100, 100); + * cnv.parent("myContainer"); + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * var div1 = createDiv('this is the child'); + * div1.parent(div0); // use p5.Element + * </code></div> + * <div class='norender'><code> + * var div0 = createDiv('this is the parent'); + * div0.id('apples'); + * var div1 = createDiv('this is the child'); + * div1.parent('apples'); // use id + * </code></div> + * <div class='norender'><code> + * var elt = document.getElementById('myParentDiv'); + * var div1 = createDiv('this is the child'); + * div1.parent(elt); // use element from page + * </code></div> + */ +p5.Element.prototype.parent = function(p) { + if (arguments.length === 0){ + return this.elt.parentNode; + } else { + if (typeof p === 'string') { + if (p[0] === '#') { + p = p.substring(1); + } + p = document.getElementById(p); + } else if (p instanceof p5.Element) { + p = p.elt; + } + p.appendChild(this.elt); + return this; + } +}; + +/** + * + * Sets the ID of the element + * + * @method id + * @param {String} id ID of the element + * @return {p5.Element} + */ +p5.Element.prototype.id = function(id) { + if (arguments.length === 0) { + return this.elt.id; + } else { + this.elt.id = id; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; + return this; + } +}; + +/** + * + * Adds given class to the element + * + * @method class + * @param {String} class class to add + * @return {p5.Element} + */ +p5.Element.prototype.class = function(c) { + if (arguments.length === 0) { + return this.elt.className; + } else { + this.elt.className = c; + this.width = this.elt.offsetWidth; + this.height = this.elt.offsetHeight; + return this; + } +}; + +/** + * The .mousePressed() function is called once after every time a + * mouse button is pressed over the element. This can be used to + * attach element specific event listeners. + * + * @method mousePressed + * @param {Function} fxn function to be fired when mouse is + * pressed over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mousePressed(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any click anywhere + * function mousePressed() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mousePressed = function (fxn) { + attachListener('mousedown', fxn, this); + attachListener('touchstart', fxn, this); + return this; +}; + +/** + * The .mouseWheel() function is called once after every time a + * mouse wheel is scrolled over the element. This can be used to + * attach element specific event listeners.<br><br> + * The event.wheelDelta or event.detail property returns negative values if + * the mouse wheel if rotated up or away from the user and positive in the + * other direction. On OS X with "natural" scrolling enabled, the values are + * opposite. + * + * @method mouseWheel + * @param {Function} fxn function to be fired when mouse wheel is + * scrolled over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseWheel(changeSize); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with mousewheel movement + * // anywhere on screen + * function mouseWheel() { + * g = g + 10; + * } + * + * // this function fires with mousewheel movement + * // over canvas only + * function changeSize() { + * if (event.wheelDelta > 0) { + * d = d + 10; + * } else { + * d = d - 10; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseWheel = function (fxn) { + attachListener('wheel', fxn, this); + return this; +}; + +/** + * The .mouseReleased() function is called once after every time a + * mouse button is released over the element. This can be used to + * attach element specific event listeners. + * + * @method mouseReleased + * @param {Function} fxn function to be fired when mouse is + * released over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseReleased(changeGray); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires after the mouse has been + * // released + * function mouseReleased() { + * d = d + 10; + * } + * + * // this function fires after the mouse has been + * // released while on canvas + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mouseReleased = function (fxn) { + attachListener('mouseup', fxn, this); + attachListener('touchend', fxn, this); + return this; +}; + + +/** + * The .mouseClicked() function is called once after a mouse button is + * pressed and released over the element. This can be used to + * attach element specific event listeners. + * + * @method mouseClicked + * @param {Function} fxn function to be fired when mouse is + * clicked over the element. + * @return {p5.Element} + * @example + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseClicked(changeGray); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires after the mouse has been + * // clicked anywhere + * function mouseClicked() { + * d = d + 10; + * } + * + * // this function fires after the mouse has been + * // clicked on canvas + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.mouseClicked = function (fxn) { + attachListener('click', fxn, this); + return this; +}; + +/** + * The .mouseMoved() function is called once every time a + * mouse moves over the element. This can be used to attach an + * element specific event listener. + * + * @method mouseMoved + * @param {Function} fxn function to be fired when mouse is + * moved over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d = 30; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseMoved(changeSize); // attach listener for + * // activity on canvas only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * fill(200); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires when mouse moves anywhere on + * // page + * function mouseMoved() { + * g = g + 5; + * if (g > 255) { + * g = 0; + * } + * } + * + * // this function fires when mouse moves over canvas + * function changeSize() { + * d = d + 2; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseMoved = function (fxn) { + attachListener('mousemove', fxn, this); + attachListener('touchmove', fxn, this); + return this; +}; + +/** + * The .mouseOver() function is called once after every time a + * mouse moves onto the element. This can be used to attach an + * element specific event listener. + * + * @method mouseOver + * @param {Function} fxn function to be fired when mouse is + * moved over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseOver(changeGray); + * d = 10; + * } + * + * function draw() { + * ellipse(width/2, height/2, d, d); + * } + * + * function changeGray() { + * d = d + 10; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseOver = function (fxn) { + attachListener('mouseover', fxn, this); + return this; +}; + + +/** + * The .changed() function is called when the value of an + * element is changed. + * This can be used to attach an element specific event listener. + * + * @method changed + * @param {Function} fxn function to be fired when the value of an + * element changes. + * @return {p5.Element} + * @example + * <div><code> + * var sel; + * + * function setup() { + * textAlign(CENTER); + * background(200); + * sel = createSelect(); + * sel.position(10, 10); + * sel.option('pear'); + * sel.option('kiwi'); + * sel.option('grape'); + * sel.changed(mySelectEvent); + * } + * + * function mySelectEvent() { + * var item = sel.value(); + * background(200); + * text("it's a "+item+"!", 50, 50); + * } + * </code></div> + * <div><code> + * var checkbox; + * var cnv; + * + * function setup() { + * checkbox = createCheckbox(" fill"); + * checkbox.changed(changeFill); + * cnv = createCanvas(100, 100); + * cnv.position(0, 30); + * noFill(); + * } + * + * function draw() { + * background(200); + * ellipse(50, 50, 50, 50); + * } + * + * function changeFill() { + * if (checkbox.checked()) { + * fill(0); + * } else { + * noFill(); + * } + * } + * </code></div> + */ +p5.Element.prototype.changed = function (fxn) { + attachListener('change', fxn, this); + return this; +}; + +/** + * The .input() function is called when any user input is + * detected with an element. The input event is often used + * to detect keystrokes in a input element, or changes on a + * slider element. This can be used to attach an element specific + * event listener. + * + * @method input + * @param {Function} fxn function to be fired on user input. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * // Open your console to see the output + * function setup() { + * var inp = createInput(''); + * inp.input(myInputEvent); + * } + * + * function myInputEvent() { + * console.log('you are typing: ', this.value()); + * } + * </code></div> + * + */ +p5.Element.prototype.input = function (fxn) { + attachListener('input', fxn, this); + return this; +}; + +/** + * The .mouseOut() function is called once after every time a + * mouse moves off the element. This can be used to attach an + * element specific event listener. + * + * @method mouseOut + * @param {Function} fxn function to be fired when mouse is + * moved off the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.mouseOut(changeGray); + * d = 10; + * } + * + * function draw() { + * ellipse(width/2, height/2, d, d); + * } + * + * function changeGray() { + * d = d + 10; + * if (d > 100) { + * d = 0; + * } + * } + * </code></div> + * + */ +p5.Element.prototype.mouseOut = function (fxn) { + attachListener('mouseout', fxn, this); + return this; +}; + +/** + * The .touchStarted() function is called once after every time a touch is + * registered. This can be used to attach element specific event listeners. + * + * @method touchStarted + * @param {Function} fxn function to be fired when touch is + * started over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchStarted(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any touch anywhere + * function touchStarted() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchStarted = function (fxn) { + attachListener('touchstart', fxn, this); + attachListener('mousedown', fxn, this); + return this; +}; + +/** + * The .touchMoved() function is called once after every time a touch move is + * registered. This can be used to attach element specific event listeners. + * + * @method touchMoved + * @param {Function} fxn function to be fired when touch is moved + * over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchMoved(changeGray); // attach listener for + * // canvas click only + * g = 100; + * } + * + * function draw() { + * background(g); + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchMoved = function (fxn) { + attachListener('touchmove', fxn, this); + attachListener('mousemove', fxn, this); + return this; +}; + +/** + * The .touchEnded() function is called once after every time a touch is + * registered. This can be used to attach element specific event listeners. + * + * @method touchEnded + * @param {Function} fxn function to be fired when touch is + * ended over the element. + * @return {p5.Element} + * @example + * <div class='norender'><code> + * var cnv; + * var d; + * var g; + * function setup() { + * cnv = createCanvas(100, 100); + * cnv.touchEnded(changeGray); // attach listener for + * // canvas click only + * d = 10; + * g = 100; + * } + * + * function draw() { + * background(g); + * ellipse(width/2, height/2, d, d); + * } + * + * // this function fires with any touch anywhere + * function touchEnded() { + * d = d + 10; + * } + * + * // this function fires only when cnv is clicked + * function changeGray() { + * g = random(0, 255); + * } + * </code></div> + * + */ +p5.Element.prototype.touchEnded = function (fxn) { + attachListener('touchend', fxn, this); + attachListener('mouseup', fxn, this); + return this; +}; + + + +/** + * The .dragOver() function is called once after every time a + * file is dragged over the element. This can be used to attach an + * element specific event listener. + * + * @method dragOver + * @param {Function} fxn function to be fired when mouse is + * dragged over the element. + * @return {p5.Element} + */ +p5.Element.prototype.dragOver = function (fxn) { + attachListener('dragover', fxn, this); + return this; +}; + +/** + * The .dragLeave() function is called once after every time a + * dragged file leaves the element area. This can be used to attach an + * element specific event listener. + * + * @method dragLeave + * @param {Function} fxn function to be fired when mouse is + * dragged over the element. + * @return {p5.Element} + */ +p5.Element.prototype.dragLeave = function (fxn) { + attachListener('dragleave', fxn, this); + return this; +}; + +/** + * The .drop() function is called for each file dropped on the element. + * It requires a callback that is passed a p5.File object. You can + * optionally pass two callbacks, the first one (required) is triggered + * for each file dropped when the file is loaded. The second (optional) + * is triggered just once when a file (or files) are dropped. + * + * @method drop + * @param {Function} callback triggered when files are dropped. + * @param {Function} callback to receive loaded file. + * @return {p5.Element} + */ +p5.Element.prototype.drop = function (callback, fxn) { + // Make a file loader callback and trigger user's callback + function makeLoader(theFile) { + // Making a p5.File object + var p5file = new p5.File(theFile); + return function(e) { + p5file.data = e.target.result; + callback(p5file); + }; + } + + // Is the file stuff supported? + if (window.File && window.FileReader && window.FileList && window.Blob) { + + // If you want to be able to drop you've got to turn off + // a lot of default behavior + attachListener('dragover',function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + },this); + + // If this is a drag area we need to turn off the default behavior + attachListener('dragleave',function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + },this); + + // If just one argument it's the callback for the files + if (arguments.length > 1) { + attachListener('drop', fxn, this); + } + + // Deal with the files + attachListener('drop', function(evt) { + + evt.stopPropagation(); + evt.preventDefault(); + + // A FileList + var files = evt.dataTransfer.files; + + // Load each one and trigger the callback + for (var i = 0; i < files.length; i++) { + var f = files[i]; + var reader = new FileReader(); + reader.onload = makeLoader(f); + + + // Text or data? + // This should likely be improved + if (f.type.indexOf('text') > -1) { + reader.readAsText(f); + } else { + reader.readAsDataURL(f); + } + } + }, this); + } else { + console.log('The File APIs are not fully supported in this browser.'); + } + + return this; +}; + + + + +function attachListener(ev, fxn, ctx) { + // LM removing, not sure why we had this? + // var _this = ctx; + // var f = function (e) { fxn(e, _this); }; + var f = fxn.bind(ctx); + ctx.elt.addEventListener(ev, f, false); + ctx._events[ev] = f; +} + +/** + * Helper fxn for sharing pixel methods + * + */ +p5.Element.prototype._setProperty = function (prop, value) { + this[prop] = value; +}; + + +module.exports = p5.Element; + +},{"./core":48}],53:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * Thin wrapper around a renderer, to be used for creating a + * graphics buffer object. Use this class if you need + * to draw into an off-screen graphics buffer. The two parameters define the + * width and height in pixels. The fields and methods for this class are + * extensive, but mirror the normal drawing API for p5. + * + * @class p5.Graphics + * @constructor + * @extends p5.Element + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + * @param {Boolean} whether we're using it as main canvas + */ +p5.Graphics = function(w, h, renderer, pInst) { + + var r = renderer || constants.P2D; + + var c = document.createElement('canvas'); + var node = this._userNode || document.body; + node.appendChild(c); + + p5.Element.call(this, c, pInst, false); + this._styles = []; + this.width = w; + this.height = h; + this._pixelDensity = pInst._pixelDensity; + + if (r === constants.WEBGL) { + this._renderer = new p5.Renderer3D(c, pInst, false); + } else { + this._renderer = new p5.Renderer2D(c, pInst, false); + } + + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + + pInst._elements.push(this); + + // bind methods and props of p5 to the new object + for (var p in p5.prototype) { + if (!this[p]) { + if (typeof p5.prototype[p] === 'function') { + this[p] = p5.prototype[p].bind(this); + } else { + this[p] = p5.prototype[p]; + } + } + } + + return this; +}; + +p5.Graphics.prototype = Object.create(p5.Element.prototype); + +module.exports = p5.Graphics; + +},{"./constants":47,"./core":48}],54:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('../core/constants'); + +/** + * Main graphics and rendering context, as well as the base API + * implementation for p5.js "core". To be used as the superclass for + * Renderer2D and Renderer3D classes, respecitvely. + * + * @class p5.Renderer + * @constructor + * @extends p5.Element + * @param {String} elt DOM node that is wrapped + * @param {Object} [pInst] pointer to p5 instance + * @param {Boolean} whether we're using it as main canvas + */ +p5.Renderer = function(elt, pInst, isMainCanvas) { + p5.Element.call(this, elt, pInst); + this.canvas = elt; + this._pInst = pInst; + if (isMainCanvas) { + this._isMainCanvas = true; + // for pixel method sharing with pimage + this._pInst._setProperty('_curElement', this); + this._pInst._setProperty('canvas', this.canvas); + this._pInst._setProperty('width', this.width); + this._pInst._setProperty('height', this.height); + } else { // hide if offscreen buffer by default + this.canvas.style.display = 'none'; + this._styles = []; // non-main elt styles stored in p5.Renderer + } + + + this._textSize = 12; + this._textLeading = 15; + this._textFont = 'sans-serif'; + this._textStyle = constants.NORMAL; + this._textAscent = null; + this._textDescent = null; + + + this._rectMode = constants.CORNER; + this._ellipseMode = constants.CENTER; + this._curveTightness = 0; + this._imageMode = constants.CORNER; + + this._tint = null; + this._doStroke = true; + this._doFill = true; + this._strokeSet = false; + this._fillSet = false; + this._colorMode = constants.RGB; + this._colorMaxes = { + rgb: [255, 255, 255, 255], + hsb: [360, 100, 100, 1], + hsl: [360, 100, 100, 1] + }; + +}; + +p5.Renderer.prototype = Object.create(p5.Element.prototype); + + + + +/** + * Resize our canvas element. + */ +p5.Renderer.prototype.resize = function(w, h) { + this.width = w; + this.height = h; + this.elt.width = w * this._pInst._pixelDensity; + this.elt.height = h * this._pInst._pixelDensity; + this.elt.style.width = w +'px'; + this.elt.style.height = h + 'px'; + if (this._isMainCanvas) { + this._pInst._setProperty('width', this.width); + this._pInst._setProperty('height', this.height); + } +}; + +p5.Renderer.prototype.textLeading = function(l) { + + if (arguments.length && arguments[0]) { + + this._setProperty('_textLeading', l); + return this; + } + + return this._textLeading; +}; + +p5.Renderer.prototype.textSize = function(s) { + + if (arguments.length && arguments[0]) { + + this._setProperty('_textSize', s); + this._setProperty('_textLeading', s * constants._DEFAULT_LEADMULT); + return this._applyTextProperties(); + } + + return this._textSize; +}; + +p5.Renderer.prototype.textStyle = function(s) { + + if (arguments.length && arguments[0]) { + + if (s === constants.NORMAL || + s === constants.ITALIC || + s === constants.BOLD) { + this._setProperty('_textStyle', s); + } + + return this._applyTextProperties(); + } + + return this._textStyle; +}; + +p5.Renderer.prototype.textAscent = function() { + if (this._textAscent === null) { + this._updateTextMetrics(); + } + return this._textAscent; +}; + +p5.Renderer.prototype.textDescent = function() { + + if (this._textDescent === null) { + this._updateTextMetrics(); + } + return this._textDescent; +}; + +/** + * Helper fxn to check font type (system or otf) + */ +p5.Renderer.prototype._isOpenType = function(f) { + + f = f || this._textFont; + return (typeof f === 'object' && f.font && f.font.supported); +}; + +p5.Renderer.prototype._updateTextMetrics = function() { + + if (this._isOpenType()) { + + this._setProperty('_textAscent', this._textFont._textAscent()); + this._setProperty('_textDescent', this._textFont._textDescent()); + return this; + } + + // Adapted from http://stackoverflow.com/a/25355178 + var text = document.createElement('span'); + text.style.fontFamily = this._textFont; + text.style.fontSize = this._textSize + 'px'; + text.innerHTML = 'ABCjgq|'; + + var block = document.createElement('div'); + block.style.display = 'inline-block'; + block.style.width = '1px'; + block.style.height = '0px'; + + var container = document.createElement('div'); + container.appendChild(text); + container.appendChild(block); + + container.style.height = '0px'; + container.style.overflow = 'hidden'; + document.body.appendChild(container); + + block.style.verticalAlign = 'baseline'; + var blockOffset = calculateOffset(block); + var textOffset = calculateOffset(text); + var ascent = blockOffset[1] - textOffset[1]; + + block.style.verticalAlign = 'bottom'; + blockOffset = calculateOffset(block); + textOffset = calculateOffset(text); + var height = blockOffset[1] - textOffset[1]; + var descent = height - ascent; + + document.body.removeChild(container); + + this._setProperty('_textAscent', ascent); + this._setProperty('_textDescent', descent); + + return this; +}; + +/** + * Helper fxn to measure ascent and descent. + * Adapted from http://stackoverflow.com/a/25355178 + */ +function calculateOffset(object) { + var currentLeft = 0, + currentTop = 0; + if (object.offsetParent) { + do { + currentLeft += object.offsetLeft; + currentTop += object.offsetTop; + } while (object = object.offsetParent); + } else { + currentLeft += object.offsetLeft; + currentTop += object.offsetTop; + } + return [currentLeft, currentTop]; +} + +module.exports = p5.Renderer; + +},{"../core/constants":47,"./core":48}],55:[function(_dereq_,module,exports){ + +var p5 = _dereq_('./core'); +var canvas = _dereq_('./canvas'); +var constants = _dereq_('./constants'); +var filters = _dereq_('../image/filters'); + +_dereq_('./p5.Renderer'); + +/** + * p5.Renderer2D + * The 2D graphics canvas renderer class. + * extends p5.Renderer + */ +var styleEmpty = 'rgba(0,0,0,0)'; +// var alphaThreshold = 0.00125; // minimum visible + +p5.Renderer2D = function(elt, pInst, isMainCanvas){ + p5.Renderer.call(this, elt, pInst, isMainCanvas); + this.drawingContext = this.canvas.getContext('2d'); + this._pInst._setProperty('drawingContext', this.drawingContext); + return this; +}; + +p5.Renderer2D.prototype = Object.create(p5.Renderer.prototype); + +p5.Renderer2D.prototype._applyDefaults = function() { + this.drawingContext.fillStyle = constants._DEFAULT_FILL; + this.drawingContext.strokeStyle = constants._DEFAULT_STROKE; + this.drawingContext.lineCap = constants.ROUND; + this.drawingContext.font = 'normal 12px sans-serif'; +}; + +p5.Renderer2D.prototype.resize = function(w,h) { + p5.Renderer.prototype.resize.call(this, w,h); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); +}; + +////////////////////////////////////////////// +// COLOR | Setting +////////////////////////////////////////////// + +p5.Renderer2D.prototype.background = function() { + this.drawingContext.save(); + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + + if (arguments[0] instanceof p5.Image) { + this._pInst.image(arguments[0], 0, 0, this.width, this.height); + } else { + var curFill = this.drawingContext.fillStyle; + // create background rect + var color = this._pInst.color.apply(this, arguments); + var newFill = color.toString(); + this.drawingContext.fillStyle = newFill; + this.drawingContext.fillRect(0, 0, this.width, this.height); + // reset fill + this.drawingContext.fillStyle = curFill; + } + this.drawingContext.restore(); +}; + +p5.Renderer2D.prototype.clear = function() { + this.drawingContext.clearRect(0, 0, this.width, this.height); +}; + +p5.Renderer2D.prototype.fill = function() { + + var ctx = this.drawingContext; + var color = this._pInst.color.apply(this, arguments); + ctx.fillStyle = color.toString(); +}; + +p5.Renderer2D.prototype.stroke = function() { + var ctx = this.drawingContext; + var color = this._pInst.color.apply(this, arguments); + ctx.strokeStyle = color.toString(); +}; + +////////////////////////////////////////////// +// IMAGE | Loading & Displaying +////////////////////////////////////////////// + +p5.Renderer2D.prototype.image = + function (img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { + var cnv; + try { + if (this._tint) { + if (p5.MediaElement && img instanceof p5.MediaElement) { + img.loadPixels(); + } + if (img.canvas) { + cnv = this._getTintedImageCanvas(img); + } + } + if (!cnv) { + cnv = img.canvas || img.elt; + } + this.drawingContext.drawImage(cnv, sx, sy, sWidth, sHeight, dx, dy, + dWidth, dHeight); + } catch (e) { + if (e.name !== 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + } +}; + +p5.Renderer2D.prototype._getTintedImageCanvas = function (img) { + if (!img.canvas) { + return img; + } + var pixels = filters._toPixels(img.canvas); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = img.canvas.width; + tmpCanvas.height = img.canvas.height; + var tmpCtx = tmpCanvas.getContext('2d'); + var id = tmpCtx.createImageData(img.canvas.width, img.canvas.height); + var newPixels = id.data; + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + var a = pixels[i + 3]; + newPixels[i] = r * this._tint[0] / 255; + newPixels[i + 1] = g * this._tint[1] / 255; + newPixels[i + 2] = b * this._tint[2] / 255; + newPixels[i + 3] = a * this._tint[3] / 255; + } + tmpCtx.putImageData(id, 0, 0); + return tmpCanvas; +}; + + +////////////////////////////////////////////// +// IMAGE | Pixels +////////////////////////////////////////////// + +p5.Renderer2D.prototype.blendMode = function(mode) { + this.drawingContext.globalCompositeOperation = mode; +}; +p5.Renderer2D.prototype.blend = function() { + var currBlend = this.drawingContext.globalCompositeOperation; + var blendMode = arguments[arguments.length - 1]; + + var copyArgs = Array.prototype.slice.call( + arguments, + 0, + arguments.length - 1 + ); + + this.drawingContext.globalCompositeOperation = blendMode; + this._pInst.copy.apply(this._pInst, copyArgs); + this.drawingContext.globalCompositeOperation = currBlend; +}; + +p5.Renderer2D.prototype.copy = function () { + var srcImage, sx, sy, sw, sh, dx, dy, dw, dh; + if (arguments.length === 9) { + srcImage = arguments[0]; + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else if (arguments.length === 8) { + srcImage = this._pInst; + sx = arguments[0]; + sy = arguments[1]; + sw = arguments[2]; + sh = arguments[3]; + dx = arguments[4]; + dy = arguments[5]; + dw = arguments[6]; + dh = arguments[7]; + } else { + throw new Error('Signature not supported'); + } + p5.Renderer2D._copyHelper(srcImage, sx, sy, sw, sh, dx, dy, dw, dh); +}; + +p5.Renderer2D._copyHelper = +function (srcImage, sx, sy, sw, sh, dx, dy, dw, dh) { + var s = srcImage.canvas.width / srcImage.width; + this.drawingContext.drawImage(srcImage.canvas, + s * sx, s * sy, s * sw, s * sh, dx, dy, dw, dh); +}; + +p5.Renderer2D.prototype.get = function(x, y, w, h) { + if (x === undefined && y === undefined && + w === undefined && h === undefined){ + x = 0; + y = 0; + w = this.width; + h = this.height; + } else if (w === undefined && h === undefined) { + w = 1; + h = 1; + } + + // if the section does not overlap the canvas + if(x + w < 0 || y + h < 0 || x > this.width || y > this.height){ + return [0, 0, 0, 255]; + } + + var ctx = this._pInst || this; + + var pd = ctx._pixelDensity; + + this.loadPixels.call(ctx); + + // round down to get integer numbers + x = Math.floor(x); + y = Math.floor(y); + + if (w === 1 && h === 1){ + + return [ + ctx.pixels[pd*4*(y*this.width+x)], + ctx.pixels[pd*(4*(y*this.width+x)+1)], + ctx.pixels[pd*(4*(y*this.width+x)+2)], + ctx.pixels[pd*(4*(y*this.width+x)+3)] + ]; + } else { + var sx = x * pd; + var sy = y * pd; + //auto constrain the width and height to + //dimensions of the source image + var dw = Math.min(w, ctx.width); + var dh = Math.min(h, ctx.height); + var sw = dw * pd; + var sh = dh * pd; + + var region = new p5.Image(dw, dh); + region.canvas.getContext('2d').drawImage(this.canvas, sx, sy, sw, sh, + 0, 0, dw, dh); + + return region; + } +}; + +p5.Renderer2D.prototype.loadPixels = function () { + var pd = this._pixelDensity || this._pInst._pixelDensity; + var w = this.width * pd; + var h = this.height * pd; + var imageData = this.drawingContext.getImageData(0, 0, w, h); + // @todo this should actually set pixels per object, so diff buffers can + // have diff pixel arrays. + if (this._pInst) { + this._pInst._setProperty('imageData', imageData); + this._pInst._setProperty('pixels', imageData.data); + } else { // if called by p5.Image + this._setProperty('imageData', imageData); + this._setProperty('pixels', imageData.data); + } +}; + +p5.Renderer2D.prototype.set = function (x, y, imgOrCol) { + // round down to get integer numbers + x = Math.floor(x); + y = Math.floor(y); + if (imgOrCol instanceof p5.Image) { + this.drawingContext.save(); + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + this.drawingContext.drawImage(imgOrCol.canvas, x, y); + this.loadPixels.call(this._pInst); + this.drawingContext.restore(); + } else { + var ctx = this._pInst || this; + var r = 0, g = 0, b = 0, a = 0; + var idx = 4*((y * ctx._pixelDensity) * + (this.width * ctx._pixelDensity) + (x * ctx._pixelDensity)); + if (!ctx.imageData) { + ctx.loadPixels.call(ctx); + } + if (typeof imgOrCol === 'number') { + if (idx < ctx.pixels.length) { + r = imgOrCol; + g = imgOrCol; + b = imgOrCol; + a = 255; + //this.updatePixels.call(this); + } + } + else if (imgOrCol instanceof Array) { + if (imgOrCol.length < 4) { + throw new Error('pixel array must be of the form [R, G, B, A]'); + } + if (idx < ctx.pixels.length) { + r = imgOrCol[0]; + g = imgOrCol[1]; + b = imgOrCol[2]; + a = imgOrCol[3]; + //this.updatePixels.call(this); + } + } else if (imgOrCol instanceof p5.Color) { + if (idx < ctx.pixels.length) { + r = imgOrCol.levels[0]; + g = imgOrCol.levels[1]; + b = imgOrCol.levels[2]; + a = imgOrCol.levels[3]; + //this.updatePixels.call(this); + } + } + // loop over pixelDensity * pixelDensity + for (var i = 0; i < ctx._pixelDensity; i++) { + for (var j = 0; j < ctx._pixelDensity; j++) { + // loop over + idx = 4*((y * ctx._pixelDensity + j) * this.width * + ctx._pixelDensity + (x * ctx._pixelDensity + i)); + ctx.pixels[idx] = r; + ctx.pixels[idx+1] = g; + ctx.pixels[idx+2] = b; + ctx.pixels[idx+3] = a; + } + } + } +}; + +p5.Renderer2D.prototype.updatePixels = function (x, y, w, h) { + var pd = this._pixelDensity || this._pInst._pixelDensity; + if (x === undefined && + y === undefined && + w === undefined && + h === undefined) { + x = 0; + y = 0; + w = this.width; + h = this.height; + } + w *= pd; + h *= pd; + + if (this._pInst) { + this.drawingContext.putImageData(this._pInst.imageData, x, y, 0, 0, w, h); + } else { + this.drawingContext.putImageData(this.imageData, x, y, 0, 0, w, h); + } +}; + +////////////////////////////////////////////// +// SHAPE | 2D Primitives +////////////////////////////////////////////// + +/** + * Generate a cubic Bezier representing an arc on the unit circle of total + * angle `size` radians, beginning `start` radians above the x-axis. Up to + * four of these curves are combined to make a full arc. + * + * See www.joecridge.me/bezier.pdf for an explanation of the method. + */ +p5.Renderer2D.prototype._acuteArcToBezier = + function _acuteArcToBezier(start, size) { + // Evauate constants. + var alpha = size / 2.0, + cos_alpha = Math.cos(alpha), + sin_alpha = Math.sin(alpha), + cot_alpha = 1.0 / Math.tan(alpha), + phi = start + alpha, // This is how far the arc needs to be rotated. + cos_phi = Math.cos(phi), + sin_phi = Math.sin(phi), + lambda = (4.0 - cos_alpha) / 3.0, + mu = sin_alpha + (cos_alpha - lambda) * cot_alpha; + + // Return rotated waypoints. + return { + ax: Math.cos(start), + ay: Math.sin(start), + bx: lambda * cos_phi + mu * sin_phi, + by: lambda * sin_phi - mu * cos_phi, + cx: lambda * cos_phi - mu * sin_phi, + cy: lambda * sin_phi + mu * cos_phi, + dx: Math.cos(start + size), + dy: Math.sin(start + size) + }; +}; + +p5.Renderer2D.prototype.arc = + function(x, y, w, h, start, stop, mode) { + var ctx = this.drawingContext; + var vals = canvas.arcModeAdjust(x, y, w, h, this._ellipseMode); + var rx = vals.w / 2.0; + var ry = vals.h / 2.0; + var epsilon = 0.00001; // Smallest visible angle on displays up to 4K. + var arcToDraw = 0; + var curves = []; + + // Create curves + while(stop - start > epsilon) { + arcToDraw = Math.min(stop - start, constants.HALF_PI); + curves.push(this._acuteArcToBezier(start, arcToDraw)); + start += arcToDraw; + } + + // Fill curves + if (this._doFill) { + ctx.beginPath(); + curves.forEach(function (curve, index) { + if (index === 0) { + ctx.moveTo(vals.x + curve.ax * rx, vals.y + curve.ay * ry); + } + ctx.bezierCurveTo(vals.x + curve.bx * rx, vals.y + curve.by * ry, + vals.x + curve.cx * rx, vals.y + curve.cy * ry, + vals.x + curve.dx * rx, vals.y + curve.dy * ry); + }); + if (mode === constants.PIE || mode == null) { + ctx.lineTo(vals.x, vals.y); + } + ctx.closePath(); + ctx.fill(); + } + + // Stroke curves + if (this._doStroke) { + ctx.beginPath(); + curves.forEach(function (curve, index) { + if (index === 0) { + ctx.moveTo(vals.x + curve.ax * rx, vals.y + curve.ay * ry); + } + ctx.bezierCurveTo(vals.x + curve.bx * rx, vals.y + curve.by * ry, + vals.x + curve.cx * rx, vals.y + curve.cy * ry, + vals.x + curve.dx * rx, vals.y + curve.dy * ry); + }); + if (mode === constants.PIE) { + ctx.lineTo(vals.x, vals.y); + ctx.closePath(); + } else if (mode === constants.CHORD) { + ctx.closePath(); + } + ctx.stroke(); + } + return this; +}; + +p5.Renderer2D.prototype.ellipse = function(x, y, w, h) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + var vals = canvas.modeAdjust(x, y, w, h, this._ellipseMode); + var kappa = 0.5522847498, + ox = (vals.w / 2) * kappa, // control point offset horizontal + oy = (vals.h / 2) * kappa, // control point offset vertical + xe = vals.x + vals.w, // x-end + ye = vals.y + vals.h, // y-end + xm = vals.x + vals.w / 2, // x-middle + ym = vals.y + vals.h / 2; // y-middle + ctx.beginPath(); + ctx.moveTo(vals.x, ym); + ctx.bezierCurveTo(vals.x, ym - oy, xm - ox, vals.y, xm, vals.y); + ctx.bezierCurveTo(xm + ox, vals.y, xe, ym - oy, xe, ym); + ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + ctx.bezierCurveTo(xm - ox, ye, vals.x, ym + oy, vals.x, ym); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } +}; + +p5.Renderer2D.prototype.line = function(x1, y1, x2, y2) { + var ctx = this.drawingContext; + if (!this._doStroke) { + return this; + } else if(ctx.strokeStyle === styleEmpty){ + return this; + } + // Translate the line by (0.5, 0.5) to draw it crisp + if (ctx.lineWidth % 2 === 1) { + ctx.translate(0.5, 0.5); + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + if (ctx.lineWidth % 2 === 1) { + ctx.translate(-0.5, -0.5); + } + return this; +}; + +p5.Renderer2D.prototype.point = function(x, y) { + var ctx = this.drawingContext; + var s = ctx.strokeStyle; + var f = ctx.fillStyle; + if (!this._doStroke) { + return this; + } else if(ctx.strokeStyle === styleEmpty){ + return this; + } + x = Math.round(x); + y = Math.round(y); + ctx.fillStyle = s; + if (ctx.lineWidth > 1) { + ctx.beginPath(); + ctx.arc( + x, + y, + ctx.lineWidth / 2, + 0, + constants.TWO_PI, + false + ); + ctx.fill(); + } else { + ctx.fillRect(x, y, 1, 1); + } + ctx.fillStyle = f; +}; + +p5.Renderer2D.prototype.quad = + function(x1, y1, x2, y2, x3, y3, x4, y4) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.lineTo(x4, y4); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } + return this; +}; + +p5.Renderer2D.prototype.rect = function(x, y, w, h, tl, tr, br, bl) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + var vals = canvas.modeAdjust(x, y, w, h, this._rectMode); + // Translate the line by (0.5, 0.5) to draw a crisp rectangle border + if (this._doStroke && ctx.lineWidth % 2 === 1) { + ctx.translate(0.5, 0.5); + } + ctx.beginPath(); + + if (typeof tl === 'undefined') { + // No rounded corners + ctx.rect(vals.x, vals.y, vals.w, vals.h); + } else { + // At least one rounded corner + // Set defaults when not specified + if (typeof tr === 'undefined') { tr = tl; } + if (typeof br === 'undefined') { br = tr; } + if (typeof bl === 'undefined') { bl = br; } + + // Cache and compute several values + var _x = vals.x; + var _y = vals.y; + var _w = vals.w; + var _h = vals.h; + var hw = _w / 2; + var hh = _h / 2; + + // Clip radii + if (_w < 2 * tl) { tl = hw; } + if (_h < 2 * tl) { tl = hh; } + if (_w < 2 * tr) { tr = hw; } + if (_h < 2 * tr) { tr = hh; } + if (_w < 2 * br) { br = hw; } + if (_h < 2 * br) { br = hh; } + if (_w < 2 * bl) { bl = hw; } + if (_h < 2 * bl) { bl = hh; } + + // Draw shape + ctx.beginPath(); + ctx.moveTo(_x + tl, _y); + ctx.arcTo(_x + _w, _y, _x + _w, _y + _h, tr); + ctx.arcTo(_x + _w, _y + _h, _x, _y + _h, br); + ctx.arcTo(_x, _y + _h, _x, _y, bl); + ctx.arcTo(_x, _y, _x + _w, _y, tl); + ctx.closePath(); + } + if (this._doFill) { + ctx.fill(); + } + if (this._doStroke) { + ctx.stroke(); + } + if (this._doStroke && ctx.lineWidth % 2 === 1) { + ctx.translate(-0.5, -0.5); + } + return this; +}; + +p5.Renderer2D.prototype.triangle = function(x1, y1, x2, y2, x3, y3) { + var ctx = this.drawingContext; + var doFill = this._doFill, doStroke = this._doStroke; + if (doFill && !doStroke) { + if(ctx.fillStyle === styleEmpty) { + return this; + } + } else if (!doFill && doStroke) { + if(ctx.strokeStyle === styleEmpty) { + return this; + } + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.closePath(); + if (doFill) { + ctx.fill(); + } + if (doStroke) { + ctx.stroke(); + } +}; + +p5.Renderer2D.prototype.endShape = +function (mode, vertices, isCurve, isBezier, + isQuadratic, isContour, shapeKind) { + if (vertices.length === 0) { + return this; + } + if (!this._doStroke && !this._doFill) { + return this; + } + var closeShape = mode === constants.CLOSE; + var v; + if (closeShape && !isContour) { + vertices.push(vertices[0]); + } + var i, j; + var numVerts = vertices.length; + if (isCurve && (shapeKind === constants.POLYGON || shapeKind === null)) { + if (numVerts > 3) { + var b = [], s = 1 - this._curveTightness; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[1][0], vertices[1][1]); + for (i = 1; i + 2 < numVerts; i++) { + v = vertices[i]; + b[0] = [ + v[0], + v[1] + ]; + b[1] = [ + v[0] + (s * vertices[i + 1][0] - s * vertices[i - 1][0]) / 6, + v[1] + (s * vertices[i + 1][1] - s * vertices[i - 1][1]) / 6 + ]; + b[2] = [ + vertices[i + 1][0] + + (s * vertices[i][0]-s * vertices[i + 2][0]) / 6, + vertices[i + 1][1]+(s * vertices[i][1] - s*vertices[i + 2][1]) / 6 + ]; + b[3] = [ + vertices[i + 1][0], + vertices[i + 1][1] + ]; + this.drawingContext.bezierCurveTo(b[1][0],b[1][1], + b[2][0],b[2][1],b[3][0],b[3][1]); + } + if (closeShape) { + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); + } + this._doFillStrokeClose(); + } + } else if (isBezier&&(shapeKind===constants.POLYGON ||shapeKind === null)) { + this.drawingContext.beginPath(); + for (i = 0; i < numVerts; i++) { + if (vertices[i].isVert) { + if (vertices[i].moveTo) { + this.drawingContext.moveTo(vertices[i][0], vertices[i][1]); + } else { + this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); + } + } else { + this.drawingContext.bezierCurveTo(vertices[i][0], vertices[i][1], + vertices[i][2], vertices[i][3], vertices[i][4], vertices[i][5]); + } + } + this._doFillStrokeClose(); + } else if (isQuadratic && + (shapeKind === constants.POLYGON || shapeKind === null)) { + this.drawingContext.beginPath(); + for (i = 0; i < numVerts; i++) { + if (vertices[i].isVert) { + if (vertices[i].moveTo) { + this.drawingContext.moveTo([0], vertices[i][1]); + } else { + this.drawingContext.lineTo(vertices[i][0], vertices[i][1]); + } + } else { + this.drawingContext.quadraticCurveTo(vertices[i][0], vertices[i][1], + vertices[i][2], vertices[i][3]); + } + } + this._doFillStrokeClose(); + } else { + if (shapeKind === constants.POINTS) { + for (i = 0; i < numVerts; i++) { + v = vertices[i]; + if (this._doStroke) { + this._pInst.stroke(v[6]); + } + this._pInst.point(v[0], v[1]); + } + } else if (shapeKind === constants.LINES) { + for (i = 0; i + 1 < numVerts; i += 2) { + v = vertices[i]; + if (this._doStroke) { + this._pInst.stroke(vertices[i + 1][6]); + } + this._pInst.line(v[0], v[1], vertices[i + 1][0], vertices[i + 1][1]); + } + } else if (shapeKind === constants.TRIANGLES) { + for (i = 0; i + 2 < numVerts; i += 3) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i + 1][1]); + this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 2][5]); + this.drawingContext.fill(); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 2][6]); + this.drawingContext.stroke(); + } + this.drawingContext.closePath(); + } + } else if (shapeKind === constants.TRIANGLE_STRIP) { + for (i = 0; i + 1 < numVerts; i++) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[i + 1][0], vertices[i + 1][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doStroke) { + this._pInst.stroke(vertices[i + 1][6]); + } + if (this._doFill) { + this._pInst.fill(vertices[i + 1][5]); + } + if (i + 2 < numVerts) { + this.drawingContext.lineTo(vertices[i + 2][0], vertices[i + 2][1]); + if (this._doStroke) { + this._pInst.stroke(vertices[i + 2][6]); + } + if (this._doFill) { + this._pInst.fill(vertices[i + 2][5]); + } + } + this._doFillStrokeClose(); + } + } else if (shapeKind === constants.TRIANGLE_FAN) { + if (numVerts > 2) { + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + this.drawingContext.lineTo(vertices[1][0], vertices[1][1]); + this.drawingContext.lineTo(vertices[2][0], vertices[2][1]); + if (this._doFill) { + this._pInst.fill(vertices[2][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[2][6]); + } + this._doFillStrokeClose(); + for (i = 3; i < numVerts; i++) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + this.drawingContext.lineTo(vertices[i - 1][0], vertices[i - 1][1]); + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(v[5]); + } + if (this._doStroke) { + this._pInst.stroke(v[6]); + } + this._doFillStrokeClose(); + } + } + } else if (shapeKind === constants.QUADS) { + for (i = 0; i + 3 < numVerts; i += 4) { + v = vertices[i]; + this.drawingContext.beginPath(); + this.drawingContext.moveTo(v[0], v[1]); + for (j = 1; j < 4; j++) { + this.drawingContext.lineTo(vertices[i + j][0], vertices[i + j][1]); + } + this.drawingContext.lineTo(v[0], v[1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 3][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 3][6]); + } + this._doFillStrokeClose(); + } + } else if (shapeKind === constants.QUAD_STRIP) { + if (numVerts > 3) { + for (i = 0; i + 1 < numVerts; i += 2) { + v = vertices[i]; + this.drawingContext.beginPath(); + if (i + 3 < numVerts) { + this.drawingContext.moveTo(vertices[i + 2][0], vertices[i+2][1]); + this.drawingContext.lineTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i+1][1]); + this.drawingContext.lineTo(vertices[i + 3][0], vertices[i+3][1]); + if (this._doFill) { + this._pInst.fill(vertices[i + 3][5]); + } + if (this._doStroke) { + this._pInst.stroke(vertices[i + 3][6]); + } + } else { + this.drawingContext.moveTo(v[0], v[1]); + this.drawingContext.lineTo(vertices[i + 1][0], vertices[i+1][1]); + } + this._doFillStrokeClose(); + } + } + } else { + this.drawingContext.beginPath(); + this.drawingContext.moveTo(vertices[0][0], vertices[0][1]); + for (i = 1; i < numVerts; i++) { + v = vertices[i]; + if (v.isVert) { + if (v.moveTo) { + this.drawingContext.moveTo(v[0], v[1]); + } else { + this.drawingContext.lineTo(v[0], v[1]); + } + } + } + this._doFillStrokeClose(); + } + } + isCurve = false; + isBezier = false; + isQuadratic = false; + isContour = false; + if (closeShape) { + vertices.pop(); + } + return this; +}; +////////////////////////////////////////////// +// SHAPE | Attributes +////////////////////////////////////////////// + +p5.Renderer2D.prototype.noSmooth = function() { + if ('imageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.imageSmoothingEnabled = false; + } + else if ('mozImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.mozImageSmoothingEnabled = false; + } + else if ('webkitImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.webkitImageSmoothingEnabled = false; + } + else if ('msImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.msImageSmoothingEnabled = false; + } + return this; +}; + +p5.Renderer2D.prototype.smooth = function() { + if ('imageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.imageSmoothingEnabled = true; + } + else if ('mozImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.mozImageSmoothingEnabled = true; + } + else if ('webkitImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.webkitImageSmoothingEnabled = true; + } + else if ('msImageSmoothingEnabled' in this.drawingContext) { + this.drawingContext.msImageSmoothingEnabled = true; + } + return this; +}; + +p5.Renderer2D.prototype.strokeCap = function(cap) { + if (cap === constants.ROUND || + cap === constants.SQUARE || + cap === constants.PROJECT) { + this.drawingContext.lineCap = cap; + } + return this; +}; + +p5.Renderer2D.prototype.strokeJoin = function(join) { + if (join === constants.ROUND || + join === constants.BEVEL || + join === constants.MITER) { + this.drawingContext.lineJoin = join; + } + return this; +}; + +p5.Renderer2D.prototype.strokeWeight = function(w) { + if (typeof w === 'undefined' || w === 0) { + // hack because lineWidth 0 doesn't work + this.drawingContext.lineWidth = 0.0001; + } else { + this.drawingContext.lineWidth = w; + } + return this; +}; + +p5.Renderer2D.prototype._getFill = function(){ + return this.drawingContext.fillStyle; +}; + +p5.Renderer2D.prototype._getStroke = function(){ + return this.drawingContext.strokeStyle; +}; + +////////////////////////////////////////////// +// SHAPE | Curves +////////////////////////////////////////////// +p5.Renderer2D.prototype.bezier = function (x1, y1, x2, y2, x3, y3, x4, y4) { + this._pInst.beginShape(); + this._pInst.vertex(x1, y1); + this._pInst.bezierVertex(x2, y2, x3, y3, x4, y4); + this._pInst.endShape(); + return this; +}; + +p5.Renderer2D.prototype.curve = function (x1, y1, x2, y2, x3, y3, x4, y4) { + this._pInst.beginShape(); + this._pInst.curveVertex(x1, y1); + this._pInst.curveVertex(x2, y2); + this._pInst.curveVertex(x3, y3); + this._pInst.curveVertex(x4, y4); + this._pInst.endShape(); + return this; +}; + +////////////////////////////////////////////// +// SHAPE | Vertex +////////////////////////////////////////////// + +p5.Renderer2D.prototype._doFillStrokeClose = function () { + if (this._doFill) { + this.drawingContext.fill(); + } + if (this._doStroke) { + this.drawingContext.stroke(); + } + this.drawingContext.closePath(); +}; + +////////////////////////////////////////////// +// TRANSFORM +////////////////////////////////////////////// + +p5.Renderer2D.prototype.applyMatrix = +function(n00, n01, n02, n10, n11, n12) { + this.drawingContext.transform(n00, n01, n02, n10, n11, n12); +}; + +p5.Renderer2D.prototype.resetMatrix = function() { + this.drawingContext.setTransform(1, 0, 0, 1, 0, 0); + this.drawingContext.scale(this._pInst._pixelDensity, + this._pInst._pixelDensity); + return this; +}; + +p5.Renderer2D.prototype.rotate = function(r) { + this.drawingContext.rotate(r); +}; + +p5.Renderer2D.prototype.scale = function(x,y) { + this.drawingContext.scale(x, y); + return this; +}; + +p5.Renderer2D.prototype.shearX = function(angle) { + if (this._pInst._angleMode === constants.DEGREES) { + angle = this._pInst.radians(angle); + } + this.drawingContext.transform(1, 0, this._pInst.tan(angle), 1, 0, 0); + return this; +}; + +p5.Renderer2D.prototype.shearY = function(angle) { + if (this._pInst._angleMode === constants.DEGREES) { + angle = this._pInst.radians(angle); + } + this.drawingContext.transform(1, this._pInst.tan(angle), 0, 1, 0, 0); + return this; +}; + +p5.Renderer2D.prototype.translate = function(x, y) { + this.drawingContext.translate(x, y); + return this; +}; + +////////////////////////////////////////////// +// TYPOGRAPHY +// +////////////////////////////////////////////// + +p5.Renderer2D.prototype.text = function (str, x, y, maxWidth, maxHeight) { + + var p = this._pInst, cars, n, ii, jj, line, testLine, + testWidth, words, totalHeight, baselineHacked, + finalMaxHeight = Number.MAX_VALUE; + + // baselineHacked: (HACK) + // A temporary fix to conform to Processing's implementation + // of BASELINE vertical alignment in a bounding box + + if (!(this._doFill || this._doStroke)) { + return; + } + + if (typeof str !== 'string') { + str = str.toString(); + } + + str = str.replace(/(\t)/g, ' '); + cars = str.split('\n'); + + if (typeof maxWidth !== 'undefined') { + + totalHeight = 0; + for (ii = 0; ii < cars.length; ii++) { + line = ''; + words = cars[ii].split(' '); + for (n = 0; n < words.length; n++) { + testLine = line + words[n] + ' '; + testWidth = this.textWidth(testLine); + if (testWidth > maxWidth) { + line = words[n] + ' '; + totalHeight += p.textLeading(); + } else { + line = testLine; + } + } + } + + if (this._rectMode === constants.CENTER) { + + x -= maxWidth / 2; + y -= maxHeight / 2; + } + + switch (this.drawingContext.textAlign) { + + case constants.CENTER: + x += maxWidth / 2; + break; + case constants.RIGHT: + x += maxWidth; + break; + } + + if (typeof maxHeight !== 'undefined') { + + switch (this.drawingContext.textBaseline) { + case constants.BOTTOM: + y += (maxHeight - totalHeight); + break; + case constants._CTX_MIDDLE: + y += (maxHeight - totalHeight) / 2; + break; + case constants.BASELINE: + baselineHacked = true; + this.drawingContext.textBaseline = constants.TOP; + break; + } + + // remember the max-allowed y-position for any line (fix to #928) + finalMaxHeight = (y + maxHeight) - p.textAscent(); + } + + for (ii = 0; ii < cars.length; ii++) { + + line = ''; + words = cars[ii].split(' '); + for (n = 0; n < words.length; n++) { + testLine = line + words[n] + ' '; + testWidth = this.textWidth(testLine); + if (testWidth > maxWidth && line.length > 0) { + this._renderText(p, line, x, y, finalMaxHeight); + line = words[n] + ' '; + y += p.textLeading(); + } else { + line = testLine; + } + } + + this._renderText(p, line, x, y, finalMaxHeight); + y += p.textLeading(); + } + } + else { + for (jj = 0; jj < cars.length; jj++) { + + this._renderText(p, cars[jj], x, y, finalMaxHeight); + y += p.textLeading(); + } + } + + if (baselineHacked) { + this.drawingContext.textBaseline = constants.BASELINE; + } + + return p; +}; + +p5.Renderer2D.prototype._renderText = function(p, line, x, y, maxY) { + + if (y >= maxY) { + return; // don't render lines beyond our maxY position + } + + p.push(); // fix to #803 + + if (!this._isOpenType()) { // a system/browser font + + // no stroke unless specified by user + if (this._doStroke && this._strokeSet) { + + this.drawingContext.strokeText(line, x, y); + } + + if (this._doFill) { + + // if fill hasn't been set by user, use default text fill + this.drawingContext.fillStyle = this._fillSet ? + this.drawingContext.fillStyle : constants._DEFAULT_TEXT_FILL; + + this.drawingContext.fillText(line, x, y); + } + } + else { // an opentype font, let it handle the rendering + + this._textFont._renderPath(line, x, y, { renderer: this }); + } + + p.pop(); + + return p; +}; + +p5.Renderer2D.prototype.textWidth = function(s) { + + if (this._isOpenType()) { + + return this._textFont._textWidth(s); + } + + return this.drawingContext.measureText(s).width; +}; + +p5.Renderer2D.prototype.textAlign = function(h, v) { + + if (arguments.length) { + + if (h === constants.LEFT || + h === constants.RIGHT || + h === constants.CENTER) { + + this.drawingContext.textAlign = h; + } + + if (v === constants.TOP || + v === constants.BOTTOM || + v === constants.CENTER || + v === constants.BASELINE) { + + if (v === constants.CENTER) { + this.drawingContext.textBaseline = constants._CTX_MIDDLE; + } else { + this.drawingContext.textBaseline = v; + } + } + + return this._pInst; + + } else { + + var valign = this.drawingContext.textBaseline; + + if (valign === constants._CTX_MIDDLE) { + + valign = constants.CENTER; + } + + return { + + horizontal: this.drawingContext.textAlign, + vertical: valign + }; + } +}; + +p5.Renderer2D.prototype._applyTextProperties = function() { + + var font, p = this._pInst; + + this._setProperty('_textAscent', null); + this._setProperty('_textDescent', null); + + font = this._textFont; + + if (this._isOpenType()) { + + font = this._textFont.font.familyName; + this._setProperty('_textStyle', this._textFont.font.styleName); + } + + this.drawingContext.font = this._textStyle + ' ' + + this._textSize + 'px ' + font; + + return p; +}; + + +////////////////////////////////////////////// +// STRUCTURE +////////////////////////////////////////////// + +p5.Renderer2D.prototype.push = function() { + this.drawingContext.save(); +}; + +p5.Renderer2D.prototype.pop = function() { + this.drawingContext.restore(); +}; + +module.exports = p5.Renderer2D; + +},{"../image/filters":65,"./canvas":46,"./constants":47,"./core":48,"./p5.Renderer":54}],56:[function(_dereq_,module,exports){ +/** + * @module Rendering + * @submodule Rendering + * @for p5 + */ + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); +_dereq_('./p5.Graphics'); +_dereq_('./p5.Renderer2D'); +_dereq_('../3d/p5.Renderer3D'); +var defaultId = 'defaultCanvas0'; // this gets set again in createCanvas + +/** + * Creates a canvas element in the document, and sets the dimensions of it + * in pixels. This method should be called only once at the start of setup. + * Calling createCanvas more than once in a sketch will result in very + * unpredicable behavior. If you want more than one drawing canvas + * you could use createGraphics (hidden by default but it can be shown). + * <br><br> + * The system variables width and height are set by the parameters passed + * to this function. If createCanvas() is not used, the window will be + * given a default size of 100x100 pixels. + * + * @method createCanvas + * @param {Number} w width of the canvas + * @param {Number} h height of the canvas + * @param {String} [renderer] 'p2d' | 'webgl' + * @return {Object} canvas generated + * @example + * <div> + * <code> + * function setup() { + * createCanvas(100, 50); + * background(153); + * line(0, 0, width, height); + * } + * </code> + * </div> + */ + +p5.prototype.createCanvas = function(w, h, renderer) { + //optional: renderer, otherwise defaults to p2d + var r = renderer || constants.P2D; + var isDefault, c; + + //4th arg (isDefault) used when called onLoad, + //otherwise hidden to the public api + if(arguments[3]){ + isDefault = + (typeof arguments[3] === 'boolean') ? arguments[3] : false; + } + + if(r === constants.WEBGL){ + c = document.getElementById(defaultId); + if(c){ //if defaultCanvas already exists + c.parentNode.removeChild(c); //replace the existing defaultCanvas + } + c = document.createElement('canvas'); + c.id = defaultId; + } + else { + if (isDefault) { + c = document.createElement('canvas'); + var i = 0; + while (document.getElementById('defaultCanvas'+i)) { + i++; + } + defaultId = 'defaultCanvas'+i; + c.id = defaultId; + } else { // resize the default canvas if new one is created + c = this.canvas; + } + } + + // set to invisible if still in setup (to prevent flashing with manipulate) + if (!this._setupDone) { + c.className += ' p5_hidden'; // tag to show later + c.style.visibility='hidden'; + } + + if (this._userNode) { // user input node case + this._userNode.appendChild(c); + } else { + document.body.appendChild(c); + } + + + + // Init our graphics renderer + //webgl mode + if (r === constants.WEBGL) { + this._setProperty('_renderer', new p5.Renderer3D(c, this, true)); + this._isdefaultGraphics = true; + } + //P2D mode + else { + if (!this._isdefaultGraphics) { + this._setProperty('_renderer', new p5.Renderer2D(c, this, true)); + this._isdefaultGraphics = true; + } + } + this._renderer.resize(w, h); + this._renderer._applyDefaults(); + if (isDefault) { // only push once + this._elements.push(this._renderer); + } + return this._renderer; +}; + +/** + * Resizes the canvas to given width and height. The canvas will be cleared + * and draw will be called immediately, allowing the sketch to re-render itself + * in the resized canvas. + * @method resizeCanvas + * @example + * <div class="norender"><code> + * function setup() { + * createCanvas(windowWidth, windowHeight); + * } + * + * function draw() { + * background(0, 100, 200); + * } + * + * function windowResized() { + * resizeCanvas(windowWidth, windowHeight); + * } + * </code></div> + */ +p5.prototype.resizeCanvas = function (w, h, noRedraw) { + if (this._renderer) { + + // save canvas properties + var props = {}; + for (var key in this.drawingContext) { + var val = this.drawingContext[key]; + if (typeof val !== 'object' && typeof val !== 'function') { + props[key] = val; + } + } + this._renderer.resize(w, h); + // reset canvas properties + for (var savedKey in props) { + this.drawingContext[savedKey] = props[savedKey]; + } + if (!noRedraw) { + this.redraw(); + } + } +}; + + +/** + * Removes the default canvas for a p5 sketch that doesn't + * require a canvas + * @method noCanvas + * @example + * <div> + * <code> + * function setup() { + * noCanvas(); + * } + * </code> + * </div> + */ +p5.prototype.noCanvas = function() { + if (this.canvas) { + this.canvas.parentNode.removeChild(this.canvas); + } +}; + +/** + * Creates and returns a new p5.Renderer object. Use this class if you need + * to draw into an off-screen graphics buffer. The two parameters define the + * width and height in pixels. + * + * @method createGraphics + * @param {Number} w width of the offscreen graphics buffer + * @param {Number} h height of the offscreen graphics buffer + * @param {String} renderer either 'p2d' or 'webgl'. + * undefined defaults to p2d + * @return {Object} offscreen graphics buffer + * @example + * <div> + * <code> + * var pg; + * function setup() { + * createCanvas(100, 100); + * pg = createGraphics(100, 100); + * } + * function draw() { + * background(200); + * pg.background(100); + * pg.noStroke(); + * pg.ellipse(pg.width/2, pg.height/2, 50, 50); + * image(pg, 50, 50); + * image(pg, 0, 0, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.createGraphics = function(w, h, renderer){ + return new p5.Graphics(w, h, renderer, this); +}; + +/** + * Blends the pixels in the display window according to the defined mode. + * There is a choice of the following modes to blend the source pixels (A) + * with the ones of pixels already in the display window (B): + * <ul> + * <li><code>BLEND</code> - linear interpolation of colours: C = + * A*factor + B. This is the default blending mode.</li> + * <li><code>ADD</code> - sum of A and B</li> + * <li><code>DARKEST</code> - only the darkest colour succeeds: C = + * min(A*factor, B).</li> + * <li><code>LIGHTEST</code> - only the lightest colour succeeds: C = + * max(A*factor, B).</li> + * <li><code>DIFFERENCE</code> - subtract colors from underlying image.</li> + * <li><code>EXCLUSION</code> - similar to <code>DIFFERENCE</code>, but less + * extreme.</li> + * <li><code>MULTIPLY</code> - multiply the colors, result will always be + * darker.</li> + * <li><code>SCREEN</code> - opposite multiply, uses inverse values of the + * colors.</li> + * <li><code>REPLACE</code> - the pixels entirely replace the others and + * don't utilize alpha (transparency) values.</li> + * <li><code>OVERLAY</code> - mix of <code>MULTIPLY</code> and <code>SCREEN + * </code>. Multiplies dark values, and screens light values.</li> + * <li><code>HARD_LIGHT</code> - <code>SCREEN</code> when greater than 50% + * gray, <code>MULTIPLY</code> when lower.</li> + * <li><code>SOFT_LIGHT</code> - mix of <code>DARKEST</code> and + * <code>LIGHTEST</code>. Works like <code>OVERLAY</code>, but not as harsh. + * </li> + * <li><code>DODGE</code> - lightens light tones and increases contrast, + * ignores darks.</li> + * <li><code>BURN</code> - darker areas are applied, increasing contrast, + * ignores lights.</li> + * </ul> + * + * @method blendMode + * @param {String/Constant} mode blend mode to set for canvas + * @example + * <div> + * <code> + * blendMode(LIGHTEST); + * strokeWeight(30); + * stroke(80, 150, 255); + * line(25, 25, 75, 75); + * stroke(255, 50, 50); + * line(75, 25, 25, 75); + * </code> + * </div> + * <div> + * <code> + * blendMode(MULTIPLY); + * strokeWeight(30); + * stroke(80, 150, 255); + * line(25, 25, 75, 75); + * stroke(255, 50, 50); + * line(75, 25, 25, 75); + * </code> + * </div> + */ +p5.prototype.blendMode = function(mode) { + if (mode === constants.BLEND || mode === constants.DARKEST || + mode === constants.LIGHTEST || mode === constants.DIFFERENCE || + mode === constants.MULTIPLY || mode === constants.EXCLUSION || + mode === constants.SCREEN || mode === constants.REPLACE || + mode === constants.OVERLAY || mode === constants.HARD_LIGHT || + mode === constants.SOFT_LIGHT || mode === constants.DODGE || + mode === constants.BURN || mode === constants.ADD || + mode === constants.NORMAL) { + this._renderer.blendMode(mode); + } else { + throw new Error('Mode '+mode+' not recognized.'); + } +}; + +module.exports = p5; + +},{"../3d/p5.Renderer3D":36,"./constants":47,"./core":48,"./p5.Graphics":53,"./p5.Renderer2D":55}],57:[function(_dereq_,module,exports){ + +// requestAnim shim layer by Paul Irish +window.requestAnimationFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback, element){ + // should '60' here be framerate? + window.setTimeout(callback, 1000 / 60); + }; +})(); + +// use window.performance() to get max fast and accurate time in milliseconds +window.performance = window.performance || {}; +window.performance.now = (function(){ + var load_date = Date.now(); + return window.performance.now || + window.performance.mozNow || + window.performance.msNow || + window.performance.oNow || + window.performance.webkitNow || + function () { + return Date.now() - load_date; + }; +})(); + +/* +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/ +// requestanimationframe-for-smart-er-animating +// requestAnimationFrame polyfill by Erik Möller +// fixes from Paul Irish and Tino Zijdel +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = + window[vendors[x]+'CancelAnimationFrame'] || + window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() + { callback(currTime + timeToCall); }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + } +}()); +*/ + +/** + * shim for Uint8ClampedArray.slice + * (allows arrayCopy to work with pixels[]) + * with thanks to http://halfpapstudios.com/blog/tag/html5-canvas/ + * Enumerable set to false to protect for...in from + * Uint8ClampedArray.prototype pollution. + */ +(function () { + 'use strict'; + if (typeof Uint8ClampedArray !== 'undefined' && + !Uint8ClampedArray.prototype.slice) { + Object.defineProperty(Uint8ClampedArray.prototype, 'slice', { + value: Array.prototype.slice, + writable: true, configurable: true, enumerable: false + }); + } +}()); + +},{}],58:[function(_dereq_,module,exports){ +/** + * @module Structure + * @submodule Structure + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('./core'); + +p5.prototype.exit = function() { + throw 'exit() not implemented, see remove()'; +}; +/** + * Stops p5.js from continuously executing the code within draw(). + * If loop() is called, the code in draw() begins to run continuously again. + * If using noLoop() in setup(), it should be the last line inside the block. + * <br><br> + * When noLoop() is used, it's not possible to manipulate or access the + * screen inside event handling functions such as mousePressed() or + * keyPressed(). Instead, use those functions to call redraw() or loop(), + * which will run draw(), which can update the screen properly. This means + * that when noLoop() has been called, no drawing can happen, and functions + * like saveFrame() or loadPixels() may not be used. + * <br><br> + * Note that if the sketch is resized, redraw() will be called to update + * the sketch, even after noLoop() has been specified. Otherwise, the sketch + * would enter an odd state until loop() was called. + * + * @method noLoop + * @example + * <div><code> + * function setup() { + * createCanvas(100, 100); + * background(200); + * noLoop(); + * } + + * function draw() { + * line(10, 10, 90, 90); + * } + * </code></div> + * + * <div><code> + * var x = 0; + * function setup() { + * createCanvas(100, 100); + * } + * + * function draw() { + * background(204); + * x = x + 0.1; + * if (x > width) { + * x = 0; + * } + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * noLoop(); + * } + * + * function mouseReleased() { + * loop(); + * } + * </code></div> + */ +p5.prototype.noLoop = function() { + this._loop = false; +}; +/** + * By default, p5.js loops through draw() continuously, executing the code + * within it. However, the draw() loop may be stopped by calling noLoop(). + * In that case, the draw() loop can be resumed with loop(). + * + * @method loop + * @example + * <div><code> + * var x = 0; + * function setup() { + * createCanvas(100, 100); + * noLoop(); + * } + * + * function draw() { + * background(204); + * x = x + 0.1; + * if (x > width) { + * x = 0; + * } + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * loop(); + * } + * + * function mouseReleased() { + * noLoop(); + * } + * </code></div> + */ + +p5.prototype.loop = function() { + this._loop = true; + this._draw(); +}; + +/** + * The push() function saves the current drawing style settings and + * transformations, while pop() restores these settings. Note that these + * functions are always used together. They allow you to change the style + * and transformation settings and later return to what you had. When a new + * state is started with push(), it builds on the current style and transform + * information. The push() and pop() functions can be embedded to provide + * more control. (See the second example for a demonstration.) + * <br><br> + * push() stores information related to the current transformation state + * and style settings controlled by the following functions: fill(), + * stroke(), tint(), strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), colorMode(), textAlign(), + * textFont(), textMode(), textSize(), textLeading(). + * + * @method push + * @example + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * translate(50, 0); + * ellipse(0, 50, 33, 33); // Middle circle + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(33, 50, 33, 33); // Left-middle circle + * + * push(); // Start another new drawing state + * stroke(0, 102, 153); + * ellipse(66, 50, 33, 33); // Right-middle circle + * pop(); // Restore previous state + * + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + */ +p5.prototype.push = function () { + this._renderer.push(); + this._styles.push({ + _doStroke: this._renderer._doStroke, + _doFill: this._renderer._doFill, + _tint: this._renderer._tint, + _imageMode: this._renderer._imageMode, + _rectMode: this._renderer._rectMode, + _ellipseMode: this._renderer._ellipseMode, + _colorMode: this._renderer._colorMode, + _textFont: this._renderer._textFont, + _textLeading: this._renderer._textLeading, + _textSize: this._renderer._textSize, + _textStyle: this._renderer._textStyle + }); +}; + +/** + * The push() function saves the current drawing style settings and + * transformations, while pop() restores these settings. Note that these + * functions are always used together. They allow you to change the style + * and transformation settings and later return to what you had. When a new + * state is started with push(), it builds on the current style and transform + * information. The push() and pop() functions can be embedded to provide + * more control. (See the second example for a demonstration.) + * <br><br> + * push() stores information related to the current transformation state + * and style settings controlled by the following functions: fill(), + * stroke(), tint(), strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), colorMode(), textAlign(), + * textFont(), textMode(), textSize(), textLeading(). + * + * @method pop + * @example + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * translate(50, 0); + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(0, 50, 33, 33); // Middle circle + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + * <div> + * <code> + * ellipse(0, 50, 33, 33); // Left circle + * + * push(); // Start a new drawing state + * strokeWeight(10); + * fill(204, 153, 0); + * ellipse(33, 50, 33, 33); // Left-middle circle + * + * push(); // Start another new drawing state + * stroke(0, 102, 153); + * ellipse(66, 50, 33, 33); // Right-middle circle + * pop(); // Restore previous state + * + * pop(); // Restore original state + * + * ellipse(100, 50, 33, 33); // Right circle + * </code> + * </div> + */ +p5.prototype.pop = function () { + this._renderer.pop(); + var lastS = this._styles.pop(); + for(var prop in lastS){ + this._renderer[prop] = lastS[prop]; + } +}; + +p5.prototype.pushStyle = function() { + throw new Error('pushStyle() not used, see push()'); +}; + +p5.prototype.popStyle = function() { + throw new Error('popStyle() not used, see pop()'); +}; + +/** + * + * Executes the code within draw() one time. This functions allows the + * program to update the display window only when necessary, for example + * when an event registered by mousePressed() or keyPressed() occurs. + * <br><br> + * In structuring a program, it only makes sense to call redraw() within + * events such as mousePressed(). This is because redraw() does not run + * draw() immediately (it only sets a flag that indicates an update is + * needed). + * <br><br> + * The redraw() function does not work properly when called inside draw(). + * To enable/disable animations, use loop() and noLoop(). + * + * @method redraw + * @example + * <div><code> + * var x = 0; + * + * function setup() { + * createCanvas(100, 100); + * noLoop(); + * } + * + * function draw() { + * background(204); + * line(x, 0, x, height); + * } + * + * function mousePressed() { + * x += 1; + * redraw(); + * } + * </code></div> + */ +p5.prototype.redraw = function () { + var userSetup = this.setup || window.setup; + var userDraw = this.draw || window.draw; + if (typeof userDraw === 'function') { + this.push(); + if (typeof userSetup === 'undefined') { + this.scale(this._pixelDensity, this._pixelDensity); + } + var self = this; + this._registeredMethods.pre.forEach(function (f) { + f.call(self); + }); + userDraw(); + this._registeredMethods.post.forEach(function (f) { + f.call(self); + }); + this.pop(); + } +}; + +p5.prototype.size = function() { + var s = 'size() is not a valid p5 function, to set the size of the '; + s += 'drawing canvas, please use createCanvas() instead'; + throw s; +}; + + +module.exports = p5; + +},{"./core":48}],59:[function(_dereq_,module,exports){ +/** + * @module Transform + * @submodule Transform + * @for p5 + * @requires core + * @requires constants + */ + + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); + +/** + * Multiplies the current matrix by the one specified through the parameters. + * This is very slow because it will try to calculate the inverse of the + * transform, so avoid it whenever possible. + * + * @method applyMatrix + * @param {Number} n00 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n01 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n02 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n10 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n11 numbers which define the 3x2 matrix to be multiplied + * @param {Number} n12 numbers which define the 3x2 matrix to be multiplied + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // Example in the works. + * </code> + * </div> + */ +p5.prototype.applyMatrix = function(n00, n01, n02, n10, n11, n12) { + this._renderer.applyMatrix(n00, n01, n02, n10, n11, n12); + return this; +}; + +p5.prototype.popMatrix = function() { + throw new Error('popMatrix() not used, see pop()'); +}; + +p5.prototype.printMatrix = function() { + throw new Error('printMatrix() not implemented'); +}; + +p5.prototype.pushMatrix = function() { + throw new Error('pushMatrix() not used, see push()'); +}; + +/** + * Replaces the current matrix with the identity matrix. + * + * @method resetMatrix + * @return {p5} the p5 object + * @example + * <div> + * <code> + * // Example in the works. + * </code> + * </div> + */ +p5.prototype.resetMatrix = function() { + this._renderer.resetMatrix(); + return this; +}; + +/** + * Rotates a shape the amount specified by the angle parameter. This + * function accounts for angleMode, so angles can be entered in either + * RADIANS or DEGREES. + * <br><br> + * Objects are always rotated around their relative position to the + * origin and positive numbers rotate objects in a clockwise direction. + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * rotate(HALF_PI) and then rotate(HALF_PI) is the same as rotate(PI). + * All tranformations are reset when draw() begins again. + * <br><br> + * Technically, rotate() multiplies the current transformation matrix + * by a rotation matrix. This function can be further controlled by + * the push() and pop(). + * + * @method rotate + * @param {Number} angle the angle of rotation, specified in radians + * or degrees, depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/2, height/2); + * rotate(PI/3.0); + * rect(-26, -26, 52, 52); + * </code> + * </div> + */ +p5.prototype.rotate = function() { + var r = arguments[0]; + if (this._angleMode === constants.DEGREES) { + r = this.radians(r); + } + //in webgl mode + if(arguments.length > 1){ + this._renderer.rotate(r, arguments[1]); + } + else { + this._renderer.rotate(r); + } + return this; +}; + +/** + * [rotateX description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateX = function(rad) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if (this._renderer.isP3D) { + this._validateParameters( + 'rotateX', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateX(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * [rotateY description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateY = function(rad) { + if (this._renderer.isP3D) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rotateY', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateY(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * [rotateZ description] + * @param {[type]} rad [description] + * @return {[type]} [description] + */ +p5.prototype.rotateZ = function(rad) { + if (this._renderer.isP3D) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'rotateZ', + args, + [ + ['Number'] + ] + ); + this._renderer.rotateZ(rad); + } else { + throw 'not supported in p2d. Please use webgl mode'; + } + return this; +}; + +/** + * Increases or decreases the size of a shape by expanding and contracting + * vertices. Objects always scale from their relative origin to the + * coordinate system. Scale values are specified as decimal percentages. + * For example, the function call scale(2.0) increases the dimension of a + * shape by 200%. + * <br><br> + * Transformations apply to everything that happens after and subsequent + * calls to the function multiply the effect. For example, calling scale(2.0) + * and then scale(1.5) is the same as scale(3.0). If scale() is called + * within draw(), the transformation is reset when the loop begins again. + * <br><br> + * Using this fuction with the z parameter requires using P3D as a + * parameter for size(), as shown in the third example above. This function + * can be further controlled with push() and pop(). + * + * @method scale + * @param {Number | p5.Vector | Array} s + * percent to scale the object, or percentage to + * scale the object in the x-axis if multiple arguments + * are given + * @param {Number} [y] percent to scale the object in the y-axis + * @param {Number} [z] percent to scale the object in the z-axis (webgl only) + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/2, height/2); + * rotate(PI/3.0); + * rect(-26, -26, 52, 52); + * </code> + * </div> + * + * <div> + * <code> + * rect(30, 20, 50, 50); + * scale(0.5, 1.3); + * rect(30, 20, 50, 50); + * </code> + * </div> + */ +p5.prototype.scale = function() { + var x,y,z; + var args = new Array(arguments.length); + for(var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + if(args[0] instanceof p5.Vector){ + x = args[0].x; + y = args[0].y; + z = args[0].z; + } + else if(args[0] instanceof Array){ + x = args[0][0]; + y = args[0][1]; + z = args[0][2] || 1; + } + else { + if(args.length === 1){ + x = y = z = args[0]; + } + else { + x = args[0]; + y = args[1]; + z = args[2] || 1; + } + } + + if(this._renderer.isP3D){ + this._renderer.scale.call(this._renderer, x,y,z); + } + else { + this._renderer.scale.call(this._renderer, x,y); + } + return this; +}; + +/** + * Shears a shape around the x-axis the amount specified by the angle + * parameter. Angles should be specified in the current angleMode. + * Objects are always sheared around their relative position to the origin + * and positive numbers shear objects in a clockwise direction. + * <br><br> + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearX(PI/2) and then shearX(PI/2) is the same as shearX(PI). + * If shearX() is called within the draw(), the transformation is reset when + * the loop begins again. + * <br><br> + * Technically, shearX() multiplies the current transformation matrix by a + * rotation matrix. This function can be further controlled by the + * push() and pop() functions. + * + * @method shearX + * @param {Number} angle angle of shear specified in radians or degrees, + * depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/4, height/4); + * shearX(PI/4.0); + * rect(0, 0, 30, 30); + * </code> + * </div> + */ +p5.prototype.shearX = function(angle) { + if (this._angleMode === constants.DEGREES) { + angle = this.radians(angle); + } + this._renderer.shearX(angle); + return this; +}; + +/** + * Shears a shape around the y-axis the amount specified by the angle + * parameter. Angles should be specified in the current angleMode. Objects + * are always sheared around their relative position to the origin and + * positive numbers shear objects in a clockwise direction. + * <br><br> + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearY(PI/2) and then shearY(PI/2) is the same as shearY(PI). If + * shearY() is called within the draw(), the transformation is reset when + * the loop begins again. + * <br><br> + * Technically, shearY() multiplies the current transformation matrix by a + * rotation matrix. This function can be further controlled by the + * push() and pop() functions. + * + * @method shearY + * @param {Number} angle angle of shear specified in radians or degrees, + * depending on current angleMode + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(width/4, height/4); + * shearY(PI/4.0); + * rect(0, 0, 30, 30); + * </code> + * </div> + */ +p5.prototype.shearY = function(angle) { + if (this._angleMode === constants.DEGREES) { + angle = this.radians(angle); + } + this._renderer.shearY(angle); + return this; +}; + +/** + * Specifies an amount to displace objects within the display window. + * The x parameter specifies left/right translation, the y parameter + * specifies up/down translation. + * <br><br> + * Transformations are cumulative and apply to everything that happens after + * and subsequent calls to the function accumulates the effect. For example, + * calling translate(50, 0) and then translate(20, 0) is the same as + * translate(70, 0). If translate() is called within draw(), the + * transformation is reset when the loop begins again. This function can be + * further controlled by using push() and pop(). + * + * @method translate + * @param {Number} x left/right translation + * @param {Number} y up/down translation + * @return {p5} the p5 object + * @example + * <div> + * <code> + * translate(30, 20); + * rect(0, 0, 55, 55); + * </code> + * </div> + * + * <div> + * <code> + * rect(0, 0, 55, 55); // Draw rect at original 0,0 + * translate(30, 20); + * rect(0, 0, 55, 55); // Draw rect at new 0,0 + * translate(14, 14); + * rect(0, 0, 55, 55); // Draw rect at new 0,0 + * </code> + * </div> + */ +p5.prototype.translate = function(x, y, z) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + + if (this._renderer.isP3D) { + this._validateParameters( + 'translate', + args, + [ + //p3d + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.translate(x, y, z); + } else { + this._validateParameters( + 'translate', + args, + [ + //p2d + ['Number', 'Number'] + ] + ); + this._renderer.translate(x, y); + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],60:[function(_dereq_,module,exports){ +/** + * @module Shape + * @submodule Vertex + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('./core'); +var constants = _dereq_('./constants'); +var shapeKind = null; +var vertices = []; +var contourVertices = []; +var isBezier = false; +var isCurve = false; +var isQuadratic = false; +var isContour = false; + +/** + * Use the beginContour() and endContour() functions to create negative + * shapes within shapes such as the center of the letter 'O'. beginContour() + * begins recording vertices for the shape and endContour() stops recording. + * The vertices that define a negative shape must "wind" in the opposite + * direction from the exterior shape. First draw vertices for the exterior + * clockwise order, then for internal shapes, draw vertices + * shape in counter-clockwise. + * <br><br> + * These functions can only be used within a beginShape()/endShape() pair and + * transformations such as translate(), rotate(), and scale() do not work + * within a beginContour()/endContour() pair. It is also not possible to use + * other shapes, such as ellipse() or rect() within. + * + * @method beginContour + * @return {Object} the p5 object + * @example + * <div> + * <code> + * translate(50, 50); + * stroke(255, 0, 0); + * beginShape(); + * // Exterior part of shape, clockwise winding + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * // Interior part of shape, counter-clockwise winding + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.beginContour = function() { + contourVertices = []; + isContour = true; + return this; +}; + +/** + * Using the beginShape() and endShape() functions allow creating more + * complex forms. beginShape() begins recording vertices for a shape and + * endShape() stops recording. The value of the kind parameter tells it which + * types of shapes to create from the provided vertices. With no mode + * specified, the shape can be any irregular polygon. + * <br><br> + * The parameters available for beginShape() are POINTS, LINES, TRIANGLES, + * TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. After calling the + * beginShape() function, a series of vertex() commands must follow. To stop + * drawing the shape, call endShape(). Each shape will be outlined with the + * current stroke color and filled with the fill color. + * <br><br> + * Transformations such as translate(), rotate(), and scale() do not work + * within beginShape(). It is also not possible to use other shapes, such as + * ellipse() or rect() within beginShape(). + * + * @method beginShape + * @param {Number/Constant} kind either POINTS, LINES, TRIANGLES, + * TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, + * or QUAD_STRIP + * @return {Object} the p5 object + * @example + * <div> + * <code> + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(CLOSE); + * </code> + * </div> + * + * <div> + * <code> + * // currently not working + * beginShape(POINTS); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(LINES); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(CLOSE); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLES); + * vertex(30, 75); + * vertex(40, 20); + * vertex(50, 75); + * vertex(60, 20); + * vertex(70, 75); + * vertex(80, 20); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLE_STRIP); + * vertex(30, 75); + * vertex(40, 20); + * vertex(50, 75); + * vertex(60, 20); + * vertex(70, 75); + * vertex(80, 20); + * vertex(90, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(TRIANGLE_FAN); + * vertex(57.5, 50); + * vertex(57.5, 15); + * vertex(92, 50); + * vertex(57.5, 85); + * vertex(22, 50); + * vertex(57.5, 15); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(QUADS); + * vertex(30, 20); + * vertex(30, 75); + * vertex(50, 75); + * vertex(50, 20); + * vertex(65, 20); + * vertex(65, 75); + * vertex(85, 75); + * vertex(85, 20); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(QUAD_STRIP); + * vertex(30, 20); + * vertex(30, 75); + * vertex(50, 20); + * vertex(50, 75); + * vertex(65, 20); + * vertex(65, 75); + * vertex(85, 20); + * vertex(85, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(); + * vertex(20, 20); + * vertex(40, 20); + * vertex(40, 40); + * vertex(60, 40); + * vertex(60, 60); + * vertex(20, 60); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.beginShape = function(kind) { + if (kind === constants.POINTS || + kind === constants.LINES || + kind === constants.TRIANGLES || + kind === constants.TRIANGLE_FAN || + kind === constants.TRIANGLE_STRIP || + kind === constants.QUADS || + kind === constants.QUAD_STRIP) { + shapeKind = kind; + } else { + shapeKind = null; + } + if(this._renderer.isP3D){ + this._renderer.beginShape(kind); + } else { + vertices = []; + contourVertices = []; + } + return this; +}; + +/** + * Specifies vertex coordinates for Bezier curves. Each call to + * bezierVertex() defines the position of two control points and + * one anchor point of a Bezier curve, adding a new segment to a + * line or shape. + * <br><br> + * The first time bezierVertex() is used within a + * beginShape() call, it must be prefaced with a call to vertex() + * to set the first anchor point. This function must be used between + * beginShape() and endShape() and only when there is no MODE + * parameter specified to beginShape(). + * + * @method bezierVertex + * @param {Number} x2 x-coordinate for the first control point + * @param {Number} y2 y-coordinate for the first control point + * @param {Number} x3 x-coordinate for the second control point + * @param {Number} y3 y-coordinate for the second control point + * @param {Number} x4 x-coordinate for the anchor point + * @param {Number} y4 y-coordinate for the anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * beginShape(); + * vertex(30, 20); + * bezierVertex(80, 0, 80, 75, 30, 75); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * beginShape(); + * vertex(30, 20); + * bezierVertex(80, 0, 80, 75, 30, 75); + * bezierVertex(50, 80, 60, 25, 30, 20); + * endShape(); + * </code> + * </div> + */ +p5.prototype.bezierVertex = function(x2, y2, x3, y3, x4, y4) { + if (vertices.length === 0) { + throw 'vertex() must be used once before calling bezierVertex()'; + } else { + isBezier = true; + var vert = []; + for (var i = 0; i < arguments.length; i++) { + vert[i] = arguments[i]; + } + vert.isVert = false; + if (isContour) { + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } + return this; +}; + +/** + * Specifies vertex coordinates for curves. This function may only + * be used between beginShape() and endShape() and only when there + * is no MODE parameter specified to beginShape(). + * <br><br> + * The first and last points in a series of curveVertex() lines will be used to + * guide the beginning and end of a the curve. A minimum of four + * points is required to draw a tiny curve between the second and + * third points. Adding a fifth point with curveVertex() will draw + * the curve between the second, third, and fourth points. The + * curveVertex() function is an implementation of Catmull-Rom + * splines. + * + * @method curveVertex + * @param {Number} x x-coordinate of the vertex + * @param {Number} y y-coordinate of the vertex + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * beginShape(); + * curveVertex(84, 91); + * curveVertex(84, 91); + * curveVertex(68, 19); + * curveVertex(21, 17); + * curveVertex(32, 100); + * curveVertex(32, 100); + * endShape(); + * </code> + * </div> + */ +p5.prototype.curveVertex = function(x,y) { + isCurve = true; + this.vertex(x, y); + return this; +}; + +/** + * Use the beginContour() and endContour() functions to create negative + * shapes within shapes such as the center of the letter 'O'. beginContour() + * begins recording vertices for the shape and endContour() stops recording. + * The vertices that define a negative shape must "wind" in the opposite + * direction from the exterior shape. First draw vertices for the exterior + * clockwise order, then for internal shapes, draw vertices + * shape in counter-clockwise. + * <br><br> + * These functions can only be used within a beginShape()/endShape() pair and + * transformations such as translate(), rotate(), and scale() do not work + * within a beginContour()/endContour() pair. It is also not possible to use + * other shapes, such as ellipse() or rect() within. + * + * @method endContour + * @return {Object} the p5 object + * @example + * <div> + * <code> + * translate(50, 50); + * stroke(255, 0, 0); + * beginShape(); + * // Exterior part of shape, clockwise winding + * vertex(-40, -40); + * vertex(40, -40); + * vertex(40, 40); + * vertex(-40, 40); + * // Interior part of shape, counter-clockwise winding + * beginContour(); + * vertex(-20, -20); + * vertex(-20, 20); + * vertex(20, 20); + * vertex(20, -20); + * endContour(); + * endShape(CLOSE); + * </code> + * </div> + */ +p5.prototype.endContour = function() { + var vert = contourVertices[0].slice(); // copy all data + vert.isVert = contourVertices[0].isVert; + vert.moveTo = false; + contourVertices.push(vert); + + vertices.push(vertices[0]); + for (var i = 0; i < contourVertices.length; i++) { + vertices.push(contourVertices[i]); + } + return this; +}; + +/** + * The endShape() function is the companion to beginShape() and may only be + * called after beginShape(). When endshape() is called, all of image data + * defined since the previous call to beginShape() is written into the image + * buffer. The constant CLOSE as the value for the MODE parameter to close + * the shape (to connect the beginning and the end). + * + * @method endShape + * @param {Number/Constant} mode use CLOSE to close the shape + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * + * beginShape(); + * vertex(20, 20); + * vertex(45, 20); + * vertex(45, 80); + * endShape(CLOSE); + * + * beginShape(); + * vertex(50, 20); + * vertex(75, 20); + * vertex(75, 80); + * endShape(); + * </code> + * </div> + */ +p5.prototype.endShape = function(mode) { + if(this._renderer.isP3D){ + this._renderer.endShape(); + }else{ + if (vertices.length === 0) { return this; } + if (!this._renderer._doStroke && !this._renderer._doFill) { return this; } + + var closeShape = mode === constants.CLOSE; + + // if the shape is closed, the first element is also the last element + if (closeShape && !isContour) { + vertices.push(vertices[0]); + } + + this._renderer.endShape(mode, vertices, isCurve, isBezier, + isQuadratic, isContour, shapeKind); + + // Reset some settings + isCurve = false; + isBezier = false; + isQuadratic = false; + isContour = false; + + // If the shape is closed, the first element was added as last element. + // We must remove it again to prevent the list of vertices from growing + // over successive calls to endShape(CLOSE) + if (closeShape) { + vertices.pop(); + } + } + return this; +}; + +/** + * Specifies vertex coordinates for quadratic Bezier curves. Each call to + * quadraticVertex() defines the position of one control points and one + * anchor point of a Bezier curve, adding a new segment to a line or shape. + * The first time quadraticVertex() is used within a beginShape() call, it + * must be prefaced with a call to vertex() to set the first anchor point. + * This function must be used between beginShape() and endShape() and only + * when there is no MODE parameter specified to beginShape(). + * + * @method quadraticVertex + * @param {Number} cx x-coordinate for the control point + * @param {Number} cy y-coordinate for the control point + * @param {Number} x3 x-coordinate for the anchor point + * @param {Number} y3 y-coordinate for the anchor point + * @return {Object} the p5 object + * @example + * <div> + * <code> + * noFill(); + * strokeWeight(4); + * beginShape(); + * vertex(20, 20); + * quadraticVertex(80, 20, 50, 50); + * endShape(); + * </code> + * </div> + * + * <div> + * <code> + * noFill(); + * strokeWeight(4); + * beginShape(); + * vertex(20, 20); + * quadraticVertex(80, 20, 50, 50); + * quadraticVertex(20, 80, 80, 80); + * vertex(80, 60); + * endShape(); + * </code> + * </div> + */ +p5.prototype.quadraticVertex = function(cx, cy, x3, y3) { + //if we're drawing a contour, put the points into an + // array for inside drawing + if(this._contourInited) { + var pt = {}; + pt.x = cx; + pt.y = cy; + pt.x3 = x3; + pt.y3 = y3; + pt.type = constants.QUADRATIC; + this._contourVertices.push(pt); + + return this; + } + if (vertices.length > 0) { + isQuadratic = true; + var vert = []; + for (var i = 0; i < arguments.length; i++) { + vert[i] = arguments[i]; + } + vert.isVert = false; + if (isContour) { + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } else { + throw 'vertex() must be used once before calling quadraticVertex()'; + } + return this; +}; + +/** + * All shapes are constructed by connecting a series of vertices. vertex() + * is used to specify the vertex coordinates for points, lines, triangles, + * quads, and polygons. It is used exclusively within the beginShape() and + * endShape() functions. + * + * @method vertex + * @param {Number} x x-coordinate of the vertex + * @param {Number} y y-coordinate of the vertex + * @return {Object} the p5 object + * @example + * <div> + * <code> + * beginShape(POINTS); + * vertex(30, 20); + * vertex(85, 20); + * vertex(85, 75); + * vertex(30, 75); + * endShape(); + * </code> + * </div> + */ +p5.prototype.vertex = function(x, y, moveTo) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + if(this._renderer.isP3D){ + this._validateParameters( + 'vertex', + args, + [ + ['Number', 'Number', 'Number'] + ] + ); + this._renderer.vertex + (arguments[0], arguments[1], arguments[2]); + }else{ + this._validateParameters( + 'vertex', + args, + [ + ['Number', 'Number'], + ['Number', 'Number', 'Number'] + ] + ); + var vert = []; + vert.isVert = true; + vert[0] = x; + vert[1] = y; + vert[2] = 0; + vert[3] = 0; + vert[4] = 0; + vert[5] = this._renderer._getFill(); + vert[6] = this._renderer._getStroke(); + + if (moveTo) { + vert.moveTo = moveTo; + } + if (isContour) { + if (contourVertices.length === 0) { + vert.moveTo = true; + } + contourVertices.push(vert); + } else { + vertices.push(vert); + } + } + return this; +}; + +module.exports = p5; + +},{"./constants":47,"./core":48}],61:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Acceleration + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * The system variable deviceOrientation always contains the orientation of + * the device. The value of this variable will either be set 'landscape' + * or 'portrait'. If no data is available it will be set to 'undefined'. + * + * @property deviceOrientation + */ +p5.prototype.deviceOrientation = undefined; + +/** + * The system variable accelerationX always contains the acceleration of the + * device along the x axis. Value is represented as meters per second squared. + * + * @property accelerationX + */ +p5.prototype.accelerationX = 0; + +/** + * The system variable accelerationY always contains the acceleration of the + * device along the y axis. Value is represented as meters per second squared. + * + * @property accelerationY + */ +p5.prototype.accelerationY = 0; + +/** + * The system variable accelerationZ always contains the acceleration of the + * device along the z axis. Value is represented as meters per second squared. + * + * @property accelerationZ + */ +p5.prototype.accelerationZ = 0; + +/** + * The system variable pAccelerationX always contains the acceleration of the + * device along the x axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationX + */ +p5.prototype.pAccelerationX = 0; + +/** + * The system variable pAccelerationY always contains the acceleration of the + * device along the y axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationY + */ +p5.prototype.pAccelerationY = 0; + +/** + * The system variable pAccelerationZ always contains the acceleration of the + * device along the z axis in the frame previous to the current frame. Value + * is represented as meters per second squared. + * + * @property pAccelerationZ + */ +p5.prototype.pAccelerationZ = 0; + +/** + * _updatePAccelerations updates the pAcceleration values + * + * @private + */ +p5.prototype._updatePAccelerations = function(){ + this._setProperty('pAccelerationX', this.accelerationX); + this._setProperty('pAccelerationY', this.accelerationY); + this._setProperty('pAccelerationZ', this.accelerationZ); +}; + +/** + * The system variable rotationX always contains the rotation of the + * device along the x axis. Value is represented as 0 to +/-180 degrees. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationX + */ +p5.prototype.rotationX = 0; + +/** + * The system variable rotationY always contains the rotation of the + * device along the y axis. Value is represented as 0 to +/-90 degrees. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationY + */ +p5.prototype.rotationY = 0; + +/** + * The system variable rotationZ always contains the rotation of the + * device along the z axis. Value is represented as 0 to 359 degrees. + * <br><br> + * Unlike rotationX and rotationY, this variable is available for devices + * with a built-in compass only. + * <br><br> + * Note: The order the rotations are called is important, ie. if used + * together, it must be called in the order Z-X-Y or there might be + * unexpected behaviour. + * + * @example + * <div> + * <code> + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * </code> + * </div> + * + * @property rotationZ + */ +p5.prototype.rotationZ = 0; + +/** + * The system variable pRotationX always contains the rotation of the + * device along the x axis in the frame previous to the current frame. Value + * is represented as 0 to +/-180 degrees. + * <br><br> + * pRotationX can also be used with rotationX to determine the rotate + * direction of the device along the X-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationX - pRotationX < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely neccessary but the logic + * // will be different in that case. + * + * var rX = rotationX + 180; + * var pRX = pRotationX + 180; + * + * if ((rX - pRX > 0 && rX - pRX < 270)|| rX - pRX < -270){ + * rotateDirection = 'clockwise'; + * } else if (rX - pRX < 0 || rX - pRX > 270){ + * rotateDirection = 'counter-clockwise'; + * } + * </code> + * </div> + * + * @property pRotationX + */ +p5.prototype.pRotationX = 0; + +/** + * The system variable pRotationY always contains the rotation of the + * device along the y axis in the frame previous to the current frame. Value + * is represented as 0 to +/-90 degrees. + * <br><br> + * pRotationY can also be used with rotationY to determine the rotate + * direction of the device along the Y-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationY - pRotationY < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * // Simple range conversion to make things simpler. + * // This is not absolutely neccessary but the logic + * // will be different in that case. + * + * var rY = rotationY + 180; + * var pRY = pRotationY + 180; + * + * if ((rY - pRY > 0 && rY - pRY < 270)|| rY - pRY < -270){ + * rotateDirection = 'clockwise'; + * } else if (rY - pRY < 0 || rY - pRY > 270){ + * rotateDirection = 'counter-clockwise'; + * } + * </code> + * </div> + * + * @property pRotationY + */ +p5.prototype.pRotationY = 0; + +/** + * The system variable pRotationZ always contains the rotation of the + * device along the z axis in the frame previous to the current frame. Value + * is represented as 0 to 359 degrees. + * <br><br> + * pRotationZ can also be used with rotationZ to determine the rotate + * direction of the device along the Z-axis. + * @example + * <div class='norender'> + * <code> + * // A simple if statement looking at whether + * // rotationZ - pRotationZ < 0 is true or not will be + * // sufficient for determining the rotate direction + * // in most cases. + * + * // Some extra logic is needed to account for cases where + * // the angles wrap around. + * var rotateDirection = 'clockwise'; + * + * if ((rotationZ - pRotationZ > 0 && + * rotationZ - pRotationZ < 270)|| + * rotationZ - pRotationZ < -270){ + * + * rotateDirection = 'clockwise'; + * + * } else if (rotationZ - pRotationZ < 0 || + * rotationZ - pRotationZ > 270){ + * + * rotateDirection = 'counter-clockwise'; + * + * } + * </code> + * </div> + * + * @property pRotationZ + */ +p5.prototype.pRotationZ = 0; + +var startAngleX = 0; +var startAngleY = 0; +var startAngleZ = 0; + +var rotateDirectionX = 'clockwise'; +var rotateDirectionY = 'clockwise'; +var rotateDirectionZ = 'clockwise'; + +var pRotateDirectionX; +var pRotateDirectionY; +var pRotateDirectionZ; + +p5.prototype._updatePRotations = function(){ + this._setProperty('pRotationX', this.rotationX); + this._setProperty('pRotationY', this.rotationY); + this._setProperty('pRotationZ', this.rotationZ); +}; + +p5.prototype.turnAxis = undefined; + +var move_threshold = 0.5; +var shake_threshold = 30; + +/** + * The setMoveThreshold() function is used to set the movement threshold for + * the deviceMoved() function. The default threshold is set to 0.5. + * + * @method setMoveThreshold + * @param {number} value The threshold value + */ +p5.prototype.setMoveThreshold = function(val){ + if(typeof val === 'number'){ + move_threshold = val; + } +}; + +/** + * The setShakeThreshold() function is used to set the movement threshold for + * the deviceShaken() function. The default threshold is set to 30. + * + * @method setShakeThreshold + * @param {number} value The threshold value + */ +p5.prototype.setShakeThreshold = function(val){ + if(typeof val === 'number'){ + shake_threshold = val; + } +}; + +/** + * The deviceMoved() function is called when the device is moved by more than + * the threshold value along X, Y or Z axis. The default threshold is set to + * 0.5. + * @method deviceMoved + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Move the device around + * // to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + */ + +/** + * The deviceTurned() function is called when the device rotates by + * more than 90 degrees continuously. + * <br><br> + * The axis that triggers the deviceTurned() method is stored in the turnAxis + * variable. The deviceTurned() method can be locked to trigger on any axis: + * X, Y or Z by comparing the turnAxis variable to 'X', 'Y' or 'Z'. + * + * @method deviceTurned + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Rotate the device by 90 degrees + * // to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceTurned() { + * if (value == 0){ + * value = 255 + * } else if (value == 255) { + * value = 0; + * } + * } + * </code> + * </div> + * <div> + * <code> + * // Run this example on a mobile device + * // Rotate the device by 90 degrees in the + * // X-axis to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceTurned() { + * if (turnAxis == 'X'){ + * if (value == 0){ + * value = 255 + * } else if (value == 255) { + * value = 0; + * } + * } + * } + * </code> + * </div> + */ + +/** + * The deviceShaken() function is called when the device total acceleration + * changes of accelerationX and accelerationY values is more than + * the threshold value. The default threshold is set to 30. + * @method deviceShaken + * @example + * <div> + * <code> + * // Run this example on a mobile device + * // Shake the device to change the value. + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function deviceShaken() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + */ + +p5.prototype._ondeviceorientation = function (e) { + this._updatePRotations(); + this._setProperty('rotationX', e.beta); + this._setProperty('rotationY', e.gamma); + this._setProperty('rotationZ', e.alpha); + this._handleMotion(); +}; +p5.prototype._ondevicemotion = function (e) { + this._updatePAccelerations(); + this._setProperty('accelerationX', e.acceleration.x * 2); + this._setProperty('accelerationY', e.acceleration.y * 2); + this._setProperty('accelerationZ', e.acceleration.z * 2); + this._handleMotion(); +}; +p5.prototype._handleMotion = function() { + if (window.orientation === 90 || window.orientation === -90) { + this._setProperty('deviceOrientation', 'landscape'); + } else if (window.orientation === 0) { + this._setProperty('deviceOrientation', 'portrait'); + } else if (window.orientation === undefined) { + this._setProperty('deviceOrientation', 'undefined'); + } + var deviceMoved = this.deviceMoved || window.deviceMoved; + if (typeof deviceMoved === 'function') { + if (Math.abs(this.accelerationX - this.pAccelerationX) > move_threshold || + Math.abs(this.accelerationY - this.pAccelerationY) > move_threshold || + Math.abs(this.accelerationZ - this.pAccelerationZ) > move_threshold) { + deviceMoved(); + } + } + var deviceTurned = this.deviceTurned || window.deviceTurned; + if (typeof deviceTurned === 'function') { + // The angles given by rotationX etc is from range -180 to 180. + // The following will convert them to 0 to 360 for ease of calculation + // of cases when the angles wrapped around. + // _startAngleX will be converted back at the end and updated. + var wRX = this.rotationX + 180; + var wPRX = this.pRotationX + 180; + var wSAX = startAngleX + 180; + if ((wRX - wPRX > 0 && wRX - wPRX < 270)|| wRX - wPRX < -270){ + rotateDirectionX = 'clockwise'; + } else if (wRX - wPRX < 0 || wRX - wPRX > 270){ + rotateDirectionX = 'counter-clockwise'; + } + if (rotateDirectionX !== pRotateDirectionX){ + wSAX = wRX; + } + if (Math.abs(wRX - wSAX) > 90 && Math.abs(wRX - wSAX) < 270){ + wSAX = wRX; + this._setProperty('turnAxis', 'X'); + deviceTurned(); + } + pRotateDirectionX = rotateDirectionX; + startAngleX = wSAX - 180; + + // Y-axis is identical to X-axis except for changing some names. + var wRY = this.rotationY + 180; + var wPRY = this.pRotationY + 180; + var wSAY = startAngleY + 180; + if ((wRY - wPRY > 0 && wRY - wPRY < 270)|| wRY - wPRY < -270){ + rotateDirectionY = 'clockwise'; + } else if (wRY - wPRY < 0 || wRY - this.pRotationY > 270){ + rotateDirectionY = 'counter-clockwise'; + } + if (rotateDirectionY !== pRotateDirectionY){ + wSAY = wRY; + } + if (Math.abs(wRY - wSAY) > 90 && Math.abs(wRY - wSAY) < 270){ + wSAY = wRY; + this._setProperty('turnAxis', 'Y'); + deviceTurned(); + } + pRotateDirectionY = rotateDirectionY; + startAngleY = wSAY - 180; + + // Z-axis is already in the range 0 to 360 + // so no conversion is needed. + if ((this.rotationZ - this.pRotationZ > 0 && + this.rotationZ - this.pRotationZ < 270)|| + this.rotationZ - this.pRotationZ < -270){ + rotateDirectionZ = 'clockwise'; + } else if (this.rotationZ - this.pRotationZ < 0 || + this.rotationZ - this.pRotationZ > 270){ + rotateDirectionZ = 'counter-clockwise'; + } + if (rotateDirectionZ !== pRotateDirectionZ){ + startAngleZ = this.rotationZ; + } + if (Math.abs(this.rotationZ - startAngleZ) > 90 && + Math.abs(this.rotationZ - startAngleZ) < 270){ + startAngleZ = this.rotationZ; + this._setProperty('turnAxis', 'Z'); + deviceTurned(); + } + pRotateDirectionZ = rotateDirectionZ; + this._setProperty('turnAxis', undefined); + } + var deviceShaken = this.deviceShaken || window.deviceShaken; + if (typeof deviceShaken === 'function') { + var accelerationChangeX; + var accelerationChangeY; + // Add accelerationChangeZ if acceleration change on Z is needed + if (this.pAccelerationX !== null) { + accelerationChangeX = Math.abs(this.accelerationX - this.pAccelerationX); + accelerationChangeY = Math.abs(this.accelerationY - this.pAccelerationY); + } + if (accelerationChangeX + accelerationChangeY > shake_threshold) { + deviceShaken(); + } + } +}; + + +module.exports = p5; + +},{"../core/core":48}],62:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Keyboard + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Holds the key codes of currently pressed keys. + * @private + */ +var downKeys = {}; + +/** + * The boolean system variable keyIsPressed is true if any key is pressed + * and false if no keys are pressed. + * + * @property keyIsPressed + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * if (keyIsPressed === true) { + * fill(0); + * } else { + * fill(255); + * } + * rect(25, 25, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.isKeyPressed = false; +p5.prototype.keyIsPressed = false; // khan + +/** + * The system variable key always contains the value of the most recent + * key on the keyboard that was typed. To get the proper capitalization, it + * is best to use it within keyTyped(). For non-ASCII keys, use the keyCode + * variable. + * + * @property key + * @example + * <div><code> + * // Click any key to display it! + * // (Not Guaranteed to be Case Sensitive) + * function setup() { + * fill(245, 123, 158); + * textSize(50); + * } + * + * function draw() { + * background(200); + * text(key, 33,65); // Display last key pressed. + * } + * </div></code> + */ +p5.prototype.key = ''; + +/** + * The variable keyCode is used to detect special keys such as BACKSPACE, + * DELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL, OPTION, ALT, UP_ARROW, + * DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW. + * + * @property keyCode + * @example + * <div><code> + * var fillVal = 126; + * function draw() { + * fill(fillVal); + * rect(25, 25, 50, 50); + * } + * + * function keyPressed() { + * if (keyCode == UP_ARROW) { + * fillVal = 255; + * } else if (keyCode == DOWN_ARROW) { + * fillVal = 0; + * } + * return false; // prevent default + * } + * </code></div> + */ +p5.prototype.keyCode = 0; + +/** + * The keyPressed() function is called once every time a key is pressed. The + * keyCode for the key that was pressed is stored in the keyCode variable. + * <br><br> + * For non-ASCII keys, use the keyCode variable. You can check if the keyCode + * equals BACKSPACE, DELETE, ENTER, RETURN, TAB, ESCAPE, SHIFT, CONTROL, + * OPTION, ALT, UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW. + * <br><br> + * For ASCII keys that was pressed is stored in the key variable. However, it + * does not distinguish between uppercase and lowercase. For this reason, it + * is recommended to use keyTyped() to read the key variable, in which the + * case of the variable will be distinguished. + * <br><br> + * Because of how operating systems handle key repeats, holding down a key + * may cause multiple calls to keyTyped() (and keyReleased() as well). The + * rate of repeat is set by the operating system and how each computer is + * configured.<br><br> + * Browsers may have different default + * behaviors attached to various key events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method keyPressed + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyPressed() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyPressed() { + * if (keyCode === LEFT_ARROW) { + * value = 255; + * } else if (keyCode === RIGHT_ARROW) { + * value = 0; + * } + * } + * </code> + * </div> + * <div class="norender"> + * <code> + * function keyPressed(){ + * // Do something + * return false; // prevent any default behaviour + * } + * </code> + * </div> + */ +p5.prototype._onkeydown = function (e) { + if (downKeys[e.which]) { // prevent multiple firings + return; + } + this._setProperty('isKeyPressed', true); + this._setProperty('keyIsPressed', true); + this._setProperty('keyCode', e.which); + downKeys[e.which] = true; + var key = String.fromCharCode(e.which); + if (!key) { + key = e.which; + } + this._setProperty('key', key); + var keyPressed = this.keyPressed || window.keyPressed; + if (typeof keyPressed === 'function' && !e.charCode) { + var executeDefault = keyPressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; +/** + * The keyReleased() function is called once every time a key is released. + * See key and keyCode for more information.<br><br> + * Browsers may have different default + * behaviors attached to various key events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method keyReleased + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyReleased() { + * if (value === 0) { + * value = 255; + * } else { + * value = 0; + * } + * return false; // prevent any default behavior + * } + * </code> + * </div> + */ +p5.prototype._onkeyup = function (e) { + var keyReleased = this.keyReleased || window.keyReleased; + this._setProperty('isKeyPressed', false); + this._setProperty('keyIsPressed', false); + this._setProperty('_lastKeyCodeTyped', null); + downKeys[e.which] = false; + //delete this._downKeys[e.which]; + var key = String.fromCharCode(e.which); + if (!key) { + key = e.which; + } + this._setProperty('key', key); + this._setProperty('keyCode', e.which); + if (typeof keyReleased === 'function') { + var executeDefault = keyReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The keyTyped() function is called once every time a key is pressed, but + * action keys such as Ctrl, Shift, and Alt are ignored. The most recent + * key pressed will be stored in the key variable. + * <br><br> + * Because of how operating systems handle key repeats, holding down a key + * will cause multiple calls to keyTyped() (and keyReleased() as well). The + * rate of repeat is set by the operating system and how each computer is + * configured.<br><br> + * Browsers may have different default behaviors attached to various key + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method keyTyped + * @example + * <div> + * <code> + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function keyTyped() { + * if (key === 'a') { + * value = 255; + * } else if (key === 'b') { + * value = 0; + * } + * return false; // prevent any default behavior + * } + * </code> + * </div> + */ +p5.prototype._onkeypress = function (e) { + if (e.which === this._lastKeyCodeTyped) { // prevent multiple firings + return; + } + this._setProperty('keyCode', e.which); + this._setProperty('_lastKeyCodeTyped', e.which); // track last keyCode + this._setProperty('key', String.fromCharCode(e.which)); + var keyTyped = this.keyTyped || window.keyTyped; + if (typeof keyTyped === 'function') { + var executeDefault = keyTyped(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; +/** + * The onblur function is called when the user is no longer focused + * on the p5 element. Because the keyup events will not fire if the user is + * not focused on the element we must assume all keys currently down have + * been released. + */ +p5.prototype._onblur = function (e) { + downKeys = {}; +}; + +/** + * The keyIsDown() function checks if the key is currently down, i.e. pressed. + * It can be used if you have an object that moves, and you want several keys + * to be able to affect its behaviour simultaneously, such as moving a + * sprite diagonally. You can put in any number representing the keyCode of + * the key, or use any of the variable keyCode names listed + * <a href="http://p5js.org/reference/#p5/keyCode">here</a>. + * + * @method keyIsDown + * @param {Number} code The key to check for. + * @return {Boolean} whether key is down or not + * @example + * <div><code> + * var x = 100; + * var y = 100; + * + * function setup() { + * createCanvas(512, 512); + * } + * + * function draw() { + * if (keyIsDown(LEFT_ARROW)) + * x-=5; + * + * if (keyIsDown(RIGHT_ARROW)) + * x+=5; + * + * if (keyIsDown(UP_ARROW)) + * y-=5; + * + * if (keyIsDown(DOWN_ARROW)) + * y+=5; + * + * clear(); + * fill(255, 0, 0); + * ellipse(x, y, 50, 50); + * } + * </code></div> + */ +p5.prototype.keyIsDown = function(code) { + return downKeys[code]; +}; + +module.exports = p5; + +},{"../core/core":48}],63:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Mouse + * @for p5 + * @requires core + * @requires constants + */ + + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +/* + * These are helper vars that store the mouseX and mouseY vals + * between the time that a mouse event happens and the next frame + * of draw. This is done to deal with the asynchronicity of event + * calls interacting with the draw loop. When a mouse event occurs + * the _nextMouseX/Y vars are updated, then on each call of draw, mouseX/Y + * and pmouseX/Y are updated using the _nextMouseX/Y vals. + */ +p5.prototype._nextMouseX = 0; +p5.prototype._nextMouseY = 0; + +/** + * The system variable mouseX always contains the current horizontal + * position of the mouse, relative to (0, 0) of the canvas. + * + * @property mouseX + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas + * function draw() { + * background(244, 248, 252); + * line(mouseX, 0, mouseX, 100); + * } + * </code> + * </div> + */ +p5.prototype.mouseX = 0; + +/** + * The system variable mouseY always contains the current vertical position + * of the mouse, relative to (0, 0) of the canvas. + * + * @property mouseY + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas + * function draw() { + * background(244, 248, 252); + * line(0, mouseY, 100, mouseY); + *} + * </code> + * </div> + */ +p5.prototype.mouseY = 0; + +/** + * The system variable pmouseX always contains the horizontal position of + * the mouse in the frame previous to the current frame, relative to (0, 0) + * of the canvas. + * + * @property pmouseX + * + * @example + * <div> + * <code> + * // Move the mouse across the canvas to leave a trail + * function setup() { + * //slow down the frameRate to make it more visible + * frameRate(10); + * } + * + * function draw() { + * background(244, 248, 252); + * line(mouseX, mouseY, pmouseX, pmouseY); + * print(pmouseX + " -> " + mouseX); + * } + * + * </code> + * </div> + */ +p5.prototype.pmouseX = 0; + +/** + * The system variable pmouseY always contains the vertical position of the + * mouse in the frame previous to the current frame, relative to (0, 0) of + * the canvas. + * + * @property pmouseY + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * //draw a square only if the mouse is not moving + * if(mouseY == pmouseY && mouseX == pmouseX) + * rect(20,20,60,60); + * + * print(pmouseY + " -> " + mouseY); + * } + * + * </code> + * </div> + */ +p5.prototype.pmouseY = 0; + +/** + * The system variable winMouseX always contains the current horizontal + * position of the mouse, relative to (0, 0) of the window. + * + * @property winMouseX + * + * @example + * <div> + * <code> + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * } + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * //move the canvas to the horizontal mouse position + * //relative to the window + * myCanvas.position(winMouseX+1, windowHeight/2); + * + * //the y of the square is relative to the canvas + * rect(20,mouseY,60,60); + * } + * + * </code> + * </div> + */ +p5.prototype.winMouseX = 0; + +/** + * The system variable winMouseY always contains the current vertical + * position of the mouse, relative to (0, 0) of the window. + * + * @property winMouseY + * + * @example + * <div> + * <code> + *var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * } + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * //move the canvas to the vertical mouse position + * //relative to the window + * myCanvas.position(windowWidth/2, winMouseY+1); + * + * //the x of the square is relative to the canvas + * rect(mouseX,20,60,60); + * } + * + * </code> + * </div> + */ +p5.prototype.winMouseY = 0; + +/** + * The system variable pwinMouseX always contains the horizontal position + * of the mouse in the frame previous to the current frame, relative to + * (0, 0) of the window. + * + * @property pwinMouseX + * + * @example + * <div> + * <code> + * + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * noStroke(); + * fill(237, 34, 93); + * } + * + * function draw() { + * clear(); + * //the difference between previous and + * //current x position is the horizontal mouse speed + * var speed = abs(winMouseX-pwinMouseX); + * //change the size of the circle + * //according to the horizontal speed + * ellipse(50, 50, 10+speed*5, 10+speed*5); + * //move the canvas to the mouse position + * myCanvas.position( winMouseX+1, winMouseY+1); + * } + * + * </code> + * </div> + */ +p5.prototype.pwinMouseX = 0; + +/** + * The system variable pwinMouseY always contains the vertical position of + * the mouse in the frame previous to the current frame, relative to (0, 0) + * of the window. + * + * @property pwinMouseY + * + * + * @example + * <div> + * <code> + * + * var myCanvas; + * + * function setup() { + * //use a variable to store a pointer to the canvas + * myCanvas = createCanvas(100, 100); + * noStroke(); + * fill(237, 34, 93); + * } + * + * function draw() { + * clear(); + * //the difference between previous and + * //current y position is the vertical mouse speed + * var speed = abs(winMouseY-pwinMouseY); + * //change the size of the circle + * //according to the vertical speed + * ellipse(50, 50, 10+speed*5, 10+speed*5); + * //move the canvas to the mouse position + * myCanvas.position( winMouseX+1, winMouseY+1); + * } + * + * </code> + * </div> + */ +p5.prototype.pwinMouseY = 0; + +/** + * Processing automatically tracks if the mouse button is pressed and which + * button is pressed. The value of the system variable mouseButton is either + * LEFT, RIGHT, or CENTER depending on which button was pressed last. + * Warning: different browsers may track mouseButton differently. + * + * @property mouseButton + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * if (mouseIsPressed) { + * if (mouseButton == LEFT) + * ellipse(50, 50, 50, 50); + * if (mouseButton == RIGHT) + * rect(25, 25, 50, 50); + * if (mouseButton == CENTER) + * triangle(23, 75, 50, 20, 78, 75); + * } + * + * print(mouseButton); + * } + * </code> + * </div> + */ +p5.prototype.mouseButton = 0; + +/** + * The boolean system variable mouseIsPressed is true if the mouse is pressed + * and false if not. + * + * @property mouseIsPressed + * + * @example + * <div> + * <code> + * function draw() { + * background(237, 34, 93); + * fill(0); + * + * if (mouseIsPressed) + * ellipse(50, 50, 50, 50); + * else + * rect(25, 25, 50, 50); + * + * print(mouseIsPressed); + * } + * </code> + * </div> + */ +p5.prototype.mouseIsPressed = false; +p5.prototype.isMousePressed = false; // both are supported + +p5.prototype._updateNextMouseCoords = function(e) { + if(e.type === 'touchstart' || + e.type === 'touchmove' || + e.type === 'touchend') { + this._setProperty('_nextMouseX', this._nextTouchX); + this._setProperty('_nextMouseY', this._nextTouchY); + } else { + if(this._curElement !== null) { + var mousePos = getMousePos(this._curElement.elt, e); + this._setProperty('_nextMouseX', mousePos.x); + this._setProperty('_nextMouseY', mousePos.y); + } + } + this._setProperty('winMouseX', e.pageX); + this._setProperty('winMouseY', e.pageY); +}; + +p5.prototype._updateMouseCoords = function() { + this._setProperty('pmouseX', this.mouseX); + this._setProperty('pmouseY', this.mouseY); + this._setProperty('mouseX', this._nextMouseX); + this._setProperty('mouseY', this._nextMouseY); + this._setProperty('pwinMouseX', this.winMouseX); + this._setProperty('pwinMouseY', this.winMouseY); +}; + +function getMousePos(canvas, evt) { + var rect = canvas.getBoundingClientRect(); + return { + x: evt.clientX - rect.left, + y: evt.clientY - rect.top + }; +} + +p5.prototype._setMouseButton = function(e) { + if (e.button === 1) { + this._setProperty('mouseButton', constants.CENTER); + } else if (e.button === 2) { + this._setProperty('mouseButton', constants.RIGHT); + } else { + this._setProperty('mouseButton', constants.LEFT); + } +}; + +/** + * The mouseMoved() function is called every time the mouse moves and a mouse + * button is not pressed.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseMoved + * @example + * <div> + * <code> + * // Move the mouse across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseMoved() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ + +/** + * The mouseDragged() function is called once every time the mouse moves and + * a mouse button is pressed. If no mouseDragged() function is defined, the + * touchMoved() function will be called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseDragged + * @example + * <div> + * <code> + * // Drag the mouse across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseDragged() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseDragged() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmousemove = function(e){ + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); + if (!this.isMousePressed) { + if (typeof context.mouseMoved === 'function') { + executeDefault = context.mouseMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } + } + else { + if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } + } +}; + +/** + * The mousePressed() function is called once after every time a mouse button + * is pressed. The mouseButton variable (see the related reference entry) + * can be used to determine which button has been pressed. If no + * mousePressed() function is defined, the touchStarted() function will be + * called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mousePressed + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mousePressed() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mousePressed() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmousedown = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._setProperty('isMousePressed', true); + this._setProperty('mouseIsPressed', true); + this._setMouseButton(e); + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); + if (typeof context.mousePressed === 'function') { + executeDefault = context.mousePressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The mouseReleased() function is called every time a mouse button is + * released. If no mouseReleased() function is defined, the touchEnded() + * function will be called instead if it is defined.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * + * @method mouseReleased + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * // after the mouse has been clicked + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseReleased() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseReleased() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onmouseup = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._setProperty('isMousePressed', false); + this._setProperty('mouseIsPressed', false); + if (typeof context.mouseReleased === 'function') { + executeDefault = context.mouseReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +p5.prototype._ondragend = p5.prototype._onmouseup; +p5.prototype._ondragover = p5.prototype._onmousemove; + +/** + * The mouseClicked() function is called once after a mouse button has been + * pressed and then released.<br><br> + * Browsers may have different default + * behaviors attached to various mouse events. To prevent any default + * behavior for this event, add "return false" to the end of the method. + * + * @method mouseClicked + * @example + * <div> + * <code> + * // Click within the image to change + * // the value of the rectangle + * // after the mouse has been clicked + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function mouseClicked() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function mouseClicked() { + * ellipse(mouseX, mouseY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._onclick = function(e) { + var context = this._isGlobal ? window : this; + if (typeof context.mouseClicked === 'function') { + var executeDefault = context.mouseClicked(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The function mouseWheel() is executed every time a vertical mouse wheel + * event is detected either triggered by an actual mouse wheel or by a + * touchpad.<br><br> + * The event.delta property returns the amount the mouse wheel + * have scrolled. The values can be positive or negative depending on the + * scroll direction (on OS X with "natural" scrolling enabled, the signs + * are inverted).<br><br> + * Browsers may have different default behaviors attached to various + * mouse events. To prevent any default behavior for this event, add + * "return false" to the end of the method.<br><br> + * Due to the current support of the "wheel" event on Safari, the function + * may only work as expected if "return false" is included while using Safari. + * + * @method mouseWheel + * + * @example + * <div> + * <code> + * var pos = 25; + * + * function draw() { + * background(237, 34, 93); + * fill(0); + * rect(25, pos, 50, 50); + * } + * + * function mouseWheel(event) { + * print(event.delta); + * //move the square according to the vertical scroll amount + * pos += event.delta; + * //uncomment to block page scrolling + * //return false; + * } + * </code> + * </div> + */ +p5.prototype._onwheel = function(e) { + var context = this._isGlobal ? window : this; + if (typeof context.mouseWheel === 'function') { + e.delta = e.deltaY; + var executeDefault = context.mouseWheel(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48}],64:[function(_dereq_,module,exports){ +/** + * @module Events + * @submodule Touch + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/* + * These are helper vars that store the touchX and touchY vals + * between the time that a mouse event happens and the next frame + * of draw. This is done to deal with the asynchronicity of event + * calls interacting with the draw loop. When a touch event occurs + * the _nextTouchX/Y vars are updated, then on each call of draw, touchX/Y + * and ptouchX/Y are updated using the _nextMouseX/Y vals. + */ +p5.prototype._nextTouchX = 0; +p5.prototype._nextTouchY = 0; + +/** + * The system variable touchX always contains the horizontal position of + * one finger, relative to (0, 0) of the canvas. This is best used for + * single touch interactions. For multi-touch interactions, use the + * touches[] array. + * + * @property touchX + */ +p5.prototype.touchX = 0; + +/** + * The system variable touchY always contains the vertical position of + * one finger, relative to (0, 0) of the canvas. This is best used for + * single touch interactions. For multi-touch interactions, use the + * touches[] array. + * + * @property touchY + */ +p5.prototype.touchY = 0; + +/** + * The system variable ptouchX always contains the horizontal position of + * one finger, relative to (0, 0) of the canvas, in the frame previous to the + * current frame. + * + * @property ptouchX + */ +p5.prototype.ptouchX = 0; + +/** + * The system variable ptouchY always contains the vertical position of + * one finger, relative to (0, 0) of the canvas, in the frame previous to the + * current frame. + * + * @property ptouchY + */ +p5.prototype.ptouchY = 0; + +/** + * The system variable touches[] contains an array of the positions of all + * current touch points, relative to (0, 0) of the canvas, and IDs identifying a + * unique touch as it moves. Each element in the array is an object with x, y, + * and id properties. + * + * @property touches[] + */ +p5.prototype.touches = []; + +/** + * The boolean system variable touchIsDown is true if the screen is + * touched and false if not. + * + * @property touchIsDown + */ +p5.prototype.touchIsDown = false; + +p5.prototype._updateNextTouchCoords = function(e) { + if(e.type === 'mousedown' || + e.type === 'mousemove' || + e.type === 'mouseup'){ + this._setProperty('_nextTouchX', this._nextMouseX); + this._setProperty('_nextTouchY', this._nextMouseY); + } else { + if(this._curElement !== null) { + var touchInfo = getTouchInfo(this._curElement.elt, e, 0); + this._setProperty('_nextTouchX', touchInfo.x); + this._setProperty('_nextTouchY', touchInfo.y); + + var touches = []; + for(var i = 0; i < e.touches.length; i++){ + touches[i] = getTouchInfo(this._curElement.elt, e, i); + } + this._setProperty('touches', touches); + } + } +}; + +p5.prototype._updateTouchCoords = function() { + this._setProperty('ptouchX', this.touchX); + this._setProperty('ptouchY', this.touchY); + this._setProperty('touchX', this._nextTouchX); + this._setProperty('touchY', this._nextTouchY); +}; + +function getTouchInfo(canvas, e, i) { + i = i || 0; + var rect = canvas.getBoundingClientRect(); + var touch = e.touches[i] || e.changedTouches[i]; + return { + x: touch.clientX - rect.left, + y: touch.clientY - rect.top, + id: touch.identifier + }; +} + +/** + * The touchStarted() function is called once after every time a touch is + * registered. If no touchStarted() function is defined, the mousePressed() + * function will be called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchStarted + * @example + * <div> + * <code> + * // Touch within the image to change + * // the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchStarted() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchStarted() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchstart = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + this._setProperty('touchIsDown', true); + if(typeof context.touchStarted === 'function') { + executeDefault = context.touchStarted(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mousePressed === 'function') { + executeDefault = context.mousePressed(e); + if(executeDefault === false) { + e.preventDefault(); + } + //this._setMouseButton(e); + } +}; + +/** + * The touchMoved() function is called every time a touch move is registered. + * If no touchMoved() function is defined, the mouseDragged() function will + * be called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchMoved + * @example + * <div> + * <code> + * // Move your finger across the page + * // to change its value + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchMoved() { + * value = value + 5; + * if (value > 255) { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchMoved() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchmove = function(e) { + var context = this._isGlobal ? window : this; + var executeDefault; + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + if (typeof context.touchMoved === 'function') { + executeDefault = context.touchMoved(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mouseDragged === 'function') { + executeDefault = context.mouseDragged(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +/** + * The touchEnded() function is called every time a touch ends. If no + * touchEnded() function is defined, the mouseReleased() function will be + * called instead if it is defined.<br><br> + * Browsers may have different default behaviors attached to various touch + * events. To prevent any default behavior for this event, add "return false" + * to the end of the method. + * + * @method touchEnded + * @example + * <div> + * <code> + * // Release touch within the image to + * // change the value of the rectangle + * + * var value = 0; + * function draw() { + * fill(value); + * rect(25, 25, 50, 50); + * } + * function touchEnded() { + * if (value == 0) { + * value = 255; + * } else { + * value = 0; + * } + * } + * </code> + * </div> + * + * <div class="norender"> + * <code> + * function touchEnded() { + * ellipse(touchX, touchY, 5, 5); + * // prevent default + * return false; + * } + * </code> + * </div> + */ +p5.prototype._ontouchend = function(e) { + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); + if (this.touches.length === 0) { + this._setProperty('touchIsDown', false); + } + var context = this._isGlobal ? window : this; + var executeDefault; + if (typeof context.touchEnded === 'function') { + executeDefault = context.touchEnded(e); + if(executeDefault === false) { + e.preventDefault(); + } + } else if (typeof context.mouseReleased === 'function') { + executeDefault = context.mouseReleased(e); + if(executeDefault === false) { + e.preventDefault(); + } + } +}; + +module.exports = p5; + +},{"../core/core":48}],65:[function(_dereq_,module,exports){ +/*global ImageData:false */ + +/** + * This module defines the filters for use with image buffers. + * + * This module is basically a collection of functions stored in an object + * as opposed to modules. The functions are destructive, modifying + * the passed in canvas rather than creating a copy. + * + * Generally speaking users of this module will use the Filters.apply method + * on a canvas to create an effect. + * + * A number of functions are borrowed/adapted from + * http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * or the java processing implementation. + */ + +'use strict'; + +var Filters = {}; + + +/* + * Helper functions + */ + + +/** + * Returns the pixel buffer for a canvas + * + * @private + * + * @param {Canvas|ImageData} canvas the canvas to get pixels from + * @return {Uint8ClampedArray} a one-dimensional array containing + * the data in thc RGBA order, with integer + * values between 0 and 255 + */ +Filters._toPixels = function (canvas) { + if (canvas instanceof ImageData) { + return canvas.data; + } else { + return canvas.getContext('2d').getImageData( + 0, + 0, + canvas.width, + canvas.height + ).data; + } +}; + +/** + * Returns a 32 bit number containing ARGB data at ith pixel in the + * 1D array containing pixels data. + * + * @private + * + * @param {Uint8ClampedArray} data array returned by _toPixels() + * @param {Integer} i index of a 1D Image Array + * @return {Integer} 32 bit integer value representing + * ARGB value. + */ +Filters._getARGB = function (data, i) { + var offset = i * 4; + return (data[offset+3] << 24) & 0xff000000 | + (data[offset] << 16) & 0x00ff0000 | + (data[offset+1] << 8) & 0x0000ff00 | + data[offset+2] & 0x000000ff; +}; + +/** + * Modifies pixels RGBA values to values contained in the data object. + * + * @private + * + * @param {Uint8ClampedArray} pixels array returned by _toPixels() + * @param {Int32Array} data source 1D array where each value + * represents ARGB values + */ +Filters._setPixels = function (pixels, data) { + var offset = 0; + for( var i = 0, al = pixels.length; i < al; i++) { + offset = i*4; + pixels[offset + 0] = (data[i] & 0x00ff0000)>>>16; + pixels[offset + 1] = (data[i] & 0x0000ff00)>>>8; + pixels[offset + 2] = (data[i] & 0x000000ff); + pixels[offset + 3] = (data[i] & 0xff000000)>>>24; + } +}; + +/** + * Returns the ImageData object for a canvas + * https://developer.mozilla.org/en-US/docs/Web/API/ImageData + * + * @private + * + * @param {Canvas|ImageData} canvas canvas to get image data from + * @return {ImageData} Holder of pixel data (and width and + * height) for a canvas + */ +Filters._toImageData = function (canvas) { + if (canvas instanceof ImageData) { + return canvas; + } else { + return canvas.getContext('2d').getImageData( + 0, + 0, + canvas.width, + canvas.height + ); + } +}; + +/** + * Returns a blank ImageData object. + * + * @private + * + * @param {Integer} width + * @param {Integer} height + * @return {ImageData} + */ +Filters._createImageData = function (width, height) { + Filters._tmpCanvas = document.createElement('canvas'); + Filters._tmpCtx = Filters._tmpCanvas.getContext('2d'); + return this._tmpCtx.createImageData(width, height); +}; + + +/** + * Applys a filter function to a canvas. + * + * The difference between this and the actual filter functions defined below + * is that the filter functions generally modify the pixel buffer but do + * not actually put that data back to the canvas (where it would actually + * update what is visible). By contrast this method does make the changes + * actually visible in the canvas. + * + * The apply method is the method that callers of this module would generally + * use. It has been separated from the actual filters to support an advanced + * use case of creating a filter chain that executes without actually updating + * the canvas in between everystep. + * + * @param {[type]} func [description] + * @param {[type]} canvas [description] + * @param {[type]} level [description] + * @return {[type]} [description] + */ +Filters.apply = function (canvas, func, filterParam) { + var ctx = canvas.getContext('2d'); + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + //Filters can either return a new ImageData object, or just modify + //the one they received. + var newImageData = func(imageData, filterParam); + if (newImageData instanceof ImageData) { + ctx.putImageData(newImageData, 0, 0, 0, 0, canvas.width, canvas.height); + } else { + ctx.putImageData(imageData, 0, 0, 0, 0, canvas.width, canvas.height); + } +}; + + +/* + * Filters + */ + + +/** + * Converts the image to black and white pixels depending if they are above or + * below the threshold defined by the level parameter. The parameter must be + * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used. + * + * Borrowed from http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * + * @param {Canvas} canvas + * @param {Float} level + */ +Filters.threshold = function (canvas, level) { + var pixels = Filters._toPixels(canvas); + + if (level === undefined) { + level = 0.5; + } + var thresh = Math.floor(level * 255); + + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + var gray = (0.2126 * r + 0.7152 * g + 0.0722 * b); + var val; + if (gray >= thresh) { + val = 255; + } else { + val = 0; + } + pixels[i] = pixels[i + 1] = pixels[i + 2] = val; + } + +}; + + +/** + * Converts any colors in the image to grayscale equivalents. + * No parameter is used. + * + * Borrowed from http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ + * + * @param {Canvas} canvas + */ +Filters.gray = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i + 1]; + var b = pixels[i + 2]; + + // CIE luminance for RGB + var gray = (0.2126 * r + 0.7152 * g + 0.0722 * b); + pixels[i] = pixels[i + 1] = pixels[i + 2] = gray; + } +}; + +/** + * Sets the alpha channel to entirely opaque. No parameter is used. + * + * @param {Canvas} canvas + */ +Filters.opaque = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + pixels[i + 3] = 255; + } + + return pixels; +}; + +/** + * Sets each pixel to its inverse value. No parameter is used. + * @param {Invert} + */ +Filters.invert = function (canvas) { + var pixels = Filters._toPixels(canvas); + + for (var i = 0; i < pixels.length; i += 4) { + pixels[i] = 255 - pixels[i]; + pixels[i + 1] = 255 - pixels[i + 1]; + pixels[i + 2] = 255 - pixels[i + 2]; + } + +}; + + +/** + * Limits each channel of the image to the number of colors specified as + * the parameter. The parameter can be set to values between 2 and 255, but + * results are most noticeable in the lower ranges. + * + * Adapted from java based processing implementation + * + * @param {Canvas} canvas + * @param {Integer} level + */ +Filters.posterize = function (canvas, level) { + var pixels = Filters._toPixels(canvas); + + if ((level < 2) || (level > 255)) { + throw new Error( + 'Level must be greater than 2 and less than 255 for posterize' + ); + } + + var levels1 = level - 1; + for (var i = 0; i < pixels.length; i+=4) { + var rlevel = pixels[i]; + var glevel = pixels[i + 1]; + var blevel = pixels[i + 2]; + + pixels[i] = (((rlevel * level) >> 8) * 255) / levels1; + pixels[i + 1] = (((glevel * level) >> 8) * 255) / levels1; + pixels[i + 2] = (((blevel * level) >> 8) * 255) / levels1; + } +}; + +/** + * reduces the bright areas in an image + * @param {Canvas} canvas + * + */ +Filters.dilate = function (canvas) { + var pixels = Filters._toPixels(canvas); + var currIdx = 0; + var maxIdx = pixels.length ? pixels.length/4 : 0; + var out = new Int32Array(maxIdx); + var currRowIdx, maxRowIdx, colOrig, colOut, currLum; + var idxRight, idxLeft, idxUp, idxDown, + colRight, colLeft, colUp, colDown, + lumRight, lumLeft, lumUp, lumDown; + + while(currIdx < maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + canvas.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = Filters._getARGB(pixels, currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - canvas.width; + idxDown = currIdx + canvas.width; + + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0){ + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = Filters._getARGB(pixels, idxUp); + colLeft = Filters._getARGB(pixels, idxLeft); + colDown = Filters._getARGB(pixels, idxDown); + colRight = Filters._getARGB(pixels, idxRight); + + //compute luminance + currLum = 77*(colOrig>>16&0xff) + + 151*(colOrig>>8&0xff) + + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + + 151*(colLeft>>8&0xff) + + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + + 151*(colRight>>8&0xff) + + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + + 151*(colUp>>8&0xff) + + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + + 151*(colDown>>8&0xff) + + 28*(colDown&0xff); + + if (lumLeft > currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight > currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp > currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown > currLum) { + colOut = colDown; + currLum = lumDown; + } + out[currIdx++]=colOut; + } + } + Filters._setPixels(pixels, out); +}; + +/** + * increases the bright areas in an image + * @param {Canvas} canvas + * + */ +Filters.erode = function(canvas) { + var pixels = Filters._toPixels(canvas); + var currIdx = 0; + var maxIdx = pixels.length ? pixels.length/4 : 0; + var out = new Int32Array(maxIdx); + var currRowIdx, maxRowIdx, colOrig, colOut, currLum; + var idxRight, idxLeft, idxUp, idxDown, + colRight, colLeft, colUp, colDown, + lumRight, lumLeft, lumUp, lumDown; + + while(currIdx < maxIdx) { + currRowIdx = currIdx; + maxRowIdx = currIdx + canvas.width; + while (currIdx < maxRowIdx) { + colOrig = colOut = Filters._getARGB(pixels, currIdx); + idxLeft = currIdx - 1; + idxRight = currIdx + 1; + idxUp = currIdx - canvas.width; + idxDown = currIdx + canvas.width; + + if (idxLeft < currRowIdx) { + idxLeft = currIdx; + } + if (idxRight >= maxRowIdx) { + idxRight = currIdx; + } + if (idxUp < 0) { + idxUp = 0; + } + if (idxDown >= maxIdx) { + idxDown = currIdx; + } + colUp = Filters._getARGB(pixels, idxUp); + colLeft = Filters._getARGB(pixels, idxLeft); + colDown = Filters._getARGB(pixels, idxDown); + colRight = Filters._getARGB(pixels, idxRight); + + //compute luminance + currLum = 77*(colOrig>>16&0xff) + + 151*(colOrig>>8&0xff) + + 28*(colOrig&0xff); + lumLeft = 77*(colLeft>>16&0xff) + + 151*(colLeft>>8&0xff) + + 28*(colLeft&0xff); + lumRight = 77*(colRight>>16&0xff) + + 151*(colRight>>8&0xff) + + 28*(colRight&0xff); + lumUp = 77*(colUp>>16&0xff) + + 151*(colUp>>8&0xff) + + 28*(colUp&0xff); + lumDown = 77*(colDown>>16&0xff) + + 151*(colDown>>8&0xff) + + 28*(colDown&0xff); + + if (lumLeft < currLum) { + colOut = colLeft; + currLum = lumLeft; + } + if (lumRight < currLum) { + colOut = colRight; + currLum = lumRight; + } + if (lumUp < currLum) { + colOut = colUp; + currLum = lumUp; + } + if (lumDown < currLum) { + colOut = colDown; + currLum = lumDown; + } + + out[currIdx++]=colOut; + } + } + Filters._setPixels(pixels, out); +}; + +// BLUR + +// internal kernel stuff for the gaussian blur filter +var blurRadius; +var blurKernelSize; +var blurKernel; +var blurMult; + +/* + * Port of https://github.com/processing/processing/blob/ + * master/core/src/processing/core/PImage.java#L1250 + * + * Optimized code for building the blur kernel. + * further optimized blur code (approx. 15% for radius=20) + * bigger speed gains for larger radii (~30%) + * added support for various image types (ALPHA, RGB, ARGB) + * [toxi 050728] + */ +function buildBlurKernel(r) { + var radius = (r * 3.5)|0; + radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); + + if (blurRadius !== radius) { + blurRadius = radius; + blurKernelSize = 1 + blurRadius<<1; + blurKernel = new Int32Array(blurKernelSize); + blurMult = new Array(blurKernelSize); + for(var l = 0; l < blurKernelSize; l++){ + blurMult[l] = new Int32Array(256); + } + + var bk,bki; + var bm,bmi; + + for (var i = 1, radiusi = radius - 1; i < radius; i++) { + blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi; + bm = blurMult[radius+i]; + bmi = blurMult[radiusi--]; + for (var j = 0; j < 256; j++){ + bm[j] = bmi[j] = bki * j; + } + } + bk = blurKernel[radius] = radius * radius; + bm = blurMult[radius]; + + for (var k = 0; k < 256; k++){ + bm[k] = bk * k; + } + } + +} + +// Port of https://github.com/processing/processing/blob/ +// master/core/src/processing/core/PImage.java#L1433 +function blurARGB(canvas, radius) { + var pixels = Filters._toPixels(canvas); + var width = canvas.width; + var height = canvas.height; + var numPackedPixels = width * height; + var argb = new Int32Array(numPackedPixels); + for (var j = 0; j < numPackedPixels; j++) { + argb[j] = Filters._getARGB(pixels, j); + } + var sum, cr, cg, cb, ca; + var read, ri, ym, ymi, bk0; + var a2 = new Int32Array(numPackedPixels); + var r2 = new Int32Array(numPackedPixels); + var g2 = new Int32Array(numPackedPixels); + var b2 = new Int32Array(numPackedPixels); + var yi = 0; + buildBlurKernel(radius); + var x, y, i; + var bm; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + read = x - blurRadius; + if (read < 0) { + bk0 = -read; + read = 0; + } else { + if (read >= width) { + break; + } + bk0 = 0; + } + for (i = bk0; i < blurKernelSize; i++) { + if (read >= width) { + break; + } + var c = argb[read + yi]; + bm = blurMult[i]; + ca += bm[(c & -16777216) >>> 24]; + cr += bm[(c & 16711680) >> 16]; + cg += bm[(c & 65280) >> 8]; + cb += bm[c & 255]; + sum += blurKernel[i]; + read++; + } + ri = yi + x; + a2[ri] = ca / sum; + r2[ri] = cr / sum; + g2[ri] = cg / sum; + b2[ri] = cb / sum; + } + yi += width; + } + yi = 0; + ym = -blurRadius; + ymi = ym * width; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + cb = cg = cr = ca = sum = 0; + if (ym < 0) { + bk0 = ri = -ym; + read = x; + } else { + if (ym >= height) { + break; + } + bk0 = 0; + ri = ym; + read = x + ymi; + } + for (i = bk0; i < blurKernelSize; i++) { + if (ri >= height) { + break; + } + bm = blurMult[i]; + ca += bm[a2[read]]; + cr += bm[r2[read]]; + cg += bm[g2[read]]; + cb += bm[b2[read]]; + sum += blurKernel[i]; + ri++; + read += width; + } + argb[x + yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum); + } + yi += width; + ymi += width; + ym++; + } + Filters._setPixels(pixels, argb); +} + +Filters.blur = function(canvas, radius){ + blurARGB(canvas, radius); +}; + + +module.exports = Filters; + +},{}],66:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Image + * @for p5 + * @requires core + */ + +/** + * This module defines the p5 methods for the p5.Image class + * for drawing images to the main display canvas. + */ +'use strict'; + + +var p5 = _dereq_('../core/core'); + +/* global frames:true */// This is not global, but JSHint is not aware that +// this module is implicitly enclosed with Browserify: this overrides the +// redefined-global error and permits using the name "frames" for the array +// of saved animation frames. +var frames = []; + + +/** + * Creates a new p5.Image (the datatype for storing images). This provides a + * fresh buffer of pixels to play with. Set the size of the buffer with the + * width and height parameters. + * <br><br> + * .pixels gives access to an array containing the values for all the pixels + * in the display window. + * These values are numbers. This array is the size (including an appropriate + * factor for the pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. See .pixels for + * more info. It may also be simpler to use set() or get(). + * <br><br> + * Before accessing the pixels of an image, the data must loaded with the + * loadPixels() function. After the array data has been modified, the + * updatePixels() function must be run to update the changes. + * + * @method createImage + * @param {Integer} width width in pixels + * @param {Integer} height height in pixels + * @return {p5.Image} the p5.Image object + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + * + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102, i % img.width * 2)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * image(img, 34, 34); + * </code> + * </div> + * + * <div> + * <code> + * var pink = color(255, 102, 204); + * img = createImage(66, 66); + * img.loadPixels(); + * var d = pixelDensity; + * var halfImage = 4 * (width * d) * (height/2 * d); + * for (var i = 0; i < halfImage; i+=4) { + * img.pixels[i] = red(pink); + * img.pixels[i+1] = green(pink); + * img.pixels[i+2] = blue(pink); + * img.pixels[i+3] = alpha(pink); + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + */ +p5.prototype.createImage = function(width, height) { + return new p5.Image(width, height); +}; + +/** + * Save the current canvas as an image. In Safari, this will open the + * image in the window and the user must provide their own + * filename on save-as. Other browsers will either save the + * file immediately, or prompt the user with a dialogue window. + * + * @method saveCanvas + * @param {[selectedCanvas]} canvas a variable representing a + * specific html5 canvas (optional) + * @param {[String]} filename + * @param {[String]} extension 'jpg' or 'png' + * @example + * <div class='norender'><code> + * function setup() { + * var c = createCanvas(100, 100); + * background(255, 0, 0); + * saveCanvas(c, 'myCanvas', 'jpg'); + * } + * </code></div> + * <div class='norender'><code> + * // note that this example has the same result as above + * // if no canvas is specified, defaults to main canvas + * function setup() { + * createCanvas(100, 100); + * background(255, 0, 0); + * saveCanvas('myCanvas', 'jpg'); + * } + * </code></div> + * <div class='norender'><code> + * // all of the following are valid + * saveCanvas(c, 'myCanvas', 'jpg'); + * saveCanvas(c, 'myCanvas'); + * saveCanvas(c); + * saveCanvas('myCanvas', 'png'); + * saveCanvas('myCanvas'); + * saveCanvas(); + * </code></div> + */ +p5.prototype.saveCanvas = function() { + + var cnv, filename, extension; + if (arguments.length === 3) { + cnv = arguments[0]; + filename = arguments[1]; + extension = arguments[2]; + } else if (arguments.length === 2) { + if (typeof arguments[0] === 'object') { + cnv = arguments[0]; + filename = arguments[1]; + } else { + filename = arguments[0]; + extension = arguments[1]; + } + } else if (arguments.length === 1) { + if (typeof arguments[0] === 'object') { + cnv = arguments[0]; + } else { + filename = arguments[0]; + } + } + + if (cnv instanceof p5.Element) { + cnv = cnv.elt; + } + if (!(cnv instanceof HTMLCanvasElement)) { + cnv = null; + } + + if (!extension) { + extension = p5.prototype._checkFileExtension(filename, extension)[1]; + if (extension === '') { + extension = 'png'; + } + } + + if (!cnv) { + if (this._curElement && this._curElement.elt) { + cnv = this._curElement.elt; + } + } + + if ( p5.prototype._isSafari() ) { + var aText = 'Hello, Safari user!\n'; + aText += 'Now capturing a screenshot...\n'; + aText += 'To save this image,\n'; + aText += 'go to File --> Save As.\n'; + alert(aText); + window.location.href = cnv.toDataURL(); + } else { + var mimeType; + if (typeof(extension) === 'undefined') { + extension = 'png'; + mimeType = 'image/png'; + } + else { + switch(extension){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = cnv.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + p5.prototype.downloadFile(imageData, filename, extension); + } +}; + +/** + * Capture a sequence of frames that can be used to create a movie. + * Accepts a callback. For example, you may wish to send the frames + * to a server where they can be stored or converted into a movie. + * If no callback is provided, the browser will attempt to download + * all of the images that have just been created. + * + * @method saveFrames + * @param {String} filename + * @param {String} extension 'jpg' or 'png' + * @param {Number} duration Duration in seconds to save the frames for. + * @param {Number} framerate Framerate to save the frames in. + * @param {Function} [callback] A callback function that will be executed + to handle the image data. This function + should accept an array as argument. The + array will contain the spcecified number of + frames of objects. Each object have three + properties: imageData - an + image/octet-stream, filename and extension. + */ +p5.prototype.saveFrames = function(fName, ext, _duration, _fps, callback) { + var duration = _duration || 3; + duration = p5.prototype.constrain(duration, 0, 15); + duration = duration * 1000; + var fps = _fps || 15; + fps = p5.prototype.constrain(fps, 0, 22); + var count = 0; + + var makeFrame = p5.prototype._makeFrame; + var cnv = this._curElement.elt; + var frameFactory = setInterval(function(){ + makeFrame(fName + count, ext, cnv); + count++; + },1000/fps); + + setTimeout(function(){ + clearInterval(frameFactory); + if (callback) { + callback(frames); + } + else { + for (var i = 0; i < frames.length; i++) { + var f = frames[i]; + p5.prototype.downloadFile(f.imageData, f.filename, f.ext); + } + } + frames = []; // clear frames + }, duration + 0.01); +}; + +p5.prototype._makeFrame = function(filename, extension, _cnv) { + var cnv; + if (this) { + cnv = this._curElement.elt; + } else { + cnv = _cnv; + } + var mimeType; + if (!extension) { + extension = 'png'; + mimeType = 'image/png'; + } + else { + switch(extension.toLowerCase()){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = cnv.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + var thisFrame = {}; + thisFrame.imageData = imageData; + thisFrame.filename = filename; + thisFrame.ext = extension; + frames.push(thisFrame); +}; + +module.exports = p5; + +},{"../core/core":48}],67:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Loading & Displaying + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); +var canvas = _dereq_('../core/canvas'); +var constants = _dereq_('../core/constants'); + +_dereq_('../core/error_helpers'); + +/** + * Loads an image from a path and creates a p5.Image from it. + * <br><br> + * The image may not be immediately available for rendering + * If you want to ensure that the image is ready before doing + * anything with it, place the loadImage() call in preload(). + * You may also supply a callback function to handle the image when it's ready. + * <br><br> + * The path to the image should be relative to the HTML file + * that links in your sketch. Loading an from a URL or other + * remote location may be blocked due to your browser's built-in + * security. + * + * @method loadImage + * @param {String} path Path of the image to be loaded + * @param {Function(p5.Image)} [successCallback] Function to be called once + * the image is loaded. Will be passed the + * p5.Image. + * @param {Function(Event)} [failureCallback] called with event error if + * the image fails to load. + * @return {p5.Image} the p5.Image object + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * // here we use a callback to display the image after loading + * loadImage("assets/laDefense.jpg", function(img) { + * image(img, 0, 0); + * }); + * } + * </code> + * </div> + */ +p5.prototype.loadImage = function(path, successCallback, failureCallback) { + var img = new Image(); + var pImg = new p5.Image(1, 1, this); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + img.onload = function() { + pImg.width = pImg.canvas.width = img.width; + pImg.height = pImg.canvas.height = img.height; + + // Draw the image into the backing canvas of the p5.Image + pImg.drawingContext.drawImage(img, 0, 0); + + if (typeof successCallback === 'function') { + successCallback(pImg); + } + if (decrementPreload && (successCallback !== decrementPreload)) { + decrementPreload(); + } + }; + img.onerror = function(e) { + p5._friendlyFileLoadError(0,img.src); + // don't get failure callback mixed up with decrementPreload + if ((typeof failureCallback === 'function') && + (failureCallback !== decrementPreload)) { + failureCallback(e); + } + }; + + //set crossOrigin in case image is served which CORS headers + //this will let us draw to canvas without tainting it. + //see https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image + // When using data-uris the file will be loaded locally + // so we don't need to worry about crossOrigin with base64 file types + if(path.indexOf('data:image/') !== 0) { + img.crossOrigin = 'Anonymous'; + } + + //start loading the image + img.src = path; + + return pImg; +}; + +/** + * Validates clipping params. Per drawImage spec sWidth and sHight cannot be + * negative or greater than image intrinsic width and height + * @private + * @param {Number} sVal + * @param {Number} iVal + * @returns {Number} + * @private + */ +function _sAssign(sVal, iVal) { + if (sVal > 0 && sVal < iVal) { + return sVal; + } + else { + return iVal; + } +} + +/** + * Draw an image to the main canvas of the p5js sketch + * + * @method image + * @param {p5.Image} img the image to display + * @param {Number} [sx=0] The X coordinate of the top left corner of the + * sub-rectangle of the source image to draw into + * the destination canvas. + * @param {Number} [sy=0] The Y coordinate of the top left corner of the + * sub-rectangle of the source image to draw into + * the destination canvas. + * @param {Number} [sWidth=img.width] The width of the sub-rectangle of the + * source image to draw into the destination + * canvas. + * @param {Number} [sHeight=img.height] The height of the sub-rectangle of the + * source image to draw into the + * destination context. + * @param {Number} [dx=0] The X coordinate in the destination canvas at + * which to place the top-left corner of the + * source image. + * @param {Number} [dy=0] The Y coordinate in the destination canvas at + * which to place the top-left corner of the + * source image. + * @param {Number} [dWidth] The width to draw the image in the destination + * canvas. This allows scaling of the drawn image. + * @param {Number} [dHeight] The height to draw the image in the destination + * canvas. This allows scaling of the drawn image. + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * image(img, 0, 0, 100, 100); + * image(img, 0, 0, 100, 100, 0, 0, 100, 100); + * } + * </code> + * </div> + * <div> + * <code> + * function setup() { + * // here we use a callback to display the image after loading + * loadImage("assets/laDefense.jpg", function(img) { + * image(img, 0, 0); + * }); + * } + * </code> + * </div> + */ +p5.prototype.image = + function(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) { + // Temporarily disabling until options for p5.Graphics are added. + // var args = new Array(arguments.length); + // for (var i = 0; i < args.length; ++i) { + // args[i] = arguments[i]; + // } + // this._validateParameters( + // 'image', + // args, + // [ + // ['p5.Image', 'Number', 'Number'], + // ['p5.Image', 'Number', 'Number', 'Number', 'Number'] + // ] + // ); + + // set defaults per spec: https://goo.gl/3ykfOq + if (arguments.length <= 5) { + dx = sx || 0; + dy = sy || 0; + sx = 0; + sy = 0; + if (img.elt && img.elt.videoWidth && !img.canvas) { // video no canvas + var actualW = img.elt.videoWidth; + var actualH = img.elt.videoHeight; + dWidth = sWidth || img.elt.width; + dHeight = sHeight || img.elt.width*actualH/actualW; + sWidth = actualW; + sHeight = actualH; + } else { + dWidth = sWidth || img.width; + dHeight = sHeight || img.height; + sWidth = img.width; + sHeight = img.height; + } + } else if (arguments.length === 9) { + sx = sx || 0; + sy = sy || 0; + sWidth = _sAssign(sWidth, img.width); + sHeight = _sAssign(sHeight, img.height); + + dx = dx || 0; + dy = dy || 0; + dWidth = dWidth || img.width; + dHeight = dHeight || img.height; + } else { + throw 'Wrong number of arguments to image()'; + } + + var vals = canvas.modeAdjust(dx, dy, dWidth, dHeight, + this._renderer._imageMode); + + // tint the image if there is a tint + this._renderer.image(img, sx, sy, sWidth, sHeight, vals.x, vals.y, vals.w, + vals.h); +}; + +/** + * Sets the fill value for displaying images. Images can be tinted to + * specified colors or made transparent by including an alpha value. + * <br><br> + * To apply transparency to an image without affecting its color, use + * white as the tint color and specify an alpha value. For instance, + * tint(255, 128) will make an image 50% transparent (assuming the default + * alpha range of 0-255, which can be changed with colorMode()). + * <br><br> + * The value for the gray parameter must be less than or equal to the current + * maximum value as specified by colorMode(). The default maximum value is + * 255. + * + * @method tint + * @param {Number|Array} v1 gray value, red or hue value (depending on the + * current color mode), or color Array + * @param {Number|Array} [v2] green or saturation value (depending on the + * current color mode) + * @param {Number|Array} [v3] blue or brightness value (depending on the + * current color mode) + * @param {Number|Array} [a] opacity of the background + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(0, 153, 204); // Tint blue + * image(img, 50, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(0, 153, 204, 126); // Tint blue and set transparency + * image(img, 50, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/laDefense.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * tint(255, 126); // Apply transparency without changing color + * image(img, 50, 0); + * } + * </code> + * </div> + */ +p5.prototype.tint = function () { + var c = this.color.apply(this, arguments); + this._renderer._tint = c.levels; +}; + +/** + * Removes the current fill value for displaying images and reverts to + * displaying images with their original hues. + * + * @method noTint + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * tint(0, 153, 204); // Tint blue + * image(img, 0, 0); + * noTint(); // Disable tint + * image(img, 50, 0); + * } + * </code> + * </div> + */ +p5.prototype.noTint = function() { + this._renderer._tint = null; +}; + +/** + * Apply the current tint color to the input image, return the resulting + * canvas. + * + * @param {p5.Image} The image to be tinted + * @return {canvas} The resulting tinted canvas + * + */ +p5.prototype._getTintedImageCanvas = function(img) { + if (!img.canvas) { + return img; + } + var pixels = Filters._toPixels(img.canvas); + var tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = img.canvas.width; + tmpCanvas.height = img.canvas.height; + var tmpCtx = tmpCanvas.getContext('2d'); + var id = tmpCtx.createImageData(img.canvas.width, img.canvas.height); + var newPixels = id.data; + + for(var i = 0; i < pixels.length; i += 4) { + var r = pixels[i]; + var g = pixels[i+1]; + var b = pixels[i+2]; + var a = pixels[i+3]; + + newPixels[i] = r*this._renderer._tint[0]/255; + newPixels[i+1] = g*this._renderer._tint[1]/255; + newPixels[i+2] = b*this._renderer._tint[2]/255; + newPixels[i+3] = a*this._renderer._tint[3]/255; + } + + tmpCtx.putImageData(id, 0, 0); + return tmpCanvas; +}; + +/** + * Set image mode. Modifies the location from which images are drawn by + * changing the way in which parameters given to image() are interpreted. + * The default mode is imageMode(CORNER), which interprets the second and + * third parameters of image() as the upper-left corner of the image. If + * two additional parameters are specified, they are used to set the image's + * width and height. + * <br><br> + * imageMode(CORNERS) interprets the second and third parameters of image() + * as the location of one corner, and the fourth and fifth parameters as the + * opposite corner. + * <br><br> + * imageMode(CENTER) interprets the second and third parameters of image() + * as the image's center point. If two additional parameters are specified, + * they are used to set the image's width and height. + * + * @method imageMode + * @param {String} m The mode: either CORNER, CORNERS, or CENTER. + * @example + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CORNER); + * image(img, 10, 10, 50, 50); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CORNERS); + * image(img, 10, 10, 90, 40); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * imageMode(CENTER); + * image(img, 50, 50, 80, 80); + * } + * </code> + * </div> + */ +p5.prototype.imageMode = function(m) { + if (m === constants.CORNER || + m === constants.CORNERS || + m === constants.CENTER) { + this._renderer._imageMode = m; + } +}; + + +module.exports = p5; + +},{"../core/canvas":46,"../core/constants":47,"../core/core":48,"../core/error_helpers":51,"./filters":65}],68:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Image + * @requires core + * @requires constants + * @requires filters + */ + +/** + * This module defines the p5.Image class and P5 methods for + * drawing images to the main display canvas. + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); + +/* + * Class methods + */ + +/** + * Creates a new p5.Image. A p5.Image is a canvas backed representation of an + * image. + * <br><br> + * p5 can display .gif, .jpg and .png images. Images may be displayed + * in 2D and 3D space. Before an image is used, it must be loaded with the + * loadImage() function. The p5.Image class contains fields for the width and + * height of the image, as well as an array called pixels[] that contains the + * values for every pixel in the image. + * <br><br> + * The methods described below allow easy access to the image's pixels and + * alpha channel and simplify the process of compositing. + * <br><br> + * Before using the pixels[] array, be sure to use the loadPixels() method on + * the image to make sure that the pixel data is properly loaded. + * + * @class p5.Image + * @constructor + * @param {Number} width + * @param {Number} height + * @param {Object} pInst An instance of a p5 sketch. + */ +p5.Image = function(width, height){ + /** + * Image width. + * @property width + * @example + * <div><code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * createCanvas(100, 100); + * image(img, 0, 0); + * for (var i=0; i < img.width; i++) { + * var c = img.get(i, img.height/2); + * stroke(c); + * line(i, height/2, i, height); + * } + * } + * </code></div> + */ + this.width = width; + /** + * Image height. + * @property height + * @example + * <div><code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * createCanvas(100, 100); + * image(img, 0, 0); + * for (var i=0; i < img.height; i++) { + * var c = img.get(img.width/2, i); + * stroke(c); + * line(0, i, width/2, i); + * } + * } + * </code></div> + */ + this.height = height; + this.canvas = document.createElement('canvas'); + this.canvas.width = this.width; + this.canvas.height = this.height; + this.drawingContext = this.canvas.getContext('2d'); + this._pixelDensity = 1; + //used for webgl texturing only + this.isTexture = false; + /** + * Array containing the values for all the pixels in the display window. + * These values are numbers. This array is the size (include an appropriate + * factor for pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. Retina and other + * high denisty displays may have more pixels[] (by a factor of + * pixelDensity^2). + * For example, if the image is 100x100 pixels, there will be 40,000. With + * pixelDensity = 2, there will be 160,000. The first four values + * (indices 0-3) in the array will be the R, G, B, A values of the pixel at + * (0, 0). The second four values (indices 4-7) will contain the R, G, B, A + * values of the pixel at (1, 0). More generally, to set values for a pixel + * at (x, y): + * <code><pre>var d = pixelDensity; + * for (var i = 0; i < d; i++) { + * for (var j = 0; j < d; j++) { + * // loop over + * idx = 4*((y * d + j) * width * d + (x * d + i)); + * pixels[idx] = r; + * pixels[idx+1] = g; + * pixels[idx+2] = b; + * pixels[idx+3] = a; + * } + * } + * </pre></code> + * <br><br> + * Before accessing this array, the data must loaded with the loadPixels() + * function. After the array data has been modified, the updatePixels() + * function must be run to update the changes. + * @property pixels[] + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + * <div> + * <code> + * var pink = color(255, 102, 204); + * img = createImage(66, 66); + * img.loadPixels(); + * for (var i = 0; i < 4*(width*height/2); i+=4) { + * img.pixels[i] = red(pink); + * img.pixels[i+1] = green(pink); + * img.pixels[i+2] = blue(pink); + * img.pixels[i+3] = alpha(pink); + * } + * img.updatePixels(); + * image(img, 17, 17); + * </code> + * </div> + */ + this.pixels = []; +}; + +/** + * Helper fxn for sharing pixel methods + * + */ +p5.Image.prototype._setProperty = function (prop, value) { + this[prop] = value; +}; + +/** + * Loads the pixels data for this image into the [pixels] attribute. + * + * @method loadPixels + * @example + * <div><code> + * var myImage; + * var halfImage; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * myImage.loadPixels(); + * halfImage = 4 * width * height/2; + * for(var i = 0; i < halfImage; i++){ + * myImage.pixels[i+halfImage] = myImage.pixels[i]; + * } + * myImage.updatePixels(); + * } + * + * function draw() { + * image(myImage, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.loadPixels = function(){ + p5.Renderer2D.prototype.loadPixels.call(this); +}; + +/** + * Updates the backing canvas for this image with the contents of + * the [pixels] array. + * + * @method updatePixels + * @param {Integer|undefined} x x-offset of the target update area for the + * underlying canvas + * @param {Integer|undefined} y y-offset of the target update area for the + * underlying canvas + * @param {Integer|undefined} w height of the target update area for the + * underlying canvas + * @param {Integer|undefined} h height of the target update area for the + * underlying canvas + * @example + * <div><code> + * var myImage; + * var halfImage; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * myImage.loadPixels(); + * halfImage = 4 * width * height/2; + * for(var i = 0; i < halfImage; i++){ + * myImage.pixels[i+halfImage] = myImage.pixels[i]; + * } + * myImage.updatePixels(); + * } + * + * function draw() { + * image(myImage, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.updatePixels = function(x, y, w, h){ + p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); +}; + +/** + * Get a region of pixels from an image. + * + * If no params are passed, those whole image is returned, + * if x and y are the only params passed a single pixel is extracted + * if all params are passed a rectangle region is extracted and a p5.Image + * is returned. + * + * Returns undefined if the region is outside the bounds of the image + * + * @method get + * @param {Number} [x] x-coordinate of the pixel + * @param {Number} [y] y-coordinate of the pixel + * @param {Number} [w] width + * @param {Number} [h] height + * @return {Array/Color | p5.Image} color of pixel at x,y in array format + * [R, G, B, A] or p5.Image + * @example + * <div><code> + * var myImage; + * var c; + * + * function preload() { + * myImage = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * background(myImage); + * noStroke(); + * c = myImage.get(60, 90); + * fill(c); + * rect(25, 25, 50, 50); + * } + * + * //get() returns color here + * </code></div> + */ +p5.Image.prototype.get = function(x, y, w, h){ + return p5.Renderer2D.prototype.get.call(this, x, y, w, h); +}; + +/** + * Set the color of a single pixel or write an image into + * this p5.Image. + * + * Note that for a large number of pixels this will + * be slower than directly manipulating the pixels array + * and then calling updatePixels(). + * + * @method set + * @param {Number} x x-coordinate of the pixel + * @param {Number} y y-coordinate of the pixel + * @param {Number|Array|Object} a grayscale value | pixel array | + * a p5.Color | image to copy + * @example + * <div> + * <code> + * img = createImage(66, 66); + * img.loadPixels(); + * for (i = 0; i < img.width; i++) { + * for (j = 0; j < img.height; j++) { + * img.set(i, j, color(0, 90, 102, i % img.width * 2)); + * } + * } + * img.updatePixels(); + * image(img, 17, 17); + * image(img, 34, 34); + * </code> + * </div> + */ +p5.Image.prototype.set = function(x, y, imgOrCol){ + p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); +}; + +/** + * Resize the image to a new width and height. To make the image scale + * proportionally, use 0 as the value for the wide or high parameter. + * For instance, to make the width of an image 150 pixels, and change + * the height using the same proportion, use resize(150, 0). + * + * @method resize + * @param {Number} width the resized image width + * @param {Number} height the resized image height + * @example + * <div><code> + * var img; + * + * function setup() { + * img = loadImage("assets/rockies.jpg"); + * } + + * function draw() { + * image(img, 0, 0); + * } + * + * function mousePressed() { + * img.resize(50, 100); + * } + * </code></div> + */ +p5.Image.prototype.resize = function(width, height){ + + // Copy contents to a temporary canvas, resize the original + // and then copy back. + // + // There is a faster approach that involves just one copy and swapping the + // this.canvas reference. We could switch to that approach if (as i think + // is the case) there an expectation that the user would not hold a + // reference to the backing canvas of a p5.Image. But since we do not + // enforce that at the moment, I am leaving in the slower, but safer + // implementation. + + // auto-resize + if (width === 0 && height === 0) { + width = this.canvas.width; + height = this.canvas.height; + } else if (width === 0) { + width = this.canvas.width * height / this.canvas.height; + } else if (height === 0) { + height = this.canvas.height * width / this.canvas.width; + } + + var tempCanvas = document.createElement('canvas'); + tempCanvas.width = width; + tempCanvas.height = height; + tempCanvas.getContext('2d').drawImage(this.canvas, + 0, 0, this.canvas.width, this.canvas.height, + 0, 0, tempCanvas.width, tempCanvas.height + ); + + + // Resize the original canvas, which will clear its contents + this.canvas.width = this.width = width; + this.canvas.height = this.height = height; + + //Copy the image back + + this.drawingContext.drawImage(tempCanvas, + 0, 0, width, height, + 0, 0, width, height + ); + + if(this.pixels.length > 0){ + this.loadPixels(); + } +}; + +/** + * Copies a region of pixels from one image to another. If no + * srcImage is specified this is used as the source. If the source + * and destination regions aren't the same size, it will + * automatically resize source pixels to fit the specified + * target region. + * + * @method copy + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @example + * <div><code> + * var photo; + * var bricks; + * var x; + * var y; + * + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * bricks = loadImage("assets/bricks.jpg"); + * } + * + * function setup() { + * x = bricks.width/2; + * y = bricks.height/2; + * photo.copy(bricks, 0, 0, x, y, 0, 0, x, y); + * image(photo, 0, 0); + * } + * </code></div> + */ +p5.Image.prototype.copy = function () { + p5.prototype.copy.apply(this, arguments); +}; + +/** + * Masks part of an image from displaying by loading another + * image and using it's blue channel as an alpha channel for + * this image. + * + * @method mask + * @param {p5.Image} srcImage source image + * @example + * <div><code> + * var photo, maskImage; + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * maskImage = loadImage("assets/mask2.png"); + * } + * + * function setup() { + * createCanvas(100, 100); + * photo.mask(maskImage); + * image(photo, 0, 0); + * } + * </code></div> + * + * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ + * + */ +// TODO: - Accept an array of alpha values. +// - Use other channels of an image. p5 uses the +// blue channel (which feels kind of arbitrary). Note: at the +// moment this method does not match native processings original +// functionality exactly. +p5.Image.prototype.mask = function(p5Image) { + if(p5Image === undefined){ + p5Image = this; + } + var currBlend = this.drawingContext.globalCompositeOperation; + + var scaleFactor = 1; + if (p5Image instanceof p5.Renderer) { + scaleFactor = p5Image._pInst._pixelDensity; + } + + var copyArgs = [ + p5Image, + 0, + 0, + scaleFactor*p5Image.width, + scaleFactor*p5Image.height, + 0, + 0, + this.width, + this.height + ]; + + this.drawingContext.globalCompositeOperation = 'destination-in'; + this.copy.apply(this, copyArgs); + this.drawingContext.globalCompositeOperation = currBlend; +}; + +/** + * Applies an image filter to a p5.Image + * + * @method filter + * @param {String} operation one of threshold, gray, invert, posterize and + * opaque see Filters.js for docs on each available + * filter + * @param {Number|undefined} value + * @example + * <div><code> + * var photo1; + * var photo2; + * + * function preload() { + * photo1 = loadImage("assets/rockies.jpg"); + * photo2 = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * photo2.filter("gray"); + * image(photo1, 0, 0); + * image(photo2, width/2, 0); + * } + * </code></div> + */ +p5.Image.prototype.filter = function(operation, value) { + Filters.apply(this.canvas, Filters[operation.toLowerCase()], value); +}; + +/** + * Copies a region of pixels from one image to another, using a specified + * blend mode to do the operation. + * + * @method blend + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @param {Integer} blendMode the blend mode + * + * Available blend modes are: normal | multiply | screen | overlay | + * darken | lighten | color-dodge | color-burn | hard-light | + * soft-light | difference | exclusion | hue | saturation | + * color | luminosity + * + * + * http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ + * + */ +p5.Image.prototype.blend = function() { + p5.prototype.blend.apply(this, arguments); +}; + +/** + * Saves the image to a file and force the browser to download it. + * Accepts two strings for filename and file extension + * Supports png (default) and jpg. + * + * @method save + * @param {String} filename give your file a name + * @param {String} extension 'png' or 'jpg' + * @example + * <div><code> + * var photo; + * + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * } + * + * function draw() { + * image(photo, 0, 0); + * } + * + * function keyTyped() { + * if (key == 's') { + * photo.save("photo", "png"); + * } + * } + * </code></div> + */ +p5.Image.prototype.save = function(filename, extension) { + var mimeType; + if (!extension) { + extension = 'png'; + mimeType = 'image/png'; + } + else { + // en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support + switch(extension.toLowerCase()){ + case 'png': + mimeType = 'image/png'; + break; + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'jpg': + mimeType = 'image/jpeg'; + break; + default: + mimeType = 'image/png'; + break; + } + } + var downloadMime = 'image/octet-stream'; + var imageData = this.canvas.toDataURL(mimeType); + imageData = imageData.replace(mimeType, downloadMime); + + //Make the browser download the file + p5.prototype.downloadFile(imageData, filename, extension); +}; + +/** + * creates a gl texture + * used in WEBGL mode only + * @param {[type]} tex [description] + * @return {[type]} [description] + */ +p5.Image.prototype.createTexture = function(tex){ + //this.texture = tex; + return this; +}; + +module.exports = p5.Image; + +},{"../core/core":48,"./filters":65}],69:[function(_dereq_,module,exports){ +/** + * @module Image + * @submodule Pixels + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var Filters = _dereq_('./filters'); +_dereq_('../color/p5.Color'); + +/** + * <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference + * /Global_Objects/Uint8ClampedArray' target='_blank'>Uint8ClampedArray</a> + * containing the values for all the pixels in the display window. + * These values are numbers. This array is the size (include an appropriate + * factor for pixelDensity) of the display window x4, + * representing the R, G, B, A values in order for each pixel, moving from + * left to right across each row, then down each column. Retina and other + * high denisty displays will have more pixels[] (by a factor of + * pixelDensity^2). + * For example, if the image is 100x100 pixels, there will be 40,000. On a + * retina display, there will be 160,000. + * <br><br> + * The first four values (indices 0-3) in the array will be the R, G, B, A + * values of the pixel at (0, 0). The second four values (indices 4-7) will + * contain the R, G, B, A values of the pixel at (1, 0). More generally, to + * set values for a pixel at (x, y): + * <code><pre> + * var d = pixelDensity; + * for (var i = 0; i < d; i++) { + * for (var j = 0; j < d; j++) { + * // loop over + * idx = 4 * ((y * d + j) * width * d + (x * d + i)); + * pixels[idx] = r; + * pixels[idx+1] = g; + * pixels[idx+2] = b; + * pixels[idx+3] = a; + * } + * } + * </pre></code> + * + * <p>While the above method is complex, it is flexible enough to work with + * any pixelDensity. Note that set() will automatically take care of + * setting all the appropriate values in pixels[] for a given (x, y) at + * any pixelDensity, but the performance may not be as fast when lots of + * modifications are made to the pixel array. + * <br><br> + * Before accessing this array, the data must loaded with the loadPixels() + * function. After the array data has been modified, the updatePixels() + * function must be run to update the changes. + * <br><br> + * Note that this is not a standard javascript array. This means that + * standard javascript functions such as <code>slice()</code> or + * <code>arrayCopy()</code> do not + * work.</p> + * + * @property pixels[] + * @example + * <div> + * <code> + * var pink = color(255, 102, 204); + * loadPixels(); + * var d = pixelDensity(); + * var halfImage = 4 * (width * d) * (height/2 * d); + * for (var i = 0; i < halfImage; i+=4) { + * pixels[i] = red(pink); + * pixels[i+1] = green(pink); + * pixels[i+2] = blue(pink); + * pixels[i+3] = alpha(pink); + * } + * updatePixels(); + * </code> + * </div> + */ +p5.prototype.pixels = []; + +/** + * Copies a region of pixels from one image to another, using a specified + * blend mode to do the operation.<br><br> + * Available blend modes are: BLEND | DARKEST | LIGHTEST | DIFFERENCE | + * MULTIPLY| EXCLUSION | SCREEN | REPLACE | OVERLAY | HARD_LIGHT | + * SOFT_LIGHT | DODGE | BURN | ADD | NORMAL + * + * + * @method blend + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * @param {Integer} blendMode the blend mode + * + * @example + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST); + * } + * </code></div> + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, DARKEST); + * } + * </code></div> + * <div><code> + * var img0; + * var img1; + * + * function preload() { + * img0 = loadImage("assets/rockies.jpg"); + * img1 = loadImage("assets/bricks_third.jpg"); + * } + * + * function setup() { + * background(img0); + * image(img1, 0, 0); + * blend(img1, 0, 0, 33, 100, 67, 0, 33, 100, ADD); + * } + * </code></div> + */ +p5.prototype.blend = function() { + this._renderer.blend.apply(this._renderer, arguments); +}; + +/** + * Copies a region of the canvas to another region of the canvas + * and copies a region of pixels from an image used as the srcImg parameter + * into the canvas srcImage is specified this is used as the source. If + * the source and destination regions aren't the same size, it will + * automatically resize source pixels to fit the specified + * target region. + * + * @method copy + * @param {p5.Image|undefined} srcImage source image + * @param {Integer} sx X coordinate of the source's upper left corner + * @param {Integer} sy Y coordinate of the source's upper left corner + * @param {Integer} sw source image width + * @param {Integer} sh source image height + * @param {Integer} dx X coordinate of the destination's upper left corner + * @param {Integer} dy Y coordinate of the destination's upper left corner + * @param {Integer} dw destination image width + * @param {Integer} dh destination image height + * + * @example + * <div><code> + * var img; + * + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * background(img); + * copy(img, 7, 22, 10, 10, 35, 25, 50, 50); + * stroke(255); + * noFill(); + * // Rectangle shows area being copied + * rect(7, 22, 10, 10); + * } + * </code></div> + */ +p5.prototype.copy = function () { + p5.Renderer2D._copyHelper.apply(this, arguments); +}; + +/** + * Applies a filter to the canvas. + * <br><br> + * + * The presets options are: + * <br><br> + * + * THRESHOLD + * Converts the image to black and white pixels depending if they are above or + * below the threshold defined by the level parameter. The parameter must be + * between 0.0 (black) and 1.0 (white). If no level is specified, 0.5 is used. + * <br><br> + * + * GRAY + * Converts any colors in the image to grayscale equivalents. No parameter + * is used. + * <br><br> + * + * OPAQUE + * Sets the alpha channel to entirely opaque. No parameter is used. + * <br><br> + * + * INVERT + * Sets each pixel to its inverse value. No parameter is used. + * <br><br> + * + * POSTERIZE + * Limits each channel of the image to the number of colors specified as the + * parameter. The parameter can be set to values between 2 and 255, but + * results are most noticeable in the lower ranges. + * <br><br> + * + * BLUR + * Executes a Guassian blur with the level parameter specifying the extent + * of the blurring. If no parameter is used, the blur is equivalent to + * Guassian blur of radius 1. Larger values increase the blur. + * <br><br> + * + * ERODE + * Reduces the light areas. No parameter is used. + * <br><br> + * + * DILATE + * Increases the light areas. No parameter is used. + * + * @method filter + * @param {String} filterType + * @param {Number} filterParam an optional parameter unique + * to each filter, see above + * + * + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(THRESHOLD); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(GRAY); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(OPAQUE); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(INVERT); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(POSTERIZE,3); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(DILATE); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(BLUR,3); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/bricks.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * filter(ERODE); + * } + * </code> + * </div> + */ +p5.prototype.filter = function(operation, value) { + Filters.apply(this.canvas, Filters[operation.toLowerCase()], value); +}; + +/** + * Returns an array of [R,G,B,A] values for any pixel or grabs a section of + * an image. If no parameters are specified, the entire image is returned. + * Use the x and y parameters to get the value of one pixel. Get a section of + * the display window by specifying additional w and h parameters. When + * getting an image, the x and y parameters define the coordinates for the + * upper-left corner of the image, regardless of the current imageMode(). + * <br><br> + * If the pixel requested is outside of the image window, [0,0,0,255] is + * returned. To get the numbers scaled according to the current color ranges + * and taking into account colorMode, use getColor instead of get. + * <br><br> + * Getting the color of a single pixel with get(x, y) is easy, but not as fast + * as grabbing the data directly from pixels[]. The equivalent statement to + * get(x, y) using pixels[] with pixel density d is + * <code>[pixels[(y*width*d+x)*d], + * pixels[(y*width*d+x)*d+1], + * pixels[(y*width*d+x)*d+2], + * pixels[(y*width*d+x)*d+3]]</code>. + * <br><br> + * See the reference for pixels[] for more information. + * + * @method get + * @param {Number} [x] x-coordinate of the pixel + * @param {Number} [y] y-coordinate of the pixel + * @param {Number} [w] width + * @param {Number} [h] height + * @return {Array|p5.Image} values of pixel at x,y in array format + * [R, G, B, A] or p5.Image + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * var c = get(); + * image(c, width/2, 0); + * } + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * function setup() { + * image(img, 0, 0); + * var c = get(50, 90); + * fill(c); + * noStroke(); + * rect(25, 25, 50, 50); + * } + * </code> + * </div> + */ +p5.prototype.get = function(x, y, w, h){ + return this._renderer.get(x, y, w, h); +}; + +/** + * Loads the pixel data for the display window into the pixels[] array. This + * function must always be called before reading from or writing to pixels[]. + * + * @method loadPixels + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * image(img, 0, 0); + * var d = pixelDensity(); + * var halfImage = 4 * (img.width * d) * + (img.height/2 * d); + * loadPixels(); + * for (var i = 0; i < halfImage; i++) { + * pixels[i+halfImage] = pixels[i]; + * } + * updatePixels(); + * } + * </code> + * </div> + */ +p5.prototype.loadPixels = function() { + this._renderer.loadPixels(); +}; + +/** + * <p>Changes the color of any pixel, or writes an image directly to the + * display window.</p> + * <p>The x and y parameters specify the pixel to change and the c parameter + * specifies the color value. This can be a p5.Color object, or [R, G, B, A] + * pixel array. It can also be a single grayscale value. + * When setting an image, the x and y parameters define the coordinates for + * the upper-left corner of the image, regardless of the current imageMode(). + * </p> + * <p> + * After using set(), you must call updatePixels() for your changes to + * appear. This should be called once all pixels have been set. + * </p> + * <p>Setting the color of a single pixel with set(x, y) is easy, but not as + * fast as putting the data directly into pixels[]. Setting the pixels[] + * values directly may be complicated when working with a retina display, + * but will perform better when lots of pixels need to be set directly on + * every loop.</p> + * <p>See the reference for pixels[] for more information.</p> + * + * @method set + * @param {Number} x x-coordinate of the pixel + * @param {Number} y y-coordinate of the pixel + * @param {Number|Array|Object} c insert a grayscale value | a pixel array | + * a p5.Color object | a p5.Image to copy + * @example + * <div> + * <code> + * var black = color(0); + * set(30, 20, black); + * set(85, 20, black); + * set(85, 75, black); + * set(30, 75, black); + * updatePixels(); + * </code> + * </div> + * + * <div> + * <code> + * for (var i = 30; i < width-15; i++) { + * for (var j = 20; j < height-25; j++) { + * var c = color(204-j, 153-i, 0); + * set(i, j, c); + * } + * } + * updatePixels(); + * </code> + * </div> + * + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * set(0, 0, img); + * updatePixels(); + * line(0, 0, width, height); + * line(0, height, width, 0); + * } + * </code> + * </div> + */ +p5.prototype.set = function (x, y, imgOrCol) { + this._renderer.set(x, y, imgOrCol); +}; +/** + * Updates the display window with the data in the pixels[] array. + * Use in conjunction with loadPixels(). If you're only reading pixels from + * the array, there's no need to call updatePixels() — updating is only + * necessary to apply changes. updatePixels() should be called anytime the + * pixels array is manipulated or set() is called. + * + * @method updatePixels + * @param {Number} [x] x-coordinate of the upper-left corner of region + * to update + * @param {Number} [y] y-coordinate of the upper-left corner of region + * to update + * @param {Number} [w] width of region to update + * @param {Number} [w] height of region to update + * @example + * <div> + * <code> + * var img; + * function preload() { + * img = loadImage("assets/rockies.jpg"); + * } + * + * function setup() { + * image(img, 0, 0); + * var halfImage = 4 * (img.width * pixelDensity()) * + * (img.height * pixelDensity()/2); + * loadPixels(); + * for (var i = 0; i < halfImage; i++) { + * pixels[i+halfImage] = pixels[i]; + * } + * updatePixels(); + * } + * </code> + * </div> + */ +p5.prototype.updatePixels = function (x, y, w, h) { + // graceful fail - if loadPixels() or set() has not been called, pixel + // array will be empty, ignore call to updatePixels() + if (this.pixels.length === 0) { + return; + } + this._renderer.updatePixels(x, y, w, h); +}; + +module.exports = p5; + +},{"../color/p5.Color":42,"../core/core":48,"./filters":65}],70:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Input + * @for p5 + * @requires core + * @requires reqwest + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var reqwest = _dereq_('reqwest'); +var opentype = _dereq_('opentype.js'); +_dereq_('../core/error_helpers'); + +/** + * Checks if we are in preload and returns the last arg which will be the + * _decrementPreload function if called from a loadX() function. Should + * only be used in loadX() functions. + * @private + */ +p5._getDecrementPreload = function () { + var decrementPreload = arguments[arguments.length - 1]; + + // when in preload decrementPreload will always be the last arg as it is set + // with args.push() before invocation in _wrapPreload + if ((window.preload || (this && this.preload)) && + typeof decrementPreload === 'function') { + return decrementPreload; + } else { + return null; + } +}; + +/** + * Loads an opentype font file (.otf, .ttf) from a file or a URL, + * and returns a PFont Object. This method is asynchronous, + * meaning it may not finish before the next line in your sketch + * is executed. + * <br><br> + * The path to the font should be relative to the HTML file + * that links in your sketch. Loading an from a URL or other + * remote location may be blocked due to your browser's built-in + * security. + * + * @method loadFont + * @param {String} path name of the file or url to load + * @param {Function} [callback] function to be executed after + * loadFont() + * completes + * @return {Object} p5.Font object + * @example + * + * <p>Calling loadFont() inside preload() guarantees that the load + * operation will have completed before setup() and draw() are called.</p> + * + * <div><code> + * var myFont; + * function preload() { + * myFont = loadFont('assets/AvenirNextLTPro-Demi.otf'); + * } + * + * function setup() { + * fill('#ED225D'); + * textFont(myFont); + * textSize(36); + * text('p5*js', 10, 50); + * } + * </code></div> + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * + * <div><code> + * function setup() { + * loadFont('assets/AvenirNextLTPro-Demi.otf', drawText); + * } + * + * function drawText(font) { + * fill('#ED225D'); + * textFont(font, 36); + * text('p5*js', 10, 50); + * } + * + * </code></div> + * + * <p>You can also use the string name of the font to style other HTML + * elements.</p> + * + * <div><code> + * var myFont; + * + * function preload() { + * myFont = loadFont('assets/Avenir.otf'); + * } + * + * function setup() { + * var myDiv = createDiv('hello there'); + * myDiv.style('font-family', 'Avenir'); + * } + * </code></div> + */ +p5.prototype.loadFont = function (path, onSuccess, onError) { + + var p5Font = new p5.Font(this); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + opentype.load(path, function (err, font) { + + if (err) { + + if ((typeof onError !== 'undefined') && (onError !== decrementPreload)) { + return onError(err); + } + throw err; + } + + p5Font.font = font; + + if (typeof onSuccess !== 'undefined') { + onSuccess(p5Font); + } + + if (decrementPreload && (onSuccess !== decrementPreload)) { + decrementPreload(); + } + + // check that we have an acceptable font type + var validFontTypes = [ 'ttf', 'otf', 'woff', 'woff2' ], + fileNoPath = path.split('\\').pop().split('/').pop(), + lastDotIdx = fileNoPath.lastIndexOf('.'), fontFamily, newStyle, + fileExt = lastDotIdx < 1 ? null : fileNoPath.substr(lastDotIdx + 1); + + // if so, add it to the DOM (name-only) for use with p5.dom + if (validFontTypes.indexOf(fileExt) > -1) { + + fontFamily = fileNoPath.substr(0, lastDotIdx); + newStyle = document.createElement('style'); + newStyle.appendChild(document.createTextNode('\n@font-face {' + + '\nfont-family: ' + fontFamily + ';\nsrc: url(' + path + ');\n}\n')); + document.head.appendChild(newStyle); + } + + }); + + return p5Font; +}; + +//BufferedReader +p5.prototype.createInput = function () { + // TODO + throw 'not yet implemented'; +}; + +p5.prototype.createReader = function () { + // TODO + throw 'not yet implemented'; +}; + +p5.prototype.loadBytes = function () { + // TODO + throw 'not yet implemented'; +}; + +/** + * Loads a JSON file from a file or a URL, and returns an Object or Array. + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. + * + * @method loadJSON + * @param {String} path name of the file or url to load + * @param {Function} [callback] function to be executed after + * loadJSON() completes, data is passed + * in as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @param {String} [datatype] "json" or "jsonp" + * @return {Object|Array} JSON data + * @example + * + * <p>Calling loadJSON() inside preload() guarantees to complete the + * operation before setup() and draw() are called.</p> + * + * <div><code> + * var weather; + * function preload() { + * var url = 'http://api.openweathermap.org/data/2.5/weather?q=London,UK'+ + * '&APPID=7bbbb47522848e8b9c26ba35c226c734'; + * weather = loadJSON(url); + * } + * + * function setup() { + * noLoop(); + * } + * + * function draw() { + * background(200); + * // get the humidity value out of the loaded JSON + * var humidity = weather.main.humidity; + * fill(0, humidity); // use the humidity value to set the alpha + * ellipse(width/2, height/2, 50, 50); + * } + * </code></div> + * + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * <div><code> + * function setup() { + * noLoop(); + * var url = 'http://api.openweathermap.org/data/2.5/weather?q=NewYork'+ + * '&APPID=7bbbb47522848e8b9c26ba35c226c734'; + * loadJSON(url, drawWeather); + * } + * + * function draw() { + * background(200); + * } + * + * function drawWeather(weather) { + * // get the humidity value out of the loaded JSON + * var humidity = weather.main.humidity; + * fill(0, humidity); // use the humidity value to set the alpha + * ellipse(width/2, height/2, 50, 50); + * } + * </code></div> + * + */ +p5.prototype.loadJSON = function () { + var path = arguments[0]; + var callback = arguments[1]; + var errorCallback; + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + var ret = []; // array needed for preload + // assume jsonp for URLs + var t = 'json'; //= path.indexOf('http') === -1 ? 'json' : 'jsonp'; + + // check for explicit data type argument + for (var i = 2; i < arguments.length; i++) { + var arg = arguments[i]; + if (typeof arg === 'string') { + if (arg === 'jsonp' || arg === 'json') { + t = arg; + } + } else if (typeof arg === 'function') { + errorCallback = arg; + } + } + + reqwest({ + url: path, + type: t, + crossOrigin: true, + error: function (resp) { + // pass to error callback if defined + if (errorCallback) { + errorCallback(resp); + } else { // otherwise log error msg + console.log(resp.statusText); + } + }, + success: function (resp) { + for (var k in resp) { + ret[k] = resp[k]; + } + if (typeof callback !== 'undefined') { + callback(resp); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + } + }); + + return ret; +}; + +/** + * Reads the contents of a file and creates a String array of its individual + * lines. If the name of the file is used as the parameter, as in the above + * example, the file must be located in the sketch directory/folder. + * <br><br> + * Alternatively, the file maybe be loaded from anywhere on the local + * computer using an absolute path (something that starts with / on Unix and + * Linux, or a drive letter on Windows), or the filename parameter can be a + * URL for a file found on a network. + * <br><br> + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. + * + * @method loadStrings + * @param {String} filename name of the file or url to load + * @param {Function} [callback] function to be executed after loadStrings() + * completes, Array is passed in as first + * argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Array} Array of Strings + * @example + * + * <p>Calling loadStrings() inside preload() guarantees to complete the + * operation before setup() and draw() are called.</p> + * + * <div><code> + * var result; + * function preload() { + * result = loadStrings('assets/test.txt'); + * } + + * function setup() { + * background(200); + * var ind = floor(random(result.length)); + * text(result[ind], 10, 10, 80, 80); + * } + * </code></div> + * + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * + * <div><code> + * function setup() { + * loadStrings('assets/test.txt', pickString); + * } + * + * function pickString(result) { + * background(200); + * var ind = floor(random(result.length)); + * text(result[ind], 10, 10, 80, 80); + * } + * </code></div> + */ +p5.prototype.loadStrings = function (path, callback, errorCallback) { + var ret = []; + var req = new XMLHttpRequest(); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + req.addEventListener('error', function (resp) { + if (errorCallback) { + errorCallback(resp); + } else { + console.log(resp.responseText); + } + }); + + req.open('GET', path, true); + req.onreadystatechange = function () { + if (req.readyState === 4) { + if (req.status === 200) { + var arr = req.responseText.match(/[^\r\n]+/g); + for (var k in arr) { + ret[k] = arr[k]; + } + if (typeof callback !== 'undefined') { + callback(ret); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + } else { + if (errorCallback) { + errorCallback(req); + } else { + console.log(req.statusText); + } + //p5._friendlyFileLoadError(3, path); + } + } + }; + req.send(null); + return ret; +}; + +/** + * <p>Reads the contents of a file or URL and creates a p5.Table object with + * its values. If a file is specified, it must be located in the sketch's + * "data" folder. The filename parameter can also be a URL to a file found + * online. By default, the file is assumed to be comma-separated (in CSV + * format). Table only looks for a header row if the 'header' option is + * included.</p> + * + * <p>Possible options include: + * <ul> + * <li>csv - parse the table as comma-separated values</li> + * <li>tsv - parse the table as tab-separated values</li> + * <li>header - this table has a header (title) row</li> + * </ul> + * </p> + * + * <p>When passing in multiple options, pass them in as separate parameters, + * seperated by commas. For example: + * <br><br> + * <code> + * loadTable("my_csv_file.csv", "csv", "header") + * </code> + * </p> + * + * <p> All files loaded and saved use UTF-8 encoding.</p> + * + * <p>This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. Calling loadTable() inside preload() + * guarantees to complete the operation before setup() and draw() are called. + * <p>Outside of preload(), you may supply a callback function to handle the + * object:</p> + * </p> + * + * @method loadTable + * @param {String} filename name of the file or URL to load + * @param {String|Strings} [options] "header" "csv" "tsv" + * @param {Function} [callback] function to be executed after + * loadTable() completes, Table object is + * passed in as first argument + * @return {Object} Table object containing data + * + * @example + * <div class="norender"> + * <code> + * // Given the following CSV file called "mammals.csv" + * // located in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * //the file can be remote + * //table = loadTable("http://p5js.org/reference/assets/mammals.csv", + * // "csv", "header"); + * } + * + * function setup() { + * //count the columns + * print(table.getRowCount() + " total rows in table"); + * print(table.getColumnCount() + " total columns in table"); + * + * print(table.getColumn("name")); + * //["Goat", "Leopard", "Zebra"] + * + * //cycle through the table + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) { + * print(table.getString(r, c)); + * } + * } + * </code> + * </div> + */ +p5.prototype.loadTable = function (path) { + var callback = null; + var options = []; + var header = false; + var sep = ','; + var separatorSet = false; + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + for (var i = 1; i < arguments.length; i++) { + if ((typeof (arguments[i]) === 'function') && + (arguments[i] !== decrementPreload)) { + callback = arguments[i]; + } else if (typeof (arguments[i]) === 'string') { + options.push(arguments[i]); + if (arguments[i] === 'header') { + header = true; + } + if (arguments[i] === 'csv') { + if (separatorSet) { + throw new Error('Cannot set multiple separator types.'); + } else { + sep = ','; + separatorSet = true; + } + } else if (arguments[i] === 'tsv') { + if (separatorSet) { + throw new Error('Cannot set multiple separator types.'); + } else { + sep = '\t'; + separatorSet = true; + } + } + } + } + + var t = new p5.Table(); + reqwest({ + url: path, + crossOrigin: true, + type: 'csv' + }) + .then(function (resp) { + resp = resp.responseText; + + var state = {}; + + // define constants + var PRE_TOKEN = 0, + MID_TOKEN = 1, + POST_TOKEN = 2, + POST_RECORD = 4; + + var QUOTE = '\"', + CR = '\r', + LF = '\n'; + + var records = []; + var offset = 0; + var currentRecord = null; + var currentChar; + + var recordBegin = function () { + state.escaped = false; + currentRecord = []; + tokenBegin(); + }; + + var recordEnd = function () { + state.currentState = POST_RECORD; + records.push(currentRecord); + currentRecord = null; + }; + + var tokenBegin = function () { + state.currentState = PRE_TOKEN; + state.token = ''; + }; + + var tokenEnd = function () { + currentRecord.push(state.token); + tokenBegin(); + }; + + while (true) { + currentChar = resp[offset++]; + + // EOF + if (currentChar == null) { + if (state.escaped) { + throw new Error('Unclosed quote in file.'); + } + if (currentRecord) { + tokenEnd(); + recordEnd(); + break; + } + } + if (currentRecord === null) { + recordBegin(); + } + + // Handle opening quote + if (state.currentState === PRE_TOKEN) { + if (currentChar === QUOTE) { + state.escaped = true; + state.currentState = MID_TOKEN; + continue; + } + state.currentState = MID_TOKEN; + } + + // mid-token and escaped, look for sequences and end quote + if (state.currentState === MID_TOKEN && state.escaped) { + if (currentChar === QUOTE) { + if (resp[offset] === QUOTE) { + state.token += QUOTE; + offset++; + } else { + state.escaped = false; + state.currentState = POST_TOKEN; + } + } else { + state.token += currentChar; + } + continue; + } + + // fall-through: mid-token or post-token, not escaped + if (currentChar === CR) { + if (resp[offset] === LF) { + offset++; + } + tokenEnd(); + recordEnd(); + } else if (currentChar === LF) { + tokenEnd(); + recordEnd(); + } else if (currentChar === sep) { + tokenEnd(); + } else if (state.currentState === MID_TOKEN) { + state.token += currentChar; + } + } + + // set up column names + if (header) { + t.columns = records.shift(); + } else { + for (i = 0; i < records[0].length; i++) { + t.columns[i] = 'null'; + } + } + var row; + for (i = 0; i < records.length; i++) { + //Handles row of 'undefined' at end of some CSVs + if (i === records.length - 1 && records[i].length === 1) { + if (records[i][0] === 'undefined') { + break; + } + } + row = new p5.TableRow(); + row.arr = records[i]; + row.obj = makeObject(records[i], t.columns); + t.addRow(row); + } + if (callback !== null) { + callback(t); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + }) + .fail(function (err, msg) { + p5._friendlyFileLoadError(2, path); + // don't get error callback mixed up with decrementPreload + if ((typeof callback !== 'undefined') && + (callback !== decrementPreload)) { + callback(false); + } + }); + + return t; +}; + +// helper function to turn a row into a JSON object +function makeObject(row, headers) { + var ret = {}; + headers = headers || []; + if (typeof (headers) === 'undefined') { + for (var j = 0; j < row.length; j++) { + headers[j.toString()] = j; + } + } + for (var i = 0; i < headers.length; i++) { + var key = headers[i]; + var val = row[i]; + ret[key] = val; + } + return ret; +} + +/** + * Reads the contents of a file and creates an XML object with its values. + * If the name of the file is used as the parameter, as in the above example, + * the file must be located in the sketch directory/folder. + * <br><br> + * Alternatively, the file maybe be loaded from anywhere on the local + * computer using an absolute path (something that starts with / on Unix and + * Linux, or a drive letter on Windows), or the filename parameter can be a + * URL for a file found on a network. + * <br><br> + * This method is asynchronous, meaning it may not finish before the next + * line in your sketch is executed. Calling loadXML() inside preload() + * guarantees to complete the operation before setup() and draw() are called. + * <br><br> + * Outside of preload(), you may supply a callback function to handle the + * object: + * + * @method loadXML + * @param {String} filename name of the file or URL to load + * @param {Function} [callback] function to be executed after loadXML() + * completes, XML object is passed in as + * first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + * @return {Object} XML object containing data + */ +p5.prototype.loadXML = function (path, callback, errorCallback) { + var ret = document.implementation.createDocument(null, null); + var decrementPreload = p5._getDecrementPreload.apply(this, arguments); + + reqwest({ + url: path, + type: 'xml', + crossOrigin: true, + error: function (resp) { + // pass to error callback if defined + if (errorCallback) { + errorCallback(resp); + } else { // otherwise log error msg + console.log(resp.statusText); + } + //p5._friendlyFileLoadError(1,path); + } + }) + .then(function (resp) { + var x = resp.documentElement; + ret.appendChild(x); + if (typeof callback !== 'undefined') { + callback(ret); + } + if (decrementPreload && (callback !== decrementPreload)) { + decrementPreload(); + } + }); + return ret; +}; + +// name clash with window.open +// p5.prototype.open = function() { +// // TODO + +// }; + +p5.prototype.parseXML = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectFolder = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectInput = function () { + // TODO + throw 'not yet implemented'; + +}; + +/** + * Method for executing an HTTP GET request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. + * + * @method httpGet + * @param {String} path name of the file or url to load + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpGet = function () { + var args = Array.prototype.slice.call(arguments); + args.push('GET'); + p5.prototype.httpDo.apply(this, args); +}; + +/** + * Method for executing an HTTP POST request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text. + * + * @method httpPost + * @param {String} path name of the file or url to load + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpPost = function () { + var args = Array.prototype.slice.call(arguments); + args.push('POST'); + p5.prototype.httpDo.apply(this, args); +}; + +/** + * Method for executing an HTTP request. If data type is not specified, + * p5 will try to guess based on the URL, defaulting to text.<br><br> + * You may also pass a single object specifying all parameters for the + * request following the examples inside the reqwest() calls here: + * <a href='https://github.com/ded/reqwest#api' + * >https://github.com/ded/reqwest#api</a> + * + * @method httpDo + * @param {String} path name of the file or url to load + * @param {String} [method] either "GET", "POST", or "PUT", + * defaults to "GET" + * @param {Object} [data] param data passed sent with request + * @param {String} [datatype] "json", "jsonp", "xml", or "text" + * @param {Function} [callback] function to be executed after + * httpGet() completes, data is passed in + * as first argument + * @param {Function} [errorCallback] function to be executed if + * there is an error, response is passed + * in as first argument + */ +p5.prototype.httpDo = function () { + if (typeof arguments[0] === 'object') { + reqwest(arguments[0]); + } else { + var method = 'GET'; + var path = arguments[0]; + var data = {}; + var type = ''; + var callback; + var errorCallback; + + for (var i = 1; i < arguments.length; i++) { + var a = arguments[i]; + if (typeof a === 'string') { + if (a === 'GET' || a === 'POST' || a === 'PUT') { + method = a; + } else { + type = a; + } + } else if (typeof a === 'object') { + data = a; + } else if (typeof a === 'function') { + if (!callback) { + callback = a; + } else { + errorCallback = a; + } + } + } + + // do some sort of smart type checking + if (type === '') { + if (path.indexOf('json') !== -1) { + type = 'json'; + } else if (path.indexOf('xml') !== -1) { + type = 'xml'; + } else { + type = 'text'; + } + } + + reqwest({ + url: path, + method: method, + data: data, + type: type, + crossOrigin: true, + success: function (resp) { + if (typeof callback !== 'undefined') { + if (type === 'text') { + callback(resp.response); + } else { + callback(resp); + } + } + }, + error: function (resp) { + if (errorCallback) { + errorCallback(resp); + } else { + console.log(resp.statusText); + } + } + }); + } +}; + +/** + * @module IO + * @submodule Output + * @for p5 + */ + +window.URL = window.URL || window.webkitURL; + +// private array of p5.PrintWriter objects +p5.prototype._pWriters = []; + +p5.prototype.beginRaw = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.beginRecord = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.createOutput = function () { + // TODO + + throw 'not yet implemented'; +}; + +p5.prototype.createWriter = function (name, extension) { + var newPW; + // check that it doesn't already exist + for (var i in p5.prototype._pWriters) { + if (p5.prototype._pWriters[i].name === name) { + // if a p5.PrintWriter w/ this name already exists... + // return p5.prototype._pWriters[i]; // return it w/ contents intact. + // or, could return a new, empty one with a unique name: + newPW = new p5.PrintWriter(name + window.millis(), extension); + p5.prototype._pWriters.push(newPW); + return newPW; + } + } + newPW = new p5.PrintWriter(name, extension); + p5.prototype._pWriters.push(newPW); + return newPW; +}; + +p5.prototype.endRaw = function () { + // TODO + + throw 'not yet implemented'; +}; + +p5.prototype.endRecord = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.PrintWriter = function (filename, extension) { + var self = this; + this.name = filename; + this.content = ''; + this.print = function (data) { + this.content += data; + }; + this.println = function (data) { + this.content += data + '\n'; + }; + this.flush = function () { + this.content = ''; + }; + this.close = function () { + // convert String to Array for the writeFile Blob + var arr = []; + arr.push(this.content); + p5.prototype.writeFile(arr, filename, extension); + // remove from _pWriters array and delete self + for (var i in p5.prototype._pWriters) { + if (p5.prototype._pWriters[i].name === this.name) { + // remove from _pWriters array + p5.prototype._pWriters.splice(i, 1); + } + } + self.flush(); + self = {}; + }; +}; + +p5.prototype.saveBytes = function () { + // TODO + throw 'not yet implemented'; + +}; + +// object, filename, options --> saveJSON, saveStrings, saveTable +// filename, [extension] [canvas] --> saveImage + +/** + * <p>Save an image, text, json, csv, wav, or html. Prompts download to + * the client's computer. <b>Note that it is not recommended to call save() + * within draw if it's looping, as the save() function will open a new save + * dialog every frame.</b></p> + * <p>The default behavior is to save the canvas as an image. You can + * optionally specify a filename. + * For example:</p> + * <pre class='language-javascript'><code> + * save(); + * save('myCanvas.jpg'); // save a specific canvas with a filename + * </code></pre> + * + * <p>Alternately, the first parameter can be a pointer to a canvas + * p5.Element, an Array of Strings, + * an Array of JSON, a JSON object, a p5.Table, a p5.Image, or a + * p5.SoundFile (requires p5.sound). The second parameter is a filename + * (including extension). The third parameter is for options specific + * to this type of object. This method will save a file that fits the + * given paramaters. For example:</p> + * + * <pre class='language-javascript'><code> + * + * save('myCanvas.jpg'); // Saves canvas as an image + * + * var cnv = createCanvas(100, 100); + * save(cnv, 'myCanvas.jpg'); // Saves canvas as an image + * + * var gb = createGraphics(100, 100); + * save(gb, 'myGraphics.jpg'); // Saves p5.Renderer object as an image + * + * save(myTable, 'myTable.html'); // Saves table as html file + * save(myTable, 'myTable.csv',); // Comma Separated Values + * save(myTable, 'myTable.tsv'); // Tab Separated Values + * + * save(myJSON, 'my.json'); // Saves pretty JSON + * save(myJSON, 'my.json', true); // Optimizes JSON filesize + * + * save(img, 'my.png'); // Saves pImage as a png image + * + * save(arrayOfStrings, 'my.txt'); // Saves strings to a text file with line + * // breaks after each item in the array + * </code></pre> + * + * @method save + * @param {[Object|String]} objectOrFilename If filename is provided, will + * save canvas as an image with + * either png or jpg extension + * depending on the filename. + * If object is provided, will + * save depending on the object + * and filename (see examples + * above). + * @param {[String]} filename If an object is provided as the first + * parameter, then the second parameter + * indicates the filename, + * and should include an appropriate + * file extension (see examples above). + * @param {[Boolean/String]} options Additional options depend on + * filetype. For example, when saving JSON, + * <code>true</code> indicates that the + * output will be optimized for filesize, + * rather than readability. + */ +p5.prototype.save = function (object, _filename, _options) { + // parse the arguments and figure out which things we are saving + var args = arguments; + // ================================================= + // OPTION 1: saveCanvas... + + // if no arguments are provided, save canvas + var cnv = this._curElement.elt; + if (args.length === 0) { + p5.prototype.saveCanvas(cnv); + return; + } + // otherwise, parse the arguments + + // if first param is a p5Graphics, then saveCanvas + else if (args[0] instanceof p5.Renderer || + args[0] instanceof p5.Graphics) { + p5.prototype.saveCanvas(args[0].elt, args[1], args[2]); + return; + } + + // if 1st param is String and only one arg, assume it is canvas filename + else if (args.length === 1 && typeof (args[0]) === 'string') { + p5.prototype.saveCanvas(cnv, args[0]); + } + + // ================================================= + // OPTION 2: extension clarifies saveStrings vs. saveJSON + else { + var extension = _checkFileExtension(args[1], args[2])[1]; + switch (extension) { + case 'json': + p5.prototype.saveJSON(args[0], args[1], args[2]); + return; + case 'txt': + p5.prototype.saveStrings(args[0], args[1], args[2]); + return; + // ================================================= + // OPTION 3: decide based on object... + default: + if (args[0] instanceof Array) { + p5.prototype.saveStrings(args[0], args[1], args[2]); + } else if (args[0] instanceof p5.Table) { + p5.prototype.saveTable(args[0], args[1], args[2], args[3]); + } else if (args[0] instanceof p5.Image) { + p5.prototype.saveCanvas(args[0].canvas, args[1]); + } else if (args[0] instanceof p5.SoundFile) { + p5.prototype.saveSound(args[0], args[1], args[2], args[3]); + } + } + } +}; + +/** + * Writes the contents of an Array or a JSON object to a .json file. + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveJSON + * @param {Array|Object} json + * @param {String} filename + * @param {Boolean} [optimize] If true, removes line breaks + * and spaces from the output + * file to optimize filesize + * (but not readability). + * @example + * <div><code> + * var json; + * + * function setup() { + * + * json = {}; // new JSON Object + * + * json.id = 0; + * json.species = 'Panthera leo'; + * json.name = 'Lion'; + * + * // To save, un-comment the line below, then click 'run' + * // saveJSONObject(json, 'lion.json'); + * } + * + * // Saves the following to a file called "lion.json": + * // { + * // "id": 0, + * // "species": "Panthera leo", + * // "name": "Lion" + * // } + * </div></code> + */ +p5.prototype.saveJSON = function (json, filename, opt) { + var stringify; + if (opt) { + stringify = JSON.stringify(json); + } else { + stringify = JSON.stringify(json, undefined, 2); + } + console.log(stringify); + this.saveStrings(stringify.split('\n'), filename, 'json'); +}; + +p5.prototype.saveJSONObject = p5.prototype.saveJSON; +p5.prototype.saveJSONArray = p5.prototype.saveJSON; + +p5.prototype.saveStream = function () { + // TODO + throw 'not yet implemented'; + +}; + +/** + * Writes an array of Strings to a text file, one line per String. + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveStrings + * @param {Array} list string array to be written + * @param {String} filename filename for output + * @example + * <div><code> + * var words = 'apple bear cat dog'; + * + * // .split() outputs an Array + * var list = split(words, ' '); + * + * // To save the file, un-comment next line and click 'run' + * // saveStrings(list, 'nouns.txt'); + * + * // Saves the following to a file called 'nouns.txt': + * // + * // apple + * // bear + * // cat + * // dog + * </code></div> + */ +p5.prototype.saveStrings = function (list, filename, extension) { + var ext = extension || 'txt'; + var pWriter = this.createWriter(filename, ext); + for (var i = 0; i < list.length; i++) { + if (i < list.length - 1) { + pWriter.println(list[i]); + } else { + pWriter.print(list[i]); + } + } + pWriter.close(); + pWriter.flush(); +}; + +p5.prototype.saveXML = function () { + // TODO + throw 'not yet implemented'; + +}; + +p5.prototype.selectOutput = function () { + // TODO + throw 'not yet implemented'; + +}; + +// ======= +// HELPERS +// ======= + +function escapeHelper(content) { + return content + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +/** + * Writes the contents of a Table object to a file. Defaults to a + * text file with comma-separated-values ('csv') but can also + * use tab separation ('tsv'), or generate an HTML table ('html'). + * The file saving process and location of the saved file will + * vary between web browsers. + * + * @method saveTable + * @param {p5.Table} Table the Table object to save to a file + * @param {String} filename the filename to which the Table should be saved + * @param {String} [options] can be one of "tsv", "csv", or "html" + * @example + * <div><code> + * var table; + * + * function setup() { + * table = new p5.Table(); + * + * table.addColumn('id'); + * table.addColumn('species'); + * table.addColumn('name'); + * + * var newRow = table.addRow(); + * newRow.setNum('id', table.getRowCount() - 1); + * newRow.setString('species', 'Panthera leo'); + * newRow.setString('name', 'Lion'); + * + * // To save, un-comment next line then click 'run' + * // saveTable(table, 'new.csv'); + * } + * + * // Saves the following to a file called 'new.csv': + * // id,species,name + * // 0,Panthera leo,Lion + * </code></div> + */ +p5.prototype.saveTable = function (table, filename, options) { + var pWriter = this.createWriter(filename, options); + + var header = table.columns; + + var sep = ','; // default to CSV + if (options === 'tsv') { + sep = '\t'; + } + if (options !== 'html') { + // make header if it has values + if (header[0] !== '0') { + for (var h = 0; h < header.length; h++) { + if (h < header.length - 1) { + pWriter.print(header[h] + sep); + } else { + pWriter.println(header[h]); + } + } + } + + // make rows + for (var i = 0; i < table.rows.length; i++) { + var j; + for (j = 0; j < table.rows[i].arr.length; j++) { + if (j < table.rows[i].arr.length - 1) { + pWriter.print(table.rows[i].arr[j] + sep); + } else if (i < table.rows.length - 1) { + pWriter.println(table.rows[i].arr[j]); + } else { + pWriter.print(table.rows[i].arr[j]); // no line break + } + } + } + } + + // otherwise, make HTML + else { + pWriter.println('<html>'); + pWriter.println('<head>'); + var str = ' <meta http-equiv=\"content-type\" content'; + str += '=\"text/html;charset=utf-8\" />'; + pWriter.println(str); + pWriter.println('</head>'); + + pWriter.println('<body>'); + pWriter.println(' <table>'); + + // make header if it has values + if (header[0] !== '0') { + pWriter.println(' <tr>'); + for (var k = 0; k < header.length; k++) { + var e = escapeHelper(header[k]); + pWriter.println(' <td>' + e); + pWriter.println(' </td>'); + } + pWriter.println(' </tr>'); + } + + // make rows + for (var row = 0; row < table.rows.length; row++) { + pWriter.println(' <tr>'); + for (var col = 0; col < table.columns.length; col++) { + var entry = table.rows[row].getString(col); + var htmlEntry = escapeHelper(entry); + pWriter.println(' <td>' + htmlEntry); + pWriter.println(' </td>'); + } + pWriter.println(' </tr>'); + } + pWriter.println(' </table>'); + pWriter.println('</body>'); + pWriter.print('</html>'); + } + // close and flush the pWriter + pWriter.close(); + pWriter.flush(); +}; // end saveTable() + +/** + * Generate a blob of file data as a url to prepare for download. + * Accepts an array of data, a filename, and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @param {Array} dataToDownload + * @param {String} filename + * @param {[String]} extension + * @private + */ +p5.prototype.writeFile = function (dataToDownload, filename, extension) { + var type = 'application\/octet-stream'; + if (p5.prototype._isSafari()) { + type = 'text\/plain'; + } + var blob = new Blob(dataToDownload, { + 'type': type + }); + var href = window.URL.createObjectURL(blob); + p5.prototype.downloadFile(href, filename, extension); +}; + +/** + * Forces download. Accepts a url to filedata/blob, a filename, + * and an extension (optional). + * This is a private function because it does not do any formatting, + * but it is used by saveStrings, saveJSON, saveTable etc. + * + * @param {String} href i.e. an href generated by createObjectURL + * @param {[String]} filename + * @param {[String]} extension + */ +p5.prototype.downloadFile = function (href, fName, extension) { + var fx = _checkFileExtension(fName, extension); + var filename = fx[0]; + var ext = fx[1]; + + var a = document.createElement('a'); + a.href = href; + a.download = filename; + + // Firefox requires the link to be added to the DOM before click() + a.onclick = destroyClickedElement; + a.style.display = 'none'; + document.body.appendChild(a); + + // Safari will open this file in the same page as a confusing Blob. + if (p5.prototype._isSafari()) { + var aText = 'Hello, Safari user! To download this file...\n'; + aText += '1. Go to File --> Save As.\n'; + aText += '2. Choose "Page Source" as the Format.\n'; + aText += '3. Name it with this extension: .\"' + ext + '\"'; + alert(aText); + } + a.click(); + href = null; +}; + +/** + * Returns a file extension, or another string + * if the provided parameter has no extension. + * + * @param {String} filename + * @return {Array} [fileName, fileExtension] + * + * @private + */ +function _checkFileExtension(filename, extension) { + if (!extension || extension === true || extension === 'true') { + extension = ''; + } + if (!filename) { + filename = 'untitled'; + } + var ext = ''; + // make sure the file will have a name, see if filename needs extension + if (filename && filename.indexOf('.') > -1) { + ext = filename.split('.').pop(); + } + // append extension if it doesn't exist + if (extension) { + if (ext !== extension) { + ext = extension; + filename = filename + '.' + ext; + } + } + return [filename, ext]; +} +p5.prototype._checkFileExtension = _checkFileExtension; + +/** + * Returns true if the browser is Safari, false if not. + * Safari makes trouble for downloading files. + * + * @return {Boolean} [description] + * @private + */ +p5.prototype._isSafari = function () { + var x = Object.prototype.toString.call(window.HTMLElement); + return x.indexOf('Constructor') > 0; +}; + +/** + * Helper function, a callback for download that deletes + * an invisible anchor element from the DOM once the file + * has been automatically downloaded. + * + * @private + */ +function destroyClickedElement(event) { + document.body.removeChild(event.target); +} + +module.exports = p5; + +},{"../core/core":48,"../core/error_helpers":51,"opentype.js":8,"reqwest":27}],71:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Table + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + + +/** + * Table Options + * <p>Generic class for handling tabular data, typically from a + * CSV, TSV, or other sort of spreadsheet file.</p> + * <p>CSV files are + * <a href="http://en.wikipedia.org/wiki/Comma-separated_values"> + * comma separated values</a>, often with the data in quotes. TSV + * files use tabs as separators, and usually don't bother with the + * quotes.</p> + * <p>File names should end with .csv if they're comma separated.</p> + * <p>A rough "spec" for CSV can be found + * <a href="http://tools.ietf.org/html/rfc4180">here</a>.</p> + * <p>To load files, use the loadTable method.</p> + * <p>To save tables to your computer, use the save method + * or the saveTable method.</p> + * + * Possible options include: + * <ul> + * <li>csv - parse the table as comma-separated values + * <li>tsv - parse the table as tab-separated values + * <li>header - this table has a header (title) row + * </ul> + */ + +/** + * Table objects store data with multiple rows and columns, much + * like in a traditional spreadsheet. Tables can be generated from + * scratch, dynamically, or using data from an existing file. + * + * @class p5.Table + * @constructor + * @param {Array} [rows] An array of p5.TableRow objects + * @return {p5.Table} p5.Table generated + */ +p5.Table = function (rows) { + /** + * @property columns + * @type {Array} + */ + this.columns = []; + + /** + * @property rows + * @type {Array} + */ + this.rows = []; +}; + +/** + * Use addRow() to add a new row of data to a p5.Table object. By default, + * an empty row is created. Typically, you would store a reference to + * the new row in a TableRow object (see newRow in the example above), + * and then set individual values using set(). + * + * If a p5.TableRow object is included as a parameter, then that row is + * duplicated and added to the table. + * + * @method addRow + * @param {p5.TableRow} [row] row to be added to the table + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //add a row + * var newRow = table.addRow(); + * newRow.setString("id", table.getRowCount() - 1); + * newRow.setString("species", "Canis Lupus"); + * newRow.setString("name", "Wolf"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.addRow = function(row) { + // make sure it is a valid TableRow + var r = row || new p5.TableRow(); + + if (typeof(r.arr) === 'undefined' || typeof(r.obj) === 'undefined') { + //r = new p5.prototype.TableRow(r); + throw 'invalid TableRow: ' + r; + } + r.table = this; + this.rows.push(r); + return r; +}; + +/** + * Removes a row from the table object. + * + * @method removeRow + * @param {Number} id ID number of the row to remove + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //remove the first row + * var r = table.removeRow(0); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.removeRow = function(id) { + this.rows[id].table = null; // remove reference to table + var chunk = this.rows.splice(id+1, this.rows.length); + this.rows.pop(); + this.rows = this.rows.concat(chunk); +}; + + +/** + * Returns a reference to the specified p5.TableRow. The reference + * can then be used to get and set values of the selected row. + * + * @method getRow + * @param {Number} rowID ID number of the row to get + * @return {TableRow} p5.TableRow object + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var row = table.getRow(1); + * //print it column by column + * //note: a row is an object, not an array + * for (var c = 0; c < table.getColumnCount(); c++) + * print(row.getString(c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.getRow = function(r) { + return this.rows[r]; +}; + +/** + * Gets all rows from the table. Returns an array of p5.TableRows. + * + * @method getRows + * @return {Array} Array of p5.TableRows + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var rows = table.getRows(); + * + * //warning: rows is an array of objects + * for (var r = 0; r < rows.length; r++) + * rows[r].set("name", "Unicorn"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.getRows = function() { + return this.rows; +}; + +/** + * Finds the first row in the Table that contains the value + * provided, and returns a reference to that row. Even if + * multiple rows are possible matches, only the first matching + * row is returned. The column to search may be specified by + * either its ID or title. + * + * @method findRow + * @param {String} value The value to match + * @param {Number|String} column ID number or title of the + * column to search + * @return {TableRow} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //find the animal named zebra + * var row = table.findRow("Zebra", "name"); + * //find the corresponding species + * print(row.getString("species")); + * } + * </code> + * </div> + */ +p5.Table.prototype.findRow = function(value, column) { + // try the Object + if (typeof(column) === 'string') { + for (var i = 0; i < this.rows.length; i++){ + if (this.rows[i].obj[column] === value) { + return this.rows[i]; + } + } + } + // try the Array + else { + for (var j = 0; j < this.rows.length; j++){ + if (this.rows[j].arr[column] === value) { + return this.rows[j]; + } + } + } + // otherwise... + return null; +}; + +/** + * Finds the rows in the Table that contain the value + * provided, and returns references to those rows. Returns an + * Array, so for must be used to iterate through all the rows, + * as shown in the example above. The column to search may be + * specified by either its ID or title. + * + * @method findRows + * @param {String} value The value to match + * @param {Number|String} column ID number or title of the + * column to search + * @return {Array} An Array of TableRow objects + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //add another goat + * var newRow = table.addRow(); + * newRow.setString("id", table.getRowCount() - 1); + * newRow.setString("species", "Scape Goat"); + * newRow.setString("name", "Goat"); + * + * //find the rows containing animals named Goat + * var rows = table.findRows("Goat", "name"); + * print(rows.length + " Goats found"); + * } + * </code> + * </div> + */ +p5.Table.prototype.findRows = function(value, column) { + var ret = []; + if (typeof(column) === 'string') { + for (var i = 0; i < this.rows.length; i++){ + if (this.rows[i].obj[column] === value) { + ret.push( this.rows[i] ); + } + } + } + // try the Array + else { + for (var j = 0; j < this.rows.length; j++){ + if (this.rows[j].arr[column] === value) { + ret.push( this.rows[j] ); + } + } + } + return ret; +}; + +/** + * Finds the first row in the Table that matches the regular + * expression provided, and returns a reference to that row. + * Even if multiple rows are possible matches, only the first + * matching row is returned. The column to search may be + * specified by either its ID or title. + * + * @method matchRow + * @param {String} regexp The regular expression to match + * @param {String|Number} column The column ID (number) or + * title (string) + * @return {TableRow} TableRow object + */ +p5.Table.prototype.matchRow = function(regexp, column) { + if (typeof(column) === 'number') { + for (var j = 0; j < this.rows.length; j++) { + if ( this.rows[j].arr[column].match(regexp) ) { + return this.rows[j]; + } + } + } + + else { + for (var i = 0; i < this.rows.length; i++) { + if ( this.rows[i].obj[column].match(regexp) ) { + return this.rows[i]; + } + } + } + return null; +}; + +/** + * Finds the first row in the Table that matches the regular + * expression provided, and returns a reference to that row. + * Even if multiple rows are possible matches, only the first + * matching row is returned. The column to search may be specified + * by either its ID or title. + * + * @method matchRows + * @param {String} regexp The regular expression to match + * @param {String|Number} [column] The column ID (number) or + * title (string) + * @return {Array} An Array of TableRow objects + */ +p5.Table.prototype.matchRows = function(regexp, column) { + var ret = []; + if (typeof(column) === 'number') { + for (var j = 0; j < this.rows.length; j++) { + if ( this.rows[j].arr[column].match(regexp) ) { + ret.push( this.rows[j] ); + } + } + } + + else { + for (var i = 0; i < this.rows.length; i++) { + if ( this.rows[i].obj[column].match(regexp) ) { + ret.push( this.rows[i] ); + } + } + } + return ret; +}; + + +/** + * Retrieves all values in the specified column, and returns them + * as an array. The column may be specified by either its ID or title. + * + * @method getColumn + * @param {String|Number} column String or Number of the column to return + * @return {Array} Array of column values + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * //getColumn returns an array that can be printed directly + * print(table.getColumn("species")); + * //outputs ["Capra hircus", "Panthera pardus", "Equus zebra"] + * } + * </code> + * </div> + */ +p5.Table.prototype.getColumn = function(value) { + var ret = []; + if (typeof(value) === 'string'){ + for (var i = 0; i < this.rows.length; i++){ + ret.push (this.rows[i].obj[value]); + } + } else { + for (var j = 0; j < this.rows.length; j++){ + ret.push (this.rows[j].arr[value]); + } + } + return ret; +}; + +/** + * Removes all rows from a Table. While all rows are removed, + * columns and column titles are maintained. + * + * @method clearRows + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.clearRows(); + * print(table.getRowCount() + " total rows in table"); + * print(table.getColumnCount() + " total columns in table"); + * } + * </code> + * </div> + */ +p5.Table.prototype.clearRows = function() { + delete this.rows; + this.rows = []; +}; + +/** + * Use addColumn() to add a new column to a Table object. + * Typically, you will want to specify a title, so the column + * may be easily referenced later by name. (If no title is + * specified, the new column's title will be null.) + * + * @method addColumn + * @param {String} [title] title of the given column + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.addColumn("carnivore"); + * table.set(0, "carnivore", "no"); + * table.set(1, "carnivore", "yes"); + * table.set(2, "carnivore", "no"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.addColumn = function(title) { + var t = title || null; + this.columns.push(t); +}; + +/** + * Returns the total number of columns in a Table. + * + * @return {Number} Number of columns in this table + */ +p5.Table.prototype.getColumnCount = function() { + return this.columns.length; +}; + +/** + * Returns the total number of rows in a Table. + * + * @method getRowCount + * @return {Number} Number of rows in this table + + */ +p5.Table.prototype.getRowCount = function() { + return this.rows.length; +}; + +/** + * <p>Removes any of the specified characters (or "tokens").</p> + * + * <p>If no column is specified, then the values in all columns and + * rows are processed. A specific column may be referenced by + * either its ID or title.</p> + * + * @method removeTokens + * @param {String} chars String listing characters to be removed + * @param {String|Number} [column] Column ID (number) + * or name (string) + */ +p5.Table.prototype.removeTokens = function(chars, column) { + var escape= function(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + }; + var charArray = []; + for (var i = 0; i < chars.length; i++) { + charArray.push( escape( chars.charAt(i) ) ); + } + var regex = new RegExp(charArray.join('|'), 'g'); + + if (typeof(column) === 'undefined'){ + for (var c = 0; c < this.columns.length; c++) { + for (var d = 0; d < this.rows.length; d++) { + var s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } + else if (typeof(column) === 'string'){ + for (var j = 0; j < this.rows.length; j++) { + var val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + var pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } + else { + for (var k = 0; k < this.rows.length; k++) { + var str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; + } + } +}; + +/** + * Trims leading and trailing whitespace, such as spaces and tabs, + * from String table values. If no column is specified, then the + * values in all columns and rows are trimmed. A specific column + * may be referenced by either its ID or title. + * + * @method trim + * @param {String|Number} column Column ID (number) + * or name (string) + */ +p5.Table.prototype.trim = function(column) { + var regex = new RegExp( (' '), 'g'); + + if (typeof(column) === 'undefined'){ + for (var c = 0; c < this.columns.length; c++) { + for (var d = 0; d < this.rows.length; d++) { + var s = this.rows[d].arr[c]; + s = s.replace(regex, ''); + this.rows[d].arr[c] = s; + this.rows[d].obj[this.columns[c]] = s; + } + } + } + else if (typeof(column) === 'string'){ + for (var j = 0; j < this.rows.length; j++) { + var val = this.rows[j].obj[column]; + val = val.replace(regex, ''); + this.rows[j].obj[column] = val; + var pos = this.columns.indexOf(column); + this.rows[j].arr[pos] = val; + } + } + else { + for (var k = 0; k < this.rows.length; k++) { + var str = this.rows[k].arr[column]; + str = str.replace(regex, ''); + this.rows[k].arr[column] = str; + this.rows[k].obj[this.columns[column]] = str; + } + } +}; + +/** + * Use removeColumn() to remove an existing column from a Table + * object. The column to be removed may be identified by either + * its title (a String) or its index value (an int). + * removeColumn(0) would remove the first column, removeColumn(1) + * would remove the second column, and so on. + * + * @method removeColumn + * @param {String|Number} column columnName (string) or ID (number) + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.removeColumn("id"); + * print(table.getColumnCount()); + * } + * </code> + * </div> + */ +p5.Table.prototype.removeColumn = function(c) { + var cString; + var cNumber; + if (typeof(c) === 'string') { + // find the position of c in the columns + cString = c; + cNumber = this.columns.indexOf(c); + console.log('string'); + } + else{ + cNumber = c; + cString = this.columns[c]; + } + + var chunk = this.columns.splice(cNumber+1, this.columns.length); + this.columns.pop(); + this.columns = this.columns.concat(chunk); + + for (var i = 0; i < this.rows.length; i++){ + var tempR = this.rows[i].arr; + var chip = tempR.splice(cNumber+1, tempR.length); + tempR.pop(); + this.rows[i].arr = tempR.concat(chip); + delete this.rows[i].obj[cString]; + } + +}; + + +/** + * Stores a value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method set + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {String|Number} value value to assign + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.set(0, "species", "Canis Lupus"); + * table.set(0, "name", "Wolf"); + * + * //print the results + * for (var r = 0; r < table.getRowCount(); r++) + * for (var c = 0; c < table.getColumnCount(); c++) + * print(table.getString(r, c)); + * } + * </code> + * </div> + */ +p5.Table.prototype.set = function(row, column, value) { + this.rows[row].set(column, value); +}; + +/** + * Stores a Float value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method setNum + * @param {Number} row row ID + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {Number} value value to assign + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * table.setNum(1, "id", 1); + * + * print(table.getColumn(0)); + * //["0", 1, "2"] + * } + * </code> + * </div> + */ +p5.Table.prototype.setNum = function(row, column, value){ + this.rows[row].setNum(column, value); +}; + + +/** + * Stores a String value in the Table's specified row and column. + * The row is specified by its ID, while the column may be specified + * by either its ID or title. + * + * @method setString + * @param {Number} row row ID + * @param {String|Number} column column ID (Number) + * or title (String) + * @param {String} value value to assign + */ +p5.Table.prototype.setString = function(row, column, value){ + this.rows[row].setString(column, value); +}; + +/** + * Retrieves a value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method get + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String|Number} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * print(table.get(0, 1)); + * //Capra hircus + * print(table.get(0, "species")); + * //Capra hircus + * } + * </code> + * </div> + */ +p5.Table.prototype.get = function(row, column) { + return this.rows[row].get(column); +}; + +/** + * Retrieves a Float value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method getNum + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {Number} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * print(table.getNum(1, 0) + 100); + * //id 1 + 100 = 101 + * } + * </code> + * </div> + */ +p5.Table.prototype.getNum = function(row, column) { + return this.rows[row].getNum(column); +}; + +/** + * Retrieves a String value from the Table's specified row and column. + * The row is specified by its ID, while the column may be specified by + * either its ID or title. + * + * @method getString + * @param {Number} row row ID + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var tableArray = table.getArray(); + * + * //output each row as array + * for (var i = 0; i < tableArray.length; i++) + * print(tableArray[i]); + * } + * </code> + * </div> + */ +p5.Table.prototype.getString = function(row, column) { + return this.rows[row].getString(column); +}; + +/** + * Retrieves all table data and returns as an object. If a column name is + * passed in, each row object will be stored with that attribute as its + * title. + * + * @method getObject + * @param {String} headerColumn Name of the column which should be used to + * title each row object (optional) + * @return {Object} + * + * @example + * <div class="norender"> + * <code> + * // Given the CSV file "mammals.csv" + * // in the project's "assets" folder: + * // + * // id,species,name + * // 0,Capra hircus,Goat + * // 1,Panthera pardus,Leopard + * // 2,Equus zebra,Zebra + * + * var table; + * + * function preload() { + * //my table is comma separated value "csv" + * //and has a header specifying the columns labels + * table = loadTable("assets/mammals.csv", "csv", "header"); + * } + * + * function setup() { + * var tableObject = table.getObject(); + * + * print(tableObject); + * //outputs an object + * } + * </code> + * </div> + + */ +p5.Table.prototype.getObject = function (headerColumn) { + var tableObject = {}; + var obj, cPos, index; + + for(var i = 0; i < this.rows.length; i++) { + obj = this.rows[i].obj; + + if (typeof(headerColumn) === 'string'){ + cPos = this.columns.indexOf(headerColumn); // index of columnID + if (cPos >= 0) { + index = obj[headerColumn]; + tableObject[index] = obj; + } else { + throw 'This table has no column named "' + headerColumn +'"'; + } + } else { + tableObject[i] = this.rows[i].obj; + } + } + return tableObject; +}; + +/** + * Retrieves all table data and returns it as a multidimensional array. + * + * @method getArray + * @return {Array} + */ +p5.Table.prototype.getArray = function () { + var tableArray = []; + for(var i = 0; i < this.rows.length; i++) { + tableArray.push(this.rows[i].arr); + } + return tableArray; +}; + +module.exports = p5.Table; + +},{"../core/core":48}],72:[function(_dereq_,module,exports){ +/** + * @module IO + * @submodule Table + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * A TableRow object represents a single row of data values, + * stored in columns, from a table. + * + * A Table Row contains both an ordered array, and an unordered + * JSON object. + * + * @class p5.TableRow + * @constructor + * @param {String} [str] optional: populate the row with a + * string of values, separated by the + * separator + * @param {String} [separator] comma separated values (csv) by default + */ +p5.TableRow = function (str, separator) { + var arr = []; + var obj = {}; + if (str){ + separator = separator || ','; + arr = str.split(separator); + } + for (var i = 0; i < arr.length; i++){ + var key = i; + var val = arr[i]; + obj[key] = val; + } + this.arr = arr; + this.obj = obj; + this.table = null; +}; + +/** + * Stores a value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method set + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {String|Number} value The value to be stored + */ +p5.TableRow.prototype.set = function(column, value) { + // if typeof column is string, use .obj + if (typeof(column) === 'string'){ + var cPos = this.table.columns.indexOf(column); // index of columnID + if (cPos >= 0) { + this.obj[column] = value; + this.arr[cPos] = value; + } + else { + throw 'This table has no column named "' + column +'"'; + } + } + + // if typeof column is number, use .arr + else { + if (column < this.table.columns.length) { + this.arr[column] = value; + var cTitle = this.table.columns[column]; + this.obj[cTitle] = value; + } + else { + throw 'Column #' + column + ' is out of the range of this table'; + } + } +}; + + +/** + * Stores a Float value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setNum + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {Number} value The value to be stored + * as a Float + */ +p5.TableRow.prototype.setNum = function(column, value){ + var floatVal = parseFloat(value, 10); + this.set(column, floatVal); +}; + + +/** + * Stores a String value in the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method setString + * @param {String|Number} column Column ID (Number) + * or Title (String) + * @param {String} value The value to be stored + * as a String + */ +p5.TableRow.prototype.setString = function(column, value){ + var stringVal = value.toString(); + this.set(column, stringVal); +}; + +/** + * Retrieves a value from the TableRow's specified column. + * The column may be specified by either its ID or title. + * + * @method get + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String|Number} + */ +p5.TableRow.prototype.get = function(column) { + if (typeof(column) === 'string'){ + return this.obj[column]; + } else { + return this.arr[column]; + } +}; + +/** + * Retrieves a Float value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getNum + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {Number} Float Floating point number + */ +p5.TableRow.prototype.getNum = function(column) { + var ret; + if (typeof(column) === 'string'){ + ret = parseFloat(this.obj[column], 10); + } else { + ret = parseFloat(this.arr[column], 10); + } + + if (ret.toString() === 'NaN') { + throw 'Error: ' + this.obj[column]+ ' is NaN (Not a Number)'; + } + return ret; +}; + +/** + * Retrieves an String value from the TableRow's specified + * column. The column may be specified by either its ID or + * title. + * + * @method getString + * @param {String|Number} column columnName (string) or + * ID (number) + * @return {String} String + */ +p5.TableRow.prototype.getString = function(column) { + if (typeof(column) === 'string'){ + return this.obj[column].toString(); + } else { + return this.arr[column].toString(); + } +}; + +module.exports = p5.TableRow; + +},{"../core/core":48}],73:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Calculation + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Calculates the absolute value (magnitude) of a number. Maps to Math.abs(). + * The absolute value of a number is always positive. + * + * @method abs + * @param {Number} n number to compute + * @return {Number} absolute value of given number + * @example + * <div class = "norender"><code> + * function setup() { + * var x = -3; + * var y = abs(x); + * + * print(x); // -3 + * print(y); // 3 + * } + * </code></div> + */ +p5.prototype.abs = Math.abs; + +/** + * Calculates the closest int value that is greater than or equal to the + * value of the parameter. Maps to Math.ceil(). For example, ceil(9.03) + * returns the value 10. + * + * @method ceil + * @param {Number} n number to round up + * @return {Number} rounded up number + * @example + * <div><code> + * function draw() { + * background(200); + * // map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * //Get the ceiling of the mapped number. + * var bx = ceil(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.ceil = Math.ceil; + +/** + * Constrains a value between a minimum and maximum value. + * + * @method constrain + * @param {Number} n number to constrain + * @param {Number} low minimum limit + * @param {Number} high maximum limit + * @return {Number} constrained number + * @example + * <div><code> + * function draw() { + * background(200); + * + * var leftWall = 25; + * var rightWall = 75; + * + * // xm is just the mouseX, while + * // xc is the mouseX, but constrained + * // between the leftWall and rightWall! + * var xm = mouseX; + * var xc = constrain(mouseX, leftWall, rightWall); + * + * // Draw the walls. + * stroke(150); + * line(leftWall, 0, leftWall, height); + * line(rightWall, 0, rightWall, height); + * + * // Draw xm and xc as circles. + * noStroke(); + * fill(150); + * ellipse(xm, 33, 9,9); // Not Constrained + * fill(0); + * ellipse(xc, 66, 9,9); // Constrained + * } + * </code></div> + */ +p5.prototype.constrain = function(n, low, high) { + return Math.max(Math.min(n, high), low); +}; + +/** + * Calculates the distance between two points. + * + * @method dist + * @param {Number} x1 x-coordinate of the first point + * @param {Number} y1 y-coordinate of the first point + * @param {Number} [z1] z-coordinate of the first point + * @param {Number} x2 x-coordinate of the second point + * @param {Number} y2 y-coordinate of the second point + * @param {Number} [z2] z-coordinate of the second point + * @return {Number} distance between the two points + * @example + * <div><code> + * // Move your mouse inside the canvas to see the + * // change in distance between two points! + * function draw() { + * background(200); + * fill(0); + * + * var x1 = 10; + * var y1 = 90; + * var x2 = mouseX; + * var y2 = mouseY; + * + * line(x1, y1, x2, y2); + * ellipse(x1, y1, 7, 7); + * ellipse(x2, y2, 7, 7); + * + * // d is the length of the line + * // the distance from point 1 to point 2. + * var d = int(dist(x1, y1, x2, y2)); + * + * // Let's write d along the line we are drawing! + * push(); + * translate( (x1+x2)/2, (y1+y2)/2 ); + * rotate( atan2(y2-y1,x2-x1) ); + * text(nfc(d,1,1), 0, -5); + * pop(); + * // Fancy! + * } + * </code></div> + */ +p5.prototype.dist = function(x1, y1, z1, x2, y2, z2) { + if (arguments.length === 4) { + // In the case of 2d: z1 means x2 and x2 means y2 + return Math.sqrt( (z1-x1)*(z1-x1) + (x2-y1)*(x2-y1) ); + } else if (arguments.length === 6) { + return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1) ); + } +}; + +/** + * Returns Euler's number e (2.71828...) raised to the power of the n + * parameter. Maps to Math.exp(). + * + * @method exp + * @param {Number} n exponent to raise + * @return {Number} e^n + * @example + * <div><code> + * function draw() { + * background(200); + * + * // Compute the exp() function with a value between 0 and 2 + * var xValue = map(mouseX, 0, width, 0, 2); + * var yValue = exp(xValue); + * + * var y = map(yValue, 0, 8, height, 0); + * + * var legend = "exp (" + nfc(xValue, 3) +")\n= " + nf(yValue, 1, 4); + * stroke(150); + * line(mouseX, y, mouseX, height); + * fill(0); + * text(legend, 5, 15); + * noStroke(); + * ellipse (mouseX,y, 7, 7); + * + * // Draw the exp(x) curve, + * // over the domain of x from 0 to 2 + * noFill(); + * stroke(0); + * beginShape(); + * for (var x = 0; x < width; x++) { + * xValue = map(x, 0, width, 0, 2); + * yValue = exp(xValue); + * y = map(yValue, 0, 8, height, 0); + * vertex(x, y); + * } + * + * endShape(); + * line(0, 0, 0, height); + * line(0, height-1, width, height-1); + * } + * </code></div> + */ +p5.prototype.exp = Math.exp; + +/** + * Calculates the closest int value that is less than or equal to the + * value of the parameter. Maps to Math.floor(). + * + * @method floor + * @param {Number} n number to round down + * @return {Number} rounded down number + * @example + * <div><code> + * function draw() { + * background(200); + * //map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * //Get the floor of the mapped number. + * var bx = floor(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.floor = Math.floor; + +/** + * Calculates a number between two numbers at a specific increment. The amt + * parameter is the amount to interpolate between the two values where 0.0 + * equal to the first point, 0.1 is very near the first point, 0.5 is + * half-way in between, etc. The lerp function is convenient for creating + * motion along a straight path and for drawing dotted lines. + * + * @method lerp + * @param {Number} start first value + * @param {Number} stop second value + * @param {Number} amt number between 0.0 and 1.0 + * @return {Number} lerped value + * @example + * <div><code> + * function setup() { + * background(200); + * var a = 20; + * var b = 80; + * var c = lerp(a,b, .2); + * var d = lerp(a,b, .5); + * var e = lerp(a,b, .8); + * + * var y = 50 + * + * strokeWeight(5); + * stroke(0); // Draw the original points in black + * point(a, y); + * point(b, y); + * + * stroke(100); // Draw the lerp points in gray + * point(c, y); + * point(d, y); + * point(e, y); + * } + * </code></div> + */ +p5.prototype.lerp = function(start, stop, amt) { + return amt*(stop-start)+start; +}; + +/** + * Calculates the natural logarithm (the base-e logarithm) of a number. This + * function expects the n parameter to be a value greater than 0.0. Maps to + * Math.log(). + * + * @method log + * @param {Number} n number greater than 0 + * @return {Number} natural logarithm of n + * @example + * <div><code> + * function draw() { + * background(200); + * var maxX = 2.8; + * var maxY = 1.5; + * + * // Compute the natural log of a value between 0 and maxX + * var xValue = map(mouseX, 0, width, 0, maxX); + * if (xValue > 0) { // Cannot take the log of a negative number. + * var yValue = log(xValue); + * var y = map(yValue, -maxY, maxY, height, 0); + * + * // Display the calculation occurring. + * var legend = "log(" + nf(xValue, 1, 2) + ")\n= " + nf(yValue, 1, 3); + * stroke(150); + * line(mouseX, y, mouseX, height); + * fill(0); + * text (legend, 5, 15); + * noStroke(); + * ellipse (mouseX, y, 7, 7); + * } + * + * // Draw the log(x) curve, + * // over the domain of x from 0 to maxX + * noFill(); + * stroke(0); + * beginShape(); + * for(var x=0; x < width; x++) { + * xValue = map(x, 0, width, 0, maxX); + * yValue = log(xValue); + * y = map(yValue, -maxY, maxY, height, 0); + * vertex(x, y); + * } + * endShape(); + * line(0,0,0,height); + * line(0,height/2,width, height/2); + * } + * </code></div> + */ +p5.prototype.log = Math.log; + +/** + * Calculates the magnitude (or length) of a vector. A vector is a direction + * in space commonly used in computer graphics and linear algebra. Because it + * has no "start" position, the magnitude of a vector can be thought of as + * the distance from the coordinate 0,0 to its x,y value. Therefore, mag() is + * a shortcut for writing dist(0, 0, x, y). + * + * @method mag + * @param {Number} a first value + * @param {Number} b second value + * @return {Number} magnitude of vector from (0,0) to (a,b) + * @example + * <div><code> + * function setup() { + * var x1 = 20; + * var x2 = 80; + * var y1 = 30; + * var y2 = 70; + * + * line(0, 0, x1, y1); + * print(mag(x1, y1)); // Prints "36.05551" + * line(0, 0, x2, y1); + * print(mag(x2, y1)); // Prints "85.44004" + * line(0, 0, x1, y2); + * print(mag(x1, y2)); // Prints "72.8011" + * line(0, 0, x2, y2); + * print(mag(x2, y2)); // Prints "106.30146" + * } + * </code></div> + */ +p5.prototype.mag = function(x, y) { + return Math.sqrt(x*x+y*y); +}; + +/** + * Re-maps a number from one range to another. + * <br><br> + * In the first example above, the number 25 is converted from a value in the + * range of 0 to 100 into a value that ranges from the left edge of the + * window (0) to the right edge (width). + * + * @method map + * @param {Number} value the incoming value to be converted + * @param {Number} start1 lower bound of the value's current range + * @param {Number} stop1 upper bound of the value's current range + * @param {Number} start2 lower bound of the value's target range + * @param {Number} stop upper bound of the value's target range + * @return {Number} remapped number + * @example + * <div><code> + * var value = 25; + * var m = map(value, 0, 100, 0, width); + * ellipse(m, 50, 10, 10); + * </code></div> + * + * <div><code> + * function setup() { + * noStroke(); + * } + * + * function draw() { + * background(204); + * var x1 = map(mouseX, 0, width, 25, 75); + * ellipse(x1, 25, 25, 25); + * var x2 = map(mouseX, 0, width, 0, 100); + * ellipse(x2, 75, 25, 25); + * } + * </code></div> + */ +p5.prototype.map = function(n, start1, stop1, start2, stop2) { + return ((n-start1)/(stop1-start1))*(stop2-start2)+start2; +}; + +/** + * Determines the largest value in a sequence of numbers, and then returns + * that value. max() accepts any number of Number parameters, or an Array + * of any length. + * + * @method max + * @param {Number|Array} n0 Numbers to compare + * @return {Number} maximum Number + * @example + * <div><code> + * function setup() { + * // Change the elements in the array and run the sketch + * // to show how max() works! + * numArray = new Array(2,1,5,4,8,9); + * fill(0); + * noStroke(); + * text("Array Elements", 0, 10); + * // Draw all numbers in the array + * var spacing = 15; + * var elemsY = 25; + * for(var i = 0; i < numArray.length; i++) { + * text(numArray[i], i * spacing, elemsY); + * } + * maxX = 33; + * maxY = 80; + * // Draw the Maximum value in the array. + * textSize(32); + * text(max(numArray), maxX, maxY); + * } + * </code></div> + */ +p5.prototype.max = function() { + if (arguments[0] instanceof Array) { + return Math.max.apply(null,arguments[0]); + } else { + return Math.max.apply(null,arguments); + } +}; + +/** + * Determines the smallest value in a sequence of numbers, and then returns + * that value. min() accepts any number of Number parameters, or an Array + * of any length. + * + * @method min + * @param {Number|Array} n0 Numbers to compare + * @return {Number} minimum Number + * @example + * <div><code> + * function setup() { + * // Change the elements in the array and run the sketch + * // to show how min() works! + * numArray = new Array(2,1,5,4,8,9); + * fill(0); + * noStroke(); + * text("Array Elements", 0, 10); + * // Draw all numbers in the array + * var spacing = 15; + * var elemsY = 25; + * for(var i = 0; i < numArray.length; i++) { + * text(numArray[i], i * spacing, elemsY); + * } + * maxX = 33; + * maxY = 80; + * // Draw the Minimum value in the array. + * textSize(32); + * text(min(numArray), maxX, maxY); + * } + * </code></div> + */ +p5.prototype.min = function() { + if (arguments[0] instanceof Array) { + return Math.min.apply(null,arguments[0]); + } else { + return Math.min.apply(null,arguments); + } +}; + +/** + * Normalizes a number from another range into a value between 0 and 1. + * Identical to map(value, low, high, 0, 1). + * Numbers outside of the range are not clamped to 0 and 1, because + * out-of-range values are often intentional and useful. (See the second + * example above.) + * + * @method norm + * @param {Number} value incoming value to be normalized + * @param {Number} start lower bound of the value's current range + * @param {Number} stop upper bound of the value's current range + * @return {Number} normalized number + * @example + * <div><code> + * function draw() { + * background(200); + * currentNum = mouseX; + * lowerBound = 0; + * upperBound = width; //100; + * normalized = norm(currentNum, lowerBound, upperBound); + * lineY = 70 + * line(0, lineY, width, lineY); + * //Draw an ellipse mapped to the non-normalized value. + * noStroke(); + * fill(50) + * var s = 7; // ellipse size + * ellipse(currentNum, lineY, s, s); + * + * // Draw the guide + * guideY = lineY + 15; + * text("0", 0, guideY); + * textAlign(RIGHT); + * text("100", width, guideY); + * + * // Draw the normalized value + * textAlign(LEFT); + * fill(0); + * textSize(32); + * normalY = 40; + * normalX = 20; + * text(normalized, normalX, normalY); + * } + * </code></div> + */ +p5.prototype.norm = function(n, start, stop) { + return this.map(n, start, stop, 0, 1); +}; + +/** + * Facilitates exponential expressions. The pow() function is an efficient + * way of multiplying numbers by themselves (or their reciprocals) in large + * quantities. For example, pow(3, 5) is equivalent to the expression + * 3*3*3*3*3 and pow(3, -5) is equivalent to 1 / 3*3*3*3*3. Maps to + * Math.pow(). + * + * @method pow + * @param {Number} n base of the exponential expression + * @param {Number} e power by which to raise the base + * @return {Number} n^e + * @example + * <div><code> + * function setup() { + * //Exponentially increase the size of an ellipse. + * eSize = 3; // Original Size + * eLoc = 10; // Original Location + * + * ellipse(eLoc, eLoc, eSize, eSize); + * + * ellipse(eLoc*2, eLoc*2, pow(eSize, 2), pow(eSize, 2)); + * + * ellipse(eLoc*4, eLoc*4, pow(eSize, 3), pow(eSize, 3)); + * + * ellipse(eLoc*8, eLoc*8, pow(eSize, 4), pow(eSize, 4)); + * } + * </code></div> + */ +p5.prototype.pow = Math.pow; + +/** + * Calculates the integer closest to the n parameter. For example, + * round(133.8) returns the value 134. Maps to Math.round(). + * + * @method round + * @param {Number} n number to round + * @return {Number} rounded number + * @example + * <div><code> + * function draw() { + * background(200); + * //map, mouseX between 0 and 5. + * var ax = map(mouseX, 0, 100, 0, 5); + * var ay = 66; + * + * // Round the mapped number. + * var bx = round(map(mouseX, 0, 100, 0,5)); + * var by = 33; + * + * // Multiply the mapped numbers by 20 to more easily + * // see the changes. + * stroke(0); + * fill(0); + * line(0, ay, ax * 20, ay); + * line(0, by, bx * 20, by); + * + * // Reformat the float returned by map and draw it. + * noStroke(); + * text(nfc(ax, 2,2), ax, ay - 5); + * text(nfc(bx,1,1), bx, by - 5); + * } + * </code></div> + */ +p5.prototype.round = Math.round; + +/** + * Squares a number (multiplies a number by itself). The result is always a + * positive number, as multiplying two negative numbers always yields a + * positive result. For example, -1 * -1 = 1. + * + * @method sq + * @param {Number} n number to square + * @return {Number} squared number + * @example + * <div><code> + * function draw() { + * background(200); + * eSize = 7; + * x1 = map(mouseX, 0, width, 0, 10); + * y1 = 80; + * x2 = sq(x1); + * y2 = 20; + * + * // Draw the non-squared. + * line(0, y1, width, y1); + * ellipse(x1, y1, eSize, eSize); + * + * // Draw the squared. + * line(0, y2, width, y2); + * ellipse(x2, y2, eSize, eSize); + * + * // Draw dividing line. + * stroke(100) + * line(0, height/2, width, height/2); + * + * // Draw text. + * var spacing = 15; + * noStroke(); + * fill(0); + * text("x = " + x1, 0, y1 + spacing); + * text("sq(x) = " + x2, 0, y2 + spacing); + * } + * </code></div> + */ +p5.prototype.sq = function(n) { return n*n; }; + +/** + * Calculates the square root of a number. The square root of a number is + * always positive, even though there may be a valid negative root. The + * square root s of number a is such that s*s = a. It is the opposite of + * squaring. Maps to Math.sqrt(). + * + * @method sqrt + * @param {Number} n non-negative number to square root + * @return {Number} square root of number + * @example + * <div><code> + * function draw() { + * background(200); + * eSize = 7; + * x1 = mouseX; + * y1 = 80; + * x2 = sqrt(x1); + * y2 = 20; + * + * // Draw the non-squared. + * line(0, y1, width, y1); + * ellipse(x1, y1, eSize, eSize); + * + * // Draw the squared. + * line(0, y2, width, y2); + * ellipse(x2, y2, eSize, eSize); + * + * // Draw dividing line. + * stroke(100) + * line(0, height/2, width, height/2); + * + * // Draw text. + * noStroke(); + * fill(0); + * var spacing = 15; + * text("x = " + x1, 0, y1 + spacing); + * text("sqrt(x) = " + x2, 0, y2 + spacing); + * } + * </code></div> + */ +p5.prototype.sqrt = Math.sqrt; + +module.exports = p5; + +},{"../core/core":48}],74:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Math + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + + +/** + * Creates a new p5.Vector (the datatype for storing vectors). This provides a + * two or three dimensional vector, specifically a Euclidean (also known as + * geometric) vector. A vector is an entity that has both magnitude and + * direction. + * + * @method createVector + * @param {Number} [x] x component of the vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + */ +p5.prototype.createVector = function (x, y, z) { + if (this instanceof p5) { + return new p5.Vector(this, arguments); + } else { + return new p5.Vector(x, y, z); + } +}; + +module.exports = p5; + +},{"../core/core":48}],75:[function(_dereq_,module,exports){ +////////////////////////////////////////////////////////////// + +// http://mrl.nyu.edu/~perlin/noise/ +// Adapting from PApplet.java +// which was adapted from toxi +// which was adapted from the german demo group farbrausch +// as used in their demo "art": http://www.farb-rausch.de/fr010src.zip + +// someday we might consider using "improved noise" +// http://mrl.nyu.edu/~perlin/paper445.pdf +// See: https://github.com/shiffman/The-Nature-of-Code-Examples-p5.js/ +// blob/master/introduction/Noise1D/noise.js + +/** + * @module Math + * @submodule Noise + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +var PERLIN_YWRAPB = 4; +var PERLIN_YWRAP = 1<<PERLIN_YWRAPB; +var PERLIN_ZWRAPB = 8; +var PERLIN_ZWRAP = 1<<PERLIN_ZWRAPB; +var PERLIN_SIZE = 4095; + +var perlin_octaves = 4; // default to medium smooth +var perlin_amp_falloff = 0.5; // 50% reduction/octave + +var scaled_cosine = function(i) { + return 0.5*(1.0-Math.cos(i*Math.PI)); +}; + +var perlin; // will be initialized lazily by noise() or noiseSeed() + + +/** + * Returns the Perlin noise value at specified coordinates. Perlin noise is + * a random sequence generator producing a more natural ordered, harmonic + * succession of numbers compared to the standard <b>random()</b> function. + * It was invented by Ken Perlin in the 1980s and been used since in + * graphical applications to produce procedural textures, natural motion, + * shapes, terrains etc.<br /><br /> The main difference to the + * <b>random()</b> function is that Perlin noise is defined in an infinite + * n-dimensional space where each pair of coordinates corresponds to a + * fixed semi-random value (fixed only for the lifespan of the program; see + * the noiseSeed() function). p5.js can compute 1D, 2D and 3D noise, + * depending on the number of coordinates given. The resulting value will + * always be between 0.0 and 1.0. The noise value can be animated by moving + * through the noise space as demonstrated in the example above. The 2nd + * and 3rd dimension can also be interpreted as time.<br /><br />The actual + * noise is structured similar to an audio signal, in respect to the + * function's use of frequencies. Similar to the concept of harmonics in + * physics, perlin noise is computed over several octaves which are added + * together for the final result. <br /><br />Another way to adjust the + * character of the resulting sequence is the scale of the input + * coordinates. As the function works within an infinite space the value of + * the coordinates doesn't matter as such, only the distance between + * successive coordinates does (eg. when using <b>noise()</b> within a + * loop). As a general rule the smaller the difference between coordinates, + * the smoother the resulting noise sequence will be. Steps of 0.005-0.03 + * work best for most applications, but this will differ depending on use. + * + * + * @method noise + * @param {Number} x x-coordinate in noise space + * @param {Number} y y-coordinate in noise space + * @param {Number} z z-coordinate in noise space + * @return {Number} Perlin noise value (between 0 and 1) at specified + * coordinates + * @example + * <div> + * <code>var xoff = 0.0; + * + * function draw() { + * background(204); + * xoff = xoff + .01; + * var n = noise(xoff) * width; + * line(n, 0, n, height); + * } + * </code> + * </div> + * <div> + * <code>var noiseScale=0.02; + * + * function draw() { + * background(0); + * for (var x=0; x < width; x++) { + * var noiseVal = noise((mouseX+x)*noiseScale, mouseY*noiseScale); + * stroke(noiseVal*255); + * line(x, mouseY+noiseVal*80, x, height); + * } + * } + * </code> + * </div> + */ +p5.prototype.noise = function(x,y,z) { + y = y || 0; + z = z || 0; + + if (perlin == null) { + perlin = new Array(PERLIN_SIZE + 1); + for (var i = 0; i < PERLIN_SIZE + 1; i++) { + perlin[i] = Math.random(); + } + } + + if (x<0) { x=-x; } + if (y<0) { y=-y; } + if (z<0) { z=-z; } + + var xi=Math.floor(x), yi=Math.floor(y), zi=Math.floor(z); + var xf = x - xi; + var yf = y - yi; + var zf = z - zi; + var rxf, ryf; + + var r=0; + var ampl=0.5; + + var n1,n2,n3; + + for (var o=0; o<perlin_octaves; o++) { + var of=xi+(yi<<PERLIN_YWRAPB)+(zi<<PERLIN_ZWRAPB); + + rxf = scaled_cosine(xf); + ryf = scaled_cosine(yf); + + n1 = perlin[of&PERLIN_SIZE]; + n1 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n1); + n2 = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE]; + n2 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n2); + n1 += ryf*(n2-n1); + + of += PERLIN_ZWRAP; + n2 = perlin[of&PERLIN_SIZE]; + n2 += rxf*(perlin[(of+1)&PERLIN_SIZE]-n2); + n3 = perlin[(of+PERLIN_YWRAP)&PERLIN_SIZE]; + n3 += rxf*(perlin[(of+PERLIN_YWRAP+1)&PERLIN_SIZE]-n3); + n2 += ryf*(n3-n2); + + n1 += scaled_cosine(zf)*(n2-n1); + + r += n1*ampl; + ampl *= perlin_amp_falloff; + xi<<=1; + xf*=2; + yi<<=1; + yf*=2; + zi<<=1; + zf*=2; + + if (xf>=1.0) { xi++; xf--; } + if (yf>=1.0) { yi++; yf--; } + if (zf>=1.0) { zi++; zf--; } + } + return r; +}; + + +/** + * + * Adjusts the character and level of detail produced by the Perlin noise + * function. Similar to harmonics in physics, noise is computed over + * several octaves. Lower octaves contribute more to the output signal and + * as such define the overall intensity of the noise, whereas higher octaves + * create finer grained details in the noise sequence. + * <br><br> + * By default, noise is computed over 4 octaves with each octave contributing + * exactly half than its predecessor, starting at 50% strength for the 1st + * octave. This falloff amount can be changed by adding an additional function + * parameter. Eg. a falloff factor of 0.75 means each octave will now have + * 75% impact (25% less) of the previous lower octave. Any value between + * 0.0 and 1.0 is valid, however note that values greater than 0.5 might + * result in greater than 1.0 values returned by <b>noise()</b>. + * <br><br> + * By changing these parameters, the signal created by the <b>noise()</b> + * function can be adapted to fit very specific needs and characteristics. + * + * @method noiseDetail + * @param {Number} lod number of octaves to be used by the noise + * @param {Number} falloff falloff factor for each octave + * @example + * <div> + * <code> + * + * var noiseVal; + * var noiseScale=0.02; + * + * function setup() { + * createCanvas(100,100); + * } + * + * function draw() { + * background(0); + * for (var y = 0; y < height; y++) { + * for (var x = 0; x < width/2; x++) { + * noiseDetail(2,0.2); + * noiseVal = noise((mouseX+x) * noiseScale, + * (mouseY+y) * noiseScale); + * stroke(noiseVal*255); + * point(x,y); + * noiseDetail(8,0.65); + * noiseVal = noise((mouseX + x + width/2) * noiseScale, + * (mouseY + y) * noiseScale); + * stroke(noiseVal*255); + * point(x + width/2, y); + * } + * } + * } + * </code> + * </div> + */ +p5.prototype.noiseDetail = function(lod, falloff) { + if (lod>0) { perlin_octaves=lod; } + if (falloff>0) { perlin_amp_falloff=falloff; } +}; + +/** + * Sets the seed value for <b>noise()</b>. By default, <b>noise()</b> + * produces different results each time the program is run. Set the + * <b>value</b> parameter to a constant to return the same pseudo-random + * numbers each time the software is run. + * + * @method noiseSeed + * @param {Number} seed the seed value + * @example + * <div> + * <code>var xoff = 0.0; + * + * function setup() { + * noiseSeed(99); + * stroke(0, 10); + * } + * + * function draw() { + * xoff = xoff + .01; + * var n = noise(xoff) * width; + * line(n, 0, n, height); + * } + * </code> + * </div> + */ +p5.prototype.noiseSeed = function(seed) { + // Linear Congruential Generator + // Variant of a Lehman Generator + var lcg = (function() { + // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes + // m is basically chosen to be large (as it is the max period) + // and for its relationships to a and c + var m = 4294967296, + // a - 1 should be divisible by m's prime factors + a = 1664525, + // c and m should be co-prime + c = 1013904223, + seed, z; + return { + setSeed : function(val) { + // pick a random seed if val is undefined or null + // the >>> 0 casts the seed to an unsigned 32-bit integer + z = seed = (val == null ? Math.random() * m : val) >>> 0; + }, + getSeed : function() { + return seed; + }, + rand : function() { + // define the recurrence relationship + z = (a * z + c) % m; + // return a float in [0, 1) + // if z = m then z / m = 0 therefore (z % m) / m < 1 always + return z / m; + } + }; + }()); + + lcg.setSeed(seed); + perlin = new Array(PERLIN_SIZE + 1); + for (var i = 0; i < PERLIN_SIZE + 1; i++) { + perlin[i] = lcg.rand(); + } +}; + +module.exports = p5; + +},{"../core/core":48}],76:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Math + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('./polargeometry'); +var constants = _dereq_('../core/constants'); + +/** + * A class to describe a two or three dimensional vector, specifically + * a Euclidean (also known as geometric) vector. A vector is an entity + * that has both magnitude and direction. The datatype, however, stores + * the components of the vector (x, y for 2D, and x, y, z for 3D). The magnitude + * and direction can be accessed via the methods mag() and heading(). + * <br><br> + * In many of the p5.js examples, you will see p5.Vector used to describe a + * position, velocity, or acceleration. For example, if you consider a rectangle + * moving across the screen, at any given instant it has a position (a vector + * that points from the origin to its location), a velocity (the rate at which + * the object's position changes per time unit, expressed as a vector), and + * acceleration (the rate at which the object's velocity changes per time + * unit, expressed as a vector). + * <br><br> + * Since vectors represent groupings of values, we cannot simply use + * traditional addition/multiplication/etc. Instead, we'll need to do some + * "vector" math, which is made easy by the methods inside the p5.Vector class. + * + * @class p5.Vector + * @constructor + * @param {Number} [x] x component of the vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + * @example + * <div> + * <code> + * var v1 = createVector(40, 50); + * var v2 = createVector(40, 50); + * + * ellipse(v1.x, v1.y, 50, 50); + * ellipse(v2.x, v2.y, 50, 50); + * v1.add(v2); + * ellipse(v1.x, v1.y, 50, 50); + * </code> + * </div> + */ +p5.Vector = function() { + var x,y,z; + // This is how it comes in with createVector() + if(arguments[0] instanceof p5) { + // save reference to p5 if passed in + this.p5 = arguments[0]; + x = arguments[1][0] || 0; + y = arguments[1][1] || 0; + z = arguments[1][2] || 0; + // This is what we'll get with new p5.Vector() + } else { + x = arguments[0] || 0; + y = arguments[1] || 0; + z = arguments[2] || 0; + } + /** + * The x component of the vector + * @property x + * @type {Number} + */ + this.x = x; + /** + * The y component of the vector + * @property y + * @type {Number} + */ + this.y = y; + /** + * The z component of the vector + * @property z + * @type {Number} + */ + this.z = z; +}; + +/** + * Returns a string representation of a vector v by calling String(v) + * or v.toString(). This method is useful for logging vectors in the + * console. + * @method toString + * @example + * <div class = "norender"><code> + * function setup() { + * var v = createVector(20,30); + * print(String(v)); // prints "p5.Vector Object : [20, 30, 0]" + * } + * </div></code> + * + */ +p5.Vector.prototype.toString = function p5VectorToString() { + return 'p5.Vector Object : ['+ this.x +', '+ this.y +', '+ this.z + ']'; +}; + +/** + * Sets the x, y, and z component of the vector using two or three separate + * variables, the data from a p5.Vector, or the values from a float array. + * @method set + * + * @param {Number|p5.Vector|Array} [x] the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @example + * <div class="norender"> + * <code> + * function setup() { + * var v = createVector(1, 2, 3); + * v.set(4,5,6); // Sets vector to [4, 5, 6] + * + * var v1 = createVector(0, 0, 0); + * var arr = [1, 2, 3]; + * v1.set(arr); // Sets vector to [1, 2, 3] + * } + * </code> + * </div> + */ +p5.Vector.prototype.set = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x = x.x || 0; + this.y = x.y || 0; + this.z = x.z || 0; + return this; + } + if (x instanceof Array) { + this.x = x[0] || 0; + this.y = x[1] || 0; + this.z = x[2] || 0; + return this; + } + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + return this; +}; + +/** + * Gets a copy of the vector, returns a p5.Vector object. + * + * @method copy + * @return {p5.Vector} the copy of the p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = v.copy(); + * print(v1.x == v2.x && v1.y == v2.y && v1.z == v2.z); + * // Prints "true" + * </code> + * </div> + */ +p5.Vector.prototype.copy = function () { + if (this.p5) { + return new p5.Vector(this.p5,[this.x, this.y, this.z]); + } else { + return new p5.Vector(this.x,this.y,this.z); + } +}; + +/** + * Adds x, y, and z components to a vector, adds one vector to another, or + * adds two independent vectors together. The version of the method that adds + * two vectors together is a static method and returns a p5.Vector, the others + * acts directly on the vector. See the examples for more context. + * + * @method add + * @chainable + * @param {Number|p5.Vector|Array} x the x component of the vector to be + * added or a p5.Vector or an Array + * @param {Number} [y] the y component of the vector to be + * added + * @param {Number} [z] the z component of the vector to be + * added + * @return {p5.Vector} the p5.Vector object. + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 2, 3); + * v.add(4,5,6); + * // v's compnents are set to [5, 7, 9] + * </code> + * </div> + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(2, 3, 4); + * + * var v3 = p5.Vector.add(v1, v2); + * // v3 has components [3, 5, 7] + * </code> + * </div> + */ +p5.Vector.prototype.add = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x += x.x || 0; + this.y += x.y || 0; + this.z += x.z || 0; + return this; + } + if (x instanceof Array) { + this.x += x[0] || 0; + this.y += x[1] || 0; + this.z += x[2] || 0; + return this; + } + this.x += x || 0; + this.y += y || 0; + this.z += z || 0; + return this; +}; + +/** + * Subtracts x, y, and z components from a vector, subtracts one vector from + * another, or subtracts two independent vectors. The version of the method + * that subtracts two vectors is a static method and returns a p5.Vector, the + * other acts directly on the vector. See the examples for more context. + * + * @method sub + * @chainable + * @param {Number|p5.Vector|Array} x the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @return {p5.Vector} p5.Vector object. + * @example + * <div class="norender"> + * <code> + * var v = createVector(4, 5, 6); + * v.sub(1, 1, 1); + * // v's compnents are set to [3, 4, 5] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(2, 3, 4); + * var v2 = createVector(1, 2, 3); + * + * var v3 = p5.Vector.sub(v1, v2); + * // v3 has compnents [1, 1, 1] + * </code> + * </div> + */ +p5.Vector.prototype.sub = function (x, y, z) { + if (x instanceof p5.Vector) { + this.x -= x.x || 0; + this.y -= x.y || 0; + this.z -= x.z || 0; + return this; + } + if (x instanceof Array) { + this.x -= x[0] || 0; + this.y -= x[1] || 0; + this.z -= x[2] || 0; + return this; + } + this.x -= x || 0; + this.y -= y || 0; + this.z -= z || 0; + return this; +}; + +/** + * Multiply the vector by a scalar. The static version of this method + * creates a new p5.Vector while the non static version acts on the vector + * directly. See the examples for more context. + * + * @method mult + * @chainable + * @param {Number} n the number to multiply with the vector + * @return {p5.Vector} a reference to the p5.Vector object (allow chaining) + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 2, 3); + * v.mult(2); + * // v's compnents are set to [2, 4, 6] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 2, 3); + * var v2 = p5.Vector.mult(v1, 2); + * // v2 has compnents [2, 4, 6] + * </code> + * </div> + */ +p5.Vector.prototype.mult = function (n) { + this.x *= n || 0; + this.y *= n || 0; + this.z *= n || 0; + return this; +}; + +/** + * Divide the vector by a scalar. The static version of this method creates a + * new p5.Vector while the non static version acts on the vector directly. + * See the examples for more context. + * + * @method div + * @chainable + * @param {number} n the number to divide the vector by + * @return {p5.Vector} a reference to the p5.Vector object (allow chaining) + * @example + * <div class="norender"> + * <code> + * var v = createVector(6, 4, 2); + * v.div(2); //v's compnents are set to [3, 2, 1] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(6, 4, 2); + * var v2 = p5.Vector.div(v, 2); + * // v2 has compnents [3, 2, 1] + * </code> + * </div> + */ +p5.Vector.prototype.div = function (n) { + this.x /= n; + this.y /= n; + this.z /= n; + return this; +}; + +/** + * Calculates the magnitude (length) of the vector and returns the result as + * a float (this is simply the equation sqrt(x*x + y*y + z*z).) + * + * @method mag + * @return {Number} magnitude of the vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(20.0, 30.0, 40.0); + * var m = v.mag(10); + * print(m); // Prints "53.85164807134504" + * </code> + * </div> + */ +p5.Vector.prototype.mag = function () { + return Math.sqrt(this.magSq()); +}; + +/** + * Calculates the squared magnitude of the vector and returns the result + * as a float (this is simply the equation <em>(x*x + y*y + z*z)</em>.) + * Faster if the real length is not required in the + * case of comparing vectors, etc. + * + * @method magSq + * @return {number} squared magnitude of the vector + * @example + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(6, 4, 2); + * print(v1.magSq()); // Prints "56" + * </code> + * </div> + */ +p5.Vector.prototype.magSq = function () { + var x = this.x, y = this.y, z = this.z; + return (x * x + y * y + z * z); +}; + +/** + * Calculates the dot product of two vectors. The version of the method + * that computes the dot product of two independent vectors is a static + * method. See the examples for more context. + * + * + * @method dot + * @param {Number|p5.Vector} x x component of the vector or a p5.Vector + * @param {Number} [y] y component of the vector + * @param {Number} [z] z component of the vector + * @return {Number} the dot product + * + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(2, 3, 4); + * + * print(v1.dot(v2)); // Prints "20" + * </code> + * </div> + * + * <div class="norender"> + * <code> + * //Static method + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(3, 2, 1); + * print (p5.Vector.dot(v1, v2)); // Prints "10" + * </code> + * </div> + */ +p5.Vector.prototype.dot = function (x, y, z) { + if (x instanceof p5.Vector) { + return this.dot(x.x, x.y, x.z); + } + return this.x * (x || 0) + + this.y * (y || 0) + + this.z * (z || 0); +}; + +/** + * Calculates and returns a vector composed of the cross product between + * two vectors. Both the static and non static methods return a new p5.Vector. + * See the examples for more context. + * + * @method cross + * @param {p5.Vector} v p5.Vector to be crossed + * @return {p5.Vector} p5.Vector composed of cross product + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 2, 3); + * var v2 = createVector(1, 2, 3); + * + * v1.cross(v2); // v's components are [0, 0, 0] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var crossProduct = p5.Vector.cross(v1, v2); + * // crossProduct has components [0, 0, 1] + * </code> + * </div> + */ +p5.Vector.prototype.cross = function (v) { + var x = this.y * v.z - this.z * v.y; + var y = this.z * v.x - this.x * v.z; + var z = this.x * v.y - this.y * v.x; + if (this.p5) { + return new p5.Vector(this.p5,[x,y,z]); + } else { + return new p5.Vector(x,y,z); + } +}; + +/** + * Calculates the Euclidean distance between two points (considering a + * point as a vector object). + * + * @method dist + * @param {p5.Vector} v the x, y, and z coordinates of a p5.Vector + * @return {Number} the distance + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var distance = v1.dist(v2); // distance is 1.4142... + * </code> + * </div> + * <div class="norender"> + * <code> + * // Static method + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var distance = p5.Vector.dist(v1,v2); + * // distance is 1.4142... + * </code> + * </div> + */ +p5.Vector.prototype.dist = function (v) { + var d = v.copy().sub(this); + return d.mag(); +}; + +/** + * Normalize the vector to length 1 (make it a unit vector). + * + * @method normalize + * @return {p5.Vector} normalized p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v.normalize(); + * // v's compnents are set to + * // [0.4454354, 0.8908708, 0.089087084] + * </code> + * </div> + * + */ +p5.Vector.prototype.normalize = function () { + return this.div(this.mag()); +}; + +/** + * Limit the magnitude of this vector to the value used for the <b>max</b> + * parameter. + * + * @method limit + * @param {Number} max the maximum magnitude for the vector + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v.limit(5); + * // v's compnents are set to + * // [2.2271771, 4.4543543, 0.4454354] + * </code> + * </div> + */ +p5.Vector.prototype.limit = function (l) { + var mSq = this.magSq(); + if(mSq > l*l) { + this.div(Math.sqrt(mSq)); //normalize it + this.mult(l); + } + return this; +}; + +/** + * Set the magnitude of this vector to the value used for the <b>len</b> + * parameter. + * + * @method setMag + * @param {number} len the new length for this vector + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(10, 20, 2); + * // v has compnents [10.0, 20.0, 2.0] + * v1.setMag(10); + * // v's compnents are set to [6.0, 8.0, 0.0] + * </code> + * </div> + */ +p5.Vector.prototype.setMag = function (n) { + return this.normalize().mult(n); +}; + +/** + * Calculate the angle of rotation for this vector (only 2D vectors) + * + * @method heading + * @return {Number} the angle of rotation + * @example + * <div class = "norender"><code> + * function setup() { + * var v1 = createVector(30,50); + * print(v1.heading()); // 1.0303768265243125 + * + * var v1 = createVector(40,50); + * print(v1.heading()); // 0.8960553845713439 + * + * var v1 = createVector(30,70); + * print(v1.heading()); // 1.1659045405098132 + * } + * </div></code> + */ +p5.Vector.prototype.heading = function () { + var h = Math.atan2(this.y, this.x); + if (this.p5) { + if (this.p5._angleMode === constants.RADIANS) { + return h; + } else { + return polarGeometry.radiansToDegrees(h); + } + } else { + return h; + } +}; + +/** + * Rotate the vector by an angle (only 2D vectors), magnitude remains the + * same + * + * @method rotate + * @param {number} angle the angle of rotation + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(10.0, 20.0); + * // v has compnents [10.0, 20.0, 0.0] + * v.rotate(HALF_PI); + * // v's compnents are set to [-20.0, 9.999999, 0.0] + * </code> + * </div> + */ +p5.Vector.prototype.rotate = function (a) { + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + a = polarGeometry.degreesToRadians(a); + } + } + var newHeading = this.heading() + a; + var mag = this.mag(); + this.x = Math.cos(newHeading) * mag; + this.y = Math.sin(newHeading) * mag; + return this; +}; + +/** + * Linear interpolate the vector to another vector + * + * @method lerp + * @param {p5.Vector} x the x component or the p5.Vector to lerp to + * @param {p5.Vector} [y] y the y component + * @param {p5.Vector} [z] z the z component + * @param {Number} amt the amount of interpolation; some value between 0.0 + * (old vector) and 1.0 (new vector). 0.1 is very near + * the new vector. 0.5 is halfway in between. + * @return {p5.Vector} the modified p5.Vector + * @example + * <div class="norender"> + * <code> + * var v = createVector(1, 1, 0); + * + * v.lerp(3, 3, 0, 0.5); // v now has components [2,2,0] + * </code> + * </div> + * + * <div class="norender"> + * <code> + * var v1 = createVector(0, 0, 0); + * var v2 = createVector(100, 100, 0); + * + * var v3 = p5.Vector.lerp(v1, v2, 0.5); + * // v3 has components [50,50,0] + * </code> + * </div> + */ +p5.Vector.prototype.lerp = function (x, y, z, amt) { + if (x instanceof p5.Vector) { + return this.lerp(x.x, x.y, x.z, y); + } + this.x += (x - this.x) * amt || 0; + this.y += (y - this.y) * amt || 0; + this.z += (z - this.z) * amt || 0; + return this; +}; + +/** + * Return a representation of this vector as a float array. This is only + * for temporary use. If used in any other fashion, the contents should be + * copied by using the <b>p5.Vector.copy()</b> method to copy into your own + * array. + * + * @method array + * @return {Array} an Array with the 3 values + * @example + * <div class = "norender"><code> + * function setup() { + * var v = createVector(20,30); + * print(v.array()); // Prints : Array [20, 30, 0] + * } + * </div></code> + * <div class="norender"> + * <code> + * var v = createVector(10.0, 20.0, 30.0); + * var f = v.array(); + * print(f[0]); // Prints "10.0" + * print(f[1]); // Prints "20.0" + * print(f[2]); // Prints "30.0" + * </code> + * </div> + */ +p5.Vector.prototype.array = function () { + return [this.x || 0, this.y || 0, this.z || 0]; +}; + +/** + * Equality check against a p5.Vector + * + * @method equals + * @param {Number|p5.Vector|Array} [x] the x component of the vector or a + * p5.Vector or an Array + * @param {Number} [y] the y component of the vector + * @param {Number} [z] the z component of the vector + * @return {Boolean} whether the vectors are equals + * @example + * <div class = "norender"><code> + * v1 = createVector(5,10,20); + * v2 = createVector(5,10,20); + * v3 = createVector(13,10,19); + * + * print(v1.equals(v2.x,v2.y,v2.z)); // true + * print(v1.equals(v3.x,v3.y,v3.z)); // false + * </div></code> + * <div class="norender"> + * <code> + * var v1 = createVector(10.0, 20.0, 30.0); + * var v2 = createVector(10.0, 20.0, 30.0); + * var v3 = createVector(0.0, 0.0, 0.0); + * print (v1.equals(v2)) // true + * print (v1.equals(v3)) // false + * </code> + * </div> + */ +p5.Vector.prototype.equals = function (x, y, z) { + var a, b, c; + if (x instanceof p5.Vector) { + a = x.x || 0; + b = x.y || 0; + c = x.z || 0; + } else if (x instanceof Array) { + a = x[0] || 0; + b = x[1] || 0; + c = x[2] || 0; + } else { + a = x || 0; + b = y || 0; + c = z || 0; + } + return this.x === a && this.y === b && this.z === c; +}; + + +// Static Methods + + +/** + * Make a new 2D unit vector from an angle + * + * @method fromAngle + * @static + * @param {Number} angle the desired angle + * @return {p5.Vector} the new p5.Vector object + * @example + * <div> + * <code> + * function draw() { + * background (200); + * + * // Create a variable, proportional to the mouseX, + * // varying from 0-360, to represent an angle in degrees. + * angleMode(DEGREES); + * var myDegrees = map(mouseX, 0,width, 0,360); + * + * // Display that variable in an onscreen text. + * // (Note the nfc() function to truncate additional decimal places, + * // and the "\xB0" character for the degree symbol.) + * var readout = "angle = " + nfc(myDegrees,1,1) + "\xB0" + * noStroke(); + * fill (0); + * text (readout, 5, 15); + * + * // Create a p5.Vector using the fromAngle function, + * // and extract its x and y components. + * var v = p5.Vector.fromAngle(radians(myDegrees)); + * var vx = v.x; + * var vy = v.y; + * + * push(); + * translate (width/2, height/2); + * noFill(); + * stroke (150); + * line (0,0, 30,0); + * stroke (0); + * line (0,0, 30*vx, 30*vy); + * pop() + * } + * </code> + * </div> + */ +p5.Vector.fromAngle = function(angle) { + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = polarGeometry.degreesToRadians(angle); + } + } + if (this.p5) { + return new p5.Vector(this.p5,[Math.cos(angle),Math.sin(angle),0]); + } else { + return new p5.Vector(Math.cos(angle),Math.sin(angle),0); + } +}; + +/** + * Make a new 2D unit vector from a random angle + * + * @method random2D + * @static + * @return {p5.Vector} the new p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v = p5.Vector.random2D(); + * // May make v's attributes something like: + * // [0.61554617, -0.51195765, 0.0] or + * // [-0.4695841, -0.14366731, 0.0] or + * // [0.6091097, -0.22805278, 0.0] + * </code> + * </div> + */ +p5.Vector.random2D = function () { + var angle; + // A lot of nonsense to determine if we know about a + // p5 sketch and whether we should make a random angle in degrees or radians + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = this.p5.random(360); + } else { + angle = this.p5.random(constants.TWO_PI); + } + } else { + angle = Math.random()*Math.PI*2; + } + return this.fromAngle(angle); +}; + +/** + * Make a new random 3D unit vector. + * + * @method random3D + * @static + * @return {p5.Vector} the new p5.Vector object + * @example + * <div class="norender"> + * <code> + * var v = p5.Vector.random3D(); + * // May make v's attributes something like: + * // [0.61554617, -0.51195765, 0.599168] or + * // [-0.4695841, -0.14366731, -0.8711202] or + * // [0.6091097, -0.22805278, -0.7595902] + * </code> + * </div> + */ +p5.Vector.random3D = function () { + var angle,vz; + // If we know about p5 + if (this.p5) { + angle = this.p5.random(0,constants.TWO_PI); + vz = this.p5.random(-1,1); + } else { + angle = Math.random()*Math.PI*2; + vz = Math.random()*2-1; + } + var vx = Math.sqrt(1-vz*vz)*Math.cos(angle); + var vy = Math.sqrt(1-vz*vz)*Math.sin(angle); + if (this.p5) { + return new p5.Vector(this.p5,[vx,vy,vz]); + } else { + return new p5.Vector(vx,vy,vz); + } +}; + + +/** + * Adds two vectors together and returns a new one. + * + * @static + * @param {p5.Vector} v1 a p5.Vector to add + * @param {p5.Vector} v2 a p5.Vector to add + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting p5.Vector + * + */ + +p5.Vector.add = function (v1, v2, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.add(v2); + return target; +}; + +/** + * Subtracts one p5.Vector from another and returns a new one. The second + * vector (v2) is subtracted from the first (v1), resulting in v1-v2. + * + * @static + * @param {p5.Vector} v1 a p5.Vector to subtract from + * @param {p5.Vector} v2 a p5.Vector to subtract + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting p5.Vector + */ + +p5.Vector.sub = function (v1, v2, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.sub(v2); + return target; +}; + + +/** + * Multiplies a vector by a scalar and returns a new vector. + * + * @static + * @param {p5.Vector} v the p5.Vector to multiply + * @param {Number} n the scalar + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting new p5.Vector + */ +p5.Vector.mult = function (v, n, target) { + if (!target) { + target = v.copy(); + } else { + target.set(v); + } + target.mult(n); + return target; +}; + +/** + * Divides a vector by a scalar and returns a new vector. + * + * @static + * @param {p5.Vector} v the p5.Vector to divide + * @param {Number} n the scalar + * @param {p5.Vector} target if undefined a new vector will be created + * @return {p5.Vector} the resulting new p5.Vector + */ +p5.Vector.div = function (v, n, target) { + if (!target) { + target = v.copy(); + } else { + target.set(v); + } + target.div(n); + return target; +}; + + +/** + * Calculates the dot product of two vectors. + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the dot product + */ +p5.Vector.dot = function (v1, v2) { + return v1.dot(v2); +}; + +/** + * Calculates the cross product of two vectors. + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the cross product + */ +p5.Vector.cross = function (v1, v2) { + return v1.cross(v2); +}; + +/** + * Calculates the Euclidean distance between two points (considering a + * point as a vector object). + * + * @static + * @param {p5.Vector} v1 the first p5.Vector + * @param {p5.Vector} v2 the second p5.Vector + * @return {Number} the distance + */ +p5.Vector.dist = function (v1,v2) { + return v1.dist(v2); +}; + +/** + * Linear interpolate a vector to another vector and return the result as a + * new vector. + * + * @static + * @param {p5.Vector} v1 a starting p5.Vector + * @param {p5.Vector} v2 the p5.Vector to lerp to + * @param {Number} the amount of interpolation; some value between 0.0 + * (old vector) and 1.0 (new vector). 0.1 is very near + * the new vector. 0.5 is halfway in between. + */ +p5.Vector.lerp = function (v1, v2, amt, target) { + if (!target) { + target = v1.copy(); + } else { + target.set(v1); + } + target.lerp(v2, amt); + return target; +}; + +/** + * Calculates and returns the angle (in radians) between two vectors. + * @method angleBetween + * @static + * @param {p5.Vector} v1 the x, y, and z components of a p5.Vector + * @param {p5.Vector} v2 the x, y, and z components of a p5.Vector + * @return {Number} the angle between (in radians) + * @example + * <div class="norender"> + * <code> + * var v1 = createVector(1, 0, 0); + * var v2 = createVector(0, 1, 0); + * + * var angle = p5.Vector.angleBetween(v1, v2); + * // angle is PI/2 + * </code> + * </div> + */ +p5.Vector.angleBetween = function (v1, v2) { + var angle = Math.acos(v1.dot(v2) / (v1.mag() * v2.mag())); + if (this.p5) { + if (this.p5._angleMode === constants.DEGREES) { + angle = polarGeometry.radiansToDegrees(angle); + } + } + return angle; +}; + +module.exports = p5.Vector; + +},{"../core/constants":47,"../core/core":48,"./polargeometry":77}],77:[function(_dereq_,module,exports){ + +module.exports = { + + degreesToRadians: function(x) { + return 2 * Math.PI * x / 360; + }, + + radiansToDegrees: function(x) { + return 360 * x / (2 * Math.PI); + } + +}; + +},{}],78:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Random + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +var seeded = false; + +// Linear Congruential Generator +// Variant of a Lehman Generator +var lcg = (function() { + // Set to values from http://en.wikipedia.org/wiki/Numerical_Recipes + // m is basically chosen to be large (as it is the max period) + // and for its relationships to a and c + var m = 4294967296, + // a - 1 should be divisible by m's prime factors + a = 1664525, + // c and m should be co-prime + c = 1013904223, + seed, z; + return { + setSeed : function(val) { + // pick a random seed if val is undefined or null + // the >>> 0 casts the seed to an unsigned 32-bit integer + z = seed = (val == null ? Math.random() * m : val) >>> 0; + }, + getSeed : function() { + return seed; + }, + rand : function() { + // define the recurrence relationship + z = (a * z + c) % m; + // return a float in [0, 1) + // if z = m then z / m = 0 therefore (z % m) / m < 1 always + return z / m; + } + }; +}()); + +/** + * Sets the seed value for random(). + * + * By default, random() produces different results each time the program + * is run. Set the seed parameter to a constant to return the same + * pseudo-random numbers each time the software is run. + * + * @method randomSeed + * @param {Number} seed the seed value + * @example + * <div> + * <code> + * randomSeed(99); + * for (var i=0; i < 100; i++) { + * var r = random(0, 255); + * stroke(r); + * line(i, 0, i, 100); + * } + * </code> + * </div> + */ +p5.prototype.randomSeed = function(seed) { + lcg.setSeed(seed); + seeded = true; +}; + +/** + * Return a random number. + * + * Takes either 0, 1 or 2 arguments. + * If no argument is given, returns a random number between 0 and 1. + * If one argument is given, returns a random number between 0 and the number. + * If two arguments are given, returns a random number between them, + * inclusive. + * + * @method random + * @param {Number} min the lower bound + * @param {Number} max the upper bound + * @return {Number} the random number + * @example + * <div> + * <code> + * for (var i = 0; i < 100; i++) { + * var r = random(50); + * stroke(r*5); + * line(50, i, 50+r, i); + * } + * </code> + * </div> + * <div> + * <code> + * for (var i = 0; i < 100; i++) { + * var r = random(-50, 50); + * line(50,i,50+r,i); + * } + * </code> + * </div> + * <div> + * <code> + * // Get a random element from an array + * var words = [ "apple", "bear", "cat", "dog" ]; + * var index = floor(random(words.length)); // Convert to integer + * text(words[index],10,50); // Displays one of the four words + * </code> + * </div> + */ +p5.prototype.random = function (min, max) { + + var rand; + + if (seeded) { + rand = lcg.rand(); + } else { + rand = Math.random(); + } + + if (arguments.length === 0) { + return rand; + } else + if (arguments.length === 1) { + return rand * min; + } else { + if (min > max) { + var tmp = min; + min = max; + max = tmp; + } + + return rand * (max-min) + min; + } +}; + + +/** + * + * Returns a random number fitting a Gaussian, or + * normal, distribution. There is theoretically no minimum or maximum + * value that randomGaussian() might return. Rather, there is + * just a very low probability that values far from the mean will be + * returned; and a higher probability that numbers near the mean will + * be returned. + * <br><br> + * Takes either 0, 1 or 2 arguments.<br> + * If no args, returns a mean of 0 and standard deviation of 1.<br> + * If one arg, that arg is the mean (standard deviation is 1).<br> + * If two args, first is mean, second is standard deviation. + * + * @method randomGaussian + * @param {Number} mean the mean + * @param {Number} sd the standard deviation + * @return {Number} the random number + * @example + * <div> + * <code>for (var y = 0; y < 100; y++) { + * var x = randomGaussian(50,15); + * line(50, y, x, y); + *} + * </code> + * </div> + * <div> + * <code> + *var distribution = new Array(360); + * + *function setup() { + * createCanvas(100, 100); + * for (var i = 0; i < distribution.length; i++) { + * distribution[i] = floor(randomGaussian(0,15)); + * } + *} + * + *function draw() { + * background(204); + * + * translate(width/2, width/2); + * + * for (var i = 0; i < distribution.length; i++) { + * rotate(TWO_PI/distribution.length); + * stroke(0); + * var dist = abs(distribution[i]); + * line(0, 0, dist, 0); + * } + *} + * </code> + * </div> + */ +var y2; +var previous = false; +p5.prototype.randomGaussian = function(mean, sd) { + var y1,x1,x2,w; + if (previous) { + y1 = y2; + previous = false; + } else { + do { + x1 = this.random(2) - 1; + x2 = this.random(2) - 1; + w = x1 * x1 + x2 * x2; + } while (w >= 1); + w = Math.sqrt((-2 * Math.log(w))/w); + y1 = x1 * w; + y2 = x2 * w; + previous = true; + } + + var m = mean || 0; + var s = sd || 1; + return y1*s + m; +}; + +module.exports = p5; + +},{"../core/core":48}],79:[function(_dereq_,module,exports){ +/** + * @module Math + * @submodule Trigonometry + * @for p5 + * @requires core + * @requires polargeometry + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var polarGeometry = _dereq_('./polargeometry'); +var constants = _dereq_('../core/constants'); + +p5.prototype._angleMode = constants.RADIANS; + +/** + * The inverse of cos(), returns the arc cosine of a value. This function + * expects the values in the range of -1 to 1 and values are returned in + * the range 0 to PI (3.1415927). + * + * @method acos + * @param {Number} value the value whose arc cosine is to be returned + * @return {Number} the arc cosine of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI; + * var c = cos(a); + * var ac = acos(c); + * // Prints: "3.1415927 : -1.0 : 3.1415927" + * println(a + " : " + c + " : " + ac); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/4.0; + * var c = cos(a); + * var ac = acos(c); + * // Prints: "3.926991 : -0.70710665 : 2.3561943" + * println(a + " : " + c + " : " + ac); + * </code> + * </div> + */ +p5.prototype.acos = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.acos(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.acos(ratio)); + } +}; + +/** + * The inverse of sin(), returns the arc sine of a value. This function + * expects the values in the range of -1 to 1 and values are returned + * in the range -PI/2 to PI/2. + * + * @method asin + * @param {Number} value the value whose arc sine is to be returned + * @return {Number} the arc sine of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI + PI/3; + * var s = sin(a); + * var as = asin(s); + * // Prints: "1.0471976 : 0.86602545 : 1.0471976" + * println(a + " : " + s + " : " + as); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/3.0; + * var s = sin(a); + * var as = asin(s); + * // Prints: "4.1887903 : -0.86602545 : -1.0471976" + * println(a + " : " + s + " : " + as); + * </code> + * </div> + * + */ +p5.prototype.asin = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.asin(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.asin(ratio)); + } +}; + +/** + * The inverse of tan(), returns the arc tangent of a value. This function + * expects the values in the range of -Infinity to Infinity (exclusive) and + * values are returned in the range -PI/2 to PI/2. + * + * @method atan + * @param {Number} value the value whose arc tangent is to be returned + * @return {Number} the arc tangent of the given value + * + * @example + * <div class= “norender"> + * <code> + * var a = PI + PI/3; + * var t = tan(a); + * var at = atan(t); + * // Prints: "1.0471976 : 1.7320509 : 1.0471976" + * println(a + " : " + t + " : " + at); + * </code> + * </div> + * + * <div class= “norender"> + * <code> + * var a = PI + PI/3.0; + * var t = tan(a); + * var at = atan(t); + * // Prints: "4.1887903 : 1.7320513 : 1.0471977" + * println(a + " : " + t + " : " + at); + * </code> + * </div> + * + */ +p5.prototype.atan = function(ratio) { + if (this._angleMode === constants.RADIANS) { + return Math.atan(ratio); + } else { + return polarGeometry.radiansToDegrees(Math.atan(ratio)); + } +}; + +/** + * Calculates the angle (in radians) from a specified point to the coordinate + * origin as measured from the positive x-axis. Values are returned as a + * float in the range from PI to -PI. The atan2() function is most often used + * for orienting geometry to the position of the cursor. + * <br><br> + * Note: The y-coordinate of the point is the first parameter, and the + * x-coordinate is the second parameter, due the the structure of calculating + * the tangent. + * + * @method atan2 + * @param {Number} y y-coordinate of the point + * @param {Number} x x-coordinate of the point + * @return {Number} the arc tangent of the given point + * + * @example + * <div> + * <code> + * function draw() { + * background(204); + * translate(width/2, height/2); + * var a = atan2(mouseY-height/2, mouseX-width/2); + * rotate(a); + * rect(-30, -5, 60, 10); + * } + * </code> + * </div> + */ +p5.prototype.atan2 = function (y, x) { + if (this._angleMode === constants.RADIANS) { + return Math.atan2(y, x); + } else { + return polarGeometry.radiansToDegrees(Math.atan2(y, x)); + } +}; + +/** + * Calculates the cosine of an angle. This function takes into account the + * current angleMode. Values are returned in the range -1 to 1. + * + * @method cos + * @param {Number} angle the angle + * @return {Number} the cosine of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/25.0; + * for (var i = 0; i < 25; i++) { + * line(i*4, 50, i*4, 50+cos(a)*40.0); + * a = a + inc; + * } + * </code> + * </div> + * + */ +p5.prototype.cos = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.cos(angle); + } else { + return Math.cos(this.radians(angle)); + } +}; + +/** + * Calculates the sine of an angle. This function takes into account the + * current angleMode. Values are returned in the range -1 to 1. + * + * @method sin + * @param {Number} angle the angle + * @return {Number} the sine of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/25.0; + * for (var i = 0; i < 25; i++) { + * line(i*4, 50, i*4, 50+sin(a)*40.0); + * a = a + inc; + * } + * </code> + * </div> + */ +p5.prototype.sin = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.sin(angle); + } else { + return Math.sin(this.radians(angle)); + } +}; + +/** + * Calculates the tangent of an angle. This function takes into account + * the current angleMode. Values are returned in the range -1 to 1. + * + * @method tan + * @param {Number} angle the angle + * @return {Number} the tangent of the angle + * + * @example + * <div> + * <code> + * var a = 0.0; + * var inc = TWO_PI/50.0; + * for (var i = 0; i < 100; i = i+2) { + * line(i, 50, i, 50+tan(a)*2.0); + * a = a + inc; + * } + * </code> + * </div> + * + */ +p5.prototype.tan = function(angle) { + if (this._angleMode === constants.RADIANS) { + return Math.tan(angle); + } else { + return Math.tan(this.radians(angle)); + } +}; + +/** + * Converts a radian measurement to its corresponding value in degrees. + * Radians and degrees are two ways of measuring the same thing. There are + * 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90° = PI/2 = 1.5707964. + * + * @method degrees + * @param {Number} radians the radians value to convert to degrees + * @return {Number} the converted angle + * + * + * @example + * <div class= “norender"> + * <code> + * var rad = PI/4; + * var deg = degrees(rad); + * println(rad + " radians is " + deg + " degrees"); + * // Prints: 45 degrees is 0.7853981633974483 radians + * </code> + * </div> + * + */ +p5.prototype.degrees = function(angle) { + return polarGeometry.radiansToDegrees(angle); +}; + +/** + * Converts a degree measurement to its corresponding value in radians. + * Radians and degrees are two ways of measuring the same thing. There are + * 360 degrees in a circle and 2*PI radians in a circle. For example, + * 90° = PI/2 = 1.5707964. + * + * @method radians + * @param {Number} degrees the degree value to convert to radians + * @return {Number} the converted angle + * + * @example + * <div class= “norender"> + * <code> + * var deg = 45.0; + * var rad = radians(deg); + * println(deg + " degrees is " + rad + " radians"); + * // Prints: 45 degrees is 0.7853981633974483 radians + * </code> + * </div> + */ +p5.prototype.radians = function(angle) { + return polarGeometry.degreesToRadians(angle); +}; + +/** + * Sets the current mode of p5 to given mode. Default mode is RADIANS. + * + * @method angleMode + * @param {Number/Constant} mode either RADIANS or DEGREES + * + * @example + * <div> + * <code> + * function draw(){ + * background(204); + * angleMode(DEGREES); // Change the mode to DEGREES + * var a = atan2(mouseY-height/2, mouseX-width/2); + * translate(width/2, height/2); + * push(); + * rotate(a); + * rect(-20, -5, 40, 10); // Larger rectangle is rotating in degrees + * pop(); + * angleMode(RADIANS); // Change the mode to RADIANS + * rotate(a); // var a stays the same + * rect(-40, -5, 20, 10); // Smaller rectangle is rotating in radians + * } + * </code> + * </div> + * + */ +p5.prototype.angleMode = function(mode) { + if (mode === constants.DEGREES || mode === constants.RADIANS) { + this._angleMode = mode; + } +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"./polargeometry":77}],80:[function(_dereq_,module,exports){ +/** + * @module Typography + * @submodule Attributes + * @for p5 + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Sets the current alignment for drawing text. The parameters LEFT, CENTER, + * and RIGHT set the alignment of text in relation to the values for + * the x and y parameters of the text() function. + * + * @method textAlign + * @param {Number/Constant} horizAlign horizontal alignment, either LEFT, + * CENTER, or RIGHT + * @param {Number/Constant} vertAlign vertical alignment, either TOP, + * BOTTOM, CENTER, or BASELINE + * @return {Number} + * @example + * <div> + * <code> + * textSize(16); + * textAlign(RIGHT); + * text("ABCD", 50, 30); + * textAlign(CENTER); + * text("EFGH", 50, 50); + * textAlign(LEFT); + * text("IJKL", 50, 70); + * </code> + * </div> + */ +p5.prototype.textAlign = function(horizAlign, vertAlign) { + return this._renderer.textAlign.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the spacing, in pixels, between lines of text. This + * setting will be used in all subsequent calls to the text() function. + * + * @method textLeading + * @param {Number} leading the size in pixels for spacing between lines + * @return {Object|Number} + * @example + * <div> + * <code> + * // Text to display. The "\n" is a "new line" character + * lines = "L1\nL2\nL3"; + * textSize(12); + * + * textLeading(10); // Set leading to 10 + * text(lines, 10, 25); + * + * textLeading(20); // Set leading to 20 + * text(lines, 40, 25); + * + * textLeading(30); // Set leading to 30 + * text(lines, 70, 25); + * </code> + * </div> + */ +p5.prototype.textLeading = function(theLeading) { + return this._renderer.textLeading.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the current font size. This size will be used in all subsequent + * calls to the text() function. Font size is measured in pixels. + * + * @method textSize + * @param {Number} theSize the size of the letters in units of pixels + * @return {Object|Number} + * @example + * <div> + * <code> + * textSize(12); + * text("Font Size 12", 10, 30); + * textSize(14); + * text("Font Size 14", 10, 60); + * textSize(16); + * text("Font Size 16", 10, 90); + * </code> + * </div> + */ +p5.prototype.textSize = function(theSize) { + return this._renderer.textSize.apply(this._renderer, arguments); +}; + +/** + * Sets/gets the style of the text for system fonts to NORMAL, ITALIC, or BOLD. + * Note: this may be is overridden by CSS styling. For non-system fonts + * (opentype, truetype, etc.) please load styled fonts instead. + * + * @method textStyle + * @param {Number/Constant} theStyle styling for text, either NORMAL, + * ITALIC, or BOLD + * @return {Object|String} + * @example + * <div> + * <code> + * strokeWeight(0); + * textSize(12); + * textStyle(NORMAL); + * text("Font Style Normal", 10, 30); + * textStyle(ITALIC); + * text("Font Style Italic", 10, 60); + * textStyle(BOLD); + * text("Font Style Bold", 10, 90); + * </code> + * </div> + */ +p5.prototype.textStyle = function(theStyle) { + return this._renderer.textStyle.apply(this._renderer, arguments); +}; + +/** + * Calculates and returns the width of any character or text string. + * + * @method textWidth + * @param {String} theText the String of characters to measure + * @return {Number} + * @example + * <div> + * <code> + * textSize(28); + * + * var aChar = 'P'; + * var cWidth = textWidth(aChar); + * text(aChar, 0, 40); + * line(cWidth, 0, cWidth, 50); + * + * var aString = "p5.js"; + * var sWidth = textWidth(aString); + * text(aString, 0, 85); + * line(sWidth, 50, sWidth, 100); + * </code> + * </div> + */ +p5.prototype.textWidth = function(theText) { + return this._renderer.textWidth.apply(this._renderer, arguments); +}; + +/** + * Returns the ascent of the current font at its current size. The ascent + * represents the distance, in pixels, of the tallest character above + * the baseline. + * + * @return {Number} + * @example + * <div> + * <code> + * var base = height * 0.75; + * var scalar = 0.8; // Different for each font + * + * textSize(32); // Set initial text size + * var asc = textAscent() * scalar; // Calc ascent + * line(0, base - asc, width, base - asc); + * text("dp", 0, base); // Draw text on baseline + * + * textSize(64); // Increase text size + * asc = textAscent() * scalar; // Recalc ascent + * line(40, base - asc, width, base - asc); + * text("dp", 40, base); // Draw text on baseline + * </code> + * </div> + */ +p5.prototype.textAscent = function() { + return this._renderer.textAscent(); +}; + +/** + * Returns the descent of the current font at its current size. The descent + * represents the distance, in pixels, of the character with the longest + * descender below the baseline. + * + * @return {Number} + * @example + * <div> + * <code> + * var base = height * 0.75; + * var scalar = 0.8; // Different for each font + * + * textSize(32); // Set initial text size + * var desc = textDescent() * scalar; // Calc ascent + * line(0, base+desc, width, base+desc); + * text("dp", 0, base); // Draw text on baseline + * + * textSize(64); // Increase text size + * desc = textDescent() * scalar; // Recalc ascent + * line(40, base + desc, width, base + desc); + * text("dp", 40, base); // Draw text on baseline + * </code> + * </div> + */ +p5.prototype.textDescent = function() { + return this._renderer.textDescent(); +}; + +/** + * Helper function to measure ascent and descent. + */ +p5.prototype._updateTextMetrics = function() { + return this._renderer._updateTextMetrics(); +}; + +module.exports = p5; + +},{"../core/core":48}],81:[function(_dereq_,module,exports){ +/** + * @module Typography + * @submodule Loading & Displaying + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +_dereq_('../core/error_helpers'); + + +/** + * Draws text to the screen. Displays the information specified in the first + * parameter on the screen in the position specified by the additional + * parameters. A default font will be used unless a font is set with the + * textFont() function and a default size will be used unless a font is set + * with textSize(). Change the color of the text with the fill() function. + * Change the outline of the text with the stroke() and strokeWeight() + * functions. + * <br><br> + * The text displays in relation to the textAlign() function, which gives the + * option to draw to the left, right, and center of the coordinates. + * <br><br> + * The x2 and y2 parameters define a rectangular area to display within and + * may only be used with string data. When these parameters are specified, + * they are interpreted based on the current rectMode() setting. Text that + * does not fit completely within the rectangle specified will not be drawn + * to the screen. + * + * @method text + * @param {String} str the alphanumeric symbols to be displayed + * @param {Number} x x-coordinate of text + * @param {Number} y y-coordinate of text + * @param {Number} x2 by default, the width of the text box, + * see rectMode() for more info + * @param {Number} y2 by default, the height of the text box, + * see rectMode() for more info + * @return {Object} this + * @example + * <div> + * <code> + * textSize(32); + * text("word", 10, 30); + * fill(0, 102, 153); + * text("word", 10, 60); + * fill(0, 102, 153, 51); + * text("word", 10, 90); + * </code> + * </div> + * <div> + * <code> + * s = "The quick brown fox jumped over the lazy dog."; + * fill(50); + * text(s, 10, 10, 70, 80); // Text wraps within text box + * </code> + * </div> + */ +p5.prototype.text = function(str, x, y, maxWidth, maxHeight) { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + this._validateParameters( + 'text', + args, + [ + ['*', 'Number', 'Number'], + ['*', 'Number', 'Number', 'Number', 'Number'] + ] + ); + + return (!(this._renderer._doFill || this._renderer._doStroke)) ? this : + this._renderer.text.apply(this._renderer, arguments); +}; + +/** + * Sets the current font that will be drawn with the text() function. + * + * @method textFont + * @param {Object|String} f a font loaded via loadFont(), or a String + * representing a browser-based dfault font. + * @return {Object} this + * @example + * <div> + * <code> + * fill(0); + * textSize(12); + * textFont("Georgia"); + * text("Georgia", 12, 30); + * textFont("Helvetica"); + * text("Helvetica", 12, 60); + * </code> + * </div> + * <div> + * <code> + * var fontRegular, fontItalic, fontBold; + * function preload() { + * fontRegular = loadFont("assets/Regular.otf"); + * fontItalic = loadFont("assets/Italic.ttf"); + * fontBold = loadFont("assets/Bold.ttf"); + * } + * function setup() { + * background(210); + * fill(0).strokeWeight(0).textSize(10); + * textFont(fontRegular); + * text("Font Style Normal", 10, 30); + * textFont(fontItalic); + * text("Font Style Italic", 10, 50); + * textFont(fontBold); + * text("Font Style Bold", 10, 70); + * } + * </code> + * </div> + */ +p5.prototype.textFont = function(theFont, theSize) { + + if (arguments.length) { + + if (!theFont) { + + throw Error('null font passed to textFont'); + } + + this._renderer._setProperty('_textFont', theFont); + + if (theSize) { + + this._renderer._setProperty('_textSize', theSize); + this._renderer._setProperty('_textLeading', + theSize * constants._DEFAULT_LEADMULT); + } + + return this._renderer._applyTextProperties(); + } + + return this; +}; + +module.exports = p5; + +},{"../core/constants":47,"../core/core":48,"../core/error_helpers":51}],82:[function(_dereq_,module,exports){ +/** + * This module defines the p5.Font class and functions for + * drawing text to the display canvas. + * @module Typography + * @submodule Font + * @requires core + * @requires constants + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); +var constants = _dereq_('../core/constants'); + +/* + * TODO: + * + * API: + * -- textBounds() + * -- getPath() + * -- getPoints() + * + * =========================================== + * -- PFont functions: + * PFont.list() + * + * -- kerning + * -- alignment: justified? + * -- integrate p5.dom? (later) + */ + +/** + * Base class for font handling + * @class p5.Font + * @constructor + * @param {Object} [pInst] pointer to p5 instance + */ +p5.Font = function(p) { + + this.parent = p; + + this.cache = {}; + + /** + * Underlying opentype font implementation + * @property font + */ + this.font = undefined; +}; + +p5.Font.prototype.list = function() { + + // TODO + throw 'not yet implemented'; +}; + +/** + * Returns a tight bounding box for the given text string using this + * font (currently only supports single lines) + * + * @method textBounds + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Number} fontSize font size to use (optional) + * @param {Object} options opentype options (optional) + * + * @return {Object} a rectangle object with properties: x, y, w, h + * + * @example + * <div> + * <code> + * var font; + * var textString = 'Lorem ipsum dolor sit amet.'; + * function preload() { + * font = loadFont('./assets/Regular.otf'); + * }; + * function setup() { + * background(210); + * + * var bbox = font.textBounds(textString, 10, 30, 12); + * fill(255); + * stroke(0); + * rect(bbox.x, bbox.y, bbox.w, bbox.h); + * fill(0); + * noStroke(); + * + * textFont(font); + * textSize(12); + * text(textString, 10, 30); + * }; + * </code> + * </div> + */ +p5.Font.prototype.textBounds = function(str, x, y, fontSize, options) { + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize || this.parent._renderer._textSize; + + var result = this.cache[cacheKey('textBounds', str, x, y, fontSize)]; + if (!result) { + + var xCoords = [], yCoords = [], self = this, + scale = this._scale(fontSize), minX, minY, maxX, maxY; + + this.font.forEachGlyph(str, x, y, fontSize, options, + function(glyph, gX, gY, gFontSize) { + + xCoords.push(gX); + yCoords.push(gY); + + var gm = glyph.getMetrics(); + + if (glyph.name !== 'space') { + + xCoords.push(gX + (gm.xMax * scale)); + yCoords.push(gY + (-gm.yMin * scale)); + yCoords.push(gY + (-gm.yMax * scale)); + + } else { // NOTE: deals with broken metrics for spaces in opentype.js + + xCoords.push(gX + self.font.charToGlyph(' ').advanceWidth * + self._scale(fontSize)); + } + }); + + minX = Math.max(0, Math.min.apply(null, xCoords)); + minY = Math.max(0, Math.min.apply(null, yCoords)); + maxX = Math.max(0, Math.max.apply(null, xCoords)); + maxY = Math.max(0, Math.max.apply(null, yCoords)); + + result = { + x: minX, + y: minY, + h: maxY - minY, + w: maxX - minX, + advance: minX - x + }; + + this.cache[cacheKey('textBounds', str, x, y, fontSize)] = result; + } + //else console.log('cache-hit'); + + return result; +}; + + +/** + * Computes an array of points following the path for specified text + * + * @param {String} txt a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Number} fontSize font size to use (optional) + * @param {Object} options an (optional) object that can contain: + * + * <br>sampleFactor - the ratio of path-length to number of samples + * (default=.25); higher values yield more points and are therefore + * more precise + * + * <br>simplifyThreshold - if set to a non-zero value, collinear points will be + * be removed from the polygon; the value represents the threshold angle to use + * when determining whether two edges are collinear + * + * @return {Array} an array of points, each with x, y, alpha (the path angle) + */ +p5.Font.prototype.textToPoints = function(txt, x, y, fontSize, options) { + + var xoff = 0, result = [], glyphs = this._getGlyphs(txt); + + fontSize = fontSize || this.parent._renderer._textSize; + + for (var i = 0; i < glyphs.length; i++) { + + var gpath = glyphs[i].getPath(x, y, fontSize), + paths = splitPaths(gpath.commands); + + for (var j = 0; j < paths.length; j++) { + + var pts = pathToPoints(paths[j], options); + + for (var k = 0; k < pts.length; k++) { + pts[k].x += xoff; + result.push(pts[k]); + } + } + + xoff += glyphs[i].advanceWidth * this._scale(fontSize); + } + + return result; +}; + +// ----------------------------- End API ------------------------------ + +/** + * Returns the set of opentype glyphs for the supplied string. + * + * Note that there is not a strict one-to-one mapping between characters + * and glyphs, so the list of returned glyphs can be larger or smaller + * than the length of the given string. + * + * @param {String} str the string to be converted + * @return {array} the opentype glyphs + */ +p5.Font.prototype._getGlyphs = function(str) { + + return this.font.stringToGlyphs(str); +}; + +/** + * Returns an opentype path for the supplied string and position. + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional) + * @return {Object} the opentype path + */ +p5.Font.prototype._getPath = function(line, x, y, options) { + + var p = this.parent, + ctx = p._renderer.drawingContext, + pos = this._handleAlignment(p, ctx, line, x, y); + + return this.font.getPath(line, pos.x, pos.y, p._renderer._textSize, options); +}; + +/* + * Creates an SVG-formatted path-data string + * (See http://www.w3.org/TR/SVG/paths.html#PathData) + * from the given opentype path or string/position + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional), set options.decimals + * to set the decimal precision of the path-data + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._getPathData = function(line, x, y, options) { + + var decimals = 3; + + // create path from string/position + if (typeof line === 'string' && arguments.length > 2) { + + line = this._getPath(line, x, y, options); + } + // handle options specified in 2nd arg + else if (typeof x === 'object') { + + options = x; + } + + // handle svg arguments + if (options && typeof options.decimals === 'number') { + + decimals = options.decimals; + } + + return line.toPathData(decimals); +}; + +/* + * Creates an SVG <path> element, as a string, + * from the given opentype path or string/position + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional), set options.decimals + * to set the decimal precision of the path-data in the <path> element, + * options.fill to set the fill color for the <path> element, + * options.stroke to set the stroke color for the <path> element, + * options.strokeWidth to set the strokeWidth for the <path> element. + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._getSVG = function(line, x, y, options) { + + var decimals = 3; + + // create path from string/position + if (typeof line === 'string' && arguments.length > 2) { + + line = this._getPath(line, x, y, options); + } + // handle options specified in 2nd arg + else if (typeof x === 'object') { + + options = x; + } + + // handle svg arguments + if (options) { + if (typeof options.decimals === 'number') { + decimals = options.decimals; + } + if (typeof options.strokeWidth === 'number') { + line.strokeWidth = options.strokeWidth; + } + if (typeof options.fill !== 'undefined') { + line.fill = options.fill; + } + if (typeof options.stroke !== 'undefined') { + line.stroke = options.stroke; + } + } + + return line.toSVG(decimals); +}; + +/* + * Renders an opentype path or string/position + * to the current graphics context + * + * @param {Object} path an opentype path, OR the following: + * + * @param {String} line a line of text + * @param {Number} x x-position + * @param {Number} y y-position + * @param {Object} options opentype options (optional) + * + * @return {Object} this p5.Font object + */ +p5.Font.prototype._renderPath = function(line, x, y, options) { + + var pdata, pg = (options && options.renderer) || this.parent._renderer, + ctx = pg.drawingContext; + + if (typeof line === 'object' && line.commands) { + + pdata = line.commands; + } else { + + //pos = handleAlignment(p, ctx, line, x, y); + pdata = this._getPath(line, x, y, pg._textSize, options).commands; + } + + ctx.beginPath(); + for (var i = 0; i < pdata.length; i += 1) { + + var cmd = pdata[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + // only draw stroke if manually set by user + if (pg._doStroke && pg._strokeSet) { + + ctx.stroke(); + } + + if (pg._doFill) { + + // if fill hasn't been set by user, use default-text-fill + ctx.fillStyle = pg._fillSet ? ctx.fillStyle : constants._DEFAULT_TEXT_FILL; + ctx.fill(); + } + + return this; +}; + +p5.Font.prototype._textWidth = function(str, fontSize) { + + if (str === ' ') { // special case for now + + return this.font.charToGlyph(' ').advanceWidth * this._scale(fontSize); + } + + var bounds = this.textBounds(str, 0, 0, fontSize); + return bounds.w + bounds.advance; +}; + +p5.Font.prototype._textAscent = function(fontSize) { + + return this.font.ascender * this._scale(fontSize); +}; + +p5.Font.prototype._textDescent = function(fontSize) { + + return -this.font.descender * this._scale(fontSize); +}; + +p5.Font.prototype._scale = function(fontSize) { + + return (1 / this.font.unitsPerEm) * (fontSize || + this.parent._renderer._textSize); +}; + +p5.Font.prototype._handleAlignment = function(p, ctx, line, x, y) { + + var textWidth = this._textWidth(line), + textAscent = this._textAscent(), + textDescent = this._textDescent(), + textHeight = textAscent + textDescent; + + if (ctx.textAlign === constants.CENTER) { + x -= textWidth / 2; + } else if (ctx.textAlign === constants.RIGHT) { + x -= textWidth; + } + + if (ctx.textBaseline === constants.TOP) { + y += textHeight; + } else if (ctx.textBaseline === constants._CTX_MIDDLE) { + y += textHeight / 2 - textDescent; + } else if (ctx.textBaseline === constants.BOTTOM) { + y -= textDescent; + } + + return { x: x, y: y }; +}; + +// path-utils + +function pathToPoints(cmds, options) { + + var opts = parseOpts(options, { + sampleFactor: 0.1, + simplifyThreshold: 0, + }); + + var len = pointAtLength(cmds,0,1), // total-length + t = len / (len * opts.sampleFactor), + pts = []; + + for (var i = 0; i < len; i += t) { + pts.push(pointAtLength(cmds, i)); + } + + if (opts.simplifyThreshold) { + /*var count = */simplify(pts, opts.simplifyThreshold); + //console.log('Simplify: removed ' + count + ' pts'); + } + + return pts; +} + +function simplify(pts, angle) { + + angle = (typeof angle === 'undefined') ? 0 : angle; + + var num = 0; + for (var i = pts.length - 1; pts.length > 3 && i >= 0; --i) { + + if (collinear(at(pts, i - 1), at(pts, i), at(pts, i + 1), angle)) { + + // Remove the middle point + pts.splice(i % pts.length, 1); + num++; + } + } + return num; +} + +function splitPaths(cmds) { + + var paths = [], current; + for (var i = 0; i < cmds.length; i++) { + if (cmds[i].type === 'M') { + if (current) { + paths.push(current); + } + current = []; + } + current.push(cmdToArr(cmds[i])); + } + paths.push(current); + + return paths; +} + +function cmdToArr(cmd) { + + var arr = [ cmd.type ]; + if (cmd.type === 'M' || cmd.type === 'L') { // moveto or lineto + arr.push(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + arr.push(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + arr.push(cmd.x1, cmd.y1, cmd.x, cmd.y); + } + // else if (cmd.type === 'Z') { /* no-op */ } + return arr; +} + +function parseOpts(options, defaults) { + + if (typeof options !== 'object') { + options = defaults; + } + else { + for (var key in defaults) { + if (typeof options[key] === 'undefined') { + options[key] = defaults[key]; + } + } + } + return options; +} + +//////////////////////// Helpers //////////////////////////// + +function at(v, i) { + var s = v.length; + return v[i < 0 ? i % s + s : i % s]; +} + +function collinear(a, b, c, thresholdAngle) { + + if (!thresholdAngle) { + return areaTriangle(a, b, c) === 0; + } + + if (typeof collinear.tmpPoint1 === 'undefined') { + collinear.tmpPoint1 = []; + collinear.tmpPoint2 = []; + } + + var ab = collinear.tmpPoint1, bc = collinear.tmpPoint2; + ab.x = b.x - a.x; + ab.y = b.y - a.y; + bc.x = c.x - b.x; + bc.y = c.y - b.y; + + var dot = ab.x * bc.x + ab.y * bc.y, + magA = Math.sqrt(ab.x * ab.x + ab.y * ab.y), + magB = Math.sqrt(bc.x * bc.x + bc.y * bc.y), + angle = Math.acos(dot / (magA * magB)); + + return angle < thresholdAngle; +} + +function areaTriangle(a, b, c) { + return (((b[0] - a[0]) * (c[1] - a[1])) - ((c[0] - a[0]) * (b[1] - a[1]))); +} + +// Portions of below code copyright 2008 Dmitry Baranovskiy (via MIT license) + +function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { + + var t1 = 1 - t, t13 = Math.pow(t1, 3), t12 = Math.pow(t1, 2), t2 = t * t, + t3 = t2 * t, x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + + t3 * p2x, y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + + t3 * p2y, mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x), + my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y), + nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x), + ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y), + ax = t1 * p1x + t * c1x, ay = t1 * p1y + t * c1y, + cx = t1 * c2x + t * p2x, cy = t1 * c2y + t * p2y, + alpha = (90 - Math.atan2(mx - nx, my - ny) * 180 / Math.PI); + + if (mx > nx || my < ny) { alpha += 180; } + + return { x: x, y: y, m: { x: mx, y: my }, n: { x: nx, y: ny }, + start: { x: ax, y: ay }, end: { x: cx, y: cy }, alpha: alpha + }; +} + +function getPointAtSegmentLength(p1x,p1y,c1x,c1y,c2x,c2y,p2x,p2y,length) { + return (length == null) ? bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) : + findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, + getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length)); +} + +function pointAtLength(path, length, istotal) { + path = path2curve(path); + var x, y, p, l, sp = '', subpaths = {}, point, len = 0; + for (var i = 0, ii = path.length; i < ii; i++) { + p = path[i]; + if (p[0] === 'M') { + x = +p[1]; + y = +p[2]; + } else { + l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]); + if (len + l > length) { + if (!istotal) { + point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], + p[6], length - len); + return { x: point.x, y: point.y, alpha: point.alpha }; + } + } + len += l; + x = +p[5]; + y = +p[6]; + } + sp += p.shift() + p; + } + subpaths.end = sp; + + point = istotal ? len : findDotsAtSegment + (x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1); + + if (point.alpha) { + point = { x: point.x, y: point.y, alpha: point.alpha }; + } + + return point; +} + +function pathToAbsolute(pathArray) { + + var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0; + if (pathArray[0][0] === 'M') { + x = +pathArray[0][1]; + y = +pathArray[0][2]; + mx = x; + my = y; + start++; + res[0] = ['M', x, y]; + } + + var dots,crz = pathArray.length===3 && pathArray[0][0]==='M' && + pathArray[1][0].toUpperCase()==='R' && pathArray[2][0].toUpperCase()==='Z'; + + for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) { + res.push(r = []); + pa = pathArray[i]; + if (pa[0] !== String.prototype.toUpperCase.call(pa[0])) { + r[0] = String.prototype.toUpperCase.call(pa[0]); + switch (r[0]) { + case 'A': + r[1] = pa[1]; + r[2] = pa[2]; + r[3] = pa[3]; + r[4] = pa[4]; + r[5] = pa[5]; + r[6] = +(pa[6] + x); + r[7] = +(pa[7] + y); + break; + case 'V': + r[1] = +pa[1] + y; + break; + case 'H': + r[1] = +pa[1] + x; + break; + case 'R': + dots = [x, y].concat(pa.slice(1)); + for (var j = 2, jj = dots.length; j < jj; j++) { + dots[j] = +dots[j] + x; + dots[++j] = +dots[j] + y; + } + res.pop(); + res = res.concat(catmullRom2bezier(dots, crz)); + break; + case 'M': + mx = +pa[1] + x; + my = +pa[2] + y; + break; + default: + for (j = 1, jj = pa.length; j < jj; j++) { + r[j] = +pa[j] + ((j % 2) ? x : y); + } + } + } else if (pa[0] === 'R') { + dots = [x, y].concat(pa.slice(1)); + res.pop(); + res = res.concat(catmullRom2bezier(dots, crz)); + r = ['R'].concat(pa.slice(-2)); + } else { + for (var k = 0, kk = pa.length; k < kk; k++) { + r[k] = pa[k]; + } + } + switch (r[0]) { + case 'Z': + x = mx; + y = my; + break; + case 'H': + x = r[1]; + break; + case 'V': + y = r[1]; + break; + case 'M': + mx = r[r.length - 2]; + my = r[r.length - 1]; + break; + default: + x = r[r.length - 2]; + y = r[r.length - 1]; + } + } + return res; +} + +function path2curve(path, path2) { + + var p = pathToAbsolute(path), p2 = path2 && pathToAbsolute(path2), + attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + attrs2 = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, + + processPath = function(path, d, pcom) { + var nx, ny, tq = { T: 1, Q: 1 }; + if (!path) { return ['C', d.x, d.y, d.x, d.y, d.x, d.y]; } + if (!(path[0] in tq)) { d.qx = d.qy = null; } + switch (path[0]) { + case 'M': + d.X = path[1]; + d.Y = path[2]; + break; + case 'A': + path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))); + break; + case 'S': + if (pcom === 'C' || pcom === 'S') { + nx = d.x * 2 - d.bx; + ny = d.y * 2 - d.by; + } else { + nx = d.x; + ny = d.y; + } + path = ['C', nx, ny].concat(path.slice(1)); + break; + case 'T': + if (pcom === 'Q' || pcom === 'T') { + d.qx = d.x * 2 - d.qx; + d.qy = d.y * 2 - d.qy; + } else { + d.qx = d.x; + d.qy = d.y; + } + path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])); + break; + case 'Q': + d.qx = path[1]; + d.qy = path[2]; + path = ['C'].concat(q2c(d.x,d.y,path[1],path[2],path[3],path[4])); + break; + case 'L': + path = ['C'].concat(l2c(d.x, d.y, path[1], path[2])); + break; + case 'H': + path = ['C'].concat(l2c(d.x, d.y, path[1], d.y)); + break; + case 'V': + path = ['C'].concat(l2c(d.x, d.y, d.x, path[1])); + break; + case 'Z': + path = ['C'].concat(l2c(d.x, d.y, d.X, d.Y)); + break; + } + return path; + }, + + fixArc = function(pp, i) { + if (pp[i].length > 7) { + pp[i].shift(); + var pi = pp[i]; + while (pi.length) { + pcoms1[i] = 'A'; + if (p2) { pcoms2[i] = 'A'; } + pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))); + } + pp.splice(i, 1); + ii = Math.max(p.length, p2 && p2.length || 0); + } + }, + + fixM = function(path1, path2, a1, a2, i) { + if (path1 && path2 && path1[i][0] === 'M' && path2[i][0] !== 'M') { + path2.splice(i, 0, ['M', a2.x, a2.y]); + a1.bx = 0; + a1.by = 0; + a1.x = path1[i][1]; + a1.y = path1[i][2]; + ii = Math.max(p.length, p2 && p2.length || 0); + } + }, + + pcoms1 = [], // path commands of original path p + pcoms2 = [], // path commands of original path p2 + pfirst = '', // temporary holder for original path command + pcom = ''; // holder for previous path command of original path + + for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) { + if (p[i]) { pfirst = p[i][0]; } // save current path command + + if (pfirst !== 'C') { + pcoms1[i] = pfirst; // Save current path command + if (i) { pcom = pcoms1[i - 1]; } // Get previous path command pcom + } + p[i] = processPath(p[i], attrs, pcom); + + if (pcoms1[i] !== 'A' && pfirst === 'C') { pcoms1[i] = 'C'; } + + fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 + + if (p2) { // the same procedures is done to p2 + if (p2[i]) { pfirst = p2[i][0]; } + if (pfirst !== 'C') { + pcoms2[i] = pfirst; + if (i) { pcom = pcoms2[i - 1]; } + } + p2[i] = processPath(p2[i], attrs2, pcom); + + if (pcoms2[i] !== 'A' && pfirst === 'C') { pcoms2[i] = 'C'; } + + fixArc(p2, i); + } + fixM(p, p2, attrs, attrs2, i); + fixM(p2, p, attrs2, attrs, i); + var seg = p[i], seg2 = p2 && p2[i], seglen = seg.length, + seg2len = p2 && seg2.length; + attrs.x = seg[seglen - 2]; + attrs.y = seg[seglen - 1]; + attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x; + attrs.by = parseFloat(seg[seglen - 3]) || attrs.y; + attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x); + attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y); + attrs2.x = p2 && seg2[seg2len - 2]; + attrs2.y = p2 && seg2[seg2len - 1]; + } + + return p2 ? [p, p2] : p; +} + +function a2c(x1, y1, rx, ry, angle, lac, sweep_flag, x2, y2, recursive) { + // for more information of where this Math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + var PI = Math.PI, _120 = PI * 120 / 180, f1, f2, cx, cy, + rad = PI / 180 * (+angle || 0), res = [], xy, + rotate = function (x, y, rad) { + var X = x * Math.cos(rad) - y * Math.sin(rad), + Y = x * Math.sin(rad) + y * Math.cos(rad); + return { x: X, y: Y }; + }; + if (!recursive) { + xy = rotate(x1, y1, -rad); + x1 = xy.x; + y1 = xy.y; + xy = rotate(x2, y2, -rad); + x2 = xy.x; + y2 = xy.y; + var x = (x1 - x2) / 2, y = (y1 - y2) / 2, + h = (x * x) / (rx * rx) + (y * y) / (ry * ry); + if (h > 1) { + h = Math.sqrt(h); + rx = h * rx; + ry = h * ry; + } + var rx2 = rx * rx, ry2 = ry * ry, + k = (lac === sweep_flag ? -1 : 1) * Math.sqrt(Math.abs + ((rx2 * ry2 - rx2 * y * y - ry2 * x * x)/(rx2 * y * y + ry2 * x * x))); + + cx = k * rx * y / ry + (x1 + x2) / 2; + cy = k * -ry * x / rx + (y1 + y2) / 2; + f1 = Math.asin(((y1 - cy) / ry).toFixed(9)); + f2 = Math.asin(((y2 - cy) / ry).toFixed(9)); + + f1 = x1 < cx ? PI - f1 : f1; + f2 = x2 < cx ? PI - f2 : f2; + + if (f1 < 0) { f1 = PI * 2 + f1; } + if (f2 < 0) { f2 = PI * 2 + f2; } + + if (sweep_flag && f1 > f2) { + f1 = f1 - PI * 2; + } + if (!sweep_flag && f2 > f1) { + f2 = f2 - PI * 2; + } + } else { + f1 = recursive[0]; + f2 = recursive[1]; + cx = recursive[2]; + cy = recursive[3]; + } + var df = f2 - f1; + if (Math.abs(df) > _120) { + var f2old = f2, x2old = x2, y2old = y2; + f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1); + x2 = cx + rx * Math.cos(f2); + y2 = cy + ry * Math.sin(f2); + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, + [f2, f2old, cx, cy]); + } + df = f2 - f1; + var c1 = Math.cos(f1), + s1 = Math.sin(f1), + c2 = Math.cos(f2), + s2 = Math.sin(f2), + t = Math.tan(df / 4), + hx = 4 / 3 * rx * t, + hy = 4 / 3 * ry * t, + m1 = [x1, y1], + m2 = [x1 + hx * s1, y1 - hy * c1], + m3 = [x2 + hx * s2, y2 - hy * c2], + m4 = [x2, y2]; + m2[0] = 2 * m1[0] - m2[0]; + m2[1] = 2 * m1[1] - m2[1]; + if (recursive) { + return [m2, m3, m4].concat(res); + } else { + res = [m2, m3, m4].concat(res).join().split(','); + var newres = []; + for (var i = 0, ii = res.length; i < ii; i++) { + newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], + res[i + 1], rad).x; + } + return newres; + } +} + +// http://schepers.cc/getting-to-the-point +function catmullRom2bezier(crp, z) { + var d = []; + for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { + var p = [{ + x: +crp[i - 2], + y: +crp[i - 1] + }, { + x: +crp[i], + y: +crp[i + 1] + }, { + x: +crp[i + 2], + y: +crp[i + 3] + }, { + x: +crp[i + 4], + y: +crp[i + 5] + }]; + if (z) { + if (!i) { + p[0] = { + x: +crp[iLen - 2], + y: +crp[iLen - 1] + }; + } else if (iLen - 4 === i) { + p[3] = { + x: +crp[0], + y: +crp[1] + }; + } else if (iLen - 2 === i) { + p[2] = { + x: +crp[0], + y: +crp[1] + }; + p[3] = { + x: +crp[2], + y: +crp[3] + }; + } + } else { + if (iLen - 4 === i) { + p[3] = p[2]; + } else if (!i) { + p[0] = { + x: +crp[i], + y: +crp[i + 1] + }; + } + } + d.push(['C', (-p[0].x + 6 * p[1].x + p[2].x) / 6, (-p[0].y + 6 * p[1].y + + p[2].y) / 6, (p[1].x + 6 * p[2].x - p[3].x) / 6, (p[1].y + 6 * p[2].y - + p[3].y) / 6, p[2].x, p[2].y ]); + } + + return d; +} + +function l2c(x1, y1, x2, y2) { return [x1, y1, x2, y2, x2, y2]; } + +function q2c(x1, y1, ax, ay, x2, y2) { + var _13 = 1 / 3, _23 = 2 / 3; + return [ + _13 * x1 + _23 * ax, _13 * y1 + _23 * ay, + _13 * x2 + _23 * ax, _13 * y2 + _23 * ay, x2, y2 + ]; +} + +function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) { + if (z == null) { z = 1; } + z = z > 1 ? 1 : z < 0 ? 0 : z; + var z2 = z / 2, + n = 12, Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, + -0.7699, 0.7699, -0.9041, 0.9041, -0.9816, 0.9816], + sum = 0, Cvalues = [0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, + 0.1601, 0.1601, 0.1069, 0.1069, 0.0472, 0.0472 ]; + for (var i = 0; i < n; i++) { + var ct = z2 * Tvalues[i] + z2, + xbase = base3(ct, x1, x2, x3, x4), + ybase = base3(ct, y1, y2, y3, y4), + comb = xbase * xbase + ybase * ybase; + sum += Cvalues[i] * Math.sqrt(comb); + } + return z2 * sum; +} + +function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) { + if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) { + return; + } + var t = 1, step = t / 2, t2 = t - step, l, e = 0.01; + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); + while (Math.abs(l - ll) > e) { + step /= 2; + t2 += (l < ll ? 1 : -1) * step; + l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2); + } + return t2; +} + +function base3(t, p1, p2, p3, p4) { + var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, + t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; + return t * t2 - 3 * p1 + 3 * p2; +} + +function cacheKey() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) { + args[i] = arguments[i]; + } + i = args.length; + var hash = ''; + while (i--) { + hash += (args[i] === Object(args[i])) ? + JSON.stringify(args[i]) : args[i]; + } + return hash; +} + +module.exports = p5.Font; + +},{"../core/constants":47,"../core/core":48}],83:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule Array Functions + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Adds a value to the end of an array. Extends the length of + * the array by one. Maps to Array.push(). + * + * @method append + * @param {Array} array Array to append + * @param {any} value to be added to the Array + * @example + * <div class = "norender"><code> + * function setup() { + * + * var myArray = new Array("Mango", "Apple", "Papaya") + * print(myArray) // ["Mango", "Apple", "Papaya"] + * + * append(myArray, "Peach") + * print(myArray) // ["Mango", "Apple", "Papaya", "Peach"] + * + * } + * </div></code> + */ +p5.prototype.append = function(array, value) { + array.push(value); + return array; +}; + +/** + * Copies an array (or part of an array) to another array. The src array is + * copied to the dst array, beginning at the position specified by + * srcPosition and into the position specified by dstPosition. The number of + * elements to copy is determined by length. Note that copying values + * overwrites existing values in the destination array. To append values + * instead of overwriting them, use concat(). + * <br><br> + * The simplified version with only two arguments, arrayCopy(src, dst), + * copies an entire array to another of the same size. It is equivalent to + * arrayCopy(src, 0, dst, 0, src.length). + * <br><br> + * Using this function is far more efficient for copying array data than + * iterating through a for() loop and copying each element individually. + * + * @method arrayCopy + * @param {Array} src the source Array + * @param {Number} [srcPosition] starting position in the source Array + * @param {Array} dst the destination Array + * @param {Number} [dstPosition] starting position in the destination Array + * @param {Number} [length] number of Array elements to be copied + * + * @example + * <div class="norender"><code> + * function setup() { + * + * var src = new Array("A", "B", "C"); + * var dst = new Array( 1 , 2 , 3 ); + * var srcPosition = 1; + * var dstPosition = 0; + * var length = 2; + * + * print(src); // ["A", "B", "C"] + * print(dst); // [ 1 , 2 , 3 ] + * + * arrayCopy(src, srcPosition, dst, dstPosition, length); + * print(dst); // ["B", "C", 3] + * + * } + * </div></code> + */ +p5.prototype.arrayCopy = function( + src, + srcPosition, + dst, + dstPosition, + length) { + + // the index to begin splicing from dst array + var start, + end; + + if (typeof length !== 'undefined') { + + end = Math.min(length, src.length); + start = dstPosition; + src = src.slice(srcPosition, end + srcPosition); + + } else { + + if (typeof dst !== 'undefined') { // src, dst, length + // rename so we don't get confused + end = dst; + end = Math.min(end, src.length); + } else { // src, dst + end = src.length; + } + + start = 0; + // rename so we don't get confused + dst = srcPosition; + src = src.slice(0, end); + } + + // Since we are not returning the array and JavaScript is pass by reference + // we must modify the actual values of the array + // instead of reassigning arrays + Array.prototype.splice.apply(dst, [start, end].concat(src)); + +}; + +/** + * Concatenates two arrays, maps to Array.concat(). Does not modify the + * input arrays. + * + * @method concat + * @param {Array} a first Array to concatenate + * @param {Array} b second Array to concatenate + * @return {Array} concatenated array + * + * @example + * <div class = "norender"><code> + * function setup() { + * var arr1 = new Array("A", "B", "C"); + * var arr2 = new Array( 1 , 2 , 3 ); + * + * print(arr1); // ["A","B","C"] + * print(arr2); // [1,2,3] + * + * var arr3 = concat(arr1, arr2); + * + * print(arr1); // ["A","B","C"] + * print(arr2); // [1,2,3] + * print(arr3); // ["A","B","C",1,2,3] + * + * } + * </div></code> + */ +p5.prototype.concat = function(list0, list1) { + return list0.concat(list1); +}; + +/** + * Reverses the order of an array, maps to Array.reverse() + * + * @method reverse + * @param {Array} list Array to reverse + * @example + * <div class="norender"><code> + * function setup() { + * var myArray = new Array("A", "B", "C"); + * print(myArray); // ["A","B","C"] + * + * reverse(myArray); + * print(myArray); // ["C","B","A"] + * } + * </div></code> + */ +p5.prototype.reverse = function(list) { + return list.reverse(); +}; + +/** + * Decreases an array by one element and returns the shortened array, + * maps to Array.pop(). + * + * @method shorten + * @param {Array} list Array to shorten + * @return {Array} shortened Array + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array("A", "B", "C"); + * print(myArray); // ["A","B","C"] + * + * var newArray = shorten(myArray); + * print(myArray); // ["A","B","C"] + * print(newArray); // ["A","B"] + * } + * </div></code> + */ +p5.prototype.shorten = function(list) { + list.pop(); + return list; +}; + +/** + * Randomizes the order of the elements of an array. Implements + * <a href="http://Bost.Ocks.org/mike/shuffle/" target=_blank> + * Fisher-Yates Shuffle Algorithm</a>. + * + * @method shuffle + * @param {Array} array Array to shuffle + * @param {Boolean} [bool] modify passed array + * @return {Array} shuffled Array + * @example + * <div><code> + * function setup() { + * var regularArr = ['ABC', 'def', createVector(), TAU, Math.E]; + * print(regularArr); + * shuffle(regularArr, true); // force modifications to passed array + * print(regularArr); + * + * // By default shuffle() returns a shuffled cloned array: + * var newArr = shuffle(regularArr); + * print(regularArr); + * print(newArr); + * } + * </code></div> + */ +p5.prototype.shuffle = function(arr, bool) { + var isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr); + arr = bool || isView ? arr : arr.slice(); + + var rnd, tmp, idx = arr.length; + while (idx > 1) { + rnd = Math.random()*idx | 0; + + tmp = arr[--idx]; + arr[idx] = arr[rnd]; + arr[rnd] = tmp; + } + + return arr; +}; + +/** + * Sorts an array of numbers from smallest to largest, or puts an array of + * words in alphabetical order. The original array is not modified; a + * re-ordered array is returned. The count parameter states the number of + * elements to sort. For example, if there are 12 elements in an array and + * count is set to 5, only the first 5 elements in the array will be sorted. + * + * @method sort + * @param {Array} list Array to sort + * @param {Number} [count] number of elements to sort, starting from 0 + * + * @example + * <div class = "norender"><code> + * function setup() { + * var words = new Array("banana", "apple", "pear","lime"); + * print(words); // ["banana", "apple", "pear", "lime"] + * var count = 4; // length of array + * + * sort(words, count); + * print(words); // ["apple", "banana", "lime", "pear"] + * } + * </div></code> + * <div class = "norender"><code> + * function setup() { + * var numbers = new Array(2,6,1,5,14,9,8,12); + * print(numbers); // [2,6,1,5,14,9,8,12] + * var count = 5; // Less than the length of the array + * + * sort(numbers, count); + * print(numbers); // [1,2,5,6,14,9,8,12] + * } + * </div></code> + */ +p5.prototype.sort = function(list, count) { + var arr = count ? list.slice(0, Math.min(count, list.length)) : list; + var rest = count ? list.slice(Math.min(count, list.length)) : []; + if (typeof arr[0] === 'string') { + arr = arr.sort(); + } else { + arr = arr.sort(function(a,b){return a-b;}); + } + return arr.concat(rest); +}; + +/** + * Inserts a value or an array of values into an existing array. The first + * parameter specifies the initial array to be modified, and the second + * parameter defines the data to be inserted. The third parameter is an index + * value which specifies the array position from which to insert data. + * (Remember that array index numbering starts at zero, so the first position + * is 0, the second position is 1, and so on.) + * + * @method splice + * @param {Array} list Array to splice into + * @param {any} value value to be spliced in + * @param {Number} position in the array from which to insert data + * + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array(0,1,2,3,4); + * var insArray = new Array("A","B","C"); + * print(myArray); // [0,1,2,3,4] + * print(insArray); // ["A","B","C"] + * + * splice(myArray, insArray, 3); + * print(myArray); // [0,1,2,"A","B","C",3,4] + * } + * </div></code> + */ +p5.prototype.splice = function(list, value, index) { + + // note that splice returns spliced elements and not an array + Array.prototype.splice.apply(list, [index, 0].concat(value)); + + return list; +}; + +/** + * Extracts an array of elements from an existing array. The list parameter + * defines the array from which the elements will be copied, and the start + * and count parameters specify which elements to extract. If no count is + * given, elements will be extracted from the start to the end of the array. + * When specifying the start, remember that the first array element is 0. + * This function does not change the source array. + * + * @method subset + * @param {Array} list Array to extract from + * @param {Number} start position to begin + * @param {Number} [count] number of values to extract + * @return {Array} Array of extracted elements + * + * @example + * <div class = "norender"><code> + * function setup() { + * var myArray = new Array(1,2,3,4,5); + * print(myArray); // [1,2,3,4,5] + * + * var sub1 = subset(myArray, 0, 3); + * var sub2 = subset(myArray, 2, 2); + * print(sub1); // [1,2,3] + * print(sub2); // [3,4] + * } + * </div></code> + */ +p5.prototype.subset = function(list, start, count) { + if (typeof count !== 'undefined') { + return list.slice(start, start + count); + } else { + return list.slice(start, list.length); + } +}; + +module.exports = p5; + +},{"../core/core":48}],84:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule Conversion + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * Converts a string to its floating point representation. The contents of a + * string must resemble a number, or NaN (not a number) will be returned. + * For example, float("1234.56") evaluates to 1234.56, but float("giraffe") + * will return NaN. + * + * @method float + * @param {String} str float string to parse + * @return {Number} floating point representation of string + * @example + * <div><code> + * var str = '20'; + * var diameter = float(str); + * ellipse(width/2, height/2, diameter, diameter); + * </code></div> + */ +p5.prototype.float = function(str) { + return parseFloat(str); +}; + +/** + * Converts a boolean, string, or float to its integer representation. + * When an array of values is passed in, then an int array of the same length + * is returned. + * + * @method int + * @param {String|Boolean|Number|Array} n value to parse + * @return {Number} integer representation of value + * @example + * <div class='norender'><code> + * print(int("10")); // 10 + * print(int(10.31)); // 10 + * print(int(-10)); // -10 + * print(int(true)); // 1 + * print(int(false)); // 0 + * print(int([false, true, "10.3", 9.8])); // [0, 1, 10, 9] + * </code></div> + */ +p5.prototype.int = function(n, radix) { + if (typeof n === 'string') { + radix = radix || 10; + return parseInt(n, radix); + } else if (typeof n === 'number') { + return n | 0; + } else if (typeof n === 'boolean') { + return n ? 1 : 0; + } else if (n instanceof Array) { + return n.map(function(n) { return p5.prototype.int(n, radix); }); + } +}; + +/** + * Converts a boolean, string or number to its string representation. + * When an array of values is passed in, then an array of strings of the same + * length is returned. + * + * @method str + * @param {String|Boolean|Number|Array} n value to parse + * @return {String} string representation of value + * @example + * <div class='norender'><code> + * print(str("10")); // "10" + * print(str(10.31)); // "10.31" + * print(str(-10)); // "-10" + * print(str(true)); // "true" + * print(str(false)); // "false" + * print(str([true, "10.3", 9.8])); // [ "true", "10.3", "9.8" ] + * </code></div> + */ +p5.prototype.str = function(n) { + if (n instanceof Array) { + return n.map(p5.prototype.str); + } else { + return String(n); + } +}; + +/** + * Converts a number or string to its boolean representation. + * For a number, any non-zero value (positive or negative) evaluates to true, + * while zero evaluates to false. For a string, the value "true" evaluates to + * true, while any other value evaluates to false. When an array of number or + * string values is passed in, then a array of booleans of the same length is + * returned. + * + * @method boolean + * @param {String|Boolean|Number|Array} n value to parse + * @return {Boolean} boolean representation of value + * @example + * <div class='norender'><code> + * print(boolean(0)); // false + * print(boolean(1)); // true + * print(boolean("true")); // true + * print(boolean("abcd")); // false + * print(boolean([0, 12, "true"])); // [false, true, false] + * </code></div> + */ +p5.prototype.boolean = function(n) { + if (typeof n === 'number') { + return n !== 0; + } else if (typeof n === 'string') { + return n.toLowerCase() === 'true'; + } else if (typeof n === 'boolean') { + return n; + } else if (n instanceof Array) { + return n.map(p5.prototype.boolean); + } +}; + +/** + * Converts a number, string or boolean to its byte representation. + * A byte can be only a whole number between -128 and 127, so when a value + * outside of this range is converted, it wraps around to the corresponding + * byte representation. When an array of number, string or boolean values is + * passed in, then an array of bytes the same length is returned. + * + * @method byte + * @param {String|Boolean|Number|Array} n value to parse + * @return {Number} byte representation of value + * @example + * <div class='norender'><code> + * print(byte(127)); // 127 + * print(byte(128)); // -128 + * print(byte(23.4)); // 23 + * print(byte("23.4")); // 23 + * print(byte(true)); // 1 + * print(byte([0, 255, "100"])); // [0, -1, 100] + * </code></div> + */ +p5.prototype.byte = function(n) { + var nn = p5.prototype.int(n, 10); + if (typeof nn === 'number') { + return ((nn + 128) % 256) - 128; + } else if (nn instanceof Array) { + return nn.map(p5.prototype.byte); + } +}; + +/** + * Converts a number or string to its corresponding single-character + * string representation. If a string parameter is provided, it is first + * parsed as an integer and then translated into a single-character string. + * When an array of number or string values is passed in, then an array of + * single-character strings of the same length is returned. + * + * @method char + * @param {String|Number|Array} n value to parse + * @return {String} string representation of value + * @example + * <div class='norender'><code> + * print(char(65)); // "A" + * print(char("65")); // "A" + * print(char([65, 66, 67])); // [ "A", "B", "C" ] + * print(join(char([65, 66, 67]), '')); // "ABC" + * </code></div> + */ +p5.prototype.char = function(n) { + if (typeof n === 'number' && !isNaN(n)) { + return String.fromCharCode(n); + } else if (n instanceof Array) { + return n.map(p5.prototype.char); + } else if (typeof n === 'string') { + return p5.prototype.char(parseInt(n, 10)); + } +}; + +/** + * Converts a single-character string to its corresponding integer + * representation. When an array of single-character string values is passed + * in, then an array of integers of the same length is returned. + * + * @method unchar + * @param {String|Array} n value to parse + * @return {Number} integer representation of value + * @example + * <div class='norender'><code> + * print(unchar("A")); // 65 + * print(unchar(["A", "B", "C"])); // [ 65, 66, 67 ] + * print(unchar(split("ABC", ""))); // [ 65, 66, 67 ] + * </code></div> + */ +p5.prototype.unchar = function(n) { + if (typeof n === 'string' && n.length === 1) { + return n.charCodeAt(0); + } else if (n instanceof Array) { + return n.map(p5.prototype.unchar); + } +}; + +/** + * Converts a number to a string in its equivalent hexadecimal notation. If a + * second parameter is passed, it is used to set the number of characters to + * generate in the hexadecimal notation. When an array is passed in, an + * array of strings in hexadecimal notation of the same length is returned. + * + * @method hex + * @param {Number|Array} n value to parse + * @return {String} hexadecimal string representation of value + * @example + * <div class='norender'><code> + * print(hex(255)); // "000000FF" + * print(hex(255, 6)); // "0000FF" + * print(hex([0, 127, 255], 6)); // [ "000000", "00007F", "0000FF" ] + * </code></div> + */ +p5.prototype.hex = function(n, digits) { + digits = (digits === undefined || digits === null) ? digits = 8 : digits; + if (n instanceof Array) { + return n.map(function(n) { return p5.prototype.hex(n, digits); }); + } else if (typeof n === 'number') { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + var hex = Number(n).toString(16).toUpperCase(); + while (hex.length < digits) { + hex = '0' + hex; + } + if (hex.length >= digits) { + hex = hex.substring(hex.length - digits, hex.length); + } + return hex; + } +}; + +/** + * Converts a string representation of a hexadecimal number to its equivalent + * integer value. When an array of strings in hexadecimal notation is passed + * in, an array of integers of the same length is returned. + * + * @method unhex + * @param {String|Array} n value to parse + * @return {Number} integer representation of hexadecimal value + * @example + * <div class='norender'><code> + * print(unhex("A")); // 10 + * print(unhex("FF")); // 255 + * print(unhex(["FF", "AA", "00"])); // [ 255, 170, 0 ] + * </code></div> + */ +p5.prototype.unhex = function(n) { + if (n instanceof Array) { + return n.map(p5.prototype.unhex); + } else { + return parseInt('0x' + n, 16); + } +}; + +module.exports = p5; + +},{"../core/core":48}],85:[function(_dereq_,module,exports){ +/** + * @module Data + * @submodule String Functions + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +//return p5; //LM is this a mistake? + +/** + * Combines an array of Strings into one String, each separated by the + * character(s) used for the separator parameter. To join arrays of ints or + * floats, it's necessary to first convert them to Strings using nf() or + * nfs(). + * + * @method join + * @param {Array} list array of Strings to be joined + * @param {String} separator String to be placed between each item + * @return {String} joined String + * @example + * <div> + * <code> + * var array = ["Hello", "world!"] + * var separator = " " + * var message = join(array, separator); + * text(message, 5, 50); + * </code> + * </div> + */ +p5.prototype.join = function(list, separator) { + return list.join(separator); +}; + +/** + * This function is used to apply a regular expression to a piece of text, + * and return matching groups (elements found inside parentheses) as a + * String array. If there are no matches, a null value will be returned. + * If no groups are specified in the regular expression, but the sequence + * matches, an array of length 1 (with the matched text as the first element + * of the array) will be returned. + * <br><br> + * To use the function, first check to see if the result is null. If the + * result is null, then the sequence did not match at all. If the sequence + * did match, an array is returned. + * <br><br> + * If there are groups (specified by sets of parentheses) in the regular + * expression, then the contents of each will be returned in the array. + * Element [0] of a regular expression match returns the entire matching + * string, and the match groups start at element [1] (the first group is [1], + * the second [2], and so on). + * + * @method match + * @param {String} str the String to be searched + * @param {String} regexp the regexp to be used for matching + * @return {Array} Array of Strings found + * @example + * <div> + * <code> + * var string = "Hello p5js*!" + * var regexp = "p5js\\*" + * var match = match(string, regexp); + * text(match, 5, 50); + * </code> + * </div> + */ +p5.prototype.match = function(str, reg) { + return str.match(reg); +}; + +/** + * This function is used to apply a regular expression to a piece of text, + * and return a list of matching groups (elements found inside parentheses) + * as a two-dimensional String array. If there are no matches, a null value + * will be returned. If no groups are specified in the regular expression, + * but the sequence matches, a two dimensional array is still returned, but + * the second dimension is only of length one. + * <br><br> + * To use the function, first check to see if the result is null. If the + * result is null, then the sequence did not match at all. If the sequence + * did match, a 2D array is returned. + * <br><br> + * If there are groups (specified by sets of parentheses) in the regular + * expression, then the contents of each will be returned in the array. + * Assuming a loop with counter variable i, element [i][0] of a regular + * expression match returns the entire matching string, and the match groups + * start at element [i][1] (the first group is [i][1], the second [i][2], + * and so on). + * + * @method matchAll + * @param {String} str the String to be searched + * @param {String} regexp the regexp to be used for matching + * @return {Array} 2d Array of Strings found + * @example + * <div class="norender"> + * <code> + * var string = "Hello p5js*! Hello world!" + * var regexp = "Hello" + * matchAll(string, regexp); + * </code> + * </div> + + */ +p5.prototype.matchAll = function(str, reg) { + var re = new RegExp(reg, 'g'); + var match = re.exec(str); + var matches = []; + while (match !== null) { + matches.push(match); + // matched text: match[0] + // match start: match.index + // capturing group n: match[n] + match = re.exec(str); + } + return matches; +}; + +/** + * Utility function for formatting numbers into strings. There are two + * versions: one for formatting floats, and one for formatting ints. + * The values for the digits, left, and right parameters should always + * be positive integers. + * + * @method nf + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the + * decimal point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num = 112.53106115; + * + * noStroke(); + * fill(0); + * textSize(14); + * // Draw formatted numbers + * text(nf(num, 5, 2), 10, 20); + * + * text(nf(num, 4, 3), 10, 55); + * + * text(nf(num, 3, 6), 10, 85); + * + * // Draw dividing lines + * stroke(120); + * line(0, 30, width, 30); + * line(0, 65, width, 65); + * } + * </code> + * </div> + */ +p5.prototype.nf = function () { + if (arguments[0] instanceof Array) { + var a = arguments[1]; + var b = arguments[2]; + return arguments[0].map(function (x) { + return doNf(x, a, b); + }); + } + else{ + var typeOfFirst = Object.prototype.toString.call(arguments[0]); + if(typeOfFirst === '[object Arguments]'){ + if(arguments[0].length===3){ + return this.nf(arguments[0][0],arguments[0][1],arguments[0][2]); + } + else if(arguments[0].length===2){ + return this.nf(arguments[0][0],arguments[0][1]); + } + else{ + return this.nf(arguments[0][0]); + } + } + else { + return doNf.apply(this, arguments); + } + } +}; + +function doNf() { + var num = arguments[0]; + var neg = num < 0; + var n = neg ? num.toString().substring(1) : num.toString(); + var decimalInd = n.indexOf('.'); + var intPart = decimalInd !== -1 ? n.substring(0, decimalInd) : n; + var decPart = decimalInd !== -1 ? n.substring(decimalInd + 1) : ''; + var str = neg ? '-' : ''; + if (arguments.length === 3) { + var decimal = ''; + if(decimalInd !== -1 || arguments[2] - decPart.length > 0){ + decimal = '.'; + } + if (decPart.length > arguments[2]) { + decPart = decPart.substring(0, arguments[2]); + } + for (var i = 0; i < arguments[1] - intPart.length; i++) { + str += '0'; + } + str += intPart; + str += decimal; + str += decPart; + for (var j = 0; j < arguments[2] - decPart.length; j++) { + str += '0'; + } + return str; + } + else { + for (var k = 0; k < Math.max(arguments[1] - intPart.length, 0); k++) { + str += '0'; + } + str += n; + return str; + } +} + +/** + * Utility function for formatting numbers into strings and placing + * appropriate commas to mark units of 1000. There are two versions: one + * for formatting ints, and one for formatting an array of ints. The value + * for the right parameter should always be a positive integer. + * + * @method nfc + * @param {Number|Array} num the Number to format + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num = 11253106.115; + * var numArr = new Array(1,1,2); + * + * noStroke(); + * fill(0); + * textSize(12); + * + * // Draw formatted numbers + * text(nfc(num, 4, 2), 10, 30); + * text(nfc(numArr, 2, 1), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfc = function () { + if (arguments[0] instanceof Array) { + var a = arguments[1]; + return arguments[0].map(function (x) { + return doNfc(x, a); + }); + } else { + return doNfc.apply(this, arguments); + } +}; +function doNfc() { + var num = arguments[0].toString(); + var dec = num.indexOf('.'); + var rem = dec !== -1 ? num.substring(dec) : ''; + var n = dec !== -1 ? num.substring(0, dec) : num; + n = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + if (arguments[1] === 0) { + rem = ''; + } + else if(arguments[1] !== undefined){ + if(arguments[1] > rem.length){ + rem+= dec === -1 ? '.' : ''; + var len = arguments[1] - rem.length + 1; + for(var i =0; i< len; i++){ + rem += '0'; + } + } + else{ + rem = rem.substring(0, arguments[1] + 1); + } + } + return n + rem; +} + +/** + * Utility function for formatting numbers into strings. Similar to nf() but + * puts a "+" in front of positive numbers and a "-" in front of negative + * numbers. There are two versions: one for formatting floats, and one for + * formatting ints. The values for left, and right parameters + * should always be positive integers. + * + * @method nfp + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the decimal + * point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num1 = 11253106.115; + * var num2 = -11253106.115; + * + * noStroke(); + * fill(0); + * textSize(12); + * + * // Draw formatted numbers + * text(nfp(num1, 4, 2), 10, 30); + * text(nfp(num2, 4, 2), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfp = function() { + var nfRes = this.nf.apply(this, arguments); + if (nfRes instanceof Array) { + return nfRes.map(addNfp); + } else { + return addNfp(nfRes); + } +}; + +function addNfp() { + return ( + parseFloat(arguments[0]) > 0) ? + '+'+arguments[0].toString() : + arguments[0].toString(); +} + +/** + * Utility function for formatting numbers into strings. Similar to nf() but + * puts a " " (space) in front of positive numbers and a "-" in front of + * negative numbers. There are two versions: one for formatting floats, and + * one for formatting ints. The values for the digits, left, and right + * parameters should always be positive integers. + * + * @method nfs + * @param {Number|Array} num the Number to format + * @param {Number} [left] number of digits to the left of the decimal + * point + * @param {Number} [right] number of digits to the right of the + * decimal point + * @return {String|Array} formatted String + * @example + * <div> + * <code> + * function setup() { + * background(200); + * var num1 = 11253106.115; + * var num2 = -11253106.115; + * + * noStroke(); + * fill(0); + * textSize(12); + * // Draw formatted numbers + * text(nfs(num1, 4, 2), 10, 30); + * + * text(nfs(num2, 4, 2), 10, 80); + * + * // Draw dividing line + * stroke(120); + * line(0, 50, width, 50); + * } + * </code> + * </div> + */ +p5.prototype.nfs = function() { + var nfRes = this.nf.apply(this, arguments); + if (nfRes instanceof Array) { + return nfRes.map(addNfs); + } else { + return addNfs(nfRes); + } +}; + +function addNfs() { + return ( + parseFloat(arguments[0]) > 0) ? + ' '+arguments[0].toString() : + arguments[0].toString(); +} + +/** + * The split() function maps to String.split(), it breaks a String into + * pieces using a character or string as the delimiter. The delim parameter + * specifies the character or characters that mark the boundaries between + * each piece. A String[] array is returned that contains each of the pieces. + * + * The splitTokens() function works in a similar fashion, except that it + * splits using a range of characters instead of a specific character or + * sequence. + * + * @method split + * @param {String} value the String to be split + * @param {String} delim the String used to separate the data + * @return {Array} Array of Strings + * @example + * <div> + * <code> + * var names = "Pat,Xio,Alex" + * var splitString = split(names, ","); + * text(splitString[0], 5, 30); + * text(splitString[1], 5, 50); + * text(splitString[2], 5, 70); + * </code> + * </div> + */ +p5.prototype.split = function(str, delim) { + return str.split(delim); +}; + +/** + * The splitTokens() function splits a String at one or many character + * delimiters or "tokens." The delim parameter specifies the character or + * characters to be used as a boundary. + * <br><br> + * If no delim characters are specified, any whitespace character is used to + * split. Whitespace characters include tab (\t), line feed (\n), carriage + * return (\r), form feed (\f), and space. + * + * @method splitTokens + * @param {String} value the String to be split + * @param {String} [delim] list of individual Strings that will be used as + * separators + * @return {Array} Array of Strings + * @example + * <div class = "norender"> + * <code> + * function setup() { + * var myStr = "Mango, Banana, Lime"; + * var myStrArr = splitTokens(myStr, ","); + * + * print(myStrArr); // prints : ["Mango"," Banana"," Lime"] + * } + * </code> + * </div> + */ +p5.prototype.splitTokens = function() { + var d,sqo,sqc,str; + str = arguments[1]; + if (arguments.length > 1) { + sqc = /\]/g.exec(str); + sqo = /\[/g.exec(str); + if ( sqo && sqc ) { + str = str.slice(0, sqc.index) + str.slice(sqc.index+1); + sqo = /\[/g.exec(str); + str = str.slice(0, sqo.index) + str.slice(sqo.index+1); + d = new RegExp('[\\['+str+'\\]]','g'); + } else if ( sqc ) { + str = str.slice(0, sqc.index) + str.slice(sqc.index+1); + d = new RegExp('[' + str + '\\]]', 'g'); + } else if(sqo) { + str = str.slice(0, sqo.index) + str.slice(sqo.index+1); + d = new RegExp('[' + str + '\\[]', 'g'); + } else { + d = new RegExp('[' + str + ']', 'g'); + } + } else { + d = /\s/g; + } + return arguments[0].split(d).filter(function(n){return n;}); +}; + +/** + * Removes whitespace characters from the beginning and end of a String. In + * addition to standard whitespace characters such as space, carriage return, + * and tab, this function also removes the Unicode "nbsp" character. + * + * @method trim + * @param {String|Array} [str] a String or Array of Strings to be trimmed + * @return {String|Array} a trimmed String or Array of Strings + * @example + * <div> + * <code> + * var string = trim(" No new lines\n "); + * text(string +" here", 2, 50); + * </code> + * </div> + */ +p5.prototype.trim = function(str) { + if (str instanceof Array) { + return str.map(this.trim); + } else { + return str.trim(); + } +}; + +module.exports = p5; + +},{"../core/core":48}],86:[function(_dereq_,module,exports){ +/** + * @module Input + * @submodule Time & Date + * @for p5 + * @requires core + */ + +'use strict'; + +var p5 = _dereq_('../core/core'); + +/** + * p5.js communicates with the clock on your computer. The day() function + * returns the current day as a value from 1 - 31. + * + * @method day + * @return {Number} the current day + * @example + * <div> + * <code> + * var day = day(); + * text("Current day: \n" + day, 5, 50); + * </code> + * </div> + */ +p5.prototype.day = function() { + return new Date().getDate(); +}; + +/** + * p5.js communicates with the clock on your computer. The hour() function + * returns the current hour as a value from 0 - 23. + * + * @method hour + * @return {Number} the current hour + * @example + * <div> + * <code> + * var hour = hour(); + * text("Current hour:\n" + hour, 5, 50); + * </code> + * </div> + */ +p5.prototype.hour = function() { + return new Date().getHours(); +}; + +/** + * p5.js communicates with the clock on your computer. The minute() function + * returns the current minute as a value from 0 - 59. + * + * @method minute + * @return {Number} the current minute + * @example + * <div> + * <code> + * var minute = minute(); + * text("Current minute: \n" + minute, 5, 50); + * </code> + * </div> + */ +p5.prototype.minute = function() { + return new Date().getMinutes(); +}; + +/** + * Returns the number of milliseconds (thousandths of a second) since + * starting the program. This information is often used for timing events and + * animation sequences. + * + * @method millis + * @return {Number} the number of milliseconds since starting the program + * @example + * <div> + * <code> + * var millisecond = millis(); + * text("Milliseconds \nrunning: \n" + millisecond, 5, 40); + * </code> + * </div> + */ +p5.prototype.millis = function() { + return window.performance.now(); +}; + +/** + * p5.js communicates with the clock on your computer. The month() function + * returns the current month as a value from 1 - 12. + * + * @method month + * @return {Number} the current month + * @example + * <div> + * <code> + * var month = month(); + * text("Current month: \n" + month, 5, 50); + * </code> + * </div> + */ +p5.prototype.month = function() { + return new Date().getMonth() + 1; //January is 0! +}; + +/** + * p5.js communicates with the clock on your computer. The second() function + * returns the current second as a value from 0 - 59. + * + * @method second + * @return {Number} the current second + * @example + * <div> + * <code> + * var second = second(); + * text("Current second: \n" + second, 5, 50); + * </code> + * </div> + */ +p5.prototype.second = function() { + return new Date().getSeconds(); +}; + +/** + * p5.js communicates with the clock on your computer. The year() function + * returns the current year as an integer (2014, 2015, 2016, etc). + * + * @method year + * @return {Number} the current year + * @example + * <div> + * <code> + * var year = year(); + * text("Current year: \n" + year, 5, 50); + * </code> + * </div> + */ +p5.prototype.year = function() { + return new Date().getFullYear(); +}; + +module.exports = p5; + +},{"../core/core":48}]},{},[39])(39) +}); \ No newline at end of file diff --git a/examples/p5js/libraries/p5.sound.js b/examples/p5js/libraries/p5.sound.js new file mode 100644 index 0000000000000000000000000000000000000000..65b358a8cbe4a5200a42ef19c58267fc61495432 --- /dev/null +++ b/examples/p5js/libraries/p5.sound.js @@ -0,0 +1,9034 @@ +/*! p5.sound.js v0.3.0 2016-01-31 */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) + define('p5.sound', ['p5'], function (p5) { (factory(p5));}); + else if (typeof exports === 'object') + factory(require('../p5')); + else + factory(root['p5']); +}(this, function (p5) { + /** + * p5.sound extends p5 with <a href="http://caniuse.com/audio-api" + * target="_blank">Web Audio</a> functionality including audio input, + * playback, analysis and synthesis. + * <br/><br/> + * <a href="#/p5.SoundFile"><b>p5.SoundFile</b></a>: Load and play sound files.<br/> + * <a href="#/p5.Amplitude"><b>p5.Amplitude</b></a>: Get the current volume of a sound.<br/> + * <a href="#/p5.AudioIn"><b>p5.AudioIn</b></a>: Get sound from an input source, typically + * a computer microphone.<br/> + * <a href="#/p5.FFT"><b>p5.FFT</b></a>: Analyze the frequency of sound. Returns + * results from the frequency spectrum or time domain (waveform).<br/> + * <a href="#/p5.Oscillator"><b>p5.Oscillator</b></a>: Generate Sine, + * Triangle, Square and Sawtooth waveforms. Base class of + * <a href="#/p5.Noise">p5.Noise</a> and <a href="#/p5.Pulse">p5.Pulse</a>. + * <br/> + * <a href="#/p5.Env"><b>p5.Env</b></a>: An Envelope is a series + * of fades over time. Often used to control an object's + * output gain level as an "ADSR Envelope" (Attack, Decay, + * Sustain, Release). Can also modulate other parameters.<br/> + * <a href="#/p5.Delay"><b>p5.Delay</b></a>: A delay effect with + * parameters for feedback, delayTime, and lowpass filter.<br/> + * <a href="#/p5.Filter"><b>p5.Filter</b></a>: Filter the frequency range of a + * sound. + * <br/> + * <a href="#/p5.Reverb"><b>p5.Reverb</b></a>: Add reverb to a sound by specifying + * duration and decay. <br/> + * <b><a href="#/p5.Convolver">p5.Convolver</a>:</b> Extends + * <a href="#/p5.Reverb">p5.Reverb</a> to simulate the sound of real + * physical spaces through convolution.<br/> + * <b><a href="#/p5.SoundRecorder">p5.SoundRecorder</a></b>: Record sound for playback + * / save the .wav file. + * <b><a href="#/p5.Phrase">p5.Phrase</a></b>, <b><a href="#/p5.Part">p5.Part</a></b> and + * <b><a href="#/p5.Score">p5.Score</a></b>: Compose musical sequences. + * <br/><br/> + * p5.sound is on <a href="https://github.com/therewasaguy/p5.sound/">GitHub</a>. + * Download the latest version + * <a href="https://github.com/therewasaguy/p5.sound/blob/master/lib/p5.sound.js">here</a>. + * + * @module p5.sound + * @submodule p5.sound + * @for p5.sound + * @main + */ +/** + * p5.sound developed by Jason Sigal for the Processing Foundation, Google Summer of Code 2014. The MIT License (MIT). + * + * http://github.com/therewasaguy/p5.sound + * + * Some of the many audio libraries & resources that inspire p5.sound: + * - TONE.js (c) Yotam Mann, 2014. Licensed under The MIT License (MIT). https://github.com/TONEnoTONE/Tone.js + * - buzz.js (c) Jay Salvat, 2013. Licensed under The MIT License (MIT). http://buzz.jaysalvat.com/ + * - Boris Smus Web Audio API book, 2013. Licensed under the Apache License http://www.apache.org/licenses/LICENSE-2.0 + * - wavesurfer.js https://github.com/katspaugh/wavesurfer.js + * - Web Audio Components by Jordan Santell https://github.com/web-audio-components + * - Wilm Thoben's Sound library for Processing https://github.com/processing/processing/tree/master/java/libraries/sound + * + * Web Audio API: http://w3.org/TR/webaudio/ + */ +var sndcore; +sndcore = function () { + 'use strict'; + /* AudioContext Monkeypatch + Copyright 2013 Chris Wilson + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + (function (global, exports, perf) { + exports = exports || {}; + 'use strict'; + function fixSetTarget(param) { + if (!param) + // if NYI, just return + return; + if (!param.setTargetAtTime) + param.setTargetAtTime = param.setTargetValueAtTime; + } + if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) { + window.AudioContext = webkitAudioContext; + if (!AudioContext.prototype.hasOwnProperty('createGain')) + AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; + if (!AudioContext.prototype.hasOwnProperty('createDelay')) + AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; + if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor')) + AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode; + if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave')) + AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; + AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain; + AudioContext.prototype.createGain = function () { + var node = this.internal_createGain(); + fixSetTarget(node.gain); + return node; + }; + AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay; + AudioContext.prototype.createDelay = function (maxDelayTime) { + var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay(); + fixSetTarget(node.delayTime); + return node; + }; + AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource; + AudioContext.prototype.createBufferSource = function () { + var node = this.internal_createBufferSource(); + if (!node.start) { + node.start = function (when, offset, duration) { + if (offset || duration) + this.noteGrainOn(when || 0, offset, duration); + else + this.noteOn(when || 0); + }; + } else { + node.internal_start = node.start; + node.start = function (when, offset, duration) { + if (typeof duration !== 'undefined') + node.internal_start(when || 0, offset, duration); + else + node.internal_start(when || 0, offset || 0); + }; + } + if (!node.stop) { + node.stop = function (when) { + this.noteOff(when || 0); + }; + } else { + node.internal_stop = node.stop; + node.stop = function (when) { + node.internal_stop(when || 0); + }; + } + fixSetTarget(node.playbackRate); + return node; + }; + AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor; + AudioContext.prototype.createDynamicsCompressor = function () { + var node = this.internal_createDynamicsCompressor(); + fixSetTarget(node.threshold); + fixSetTarget(node.knee); + fixSetTarget(node.ratio); + fixSetTarget(node.reduction); + fixSetTarget(node.attack); + fixSetTarget(node.release); + return node; + }; + AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter; + AudioContext.prototype.createBiquadFilter = function () { + var node = this.internal_createBiquadFilter(); + fixSetTarget(node.frequency); + fixSetTarget(node.detune); + fixSetTarget(node.Q); + fixSetTarget(node.gain); + return node; + }; + if (AudioContext.prototype.hasOwnProperty('createOscillator')) { + AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator; + AudioContext.prototype.createOscillator = function () { + var node = this.internal_createOscillator(); + if (!node.start) { + node.start = function (when) { + this.noteOn(when || 0); + }; + } else { + node.internal_start = node.start; + node.start = function (when) { + node.internal_start(when || 0); + }; + } + if (!node.stop) { + node.stop = function (when) { + this.noteOff(when || 0); + }; + } else { + node.internal_stop = node.stop; + node.stop = function (when) { + node.internal_stop(when || 0); + }; + } + if (!node.setPeriodicWave) + node.setPeriodicWave = node.setWaveTable; + fixSetTarget(node.frequency); + fixSetTarget(node.detune); + return node; + }; + } + } + if (window.hasOwnProperty('webkitOfflineAudioContext') && !window.hasOwnProperty('OfflineAudioContext')) { + window.OfflineAudioContext = webkitOfflineAudioContext; + } + return exports; + }(window)); + // <-- end MonkeyPatch. + // Create the Audio Context + var audiocontext = new window.AudioContext(); + /** + * <p>Returns the Audio Context for this sketch. Useful for users + * who would like to dig deeper into the <a target='_blank' href= + * 'http://webaudio.github.io/web-audio-api/'>Web Audio API + * </a>.</p> + * + * @method getAudioContext + * @return {Object} AudioContext for this sketch + */ + p5.prototype.getAudioContext = function () { + return audiocontext; + }; + // Polyfill for AudioIn, also handled by p5.dom createCapture + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + /** + * Determine which filetypes are supported (inspired by buzz.js) + * The audio element (el) will only be used to test browser support for various audio formats + */ + var el = document.createElement('audio'); + p5.prototype.isSupported = function () { + return !!el.canPlayType; + }; + var isOGGSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/ogg; codecs="vorbis"'); + }; + var isMP3Supported = function () { + return !!el.canPlayType && el.canPlayType('audio/mpeg;'); + }; + var isWAVSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/wav; codecs="1"'); + }; + var isAACSupported = function () { + return !!el.canPlayType && (el.canPlayType('audio/x-m4a;') || el.canPlayType('audio/aac;')); + }; + var isAIFSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/x-aiff;'); + }; + p5.prototype.isFileSupported = function (extension) { + switch (extension.toLowerCase()) { + case 'mp3': + return isMP3Supported(); + case 'wav': + return isWAVSupported(); + case 'ogg': + return isOGGSupported(); + case 'aac', 'm4a', 'mp4': + return isAACSupported(); + case 'aif', 'aiff': + return isAIFSupported(); + default: + return false; + } + }; + // if it is iOS, we have to have a user interaction to start Web Audio + // http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ + var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false; + if (iOS) { + var iosStarted = false; + var startIOS = function () { + if (iosStarted) + return; + // create empty buffer + var buffer = audiocontext.createBuffer(1, 1, 22050); + var source = audiocontext.createBufferSource(); + source.buffer = buffer; + // connect to output (your speakers) + source.connect(audiocontext.destination); + // play the file + source.start(0); + console.log('start ios!'); + if (audiocontext.state === 'running') { + iosStarted = true; + } + }; + document.addEventListener('touchend', startIOS, false); + document.addEventListener('touchstart', startIOS, false); + } +}(); +var master; +master = function () { + 'use strict'; + /** + * Master contains AudioContext and the master sound output. + */ + var Master = function () { + var audiocontext = p5.prototype.getAudioContext(); + this.input = audiocontext.createGain(); + this.output = audiocontext.createGain(); + //put a hard limiter on the output + this.limiter = audiocontext.createDynamicsCompressor(); + this.limiter.threshold.value = 0; + this.limiter.ratio.value = 100; + this.audiocontext = audiocontext; + this.output.disconnect(); + // an array of input sources + this.inputSources = []; + // connect input to limiter + this.input.connect(this.limiter); + // connect limiter to output + this.limiter.connect(this.output); + // meter is just for global Amplitude / FFT analysis + this.meter = audiocontext.createGain(); + this.fftMeter = audiocontext.createGain(); + this.output.connect(this.meter); + this.output.connect(this.fftMeter); + // connect output to destination + this.output.connect(this.audiocontext.destination); + // an array of all sounds in the sketch + this.soundArray = []; + // an array of all musical parts in the sketch + this.parts = []; + // file extensions to search for + this.extensions = []; + }; + // create a single instance of the p5Sound / master output for use within this sketch + var p5sound = new Master(); + /** + * Returns a number representing the master amplitude (volume) for sound + * in this sketch. + * + * @method getMasterVolume + * @return {Number} Master amplitude (volume) for sound in this sketch. + * Should be between 0.0 (silence) and 1.0. + */ + p5.prototype.getMasterVolume = function () { + return p5sound.output.gain.value; + }; + /** + * <p>Scale the output of all sound in this sketch</p> + * Scaled between 0.0 (silence) and 1.0 (full volume). + * 1.0 is the maximum amplitude of a digital sound, so multiplying + * by greater than 1.0 may cause digital distortion. To + * fade, provide a <code>rampTime</code> parameter. For more + * complex fades, see the Env class. + * + * Alternately, you can pass in a signal source such as an + * oscillator to modulate the amplitude with an audio signal. + * + * <p><b>How This Works</b>: When you load the p5.sound module, it + * creates a single instance of p5sound. All sound objects in this + * module output to p5sound before reaching your computer's output. + * So if you change the amplitude of p5sound, it impacts all of the + * sound in this module.</p> + * + * <p>If no value is provided, returns a Web Audio API Gain Node</p> + * + * @method masterVolume + * @param {Number|Object} volume Volume (amplitude) between 0.0 + * and 1.0 or modulating signal/oscillator + * @param {Number} [rampTime] Fade for t seconds + * @param {Number} [timeFromNow] Schedule this event to happen at + * t seconds in the future + */ + p5.prototype.masterVolume = function (vol, rampTime, tFromNow) { + if (typeof vol === 'number') { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = p5sound.output.gain.value; + p5sound.output.gain.cancelScheduledValues(now + tFromNow); + p5sound.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + p5sound.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(p5sound.output.gain); + } else { + // return the Gain Node + return p5sound.output.gain; + } + }; + /** + * p5.soundOut is the p5.sound master output. It sends output to + * the destination of this window's web audio context. It contains + * Web Audio API nodes including a dyanmicsCompressor (<code>.limiter</code>), + * and Gain Nodes for <code>.input</code> and <code>.output</code>. + * + * @property p5.soundOut + * @type {Object} + */ + p5.soundOut = p5sound; + /** + * a silent connection to the DesinationNode + * which will ensure that anything connected to it + * will not be garbage collected + * + * @private + */ + p5.soundOut._silentNode = p5sound.audiocontext.createGain(); + p5.soundOut._silentNode.gain.value = 0; + p5.soundOut._silentNode.connect(p5sound.audiocontext.destination); + return p5sound; +}(sndcore); +var helpers; +helpers = function () { + 'use strict'; + var p5sound = master; + /** + * Returns a number representing the sample rate, in samples per second, + * of all sound objects in this audio context. It is determined by the + * sampling rate of your operating system's sound card, and it is not + * currently possile to change. + * It is often 44100, or twice the range of human hearing. + * + * @method sampleRate + * @return {Number} samplerate samples per second + */ + p5.prototype.sampleRate = function () { + return p5sound.audiocontext.sampleRate; + }; + /** + * Returns the closest MIDI note value for + * a given frequency. + * + * @param {Number} frequency A freqeuncy, for example, the "A" + * above Middle C is 440Hz + * @return {Number} MIDI note value + */ + p5.prototype.freqToMidi = function (f) { + var mathlog2 = Math.log(f / 440) / Math.log(2); + var m = Math.round(12 * mathlog2) + 57; + return m; + }; + /** + * Returns the frequency value of a MIDI note value. + * General MIDI treats notes as integers where middle C + * is 60, C# is 61, D is 62 etc. Useful for generating + * musical frequencies with oscillators. + * + * @method midiToFreq + * @param {Number} midiNote The number of a MIDI note + * @return {Number} Frequency value of the given MIDI note + * @example + * <div><code> + * var notes = [60, 64, 67, 72]; + * var i = 0; + * + * function setup() { + * osc = new p5.Oscillator('Triangle'); + * osc.start(); + * frameRate(1); + * } + * + * function draw() { + * var freq = midiToFreq(notes[i]); + * osc.freq(freq); + * i++; + * if (i >= notes.length){ + * i = 0; + * } + * } + * </code></div> + */ + p5.prototype.midiToFreq = function (m) { + return 440 * Math.pow(2, (m - 69) / 12); + }; + /** + * List the SoundFile formats that you will include. LoadSound + * will search your directory for these extensions, and will pick + * a format that is compatable with the client's web browser. + * <a href="http://media.io/">Here</a> is a free online file + * converter. + * + * @method soundFormats + * @param {String|Strings} formats i.e. 'mp3', 'wav', 'ogg' + * @example + * <div><code> + * function preload() { + * // set the global sound formats + * soundFormats('mp3', 'ogg'); + * + * // load either beatbox.mp3, or .ogg, depending on browser + * mySound = loadSound('../sounds/beatbox.mp3'); + * } + * + * function setup() { + * mySound.play(); + * } + * </code></div> + */ + p5.prototype.soundFormats = function () { + // reset extensions array + p5sound.extensions = []; + // add extensions + for (var i = 0; i < arguments.length; i++) { + arguments[i] = arguments[i].toLowerCase(); + if ([ + 'mp3', + 'wav', + 'ogg', + 'm4a', + 'aac' + ].indexOf(arguments[i]) > -1) { + p5sound.extensions.push(arguments[i]); + } else { + throw arguments[i] + ' is not a valid sound format!'; + } + } + }; + p5.prototype.disposeSound = function () { + for (var i = 0; i < p5sound.soundArray.length; i++) { + p5sound.soundArray[i].dispose(); + } + }; + // register removeSound to dispose of p5sound SoundFiles, Convolvers, + // Oscillators etc when sketch ends + p5.prototype.registerMethod('remove', p5.prototype.disposeSound); + p5.prototype._checkFileFormats = function (paths) { + var path; + // if path is a single string, check to see if extension is provided + if (typeof paths === 'string') { + path = paths; + // see if extension is provided + var extTest = path.split('.').pop(); + // if an extension is provided... + if ([ + 'mp3', + 'wav', + 'ogg', + 'm4a', + 'aac' + ].indexOf(extTest) > -1) { + var supported = p5.prototype.isFileSupported(extTest); + if (supported) { + path = path; + } else { + var pathSplit = path.split('.'); + var pathCore = pathSplit[pathSplit.length - 1]; + for (var i = 0; i < p5sound.extensions.length; i++) { + var extension = p5sound.extensions[i]; + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + pathCore = ''; + if (pathSplit.length === 2) { + pathCore += pathSplit[0]; + } + for (var i = 1; i <= pathSplit.length - 2; i++) { + var p = pathSplit[i]; + pathCore += '.' + p; + } + path = pathCore += '.'; + path = path += extension; + break; + } + } + } + } else { + for (var i = 0; i < p5sound.extensions.length; i++) { + var extension = p5sound.extensions[i]; + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + path = path + '.' + extension; + break; + } + } + } + } else if (typeof paths === 'object') { + for (var i = 0; i < paths.length; i++) { + var extension = paths[i].split('.').pop(); + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + // console.log('.'+extension + ' is ' + supported + + // ' supported by your browser.'); + path = paths[i]; + break; + } + } + } + return path; + }; + /** + * Used by Osc and Env to chain signal math + */ + p5.prototype._mathChain = function (o, math, thisChain, nextChain, type) { + // if this type of math already exists in the chain, replace it + for (var i in o.mathOps) { + if (o.mathOps[i] instanceof type) { + o.mathOps[i].dispose(); + thisChain = i; + if (thisChain < o.mathOps.length - 1) { + nextChain = o.mathOps[i + 1]; + } + } + } + o.mathOps[thisChain - 1].disconnect(); + o.mathOps[thisChain - 1].connect(math); + math.connect(nextChain); + o.mathOps[thisChain] = math; + return o; + }; +}(master); +var errorHandler; +errorHandler = function () { + 'use strict'; + /** + * Helper function to generate an error + * with a custom stack trace that points to the sketch + * and removes other parts of the stack trace. + * + * @private + * + * @param {String} name custom error name + * @param {String} errorTrace custom error trace + * @param {String} failedPath path to the file that failed to load + * @property {String} name custom error name + * @property {String} message custom error message + * @property {String} stack trace the error back to a line in the user's sketch. + * Note: this edits out stack trace within p5.js and p5.sound. + * @property {String} originalStack unedited, original stack trace + * @property {String} failedPath path to the file that failed to load + * @return {Error} returns a custom Error object + */ + var CustomError = function (name, errorTrace, failedPath) { + var err = new Error(); + var tempStack, splitStack; + err.name = name; + err.originalStack = err.stack + errorTrace; + tempStack = err.stack + errorTrace; + err.failedPath = failedPath; + // only print the part of the stack trace that refers to the user code: + var splitStack = tempStack.split('\n'); + splitStack = splitStack.filter(function (ln) { + return !ln.match(/(p5.|native code|globalInit)/g); + }); + err.stack = splitStack.join('\n'); + return err; + }; + return CustomError; +}(); +var panner; +panner = function () { + 'use strict'; + var p5sound = master; + var ac = p5sound.audiocontext; + // Stereo panner + // if there is a stereo panner node use it + if (typeof ac.createStereoPanner !== 'undefined') { + p5.Panner = function (input, output, numInputChannels) { + this.stereoPanner = this.input = ac.createStereoPanner(); + input.connect(this.stereoPanner); + this.stereoPanner.connect(output); + }; + p5.Panner.prototype.pan = function (val, tFromNow) { + var time = tFromNow || 0; + var t = ac.currentTime + time; + this.stereoPanner.pan.linearRampToValueAtTime(val, t); + }; + p5.Panner.prototype.inputChannels = function (numChannels) { + }; + p5.Panner.prototype.connect = function (obj) { + this.stereoPanner.connect(obj); + }; + p5.Panner.prototype.disconnect = function (obj) { + this.stereoPanner.disconnect(); + }; + } else { + // if there is no createStereoPanner object + // such as in safari 7.1.7 at the time of writing this + // use this method to create the effect + p5.Panner = function (input, output, numInputChannels) { + this.input = ac.createGain(); + input.connect(this.input); + this.left = ac.createGain(); + this.right = ac.createGain(); + this.left.channelInterpretation = 'discrete'; + this.right.channelInterpretation = 'discrete'; + // if input is stereo + if (numInputChannels > 1) { + this.splitter = ac.createChannelSplitter(2); + this.input.connect(this.splitter); + this.splitter.connect(this.left, 1); + this.splitter.connect(this.right, 0); + } else { + this.input.connect(this.left); + this.input.connect(this.right); + } + this.output = ac.createChannelMerger(2); + this.left.connect(this.output, 0, 1); + this.right.connect(this.output, 0, 0); + this.output.connect(output); + }; + // -1 is left, +1 is right + p5.Panner.prototype.pan = function (val, tFromNow) { + var time = tFromNow || 0; + var t = ac.currentTime + time; + var v = (val + 1) / 2; + var rightVal = Math.cos(v * Math.PI / 2); + var leftVal = Math.sin(v * Math.PI / 2); + this.left.gain.linearRampToValueAtTime(leftVal, t); + this.right.gain.linearRampToValueAtTime(rightVal, t); + }; + p5.Panner.prototype.inputChannels = function (numChannels) { + if (numChannels === 1) { + this.input.disconnect(); + this.input.connect(this.left); + this.input.connect(this.right); + } else if (numChannels === 2) { + if (typeof (this.splitter === 'undefined')) { + this.splitter = ac.createChannelSplitter(2); + } + this.input.disconnect(); + this.input.connect(this.splitter); + this.splitter.connect(this.left, 1); + this.splitter.connect(this.right, 0); + } + }; + p5.Panner.prototype.connect = function (obj) { + this.output.connect(obj); + }; + p5.Panner.prototype.disconnect = function (obj) { + this.output.disconnect(); + }; + } + // 3D panner + p5.Panner3D = function (input, output) { + var panner3D = ac.createPanner(); + panner3D.panningModel = 'HRTF'; + panner3D.distanceModel = 'linear'; + panner3D.setPosition(0, 0, 0); + input.connect(panner3D); + panner3D.connect(output); + panner3D.pan = function (xVal, yVal, zVal) { + panner3D.setPosition(xVal, yVal, zVal); + }; + return panner3D; + }; +}(master); +var soundfile; +soundfile = function () { + 'use strict'; + var CustomError = errorHandler; + var p5sound = master; + var ac = p5sound.audiocontext; + /** + * <p>SoundFile object with a path to a file.</p> + * + * <p>The p5.SoundFile may not be available immediately because + * it loads the file information asynchronously.</p> + * + * <p>To do something with the sound as soon as it loads + * pass the name of a function as the second parameter.</p> + * + * <p>Only one file path is required. However, audio file formats + * (i.e. mp3, ogg, wav and m4a/aac) are not supported by all + * web browsers. If you want to ensure compatability, instead of a single + * file path, you may include an Array of filepaths, and the browser will + * choose a format that works.</p> + * + * @class p5.SoundFile + * @constructor + * @param {String/Array} path path to a sound file (String). Optionally, + * you may include multiple file formats in + * an array. Alternately, accepts an object + * from the HTML5 File API, or a p5.File. + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if file fails to + * load. This function will receive an error or + * XMLHttpRequest object with information + * about what went wrong. + * @param {Function} [whileLoadingCallback] Name of a function to call while file + * is loading. That function will + * receive percentage loaded + * (between 0 and 1) as a + * parameter. + * + * @return {Object} p5.SoundFile Object + * @example + * <div><code> + * + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * mySound.setVolume(0.1); + * mySound.play(); + * } + * + * </code></div> + */ + p5.SoundFile = function (paths, onload, onerror, whileLoading) { + if (typeof paths !== 'undefined') { + if (typeof paths == 'string' || typeof paths[0] == 'string') { + var path = p5.prototype._checkFileFormats(paths); + this.url = path; + } else if (typeof paths == 'object') { + if (!(window.File && window.FileReader && window.FileList && window.Blob)) { + // The File API isn't supported in this browser + throw 'Unable to load file because the File API is not supported'; + } + } + // if type is a p5.File...get the actual file + if (paths.file) { + paths = paths.file; + } + this.file = paths; + } + // private _onended callback, set by the method: onended(callback) + this._onended = function () { + }; + this._looping = false; + this._playing = false; + this._paused = false; + this._pauseTime = 0; + // cues for scheduling events with addCue() removeCue() + this._cues = []; + // position of the most recently played sample + this._lastPos = 0; + this._counterNode; + this._scopeNode; + // array of sources so that they can all be stopped! + this.bufferSourceNodes = []; + // current source + this.bufferSourceNode = null; + this.buffer = null; + this.playbackRate = 1; + this.gain = 1; + this.input = p5sound.audiocontext.createGain(); + this.output = p5sound.audiocontext.createGain(); + this.reversed = false; + // start and end of playback / loop + this.startTime = 0; + this.endTime = null; + this.pauseTime = 0; + // "restart" would stop playback before retriggering + this.mode = 'sustain'; + // time that playback was started, in millis + this.startMillis = null; + // stereo panning + this.panPosition = 0; + this.panner = new p5.Panner(this.output, p5sound.input, 2); + // it is possible to instantiate a soundfile with no path + if (this.url || this.file) { + this.load(onload, onerror); + } + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + if (typeof whileLoading === 'function') { + this._whileLoading = whileLoading; + } else { + this._whileLoading = function () { + }; + } + }; + // register preload handling of loadSound + p5.prototype.registerPreloadMethod('loadSound', p5.prototype); + /** + * loadSound() returns a new p5.SoundFile from a specified + * path. If called during preload(), the p5.SoundFile will be ready + * to play in time for setup() and draw(). If called outside of + * preload, the p5.SoundFile will not be ready immediately, so + * loadSound accepts a callback as the second parameter. Using a + * <a href="https://github.com/processing/p5.js/wiki/Local-server"> + * local server</a> is recommended when loading external files. + * + * @method loadSound + * @param {String/Array} path Path to the sound file, or an array with + * paths to soundfiles in multiple formats + * i.e. ['sound.ogg', 'sound.mp3']. + * Alternately, accepts an object: either + * from the HTML5 File API, or a p5.File. + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if there is + * an error loading the file. + * @param {Function} [whileLoading] Name of a function to call while file is loading. + * This function will receive the percentage loaded + * so far, from 0.0 to 1.0. + * @return {SoundFile} Returns a p5.SoundFile + * @example + * <div><code> + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * mySound.setVolume(0.1); + * mySound.play(); + * } + * </code></div> + */ + p5.prototype.loadSound = function (path, callback, onerror, whileLoading) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + var s = new p5.SoundFile(path, callback, onerror, whileLoading); + return s; + }; + /** + * This is a helper function that the p5.SoundFile calls to load + * itself. Accepts a callback (the name of another function) + * as an optional parameter. + * + * @private + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if there is an error + */ + p5.SoundFile.prototype.load = function (callback, errorCallback) { + var loggedError = false; + var self = this; + var errorTrace = new Error().stack; + if (this.url != undefined && this.url != '') { + var request = new XMLHttpRequest(); + request.addEventListener('progress', function (evt) { + self._updateProgress(evt); + }, false); + request.open('GET', this.url, true); + request.responseType = 'arraybuffer'; + request.onload = function () { + if (request.status == 200) { + // on sucess loading file: + ac.decodeAudioData(request.response, // success decoding buffer: + function (buff) { + self.buffer = buff; + self.panner.inputChannels(buff.numberOfChannels); + if (callback) { + callback(self); + } + }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 + function (e) { + var err = new CustomError('decodeAudioData', errorTrace, self.url); + var msg = 'AudioContext error at decodeAudioData for ' + self.url; + if (errorCallback) { + err.msg = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }); + } else { + var err = new CustomError('loadSound', errorTrace, self.url); + var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + } + }; + // if there is another error, aside from 404... + request.onerror = function (e) { + var err = new CustomError('loadSound', errorTrace, self.url); + var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }; + request.send(); + } else if (this.file != undefined) { + var reader = new FileReader(); + var self = this; + reader.onload = function () { + ac.decodeAudioData(reader.result, function (buff) { + self.buffer = buff; + self.panner.inputChannels(buff.numberOfChannels); + if (callback) { + callback(self); + } + }); + }; + reader.onerror = function (e) { + if (onerror) + onerror(e); + }; + reader.readAsArrayBuffer(this.file); + } + }; + // TO DO: use this method to create a loading bar that shows progress during file upload/decode. + p5.SoundFile.prototype._updateProgress = function (evt) { + if (evt.lengthComputable) { + var percentComplete = Math.log(evt.loaded / evt.total * 9.9); + this._whileLoading(percentComplete); + } else { + // Unable to compute progress information since the total size is unknown + this._whileLoading('size unknown'); + } + }; + /** + * Returns true if the sound file finished loading successfully. + * + * @method isLoaded + * @return {Boolean} + */ + p5.SoundFile.prototype.isLoaded = function () { + if (this.buffer) { + return true; + } else { + return false; + } + }; + /** + * Play the p5.SoundFile + * + * @method play + * @param {Number} [startTime] (optional) schedule playback to start (in seconds from now). + * @param {Number} [rate] (optional) playback rate + * @param {Number} [amp] (optional) amplitude (volume) + * of playback + * @param {Number} [cueStart] (optional) cue start time in seconds + * @param {Number} [duration] (optional) duration of playback in seconds + */ + p5.SoundFile.prototype.play = function (time, rate, amp, _cueStart, duration) { + var self = this; + var now = p5sound.audiocontext.currentTime; + var cueStart, cueEnd; + var time = time || 0; + if (time < 0) { + time = 0; + } + time = time + now; + // TO DO: if already playing, create array of buffers for easy stop() + if (this.buffer) { + // reset the pause time (if it was paused) + this._pauseTime = 0; + // handle restart playmode + if (this.mode === 'restart' && this.buffer && this.bufferSourceNode) { + var now = p5sound.audiocontext.currentTime; + this.bufferSourceNode.stop(time); + this._counterNode.stop(time); + } + // make a new source and counter. They are automatically assigned playbackRate and buffer + this.bufferSourceNode = this._initSourceNode(); + // garbage collect counterNode and create a new one + if (this._counterNode) + this._counterNode = undefined; + this._counterNode = this._initCounterNode(); + if (_cueStart) { + if (_cueStart >= 0 && _cueStart < this.buffer.duration) { + // this.startTime = cueStart; + cueStart = _cueStart; + } else { + throw 'start time out of range'; + } + } else { + cueStart = 0; + } + if (duration) { + // if duration is greater than buffer.duration, just play entire file anyway rather than throw an error + duration = duration <= this.buffer.duration - cueStart ? duration : this.buffer.duration; + } else { + duration = this.buffer.duration - cueStart; + } + // TO DO: Fix this. It broke in Safari + // + // method of controlling gain for individual bufferSourceNodes, without resetting overall soundfile volume + // if (typeof(this.bufferSourceNode.gain === 'undefined' ) ) { + // this.bufferSourceNode.gain = p5sound.audiocontext.createGain(); + // } + // this.bufferSourceNode.connect(this.bufferSourceNode.gain); + // set local amp if provided, otherwise 1 + var a = amp || 1; + // this.bufferSourceNode.gain.gain.setValueAtTime(a, p5sound.audiocontext.currentTime); + // this.bufferSourceNode.gain.connect(this.output); + this.bufferSourceNode.connect(this.output); + this.output.gain.value = a; + // not necessary with _initBufferSource ? + // this.bufferSourceNode.playbackRate.cancelScheduledValues(now); + rate = rate || Math.abs(this.playbackRate); + this.bufferSourceNode.playbackRate.setValueAtTime(rate, now); + // if it was paused, play at the pause position + if (this._paused) { + this.bufferSourceNode.start(time, this.pauseTime, duration); + this._counterNode.start(time, this.pauseTime, duration); + } else { + this.bufferSourceNode.start(time, cueStart, duration); + this._counterNode.start(time, cueStart, duration); + } + this._playing = true; + this._paused = false; + // add source to sources array, which is used in stopAll() + this.bufferSourceNodes.push(this.bufferSourceNode); + this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1; + // delete this.bufferSourceNode from the sources array when it is done playing: + var clearOnEnd = function (e) { + this._playing = false; + this.removeEventListener('ended', clearOnEnd, false); + // call the onended callback + self._onended(self); + self.bufferSourceNodes.forEach(function (n, i) { + if (n._playing === false) { + self.bufferSourceNodes.splice(i); + } + }); + if (self.bufferSourceNodes.length === 0) { + self._playing = false; + } + }; + this.bufferSourceNode.onended = clearOnEnd; + } else { + throw 'not ready to play file, buffer has yet to load. Try preload()'; + } + // if looping, will restart at original time + this.bufferSourceNode.loop = this._looping; + this._counterNode.loop = this._looping; + if (this._looping === true) { + var cueEnd = cueStart + duration; + this.bufferSourceNode.loopStart = cueStart; + this.bufferSourceNode.loopEnd = cueEnd; + this._counterNode.loopStart = cueStart; + this._counterNode.loopEnd = cueEnd; + } + }; + /** + * p5.SoundFile has two play modes: <code>restart</code> and + * <code>sustain</code>. Play Mode determines what happens to a + * p5.SoundFile if it is triggered while in the middle of playback. + * In sustain mode, playback will continue simultaneous to the + * new playback. In restart mode, play() will stop playback + * and start over. Sustain is the default mode. + * + * @method playMode + * @param {String} str 'restart' or 'sustain' + * @example + * <div><code> + * function setup(){ + * mySound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * function mouseClicked() { + * mySound.playMode('sustain'); + * mySound.play(); + * } + * function keyPressed() { + * mySound.playMode('restart'); + * mySound.play(); + * } + * + * </code></div> + */ + p5.SoundFile.prototype.playMode = function (str) { + var s = str.toLowerCase(); + // if restart, stop all other sounds from playing + if (s === 'restart' && this.buffer && this.bufferSourceNode) { + for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { + var now = p5sound.audiocontext.currentTime; + this.bufferSourceNodes[i].stop(now); + } + } + // set play mode to effect future playback + if (s === 'restart' || s === 'sustain') { + this.mode = s; + } else { + throw 'Invalid play mode. Must be either "restart" or "sustain"'; + } + }; + /** + * Pauses a file that is currently playing. If the file is not + * playing, then nothing will happen. + * + * After pausing, .play() will resume from the paused + * position. + * If p5.SoundFile had been set to loop before it was paused, + * it will continue to loop after it is unpaused with .play(). + * + * @method pause + * @param {Number} [startTime] (optional) schedule event to occur + * seconds from now + * @example + * <div><code> + * var soundFile; + * + * function preload() { + * soundFormats('ogg', 'mp3'); + * soundFile = loadSound('assets/Damscray_-_Dancing_Tiger_02.mp3'); + * } + * function setup() { + * background(0, 255, 0); + * soundFile.setVolume(0.1); + * soundFile.loop(); + * } + * function keyTyped() { + * if (key == 'p') { + * soundFile.pause(); + * background(255, 0, 0); + * } + * } + * + * function keyReleased() { + * if (key == 'p') { + * soundFile.play(); + * background(0, 255, 0); + * } + * </code> + * </div> + */ + p5.SoundFile.prototype.pause = function (time) { + var now = p5sound.audiocontext.currentTime; + var time = time || 0; + var pTime = time + now; + if (this.isPlaying() && this.buffer && this.bufferSourceNode) { + this.pauseTime = this.currentTime(); + this.bufferSourceNode.stop(pTime); + this._counterNode.stop(pTime); + this._paused = true; + this._playing = false; + this._pauseTime = this.currentTime(); + } else { + this._pauseTime = 0; + } + }; + /** + * Loop the p5.SoundFile. Accepts optional parameters to set the + * playback rate, playback volume, loopStart, loopEnd. + * + * @method loop + * @param {Number} [startTime] (optional) schedule event to occur + * seconds from now + * @param {Number} [rate] (optional) playback rate + * @param {Number} [amp] (optional) playback volume + * @param {Number} [cueLoopStart](optional) startTime in seconds + * @param {Number} [duration] (optional) loop duration in seconds + */ + p5.SoundFile.prototype.loop = function (startTime, rate, amp, loopStart, duration) { + this._looping = true; + this.play(startTime, rate, amp, loopStart, duration); + }; + /** + * Set a p5.SoundFile's looping flag to true or false. If the sound + * is currently playing, this change will take effect when it + * reaches the end of the current playback. + * + * @param {Boolean} Boolean set looping to true or false + */ + p5.SoundFile.prototype.setLoop = function (bool) { + if (bool === true) { + this._looping = true; + } else if (bool === false) { + this._looping = false; + } else { + throw 'Error: setLoop accepts either true or false'; + } + if (this.bufferSourceNode) { + this.bufferSourceNode.loop = this._looping; + this._counterNode.loop = this._looping; + } + }; + /** + * Returns 'true' if a p5.SoundFile is currently looping and playing, 'false' if not. + * + * @return {Boolean} + */ + p5.SoundFile.prototype.isLooping = function () { + if (!this.bufferSourceNode) { + return false; + } + if (this._looping === true && this.isPlaying() === true) { + return true; + } + return false; + }; + /** + * Returns true if a p5.SoundFile is playing, false if not (i.e. + * paused or stopped). + * + * @method isPlaying + * @return {Boolean} + */ + p5.SoundFile.prototype.isPlaying = function () { + return this._playing; + }; + /** + * Returns true if a p5.SoundFile is paused, false if not (i.e. + * playing or stopped). + * + * @method isPaused + * @return {Boolean} + */ + p5.SoundFile.prototype.isPaused = function () { + return this._paused; + }; + /** + * Stop soundfile playback. + * + * @method stop + * @param {Number} [startTime] (optional) schedule event to occur + * in seconds from now + */ + p5.SoundFile.prototype.stop = function (timeFromNow) { + var time = timeFromNow || 0; + if (this.mode == 'sustain') { + this.stopAll(time); + this._playing = false; + this.pauseTime = 0; + this._paused = false; + } else if (this.buffer && this.bufferSourceNode) { + var now = p5sound.audiocontext.currentTime; + var t = time || 0; + this.pauseTime = 0; + this.bufferSourceNode.stop(now + t); + this._counterNode.stop(now + t); + this._playing = false; + this._paused = false; + } + }; + /** + * Stop playback on all of this soundfile's sources. + * @private + */ + p5.SoundFile.prototype.stopAll = function (_time) { + var now = p5sound.audiocontext.currentTime; + var time = _time || 0; + if (this.buffer && this.bufferSourceNode) { + for (var i = 0; i < this.bufferSourceNodes.length; i++) { + if (typeof this.bufferSourceNodes[i] != undefined) { + try { + this.bufferSourceNodes[i].onended = function () { + }; + this.bufferSourceNodes[i].stop(now + time); + } catch (e) { + } + } + } + this._counterNode.stop(now + time); + this._onended(this); + } + }; + /** + * Multiply the output volume (amplitude) of a sound file + * between 0.0 (silence) and 1.0 (full volume). + * 1.0 is the maximum amplitude of a digital sound, so multiplying + * by greater than 1.0 may cause digital distortion. To + * fade, provide a <code>rampTime</code> parameter. For more + * complex fades, see the Env class. + * + * Alternately, you can pass in a signal source such as an + * oscillator to modulate the amplitude with an audio signal. + * + * @method setVolume + * @param {Number|Object} volume Volume (amplitude) between 0.0 + * and 1.0 or modulating signal/oscillator + * @param {Number} [rampTime] Fade for t seconds + * @param {Number} [timeFromNow] Schedule this event to happen at + * t seconds in the future + */ + p5.SoundFile.prototype.setVolume = function (vol, rampTime, tFromNow) { + if (typeof vol === 'number') { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now + tFromNow); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(this.output.gain); + } else { + // return the Gain Node + return this.output.gain; + } + }; + // same as setVolume, to match Processing Sound + p5.SoundFile.prototype.amp = p5.SoundFile.prototype.setVolume; + // these are the same thing + p5.SoundFile.prototype.fade = p5.SoundFile.prototype.setVolume; + p5.SoundFile.prototype.getVolume = function () { + return this.output.gain.value; + }; + /** + * Set the stereo panning of a p5.sound object to + * a floating point number between -1.0 (left) and 1.0 (right). + * Default is 0.0 (center). + * + * @method pan + * @param {Number} [panValue] Set the stereo panner + * @param {Number} timeFromNow schedule this event to happen + * seconds from now + * @example + * <div><code> + * + * var ball = {}; + * var soundFile; + * + * function setup() { + * soundFormats('ogg', 'mp3'); + * soundFile = loadSound('assets/beatbox.mp3'); + * } + * + * function draw() { + * background(0); + * ball.x = constrain(mouseX, 0, width); + * ellipse(ball.x, height/2, 20, 20) + * } + * + * function mousePressed(){ + * // map the ball's x location to a panning degree + * // between -1.0 (left) and 1.0 (right) + * var panning = map(ball.x, 0., width,-1.0, 1.0); + * soundFile.pan(panning); + * soundFile.play(); + * } + * </div></code> + */ + p5.SoundFile.prototype.pan = function (pval, tFromNow) { + this.panPosition = pval; + this.panner.pan(pval, tFromNow); + }; + /** + * Returns the current stereo pan position (-1.0 to 1.0) + * + * @return {Number} Returns the stereo pan setting of the Oscillator + * as a number between -1.0 (left) and 1.0 (right). + * 0.0 is center and default. + */ + p5.SoundFile.prototype.getPan = function () { + return this.panPosition; + }; + /** + * Set the playback rate of a sound file. Will change the speed and the pitch. + * Values less than zero will reverse the audio buffer. + * + * @method rate + * @param {Number} [playbackRate] Set the playback rate. 1.0 is normal, + * .5 is half-speed, 2.0 is twice as fast. + * Must be greater than zero. + * @example + * <div><code> + * var song; + * + * function preload() { + * song = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * song.loop(); + * } + * + * function draw() { + * background(200); + * + * // Set the rate to a range between 0.1 and 4 + * // Changing the rate also alters the pitch + * var speed = map(mouseY, 0.1, height, 0, 2); + * speed = constrain(speed, 0.01, 4); + * song.rate(speed); + * + * // Draw a circle to show what is going on + * stroke(0); + * fill(51, 100); + * ellipse(mouseX, 100, 48, 48); + * } + * + * </code> + * </div> + * + */ + p5.SoundFile.prototype.rate = function (playbackRate) { + if (this.playbackRate === playbackRate && this.bufferSourceNode) { + if (this.bufferSourceNode.playbackRate.value === playbackRate) { + return; + } + } + this.playbackRate = playbackRate; + var rate = playbackRate; + if (this.playbackRate === 0 && this._playing) { + this.pause(); + } + if (this.playbackRate < 0 && !this.reversed) { + var cPos = this.currentTime(); + var cRate = this.bufferSourceNode.playbackRate.value; + // this.pause(); + this.reverseBuffer(); + rate = Math.abs(playbackRate); + var newPos = (cPos - this.duration()) / rate; + this.pauseTime = newPos; + } else if (this.playbackRate > 0 && this.reversed) { + this.reverseBuffer(); + } + if (this.bufferSourceNode) { + var now = p5sound.audiocontext.currentTime; + this.bufferSourceNode.playbackRate.cancelScheduledValues(now); + this.bufferSourceNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now); + this._counterNode.playbackRate.cancelScheduledValues(now); + this._counterNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now); + } + }; + // TO DO: document this + p5.SoundFile.prototype.setPitch = function (num) { + var newPlaybackRate = midiToFreq(num) / midiToFreq(60); + this.rate(newPlaybackRate); + }; + p5.SoundFile.prototype.getPlaybackRate = function () { + return this.playbackRate; + }; + /** + * Returns the duration of a sound file in seconds. + * + * @method duration + * @return {Number} The duration of the soundFile in seconds. + */ + p5.SoundFile.prototype.duration = function () { + // Return Duration + if (this.buffer) { + return this.buffer.duration; + } else { + return 0; + } + }; + /** + * Return the current position of the p5.SoundFile playhead, in seconds. + * Note that if you change the playbackRate while the p5.SoundFile is + * playing, the results may not be accurate. + * + * @method currentTime + * @return {Number} currentTime of the soundFile in seconds. + */ + p5.SoundFile.prototype.currentTime = function () { + // TO DO --> make reverse() flip these values appropriately + if (this._pauseTime > 0) { + return this._pauseTime; + } else { + return this._lastPos / ac.sampleRate; + } + }; + /** + * Move the playhead of the song to a position, in seconds. Start + * and Stop time. If none are given, will reset the file to play + * entire duration from start to finish. + * + * @method jump + * @param {Number} cueTime cueTime of the soundFile in seconds. + * @param {Number} uuration duration in seconds. + */ + p5.SoundFile.prototype.jump = function (cueTime, duration) { + if (cueTime < 0 || cueTime > this.buffer.duration) { + throw 'jump time out of range'; + } + if (duration > this.buffer.duration - cueTime) { + throw 'end time out of range'; + } + var cTime = cueTime || 0; + var eTime = duration || this.buffer.duration - cueTime; + if (this.isPlaying()) { + this.stop(); + } + this.play(0, this.playbackRate, this.output.gain.value, cTime, eTime); + }; + /** + * Return the number of channels in a sound file. + * For example, Mono = 1, Stereo = 2. + * + * @method channels + * @return {Number} [channels] + */ + p5.SoundFile.prototype.channels = function () { + return this.buffer.numberOfChannels; + }; + /** + * Return the sample rate of the sound file. + * + * @method sampleRate + * @return {Number} [sampleRate] + */ + p5.SoundFile.prototype.sampleRate = function () { + return this.buffer.sampleRate; + }; + /** + * Return the number of samples in a sound file. + * Equal to sampleRate * duration. + * + * @method frames + * @return {Number} [sampleCount] + */ + p5.SoundFile.prototype.frames = function () { + return this.buffer.length; + }; + /** + * Returns an array of amplitude peaks in a p5.SoundFile that can be + * used to draw a static waveform. Scans through the p5.SoundFile's + * audio buffer to find the greatest amplitudes. Accepts one + * parameter, 'length', which determines size of the array. + * Larger arrays result in more precise waveform visualizations. + * + * Inspired by Wavesurfer.js. + * + * @method getPeaks + * @params {Number} [length] length is the size of the returned array. + * Larger length results in more precision. + * Defaults to 5*width of the browser window. + * @returns {Float32Array} Array of peaks. + */ + p5.SoundFile.prototype.getPeaks = function (length) { + if (this.buffer) { + // set length to window's width if no length is provided + if (!length) { + length = window.width * 5; + } + if (this.buffer) { + var buffer = this.buffer; + var sampleSize = buffer.length / length; + var sampleStep = ~~(sampleSize / 10) || 1; + var channels = buffer.numberOfChannels; + var peaks = new Float32Array(Math.round(length)); + for (var c = 0; c < channels; c++) { + var chan = buffer.getChannelData(c); + for (var i = 0; i < length; i++) { + var start = ~~(i * sampleSize); + var end = ~~(start + sampleSize); + var max = 0; + for (var j = start; j < end; j += sampleStep) { + var value = chan[j]; + if (value > max) { + max = value; + } else if (-value > max) { + max = value; + } + } + if (c === 0 || Math.abs(max) > peaks[i]) { + peaks[i] = max; + } + } + } + return peaks; + } + } else { + throw 'Cannot load peaks yet, buffer is not loaded'; + } + }; + /** + * Reverses the p5.SoundFile's buffer source. + * Playback must be handled separately (see example). + * + * @method reverseBuffer + * @example + * <div><code> + * var drum; + * + * function preload() { + * drum = loadSound('assets/drum.mp3'); + * } + * + * function setup() { + * drum.reverseBuffer(); + * drum.play(); + * } + * + * </code> + * </div> + */ + p5.SoundFile.prototype.reverseBuffer = function () { + var curVol = this.getVolume(); + this.setVolume(0, 0.01, 0); + this.pause(); + if (this.buffer) { + for (var i = 0; i < this.buffer.numberOfChannels; i++) { + Array.prototype.reverse.call(this.buffer.getChannelData(i)); + } + // set reversed flag + this.reversed = !this.reversed; + } else { + throw 'SoundFile is not done loading'; + } + this.setVolume(curVol, 0.01, 0.0101); + this.play(); + }; + /** + * Schedule an event to be called when the soundfile + * reaches the end of a buffer. If the soundfile is + * playing through once, this will be called when it + * ends. If it is looping, it will be called when + * stop is called. + * + * @method onended + * @param {Function} callback function to call when the + * soundfile has ended. + */ + p5.SoundFile.prototype.onended = function (callback) { + this._onended = callback; + return this; + }; + p5.SoundFile.prototype.add = function () { + }; + p5.SoundFile.prototype.dispose = function () { + var now = p5sound.audiocontext.currentTime; + // remove reference to soundfile + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.stop(now); + if (this.buffer && this.bufferSourceNode) { + for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { + if (this.bufferSourceNodes[i] !== null) { + this.bufferSourceNodes[i].disconnect(); + try { + this.bufferSourceNodes[i].stop(now); + } catch (e) { + } + this.bufferSourceNodes[i] = null; + } + } + if (this.isPlaying()) { + try { + this._counterNode.stop(now); + } catch (e) { + console.log(e); + } + this._counterNode = null; + } + } + if (this.output) { + this.output.disconnect(); + this.output = null; + } + if (this.panner) { + this.panner.disconnect(); + this.panner = null; + } + }; + /** + * Connects the output of a p5sound object to input of another + * p5.sound object. For example, you may connect a p5.SoundFile to an + * FFT or an Effect. If no parameter is given, it will connect to + * the master output. Most p5sound objects connect to the master + * output when they are created. + * + * @method connect + * @param {Object} [object] Audio object that accepts an input + */ + p5.SoundFile.prototype.connect = function (unit) { + if (!unit) { + this.panner.connect(p5sound.input); + } else { + if (unit.hasOwnProperty('input')) { + this.panner.connect(unit.input); + } else { + this.panner.connect(unit); + } + } + }; + /** + * Disconnects the output of this p5sound object. + * + * @method disconnect + */ + p5.SoundFile.prototype.disconnect = function (unit) { + this.panner.disconnect(unit); + }; + /** + */ + p5.SoundFile.prototype.getLevel = function (smoothing) { + console.warn('p5.SoundFile.getLevel has been removed from the library. Use p5.Amplitude instead'); + }; + /** + * Reset the source for this SoundFile to a + * new path (URL). + * + * @method setPath + * @param {String} path path to audio file + * @param {Function} callback Callback + */ + p5.SoundFile.prototype.setPath = function (p, callback) { + var path = p5.prototype._checkFileFormats(p); + this.url = path; + this.load(callback); + }; + /** + * Replace the current Audio Buffer with a new Buffer. + * + * @param {Array} buf Array of Float32 Array(s). 2 Float32 Arrays + * will create a stereo source. 1 will create + * a mono source. + */ + p5.SoundFile.prototype.setBuffer = function (buf) { + var numChannels = buf.length; + var size = buf[0].length; + var newBuffer = ac.createBuffer(numChannels, size, ac.sampleRate); + if (!buf[0] instanceof Float32Array) { + buf[0] = new Float32Array(buf[0]); + } + for (var channelNum = 0; channelNum < numChannels; channelNum++) { + var channel = newBuffer.getChannelData(channelNum); + channel.set(buf[channelNum]); + } + this.buffer = newBuffer; + // set numbers of channels on input to the panner + this.panner.inputChannels(numChannels); + }; + ////////////////////////////////////////////////// + // script processor node with an empty buffer to help + // keep a sample-accurate position in playback buffer. + // Inspired by Chinmay Pendharkar's technique for Sonoport --> http://bit.ly/1HwdCsV + // Copyright [2015] [Sonoport (Asia) Pte. Ltd.], + // Licensed under the Apache License http://apache.org/licenses/LICENSE-2.0 + //////////////////////////////////////////////////////////////////////////////////// + // initialize counterNode, set its initial buffer and playbackRate + p5.SoundFile.prototype._initCounterNode = function () { + var self = this; + var now = ac.currentTime; + var cNode = ac.createBufferSource(); + // dispose of scope node if it already exists + if (self._scopeNode) { + self._scopeNode.disconnect(); + self._scopeNode.onaudioprocess = undefined; + self._scopeNode = null; + } + self._scopeNode = ac.createScriptProcessor(256, 1, 1); + // create counter buffer of the same length as self.buffer + cNode.buffer = _createCounterBuffer(self.buffer); + cNode.playbackRate.setValueAtTime(self.playbackRate, now); + cNode.connect(self._scopeNode); + self._scopeNode.connect(p5.soundOut._silentNode); + self._scopeNode.onaudioprocess = function (processEvent) { + var inputBuffer = processEvent.inputBuffer.getChannelData(0); + // update the lastPos + self._lastPos = inputBuffer[inputBuffer.length - 1] || 0; + // do any callbacks that have been scheduled + self._onTimeUpdate(self._lastPos); + }; + return cNode; + }; + // initialize sourceNode, set its initial buffer and playbackRate + p5.SoundFile.prototype._initSourceNode = function () { + var self = this; + var now = ac.currentTime; + var bufferSourceNode = ac.createBufferSource(); + bufferSourceNode.buffer = self.buffer; + bufferSourceNode.playbackRate.setValueAtTime(self.playbackRate, now); + return bufferSourceNode; + }; + var _createCounterBuffer = function (buffer) { + var array = new Float32Array(buffer.length); + var audioBuf = ac.createBuffer(1, buffer.length, 44100); + for (var index = 0; index < buffer.length; index++) { + array[index] = index; + } + audioBuf.getChannelData(0).set(array); + return audioBuf; + }; + /** + * processPeaks returns an array of timestamps where it thinks there is a beat. + * + * This is an asynchronous function that processes the soundfile in an offline audio context, + * and sends the results to your callback function. + * + * The process involves running the soundfile through a lowpass filter, and finding all of the + * peaks above the initial threshold. If the total number of peaks are below the minimum number of peaks, + * it decreases the threshold and re-runs the analysis until either minPeaks or minThreshold are reached. + * + * @method processPeaks + * @param {Function} callback a function to call once this data is returned + * @param {Number} [initThreshold] initial threshold defaults to 0.9 + * @param {Number} [minThreshold] minimum threshold defaults to 0.22 + * @param {Number} [minPeaks] minimum number of peaks defaults to 200 + * @return {Array} Array of timestamped peaks + */ + p5.SoundFile.prototype.processPeaks = function (callback, _initThreshold, _minThreshold, _minPeaks) { + var bufLen = this.buffer.length; + var sampleRate = this.buffer.sampleRate; + var buffer = this.buffer; + var initialThreshold = _initThreshold || 0.9, threshold = initialThreshold, minThreshold = _minThreshold || 0.22, minPeaks = _minPeaks || 200; + // Create offline context + var offlineContext = new OfflineAudioContext(1, bufLen, sampleRate); + // create buffer source + var source = offlineContext.createBufferSource(); + source.buffer = buffer; + // Create filter. TO DO: allow custom setting of filter + var filter = offlineContext.createBiquadFilter(); + filter.type = 'lowpass'; + source.connect(filter); + filter.connect(offlineContext.destination); + // start playing at time:0 + source.start(0); + offlineContext.startRendering(); + // Render the song + // act on the result + offlineContext.oncomplete = function (e) { + var data = {}; + var filteredBuffer = e.renderedBuffer; + var bufferData = filteredBuffer.getChannelData(0); + // step 1: + // create Peak instances, add them to array, with strength and sampleIndex + do { + allPeaks = getPeaksAtThreshold(bufferData, threshold); + threshold -= 0.005; + } while (Object.keys(allPeaks).length < minPeaks && threshold >= minThreshold); + // step 2: + // find intervals for each peak in the sampleIndex, add tempos array + var intervalCounts = countIntervalsBetweenNearbyPeaks(allPeaks); + // step 3: find top tempos + var groups = groupNeighborsByTempo(intervalCounts, filteredBuffer.sampleRate); + // sort top intervals + var topTempos = groups.sort(function (intA, intB) { + return intB.count - intA.count; + }).splice(0, 5); + // set this SoundFile's tempo to the top tempo ?? + this.tempo = topTempos[0].tempo; + // step 4: + // new array of peaks at top tempo within a bpmVariance + var bpmVariance = 5; + var tempoPeaks = getPeaksAtTopTempo(allPeaks, topTempos[0].tempo, filteredBuffer.sampleRate, bpmVariance); + callback(tempoPeaks); + }; + }; + // process peaks + var Peak = function (amp, i) { + this.sampleIndex = i; + this.amplitude = amp; + this.tempos = []; + this.intervals = []; + }; + var allPeaks = []; + // 1. for processPeaks() Function to identify peaks above a threshold + // returns an array of peak indexes as frames (samples) of the original soundfile + function getPeaksAtThreshold(data, threshold) { + var peaksObj = {}; + var length = data.length; + for (var i = 0; i < length; i++) { + if (data[i] > threshold) { + var amp = data[i]; + var peak = new Peak(amp, i); + peaksObj[i] = peak; + // Skip forward ~ 1/8s to get past this peak. + i += 6000; + } + i++; + } + return peaksObj; + } + // 2. for processPeaks() + function countIntervalsBetweenNearbyPeaks(peaksObj) { + var intervalCounts = []; + var peaksArray = Object.keys(peaksObj).sort(); + for (var index = 0; index < peaksArray.length; index++) { + // find intervals in comparison to nearby peaks + for (var i = 0; i < 10; i++) { + var startPeak = peaksObj[peaksArray[index]]; + var endPeak = peaksObj[peaksArray[index + i]]; + if (startPeak && endPeak) { + var startPos = startPeak.sampleIndex; + var endPos = endPeak.sampleIndex; + var interval = endPos - startPos; + // add a sample interval to the startPeek in the allPeaks array + if (interval > 0) { + startPeak.intervals.push(interval); + } + // tally the intervals and return interval counts + var foundInterval = intervalCounts.some(function (intervalCount, p) { + if (intervalCount.interval === interval) { + intervalCount.count++; + return intervalCount; + } + }); + // store with JSON like formatting + if (!foundInterval) { + intervalCounts.push({ + interval: interval, + count: 1 + }); + } + } + } + } + return intervalCounts; + } + // 3. for processPeaks --> find tempo + function groupNeighborsByTempo(intervalCounts, sampleRate) { + var tempoCounts = []; + intervalCounts.forEach(function (intervalCount, i) { + try { + // Convert an interval to tempo + var theoreticalTempo = Math.abs(60 / (intervalCount.interval / sampleRate)); + theoreticalTempo = mapTempo(theoreticalTempo); + var foundTempo = tempoCounts.some(function (tempoCount) { + if (tempoCount.tempo === theoreticalTempo) + return tempoCount.count += intervalCount.count; + }); + if (!foundTempo) { + if (isNaN(theoreticalTempo)) { + return; + } + tempoCounts.push({ + tempo: Math.round(theoreticalTempo), + count: intervalCount.count + }); + } + } catch (e) { + throw e; + } + }); + return tempoCounts; + } + // 4. for processPeaks - get peaks at top tempo + function getPeaksAtTopTempo(peaksObj, tempo, sampleRate, bpmVariance) { + var peaksAtTopTempo = []; + var peaksArray = Object.keys(peaksObj).sort(); + // TO DO: filter out peaks that have the tempo and return + for (var i = 0; i < peaksArray.length; i++) { + var key = peaksArray[i]; + var peak = peaksObj[key]; + for (var j = 0; j < peak.intervals.length; j++) { + var intervalBPM = Math.round(Math.abs(60 / (peak.intervals[j] / sampleRate))); + intervalBPM = mapTempo(intervalBPM); + var dif = intervalBPM - tempo; + if (Math.abs(intervalBPM - tempo) < bpmVariance) { + // convert sampleIndex to seconds + peaksAtTopTempo.push(peak.sampleIndex / 44100); + } + } + } + // filter out peaks that are very close to each other + peaksAtTopTempo = peaksAtTopTempo.filter(function (peakTime, index, arr) { + var dif = arr[index + 1] - peakTime; + if (dif > 0.01) { + return true; + } + }); + return peaksAtTopTempo; + } + // helper function for processPeaks + function mapTempo(theoreticalTempo) { + // these scenarios create infinite while loop + if (!isFinite(theoreticalTempo) || theoreticalTempo == 0) { + return; + } + // Adjust the tempo to fit within the 90-180 BPM range + while (theoreticalTempo < 90) + theoreticalTempo *= 2; + while (theoreticalTempo > 180 && theoreticalTempo > 90) + theoreticalTempo /= 2; + return theoreticalTempo; + } + /*** SCHEDULE EVENTS ***/ + /** + * Schedule events to trigger every time a MediaElement + * (audio/video) reaches a playback cue point. + * + * Accepts a callback function, a time (in seconds) at which to trigger + * the callback, and an optional parameter for the callback. + * + * Time will be passed as the first parameter to the callback function, + * and param will be the second parameter. + * + * + * @method addCue + * @param {Number} time Time in seconds, relative to this media + * element's playback. For example, to trigger + * an event every time playback reaches two + * seconds, pass in the number 2. This will be + * passed as the first parameter to + * the callback function. + * @param {Function} callback Name of a function that will be + * called at the given time. The callback will + * receive time and (optionally) param as its + * two parameters. + * @param {Object} [value] An object to be passed as the + * second parameter to the + * callback function. + * @return {Number} id ID of this cue, + * useful for removeCue(id) + * @example + * <div><code> + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * mySound = loadSound('assets/beat.mp3'); + * + * // schedule calls to changeText + * mySound.addCue(0.50, changeText, "hello" ); + * mySound.addCue(1.00, changeText, "p5" ); + * mySound.addCue(1.50, changeText, "what" ); + * mySound.addCue(2.00, changeText, "do" ); + * mySound.addCue(2.50, changeText, "you" ); + * mySound.addCue(3.00, changeText, "want" ); + * mySound.addCue(4.00, changeText, "to" ); + * mySound.addCue(5.00, changeText, "make" ); + * mySound.addCue(6.00, changeText, "?" ); + * } + * + * function changeText(val) { + * background(0); + * text(val, width/2, height/2); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * if (mySound.isPlaying() ) { + * mySound.stop(); + * } else { + * mySound.play(); + * } + * } + * } + * </code></div> + */ + p5.SoundFile.prototype.addCue = function (time, callback, val) { + var id = this._cueIDCounter++; + var cue = new Cue(callback, time, id, val); + this._cues.push(cue); + // if (!this.elt.ontimeupdate) { + // this.elt.ontimeupdate = this._onTimeUpdate.bind(this); + // } + return id; + }; + /** + * Remove a callback based on its ID. The ID is returned by the + * addCue method. + * + * @method removeCue + * @param {Number} id ID of the cue, as returned by addCue + */ + p5.SoundFile.prototype.removeCue = function (id) { + var cueLength = this._cues.length; + for (var i = 0; i < cueLength; i++) { + var cue = this._cues[i]; + if (cue.id === id) { + this.cues.splice(i, 1); + } + } + if (this._cues.length === 0) { + } + }; + /** + * Remove all of the callbacks that had originally been scheduled + * via the addCue method. + * + * @method clearCues + */ + p5.SoundFile.prototype.clearCues = function () { + this._cues = []; + }; + // private method that checks for cues to be fired if events + // have been scheduled using addCue(callback, time). + p5.SoundFile.prototype._onTimeUpdate = function (position) { + var playbackTime = position / this.buffer.sampleRate; + var cueLength = this._cues.length; + for (var i = 0; i < cueLength; i++) { + var cue = this._cues[i]; + var callbackTime = cue.time; + var val = cue.val; + if (this._prevTime < callbackTime && callbackTime <= playbackTime) { + // pass the scheduled callbackTime as parameter to the callback + cue.callback(val); + } + } + this._prevTime = playbackTime; + }; + // Cue inspired by JavaScript setTimeout, and the + // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org + var Cue = function (callback, time, id, val) { + this.callback = callback; + this.time = time; + this.id = id; + this.val = val; + }; +}(sndcore, errorHandler, master); +var amplitude; +amplitude = function () { + 'use strict'; + var p5sound = master; + /** + * Amplitude measures volume between 0.0 and 1.0. + * Listens to all p5sound by default, or use setInput() + * to listen to a specific sound source. Accepts an optional + * smoothing value, which defaults to 0. + * + * @class p5.Amplitude + * @constructor + * @param {Number} [smoothing] between 0.0 and .999 to smooth + * amplitude readings (defaults to 0) + * @return {Object} Amplitude Object + * @example + * <div><code> + * var sound, amplitude, cnv; + * + * function preload(){ + * sound = loadSound('assets/beat.mp3'); + * } + * function setup() { + * cnv = createCanvas(100,100); + * amplitude = new p5.Amplitude(); + * + * // start / stop the sound when canvas is clicked + * cnv.mouseClicked(function() { + * if (sound.isPlaying() ){ + * sound.stop(); + * } else { + * sound.play(); + * } + * }); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * + * </code></div> + */ + p5.Amplitude = function (smoothing) { + // Set to 2048 for now. In future iterations, this should be inherited or parsed from p5sound's default + this.bufferSize = 2048; + // set audio context + this.audiocontext = p5sound.audiocontext; + this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1); + // for connections + this.input = this.processor; + this.output = this.audiocontext.createGain(); + // smoothing defaults to 0 + this.smoothing = smoothing || 0; + // the variables to return + this.volume = 0; + this.average = 0; + this.stereoVol = [ + 0, + 0 + ]; + this.stereoAvg = [ + 0, + 0 + ]; + this.stereoVolNorm = [ + 0, + 0 + ]; + this.volMax = 0.001; + this.normalize = false; + this.processor.onaudioprocess = this._audioProcess.bind(this); + this.processor.connect(this.output); + this.output.gain.value = 0; + // this may only be necessary because of a Chrome bug + this.output.connect(this.audiocontext.destination); + // connect to p5sound master output by default, unless set by input() + p5sound.meter.connect(this.processor); + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connects to the p5sound instance (master output) by default. + * Optionally, you can pass in a specific source (i.e. a soundfile). + * + * @method setInput + * @param {soundObject|undefined} [snd] set the sound source + * (optional, defaults to + * master output) + * @param {Number|undefined} [smoothing] a range between 0.0 and 1.0 + * to smooth amplitude readings + * @example + * <div><code> + * function preload(){ + * sound1 = loadSound('assets/beat.mp3'); + * sound2 = loadSound('assets/drum.mp3'); + * } + * function setup(){ + * amplitude = new p5.Amplitude(); + * sound1.play(); + * sound2.play(); + * amplitude.setInput(sound2); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * function mouseClicked(){ + * sound1.stop(); + * sound2.stop(); + * } + * </code></div> + */ + p5.Amplitude.prototype.setInput = function (source, smoothing) { + p5sound.meter.disconnect(); + if (smoothing) { + this.smoothing = smoothing; + } + // connect to the master out of p5s instance if no snd is provided + if (source == null) { + console.log('Amplitude input source is not ready! Connecting to master output instead'); + p5sound.meter.connect(this.processor); + } else if (source instanceof p5.Signal) { + source.output.connect(this.processor); + } else if (source) { + source.connect(this.processor); + this.processor.disconnect(); + this.processor.connect(this.output); + } else { + p5sound.meter.connect(this.processor); + } + }; + p5.Amplitude.prototype.connect = function (unit) { + if (unit) { + if (unit.hasOwnProperty('input')) { + this.output.connect(unit.input); + } else { + this.output.connect(unit); + } + } else { + this.output.connect(this.panner.connect(p5sound.input)); + } + }; + p5.Amplitude.prototype.disconnect = function (unit) { + this.output.disconnect(); + }; + // TO DO make this stereo / dependent on # of audio channels + p5.Amplitude.prototype._audioProcess = function (event) { + for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) { + var inputBuffer = event.inputBuffer.getChannelData(channel); + var bufLength = inputBuffer.length; + var total = 0; + var sum = 0; + var x; + for (var i = 0; i < bufLength; i++) { + x = inputBuffer[i]; + if (this.normalize) { + total += Math.max(Math.min(x / this.volMax, 1), -1); + sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1); + } else { + total += x; + sum += x * x; + } + } + var average = total / bufLength; + // ... then take the square root of the sum. + var rms = Math.sqrt(sum / bufLength); + this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing); + this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing); + this.volMax = Math.max(this.stereoVol[channel], this.volMax); + } + // add volume from all channels together + var self = this; + var volSum = this.stereoVol.reduce(function (previousValue, currentValue, index) { + self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1] / self.volMax, 1), 0); + self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index] / self.volMax, 1), 0); + return previousValue + currentValue; + }); + // volume is average of channels + this.volume = volSum / this.stereoVol.length; + // normalized value + this.volNorm = Math.max(Math.min(this.volume / this.volMax, 1), 0); + }; + /** + * Returns a single Amplitude reading at the moment it is called. + * For continuous readings, run in the draw loop. + * + * @method getLevel + * @param {Number} [channel] Optionally return only channel 0 (left) or 1 (right) + * @return {Number} Amplitude as a number between 0.0 and 1.0 + * @example + * <div><code> + * function preload(){ + * sound = loadSound('assets/beat.mp3'); + * } + * function setup() { + * amplitude = new p5.Amplitude(); + * sound.play(); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * function mouseClicked(){ + * sound.stop(); + * } + * </code></div> + */ + p5.Amplitude.prototype.getLevel = function (channel) { + if (typeof channel !== 'undefined') { + if (this.normalize) { + return this.stereoVolNorm[channel]; + } else { + return this.stereoVol[channel]; + } + } else if (this.normalize) { + return this.volNorm; + } else { + return this.volume; + } + }; + /** + * Determines whether the results of Amplitude.process() will be + * Normalized. To normalize, Amplitude finds the difference the + * loudest reading it has processed and the maximum amplitude of + * 1.0. Amplitude adds this difference to all values to produce + * results that will reliably map between 0.0 and 1.0. However, + * if a louder moment occurs, the amount that Normalize adds to + * all the values will change. Accepts an optional boolean parameter + * (true or false). Normalizing is off by default. + * + * @method toggleNormalize + * @param {boolean} [boolean] set normalize to true (1) or false (0) + */ + p5.Amplitude.prototype.toggleNormalize = function (bool) { + if (typeof bool === 'boolean') { + this.normalize = bool; + } else { + this.normalize = !this.normalize; + } + }; + /** + * Smooth Amplitude analysis by averaging with the last analysis + * frame. Off by default. + * + * @method smooth + * @param {Number} set smoothing from 0.0 <= 1 + */ + p5.Amplitude.prototype.smooth = function (s) { + if (s >= 0 && s < 1) { + this.smoothing = s; + } else { + console.log('Error: smoothing must be between 0 and 1'); + } + }; + p5.Amplitude.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.input.disconnect(); + this.output.disconnect(); + this.input = this.processor = undefined; + this.output = undefined; + }; +}(master); +var fft; +fft = function () { + 'use strict'; + var p5sound = master; + /** + * <p>FFT (Fast Fourier Transform) is an analysis algorithm that + * isolates individual + * <a href="https://en.wikipedia.org/wiki/Audio_frequency"> + * audio frequencies</a> within a waveform.</p> + * + * <p>Once instantiated, a p5.FFT object can return an array based on + * two types of analyses: <br> • <code>FFT.waveform()</code> computes + * amplitude values along the time domain. The array indices correspond + * to samples across a brief moment in time. Each value represents + * amplitude of the waveform at that sample of time.<br> + * • <code>FFT.analyze() </code> computes amplitude values along the + * frequency domain. The array indices correspond to frequencies (i.e. + * pitches), from the lowest to the highest that humans can hear. Each + * value represents amplitude at that slice of the frequency spectrum. + * Use with <code>getEnergy()</code> to measure amplitude at specific + * frequencies, or within a range of frequencies. </p> + * + * <p>FFT analyzes a very short snapshot of sound called a sample + * buffer. It returns an array of amplitude measurements, referred + * to as <code>bins</code>. The array is 1024 bins long by default. + * You can change the bin array length, but it must be a power of 2 + * between 16 and 1024 in order for the FFT algorithm to function + * correctly. The actual size of the FFT buffer is twice the + * number of bins, so given a standard sample rate, the buffer is + * 2048/44100 seconds long.</p> + * + * + * @class p5.FFT + * @constructor + * @param {Number} [smoothing] Smooth results of Freq Spectrum. + * 0.0 < smoothing < 1.0. + * Defaults to 0.8. + * @param {Number} [bins] Length of resulting array. + * Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @return {Object} FFT Object + * @example + * <div><code> + * function preload(){ + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup(){ + * var cnv = createCanvas(100,100); + * cnv.mouseClicked(togglePlay); + * fft = new p5.FFT(); + * sound.amp(0.2); + * } + * + * function draw(){ + * background(0); + * + * var spectrum = fft.analyze(); + * noStroke(); + * fill(0,255,0); // spectrum is green + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width / spectrum.length, h ) + * } + * + * var waveform = fft.waveform(); + * noFill(); + * beginShape(); + * stroke(255,0,0); // waveform is red + * strokeWeight(1); + * for (var i = 0; i< waveform.length; i++){ + * var x = map(i, 0, waveform.length, 0, width); + * var y = map( waveform[i], -1, 1, 0, height); + * vertex(x,y); + * } + * endShape(); + * + * text('click to play/pause', 4, 10); + * } + * + * // fade sound if mouse is over canvas + * function togglePlay() { + * if (sound.isPlaying()) { + * sound.pause(); + * } else { + * sound.loop(); + * } + * } + * </code></div> + */ + p5.FFT = function (smoothing, bins) { + this.smoothing = smoothing || 0.8; + this.bins = bins || 1024; + var FFT_SIZE = bins * 2 || 2048; + this.input = this.analyser = p5sound.audiocontext.createAnalyser(); + // default connections to p5sound fftMeter + p5sound.fftMeter.connect(this.analyser); + this.analyser.smoothingTimeConstant = this.smoothing; + this.analyser.fftSize = FFT_SIZE; + this.freqDomain = new Uint8Array(this.analyser.frequencyBinCount); + this.timeDomain = new Uint8Array(this.analyser.frequencyBinCount); + // predefined frequency ranages, these will be tweakable + this.bass = [ + 20, + 140 + ]; + this.lowMid = [ + 140, + 400 + ]; + this.mid = [ + 400, + 2600 + ]; + this.highMid = [ + 2600, + 5200 + ]; + this.treble = [ + 5200, + 14000 + ]; + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Set the input source for the FFT analysis. If no source is + * provided, FFT will analyze all sound in the sketch. + * + * @method setInput + * @param {Object} [source] p5.sound object (or web audio API source node) + */ + p5.FFT.prototype.setInput = function (source) { + if (!source) { + p5sound.fftMeter.connect(this.analyser); + } else { + if (source.output) { + source.output.connect(this.analyser); + } else if (source.connect) { + source.connect(this.analyser); + } + p5sound.fftMeter.disconnect(); + } + }; + /** + * Returns an array of amplitude values (between -1.0 and +1.0) that represent + * a snapshot of amplitude readings in a single buffer. Length will be + * equal to bins (defaults to 1024). Can be used to draw the waveform + * of a sound. + * + * @method waveform + * @param {Number} [bins] Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @param {String} [precision] If any value is provided, will return results + * in a Float32 Array which is more precise + * than a regular array. + * @return {Array} Array Array of amplitude values (-1 to 1) + * over time. Array length = bins. + * + */ + p5.FFT.prototype.waveform = function () { + var bins, mode, normalArray; + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] === 'number') { + bins = arguments[i]; + this.analyser.fftSize = bins * 2; + } + if (typeof arguments[i] === 'string') { + mode = arguments[i]; + } + } + // getFloatFrequencyData doesnt work in Safari as of 5/2015 + if (mode && !p5.prototype._isSafari()) { + timeToFloat(this, this.timeDomain); + this.analyser.getFloatTimeDomainData(this.timeDomain); + return this.timeDomain; + } else { + timeToInt(this, this.timeDomain); + this.analyser.getByteTimeDomainData(this.timeDomain); + var normalArray = new Array(); + for (var i = 0; i < this.timeDomain.length; i++) { + var scaled = p5.prototype.map(this.timeDomain[i], 0, 255, -1, 1); + normalArray.push(scaled); + } + return normalArray; + } + }; + /** + * Returns an array of amplitude values (between 0 and 255) + * across the frequency spectrum. Length is equal to FFT bins + * (1024 by default). The array indices correspond to frequencies + * (i.e. pitches), from the lowest to the highest that humans can + * hear. Each value represents amplitude at that slice of the + * frequency spectrum. Must be called prior to using + * <code>getEnergy()</code>. + * + * @method analyze + * @param {Number} [bins] Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @param {Number} [scale] If "dB," returns decibel + * float measurements between + * -140 and 0 (max). + * Otherwise returns integers from 0-255. + * @return {Array} spectrum Array of energy (amplitude/volume) + * values across the frequency spectrum. + * Lowest energy (silence) = 0, highest + * possible is 255. + * @example + * <div><code> + * var osc; + * var fft; + * + * function setup(){ + * createCanvas(100,100); + * osc = new p5.Oscillator(); + * osc.amp(0); + * osc.start(); + * fft = new p5.FFT(); + * } + * + * function draw(){ + * background(0); + * + * var freq = map(mouseX, 0, 800, 20, 15000); + * freq = constrain(freq, 1, 20000); + * osc.freq(freq); + * + * var spectrum = fft.analyze(); + * noStroke(); + * fill(0,255,0); // spectrum is green + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width / spectrum.length, h ); + * } + * + * stroke(255); + * text('Freq: ' + round(freq)+'Hz', 10, 10); + * + * isMouseOverCanvas(); + * } + * + * // only play sound when mouse is over canvas + * function isMouseOverCanvas() { + * var mX = mouseX, mY = mouseY; + * if (mX > 0 && mX < width && mY < height && mY > 0) { + * osc.amp(0.5, 0.2); + * } else { + * osc.amp(0, 0.2); + * } + * } + * </code></div> + * + * + */ + p5.FFT.prototype.analyze = function () { + var bins, mode; + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] === 'number') { + bins = this.bins = arguments[i]; + this.analyser.fftSize = this.bins * 2; + } + if (typeof arguments[i] === 'string') { + mode = arguments[i]; + } + } + if (mode && mode.toLowerCase() === 'db') { + freqToFloat(this); + this.analyser.getFloatFrequencyData(this.freqDomain); + return this.freqDomain; + } else { + freqToInt(this, this.freqDomain); + this.analyser.getByteFrequencyData(this.freqDomain); + var normalArray = Array.apply([], this.freqDomain); + normalArray.length === this.analyser.fftSize; + normalArray.constructor === Array; + return normalArray; + } + }; + /** + * Returns the amount of energy (volume) at a specific + * <a href="en.wikipedia.org/wiki/Audio_frequency" target="_blank"> + * frequency</a>, or the average amount of energy between two + * frequencies. Accepts Number(s) corresponding + * to frequency (in Hz), or a String corresponding to predefined + * frequency ranges ("bass", "lowMid", "mid", "highMid", "treble"). + * Returns a range between 0 (no energy/volume at that frequency) and + * 255 (maximum energy). + * <em>NOTE: analyze() must be called prior to getEnergy(). Analyze() + * tells the FFT to analyze frequency data, and getEnergy() uses + * the results determine the value at a specific frequency or + * range of frequencies.</em></p> + * + * @method getEnergy + * @param {Number|String} frequency1 Will return a value representing + * energy at this frequency. Alternately, + * the strings "bass", "lowMid" "mid", + * "highMid", and "treble" will return + * predefined frequency ranges. + * @param {Number} [frequency2] If a second frequency is given, + * will return average amount of + * energy that exists between the + * two frequencies. + * @return {Number} Energy Energy (volume/amplitude) from + * 0 and 255. + * + */ + p5.FFT.prototype.getEnergy = function (frequency1, frequency2) { + var nyquist = p5sound.audiocontext.sampleRate / 2; + if (frequency1 === 'bass') { + frequency1 = this.bass[0]; + frequency2 = this.bass[1]; + } else if (frequency1 === 'lowMid') { + frequency1 = this.lowMid[0]; + frequency2 = this.lowMid[1]; + } else if (frequency1 === 'mid') { + frequency1 = this.mid[0]; + frequency2 = this.mid[1]; + } else if (frequency1 === 'highMid') { + frequency1 = this.highMid[0]; + frequency2 = this.highMid[1]; + } else if (frequency1 === 'treble') { + frequency1 = this.treble[0]; + frequency2 = this.treble[1]; + } + if (typeof frequency1 !== 'number') { + throw 'invalid input for getEnergy()'; + } else if (!frequency2) { + var index = Math.round(frequency1 / nyquist * this.freqDomain.length); + return this.freqDomain[index]; + } else if (frequency1 && frequency2) { + // if second is higher than first + if (frequency1 > frequency2) { + var swap = frequency2; + frequency2 = frequency1; + frequency1 = swap; + } + var lowIndex = Math.round(frequency1 / nyquist * this.freqDomain.length); + var highIndex = Math.round(frequency2 / nyquist * this.freqDomain.length); + var total = 0; + var numFrequencies = 0; + // add up all of the values for the frequencies + for (var i = lowIndex; i <= highIndex; i++) { + total += this.freqDomain[i]; + numFrequencies += 1; + } + // divide by total number of frequencies + var toReturn = total / numFrequencies; + return toReturn; + } else { + throw 'invalid input for getEnergy()'; + } + }; + // compatability with v.012, changed to getEnergy in v.0121. Will be deprecated... + p5.FFT.prototype.getFreq = function (freq1, freq2) { + console.log('getFreq() is deprecated. Please use getEnergy() instead.'); + var x = this.getEnergy(freq1, freq2); + return x; + }; + /** + * Returns the + * <a href="http://en.wikipedia.org/wiki/Spectral_centroid" target="_blank"> + * spectral centroid</a> of the input signal. + * <em>NOTE: analyze() must be called prior to getCentroid(). Analyze() + * tells the FFT to analyze frequency data, and getCentroid() uses + * the results determine the spectral centroid.</em></p> + * + * @method getCentroid + * @return {Number} Spectral Centroid Frequency Frequency of the spectral centroid in Hz. + * + * + * @example + * <div><code> + * + * + *function setup(){ + * cnv = createCanvas(800,400); + * sound = new p5.AudioIn(); + * sound.start(); + * fft = new p5.FFT(); + * sound.connect(fft); + *} + * + * + *function draw(){ + * + * var centroidplot = 0.0; + * var spectralCentroid = 0; + * + * + * background(0); + * stroke(0,255,0); + * var spectrum = fft.analyze(); + * fill(0,255,0); // spectrum is green + * + * //draw the spectrum + * + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(log(i), 0, log(spectrum.length), 0, width); + * var h = map(spectrum[i], 0, 255, 0, height); + * var rectangle_width = (log(i+1)-log(i))*(width/log(spectrum.length)); + * rect(x, height, rectangle_width, -h ) + * } + + * var nyquist = 22050; + * + * // get the centroid + * spectralCentroid = fft.getCentroid(); + * + * // the mean_freq_index calculation is for the display. + * var mean_freq_index = spectralCentroid/(nyquist/spectrum.length); + * + * centroidplot = map(log(mean_freq_index), 0, log(spectrum.length), 0, width); + * + * + * stroke(255,0,0); // the line showing where the centroid is will be red + * + * rect(centroidplot, 0, width / spectrum.length, height) + * noStroke(); + * fill(255,255,255); // text is white + * textSize(40); + * text("centroid: "+round(spectralCentroid)+" Hz", 10, 40); + *} + * </code></div> + */ + p5.FFT.prototype.getCentroid = function () { + var nyquist = p5sound.audiocontext.sampleRate / 2; + var cumulative_sum = 0; + var centroid_normalization = 0; + for (var i = 0; i < this.freqDomain.length; i++) { + cumulative_sum += i * this.freqDomain[i]; + centroid_normalization += this.freqDomain[i]; + } + var mean_freq_index = 0; + if (centroid_normalization != 0) { + mean_freq_index = cumulative_sum / centroid_normalization; + } + var spec_centroid_freq = mean_freq_index * (nyquist / this.freqDomain.length); + return spec_centroid_freq; + }; + /** + * Smooth FFT analysis by averaging with the last analysis frame. + * + * @method smooth + * @param {Number} smoothing 0.0 < smoothing < 1.0. + * Defaults to 0.8. + */ + p5.FFT.prototype.smooth = function (s) { + if (s) { + this.smoothing = s; + } + this.analyser.smoothingTimeConstant = s; + }; + p5.FFT.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.analyser.disconnect(); + this.analyser = undefined; + }; + // helper methods to convert type from float (dB) to int (0-255) + var freqToFloat = function (fft) { + if (fft.freqDomain instanceof Float32Array === false) { + fft.freqDomain = new Float32Array(fft.analyser.frequencyBinCount); + } + }; + var freqToInt = function (fft) { + if (fft.freqDomain instanceof Uint8Array === false) { + fft.freqDomain = new Uint8Array(fft.analyser.frequencyBinCount); + } + }; + var timeToFloat = function (fft) { + if (fft.timeDomain instanceof Float32Array === false) { + fft.timeDomain = new Float32Array(fft.analyser.frequencyBinCount); + } + }; + var timeToInt = function (fft) { + if (fft.timeDomain instanceof Uint8Array === false) { + fft.timeDomain = new Uint8Array(fft.analyser.frequencyBinCount); + } + }; +}(master); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Tone; +Tone_core_Tone = function () { + 'use strict'; + function isUndef(val) { + return val === void 0; + } + function isFunction(val) { + return typeof val === 'function'; + } + var audioContext; + if (isUndef(window.AudioContext)) { + window.AudioContext = window.webkitAudioContext; + } + if (isUndef(window.OfflineAudioContext)) { + window.OfflineAudioContext = window.webkitOfflineAudioContext; + } + if (!isUndef(AudioContext)) { + audioContext = new AudioContext(); + } else { + throw new Error('Web Audio is not supported in this browser'); + } + if (!isFunction(AudioContext.prototype.createGain)) { + AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; + } + if (!isFunction(AudioContext.prototype.createDelay)) { + AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; + } + if (!isFunction(AudioContext.prototype.createPeriodicWave)) { + AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; + } + if (!isFunction(AudioBufferSourceNode.prototype.start)) { + AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn; + } + if (!isFunction(AudioBufferSourceNode.prototype.stop)) { + AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff; + } + if (!isFunction(OscillatorNode.prototype.start)) { + OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn; + } + if (!isFunction(OscillatorNode.prototype.stop)) { + OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff; + } + if (!isFunction(OscillatorNode.prototype.setPeriodicWave)) { + OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; + } + AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; + AudioNode.prototype.connect = function (B, outNum, inNum) { + if (B.input) { + if (Array.isArray(B.input)) { + if (isUndef(inNum)) { + inNum = 0; + } + this.connect(B.input[inNum]); + } else { + this.connect(B.input, outNum, inNum); + } + } else { + try { + if (B instanceof AudioNode) { + this._nativeConnect(B, outNum, inNum); + } else { + this._nativeConnect(B, outNum); + } + } catch (e) { + throw new Error('error connecting to node: ' + B); + } + } + }; + var Tone = function (inputs, outputs) { + if (isUndef(inputs) || inputs === 1) { + this.input = this.context.createGain(); + } else if (inputs > 1) { + this.input = new Array(inputs); + } + if (isUndef(outputs) || outputs === 1) { + this.output = this.context.createGain(); + } else if (outputs > 1) { + this.output = new Array(inputs); + } + }; + Tone.prototype.set = function (params, value, rampTime) { + if (this.isObject(params)) { + rampTime = value; + } else if (this.isString(params)) { + var tmpObj = {}; + tmpObj[params] = value; + params = tmpObj; + } + for (var attr in params) { + value = params[attr]; + var parent = this; + if (attr.indexOf('.') !== -1) { + var attrSplit = attr.split('.'); + for (var i = 0; i < attrSplit.length - 1; i++) { + parent = parent[attrSplit[i]]; + } + attr = attrSplit[attrSplit.length - 1]; + } + var param = parent[attr]; + if (isUndef(param)) { + continue; + } + if (Tone.Signal && param instanceof Tone.Signal || Tone.Param && param instanceof Tone.Param) { + if (param.value !== value) { + if (isUndef(rampTime)) { + param.value = value; + } else { + param.rampTo(value, rampTime); + } + } + } else if (param instanceof AudioParam) { + if (param.value !== value) { + param.value = value; + } + } else if (param instanceof Tone) { + param.set(value); + } else if (param !== value) { + parent[attr] = value; + } + } + return this; + }; + Tone.prototype.get = function (params) { + if (isUndef(params)) { + params = this._collectDefaults(this.constructor); + } else if (this.isString(params)) { + params = [params]; + } + var ret = {}; + for (var i = 0; i < params.length; i++) { + var attr = params[i]; + var parent = this; + var subRet = ret; + if (attr.indexOf('.') !== -1) { + var attrSplit = attr.split('.'); + for (var j = 0; j < attrSplit.length - 1; j++) { + var subAttr = attrSplit[j]; + subRet[subAttr] = subRet[subAttr] || {}; + subRet = subRet[subAttr]; + parent = parent[subAttr]; + } + attr = attrSplit[attrSplit.length - 1]; + } + var param = parent[attr]; + if (this.isObject(params[attr])) { + subRet[attr] = param.get(); + } else if (Tone.Signal && param instanceof Tone.Signal) { + subRet[attr] = param.value; + } else if (Tone.Param && param instanceof Tone.Param) { + subRet[attr] = param.value; + } else if (param instanceof AudioParam) { + subRet[attr] = param.value; + } else if (param instanceof Tone) { + subRet[attr] = param.get(); + } else if (!isFunction(param) && !isUndef(param)) { + subRet[attr] = param; + } + } + return ret; + }; + Tone.prototype._collectDefaults = function (constr) { + var ret = []; + if (!isUndef(constr.defaults)) { + ret = Object.keys(constr.defaults); + } + if (!isUndef(constr._super)) { + var superDefs = this._collectDefaults(constr._super); + for (var i = 0; i < superDefs.length; i++) { + if (ret.indexOf(superDefs[i]) === -1) { + ret.push(superDefs[i]); + } + } + } + return ret; + }; + Tone.prototype.toString = function () { + for (var className in Tone) { + var isLetter = className[0].match(/^[A-Z]$/); + var sameConstructor = Tone[className] === this.constructor; + if (isFunction(Tone[className]) && isLetter && sameConstructor) { + return className; + } + } + return 'Tone'; + }; + Tone.context = audioContext; + Tone.prototype.context = Tone.context; + Tone.prototype.bufferSize = 2048; + Tone.prototype.blockTime = 128 / Tone.context.sampleRate; + Tone.prototype.dispose = function () { + if (!this.isUndef(this.input)) { + if (this.input instanceof AudioNode) { + this.input.disconnect(); + } + this.input = null; + } + if (!this.isUndef(this.output)) { + if (this.output instanceof AudioNode) { + this.output.disconnect(); + } + this.output = null; + } + return this; + }; + var _silentNode = null; + Tone.prototype.noGC = function () { + this.output.connect(_silentNode); + return this; + }; + AudioNode.prototype.noGC = function () { + this.connect(_silentNode); + return this; + }; + Tone.prototype.connect = function (unit, outputNum, inputNum) { + if (Array.isArray(this.output)) { + outputNum = this.defaultArg(outputNum, 0); + this.output[outputNum].connect(unit, 0, inputNum); + } else { + this.output.connect(unit, outputNum, inputNum); + } + return this; + }; + Tone.prototype.disconnect = function (outputNum) { + if (Array.isArray(this.output)) { + outputNum = this.defaultArg(outputNum, 0); + this.output[outputNum].disconnect(); + } else { + this.output.disconnect(); + } + return this; + }; + Tone.prototype.connectSeries = function () { + if (arguments.length > 1) { + var currentUnit = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + var toUnit = arguments[i]; + currentUnit.connect(toUnit); + currentUnit = toUnit; + } + } + return this; + }; + Tone.prototype.connectParallel = function () { + var connectFrom = arguments[0]; + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + var connectTo = arguments[i]; + connectFrom.connect(connectTo); + } + } + return this; + }; + Tone.prototype.chain = function () { + if (arguments.length > 0) { + var currentUnit = this; + for (var i = 0; i < arguments.length; i++) { + var toUnit = arguments[i]; + currentUnit.connect(toUnit); + currentUnit = toUnit; + } + } + return this; + }; + Tone.prototype.fan = function () { + if (arguments.length > 0) { + for (var i = 0; i < arguments.length; i++) { + this.connect(arguments[i]); + } + } + return this; + }; + AudioNode.prototype.chain = Tone.prototype.chain; + AudioNode.prototype.fan = Tone.prototype.fan; + Tone.prototype.defaultArg = function (given, fallback) { + if (this.isObject(given) && this.isObject(fallback)) { + var ret = {}; + for (var givenProp in given) { + ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); + } + for (var fallbackProp in fallback) { + ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); + } + return ret; + } else { + return isUndef(given) ? fallback : given; + } + }; + Tone.prototype.optionsObject = function (values, keys, defaults) { + var options = {}; + if (values.length === 1 && this.isObject(values[0])) { + options = values[0]; + } else { + for (var i = 0; i < keys.length; i++) { + options[keys[i]] = values[i]; + } + } + if (!this.isUndef(defaults)) { + return this.defaultArg(options, defaults); + } else { + return options; + } + }; + Tone.prototype.isUndef = isUndef; + Tone.prototype.isFunction = isFunction; + Tone.prototype.isNumber = function (arg) { + return typeof arg === 'number'; + }; + Tone.prototype.isObject = function (arg) { + return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object; + }; + Tone.prototype.isBoolean = function (arg) { + return typeof arg === 'boolean'; + }; + Tone.prototype.isArray = function (arg) { + return Array.isArray(arg); + }; + Tone.prototype.isString = function (arg) { + return typeof arg === 'string'; + }; + Tone.noOp = function () { + }; + Tone.prototype._readOnly = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._readOnly(property[i]); + } + } else { + Object.defineProperty(this, property, { + writable: false, + enumerable: true + }); + } + }; + Tone.prototype._writable = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._writable(property[i]); + } + } else { + Object.defineProperty(this, property, { writable: true }); + } + }; + Tone.State = { + Started: 'started', + Stopped: 'stopped', + Paused: 'paused' + }; + Tone.prototype.equalPowerScale = function (percent) { + var piFactor = 0.5 * Math.PI; + return Math.sin(percent * piFactor); + }; + Tone.prototype.dbToGain = function (db) { + return Math.pow(2, db / 6); + }; + Tone.prototype.gainToDb = function (gain) { + return 20 * (Math.log(gain) / Math.LN10); + }; + Tone.prototype.now = function () { + return this.context.currentTime; + }; + Tone.extend = function (child, parent) { + if (isUndef(parent)) { + parent = Tone; + } + function TempConstructor() { + } + TempConstructor.prototype = parent.prototype; + child.prototype = new TempConstructor(); + child.prototype.constructor = child; + child._super = parent; + }; + var newContextCallbacks = []; + Tone._initAudioContext = function (callback) { + callback(Tone.context); + newContextCallbacks.push(callback); + }; + Tone.setContext = function (ctx) { + Tone.prototype.context = ctx; + Tone.context = ctx; + for (var i = 0; i < newContextCallbacks.length; i++) { + newContextCallbacks[i](ctx); + } + }; + Tone.startMobile = function () { + var osc = Tone.context.createOscillator(); + var silent = Tone.context.createGain(); + silent.gain.value = 0; + osc.connect(silent); + silent.connect(Tone.context.destination); + var now = Tone.context.currentTime; + osc.start(now); + osc.stop(now + 1); + }; + Tone._initAudioContext(function (audioContext) { + Tone.prototype.blockTime = 128 / audioContext.sampleRate; + _silentNode = audioContext.createGain(); + _silentNode.gain.value = 0; + _silentNode.connect(audioContext.destination); + }); + Tone.version = 'r7-dev'; + return Tone; +}(); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_SignalBase; +Tone_signal_SignalBase = function (Tone) { + 'use strict'; + Tone.SignalBase = function () { + }; + Tone.extend(Tone.SignalBase); + Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) { + if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor || Tone.TimelineSignal && Tone.TimelineSignal === node.constructor) { + node._param.cancelScheduledValues(0); + node._param.value = 0; + node.overridden = true; + } else if (node instanceof AudioParam) { + node.cancelScheduledValues(0); + node.value = 0; + } + Tone.prototype.connect.call(this, node, outputNumber, inputNumber); + return this; + }; + return Tone.SignalBase; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_WaveShaper; +Tone_signal_WaveShaper = function (Tone) { + 'use strict'; + Tone.WaveShaper = function (mapping, bufferLen) { + this._shaper = this.input = this.output = this.context.createWaveShaper(); + this._curve = null; + if (Array.isArray(mapping)) { + this.curve = mapping; + } else if (isFinite(mapping) || this.isUndef(mapping)) { + this._curve = new Float32Array(this.defaultArg(mapping, 1024)); + } else if (this.isFunction(mapping)) { + this._curve = new Float32Array(this.defaultArg(bufferLen, 1024)); + this.setMap(mapping); + } + }; + Tone.extend(Tone.WaveShaper, Tone.SignalBase); + Tone.WaveShaper.prototype.setMap = function (mapping) { + for (var i = 0, len = this._curve.length; i < len; i++) { + var normalized = i / len * 2 - 1; + this._curve[i] = mapping(normalized, i); + } + this._shaper.curve = this._curve; + return this; + }; + Object.defineProperty(Tone.WaveShaper.prototype, 'curve', { + get: function () { + return this._shaper.curve; + }, + set: function (mapping) { + this._curve = new Float32Array(mapping); + this._shaper.curve = this._curve; + } + }); + Object.defineProperty(Tone.WaveShaper.prototype, 'oversample', { + get: function () { + return this._shaper.oversample; + }, + set: function (oversampling) { + if ([ + 'none', + '2x', + '4x' + ].indexOf(oversampling) !== -1) { + this._shaper.oversample = oversampling; + } else { + throw new Error('invalid oversampling: ' + oversampling); + } + } + }); + Tone.WaveShaper.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._shaper.disconnect(); + this._shaper = null; + this._curve = null; + return this; + }; + return Tone.WaveShaper; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Type; +Tone_core_Type = function (Tone) { + 'use strict'; + Tone.Type = { + Default: 'number', + Time: 'time', + Frequency: 'frequency', + NormalRange: 'normalRange', + AudioRange: 'audioRange', + Decibels: 'db', + Interval: 'interval', + BPM: 'bpm', + Positive: 'positive', + Cents: 'cents', + Degrees: 'degrees', + MIDI: 'midi', + TransportTime: 'transportTime', + Ticks: 'tick', + Note: 'note', + Milliseconds: 'milliseconds', + Notation: 'notation' + }; + Tone.prototype.isNowRelative = function () { + var nowRelative = new RegExp(/^\s*\+(.)+/i); + return function (note) { + return nowRelative.test(note); + }; + }(); + Tone.prototype.isTicks = function () { + var tickFormat = new RegExp(/^\d+i$/i); + return function (note) { + return tickFormat.test(note); + }; + }(); + Tone.prototype.isNotation = function () { + var notationFormat = new RegExp(/^[0-9]+[mnt]$/i); + return function (note) { + return notationFormat.test(note); + }; + }(); + Tone.prototype.isTransportTime = function () { + var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i); + return function (transportTime) { + return transportTimeFormat.test(transportTime); + }; + }(); + Tone.prototype.isNote = function () { + var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i); + return function (note) { + return noteFormat.test(note); + }; + }(); + Tone.prototype.isFrequency = function () { + var freqFormat = new RegExp(/^\d*\.?\d+hz$/i); + return function (freq) { + return freqFormat.test(freq); + }; + }(); + function getTransportBpm() { + if (Tone.Transport && Tone.Transport.bpm) { + return Tone.Transport.bpm.value; + } else { + return 120; + } + } + function getTransportTimeSignature() { + if (Tone.Transport && Tone.Transport.timeSignature) { + return Tone.Transport.timeSignature; + } else { + return 4; + } + } + Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) { + bpm = this.defaultArg(bpm, getTransportBpm()); + timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); + var beatTime = 60 / bpm; + if (notation === '1n') { + notation = '1m'; + } + var subdivision = parseInt(notation, 10); + var beats = 0; + if (subdivision === 0) { + beats = 0; + } + var lastLetter = notation.slice(-1); + if (lastLetter === 't') { + beats = 4 / subdivision * 2 / 3; + } else if (lastLetter === 'n') { + beats = 4 / subdivision; + } else if (lastLetter === 'm') { + beats = subdivision * timeSignature; + } else { + beats = 0; + } + return beatTime * beats; + }; + Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) { + bpm = this.defaultArg(bpm, getTransportBpm()); + timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); + var measures = 0; + var quarters = 0; + var sixteenths = 0; + var split = transportTime.split(':'); + if (split.length === 2) { + measures = parseFloat(split[0]); + quarters = parseFloat(split[1]); + } else if (split.length === 1) { + quarters = parseFloat(split[0]); + } else if (split.length === 3) { + measures = parseFloat(split[0]); + quarters = parseFloat(split[1]); + sixteenths = parseFloat(split[2]); + } + var beats = measures * timeSignature + quarters + sixteenths / 4; + return beats * (60 / bpm); + }; + Tone.prototype.ticksToSeconds = function (ticks, bpm) { + if (this.isUndef(Tone.Transport)) { + return 0; + } + ticks = parseFloat(ticks); + bpm = this.defaultArg(bpm, getTransportBpm()); + var tickTime = 60 / bpm / Tone.Transport.PPQ; + return tickTime * ticks; + }; + Tone.prototype.frequencyToSeconds = function (freq) { + return 1 / parseFloat(freq); + }; + Tone.prototype.samplesToSeconds = function (samples) { + return samples / this.context.sampleRate; + }; + Tone.prototype.secondsToSamples = function (seconds) { + return seconds * this.context.sampleRate; + }; + Tone.prototype.secondsToTransportTime = function (seconds, bpm, timeSignature) { + bpm = this.defaultArg(bpm, getTransportBpm()); + timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); + var quarterTime = 60 / bpm; + var quarters = seconds / quarterTime; + var measures = Math.floor(quarters / timeSignature); + var sixteenths = quarters % 1 * 4; + quarters = Math.floor(quarters) % timeSignature; + var progress = [ + measures, + quarters, + sixteenths + ]; + return progress.join(':'); + }; + Tone.prototype.secondsToFrequency = function (seconds) { + return 1 / seconds; + }; + Tone.prototype.toTransportTime = function (time, bpm, timeSignature) { + var seconds = this.toSeconds(time); + return this.secondsToTransportTime(seconds, bpm, timeSignature); + }; + Tone.prototype.toFrequency = function (freq, now) { + if (this.isFrequency(freq)) { + return parseFloat(freq); + } else if (this.isNotation(freq) || this.isTransportTime(freq)) { + return this.secondsToFrequency(this.toSeconds(freq, now)); + } else if (this.isNote(freq)) { + return this.noteToFrequency(freq); + } else { + return freq; + } + }; + Tone.prototype.toTicks = function (time) { + if (this.isUndef(Tone.Transport)) { + return 0; + } + var bpm = Tone.Transport.bpm.value; + var plusNow = 0; + if (this.isNowRelative(time)) { + time = time.replace('+', ''); + plusNow = Tone.Transport.ticks; + } else if (this.isUndef(time)) { + return Tone.Transport.ticks; + } + var seconds = this.toSeconds(time); + var quarter = 60 / bpm; + var quarters = seconds / quarter; + var tickNum = quarters * Tone.Transport.PPQ; + return Math.round(tickNum + plusNow); + }; + Tone.prototype.toSamples = function (time) { + var seconds = this.toSeconds(time); + return Math.round(seconds * this.context.sampleRate); + }; + Tone.prototype.toSeconds = function (time, now) { + now = this.defaultArg(now, this.now()); + if (this.isNumber(time)) { + return time; + } else if (this.isString(time)) { + var plusTime = 0; + if (this.isNowRelative(time)) { + time = time.replace('+', ''); + plusTime = now; + } + var betweenParens = time.match(/\(([^)(]+)\)/g); + if (betweenParens) { + for (var j = 0; j < betweenParens.length; j++) { + var symbol = betweenParens[j].replace(/[\(\)]/g, ''); + var symbolVal = this.toSeconds(symbol); + time = time.replace(betweenParens[j], symbolVal); + } + } + if (time.indexOf('@') !== -1) { + var quantizationSplit = time.split('@'); + if (!this.isUndef(Tone.Transport)) { + var toQuantize = quantizationSplit[0].trim(); + if (toQuantize === '') { + toQuantize = undefined; + } + if (plusTime > 0) { + toQuantize = '+' + toQuantize; + plusTime = 0; + } + var subdivision = quantizationSplit[1].trim(); + time = Tone.Transport.quantize(toQuantize, subdivision); + } else { + throw new Error('quantization requires Tone.Transport'); + } + } else { + var components = time.split(/[\(\)\-\+\/\*]/); + if (components.length > 1) { + var originalTime = time; + for (var i = 0; i < components.length; i++) { + var symb = components[i].trim(); + if (symb !== '') { + var val = this.toSeconds(symb); + time = time.replace(symb, val); + } + } + try { + time = eval(time); + } catch (e) { + throw new EvalError('cannot evaluate Time: ' + originalTime); + } + } else if (this.isNotation(time)) { + time = this.notationToSeconds(time); + } else if (this.isTransportTime(time)) { + time = this.transportTimeToSeconds(time); + } else if (this.isFrequency(time)) { + time = this.frequencyToSeconds(time); + } else if (this.isTicks(time)) { + time = this.ticksToSeconds(time); + } else { + time = parseFloat(time); + } + } + return time + plusTime; + } else { + return now; + } + }; + Tone.prototype.toNotation = function (time, bpm, timeSignature) { + var testNotations = [ + '1m', + '2n', + '4n', + '8n', + '16n', + '32n', + '64n', + '128n' + ]; + var retNotation = toNotationHelper.call(this, time, bpm, timeSignature, testNotations); + var testTripletNotations = [ + '1m', + '2n', + '2t', + '4n', + '4t', + '8n', + '8t', + '16n', + '16t', + '32n', + '32t', + '64n', + '64t', + '128n' + ]; + var retTripletNotation = toNotationHelper.call(this, time, bpm, timeSignature, testTripletNotations); + if (retTripletNotation.split('+').length < retNotation.split('+').length) { + return retTripletNotation; + } else { + return retNotation; + } + }; + function toNotationHelper(time, bpm, timeSignature, testNotations) { + var seconds = this.toSeconds(time); + var threshold = this.notationToSeconds(testNotations[testNotations.length - 1], bpm, timeSignature); + var retNotation = ''; + for (var i = 0; i < testNotations.length; i++) { + var notationTime = this.notationToSeconds(testNotations[i], bpm, timeSignature); + var multiple = seconds / notationTime; + var floatingPointError = 0.000001; + if (1 - multiple % 1 < floatingPointError) { + multiple += floatingPointError; + } + multiple = Math.floor(multiple); + if (multiple > 0) { + if (multiple === 1) { + retNotation += testNotations[i]; + } else { + retNotation += multiple.toString() + '*' + testNotations[i]; + } + seconds -= multiple * notationTime; + if (seconds < threshold) { + break; + } else { + retNotation += ' + '; + } + } + } + if (retNotation === '') { + retNotation = '0'; + } + return retNotation; + } + Tone.prototype.fromUnits = function (val, units) { + if (this.convert || this.isUndef(this.convert)) { + switch (units) { + case Tone.Type.Time: + return this.toSeconds(val); + case Tone.Type.Frequency: + return this.toFrequency(val); + case Tone.Type.Decibels: + return this.dbToGain(val); + case Tone.Type.NormalRange: + return Math.min(Math.max(val, 0), 1); + case Tone.Type.AudioRange: + return Math.min(Math.max(val, -1), 1); + case Tone.Type.Positive: + return Math.max(val, 0); + default: + return val; + } + } else { + return val; + } + }; + Tone.prototype.toUnits = function (val, units) { + if (this.convert || this.isUndef(this.convert)) { + switch (units) { + case Tone.Type.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; + } + }; + var noteToScaleIndex = { + 'cbb': -2, + 'cb': -1, + 'c': 0, + 'c#': 1, + 'cx': 2, + 'dbb': 0, + 'db': 1, + 'd': 2, + 'd#': 3, + 'dx': 4, + 'ebb': 2, + 'eb': 3, + 'e': 4, + 'e#': 5, + 'ex': 6, + 'fbb': 3, + 'fb': 4, + 'f': 5, + 'f#': 6, + 'fx': 7, + 'gbb': 5, + 'gb': 6, + 'g': 7, + 'g#': 8, + 'gx': 9, + 'abb': 7, + 'ab': 8, + 'a': 9, + 'a#': 10, + 'ax': 11, + 'bbb': 9, + 'bb': 10, + 'b': 11, + 'b#': 12, + 'bx': 13 + }; + var scaleIndexToNote = [ + 'C', + 'C#', + 'D', + 'D#', + 'E', + 'F', + 'F#', + 'G', + 'G#', + 'A', + 'A#', + 'B' + ]; + Tone.A4 = 440; + Tone.prototype.noteToFrequency = function (note) { + var parts = note.split(/(-?\d+)/); + if (parts.length === 3) { + var index = noteToScaleIndex[parts[0].toLowerCase()]; + var octave = parts[1]; + var noteNumber = index + (parseInt(octave, 10) + 1) * 12; + return this.midiToFrequency(noteNumber); + } else { + return 0; + } + }; + Tone.prototype.frequencyToNote = function (freq) { + var log = Math.log(freq / Tone.A4) / Math.LN2; + var noteNumber = Math.round(12 * log) + 57; + var octave = Math.floor(noteNumber / 12); + if (octave < 0) { + noteNumber += -12 * octave; + } + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); + }; + Tone.prototype.intervalToFrequencyRatio = function (interval) { + return Math.pow(2, interval / 12); + }; + Tone.prototype.midiToNote = function (midiNumber) { + var octave = Math.floor(midiNumber / 12) - 1; + var note = midiNumber % 12; + return scaleIndexToNote[note] + octave; + }; + Tone.prototype.noteToMidi = function (note) { + var parts = note.split(/(\d+)/); + if (parts.length === 3) { + var index = noteToScaleIndex[parts[0].toLowerCase()]; + var octave = parts[1]; + return index + (parseInt(octave, 10) + 1) * 12; + } else { + return 0; + } + }; + Tone.prototype.midiToFrequency = function (midi) { + return Tone.A4 * Math.pow(2, (midi - 69) / 12); + }; + return Tone; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Param; +Tone_core_Param = function (Tone) { + 'use strict'; + Tone.Param = function () { + var options = this.optionsObject(arguments, [ + 'param', + 'units', + 'convert' + ], Tone.Param.defaults); + this._param = this.input = options.param; + this.units = options.units; + this.convert = options.convert; + this.overridden = false; + if (!this.isUndef(options.value)) { + this.value = options.value; + } + }; + Tone.extend(Tone.Param); + Tone.Param.defaults = { + 'units': Tone.Type.Default, + 'convert': true, + 'param': undefined + }; + Object.defineProperty(Tone.Param.prototype, 'value', { + get: function () { + return this._toUnits(this._param.value); + }, + set: function (value) { + var convertedVal = this._fromUnits(value); + this._param.value = convertedVal; + } + }); + Tone.Param.prototype._fromUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Time: + return this.toSeconds(val); + case Tone.Type.Frequency: + return this.toFrequency(val); + case Tone.Type.Decibels: + return this.dbToGain(val); + case Tone.Type.NormalRange: + return Math.min(Math.max(val, 0), 1); + case Tone.Type.AudioRange: + return Math.min(Math.max(val, -1), 1); + case Tone.Type.Positive: + return Math.max(val, 0); + default: + return val; + } + } else { + return val; + } + }; + Tone.Param.prototype._toUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; + } + }; + Tone.Param.prototype._minOutput = 0.00001; + Tone.Param.prototype.setValueAtTime = function (value, time) { + value = this._fromUnits(value); + this._param.setValueAtTime(value, this.toSeconds(time)); + return this; + }; + Tone.Param.prototype.setRampPoint = function (now) { + now = this.defaultArg(now, this.now()); + var currentVal = this._param.value; + this._param.setValueAtTime(currentVal, now); + return this; + }; + Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + this._param.linearRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + Tone.Param.prototype.exponentialRampToValue = function (value, rampTime) { + var now = this.now(); + var currentVal = this.value; + this.setValueAtTime(Math.max(currentVal, this._minOutput), now); + this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); + return this; + }; + Tone.Param.prototype.linearRampToValue = function (value, rampTime) { + var now = this.now(); + this.setRampPoint(now); + this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); + return this; + }; + Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); + return this; + }; + Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration) { + for (var i = 0; i < values.length; i++) { + values[i] = this._fromUnits(values[i]); + } + this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); + return this; + }; + Tone.Param.prototype.cancelScheduledValues = function (startTime) { + this._param.cancelScheduledValues(this.toSeconds(startTime)); + return this; + }; + Tone.Param.prototype.rampTo = function (value, rampTime) { + rampTime = this.defaultArg(rampTime, 0); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) { + this.exponentialRampToValue(value, rampTime); + } else { + this.linearRampToValue(value, rampTime); + } + return this; + }; + Tone.Param.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._param = null; + return this; + }; + return Tone.Param; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Gain; +Tone_core_Gain = function (Tone) { + 'use strict'; + Tone.Gain = function () { + var options = this.optionsObject(arguments, [ + 'gain', + 'units' + ], Tone.Gain.defaults); + this.input = this.output = this._gainNode = this.context.createGain(); + this.gain = new Tone.Param({ + 'param': this._gainNode.gain, + 'units': options.units, + 'value': options.gain, + 'convert': options.convert + }); + this._readOnly('gain'); + }; + Tone.extend(Tone.Gain); + Tone.Gain.defaults = { + 'gain': 1, + 'convert': true + }; + Tone.Gain.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._gainNode.disconnect(); + this._gainNode = null; + this._writable('gain'); + this.gain.dispose(); + this.gain = null; + }; + return Tone.Gain; +}(Tone_core_Tone, Tone_core_Param); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Signal; +Tone_signal_Signal = function (Tone) { + 'use strict'; + Tone.Signal = function () { + var options = this.optionsObject(arguments, [ + 'value', + 'units' + ], Tone.Signal.defaults); + this.output = this._gain = this.context.createGain(); + options.param = this._gain.gain; + Tone.Param.call(this, options); + this.input = this._param = this._gain.gain; + Tone.Signal._constant.chain(this._gain); + }; + Tone.extend(Tone.Signal, Tone.Param); + Tone.Signal.defaults = { + 'value': 0, + 'units': Tone.Type.Default, + 'convert': true + }; + Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect; + Tone.Signal.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._param = null; + this._gain.disconnect(); + this._gain = null; + return this; + }; + Tone.Signal._constant = null; + Tone._initAudioContext(function (audioContext) { + var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate); + var arr = buffer.getChannelData(0); + for (var i = 0; i < arr.length; i++) { + arr[i] = 1; + } + Tone.Signal._constant = audioContext.createBufferSource(); + Tone.Signal._constant.channelCount = 1; + Tone.Signal._constant.channelCountMode = 'explicit'; + Tone.Signal._constant.buffer = buffer; + Tone.Signal._constant.loop = true; + Tone.Signal._constant.start(0); + Tone.Signal._constant.noGC(); + }); + return Tone.Signal; +}(Tone_core_Tone, Tone_signal_WaveShaper, Tone_core_Type, Tone_core_Param); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Add; +Tone_signal_Add = function (Tone) { + 'use strict'; + Tone.Add = function (value) { + Tone.call(this, 2, 0); + this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain(); + this._param = this.input[1] = new Tone.Signal(value); + this._param.connect(this._sum); + }; + Tone.extend(Tone.Add, Tone.Signal); + Tone.Add.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._sum.disconnect(); + this._sum = null; + this._param.dispose(); + this._param = null; + return this; + }; + return Tone.Add; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Multiply; +Tone_signal_Multiply = function (Tone) { + 'use strict'; + Tone.Multiply = function (value) { + Tone.call(this, 2, 0); + this._mult = this.input[0] = this.output = this.context.createGain(); + this._param = this.input[1] = this.output.gain; + this._param.value = this.defaultArg(value, 0); + }; + Tone.extend(Tone.Multiply, Tone.Signal); + Tone.Multiply.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._mult.disconnect(); + this._mult = null; + this._param = null; + return this; + }; + return Tone.Multiply; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Scale; +Tone_signal_Scale = function (Tone) { + 'use strict'; + Tone.Scale = function (outputMin, outputMax) { + this._outputMin = this.defaultArg(outputMin, 0); + this._outputMax = this.defaultArg(outputMax, 1); + this._scale = this.input = new Tone.Multiply(1); + this._add = this.output = new Tone.Add(0); + this._scale.connect(this._add); + this._setRange(); + }; + Tone.extend(Tone.Scale, Tone.SignalBase); + Object.defineProperty(Tone.Scale.prototype, 'min', { + get: function () { + return this._outputMin; + }, + set: function (min) { + this._outputMin = min; + this._setRange(); + } + }); + Object.defineProperty(Tone.Scale.prototype, 'max', { + get: function () { + return this._outputMax; + }, + set: function (max) { + this._outputMax = max; + this._setRange(); + } + }); + Tone.Scale.prototype._setRange = function () { + this._add.value = this._outputMin; + this._scale.value = this._outputMax - this._outputMin; + }; + Tone.Scale.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._add.dispose(); + this._add = null; + this._scale.dispose(); + this._scale = null; + return this; + }; + return Tone.Scale; +}(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply); +var signal; +signal = function () { + 'use strict'; + // Signal is built with the Tone.js signal by Yotam Mann + // https://github.com/TONEnoTONE/Tone.js/ + var Signal = Tone_signal_Signal; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + var Tone = Tone_core_Tone; + var p5sound = master; + Tone.setContext(p5sound.audiocontext); + /** + * <p>p5.Signal is a constant audio-rate signal used by p5.Oscillator + * and p5.Envelope for modulation math.</p> + * + * <p>This is necessary because Web Audio is processed on a seprate clock. + * For example, the p5 draw loop runs about 60 times per second. But + * the audio clock must process samples 44100 times per second. If we + * want to add a value to each of those samples, we can't do it in the + * draw loop, but we can do it by adding a constant-rate audio signal.</p. + * + * <p>This class mostly functions behind the scenes in p5.sound, and returns + * a Tone.Signal from the Tone.js library by Yotam Mann. + * If you want to work directly with audio signals for modular + * synthesis, check out + * <a href='http://bit.ly/1oIoEng' target=_'blank'>tone.js.</a></p> + * + * @class p5.Signal + * @constructor + * @return {Tone.Signal} A Signal object from the Tone.js library + * @example + * <div><code> + * function setup() { + * carrier = new p5.Oscillator('sine'); + * carrier.amp(1); // set amplitude + * carrier.freq(220); // set frequency + * carrier.start(); // start oscillating + * + * modulator = new p5.Oscillator('sawtooth'); + * modulator.disconnect(); + * modulator.amp(1); + * modulator.freq(4); + * modulator.start(); + * + * // Modulator's default amplitude range is -1 to 1. + * // Multiply it by -200, so the range is -200 to 200 + * // then add 220 so the range is 20 to 420 + * carrier.freq( modulator.mult(-200).add(220) ); + * } + * </code></div> + */ + p5.Signal = function (value) { + var s = new Signal(value); + // p5sound.soundArray.push(s); + return s; + }; + /** + * Fade to value, for smooth transitions + * + * @method fade + * @param {Number} value Value to set this signal + * @param {[Number]} secondsFromNow Length of fade, in seconds from now + */ + Signal.prototype.fade = Signal.prototype.linearRampToValueAtTime; + Mult.prototype.fade = Signal.prototype.fade; + Add.prototype.fade = Signal.prototype.fade; + Scale.prototype.fade = Signal.prototype.fade; + /** + * Connect a p5.sound object or Web Audio node to this + * p5.Signal so that its amplitude values can be scaled. + * + * @param {Object} input + */ + Signal.prototype.setInput = function (_input) { + _input.connect(this); + }; + Mult.prototype.setInput = Signal.prototype.setInput; + Add.prototype.setInput = Signal.prototype.setInput; + Scale.prototype.setInput = Signal.prototype.setInput; + // signals can add / mult / scale themselves + /** + * Add a constant value to this audio signal, + * and return the resulting audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalAdd. + * + * @method add + * @param {Number} number + * @return {p5.SignalAdd} object + */ + Signal.prototype.add = function (num) { + var add = new Add(num); + // add.setInput(this); + this.connect(add); + return add; + }; + Mult.prototype.add = Signal.prototype.add; + Add.prototype.add = Signal.prototype.add; + Scale.prototype.add = Signal.prototype.add; + /** + * Multiply this signal by a constant value, + * and return the resulting audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalMult. + * + * @method mult + * @param {Number} number to multiply + * @return {Tone.Multiply} object + */ + Signal.prototype.mult = function (num) { + var mult = new Mult(num); + // mult.setInput(this); + this.connect(mult); + return mult; + }; + Mult.prototype.mult = Signal.prototype.mult; + Add.prototype.mult = Signal.prototype.mult; + Scale.prototype.mult = Signal.prototype.mult; + /** + * Scale this signal value to a given range, + * and return the result as an audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalScale. + * + * @method scale + * @param {Number} number to multiply + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.SignalScale} object + */ + Signal.prototype.scale = function (inMin, inMax, outMin, outMax) { + var mapOutMin, mapOutMax; + if (arguments.length === 4) { + mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; + mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; + } else { + mapOutMin = arguments[0]; + mapOutMax = arguments[1]; + } + var scale = new Scale(mapOutMin, mapOutMax); + this.connect(scale); + return scale; + }; + Mult.prototype.scale = Signal.prototype.scale; + Add.prototype.scale = Signal.prototype.scale; + Scale.prototype.scale = Signal.prototype.scale; +}(Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone, master); +var oscillator; +oscillator = function () { + 'use strict'; + var p5sound = master; + var Signal = Tone_signal_Signal; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + /** + * <p>Creates a signal that oscillates between -1.0 and 1.0. + * By default, the oscillation takes the form of a sinusoidal + * shape ('sine'). Additional types include 'triangle', + * 'sawtooth' and 'square'. The frequency defaults to + * 440 oscillations per second (440Hz, equal to the pitch of an + * 'A' note).</p> + * + * <p>Set the type of oscillation with setType(), or by creating a + * specific oscillator.</p> For example: + * <code>new p5.SinOsc(freq)</code> + * <code>new p5.TriOsc(freq)</code> + * <code>new p5.SqrOsc(freq)</code> + * <code>new p5.SawOsc(freq)</code>. + * </p> + * + * @class p5.Oscillator + * @constructor + * @param {Number} [freq] frequency defaults to 440Hz + * @param {String} [type] type of oscillator. Options: + * 'sine' (default), 'triangle', + * 'sawtooth', 'square' + * @return {Object} Oscillator object + * @example + * <div><code> + * var osc; + * var playing = false; + * + * function setup() { + * backgroundColor = color(255,0,255); + * textAlign(CENTER); + * + * osc = new p5.Oscillator(); + * osc.setType('sine'); + * osc.freq(240); + * osc.amp(0); + * osc.start(); + * } + * + * function draw() { + * background(backgroundColor) + * text('click to play', width/2, height/2); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY < height && mouseY > 0) { + * if (!playing) { + * // ramp amplitude to 0.5 over 0.1 seconds + * osc.amp(0.5, 0.05); + * playing = true; + * backgroundColor = color(0,255,255); + * } else { + * // ramp amplitude to 0 over 0.5 seconds + * osc.amp(0, 0.5); + * playing = false; + * backgroundColor = color(255,0,255); + * } + * } + * } + * </code> </div> + */ + p5.Oscillator = function (freq, type) { + if (typeof freq === 'string') { + var f = type; + type = freq; + freq = f; + } + if (typeof type === 'number') { + var f = type; + type = freq; + freq = f; + } + this.started = false; + // components + this.phaseAmount = undefined; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.f = freq || 440; + // frequency + this.oscillator.frequency.setValueAtTime(this.f, p5sound.audiocontext.currentTime); + this.oscillator.type = type || 'sine'; + var o = this.oscillator; + // connections + this.input = p5sound.audiocontext.createGain(); + this.output = p5sound.audiocontext.createGain(); + this._freqMods = []; + // modulators connected to this oscillator's frequency + // set default output gain to 0.5 + this.output.gain.value = 0.5; + this.output.gain.setValueAtTime(0.5, p5sound.audiocontext.currentTime); + this.oscillator.connect(this.output); + // stereo panning + this.panPosition = 0; + this.connection = p5sound.input; + // connect to p5sound by default + this.panner = new p5.Panner(this.output, this.connection, 1); + //array of math operation signal chaining + this.mathOps = [this.output]; + // add to the soundArray so we can dispose of the osc later + p5sound.soundArray.push(this); + }; + /** + * Start an oscillator. Accepts an optional parameter to + * determine how long (in seconds from now) until the + * oscillator starts. + * + * @method start + * @param {Number} [time] startTime in seconds from now. + * @param {Number} [frequency] frequency in Hz. + */ + p5.Oscillator.prototype.start = function (time, f) { + if (this.started) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + } + if (!this.started) { + var freq = f || this.f; + var type = this.oscillator.type; + // var detune = this.oscillator.frequency.value; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.oscillator.frequency.exponentialRampToValueAtTime(Math.abs(freq), p5sound.audiocontext.currentTime); + this.oscillator.type = type; + // this.oscillator.detune.value = detune; + this.oscillator.connect(this.output); + time = time || 0; + this.oscillator.start(time + p5sound.audiocontext.currentTime); + this.freqNode = this.oscillator.frequency; + // if other oscillators are already connected to this osc's freq + for (var i in this._freqMods) { + if (typeof this._freqMods[i].connect !== 'undefined') { + this._freqMods[i].connect(this.oscillator.frequency); + } + } + this.started = true; + } + }; + /** + * Stop an oscillator. Accepts an optional parameter + * to determine how long (in seconds from now) until the + * oscillator stops. + * + * @method stop + * @param {Number} secondsFromNow Time, in seconds from now. + */ + p5.Oscillator.prototype.stop = function (time) { + if (this.started) { + var t = time || 0; + var now = p5sound.audiocontext.currentTime; + this.oscillator.stop(t + now); + this.started = false; + } + }; + /** + * Set the amplitude between 0 and 1.0. Or, pass in an object + * such as an oscillator to modulate amplitude with an audio signal. + * + * @method amp + * @param {Number|Object} vol between 0 and 1.0 + * or a modulating signal/oscillator + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {AudioParam} gain If no value is provided, + * returns the Web Audio API + * AudioParam that controls + * this oscillator's + * gain/amplitude/volume) + */ + p5.Oscillator.prototype.amp = function (vol, rampTime, tFromNow) { + var self = this; + if (typeof vol === 'number') { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(self.output.gain); + } else { + // return the Gain Node + return this.output.gain; + } + }; + // these are now the same thing + p5.Oscillator.prototype.fade = p5.Oscillator.prototype.amp; + p5.Oscillator.prototype.getAmp = function () { + return this.output.gain.value; + }; + /** + * Set frequency of an oscillator to a value. Or, pass in an object + * such as an oscillator to modulate the frequency with an audio signal. + * + * @method freq + * @param {Number|Object} Frequency Frequency in Hz + * or modulating signal/oscillator + * @param {Number} [rampTime] Ramp time (in seconds) + * @param {Number} [timeFromNow] Schedule this event to happen + * at x seconds from now + * @return {AudioParam} Frequency If no value is provided, + * returns the Web Audio API + * AudioParam that controls + * this oscillator's frequency + * @example + * <div><code> + * var osc = new p5.Oscillator(300); + * osc.start(); + * osc.freq(40, 10); + * </code></div> + */ + p5.Oscillator.prototype.freq = function (val, rampTime, tFromNow) { + if (typeof val === 'number' && !isNaN(val)) { + this.f = val; + var now = p5sound.audiocontext.currentTime; + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + // var currentFreq = this.oscillator.frequency.value; + // this.oscillator.frequency.cancelScheduledValues(now); + if (rampTime == 0) { + this.oscillator.frequency.cancelScheduledValues(now); + this.oscillator.frequency.setValueAtTime(val, tFromNow + now); + } else { + if (val > 0) { + this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + } else { + this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now); + } + } + // reset phase if oscillator has a phase + if (this.phaseAmount) { + this.phase(this.phaseAmount); + } + } else if (val) { + if (val.output) { + val = val.output; + } + val.connect(this.oscillator.frequency); + // keep track of what is modulating this param + // so it can be re-connected if + this._freqMods.push(val); + } else { + // return the Frequency Node + return this.oscillator.frequency; + } + }; + p5.Oscillator.prototype.getFreq = function () { + return this.oscillator.frequency.value; + }; + /** + * Set type to 'sine', 'triangle', 'sawtooth' or 'square'. + * + * @method setType + * @param {String} type 'sine', 'triangle', 'sawtooth' or 'square'. + */ + p5.Oscillator.prototype.setType = function (type) { + this.oscillator.type = type; + }; + p5.Oscillator.prototype.getType = function () { + return this.oscillator.type; + }; + /** + * Connect to a p5.sound / Web Audio object. + * + * @method connect + * @param {Object} unit A p5.sound or Web Audio object + */ + p5.Oscillator.prototype.connect = function (unit) { + if (!unit) { + this.panner.connect(p5sound.input); + } else if (unit.hasOwnProperty('input')) { + this.panner.connect(unit.input); + this.connection = unit.input; + } else { + this.panner.connect(unit); + this.connection = unit; + } + }; + /** + * Disconnect all outputs + * + * @method disconnect + */ + p5.Oscillator.prototype.disconnect = function (unit) { + this.output.disconnect(); + this.panner.disconnect(); + this.output.connect(this.panner); + this.oscMods = []; + }; + /** + * Pan between Left (-1) and Right (1) + * + * @method pan + * @param {Number} panning Number between -1 and 1 + * @param {Number} timeFromNow schedule this event to happen + * seconds from now + */ + p5.Oscillator.prototype.pan = function (pval, tFromNow) { + this.panPosition = pval; + this.panner.pan(pval, tFromNow); + }; + p5.Oscillator.prototype.getPan = function () { + return this.panPosition; + }; + // get rid of the oscillator + p5.Oscillator.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.oscillator) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + this.disconnect(); + this.oscillator.disconnect(); + this.panner = null; + this.oscillator = null; + } + // if it is a Pulse + if (this.osc2) { + this.osc2.dispose(); + } + }; + /** + * Set the phase of an oscillator between 0.0 and 1.0. + * In this implementation, phase is a delay time + * based on the oscillator's current frequency. + * + * @method phase + * @param {Number} phase float between 0.0 and 1.0 + */ + p5.Oscillator.prototype.phase = function (p) { + var delayAmt = p5.prototype.map(p, 0, 1, 0, 1 / this.f); + var now = p5sound.audiocontext.currentTime; + this.phaseAmount = p; + if (!this.dNode) { + // create a delay node + this.dNode = p5sound.audiocontext.createDelay(); + // put the delay node in between output and panner + this.oscillator.disconnect(); + this.oscillator.connect(this.dNode); + this.dNode.connect(this.output); + } + // set delay time to match phase: + this.dNode.delayTime.setValueAtTime(delayAmt, now); + }; + // ========================== // + // SIGNAL MATH FOR MODULATION // + // ========================== // + // return sigChain(this, scale, thisChain, nextChain, Scale); + var sigChain = function (o, mathObj, thisChain, nextChain, type) { + var chainSource = o.oscillator; + // if this type of math already exists in the chain, replace it + for (var i in o.mathOps) { + if (o.mathOps[i] instanceof type) { + chainSource.disconnect(); + o.mathOps[i].dispose(); + thisChain = i; + // assume nextChain is output gain node unless... + if (thisChain < o.mathOps.length - 2) { + nextChain = o.mathOps[i + 1]; + } + } + } + if (thisChain == o.mathOps.length - 1) { + o.mathOps.push(nextChain); + } + // assume source is the oscillator unless i > 0 + if (i > 0) { + chainSource = o.mathOps[i - 1]; + } + chainSource.disconnect(); + chainSource.connect(mathObj); + mathObj.connect(nextChain); + o.mathOps[thisChain] = mathObj; + return o; + }; + /** + * Add a value to the p5.Oscillator's output amplitude, + * and return the oscillator. Calling this method again + * will override the initial add() with a new value. + * + * @method add + * @param {Number} number Constant number to add + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with scaled output + * + */ + p5.Oscillator.prototype.add = function (num) { + var add = new Add(num); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, add, thisChain, nextChain, Add); + }; + /** + * Multiply the p5.Oscillator's output amplitude + * by a fixed value (i.e. turn it up!). Calling this method + * again will override the initial mult() with a new value. + * + * @method mult + * @param {Number} number Constant number to multiply + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with multiplied output + */ + p5.Oscillator.prototype.mult = function (num) { + var mult = new Mult(num); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, mult, thisChain, nextChain, Mult); + }; + /** + * Scale this oscillator's amplitude values to a given + * range, and return the oscillator. Calling this method + * again will override the initial scale() with new values. + * + * @method scale + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with scaled output + */ + p5.Oscillator.prototype.scale = function (inMin, inMax, outMin, outMax) { + var mapOutMin, mapOutMax; + if (arguments.length === 4) { + mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; + mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; + } else { + mapOutMin = arguments[0]; + mapOutMax = arguments[1]; + } + var scale = new Scale(mapOutMin, mapOutMax); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, scale, thisChain, nextChain, Scale); + }; + // ============================== // + // SinOsc, TriOsc, SqrOsc, SawOsc // + // ============================== // + /** + * Constructor: <code>new p5.SinOsc()</code>. + * This creates a Sine Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('sine') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('sine')</code>. + * See p5.Oscillator for methods. + * + * @method p5.SinOsc + * @param {[Number]} freq Set the frequency + */ + p5.SinOsc = function (freq) { + p5.Oscillator.call(this, freq, 'sine'); + }; + p5.SinOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.TriOsc()</code>. + * This creates a Triangle Wave Oscillator and is + * equivalent to <code>new p5.Oscillator('triangle') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('triangle')</code>. + * See p5.Oscillator for methods. + * + * @method p5.TriOsc + * @param {[Number]} freq Set the frequency + */ + p5.TriOsc = function (freq) { + p5.Oscillator.call(this, freq, 'triangle'); + }; + p5.TriOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.SawOsc()</code>. + * This creates a SawTooth Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('sawtooth') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('sawtooth')</code>. + * See p5.Oscillator for methods. + * + * @method p5.SawOsc + * @param {[Number]} freq Set the frequency + */ + p5.SawOsc = function (freq) { + p5.Oscillator.call(this, freq, 'sawtooth'); + }; + p5.SawOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.SqrOsc()</code>. + * This creates a Square Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('square') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('square')</code>. + * See p5.Oscillator for methods. + * + * @method p5.SqrOsc + * @param {[Number]} freq Set the frequency + */ + p5.SqrOsc = function (freq) { + p5.Oscillator.call(this, freq, 'square'); + }; + p5.SqrOsc.prototype = Object.create(p5.Oscillator.prototype); +}(master, Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Timeline; +Tone_core_Timeline = function (Tone) { + 'use strict'; + Tone.Timeline = function () { + var options = this.optionsObject(arguments, ['memory'], Tone.Timeline.defaults); + this._timeline = []; + this._toRemove = []; + this._iterating = false; + this.memory = options.memory; + }; + Tone.extend(Tone.Timeline); + Tone.Timeline.defaults = { 'memory': Infinity }; + Object.defineProperty(Tone.Timeline.prototype, 'length', { + get: function () { + return this._timeline.length; + } + }); + Tone.Timeline.prototype.addEvent = function (event) { + if (this.isUndef(event.time)) { + throw new Error('events must have a time attribute'); + } + event.time = this.toSeconds(event.time); + if (this._timeline.length) { + var index = this._search(event.time); + this._timeline.splice(index + 1, 0, event); + } else { + this._timeline.push(event); + } + if (this.length > this.memory) { + var diff = this.length - this.memory; + this._timeline.splice(0, diff); + } + return this; + }; + Tone.Timeline.prototype.removeEvent = function (event) { + if (this._iterating) { + this._toRemove.push(event); + } else { + var index = this._timeline.indexOf(event); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } + return this; + }; + Tone.Timeline.prototype.getEvent = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index !== -1) { + return this._timeline[index]; + } else { + return null; + } + }; + Tone.Timeline.prototype.getEventAfter = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index + 1 < this._timeline.length) { + return this._timeline[index + 1]; + } else { + return null; + } + }; + Tone.Timeline.prototype.getEventBefore = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index - 1 >= 0) { + return this._timeline[index - 1]; + } else { + return null; + } + }; + Tone.Timeline.prototype.cancel = function (after) { + if (this._timeline.length > 1) { + after = this.toSeconds(after); + var index = this._search(after); + if (index >= 0) { + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = []; + } + } else if (this._timeline.length === 1) { + if (this._timeline[0].time >= after) { + this._timeline = []; + } + } + return this; + }; + Tone.Timeline.prototype.cancelBefore = function (time) { + if (this._timeline.length) { + time = this.toSeconds(time); + var index = this._search(time); + if (index >= 0) { + this._timeline = this._timeline.slice(index + 1); + } + } + return this; + }; + Tone.Timeline.prototype._search = function (time) { + var beginning = 0; + var len = this._timeline.length; + var end = len; + while (beginning <= end && beginning < len) { + var midPoint = Math.floor(beginning + (end - beginning) / 2); + var event = this._timeline[midPoint]; + if (event.time === time) { + for (var i = midPoint; i < this._timeline.length; i++) { + var testEvent = this._timeline[i]; + if (testEvent.time === time) { + midPoint = i; + } + } + return midPoint; + } else if (event.time > time) { + end = midPoint - 1; + } else if (event.time < time) { + beginning = midPoint + 1; + } + } + return beginning - 1; + }; + Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) { + this._iterating = true; + lowerBound = this.defaultArg(lowerBound, 0); + upperBound = this.defaultArg(upperBound, this._timeline.length - 1); + for (var i = lowerBound; i <= upperBound; i++) { + callback(this._timeline[i]); + } + this._iterating = false; + if (this._toRemove.length > 0) { + for (var j = 0; j < this._toRemove.length; j++) { + var index = this._timeline.indexOf(this._toRemove[j]); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } + this._toRemove = []; + } + }; + Tone.Timeline.prototype.forEach = function (callback) { + this._iterate(callback); + return this; + }; + Tone.Timeline.prototype.forEachBefore = function (time, callback) { + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(callback, 0, upperBound); + } + return this; + }; + Tone.Timeline.prototype.forEachAfter = function (time, callback) { + time = this.toSeconds(time); + var lowerBound = this._search(time); + this._iterate(callback, lowerBound + 1); + return this; + }; + Tone.Timeline.prototype.forEachFrom = function (time, callback) { + time = this.toSeconds(time); + var lowerBound = this._search(time); + while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) { + lowerBound--; + } + this._iterate(callback, lowerBound + 1); + return this; + }; + Tone.Timeline.prototype.forEachAtTime = function (time, callback) { + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(function (event) { + if (event.time === time) { + callback(event); + } + }, 0, upperBound); + } + return this; + }; + Tone.Timeline.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._timeline = null; + this._toRemove = null; + }; + return Tone.Timeline; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_TimelineSignal; +Tone_signal_TimelineSignal = function (Tone) { + 'use strict'; + Tone.TimelineSignal = function () { + var options = this.optionsObject(arguments, [ + 'value', + 'units' + ], Tone.Signal.defaults); + Tone.Signal.apply(this, options); + options.param = this._param; + Tone.Param.call(this, options); + this._events = new Tone.Timeline(10); + this._initial = this._fromUnits(this._param.value); + }; + Tone.extend(Tone.TimelineSignal, Tone.Param); + Tone.TimelineSignal.Type = { + Linear: 'linear', + Exponential: 'exponential', + Target: 'target', + Set: 'set' + }; + Object.defineProperty(Tone.TimelineSignal.prototype, 'value', { + get: function () { + return this._toUnits(this._param.value); + }, + set: function (value) { + var convertedVal = this._fromUnits(value); + this._initial = convertedVal; + this._param.value = convertedVal; + } + }); + Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) { + value = this._fromUnits(value); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Set, + 'value': value, + 'time': startTime + }); + this._param.setValueAtTime(value, startTime); + return this; + }; + Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Linear, + 'value': value, + 'time': endTime + }); + this._param.linearRampToValueAtTime(value, endTime); + return this; + }; + Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Exponential, + 'value': value, + 'time': endTime + }); + this._param.exponentialRampToValueAtTime(value, endTime); + return this; + }; + Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Target, + 'value': value, + 'time': startTime, + 'constant': timeConstant + }); + this._param.setTargetAtTime(value, startTime, timeConstant); + return this; + }; + Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) { + this._events.cancel(after); + this._param.cancelScheduledValues(this.toSeconds(after)); + return this; + }; + Tone.TimelineSignal.prototype.setRampPoint = function (time) { + time = this.toSeconds(time); + var val = this.getValueAtTime(time); + var after = this._searchAfter(time); + if (after) { + this.cancelScheduledValues(time); + if (after.type === Tone.TimelineSignal.Type.Linear) { + this.linearRampToValueAtTime(val, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + this.exponentialRampToValueAtTime(val, time); + } + } + this.setValueAtTime(val, time); + return this; + }; + Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.linearRampToValueAtTime(value, finish); + return this; + }; + Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.exponentialRampToValueAtTime(value, finish); + return this; + }; + Tone.TimelineSignal.prototype._searchBefore = function (time) { + return this._events.getEvent(time); + }; + Tone.TimelineSignal.prototype._searchAfter = function (time) { + return this._events.getEventAfter(time); + }; + Tone.TimelineSignal.prototype.getValueAtTime = function (time) { + var after = this._searchAfter(time); + var before = this._searchBefore(time); + var value = this._initial; + if (before === null) { + value = this._initial; + } else if (before.type === Tone.TimelineSignal.Type.Target) { + var previous = this._events.getEventBefore(before.time); + var previouVal; + if (previous === null) { + previouVal = this._initial; + } else { + previouVal = previous.value; + } + value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (after === null) { + value = before.value; + } else if (after.type === Tone.TimelineSignal.Type.Linear) { + value = this._linearInterpolate(before.time, before.value, after.time, after.value, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time); + } else { + value = before.value; + } + return value; + }; + Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect; + Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { + v0 = Math.max(this._minOutput, v0); + return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); + }; + Tone.TimelineSignal.prototype.dispose = function () { + Tone.Signal.prototype.dispose.call(this); + Tone.Param.prototype.dispose.call(this); + this._events.dispose(); + this._events = null; + }; + return Tone.TimelineSignal; +}(Tone_core_Tone, Tone_signal_Signal); +var env; +env = function () { + 'use strict'; + var p5sound = master; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + var TimelineSignal = Tone_signal_TimelineSignal; + var Tone = Tone_core_Tone; + Tone.setContext(p5sound.audiocontext); + /** + * <p>Envelopes are pre-defined amplitude distribution over time. + * Typically, envelopes are used to control the output volume + * of an object, a series of fades referred to as Attack, Decay, + * Sustain and Release ( + * <a href="https://upload.wikimedia.org/wikipedia/commons/e/ea/ADSR_parameter.svg">ADSR</a> + * ). Envelopes can also control other Web Audio Parameters—for example, a p5.Env can + * control an Oscillator's frequency like this: <code>osc.freq(env)</code>.</p> + * <p>Use <code><a href="#/p5.Env/setRange">setRange</a></code> to change the attack/release level. + * Use <code><a href="#/p5.Env/setADSR">setADSR</a></code> to change attackTime, decayTime, sustainPercent and releaseTime.</p> + * <p>Use the <code><a href="#/p5.Env/play">play</a></code> method to play the entire envelope, + * the <code><a href="#/p5.Env/ramp">ramp</a></code> method for a pingable trigger, + * or <code><a href="#/p5.Env/triggerAttack">triggerAttack</a></code>/ + * <code><a href="#/p5.Env/triggerRelease">triggerRelease</a></code> to trigger noteOn/noteOff.</p> + * + * @class p5.Env + * @constructor + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv(){ + * env.play(); + * } + * </code></div> + */ + p5.Env = function (t1, l1, t2, l2, t3, l3) { + var now = p5sound.audiocontext.currentTime; + /** + * Time until envelope reaches attackLevel + * @property attackTime + */ + this.aTime = t1 || 0.1; + /** + * Level once attack is complete. + * @property attackLevel + */ + this.aLevel = l1 || 1; + /** + * Time until envelope reaches decayLevel. + * @property decayTime + */ + this.dTime = t2 || 0.5; + /** + * Level after decay. The envelope will sustain here until it is released. + * @property decayLevel + */ + this.dLevel = l2 || 0; + /** + * Duration of the release portion of the envelope. + * @property releaseTime + */ + this.rTime = t3 || 0; + /** + * Level at the end of the release. + * @property releaseLevel + */ + this.rLevel = l3 || 0; + this._rampHighPercentage = 0.98; + this._rampLowPercentage = 0.02; + this.output = p5sound.audiocontext.createGain(); + this.control = new TimelineSignal(); + this._init(); + // this makes sure the envelope starts at zero + this.control.connect(this.output); + // connect to the output + this.connection = null; + // store connection + //array of math operation signal chaining + this.mathOps = [this.control]; + //whether envelope should be linear or exponential curve + this.isExponential = false; + // oscillator or buffer source to clear on env complete + // to save resources if/when it is retriggered + this.sourceToClear = null; + // set to true if attack is set, then false on release + this.wasTriggered = false; + // add to the soundArray so we can dispose of the env later + p5sound.soundArray.push(this); + }; + // this init function just smooths the starting value to zero and gives a start point for the timeline + // - it was necessary to remove glitches at the beginning. + p5.Env.prototype._init = function () { + var now = p5sound.audiocontext.currentTime; + var t = now; + this.control.setTargetAtTime(0.00001, t, 0.001); + //also, compute the correct time constants + this._setRampAD(this.aTime, this.dTime); + }; + /** + * Reset the envelope with a series of time/value pairs. + * + * @method set + * @param {Number} attackTime Time (in seconds) before level + * reaches attackLevel + * @param {Number} attackLevel Typically an amplitude between + * 0.0 and 1.0 + * @param {Number} decayTime Time + * @param {Number} decayLevel Amplitude (In a standard ADSR envelope, + * decayLevel = sustainLevel) + * @param {Number} releaseTime Release Time (in seconds) + * @param {Number} releaseLevel Amplitude + */ + p5.Env.prototype.set = function (t1, l1, t2, l2, t3, l3) { + this.aTime = t1; + this.aLevel = l1; + this.dTime = t2 || 0; + this.dLevel = l2 || 0; + this.rTime = t4 || 0; + this.rLevel = l4 || 0; + // set time constants for ramp + this._setRampAD(t1, t2); + }; + /** + * Set values like a traditional + * <a href="https://en.wikipedia.org/wiki/Synthesizer#/media/File:ADSR_parameter.svg"> + * ADSR envelope + * </a>. + * + * @method setADSR + * @param {Number} attackTime Time (in seconds before envelope + * reaches Attack Level + * @param {Number} [decayTime] Time (in seconds) before envelope + * reaches Decay/Sustain Level + * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, + * where 1.0 = attackLevel, 0.0 = releaseLevel. + * The susRatio determines the decayLevel and the level at which the + * sustain portion of the envelope will sustain. + * For example, if attackLevel is 0.4, releaseLevel is 0, + * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is + * increased to 1.0 (using <code>setRange</code>), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv(){ + * env.play(); + * } + * </code></div> + */ + p5.Env.prototype.setADSR = function (aTime, dTime, sPercent, rTime) { + this.aTime = aTime; + this.dTime = dTime || 0; + // lerp + this.sPercent = sPercent || 0; + this.dLevel = typeof sPercent !== 'undefined' ? sPercent * (this.aLevel - this.rLevel) + this.rLevel : 0; + this.rTime = rTime || 0; + // also set time constants for ramp + this._setRampAD(aTime, dTime); + }; + /** + * Set max (attackLevel) and min (releaseLevel) of envelope. + * + * @method setRange + * @param {Number} aLevel attack level (defaults to 1) + * @param {Number} rLevel release level (defaults to 0) + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv(){ + * env.play(); + * } + * </code></div> + */ + p5.Env.prototype.setRange = function (aLevel, rLevel) { + this.aLevel = aLevel || 1; + this.rLevel = rLevel || 0; + }; + // private (undocumented) method called when ADSR is set to set time constants for ramp + // + // Set the <a href="https://en.wikipedia.org/wiki/RC_time_constant"> + // time constants</a> for simple exponential ramps. + // The larger the time constant value, the slower the + // transition will be. + // + // method _setRampAD + // param {Number} attackTimeConstant attack time constant + // param {Number} decayTimeConstant decay time constant + // + p5.Env.prototype._setRampAD = function (t1, t2) { + this._rampAttackTime = this.checkExpInput(t1); + this._rampDecayTime = this.checkExpInput(t2); + var TCDenominator = 1; + /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) + TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); + this._rampAttackTC = t1 / this.checkExpInput(TCDenominator); + TCDenominator = Math.log(1 / this._rampLowPercentage); + this._rampDecayTC = t2 / this.checkExpInput(TCDenominator); + }; + // private method + p5.Env.prototype.setRampPercentages = function (p1, p2) { + //set the percentages that the simple exponential ramps go to + this._rampHighPercentage = this.checkExpInput(p1); + this._rampLowPercentage = this.checkExpInput(p2); + var TCDenominator = 1; + //now re-compute the time constants based on those percentages + /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) + TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); + this._rampAttackTC = this._rampAttackTime / this.checkExpInput(TCDenominator); + TCDenominator = Math.log(1 / this._rampLowPercentage); + this._rampDecayTC = this._rampDecayTime / this.checkExpInput(TCDenominator); + }; + /** + * Assign a parameter to be controlled by this envelope. + * If a p5.Sound object is given, then the p5.Env will control its + * output gain. If multiple inputs are provided, the env will + * control all of them. + * + * @method setInput + * @param {Object} unit A p5.sound object or + * Web Audio Param. + */ + p5.Env.prototype.setInput = function (unit) { + for (var i = 0; i < arguments.length; i++) { + this.connect(arguments[i]); + } + }; + /** + * Set whether the envelope ramp is linear (default) or exponential. + * Exponential ramps can be useful because we perceive amplitude + * and frequency logarithmically. + * + * @method setExp + * @param {Boolean} isExp true is exponential, false is linear + */ + p5.Env.prototype.setExp = function (isExp) { + this.isExponential = isExp; + }; + //helper method to protect against zero values being sent to exponential functions + p5.Env.prototype.checkExpInput = function (value) { + if (value <= 0) { + value = 0.0001; + } + return value; + }; + /** + * Play tells the envelope to start acting on a given input. + * If the input is a p5.sound object (i.e. AudioIn, Oscillator, + * SoundFile), then Env will control its output volume. + * Envelopes can also be used to control any <a href=" + * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> + * Web Audio Audio Param.</a> + * + * @method play + * @param {Object} unit A p5.sound object or + * Web Audio Param. + * @param {Number} [startTime] time from now (in seconds) at which to play + * @param {Number} [sustainTime] time to sustain before releasing the envelope + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv(){ + * // trigger env on triOsc, 0 seconds from now + * // After decay, sustain for 0.2 seconds before release + * env.play(triOsc, 0, 0.2); + * } + * </code></div> + */ + p5.Env.prototype.play = function (unit, secondsFromNow, susTime) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var susTime = susTime || 0; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + this.triggerAttack(unit, tFromNow); + this.triggerRelease(unit, tFromNow + this.aTime + this.dTime + susTime); + }; + /** + * Trigger the Attack, and Decay portion of the Envelope. + * Similar to holding down a key on a piano, but it will + * hold the sustain level until you let go. Input can be + * any p5.sound object, or a <a href=" + * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> + * Web Audio Param</a>. + * + * @method triggerAttack + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow time from now (in seconds) + * @example + * <div><code> + * + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.3; + * var susPercent = 0.4; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * background(200); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(envAttack); + * } + * + * function envAttack(){ + * console.log('trigger attack'); + * env.triggerAttack(); + * + * background(0,255,0); + * text('attack!', width/2, height/2); + * } + * + * function mouseReleased() { + * env.triggerRelease(); + * + * background(200); + * text('click to play', width/2, height/2); + * } + * </code></div> + */ + p5.Env.prototype.triggerAttack = function (unit, secondsFromNow) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + this.lastAttack = t; + this.wasTriggered = true; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + // get and set value (with linear ramp) to anchor automation + var valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + // not sure if this is necessary + if (this.isExponential == true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); + } else { + this.control.linearRampToValueAtTime(valToSet, t); + } + // after each ramp completes, cancel scheduled values + // (so they can be overridden in case env has been re-triggered) + // then, set current value (with linearRamp to avoid click) + // then, schedule the next automation... + // attack + t += this.aTime; + if (this.isExponential == true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.aLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.aLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + // decay to decay level (if using ADSR, then decay level == sustain level) + t += this.dTime; + if (this.isExponential == true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.dLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.dLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + }; + /** + * Trigger the Release of the Envelope. This is similar to releasing + * the key on a piano and letting the sound fade according to the + * release level and release time. + * + * @method triggerRelease + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow time to trigger the release + * @example + * <div><code> + * + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001 + * var decayTime = 0.3; + * var susPercent = 0.4; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * background(200); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(envAttack); + * } + * + * function envAttack(){ + * console.log('trigger attack'); + * env.triggerAttack(); + * + * background(0,255,0); + * text('attack!', width/2, height/2); + * } + * + * function mouseReleased() { + * env.triggerRelease(); + * + * background(200); + * text('click to play', width/2, height/2); + * } + * </code></div> + */ + p5.Env.prototype.triggerRelease = function (unit, secondsFromNow) { + // only trigger a release if an attack was triggered + if (!this.wasTriggered) { + // this currently causes a bit of trouble: + // if a later release has been scheduled (via the play function) + // a new earlier release won't interrupt it, because + // this.wasTriggered has already been set to false. + // If we want new earlier releases to override, then we need to + // keep track of the last release time, and if the new release time is + // earlier, then use it. + return; + } + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + // get and set value (with linear or exponential ramp) to anchor automation + var valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + // not sure if this is necessary + if (this.isExponential == true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); + } else { + this.control.linearRampToValueAtTime(valToSet, t); + } + // release + t += this.rTime; + if (this.isExponential == true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.rLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.rLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + this.wasTriggered = false; + }; + /** + * Exponentially ramp to a value using the first two + * values from <code><a href="#/p5.Env/setADSR">setADSR(attackTime, decayTime)</a></code> + * as <a href="https://en.wikipedia.org/wiki/RC_time_constant"> + * time constants</a> for simple exponential ramps. + * If the value is higher than current value, it uses attackTime, + * while a decrease uses decayTime. + * + * @method ramp + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow When to trigger the ramp + * @param {Number} v Target value + * @param {Number} [v2] Second target value (optional) + * @example + * <div><code> + * var env, osc, amp, cnv; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var attackLevel = 1; + * var decayLevel = 0; + * + * function setup() { + * cnv = createCanvas(100, 100); + * fill(0,255,0); + * noStroke(); + * + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime); + * + * osc = new p5.Oscillator(); + * osc.amp(env); + * osc.start(); + * + * amp = new p5.Amplitude(); + * + * cnv.mousePressed(triggerRamp); + * } + * + * function triggerRamp() { + * env.ramp(osc, 0, attackLevel, decayLevel); + * } + * + * function draw() { + * background(20,20,20); + * text('click me', 10, 20); + * var h = map(amp.getLevel(), 0, 0.4, 0, height);; + * + * rect(0, height, width, -h); + * } + * </code></div> + */ + p5.Env.prototype.ramp = function (unit, secondsFromNow, v1, v2) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + var destination1 = this.checkExpInput(v1); + var destination2 = typeof v2 !== 'undefined' ? this.checkExpInput(v2) : undefined; + // connect env to unit if not already connected + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + //get current value + var currentVal = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + //if it's going up + if (destination1 > currentVal) { + this.control.setTargetAtTime(destination1, t, this._rampAttackTC); + t += this._rampAttackTime; + } else if (destination1 < currentVal) { + this.control.setTargetAtTime(destination1, t, this._rampDecayTC); + t += this._rampDecayTime; + } + // Now the second part of envelope begins + if (destination2 === undefined) + return; + //if it's going up + if (destination2 > destination1) { + this.control.setTargetAtTime(destination2, t, this._rampAttackTC); + } else if (destination2 < destination1) { + this.control.setTargetAtTime(destination2, t, this._rampDecayTC); + } + }; + p5.Env.prototype.connect = function (unit) { + this.connection = unit; + // assume we're talking about output gain + // unless given a different audio param + if (unit instanceof p5.Oscillator || unit instanceof p5.SoundFile || unit instanceof p5.AudioIn || unit instanceof p5.Reverb || unit instanceof p5.Noise || unit instanceof p5.Filter || unit instanceof p5.Delay) { + unit = unit.output.gain; + } + if (unit instanceof AudioParam) { + //set the initial value + unit.setValueAtTime(0, p5sound.audiocontext.currentTime); + } + if (unit instanceof p5.Signal) { + unit.setValue(0); + } + this.output.connect(unit); + }; + p5.Env.prototype.disconnect = function (unit) { + this.output.disconnect(); + }; + // Signal Math + /** + * Add a value to the p5.Oscillator's output amplitude, + * and return the oscillator. Calling this method + * again will override the initial add() with new values. + * + * @method add + * @param {Number} number Constant number to add + * @return {p5.Env} Envelope Returns this envelope + * with scaled output + */ + p5.Env.prototype.add = function (num) { + var add = new Add(num); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, add, thisChain, nextChain, Add); + }; + /** + * Multiply the p5.Env's output amplitude + * by a fixed value. Calling this method + * again will override the initial mult() with new values. + * + * @method mult + * @param {Number} number Constant number to multiply + * @return {p5.Env} Envelope Returns this envelope + * with scaled output + */ + p5.Env.prototype.mult = function (num) { + var mult = new Mult(num); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, mult, thisChain, nextChain, Mult); + }; + /** + * Scale this envelope's amplitude values to a given + * range, and return the envelope. Calling this method + * again will override the initial scale() with new values. + * + * @method scale + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.Env} Envelope Returns this envelope + * with scaled output + */ + p5.Env.prototype.scale = function (inMin, inMax, outMin, outMax) { + var scale = new Scale(inMin, inMax, outMin, outMax); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, scale, thisChain, nextChain, Scale); + }; + // get rid of the oscillator + p5.Env.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + var now = p5sound.audiocontext.currentTime; + this.disconnect(); + try { + this.control.dispose(); + this.control = null; + } catch (e) { + } + for (var i = 1; i < this.mathOps.length; i++) { + mathOps[i].dispose(); + } + }; +}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_signal_TimelineSignal, Tone_core_Tone); +var pulse; +pulse = function () { + 'use strict'; + var p5sound = master; + /** + * Creates a Pulse object, an oscillator that implements + * Pulse Width Modulation. + * The pulse is created with two oscillators. + * Accepts a parameter for frequency, and to set the + * width between the pulses. See <a href=" + * http://p5js.org/reference/#/p5.Oscillator"> + * <code>p5.Oscillator</code> for a full list of methods. + * + * @class p5.Pulse + * @constructor + * @param {Number} [freq] Frequency in oscillations per second (Hz) + * @param {Number} [w] Width between the pulses (0 to 1.0, + * defaults to 0) + * @example + * <div><code> + * var pulse; + * function setup() { + * background(0); + * + * // Create and start the pulse wave oscillator + * pulse = new p5.Pulse(); + * pulse.amp(0.5); + * pulse.freq(220); + * pulse.start(); + * } + * + * function draw() { + * var w = map(mouseX, 0, width, 0, 1); + * w = constrain(w, 0, 1); + * pulse.width(w) + * } + * </code></div> + */ + p5.Pulse = function (freq, w) { + p5.Oscillator.call(this, freq, 'sawtooth'); + // width of PWM, should be betw 0 to 1.0 + this.w = w || 0; + // create a second oscillator with inverse frequency + this.osc2 = new p5.SawOsc(freq); + // create a delay node + this.dNode = p5sound.audiocontext.createDelay(); + // dc offset + this.dcOffset = createDCOffset(); + this.dcGain = p5sound.audiocontext.createGain(); + this.dcOffset.connect(this.dcGain); + this.dcGain.connect(this.output); + // set delay time based on PWM width + this.f = freq || 440; + var mW = this.w / this.oscillator.frequency.value; + this.dNode.delayTime.value = mW; + this.dcGain.gain.value = 1.7 * (0.5 - this.w); + // disconnect osc2 and connect it to delay, which is connected to output + this.osc2.disconnect(); + this.osc2.panner.disconnect(); + this.osc2.amp(-1); + // inverted amplitude + this.osc2.output.connect(this.dNode); + this.dNode.connect(this.output); + this.output.gain.value = 1; + this.output.connect(this.panner); + }; + p5.Pulse.prototype = Object.create(p5.Oscillator.prototype); + /** + * Set the width of a Pulse object (an oscillator that implements + * Pulse Width Modulation). + * + * @method width + * @param {Number} [width] Width between the pulses (0 to 1.0, + * defaults to 0) + */ + p5.Pulse.prototype.width = function (w) { + if (typeof w === 'number') { + if (w <= 1 && w >= 0) { + this.w = w; + // set delay time based on PWM width + // var mW = map(this.w, 0, 1.0, 0, 1/this.f); + var mW = this.w / this.oscillator.frequency.value; + this.dNode.delayTime.value = mW; + } + this.dcGain.gain.value = 1.7 * (0.5 - this.w); + } else { + w.connect(this.dNode.delayTime); + var sig = new p5.SignalAdd(-0.5); + sig.setInput(w); + sig = sig.mult(-1); + sig = sig.mult(1.7); + sig.connect(this.dcGain.gain); + } + }; + p5.Pulse.prototype.start = function (f, time) { + var now = p5sound.audiocontext.currentTime; + var t = time || 0; + if (!this.started) { + var freq = f || this.f; + var type = this.oscillator.type; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.oscillator.frequency.setValueAtTime(freq, now); + this.oscillator.type = type; + this.oscillator.connect(this.output); + this.oscillator.start(t + now); + // set up osc2 + this.osc2.oscillator = p5sound.audiocontext.createOscillator(); + this.osc2.oscillator.frequency.setValueAtTime(freq, t + now); + this.osc2.oscillator.type = type; + this.osc2.oscillator.connect(this.osc2.output); + this.osc2.start(t + now); + this.freqNode = [ + this.oscillator.frequency, + this.osc2.oscillator.frequency + ]; + // start dcOffset, too + this.dcOffset = createDCOffset(); + this.dcOffset.connect(this.dcGain); + this.dcOffset.start(t + now); + // if LFO connections depend on these oscillators + if (this.mods !== undefined && this.mods.frequency !== undefined) { + this.mods.frequency.connect(this.freqNode[0]); + this.mods.frequency.connect(this.freqNode[1]); + } + this.started = true; + this.osc2.started = true; + } + }; + p5.Pulse.prototype.stop = function (time) { + if (this.started) { + var t = time || 0; + var now = p5sound.audiocontext.currentTime; + this.oscillator.stop(t + now); + this.osc2.oscillator.stop(t + now); + this.dcOffset.stop(t + now); + this.started = false; + this.osc2.started = false; + } + }; + p5.Pulse.prototype.freq = function (val, rampTime, tFromNow) { + if (typeof val === 'number') { + this.f = val; + var now = p5sound.audiocontext.currentTime; + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var currentFreq = this.oscillator.frequency.value; + this.oscillator.frequency.cancelScheduledValues(now); + this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); + this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + this.osc2.oscillator.frequency.cancelScheduledValues(now); + this.osc2.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); + this.osc2.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + if (this.freqMod) { + this.freqMod.output.disconnect(); + this.freqMod = null; + } + } else if (val.output) { + val.output.disconnect(); + val.output.connect(this.oscillator.frequency); + val.output.connect(this.osc2.oscillator.frequency); + this.freqMod = val; + } + }; + // inspiration: http://webaudiodemos.appspot.com/oscilloscope/ + function createDCOffset() { + var ac = p5sound.audiocontext; + var buffer = ac.createBuffer(1, 2048, ac.sampleRate); + var data = buffer.getChannelData(0); + for (var i = 0; i < 2048; i++) + data[i] = 1; + var bufferSource = ac.createBufferSource(); + bufferSource.buffer = buffer; + bufferSource.loop = true; + return bufferSource; + } +}(master, oscillator); +var noise; +noise = function () { + 'use strict'; + var p5sound = master; + /** + * Noise is a type of oscillator that generates a buffer with random values. + * + * @class p5.Noise + * @constructor + * @param {String} type Type of noise can be 'white' (default), + * 'brown' or 'pink'. + * @return {Object} Noise Object + */ + p5.Noise = function (type) { + p5.Oscillator.call(this); + delete this.f; + delete this.freq; + delete this.oscillator; + this.buffer = _whiteNoise; + }; + p5.Noise.prototype = Object.create(p5.Oscillator.prototype); + // generate noise buffers + var _whiteNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var whiteBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = whiteBuffer.getChannelData(0); + for (var i = 0; i < bufferSize; i++) { + noiseData[i] = Math.random() * 2 - 1; + } + whiteBuffer.type = 'white'; + return whiteBuffer; + }(); + var _pinkNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var pinkBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = pinkBuffer.getChannelData(0); + var b0, b1, b2, b3, b4, b5, b6; + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; + for (var i = 0; i < bufferSize; i++) { + var white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + noiseData[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + noiseData[i] *= 0.11; + // (roughly) compensate for gain + b6 = white * 0.115926; + } + pinkBuffer.type = 'pink'; + return pinkBuffer; + }(); + var _brownNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var brownBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = brownBuffer.getChannelData(0); + var lastOut = 0; + for (var i = 0; i < bufferSize; i++) { + var white = Math.random() * 2 - 1; + noiseData[i] = (lastOut + 0.02 * white) / 1.02; + lastOut = noiseData[i]; + noiseData[i] *= 3.5; + } + brownBuffer.type = 'brown'; + return brownBuffer; + }(); + /** + * Set type of noise to 'white', 'pink' or 'brown'. + * White is the default. + * + * @method setType + * @param {String} [type] 'white', 'pink' or 'brown' + */ + p5.Noise.prototype.setType = function (type) { + switch (type) { + case 'white': + this.buffer = _whiteNoise; + break; + case 'pink': + this.buffer = _pinkNoise; + break; + case 'brown': + this.buffer = _brownNoise; + break; + default: + this.buffer = _whiteNoise; + } + if (this.started) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + this.start(now + 0.01); + } + }; + p5.Noise.prototype.getType = function () { + return this.buffer.type; + }; + /** + * Start the noise + * + * @method start + */ + p5.Noise.prototype.start = function () { + if (this.started) { + this.stop(); + } + this.noise = p5sound.audiocontext.createBufferSource(); + this.noise.buffer = this.buffer; + this.noise.loop = true; + this.noise.connect(this.output); + var now = p5sound.audiocontext.currentTime; + this.noise.start(now); + this.started = true; + }; + /** + * Stop the noise. + * + * @method stop + */ + p5.Noise.prototype.stop = function () { + var now = p5sound.audiocontext.currentTime; + if (this.noise) { + this.noise.stop(now); + this.started = false; + } + }; + /** + * Pan the noise. + * + * @method pan + * @param {Number} panning Number between -1 (left) + * and 1 (right) + * @param {Number} timeFromNow schedule this event to happen + * seconds from now + */ + /** + * Set the amplitude of the noise between 0 and 1.0. Or, + * modulate amplitude with an audio signal such as an oscillator. + * + * @param {Number|Object} volume amplitude between 0 and 1.0 + * or modulating signal/oscillator + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Noise.prototype.dispose = function () { + var now = p5sound.audiocontext.currentTime; + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.noise) { + this.noise.disconnect(); + this.stop(now); + } + if (this.output) { + this.output.disconnect(); + } + if (this.panner) { + this.panner.disconnect(); + } + this.output = null; + this.panner = null; + this.buffer = null; + this.noise = null; + }; +}(master); +var audioin; +audioin = function () { + 'use strict'; + var p5sound = master; + var CustomError = errorHandler; + /** + * <p>Get audio from an input, i.e. your computer's microphone.</p> + * + * <p>Turn the mic on/off with the start() and stop() methods. When the mic + * is on, its volume can be measured with getLevel or by connecting an + * FFT object.</p> + * + * <p>If you want to hear the AudioIn, use the .connect() method. + * AudioIn does not connect to p5.sound output by default to prevent + * feedback.</p> + * + * <p><em>Note: This uses the <a href="http://caniuse.com/stream">getUserMedia/ + * Stream</a> API, which is not supported by certain browsers.</em></p> + * + * @class p5.AudioIn + * @constructor + * @return {Object} AudioIn + * @example + * <div><code> + * var mic; + * function setup(){ + * mic = new p5.AudioIn() + * mic.start(); + * } + * function draw(){ + * background(0); + * micLevel = mic.getLevel(); + * ellipse(width/2, constrain(height-micLevel*height*5, 0, height), 10, 10); + * } + * </code></div> + */ + p5.AudioIn = function () { + // set up audio input + this.input = p5sound.audiocontext.createGain(); + this.output = p5sound.audiocontext.createGain(); + this.stream = null; + this.mediaStream = null; + this.currentSource = 0; + /** + * Client must allow browser to access their microphone / audioin source. + * Default: false. Will become true when the client enables acces. + * + * @property {Boolean} enabled + */ + this.enabled = false; + // create an amplitude, connect to it by default but not to master out + this.amplitude = new p5.Amplitude(); + this.output.connect(this.amplitude.input); + // Some browsers let developer determine their input sources + if (typeof window.MediaStreamTrack === 'undefined') { + window.alert('This browser does not support MediaStreamTrack'); + } else if (typeof window.MediaStreamTrack.getSources === 'function') { + // Chrome supports getSources to list inputs. Dev picks default + window.MediaStreamTrack.getSources(this._gotSources); + } else { + } + // add to soundArray so we can dispose on close + p5sound.soundArray.push(this); + }; + /** + * Start processing audio input. This enables the use of other + * AudioIn methods like getLevel(). Note that by default, AudioIn + * is not connected to p5.sound's output. So you won't hear + * anything unless you use the connect() method.<br/> + * + * @method start + * @param {Function} successCallback Name of a function to call on + * success. + * @param {Function} errorCallback Name of a function to call if + * there was an error. For example, + * some browsers do not support + * getUserMedia. + */ + p5.AudioIn.prototype.start = function (successCallback, errorCallback) { + var self = this; + // if _gotSources() i.e. developers determine which source to use + if (p5sound.inputSources[self.currentSource]) { + // set the audio source + var audioSource = p5sound.inputSources[self.currentSource].id; + var constraints = { audio: { optional: [{ sourceId: audioSource }] } }; + window.navigator.getUserMedia(constraints, this._onStream = function (stream) { + self.stream = stream; + self.enabled = true; + // Wrap a MediaStreamSourceNode around the live input + self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); + self.mediaStream.connect(self.output); + if (successCallback) + successCallback(); + // only send to the Amplitude reader, so we can see it but not hear it. + self.amplitude.setInput(self.output); + }, this._onStreamError = function (e) { + if (errorCallback) + errorCallback(e); + else + console.error(e); + }); + } else { + // if Firefox where users select their source via browser + // if (typeof MediaStreamTrack.getSources === 'undefined') { + // Only get the audio stream. + window.navigator.getUserMedia({ 'audio': true }, this._onStream = function (stream) { + self.stream = stream; + self.enabled = true; + // Wrap a MediaStreamSourceNode around the live input + self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); + self.mediaStream.connect(self.output); + // only send to the Amplitude reader, so we can see it but not hear it. + self.amplitude.setInput(self.output); + if (successCallback) + successCallback(); + }, this._onStreamError = function (e) { + if (errorCallback) + errorCallback(e); + else + console.error(e); + }); + } + }; + /** + * Turn the AudioIn off. If the AudioIn is stopped, it cannot getLevel().<br/> + * + * @method stop + */ + p5.AudioIn.prototype.stop = function () { + if (this.stream) { + this.stream.stop(); + } + }; + /** + * Connect to an audio unit. If no parameter is provided, will + * connect to the master output (i.e. your speakers).<br/> + * + * @method connect + * @param {Object} [unit] An object that accepts audio input, + * such as an FFT + */ + p5.AudioIn.prototype.connect = function (unit) { + if (unit) { + if (unit.hasOwnProperty('input')) { + this.output.connect(unit.input); + } else if (unit.hasOwnProperty('analyser')) { + this.output.connect(unit.analyser); + } else { + this.output.connect(unit); + } + } else { + this.output.connect(p5sound.input); + } + }; + /** + * Disconnect the AudioIn from all audio units. For example, if + * connect() had been called, disconnect() will stop sending + * signal to your speakers.<br/> + * + * @method disconnect + */ + p5.AudioIn.prototype.disconnect = function (unit) { + this.output.disconnect(unit); + // stay connected to amplitude even if not outputting to p5 + this.output.connect(this.amplitude.input); + }; + /** + * Read the Amplitude (volume level) of an AudioIn. The AudioIn + * class contains its own instance of the Amplitude class to help + * make it easy to get a microphone's volume level. Accepts an + * optional smoothing value (0.0 < 1.0). <em>NOTE: AudioIn must + * .start() before using .getLevel().</em><br/> + * + * @method getLevel + * @param {Number} [smoothing] Smoothing is 0.0 by default. + * Smooths values based on previous values. + * @return {Number} Volume level (between 0.0 and 1.0) + */ + p5.AudioIn.prototype.getLevel = function (smoothing) { + if (smoothing) { + this.amplitude.smoothing = smoothing; + } + return this.amplitude.getLevel(); + }; + /** + * Add input sources to the list of available sources. + * + * @private + */ + p5.AudioIn.prototype._gotSources = function (sourceInfos) { + for (var i = 0; i < sourceInfos.length; i++) { + var sourceInfo = sourceInfos[i]; + if (sourceInfo.kind === 'audio') { + // add the inputs to inputSources + //p5sound.inputSources.push(sourceInfo); + return sourceInfo; + } + } + }; + /** + * Set amplitude (volume) of a mic input between 0 and 1.0. <br/> + * + * @method amp + * @param {Number} vol between 0 and 1.0 + * @param {Number} [time] ramp time (optional) + */ + p5.AudioIn.prototype.amp = function (vol, t) { + if (t) { + var rampTime = t || 0; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); + this.output.gain.setValueAtTime(currentVol, p5sound.audiocontext.currentTime); + this.output.gain.linearRampToValueAtTime(vol, rampTime + p5sound.audiocontext.currentTime); + } else { + this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); + this.output.gain.setValueAtTime(vol, p5sound.audiocontext.currentTime); + } + }; + p5.AudioIn.prototype.listSources = function () { + console.log('listSources is deprecated - please use AudioIn.getSources'); + console.log('input sources: '); + if (p5sound.inputSources.length > 0) { + return p5sound.inputSources; + } else { + return 'This browser does not support MediaStreamTrack.getSources()'; + } + }; + /** + * Chrome only. Returns a list of available input sources + * and allows the user to set the media source. Firefox allows + * the user to choose from input sources in the permissions dialogue + * instead of enumerating available sources and selecting one. + * Note: in order to have descriptive media names your page must be + * served over a secure (HTTPS) connection and the page should + * request user media before enumerating devices. Otherwise device + * ID will be a long device ID number and does not specify device + * type. For example see + * https://simpl.info/getusermedia/sources/index.html vs. + * http://simpl.info/getusermedia/sources/index.html + * + * @method getSources + * @param {Function} callback a callback to handle the sources + * when they have been enumerated + * @example + * <div><code> + * var audiograb; + * + * function setup(){ + * //new audioIn + * audioGrab = new p5.AudioIn(); + * + * audioGrab.getSources(function(sourceList) { + * //print out the array of available sources + * console.log(sourceList); + * //set the source to the first item in the inputSources array + * audioGrab.setSource(0); + * }); + * } + * </code></div> + */ + p5.AudioIn.prototype.getSources = function (callback) { + if (typeof window.MediaStreamTrack.getSources === 'function') { + window.MediaStreamTrack.getSources(function (data) { + for (var i = 0, max = data.length; i < max; i++) { + var sourceInfo = data[i]; + if (sourceInfo.kind === 'audio') { + // add the inputs to inputSources + p5sound.inputSources.push(sourceInfo); + } + } + callback(p5sound.inputSources); + }); + } else { + console.log('This browser does not support MediaStreamTrack.getSources()'); + } + }; + /** + * Set the input source. Accepts a number representing a + * position in the array returned by listSources(). + * This is only available in browsers that support + * MediaStreamTrack.getSources(). Instead, some browsers + * give users the option to set their own media source.<br/> + * + * @method setSource + * @param {number} num position of input source in the array + */ + p5.AudioIn.prototype.setSource = function (num) { + // TO DO - set input by string or # (array position) + var self = this; + if (p5sound.inputSources.length > 0 && num < p5sound.inputSources.length) { + // set the current source + self.currentSource = num; + console.log('set source to ' + p5sound.inputSources[self.currentSource].id); + } else { + console.log('unable to set input source'); + } + }; + // private method + p5.AudioIn.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.stop(); + if (this.output) { + this.output.disconnect(); + } + if (this.amplitude) { + this.amplitude.disconnect(); + } + this.amplitude = null; + this.output = null; + }; +}(master, errorHandler); +var filter; +filter = function () { + 'use strict'; + var p5sound = master; + /** + * A p5.Filter uses a Web Audio Biquad Filter to filter + * the frequency response of an input source. Inheriting + * classes include:<br/> + * * <code>p5.LowPass</code> - allows frequencies below + * the cutoff frequency to pass through, and attenuates + * frequencies above the cutoff.<br/> + * * <code>p5.HighPass</code> - the opposite of a lowpass + * filter. <br/> + * * <code>p5.BandPass</code> - allows a range of + * frequencies to pass through and attenuates the frequencies + * below and above this frequency range.<br/> + * + * The <code>.res()</code> method controls either width of the + * bandpass, or resonance of the low/highpass cutoff frequency. + * + * @class p5.Filter + * @constructor + * @param {[String]} type 'lowpass' (default), 'highpass', 'bandpass' + * @return {Object} p5.Filter + * @example + * <div><code> + * var fft, noise, filter; + * + * function setup() { + * fill(255, 40, 255); + * + * filter = new p5.BandPass(); + * + * noise = new p5.Noise(); + * // disconnect unfiltered noise, + * // and connect to filter + * noise.disconnect(); + * noise.connect(filter); + * noise.start(); + * + * fft = new p5.FFT(); + * } + * + * function draw() { + * background(30); + * + * // set the BandPass frequency based on mouseX + * var freq = map(mouseX, 0, width, 20, 10000); + * filter.freq(freq); + * // give the filter a narrow band (lower res = wider bandpass) + * filter.res(50); + * + * // draw filtered spectrum + * var spectrum = fft.analyze(); + * noStroke(); + * for (var i = 0; i < spectrum.length; i++) { + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width/spectrum.length, h); + * } + * + * isMouseOverCanvas(); + * } + * + * function isMouseOverCanvas() { + * var mX = mouseX, mY = mouseY; + * if (mX > 0 && mX < width && mY < height && mY > 0) { + * noise.amp(0.5, 0.2); + * } else { + * noise.amp(0, 0.2); + * } + * } + * </code></div> + */ + p5.Filter = function (type) { + this.ac = p5sound.audiocontext; + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + /** + * The p5.Filter is built with a + * <a href="http://www.w3.org/TR/webaudio/#BiquadFilterNode"> + * Web Audio BiquadFilter Node</a>. + * + * @property biquadFilter + * @type {Object} Web Audio Delay Node + */ + this.biquad = this.ac.createBiquadFilter(); + this.input.connect(this.biquad); + this.biquad.connect(this.output); + this.connect(); + if (type) { + this.setType(type); + } + // add to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Filter an audio signal according to a set + * of filter parameters. + * + * @method process + * @param {Object} Signal An object that outputs audio + * @param {[Number]} freq Frequency in Hz, from 10 to 22050 + * @param {[Number]} res Resonance/Width of the filter frequency + * from 0.001 to 1000 + */ + p5.Filter.prototype.process = function (src, freq, res) { + src.connect(this.input); + this.set(freq, res); + }; + /** + * Set the frequency and the resonance of the filter. + * + * @method set + * @param {Number} freq Frequency in Hz, from 10 to 22050 + * @param {Number} res Resonance (Q) from 0.001 to 1000 + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Filter.prototype.set = function (freq, res, time) { + if (freq) { + this.freq(freq, time); + } + if (res) { + this.res(res, time); + } + }; + /** + * Set the filter frequency, in Hz, from 10 to 22050 (the range of + * human hearing, although in reality most people hear in a narrower + * range). + * + * @method freq + * @param {Number} freq Filter Frequency + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {Number} value Returns the current frequency value + */ + p5.Filter.prototype.freq = function (freq, time) { + var self = this; + var t = time || 0; + if (freq <= 0) { + freq = 1; + } + if (typeof freq === 'number') { + self.biquad.frequency.value = freq; + self.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + self.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t); + } else if (freq) { + freq.connect(this.biquad.frequency); + } + return self.biquad.frequency.value; + }; + /** + * Controls either width of a bandpass frequency, + * or the resonance of a low/highpass cutoff frequency. + * + * @method res + * @param {Number} res Resonance/Width of filter freq + * from 0.001 to 1000 + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {Number} value Returns the current res value + */ + p5.Filter.prototype.res = function (res, time) { + var self = this; + var t = time || 0; + if (typeof res == 'number') { + self.biquad.Q.value = res; + self.biquad.Q.cancelScheduledValues(self.ac.currentTime + 0.01 + t); + self.biquad.Q.linearRampToValueAtTime(res, self.ac.currentTime + 0.02 + t); + } else if (res) { + freq.connect(this.biquad.Q); + } + return self.biquad.Q.value; + }; + /** + * Set the type of a p5.Filter. Possible types include: + * "lowpass" (default), "highpass", "bandpass", + * "lowshelf", "highshelf", "peaking", "notch", + * "allpass". + * + * @method setType + * @param {String} + */ + p5.Filter.prototype.setType = function (t) { + this.biquad.type = t; + }; + /** + * Set the output level of the filter. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Filter.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); + }; + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + p5.Filter.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Filter.prototype.disconnect = function () { + this.output.disconnect(); + }; + p5.Filter.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.input.disconnect(); + this.input = undefined; + this.output.disconnect(); + this.output = undefined; + this.biquad.disconnect(); + this.biquad = undefined; + }; + /** + * Constructor: <code>new p5.LowPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('lowpass')</code>. + * See p5.Filter for methods. + * + * @method p5.LowPass + */ + p5.LowPass = function () { + p5.Filter.call(this, 'lowpass'); + }; + p5.LowPass.prototype = Object.create(p5.Filter.prototype); + /** + * Constructor: <code>new p5.HighPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('highpass')</code>. + * See p5.Filter for methods. + * + * @method p5.HighPass + */ + p5.HighPass = function () { + p5.Filter.call(this, 'highpass'); + }; + p5.HighPass.prototype = Object.create(p5.Filter.prototype); + /** + * Constructor: <code>new p5.BandPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('bandpass')</code>. + * See p5.Filter for methods. + * + * @method p5.BandPass + */ + p5.BandPass = function () { + p5.Filter.call(this, 'bandpass'); + }; + p5.BandPass.prototype = Object.create(p5.Filter.prototype); +}(master); +var delay; +delay = function () { + 'use strict'; + var p5sound = master; + var Filter = filter; + /** + * Delay is an echo effect. It processes an existing sound source, + * and outputs a delayed version of that sound. The p5.Delay can + * produce different effects depending on the delayTime, feedback, + * filter, and type. In the example below, a feedback of 0.5 will + * produce a looping delay that decreases in volume by + * 50% each repeat. A filter will cut out the high frequencies so + * that the delay does not sound as piercing as the original source. + * + * @class p5.Delay + * @constructor + * @return {Object} Returns a p5.Delay object + * @example + * <div><code> + * var noise, env, delay; + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * noise = new p5.Noise('brown'); + * noise.amp(0); + * noise.start(); + * + * delay = new p5.Delay(); + * + * // delay.process() accepts 4 parameters: + * // source, delayTime, feedback, filter frequency + * // play with these numbers!! + * delay.process(noise, .12, .7, 2300); + * + * // play the noise with an envelope, + * // a series of fades ( time / value pairs ) + * env = new p5.Env(.01, 0.2, .2, .1); + * } + * + * // mouseClick triggers envelope + * function mouseClicked() { + * // is mouse over canvas? + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * env.play(noise); + * } + * } + * </code></div> + */ + p5.Delay = function () { + this.ac = p5sound.audiocontext; + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + this._split = this.ac.createChannelSplitter(2); + this._merge = this.ac.createChannelMerger(2); + this._leftGain = this.ac.createGain(); + this._rightGain = this.ac.createGain(); + /** + * The p5.Delay is built with two + * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> + * Web Audio Delay Nodes</a>, one for each stereo channel. + * + * @property leftDelay + * @type {Object} Web Audio Delay Node + */ + this.leftDelay = this.ac.createDelay(); + /** + * The p5.Delay is built with two + * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> + * Web Audio Delay Nodes</a>, one for each stereo channel. + * + * @property rightDelay + * @type {Object} Web Audio Delay Node + */ + this.rightDelay = this.ac.createDelay(); + this._leftFilter = new p5.Filter(); + this._rightFilter = new p5.Filter(); + this._leftFilter.disconnect(); + this._rightFilter.disconnect(); + this._leftFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); + this._rightFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); + this._leftFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); + this._rightFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); + // graph routing + this.input.connect(this._split); + this.leftDelay.connect(this._leftGain); + this.rightDelay.connect(this._rightGain); + this._leftGain.connect(this._leftFilter.input); + this._rightGain.connect(this._rightFilter.input); + this._merge.connect(this.output); + this.output.connect(p5.soundOut.input); + this._leftFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); + this._rightFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); + // default routing + this.setType(0); + this._maxDelay = this.leftDelay.delayTime.maxValue; + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Add delay to an audio signal according to a set + * of delay parameters. + * + * @method process + * @param {Object} Signal An object that outputs audio + * @param {Number} [delayTime] Time (in seconds) of the delay/echo. + * Some browsers limit delayTime to + * 1 second. + * @param {Number} [feedback] sends the delay back through itself + * in a loop that decreases in volume + * each time. + * @param {Number} [lowPass] Cutoff frequency. Only frequencies + * below the lowPass will be part of the + * delay. + */ + p5.Delay.prototype.process = function (src, _delayTime, _feedback, _filter) { + var feedback = _feedback || 0; + var delayTime = _delayTime || 0; + if (feedback >= 1) { + throw new Error('Feedback value will force a positive feedback loop.'); + } + if (delayTime >= this._maxDelay) { + throw new Error('Delay Time exceeds maximum delay time of ' + this._maxDelay + ' second.'); + } + src.connect(this.input); + this.leftDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); + this.rightDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); + this._leftGain.gain.setValueAtTime(feedback, this.ac.currentTime); + this._rightGain.gain.setValueAtTime(feedback, this.ac.currentTime); + if (_filter) { + this._leftFilter.freq(_filter); + this._rightFilter.freq(_filter); + } + }; + /** + * Set the delay (echo) time, in seconds. Usually this value will be + * a floating point number between 0.0 and 1.0. + * + * @method delayTime + * @param {Number} delayTime Time (in seconds) of the delay + */ + p5.Delay.prototype.delayTime = function (t) { + // if t is an audio node... + if (typeof t !== 'number') { + t.connect(this.leftDelay.delayTime); + t.connect(this.rightDelay.delayTime); + } else { + this.leftDelay.delayTime.cancelScheduledValues(this.ac.currentTime); + this.rightDelay.delayTime.cancelScheduledValues(this.ac.currentTime); + this.leftDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); + this.rightDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); + } + }; + /** + * Feedback occurs when Delay sends its signal back through its input + * in a loop. The feedback amount determines how much signal to send each + * time through the loop. A feedback greater than 1.0 is not desirable because + * it will increase the overall output each time through the loop, + * creating an infinite feedback loop. + * + * @method feedback + * @param {Number|Object} feedback 0.0 to 1.0, or an object such as an + * Oscillator that can be used to + * modulate this param + */ + p5.Delay.prototype.feedback = function (f) { + // if f is an audio node... + if (typeof f !== 'number') { + f.connect(this._leftGain.gain); + f.connect(this._rightGain.gain); + } else if (f >= 1) { + throw new Error('Feedback value will force a positive feedback loop.'); + } else { + this._leftGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); + this._rightGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); + } + }; + /** + * Set a lowpass filter frequency for the delay. A lowpass filter + * will cut off any frequencies higher than the filter frequency. + * + * @method filter + * @param {Number|Object} cutoffFreq A lowpass filter will cut off any + * frequencies higher than the filter frequency. + * @param {Number|Object} res Resonance of the filter frequency + * cutoff, or an object (i.e. a p5.Oscillator) + * that can be used to modulate this parameter. + * High numbers (i.e. 15) will produce a resonance, + * low numbers (i.e. .2) will produce a slope. + */ + p5.Delay.prototype.filter = function (freq, q) { + this._leftFilter.set(freq, q); + this._rightFilter.set(freq, q); + }; + /** + * Choose a preset type of delay. 'pingPong' bounces the signal + * from the left to the right channel to produce a stereo effect. + * Any other parameter will revert to the default delay setting. + * + * @method setType + * @param {String|Number} type 'pingPong' (1) or 'default' (0) + */ + p5.Delay.prototype.setType = function (t) { + if (t === 1) { + t = 'pingPong'; + } + this._split.disconnect(); + this._leftFilter.disconnect(); + this._rightFilter.disconnect(); + this._split.connect(this.leftDelay, 0); + this._split.connect(this.rightDelay, 1); + switch (t) { + case 'pingPong': + this._rightFilter.setType(this._leftFilter.biquad.type); + this._leftFilter.output.connect(this._merge, 0, 0); + this._rightFilter.output.connect(this._merge, 0, 1); + this._leftFilter.output.connect(this.rightDelay); + this._rightFilter.output.connect(this.leftDelay); + break; + default: + this._leftFilter.output.connect(this._merge, 0, 0); + this._leftFilter.output.connect(this._merge, 0, 1); + this._leftFilter.output.connect(this.leftDelay); + this._leftFilter.output.connect(this.rightDelay); + } + }; + /** + * Set the output level of the delay effect. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Delay.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); + }; + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + p5.Delay.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Delay.prototype.disconnect = function () { + this.output.disconnect(); + }; + p5.Delay.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.input.disconnect(); + this.output.disconnect(); + this._split.disconnect(); + this._leftFilter.disconnect(); + this._rightFilter.disconnect(); + this._merge.disconnect(); + this._leftGain.disconnect(); + this._rightGain.disconnect(); + this.leftDelay.disconnect(); + this.rightDelay.disconnect(); + this.input = undefined; + this.output = undefined; + this._split = undefined; + this._leftFilter = undefined; + this._rightFilter = undefined; + this._merge = undefined; + this._leftGain = undefined; + this._rightGain = undefined; + this.leftDelay = undefined; + this.rightDelay = undefined; + }; +}(master, filter); +var reverb; +reverb = function () { + 'use strict'; + var p5sound = master; + var CustomError = errorHandler; + /** + * Reverb adds depth to a sound through a large number of decaying + * echoes. It creates the perception that sound is occurring in a + * physical space. The p5.Reverb has paramters for Time (how long does the + * reverb last) and decayRate (how much the sound decays with each echo) + * that can be set with the .set() or .process() methods. The p5.Convolver + * extends p5.Reverb allowing you to recreate the sound of actual physical + * spaces through convolution. + * + * @class p5.Reverb + * @constructor + * @example + * <div><code> + * var soundFile, reverb; + * function preload() { + * soundFile = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * reverb = new p5.Reverb(); + * soundFile.disconnect(); // so we'll only hear reverb... + * + * // connect soundFile to reverb, process w/ + * // 3 second reverbTime, decayRate of 2% + * reverb.process(soundFile, 3, 2); + * soundFile.play(); + * } + * </code></div> + */ + p5.Reverb = function () { + this.ac = p5sound.audiocontext; + this.convolverNode = this.ac.createConvolver(); + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + this.input.connect(this.convolverNode); + this.convolverNode.connect(this.output); + // default params + this._seconds = 3; + this._decay = 2; + this._reverse = false; + this._buildImpulse(); + this.connect(); + p5sound.soundArray.push(this); + }; + /** + * Connect a source to the reverb, and assign reverb parameters. + * + * @method process + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + * @param {Number} [seconds] Duration of the reverb, in seconds. + * Min: 0, Max: 10. Defaults to 3. + * @param {Number} [decayRate] Percentage of decay with each echo. + * Min: 0, Max: 100. Defaults to 2. + * @param {Boolean} [reverse] Play the reverb backwards or forwards. + */ + p5.Reverb.prototype.process = function (src, seconds, decayRate, reverse) { + src.connect(this.input); + var rebuild = false; + if (seconds) { + this._seconds = seconds; + rebuild = true; + } + if (decayRate) { + this._decay = decayRate; + } + if (reverse) { + this._reverse = reverse; + } + if (rebuild) { + this._buildImpulse(); + } + }; + /** + * Set the reverb settings. Similar to .process(), but without + * assigning a new input. + * + * @method set + * @param {Number} [seconds] Duration of the reverb, in seconds. + * Min: 0, Max: 10. Defaults to 3. + * @param {Number} [decayRate] Percentage of decay with each echo. + * Min: 0, Max: 100. Defaults to 2. + * @param {Boolean} [reverse] Play the reverb backwards or forwards. + */ + p5.Reverb.prototype.set = function (seconds, decayRate, reverse) { + var rebuild = false; + if (seconds) { + this._seconds = seconds; + rebuild = true; + } + if (decayRate) { + this._decay = decayRate; + } + if (reverse) { + this._reverse = reverse; + } + if (rebuild) { + this._buildImpulse(); + } + }; + /** + * Set the output level of the delay effect. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Reverb.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); + }; + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + p5.Reverb.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Reverb.prototype.disconnect = function () { + this.output.disconnect(); + }; + /** + * Inspired by Simple Reverb by Jordan Santell + * https://github.com/web-audio-components/simple-reverb/blob/master/index.js + * + * Utility function for building an impulse response + * based on the module parameters. + * + * @private + */ + p5.Reverb.prototype._buildImpulse = function () { + var rate = this.ac.sampleRate; + var length = rate * this._seconds; + var decay = this._decay; + var impulse = this.ac.createBuffer(2, length, rate); + var impulseL = impulse.getChannelData(0); + var impulseR = impulse.getChannelData(1); + var n, i; + for (i = 0; i < length; i++) { + n = this.reverse ? length - i : i; + impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); + impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); + } + this.convolverNode.buffer = impulse; + }; + p5.Reverb.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.convolverNode) { + this.convolverNode.buffer = null; + this.convolverNode = null; + } + if (typeof this.output !== 'undefined') { + this.output.disconnect(); + this.output = null; + } + if (typeof this.panner !== 'undefined') { + this.panner.disconnect(); + this.panner = null; + } + }; + // ======================================================================= + // *** p5.Convolver *** + // ======================================================================= + /** + * <p>p5.Convolver extends p5.Reverb. It can emulate the sound of real + * physical spaces through a process called <a href=" + * https://en.wikipedia.org/wiki/Convolution_reverb#Real_space_simulation"> + * convolution</a>.</p> + * + * <p>Convolution multiplies any audio input by an "impulse response" + * to simulate the dispersion of sound over time. The impulse response is + * generated from an audio file that you provide. One way to + * generate an impulse response is to pop a balloon in a reverberant space + * and record the echo. Convolution can also be used to experiment with + * sound.</p> + * + * <p>Use the method <code>createConvolution(path)</code> to instantiate a + * p5.Convolver with a path to your impulse response audio file.</p> + * + * @class p5.Convolver + * @constructor + * @param {String} path path to a sound file + * @param {Function} [callback] function to call when loading succeeds + * @param {Function} [errorCallback] function to call if loading fails. + * This function will receive an error or + * XMLHttpRequest object with information + * about what went wrong. + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * // We have both MP3 and OGG versions of all sound assets + * soundFormats('ogg', 'mp3'); + * + * // Try replacing 'bx-spring' with other soundfiles like + * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' + * cVerb = createConvolver('assets/bx-spring.mp3'); + * + * // Try replacing 'Damscray_DancingTiger' with + * // 'beat', 'doorbell', lucky_dragons_-_power_melody' + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.Convolver = function (path, callback, errorCallback) { + this.ac = p5sound.audiocontext; + /** + * Internally, the p5.Convolver uses the a + * <a href="http://www.w3.org/TR/webaudio/#ConvolverNode"> + * Web Audio Convolver Node</a>. + * + * @property convolverNode + * @type {Object} Web Audio Convolver Node + */ + this.convolverNode = this.ac.createConvolver(); + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + this.input.connect(this.convolverNode); + this.convolverNode.connect(this.output); + if (path) { + this.impulses = []; + this._loadBuffer(path, callback, errorCallback); + } else { + // parameters + this._seconds = 3; + this._decay = 2; + this._reverse = false; + this._buildImpulse(); + } + this.connect(); + p5sound.soundArray.push(this); + }; + p5.Convolver.prototype = Object.create(p5.Reverb.prototype); + p5.prototype.registerPreloadMethod('createConvolver', p5.prototype); + /** + * Create a p5.Convolver. Accepts a path to a soundfile + * that will be used to generate an impulse response. + * + * @method createConvolver + * @param {String} path path to a sound file + * @param {Function} [callback] function to call if loading is successful. + * The object will be passed in as the argument + * to the callback function. + * @param {Function} [errorCallback] function to call if loading is not successful. + * A custom error will be passed in as the argument + * to the callback function. + * @return {p5.Convolver} + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * // We have both MP3 and OGG versions of all sound assets + * soundFormats('ogg', 'mp3'); + * + * // Try replacing 'bx-spring' with other soundfiles like + * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' + * cVerb = createConvolver('assets/bx-spring.mp3'); + * + * // Try replacing 'Damscray_DancingTiger' with + * // 'beat', 'doorbell', lucky_dragons_-_power_melody' + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.prototype.createConvolver = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + var cReverb = new p5.Convolver(path, callback, errorCallback); + cReverb.impulses = []; + return cReverb; + }; + /** + * Private method to load a buffer as an Impulse Response, + * assign it to the convolverNode, and add to the Array of .impulses. + * + * @param {String} path + * @param {Function} callback + * @param {Function} errorCallback + * @private + */ + p5.Convolver.prototype._loadBuffer = function (path, callback, errorCallback) { + var path = p5.prototype._checkFileFormats(path); + var self = this; + var errorTrace = new Error().stack; + var ac = p5.prototype.getAudioContext(); + var request = new XMLHttpRequest(); + request.open('GET', path, true); + request.responseType = 'arraybuffer'; + request.onload = function () { + if (request.status == 200) { + // on success loading file: + ac.decodeAudioData(request.response, function (buff) { + var buffer = {}; + var chunks = path.split('/'); + buffer.name = chunks[chunks.length - 1]; + buffer.audioBuffer = buff; + self.impulses.push(buffer); + self.convolverNode.buffer = buffer.audioBuffer; + if (callback) { + callback(buffer); + } + }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 + function (e) { + var err = new CustomError('decodeAudioData', errorTrace, self.url); + var msg = 'AudioContext error at decodeAudioData for ' + self.url; + if (errorCallback) { + err.msg = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }); + } else { + var err = new CustomError('loadConvolver', errorTrace, self.url); + var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + } + }; + // if there is another error, aside from 404... + request.onerror = function (e) { + var err = new CustomError('loadConvolver', errorTrace, self.url); + var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }; + request.send(); + }; + p5.Convolver.prototype.set = null; + /** + * Connect a source to the reverb, and assign reverb parameters. + * + * @method process + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * soundFormats('ogg', 'mp3'); + * + * cVerb = createConvolver('assets/concrete-tunnel.mp3'); + * + * sound = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with (i.e. connect to) cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.Convolver.prototype.process = function (src) { + src.connect(this.input); + }; + /** + * If you load multiple impulse files using the .addImpulse method, + * they will be stored as Objects in this Array. Toggle between them + * with the <code>toggleImpulse(id)</code> method. + * + * @property impulses + * @type {Array} Array of Web Audio Buffers + */ + p5.Convolver.prototype.impulses = []; + /** + * Load and assign a new Impulse Response to the p5.Convolver. + * The impulse is added to the <code>.impulses</code> array. Previous + * impulses can be accessed with the <code>.toggleImpulse(id)</code> + * method. + * + * @method addImpulse + * @param {String} path path to a sound file + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) + */ + p5.Convolver.prototype.addImpulse = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + this._loadBuffer(path, callback, errorCallback); + }; + /** + * Similar to .addImpulse, except that the <code>.impulses</code> + * Array is reset to save memory. A new <code>.impulses</code> + * array is created with this impulse as the only item. + * + * @method resetImpulse + * @param {String} path path to a sound file + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) + */ + p5.Convolver.prototype.resetImpulse = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + this.impulses = []; + this._loadBuffer(path, callback, errorCallback); + }; + /** + * If you have used <code>.addImpulse()</code> to add multiple impulses + * to a p5.Convolver, then you can use this method to toggle between + * the items in the <code>.impulses</code> Array. Accepts a parameter + * to identify which impulse you wish to use, identified either by its + * original filename (String) or by its position in the <code>.impulses + * </code> Array (Number).<br/> + * You can access the objects in the .impulses Array directly. Each + * Object has two attributes: an <code>.audioBuffer</code> (type: + * Web Audio <a href=" + * http://webaudio.github.io/web-audio-api/#the-audiobuffer-interface"> + * AudioBuffer)</a> and a <code>.name</code>, a String that corresponds + * with the original filename. + * + * @method toggleImpulse + * @param {String|Number} id Identify the impulse by its original filename + * (String), or by its position in the + * <code>.impulses</code> Array (Number). + */ + p5.Convolver.prototype.toggleImpulse = function (id) { + if (typeof id === 'number' && id < this.impulses.length) { + this.convolverNode.buffer = this.impulses[id].audioBuffer; + } + if (typeof id === 'string') { + for (var i = 0; i < this.impulses.length; i++) { + if (this.impulses[i].name === id) { + this.convolverNode.buffer = this.impulses[i].audioBuffer; + break; + } + } + } + }; + p5.Convolver.prototype.dispose = function () { + // remove all the Impulse Response buffers + for (var i in this.impulses) { + this.impulses[i] = null; + } + this.convolverNode.disconnect(); + this.concolverNode = null; + if (typeof this.output !== 'undefined') { + this.output.disconnect(); + this.output = null; + } + if (typeof this.panner !== 'undefined') { + this.panner.disconnect(); + this.panner = null; + } + }; +}(master, errorHandler, sndcore); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_TimelineState; +Tone_core_TimelineState = function (Tone) { + 'use strict'; + Tone.TimelineState = function (initial) { + Tone.Timeline.call(this); + this._initial = initial; + }; + Tone.extend(Tone.TimelineState, Tone.Timeline); + Tone.TimelineState.prototype.getStateAtTime = function (time) { + var event = this.getEvent(time); + if (event !== null) { + return event.state; + } else { + return this._initial; + } + }; + Tone.TimelineState.prototype.setStateAtTime = function (state, time) { + this.addEvent({ + 'state': state, + 'time': this.toSeconds(time) + }); + }; + return Tone.TimelineState; +}(Tone_core_Tone, Tone_core_Timeline); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Clock; +Tone_core_Clock = function (Tone) { + 'use strict'; + Tone.Clock = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'frequency' + ], Tone.Clock.defaults); + this.callback = options.callback; + this._lookAhead = 'auto'; + this._computedLookAhead = 1 / 60; + this._threshold = 0.5; + this._nextTick = -1; + this._lastUpdate = 0; + this._loopID = -1; + this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency); + this.ticks = 0; + this._state = new Tone.TimelineState(Tone.State.Stopped); + this._boundLoop = this._loop.bind(this); + this._readOnly('frequency'); + this._loop(); + }; + Tone.extend(Tone.Clock); + Tone.Clock.defaults = { + 'callback': Tone.noOp, + 'frequency': 1, + 'lookAhead': 'auto' + }; + Object.defineProperty(Tone.Clock.prototype, 'state', { + get: function () { + return this._state.getStateAtTime(this.now()); + } + }); + Object.defineProperty(Tone.Clock.prototype, 'lookAhead', { + get: function () { + return this._lookAhead; + }, + set: function (val) { + if (val === 'auto') { + this._lookAhead = 'auto'; + } else { + this._lookAhead = this.toSeconds(val); + } + } + }); + Tone.Clock.prototype.start = function (time, offset) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Started) { + this._state.addEvent({ + 'state': Tone.State.Started, + 'time': time, + 'offset': offset + }); + } + return this; + }; + Tone.Clock.prototype.stop = function (time) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Stopped) { + this._state.setStateAtTime(Tone.State.Stopped, time); + } + return this; + }; + Tone.Clock.prototype.pause = function (time) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) === Tone.State.Started) { + this._state.setStateAtTime(Tone.State.Paused, time); + } + return this; + }; + Tone.Clock.prototype._loop = function (time) { + this._loopID = requestAnimationFrame(this._boundLoop); + if (this._lookAhead === 'auto') { + if (!this.isUndef(time)) { + var diff = (time - this._lastUpdate) / 1000; + this._lastUpdate = time; + if (diff < this._threshold) { + this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; + } + } + } else { + this._computedLookAhead = this._lookAhead; + } + var now = this.now(); + var lookAhead = this._computedLookAhead * 2; + var event = this._state.getEvent(now + lookAhead); + var state = Tone.State.Stopped; + if (event) { + state = event.state; + if (this._nextTick === -1 && state === Tone.State.Started) { + this._nextTick = event.time; + if (!this.isUndef(event.offset)) { + this.ticks = event.offset; + } + } + } + if (state === Tone.State.Started) { + while (now + lookAhead > this._nextTick) { + if (now > this._nextTick + this._threshold) { + this._nextTick = now; + } + var tickTime = this._nextTick; + this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick); + this.callback(tickTime); + this.ticks++; + } + } else if (state === Tone.State.Stopped) { + this._nextTick = -1; + this.ticks = 0; + } + }; + Tone.Clock.prototype.getStateAtTime = function (time) { + return this._state.getStateAtTime(time); + }; + Tone.Clock.prototype.dispose = function () { + cancelAnimationFrame(this._loopID); + Tone.TimelineState.prototype.dispose.call(this); + this._writable('frequency'); + this.frequency.dispose(); + this.frequency = null; + this._boundLoop = Tone.noOp; + this._nextTick = Infinity; + this.callback = null; + this._state.dispose(); + this._state = null; + }; + return Tone.Clock; +}(Tone_core_Tone, Tone_signal_TimelineSignal); +var metro; +metro = function () { + 'use strict'; + var p5sound = master; + // requires the Tone.js library's Clock (MIT license, Yotam Mann) + // https://github.com/TONEnoTONE/Tone.js/ + var Clock = Tone_core_Clock; + var ac = p5sound.audiocontext; + // var upTick = false; + p5.Metro = function () { + this.clock = new Clock({ 'callback': this.ontick.bind(this) }); + this.syncedParts = []; + this.bpm = 120; + // gets overridden by p5.Part + this._init(); + this.tickCallback = function () { + }; + }; + var prevTick = 0; + var tatumTime = 0; + p5.Metro.prototype.ontick = function (tickTime) { + var elapsedTime = tickTime - prevTick; + var secondsFromNow = tickTime - p5sound.audiocontext.currentTime; + if (elapsedTime - tatumTime <= -0.02) { + return; + } else { + prevTick = tickTime; + // for all of the active things on the metro: + for (var i in this.syncedParts) { + var thisPart = this.syncedParts[i]; + thisPart.incrementStep(secondsFromNow); + // each synced source keeps track of its own beat number + for (var j in thisPart.phrases) { + var thisPhrase = thisPart.phrases[j]; + var phraseArray = thisPhrase.sequence; + var bNum = this.metroTicks % phraseArray.length; + if (phraseArray[bNum] !== 0 && (this.metroTicks < phraseArray.length || !thisPhrase.looping)) { + thisPhrase.callback(secondsFromNow, phraseArray[bNum]); + } + } + } + this.metroTicks += 1; + this.tickCallback(secondsFromNow); + } + }; + p5.Metro.prototype.setBPM = function (bpm, rampTime) { + var beatTime = 60 / (bpm * this.tatums); + var now = p5sound.audiocontext.currentTime; + tatumTime = beatTime; + var rampTime = rampTime || 0; + this.clock.frequency.setValueAtTime(this.clock.frequency.value, now); + this.clock.frequency.linearRampToValueAtTime(bpm, now + rampTime); + this.bpm = bpm; + }; + p5.Metro.prototype.getBPM = function (tempo) { + return this.clock.getRate() / this.tatums * 60; + }; + p5.Metro.prototype._init = function () { + this.metroTicks = 0; + }; + // clear existing synced parts, add only this one + p5.Metro.prototype.resetSync = function (part) { + this.syncedParts = [part]; + }; + // push a new synced part to the array + p5.Metro.prototype.pushSync = function (part) { + this.syncedParts.push(part); + }; + p5.Metro.prototype.start = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + this.clock.start(now + t); + this.setBPM(this.bpm); + }; + p5.Metro.prototype.stop = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + if (this.clock._oscillator) { + this.clock.stop(now + t); + } + }; + p5.Metro.prototype.beatLength = function (tatums) { + this.tatums = 1 / tatums / 4; + }; +}(master, Tone_core_Clock); +var looper; +looper = function () { + 'use strict'; + var p5sound = master; + var bpm = 120; + /** + * Set the global tempo, in beats per minute, for all + * p5.Parts. This method will impact all active p5.Parts. + * + * @param {Number} BPM Beats Per Minute + * @param {Number} rampTime Seconds from now + */ + p5.prototype.setBPM = function (BPM, rampTime) { + bpm = BPM; + for (var i in p5sound.parts) { + p5sound.parts[i].setBPM(bpm, rampTime); + } + }; + /** + * <p>A phrase is a pattern of musical events over time, i.e. + * a series of notes and rests.</p> + * + * <p>Phrases must be added to a p5.Part for playback, and + * each part can play multiple phrases at the same time. + * For example, one Phrase might be a kick drum, another + * could be a snare, and another could be the bassline.</p> + * + * <p>The first parameter is a name so that the phrase can be + * modified or deleted later. The callback is a a function that + * this phrase will call at every step—for example it might be + * called <code>playNote(value){}</code>. The array determines + * which value is passed into the callback at each step of the + * phrase. It can be numbers, an object with multiple numbers, + * or a zero (0) indicates a rest so the callback won't be called).</p> + * + * @class p5.Phrase + * @constructor + * @param {String} name Name so that you can access the Phrase. + * @param {Function} callback The name of a function that this phrase + * will call. Typically it will play a sound, + * and accept two parameters: a time at which + * to play the sound (in seconds from now), + * and a value from the sequence array. The + * time should be passed into the play() or + * start() method to ensure precision. + * @param {Array} sequence Array of values to pass into the callback + * at each step of the phrase. + * @example + * <div><code> + * var mySound, myPhrase, myPart; + * var pattern = [1,0,0,2,0,2,0,0]; + * var msg = 'click to play'; + * + * function preload() { + * mySound = loadSound('assets/beatbox.mp3'); + * } + * + * function setup() { + * noStroke(); + * fill(255); + * textAlign(CENTER); + * masterVolume(0.1); + * + * myPhrase = new p5.Phrase('bbox', makeSound, pattern); + * myPart = new p5.Part(); + * myPart.addPhrase(myPhrase); + * myPart.setBPM(60); + * } + * + * function draw() { + * background(0); + * text(msg, width/2, height/2); + * } + * + * function makeSound(time, playbackRate) { + * mySound.rate(playbackRate); + * mySound.play(time); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * myPart.start(); + * msg = 'playing pattern'; + * } + * } + * + * </code></div> + */ + p5.Phrase = function (name, callback, sequence) { + this.phraseStep = 0; + this.name = name; + this.callback = callback; + /** + * Array of values to pass into the callback + * at each step of the phrase. Depending on the callback + * function's requirements, these values may be numbers, + * strings, or an object with multiple parameters. + * Zero (0) indicates a rest. + * + * @property sequence + * @type {Array} + */ + this.sequence = sequence; + }; + /** + * <p>A p5.Part plays back one or more p5.Phrases. Instantiate a part + * with steps and tatums. By default, each step represents 1/16th note.</p> + * + * <p>See p5.Phrase for more about musical timing.</p> + * + * @class p5.Part + * @constructor + * @param {Number} [steps] Steps in the part + * @param {Number} [tatums] Divisions of a beat (default is 1/16, a quarter note) + * @example + * <div><code> + * var box, drum, myPart; + * var boxPat = [1,0,0,2,0,2,0,0]; + * var drumPat = [0,1,1,0,2,0,1,0]; + * var msg = 'click to play'; + * + * function preload() { + * box = loadSound('assets/beatbox.mp3'); + * drum = loadSound('assets/drum.mp3'); + * } + * + * function setup() { + * noStroke(); + * fill(255); + * textAlign(CENTER); + * masterVolume(0.1); + * + * var boxPhrase = new p5.Phrase('box', playBox, boxPat); + * var drumPhrase = new p5.Phrase('drum', playDrum, drumPat); + * myPart = new p5.Part(); + * myPart.addPhrase(boxPhrase); + * myPart.addPhrase(drumPhrase); + * myPart.setBPM(60); + * masterVolume(0.1); + * } + * + * function draw() { + * background(0); + * text(msg, width/2, height/2); + * } + * + * function playBox(time, playbackRate) { + * box.rate(playbackRate); + * box.play(time); + * } + * + * function playDrum(time, playbackRate) { + * drum.rate(playbackRate); + * drum.play(time); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * myPart.start(); + * msg = 'playing part'; + * } + * } + * </code></div> + */ + p5.Part = function (steps, bLength) { + this.length = steps || 0; + // how many beats + this.partStep = 0; + this.phrases = []; + this.looping = false; + this.isPlaying = false; + // what does this looper do when it gets to the last step? + this.onended = function () { + this.stop(); + }; + this.tatums = bLength || 0.0625; + // defaults to quarter note + this.metro = new p5.Metro(); + this.metro._init(); + this.metro.beatLength(this.tatums); + this.metro.setBPM(bpm); + p5sound.parts.push(this); + this.callback = function () { + }; + }; + /** + * Set the tempo of this part, in Beats Per Minute. + * + * @method setBPM + * @param {Number} BPM Beats Per Minute + * @param {Number} [rampTime] Seconds from now + */ + p5.Part.prototype.setBPM = function (tempo, rampTime) { + this.metro.setBPM(tempo, rampTime); + }; + /** + * Returns the Beats Per Minute of this currently part. + * + * @method getBPM + * @return {Number} + */ + p5.Part.prototype.getBPM = function () { + return this.metro.getBPM(); + }; + /** + * Start playback of this part. It will play + * through all of its phrases at a speed + * determined by setBPM. + * + * @method start + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.start = function (time) { + if (!this.isPlaying) { + this.isPlaying = true; + this.metro.resetSync(this); + var t = time || 0; + this.metro.start(t); + } + }; + /** + * Loop playback of this part. It will begin + * looping through all of its phrases at a speed + * determined by setBPM. + * + * @method loop + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.loop = function (time) { + this.looping = true; + // rest onended function + this.onended = function () { + this.partStep = 0; + }; + var t = time || 0; + this.start(t); + }; + /** + * Tell the part to stop looping. + * + * @method noLoop + */ + p5.Part.prototype.noLoop = function () { + this.looping = false; + // rest onended function + this.onended = function () { + this.stop(); + }; + }; + /** + * Stop the part and cue it to step 0. + * + * @method stop + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.stop = function (time) { + this.partStep = 0; + this.pause(time); + }; + /** + * Pause the part. Playback will resume + * from the current step. + * + * @method pause + * @param {Number} time seconds from now + */ + p5.Part.prototype.pause = function (time) { + this.isPlaying = false; + var t = time || 0; + this.metro.stop(t); + }; + /** + * Add a p5.Phrase to this Part. + * + * @method addPhrase + * @param {p5.Phrase} phrase reference to a p5.Phrase + */ + p5.Part.prototype.addPhrase = function (name, callback, array) { + var p; + if (arguments.length === 3) { + p = new p5.Phrase(name, callback, array); + } else if (arguments[0] instanceof p5.Phrase) { + p = arguments[0]; + } else { + throw 'invalid input. addPhrase accepts name, callback, array or a p5.Phrase'; + } + this.phrases.push(p); + // reset the length if phrase is longer than part's existing length + if (p.sequence.length > this.length) { + this.length = p.sequence.length; + } + }; + /** + * Remove a phrase from this part, based on the name it was + * given when it was created. + * + * @method removePhrase + * @param {String} phraseName + */ + p5.Part.prototype.removePhrase = function (name) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + this.phrases.split(i, 1); + } + } + }; + /** + * Get a phrase from this part, based on the name it was + * given when it was created. Now you can modify its array. + * + * @method getPhrase + * @param {String} phraseName + */ + p5.Part.prototype.getPhrase = function (name) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + return this.phrases[i]; + } + } + }; + /** + * Get a phrase from this part, based on the name it was + * given when it was created. Now you can modify its array. + * + * @method replaceSequence + * @param {String} phraseName + * @param {Array} sequence Array of values to pass into the callback + * at each step of the phrase. + */ + p5.Part.prototype.replaceSequence = function (name, array) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + this.phrases[i].sequence = array; + } + } + }; + p5.Part.prototype.incrementStep = function (time) { + if (this.partStep < this.length - 1) { + this.callback(time); + this.partStep += 1; + } else { + if (this.looping) { + this.callback(time); + } + this.onended(); + this.partStep = 0; + } + }; + /** + * Fire a callback function at every step. + * + * @method onStep + * @param {Function} callback The name of the callback + * you want to fire + * on every beat/tatum. + */ + p5.Part.prototype.onStep = function (callback) { + this.callback = callback; + }; + // =============== + // p5.Score + // =============== + /** + * A Score consists of a series of Parts. The parts will + * be played back in order. For example, you could have an + * A part, a B part, and a C part, and play them back in this order + * <code>new p5.Score(a, a, b, a, c)</code> + * + * @class p5.Score + * @constructor + * @param {p5.Part} part(s) One or multiple parts, to be played in sequence. + * @return {p5.Score} + */ + p5.Score = function () { + // for all of the arguments + this.parts = []; + this.currentPart = 0; + var thisScore = this; + for (var i in arguments) { + this.parts[i] = arguments[i]; + this.parts[i].nextPart = this.parts[i + 1]; + this.parts[i].onended = function () { + thisScore.resetPart(i); + playNextPart(thisScore); + }; + } + this.looping = false; + }; + p5.Score.prototype.onended = function () { + if (this.looping) { + // this.resetParts(); + this.parts[0].start(); + } else { + this.parts[this.parts.length - 1].onended = function () { + this.stop(); + this.resetParts(); + }; + } + this.currentPart = 0; + }; + /** + * Start playback of the score. + * + * @method start + */ + p5.Score.prototype.start = function () { + this.parts[this.currentPart].start(); + this.scoreStep = 0; + }; + /** + * Stop playback of the score. + * + * @method stop + */ + p5.Score.prototype.stop = function () { + this.parts[this.currentPart].stop(); + this.currentPart = 0; + this.scoreStep = 0; + }; + /** + * Pause playback of the score. + * + * @method pause + */ + p5.Score.prototype.pause = function () { + this.parts[this.currentPart].stop(); + }; + /** + * Loop playback of the score. + * + * @method loop + */ + p5.Score.prototype.loop = function () { + this.looping = true; + this.start(); + }; + /** + * Stop looping playback of the score. If it + * is currently playing, this will go into effect + * after the current round of playback completes. + * + * @method noLoop + */ + p5.Score.prototype.noLoop = function () { + this.looping = false; + }; + p5.Score.prototype.resetParts = function () { + for (var i in this.parts) { + this.resetPart(i); + } + }; + p5.Score.prototype.resetPart = function (i) { + this.parts[i].stop(); + this.parts[i].partStep = 0; + for (var p in this.parts[i].phrases) { + this.parts[i].phrases[p].phraseStep = 0; + } + }; + /** + * Set the tempo for all parts in the score + * + * @param {Number} BPM Beats Per Minute + * @param {Number} rampTime Seconds from now + */ + p5.Score.prototype.setBPM = function (bpm, rampTime) { + for (var i in this.parts) { + this.parts[i].setBPM(bpm, rampTime); + } + }; + function playNextPart(aScore) { + aScore.currentPart++; + if (aScore.currentPart >= aScore.parts.length) { + aScore.scoreStep = 0; + aScore.onended(); + } else { + aScore.scoreStep = 0; + aScore.parts[aScore.currentPart - 1].stop(); + aScore.parts[aScore.currentPart].start(); + } + } +}(master); +var soundRecorder; +soundRecorder = function () { + 'use strict'; + var p5sound = master; + var ac = p5sound.audiocontext; + /** + * <p>Record sounds for playback and/or to save as a .wav file. + * The p5.SoundRecorder records all sound output from your sketch, + * or can be assigned a specific source with setInput().</p> + * <p>The record() method accepts a p5.SoundFile as a parameter. + * When playback is stopped (either after the given amount of time, + * or with the stop() method), the p5.SoundRecorder will send its + * recording to that p5.SoundFile for playback.</p> + * + * @class p5.SoundRecorder + * @constructor + * @example + * <div><code> + * var mic, recorder, soundFile; + * var state = 0; + * + * function setup() { + * background(200); + * // create an audio in + * mic = new p5.AudioIn(); + * + * // prompts user to enable their browser mic + * mic.start(); + * + * // create a sound recorder + * recorder = new p5.SoundRecorder(); + * + * // connect the mic to the recorder + * recorder.setInput(mic); + * + * // this sound file will be used to + * // playback & save the recording + * soundFile = new p5.SoundFile(); + * + * text('keyPress to record', 20, 20); + * } + * + * function keyPressed() { + * // make sure user enabled the mic + * if (state === 0 && mic.enabled) { + * + * // record to our p5.SoundFile + * recorder.record(soundFile); + * + * background(255,0,0); + * text('Recording!', 20, 20); + * state++; + * } + * else if (state === 1) { + * background(0,255,0); + * + * // stop recorder and + * // send result to soundFile + * recorder.stop(); + * + * text('Stopped', 20, 20); + * state++; + * } + * + * else if (state === 2) { + * soundFile.play(); // play the result! + * save(soundFile, 'mySound.wav'); + * state++; + * } + * } + * </div></code> + */ + p5.SoundRecorder = function () { + this.input = ac.createGain(); + this.output = ac.createGain(); + this.recording = false; + this.bufferSize = 1024; + this._channels = 2; + // stereo (default) + this._clear(); + // initialize variables + this._jsNode = ac.createScriptProcessor(this.bufferSize, this._channels, 2); + this._jsNode.onaudioprocess = this._audioprocess.bind(this); + /** + * callback invoked when the recording is over + * @private + * @type {function(Float32Array)} + */ + this._callback = function () { + }; + // connections + this._jsNode.connect(p5.soundOut._silentNode); + this.setInput(); + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connect a specific device to the p5.SoundRecorder. + * If no parameter is given, p5.SoundRecorer will record + * all audible p5.sound from your sketch. + * + * @method setInput + * @param {Object} [unit] p5.sound object or a web audio unit + * that outputs sound + */ + p5.SoundRecorder.prototype.setInput = function (unit) { + this.input.disconnect(); + this.input = null; + this.input = ac.createGain(); + this.input.connect(this._jsNode); + this.input.connect(this.output); + if (unit) { + unit.connect(this.input); + } else { + p5.soundOut.output.connect(this.input); + } + }; + /** + * Start recording. To access the recording, provide + * a p5.SoundFile as the first parameter. The p5.SoundRecorder + * will send its recording to that p5.SoundFile for playback once + * recording is complete. Optional parameters include duration + * (in seconds) of the recording, and a callback function that + * will be called once the complete recording has been + * transfered to the p5.SoundFile. + * + * @method record + * @param {p5.SoundFile} soundFile p5.SoundFile + * @param {Number} [duration] Time (in seconds) + * @param {Function} [callback] The name of a function that will be + * called once the recording completes + */ + p5.SoundRecorder.prototype.record = function (sFile, duration, callback) { + this.recording = true; + if (duration) { + this.sampleLimit = Math.round(duration * ac.sampleRate); + } + if (sFile && callback) { + this._callback = function () { + this.buffer = this._getBuffer(); + sFile.setBuffer(this.buffer); + callback(); + }; + } else if (sFile) { + this._callback = function () { + this.buffer = this._getBuffer(); + sFile.setBuffer(this.buffer); + }; + } + }; + /** + * Stop the recording. Once the recording is stopped, + * the results will be sent to the p5.SoundFile that + * was given on .record(), and if a callback function + * was provided on record, that function will be called. + * + * @method stop + */ + p5.SoundRecorder.prototype.stop = function () { + this.recording = false; + this._callback(); + this._clear(); + }; + p5.SoundRecorder.prototype._clear = function () { + this._leftBuffers = []; + this._rightBuffers = []; + this.recordedSamples = 0; + this.sampleLimit = null; + }; + /** + * internal method called on audio process + * + * @private + * @param {AudioProcessorEvent} event + */ + p5.SoundRecorder.prototype._audioprocess = function (event) { + if (this.recording === false) { + return; + } else if (this.recording === true) { + // if we are past the duration, then stop... else: + if (this.sampleLimit && this.recordedSamples >= this.sampleLimit) { + this.stop(); + } else { + // get channel data + var left = event.inputBuffer.getChannelData(0); + var right = event.inputBuffer.getChannelData(1); + // clone the samples + this._leftBuffers.push(new Float32Array(left)); + this._rightBuffers.push(new Float32Array(right)); + this.recordedSamples += this.bufferSize; + } + } + }; + p5.SoundRecorder.prototype._getBuffer = function () { + var buffers = []; + buffers.push(this._mergeBuffers(this._leftBuffers)); + buffers.push(this._mergeBuffers(this._rightBuffers)); + return buffers; + }; + p5.SoundRecorder.prototype._mergeBuffers = function (channelBuffer) { + var result = new Float32Array(this.recordedSamples); + var offset = 0; + var lng = channelBuffer.length; + for (var i = 0; i < lng; i++) { + var buffer = channelBuffer[i]; + result.set(buffer, offset); + offset += buffer.length; + } + return result; + }; + p5.SoundRecorder.prototype.dispose = function () { + this._clear(); + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this._callback = function () { + }; + if (this.input) { + this.input.disconnect(); + } + this.input = null; + this._jsNode = null; + }; + /** + * Save a p5.SoundFile as a .wav audio file. + * + * @method saveSound + * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save + * @param {String} name name of the resulting .wav file. + */ + p5.prototype.saveSound = function (soundFile, name) { + var leftChannel = soundFile.buffer.getChannelData(0); + var rightChannel = soundFile.buffer.getChannelData(1); + var interleaved = interleave(leftChannel, rightChannel); + // create the buffer and view to create the .WAV file + var buffer = new ArrayBuffer(44 + interleaved.length * 2); + var view = new DataView(buffer); + // write the WAV container, + // check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + // RIFF chunk descriptor + writeUTFBytes(view, 0, 'RIFF'); + view.setUint32(4, 44 + interleaved.length * 2, true); + writeUTFBytes(view, 8, 'WAVE'); + // FMT sub-chunk + writeUTFBytes(view, 12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + // stereo (2 channels) + view.setUint16(22, 2, true); + view.setUint32(24, 44100, true); + view.setUint32(28, 44100 * 4, true); + view.setUint16(32, 4, true); + view.setUint16(34, 16, true); + // data sub-chunk + writeUTFBytes(view, 36, 'data'); + view.setUint32(40, interleaved.length * 2, true); + // write the PCM samples + var lng = interleaved.length; + var index = 44; + var volume = 1; + for (var i = 0; i < lng; i++) { + view.setInt16(index, interleaved[i] * (32767 * volume), true); + index += 2; + } + p5.prototype.writeFile([view], name, 'wav'); + }; + // helper methods to save waves + function interleave(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + var result = new Float32Array(length); + var inputIndex = 0; + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + function writeUTFBytes(view, offset, string) { + var lng = string.length; + for (var i = 0; i < lng; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + } +}(sndcore, master); +var peakdetect; +peakdetect = function () { + 'use strict'; + var p5sound = master; + /** + * <p>PeakDetect works in conjunction with p5.FFT to + * look for onsets in some or all of the frequency spectrum. + * </p> + * <p> + * To use p5.PeakDetect, call <code>update</code> in the draw loop + * and pass in a p5.FFT object. + * </p> + * <p> + * You can listen for a specific part of the frequency spectrum by + * setting the range between <code>freq1</code> and <code>freq2</code>. + * </p> + * + * <p><code>threshold</code> is the threshold for detecting a peak, + * scaled between 0 and 1. It is logarithmic, so 0.1 is half as loud + * as 1.0.</p> + * + * <p> + * The update method is meant to be run in the draw loop, and + * <b>frames</b> determines how many loops must pass before + * another peak can be detected. + * For example, if the frameRate() = 60, you could detect the beat of a + * 120 beat-per-minute song with this equation: + * <code> framesPerPeak = 60 / (estimatedBPM / 60 );</code> + * </p> + * + * <p> + * Based on example contribtued by @b2renger, and a simple beat detection + * explanation by <a + * href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/" + * target="_blank">Felix Turner</a>. + * </p> + * + * @class p5.PeakDetect + * @constructor + * @param {Number} [freq1] lowFrequency - defaults to 20Hz + * @param {Number} [freq2] highFrequency - defaults to 20000 Hz + * @param {Number} [threshold] Threshold for detecting a beat between 0 and 1 + * scaled logarithmically where 0.1 is 1/2 the loudness + * of 1.0. Defaults to 0.35. + * @param {Number} [framesPerPeak] Defaults to 20. + * @example + * <div><code> + * + * var cnv, soundFile, fft, peakDetect; + * var ellipseWidth = 10; + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * + * soundFile = loadSound('assets/beat.mp3'); + * + * // p5.PeakDetect requires a p5.FFT + * fft = new p5.FFT(); + * peakDetect = new p5.PeakDetect(); + * + * } + * + * function draw() { + * background(0); + * text('click to play/pause', width/2, height/2); + * + * // peakDetect accepts an fft post-analysis + * fft.analyze(); + * peakDetect.update(fft); + * + * if ( peakDetect.isDetected ) { + * ellipseWidth = 50; + * } else { + * ellipseWidth *= 0.95; + * } + * + * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); + * } + * + * // toggle play/stop when canvas is clicked + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * if (soundFile.isPlaying() ) { + * soundFile.stop(); + * } else { + * soundFile.play(); + * } + * } + * } + * </code></div> + */ + p5.PeakDetect = function (freq1, freq2, threshold, _framesPerPeak) { + var framesPerPeak; + // framesPerPeak determines how often to look for a beat. + // If a beat is provided, try to look for a beat based on bpm + this.framesPerPeak = _framesPerPeak || 20; + this.framesSinceLastPeak = 0; + this.decayRate = 0.95; + this.threshold = threshold || 0.35; + this.cutoff = 0; + // how much to increase the cutoff + // TO DO: document this / figure out how to make it accessible + this.cutoffMult = 1.5; + this.energy = 0; + this.penergy = 0; + // TO DO: document this property / figure out how to make it accessible + this.currentValue = 0; + /** + * isDetected is set to true when a peak is detected. + * + * @attribute isDetected + * @type {Boolean} + * @default false + */ + this.isDetected = false; + this.f1 = freq1 || 40; + this.f2 = freq2 || 20000; + // function to call when a peak is detected + this._onPeak = function () { + }; + }; + /** + * The update method is run in the draw loop. + * + * Accepts an FFT object. You must call .analyze() + * on the FFT object prior to updating the peakDetect + * because it relies on a completed FFT analysis. + * + * @method update + * @param {p5.FFT} fftObject A p5.FFT object + */ + p5.PeakDetect.prototype.update = function (fftObject) { + var nrg = this.energy = fftObject.getEnergy(this.f1, this.f2) / 255; + if (nrg > this.cutoff && nrg > this.threshold && nrg - this.penergy > 0) { + // trigger callback + this._onPeak(); + this.isDetected = true; + // debounce + this.cutoff = nrg * this.cutoffMult; + this.framesSinceLastPeak = 0; + } else { + this.isDetected = false; + if (this.framesSinceLastPeak <= this.framesPerPeak) { + this.framesSinceLastPeak++; + } else { + this.cutoff *= this.decayRate; + this.cutoff = Math.max(this.cutoff, this.threshold); + } + } + this.currentValue = nrg; + this.penergy = nrg; + }; + /** + * onPeak accepts two arguments: a function to call when + * a peak is detected. The value of the peak, + * between 0.0 and 1.0, is passed to the callback. + * + * @method onPeak + * @param {Function} callback Name of a function that will + * be called when a peak is + * detected. + * @param {Object} [val] Optional value to pass + * into the function when + * a peak is detected. + * @example + * <div><code> + * var cnv, soundFile, fft, peakDetect; + * var ellipseWidth = 0; + * + * function setup() { + * cnv = createCanvas(100,100); + * textAlign(CENTER); + * + * soundFile = loadSound('assets/beat.mp3'); + * fft = new p5.FFT(); + * peakDetect = new p5.PeakDetect(); + * + * setupSound(); + * + * // when a beat is detected, call triggerBeat() + * peakDetect.onPeak(triggerBeat); + * } + * + * function draw() { + * background(0); + * fill(255); + * text('click to play', width/2, height/2); + * + * fft.analyze(); + * peakDetect.update(fft); + * + * ellipseWidth *= 0.95; + * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); + * } + * + * // this function is called by peakDetect.onPeak + * function triggerBeat() { + * ellipseWidth = 50; + * } + * + * // mouseclick starts/stops sound + * function setupSound() { + * cnv.mouseClicked( function() { + * if (soundFile.isPlaying() ) { + * soundFile.stop(); + * } else { + * soundFile.play(); + * } + * }); + * } + * </code></div> + */ + p5.PeakDetect.prototype.onPeak = function (callback, val) { + var self = this; + self._onPeak = function () { + callback(self.energy, val); + }; + }; +}(master); +var gain; +gain = function () { + 'use strict'; + var p5sound = master; + /** + * A gain node is usefull to set the relative volume of sound. + * It's typically used to build mixers. + * + * @class p5.Gain + * @constructor + * @example + * <div><code> + * + * // load two soundfile and crossfade beetween them + * var sound1,sound2; + * var gain1, gain2, gain3; + * + * function preload(){ + * soundFormats('ogg', 'mp3'); + * sound1 = loadSound('../_files/Damscray_-_Dancing_Tiger_01'); + * sound2 = loadSound('../_files/beat.mp3'); + * } + * + * function setup() { + * createCanvas(400,200); + * + * // create a 'master' gain to which we will connect both soundfiles + * gain3 = new p5.Gain(); + * gain3.connect(); + * + * // setup first sound for playing + * sound1.rate(1); + * sound1.loop(); + * sound1.disconnect(); // diconnect from p5 output + * + * gain1 = new p5.Gain(); // setup a gain node + * gain1.setInput(sound1); // connect the first sound to its input + * gain1.connect(gain3); // connect its output to the 'master' + * + * sound2.rate(1); + * sound2.disconnect(); + * sound2.loop(); + * + * gain2 = new p5.Gain(); + * gain2.setInput(sound2); + * gain2.connect(gain3); + * + * } + * + * function draw(){ + * background(180); + * + * // calculate the horizontal distance beetween the mouse and the right of the screen + * var d = dist(mouseX,0,width,0); + * + * // map the horizontal position of the mouse to values useable for volume control of sound1 + * var vol1 = map(mouseX,0,width,0,1); + * var vol2 = 1-vol1; // when sound1 is loud, sound2 is quiet and vice versa + * + * gain1.amp(vol1,0.5,0); + * gain2.amp(vol2,0.5,0); + * + * // map the vertical position of the mouse to values useable for 'master volume control' + * var vol3 = map(mouseY,0,height,0,1); + * gain3.amp(vol3,0.5,0); + * } + *</code></div> + * + */ + p5.Gain = function () { + this.ac = p5sound.audiocontext; + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + this.input.connect(this.output); + // add to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connect a source to the gain node. + * + * @method setInput + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + */ + p5.Gain.prototype.setInput = function (src) { + src.connect(this.input); + }; + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + p5.Gain.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Gain.prototype.disconnect = function () { + this.output.disconnect(); + }; + /** + * Set the output level of the gain node. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Gain.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + }; + p5.Gain.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.output.disconnect(); + this.input.disconnect(); + this.output = undefined; + this.input = undefined; + }; +}(master, sndcore); +var src_app; +src_app = function () { + 'use strict'; + var p5SOUND = sndcore; + return p5SOUND; +}(sndcore, master, helpers, errorHandler, panner, soundfile, amplitude, fft, signal, oscillator, env, pulse, noise, audioin, filter, delay, reverb, metro, looper, soundRecorder, peakdetect, gain); +})); \ No newline at end of file diff --git a/examples/p5js/p5js_sketches/grid24x8_clouds.js b/examples/p5js/p5js_sketches/grid24x8_clouds.js new file mode 100644 index 0000000000000000000000000000000000000000..5c60aebcd421acf1780c2e19a4baa7cef155d6a3 --- /dev/null +++ b/examples/p5js/p5js_sketches/grid24x8_clouds.js @@ -0,0 +1,77 @@ +// Connect to the local instance of fcserver +var WebSocketAddress = "ws://127.0.0.1:7890"; +//Show LED pixel locations. +var showPixelLocations = true; +//Change the HTML Id of the canvas. +var canvasId = "grid24x8_clouds" + + +var dx = 0; +var dy = 0; +var im; +var d = 0; +function setup(){ + var canvas = createCanvas(52, 24); + canvas.id(canvasId); + socketSetup(WebSocketAddress); // Connect to the local instance of fcserver via websocket. + + + loadPixels(); + pixelDensity(1); + d = pixelDensity(); + + // Map an 8x8 grid of LEDs to the center of the window, scaled to take up most of the space + var spacing = (height / 8.0)-1; + ledGrid8x8(0, width/2, height/2, spacing, 0, true); + + //Put two more 8x8 grids to the left and to the right of that one. + ledGrid8x8(64, width/2 - spacing * 8, height/2, spacing, 0, true); + ledGrid8x8(128, width/2 + spacing * 8, height/2, spacing, 0, true); + colorMode(HSB, 100); + background(0); +} + +var noiseScale=0.02; + +function fractalNoise(x, y, z) { + var r = 0; + var amp = 1.0; + for (var octave = 0; octave < 4; octave++) { + r += noise(x, y, z) * amp; + amp /= 2; + x *= 2; + y *= 2; + z *= 2; + } + return r; +} + +function draw() { + var now = millis(); + var speed = 0.002; + var angle = sin(now * 0.001); + var z = now * 0.00008; + var Hue = now * 0.01; + var Scale = 0.005; + dx += cos(angle) * speed; + dy += sin(angle) * speed; + loadPixels(); + for (var x=0; x < width*d*4; x+=4) { + for (var y=0; y < height*d*4; y+=4) { + var m = fractalNoise(dx + x*Scale, dy + y*Scale, z+10) - 0.75; + var n = fractalNoise(dx + x*Scale, dy + y*Scale, z)*3 - 0.75*3; + var c = color( + (Hue + 80.0 * m) % 100.0, + 100 - 100 * constrain(pow(n, 3), 0, .9), + 100 * constrain(pow(n, 1.5), 0, .9) + ); + var idx = width*d*y+x; + pixels[idx] = c._getRed(); + pixels[idx+1] = c._getGreen(); + pixels[idx+2] = c._getBlue(); + //pixels[idx+3] = c._getAlpha(); + } + } + updatePixels(); + drawFrame(); +} \ No newline at end of file diff --git a/examples/p5js/p5js_sketches/grid8x8_dot.js b/examples/p5js/p5js_sketches/grid8x8_dot.js new file mode 100644 index 0000000000000000000000000000000000000000..921541258f2d635c8a11d8132c1e921b014cff4f --- /dev/null +++ b/examples/p5js/p5js_sketches/grid8x8_dot.js @@ -0,0 +1,29 @@ +// Connect to the local instance of fcserver +var WebSocketAddress = "ws://127.0.0.1:7890"; +//Show LED pixel locations. +var showPixelLocations = true; +//Change the HTML Id of the canvas. +var canvasId = "grid8x8_dot"; + + +//Canvas +function setup() { + var canvas = createCanvas(640, 360); + canvas.id(canvasId); + socketSetup(WebSocketAddress); + + // Load a sample image + dot = loadImage("images/dot.png"); + + // Map an 8x8 grid of LEDs to the center of the window + ledGrid8x8(0, width/2, height/2, height/12, 0, false); +} + +function draw() { + background(0); + + // Draw the image, centered at the mouse location + var dotSize = height * 0.7; + image(dot, mouseX - dotSize/2, mouseY - dotSize/2, dotSize, dotSize); + drawFrame(); +} \ No newline at end of file diff --git a/examples/p5js/p5js_sketches/grid8x8_orbits.js b/examples/p5js/p5js_sketches/grid8x8_orbits.js new file mode 100644 index 0000000000000000000000000000000000000000..c02abb95f464ee428146bdcfb8093d761411f41c --- /dev/null +++ b/examples/p5js/p5js_sketches/grid8x8_orbits.js @@ -0,0 +1,45 @@ +// Connect to the local instance of fcserver. You can change this line to connect to another computer's fcserver +var WebSocketAddress = "ws://127.0.0.1:7890"; +//Show LED pixel locations. +var showPixelLocations = true; +//Change the HTML Id of the canvas. +var canvasId = "grid8x8_orbits"; + + +var dot1; +var dot2; +var px = 0; +var py = 0; +function setup(){ + var canvas = createCanvas(640, 360); + canvas.id(canvasId); + var spacing = height / 14.0; + socketSetup(WebSocketAddress); + dot1 = loadImage("images/greenDot.png"); + dot2 = loadImage("images/purpleDot.png"); + //var spacing = (canvas.height/14); + //Connect to the local instance of fcserver + //Map an 8x8 grid of LEDs to the center of the window, scaled to take up most of the space + ledGrid8x8(0, width/2, height/2, spacing, HALF_PI, false); +} + +function draw(){ + background(0); + blendMode(ADD); + // Smooth out the mouse location + px += (mouseX - px * 0.2); + py += (mouseY - py * 0.2); + var a = (millis() * 0.001); + var r = (py * 0.5); + var dotSize = (r * 4); + var dx = (width/2 + cos(a) * r); + var dy = (height/2 + sin(a) * r); + + // Draw it centered at the mouse location + image(dot1, dx - dotSize/2, dy - dotSize/2, dotSize, dotSize); + + // Another dot, mirrored around the center + image(dot2, width - dx - dotSize/2, height - dy - dotSize/2, dotSize, dotSize); + + drawFrame(); +} \ No newline at end of file diff --git a/examples/p5js/p5js_sketches/grid8x8_simple_noise.js b/examples/p5js/p5js_sketches/grid8x8_simple_noise.js new file mode 100644 index 0000000000000000000000000000000000000000..8cdf5262ec430cd038f5bfbbf212f67f8fcfd3e3 --- /dev/null +++ b/examples/p5js/p5js_sketches/grid8x8_simple_noise.js @@ -0,0 +1,41 @@ +// Connect to the local instance of fcserver +var WebSocketAddress = "ws://127.0.0.1:7890"; +//Show LED pixel locations. +var showPixelLocations = true; +//Change the HTML Id of the canvas. +var canvasId = "grid8x8_simple_noise"; + + +//A simple example of using Processing's noise() function to draw LED clouds +var clouds; +// +function setup(){ +createCanvas(128, 128); +socketSetup(WebSocketAddress); +ledGrid8x8(0, width/2, height/2, height / 8.0, 0, false); +colorMode(HSB, 100); +noiseDetail(5, 0.4); +// Render the noise to a smaller image, it's faster than updating the entire window. +clouds = new p5.Image(128, 128, RGB); +} + +function draw(){ + var colorHue = (noise(millis() * 0.0001) * 200) % 100; + var z = millis() * 0.0001; + var dx = millis() * 0.0001; + clouds.loadPixels(); + for (var x=0; x < clouds.width*4; x+=2) { + for (var y=0; y < clouds.height*4; y+=4) { + var n = 500 * (noise(dx + x * 0.01, y * 0.01, z) - 0.4); + //var c = color(hue, 80 - n, n); + clouds.pixels[((x + clouds.width*y))] = colorHue; + clouds.pixels[((x + clouds.width*y))+1] = 80 - n; + clouds.pixels[((x + clouds.width*y))+2] = n; + } + } + clouds.updatePixels(); + image(clouds, 0, 0, clouds.width, clouds.height,0,0,width,height); + + + drawFrame(); +} \ No newline at end of file diff --git a/examples/p5js/p5js_sketches/strip64_flames.js b/examples/p5js/p5js_sketches/strip64_flames.js new file mode 100644 index 0000000000000000000000000000000000000000..050aa10a81fa940ea918d01a2b5a931e072fea45 --- /dev/null +++ b/examples/p5js/p5js_sketches/strip64_flames.js @@ -0,0 +1,39 @@ +// Connect to the local instance of fcserver +var WebSocketAddress = "ws://127.0.0.1:7890"; +//Show LED pixel locations. +var showPixelLocations = true; +//Change the HTML Id of the canvas. +var canvasId = "strip64_flames" + + +var im; +function setup(){ + var canvas = createCanvas(800, 200); + canvas.id(canvasId); + socketSetup(WebSocketAddress); // Connect to the local instance of fcserver via websocket. + + + im = loadImage("images/flames.jpeg"); // Load a sample image + ledStrip(0, 64, width/2, height/2, width/70, 0, false); + //ledRing(0, 120, width/2, height/2, height/4, 0); + //ledGrid8x8(0, width/2, height/2, 5, 0, true); + //ledGrid(0, 15, 8, width/2, height/2, 2, 2, 0, true); + frameRate(60); +} + +function draw() { + + // Scale the image so that it matches the width of the window + var imHeight = im.height * width / im.width; + + // Scroll down slowly, and wrap around + var speed = 0.05; + var y = (millis() * -speed) % imHeight; + + // Use two copies of the image, so it seems to repeat infinitely + image(im, 0, y, width, imHeight); + image(im, 0, y + imHeight, width, imHeight); + + //Send to fcServer. + drawFrame(); +} \ No newline at end of file diff --git a/examples/p5js/strip64_flames.html b/examples/p5js/strip64_flames.html new file mode 100644 index 0000000000000000000000000000000000000000..a51ed7562613eee08bc6b6628683b83d4c18d32f --- /dev/null +++ b/examples/p5js/strip64_flames.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>strip64_flames</title> + <script src="libraries/p5.js" type="text/javascript"></script> + <script src="libraries/p5.dom.js" type="text/javascript"></script> + <script src="libraries/p5.sound.js" type="text/javascript"></script> + <script src="libraries/opc.js" type="text/javascript"></script> + <script src="p5js_sketches/strip64_flames.js" type="text/javascript"></script> + + <style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style> + </head> + <body> + </body> +</html> \ No newline at end of file