diff --git a/app/debug/index.js b/app/debug/index.js index 910f8d6..c798ce7 100755 --- a/app/debug/index.js +++ b/app/debug/index.js @@ -63,12 +63,16 @@ module.exports = { width: this.debugWidth.toString() + "px", height: 'auto' }); + $('.prompt-label').css('margin-left', '8px'); + $('.console-label').css('margin-left', '5px'); } else { container.css({ width: 'auto', height: this.debugWidth > $('#editor-container').height() ? "100px" : this.debugWidth.toString() + "px" }); + $('.prompt-label').css('margin-left', '25px'); + $('.console-label').css('margin-left', '23px'); } } }, diff --git a/app/debug/style.scss b/app/debug/style.scss index 2eb633c..58ae2e4 100644 --- a/app/debug/style.scss +++ b/app/debug/style.scss @@ -1,6 +1,5 @@ #debug { - border-radius: 7px; - background-color: #F1F1F2; + background-color: #eeeeee; flex: 1; color: #333; overflow: auto; @@ -23,7 +22,7 @@ width: 100px; height: auto; display: flex; - flex-direction: row; + flex-direction: column; justify-content: flex-start; position: relative; min-width: 10px; @@ -31,11 +30,23 @@ } #debug-drag { - background-color: #C8Cw4C4; - width: 10px; + background-color: rgba(153, 153, 153, 0.5); + padding-top: 2px; + height: 18px !important; cursor: col-resize; + color: #b1b1b1; + font-size: 12px; } +.prompt-label { + margin-left: 25px; +} + +.console-label { + margin-left: 23px; +} + + body.horizontal { #debug-container { height: 100px; diff --git a/app/debug/template.html b/app/debug/template.html index 49e7feb..06f8601 100755 --- a/app/debug/template.html +++ b/app/debug/template.html @@ -1,2 +1,2 @@ -
+
>_console
diff --git a/app/editor/style.scss b/app/editor/style.scss index 055a10a..499d0b8 100644 --- a/app/editor/style.scss +++ b/app/editor/style.scss @@ -1,17 +1,21 @@ #main-container { - margin-top: 10px; + margin-top: 30px; margin-bottom: 10px; margin-right: 10px; flex: 1; display: flex; flex-direction: column; min-width: 100px; + /*padding-top: 21px !important;*/ } div#editor-container { display: flex; flex: 1; flex-direction: row; + border-top: 1px solid #f4f4f4; + border-right: 1px solid #f4f4f4; + border-bottom: 1px solid #f4f4f4; } #main { @@ -20,36 +24,112 @@ div#editor-container { } #editor { - font: 14px source-code; - border-radius: 7px; + font: 16px inconsolata; flex: 1; + background: #fdfdfd; } -.ace-gutter { - color: #58585b; - min-width:52px !important;; +.ace_scroller { + top: 21px !important; +} + +.ace_editor { + overflow: visible !important; +} + +.ace_active-line { + background: rgba(207, 207, 207, 0.2); +} + +.ace_print-margin { + visibility: hidden !important; +} + +.ace_gutter { + color: #b5b5b5 !important; + background: rgba(153, 153, 153, 0.1) !important; + width: 53px !important; + border-right: 1px solid rgba(153, 153, 153, 0.3); +} + +.ace_gutter-active-line { + margin-top: 21px; +} + +.ace_gutter-layer { + width: 53px !important; + margin-top: 21px !important; } .ace_gutter-cell { background-image: none !important; background-position: 2px center !important; - padding-right: 15px !important;; -} - -.ace_info { - background-color: #D7E5F5 !important; + padding-right: 15px !important; } .ace_warning { - background-color: none !important; - color: #58585b !important; + background-color: rgba(255, 190, 5, 0.3) !important; } .ace_error { - background-color: #FF6347 !important; - color: #ffffff !important; + background-color: rgba(255, 95, 81, 0.3) !important; +} + + +.ace_function, +.ace_p5_function { + color: #DC3787 !important; /* not p5 pink, but related */ +} + +.ace_p5_variable { + color: #00A1D3 !important; /* not p5 blue, but related */ +} + +/* property, tag, boolean, + number, function-name, constant, + symbol */ +.ace_numeric, +.ace_tag { + color: #333333 !important; +} + +/* atrule, attr-value, keyword, + class-name */ +.ace_type, +.ace_class, +.ace_attribute-name { + color: #704F21 !important; /* darker brown */ +} + +/* selector, attr-name, +function, builtin */ +.ace_keyword, +.ace_support { + color: #B48318 !important; +} + +/* comment, block-comment, prolog, + doctype, cdata */ +.ace_comment { + color: #A0A0A0 !important; /* light gray */ } +/* operator, entity, url, +variable */ +.ace_string { + color: #a67f59 !important; /* og coy a67f59 a light brown */ +} + +.ace_operator { + color: #333 !important; +} + +/* regex, important */ +.ace_regexp { + color: #e90 !important; /* og coy e90 orange */ +} + + body.horizontal { div#editor-container { @@ -60,3 +140,131 @@ body.horizontal { .ace_scroller.ace_scroll-left { box-shadow: none !important; } + +/* SEARCH */ + +.ace_search { + background-color: #f4f4f4 !important; + box-shadow: 0px 12px 12px rgba(0, 0, 0, 0.16) !important; + max-width: 365px !important; + width: 365px !important; + height: 138px !important; + font-family: montserrat !important; + font-size: 12px !important; + color: #b5b5b5 !important; + border-radius: 0 !important; + border: none !important; + padding: 0 !important; + overflow: visible !important; +} + +.ace_replace_form { + width: 365px !important; + height: 134px !important; + background-color: #f4f4f4 !important; + box-shadow: 0px 12px 12px rgba(0, 0, 0, 0.16) !important; + margin-top: 80px !important; + border: none !important; + padding: 0 !important; +} + +.ace_search_form .ace_search_field { + top: 51px !important; +} + +.ace_replace_form .ace_search_field { + top: 131px !important; +} + +.ace_search_field { + width: 324px !important; + height: 40px !important; + padding: 10px !important; + position: absolute !important; + left: 22px !important; + background-color: white !important; + outline: 0 !important; + border: 1px solid #979797 !important; +} + +.ace_search_form { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.ace_search_options { + margin: 0 !important; + padding: 0 !important; + position: absolute !important; + top: 100px !important; + left: 31px !important; +} + +.ace_button { + border: none !important; + opacity: 0.75 !important; + color: #b5b5b5 !important; + margin-right: 12px !important; + + &:hover { + background-color: none !important; + opacity: 0.35 !important; + } +} + +.ace_searchbtn, .ace_replacebtn { + background-color: rgba(0, 0, 0, 0) !important; + border: none !important; + color: #b5b5b5 !important; + opacity: 0.75 !important; + position: absolute !important; + height: 20px !important; + + &:hover { + opacity: 0.35 !important; + } +} + +.ace_searchbtn { + top: 100px !important; +} +.ace_replacebtn { + top: 180px !important; +} + +.ace_search_form button:nth-of-type(1) { + left: 257px !important; +} +.ace_search_form button:nth-of-type(2) { + left: 287px !important; +} +.ace_search_form button:nth-of-type(3) { + left: 317px !important; +} + +.ace_searchbtn_close { + background: url('../images/close-button.svg') !important; + + width: 24px !important; + height: 24px !important; + position: absolute !important; + top: 15px !important; + right: 14px !important; + fill: #2F2F30 !important; + transition: all 0.1s ease !important; + margin: 0 !important; + padding: 0 !important; + + &:hover { + opacity: 0.65 !important; + } +} + +.ace_replace_form button:nth-of-type(1) { + margin-left: 31px !important; +} + +.ace_replace_form button:nth-of-type(2) { + margin-left: 319px !important; + width: 27px !important; +} diff --git a/app/modes/p5/p5-mode.js b/app/modes/p5/p5-mode.js index 05ad215..ef0e0e7 100644 --- a/app/modes/p5/p5-mode.js +++ b/app/modes/p5/p5-mode.js @@ -113,7 +113,7 @@ module.exports = { gui.App.clearCache(); if (this.outputWindow) { - if (this.settings.runInBrowser) { + if ((String(self.settings.runInBrowser) == "true")) { gui.Shell.openExternal(url); } else { this.outputWindow.reloadIgnoringCache(); @@ -124,7 +124,7 @@ module.exports = { } } else { startServer(this.projectPath, this, function(url) { - if (self.settings.runInBrowser) { + if ((String(self.settings.runInBrowser) === "true")) { gui.Shell.openExternal(url); } else { fs.readFile(Path.join(self.projectPath, 'sketch.js'), function(err, data){ diff --git a/app/settings.js b/app/settings.js index 81b075f..fe598e1 100644 --- a/app/settings.js +++ b/app/settings.js @@ -1,7 +1,7 @@ var defaults = { fontSize: 14, tabSize: 2, - tabType: "spaces", + tabType: 'spaces', theme: 'tomorrow', consoleOrientation: 'horizontal', showSidebar: false, diff --git a/app/settings/index.js b/app/settings/index.js index 57cc38d..3388633 100755 --- a/app/settings/index.js +++ b/app/settings/index.js @@ -14,9 +14,21 @@ module.exports = { methods: { updateTabSize: function(e) { - var parsed = parseInt(e.target.value); + var parsed = typeof e === 'number' ? e : parseInt(e.target.value); this.tabSize = parsed >= 1 ? parsed : 1; this.tabSizeDisplay = this.tabSize; + }, + decreaseTabSize: function(e) { + this.updateTabSize(this.tabSize-1); + }, + increaseTabSize: function(e) { + this.updateTabSize(this.tabSize+1); + }, + decreaseFontSize: function(e) { + this.fontSize--; + }, + increaseFontSize: function(e) { + this.fontSize++; } } diff --git a/app/settings/style.scss b/app/settings/style.scss index 41a01f4..eb8e1ab 100644 --- a/app/settings/style.scss +++ b/app/settings/style.scss @@ -1,58 +1,101 @@ #settingsContainer { position: absolute; - background-color: #959595; - top: 10px; - right: 10px; + background-color: #f4f4f4; + top: 20px; + right: 30px; z-index: 999; - box-shadow: 0px 0px 6px #999; + box-shadow: 0px 12px 12px rgba(0, 0, 0, 0.16); color: #fff; + width: 276px; + padding: 14px 14px 8px 19px; +} + +#settingsPane p { + padding: 0; + margin: 0; } #settingsPane { - // width: 178px; width: 100%; - font-family: Karla; - // -webkit-box-shadow: 0px 8px 17px -2px #58585b; + font-family: montserrat; + font-size: 16px; + margin: 0; + padding: 0; #titleBar { - background-color: #58585b; - padding: 5px 12px 1px; + font-family: montserrat-bold; + font-size: 21px; + color: rgba(51, 51, 51, 0.87); + margin-bottom: 19px; } #optionsZone { - padding: 0px 12px; div { clear: both; - padding-bottom: 14px; } } - .hiddenRadio { - overflow: auto; + .label { + color: #333333; + font-size: 16px; + margin-bottom: 7px; + } - input[type="radio"] { - display:none; - } + .section { + border-bottom: dashed 1px #b5b5b5; + margin-bottom: 13.5px; + padding-bottom: 13.5px; + } - input[type="radio"]:checked + label svg { - fill: #e1dedc; + .section:last-child { + border: none; + padding-bottom: 0; + margin-bottom: 0; + } - &:hover { - fill: #fff; - } - } + input[type="text"] { + width: 44px; + height: 40px; + border: 1px solid rgba(153, 153, 153, 0.4); + color: #333333; + background-color: #f4f4f4; + font-size: 16px; + font-family: inconsolata; + text-align: center; + margin: 0 22px; + display: inline-block; + vertical-align: top; + } - label { - display: block; - } + .adjustButtonContainer { + width: 43px; + text-align: center; + display: inline-block; + vertical-align: top; + } - label.left { - float: left; - } + .adjustButton { + width: 32px; + height: 32px; + cursor: pointer; + } + + .adjustButtonContainer label { + font-size: 9px; + color: #b5b5b5; + margin-top: 8px; + } + + input:focus { + outline: 0; + } - label.right { - float: right; + .hiddenRadio { + overflow: auto; + + input[type="radio"] { + display:none; } } @@ -62,12 +105,6 @@ } } - #consoleText { - position: relative; - top: 5px; - margin: 4px 0 6px; - } - .console { height: 40px; width: 70px; @@ -81,11 +118,11 @@ } #close { - width: 19px; - height: 19px; + width: 24px; + height: 24px; position: absolute; - top: 4px; - right: 4px; + top: 15px; + right: 14px; } #x { @@ -108,34 +145,6 @@ position: relative; } - #textAdjust { - width: 30px; - float: left; - position: relative; - top: 5px; - left: 3px; - } - - #textAdjustInput { - width: 105px; - } - - #indentation { - padding-top: 14px; - } - - #libs { - margin-bottom: 28px; - margin-top: 4px; - } - - #ww { - overflow:auto; - } - - #runInBrowserContainer { - margin-bottom: 30px; - } #wordWrapIcon { width: 23px; @@ -146,11 +155,10 @@ } #browserIcon { - width: 23px; - float: left; - position: relative; - top: 8px; - left: 5px; + width: 44px; + height: 44px; + margin-right: 20px; + cursor: pointer; } .fade { @@ -202,83 +210,47 @@ transition: all 0.3s ease; } - #tabBox { - float: right; - } - #spaceBox { - float: left; + #indentationAmount { + width: 200px; + display: inline; } #indentOptions { - position: relative; - /* margin-bottom: 12px; */ - - input[type="radio"]:checked + label { - background-color: #e1dedc; - } - - input[type="radio"] + label:hover { - background-color: #fff; - } + margin-left: 22px; } - .indentSelection { - color: #828384; - background-color: #adacac; - display: inline-block; - border-radius: 4px; - padding: 4px; - // float: right; - text-align: center; - // width: 67px; - width: 44%; - transition: all 0.3s ease; + #indentOptions .radioSelection { + margin-bottom: 10px; + margin-right: 0px; + display: block; } - .highlight{ - background-color: #e1dedc; + .radioOptions { + display: inline-block; - border-radius: 4px; - padding: 4px; - float: right; - width: 105px; - text-align: center; - } + color: #b5b5b5; + font-size: 12px; + input[type="radio"]:checked + label { + color: #333333; + } - h2 { - font-size: 16px; - margin-top: 0; - font-weight: normal; - -webkit-margin-after: 2px; - } + input[type="radio"] + label:hover { + color: #333333; + } - p { - font-size: 13px; - color: #58585b; - } + label { + cursor: pointer !important; + } - label { - color: #58585b; - font-size: 13px; - position: relative; - top: 5px; } - input[type="text"] { - width: 25px; - float: right; - border-radius: 5px; - border: none; - background-color: #e1dedc; - padding: 4px; - outline: none; - color: #58585b; - font-family: Karla; - font-size: 13px; + .radioSelection { + color: #828384; + border-radius: 4px; + text-align: left; + margin-right: 44px; + transition: all 0.15s ease; } - input#tabSize { - margin-left: 0.3em; - } } diff --git a/app/settings/template.html b/app/settings/template.html index f222ba1..3cbf13b 100755 --- a/app/settings/template.html +++ b/app/settings/template.html @@ -1,68 +1,93 @@
-

Preferences

- -
- +

Preferences

+
-
-

Console Orientation

+ +
+

Text size

+ + + + + + + + + +
- - + +
+

Indentation amount

+ + + + + + + + + - - +
+ + + + +
-
- - - + +
+

Word wrap

+
+ + + + +
-
- - + +
+

Run in browser

+
+ + + + +
-
- -
+ +
+

Console orientation

-
- - - -
-
- - - + +
+

Show sidebar

+
+ + + + +
-
- - - -
+ +
diff --git a/app/sidebar/folder.html b/app/sidebar/folder.html index 0afb943..ef39023 100644 --- a/app/sidebar/folder.html +++ b/app/sidebar/folder.html @@ -1,6 +1,5 @@
- {{name}}
    diff --git a/app/sidebar/index.js b/app/sidebar/index.js index 10dcc47..124e8a9 100755 --- a/app/sidebar/index.js +++ b/app/sidebar/index.js @@ -16,7 +16,7 @@ module.exports = { computed: { className: function() { var container = $('#sidebar-container'); - if (this.$root.settings.showSidebar) { + if (String(this.$root.settings.showSidebar) === "true") { // ask sam $('#showSidebarLabel').html( $('#showSidebarLabel').data('hide') ); container.css({ width: this.sidebarWidth diff --git a/app/sidebar/style.scss b/app/sidebar/style.scss index 5602a25..8c410ab 100644 --- a/app/sidebar/style.scss +++ b/app/sidebar/style.scss @@ -7,6 +7,8 @@ #side-container { flex-direction: column; display: flex; + font-size: 12px; + margin-top: 5px; } #sidebar-container { @@ -14,7 +16,7 @@ visibility: hidden; position: relative; width: 10px; - margin-top: 120px; + margin-top: 55px; flex: 1; overflow-y: auto; } @@ -22,9 +24,9 @@ #sidebar-container.expanded{ display: flex; flex-direction: column; - width:160px; + width:120px; visibility: visible; - min-width: 160px; + min-width: 120px; } @@ -82,21 +84,19 @@ div { padding: 3px; - padding-left: 64px; - margin-left:10px; + padding-left:30px; cursor: pointer; - color: $dark-gray; + color: #b5b5b5; min-width: 60px; } div.selected { - background-color: #f6f6f6; - border-radius: 5px 0px 0px 5px; + background-color: rgba(153, 153, 153, 0.1); } ul li { div { - padding-left: 85px; + padding-left: 46px; } @@ -117,11 +117,12 @@ .toggle { top: 1px; - left: 13px; + margin-left: -23px; position: absolute; width: 24px; - margin-top: -2px; + margin-top: -3px; height: 24px; + opacity: 0.4; &.open { @@ -134,13 +135,14 @@ } .icon { - width: 13px; - height: 15px; + width: 9px; + height: 12px; position: absolute; top: 4px; - margin-left: -20px; - background-size: 13px auto; + margin-left: -15px; + background-size: 9px auto; background-repeat: no-repeat; + opacity: 0.5; } .icon.file { @@ -153,11 +155,11 @@ .icon.folder { background-image: url('../images/folder.svg'); - background-size: 17px auto; + background-size: 12px auto; top: 5px; margin-left: -22px; - width: 17px; - height: 16px; + width: 12px; + height: 12px; } .icon.image { diff --git a/app/tabs/style.scss b/app/tabs/style.scss index 8d2044a..cbcbb34 100644 --- a/app/tabs/style.scss +++ b/app/tabs/style.scss @@ -1,97 +1,33 @@ -#header-container { +#tab-container { + font-size: 12px; display: flex; flex: 1; flex-direction: column; - max-height: 85px; - -webkit-touch-callout: none; - -webkit-user-select: none; + max-height: 30px; + -webkit-touch-callout: none; + -webkit-user-select: none; } -#play { - background: none; - background-image: url('../images/play-gray.svg'); - border: 0; - width: 33px; - height: 34px; - padding: 7px; - font-size: 25px; - display: inline; - cursor: pointer; - outline: 0; - float: left; - margin-top: 8px; - &:hover { - background-image: url('../images/play-green.svg'); -} - -&.running { - background-image: url('../images/stop-gray.svg'); - - &:hover { - background-image: url('../images/stop-red.svg'); - } -} -} - - - -#button_header { - margin-left: 15px; - height: 50px; - margin-bottom: 8px; -} - -#project-name { - padding-left: 20px; - font-size: 24px; - color: #58585b; - font-weight: bold; - margin-top: 10px; - margin-bottom: 10px; - cursor: default; - float:left; -} - - #toolbar { height: 60px; width: 100%; } #actions { - button { - border: 0; - cursor: pointer; - background: none; - margin: 0; - padding: 0; - margin-right: 15px; - outline: 0; - } - - button, button img { - width: 32px; - } - - button img:hover { - opacity: 0.6; - } - #add, #export { float: left; } #settings { float: right; - margin-top: 10px; + margin-right: 15px; } } #tabs { margin-left: 53px; - overflow: hidden; ul { @@ -109,36 +45,70 @@ div { display: inline-block; height: 30px; - background-color: #DCDCDC; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - min-width:20px; - padding: 0 5px 0 5px; - margin-right:2px; + background-color: rgba(153, 153, 153, 0.1); + width: 128px; text-align: center; } + div.selected{ - background-color: #fff + background-color: rgba(153, 153, 153, 0.4); + } + + li div { + border-right: 1px solid rgba(153, 153, 153, 0.3); } + + li:first-child div { + border-top-left-radius: 8px; + } + + li:nth-last-child(2) div { + border-top-right-radius: 8px; + border-right: none; + } + + li:nth-last-child(1) div { + border-right: none; + } + a{ display: inline-block; text-decoration: none; line-height: 2.4; text-align: center; - color: #000; + color: #b5b5b5; + outline: none; } a.delete{ color: #aaa; + float: right; + padding-right: 18px; + background: url('../images/close-button.svg'); + background-repeat: no-repeat; + margin-top: 10px; + height: 10px; + } + .selected a{ + color: #333333; + } + a.delete:hover { + opacity: 0.5; } } #add { - width: 30px; - height: 30px; + div { + width: 30px !important; + background-color: #fbfbfb;; + } a { width: 100%; display:block; + color: #333333; + } + a:hover { + opacity: 0.5; } } diff --git a/app/tabs/tab.html b/app/tabs/tab.html index 9dedd87..ff13645 100644 --- a/app/tabs/tab.html +++ b/app/tabs/tab.html @@ -1,5 +1,5 @@ diff --git a/app/tabs/template.html b/app/tabs/template.html index 6a14213..7460195 100755 --- a/app/tabs/template.html +++ b/app/tabs/template.html @@ -1,14 +1,3 @@ -
    - -

    -
    -
    - -
    -
    -
    • diff --git a/app/window/window.scss b/app/window/window.scss index a973a34..67194b6 100644 --- a/app/window/window.scss +++ b/app/window/window.scss @@ -1,35 +1,127 @@ @font-face { - font-family: karla; - src: url('../fonts/karla/Karla-Regular.ttf'); + font-family: inconsolata; + src: url('../fonts/Inconsolata.otf'); } @font-face { - font-family: karla-bold; - src: url('../fonts/karla/Karla-Bold.ttf'); + font-family: montserrat-bold; + src: url('../fonts/Montserrat-Bold.ttf'); } @font-face { - font-family: source-code; - src: url('../fonts/Source_Code_Pro/SourceCodePro-Regular.ttf'); + font-family: montserrat; + src: url('../fonts/Montserrat-Regular.ttf'); } body, html { - background: #c8c5c2; + background: #fbfbfb; color: #231f20; height: 100%; margin: 0; padding: 0; - font: 14px karla, sans-serif; + font: 14px montserrat, sans-serif; } body { display: flex; - flex-direction: row; + flex-direction: column; justify-content: flex-start; align-items: stretch; overflow: hidden; + height: 100%; +} + +#flex-container { + display: flex; + flex-direction: row; + height: 100%; } .file-handler { display: none; } + +::-webkit-scrollbar-track { + background: rgba(153, 153, 153, 0.1); +} + +::-webkit-scrollbar-thumb { + background-color: #d8d8d8; + border: 4px solid transparent; + border-radius: 8px; + background-clip: content-box; +} +::-webkit-scrollbar { + width: 16px; +} + + +.button { + cursor: pointer; +} + +.button:hover { + opacity: 0.6; +} + +#header-container { + height: 44px; + margin-top: 30px; + margin-left: 102px; +} + +#logo { + height: 43px; + float: left; + margin-right: 25px; +} + +#play { + background: none; + background-image: url('../images/play-button.svg'); + border: 0; + width: 44px; + height: 44px; + font-size: 25px; + display: inline; + outline: 0; + float: left; + + &.running { + background-image: url('../images/stop-button.svg'); + } +} + +#settings { + width: 44px; + height: 44px; +} + +#project-name { + font-size: 12px; + padding-left: 20px; + color: #b5b5b5; + margin-top: 14px; + margin-bottom: 10px; + cursor: default; + float:left; +} + + +#toolbar { + height: 60px; + width: 100%; +} + +#actions { + + #add, #export { + float: left; + } + + #settings { + float: right; + margin-right: 15px; + } +} + diff --git a/package.json b/package.json old mode 100644 new mode 100755 index a2ec82e..161dcf0 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "author": "Sam Lavigne", "name": "p5", "description": "Javascript IDE for beginners", - "version": "0.5.7", + "version": "0.5.8", "devDependencies": { + "autolinker": "^0.17.1", "br-mousetrap": "~1.1.3", "brace": "git+https://gigit@github.com/antiboredom/brace.git", "browserify": "^3.33.1", @@ -16,17 +17,16 @@ "gulp-notify": "~1.4.0", "gulp-plumber": "~0.6.3", "gulp-rename": "^1.2.0", - "gulp-sass": "^0.7.1", + "gulp-sass": "^2.2.0", "gulp-zip": "~0.4.0", "jquery": "~2.1.1", "js-beautify": "~1.5.1", "nw": "0.12.3", + "nw-builder": "^2.0.2", "partialify": "^3.1.1", + "request": "^2.56.0", "underscore": "~1.6.0", - "vue": "^0.10.3", - "autolinker": "^0.17.1", - "nw-builder": "^2.0.2", - "request": "^2.56.0" + "vue": "^0.10.3" }, "scripts": { "app": "nw public" diff --git a/public/fonts/Inconsolata.otf b/public/fonts/Inconsolata.otf new file mode 100644 index 0000000..3488898 Binary files /dev/null and b/public/fonts/Inconsolata.otf differ diff --git a/public/fonts/Karla/Karla-Bold.ttf b/public/fonts/Karla/Karla-Bold.ttf deleted file mode 100755 index 54f4e01..0000000 Binary files a/public/fonts/Karla/Karla-Bold.ttf and /dev/null differ diff --git a/public/fonts/Karla/Karla-BoldItalic.ttf b/public/fonts/Karla/Karla-BoldItalic.ttf deleted file mode 100755 index 1e3d92c..0000000 Binary files a/public/fonts/Karla/Karla-BoldItalic.ttf and /dev/null differ diff --git a/public/fonts/Karla/Karla-Italic.ttf b/public/fonts/Karla/Karla-Italic.ttf deleted file mode 100755 index ebb5c8c..0000000 Binary files a/public/fonts/Karla/Karla-Italic.ttf and /dev/null differ diff --git a/public/fonts/Karla/Karla-Regular.ttf b/public/fonts/Karla/Karla-Regular.ttf deleted file mode 100755 index edcd1eb..0000000 Binary files a/public/fonts/Karla/Karla-Regular.ttf and /dev/null differ diff --git a/public/fonts/Karla/OFL.txt b/public/fonts/Karla/OFL.txt deleted file mode 100755 index 97b67de..0000000 --- a/public/fonts/Karla/OFL.txt +++ /dev/null @@ -1,92 +0,0 @@ -Copyright (c) 2011-2012, Jonathan Pinhorn (jonpinhorn.typedesign@gmail.com), with Reserved Font Names 'Karla' -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/Montserrat-Bold.ttf b/public/fonts/Montserrat-Bold.ttf new file mode 100755 index 0000000..ae33a45 Binary files /dev/null and b/public/fonts/Montserrat-Bold.ttf differ diff --git a/public/fonts/Montserrat-Regular.ttf b/public/fonts/Montserrat-Regular.ttf new file mode 100755 index 0000000..5b4b5af Binary files /dev/null and b/public/fonts/Montserrat-Regular.ttf differ diff --git a/public/fonts/Source_Code_Pro/OFL.txt b/public/fonts/Source_Code_Pro/OFL.txt deleted file mode 100755 index 00585f4..0000000 --- a/public/fonts/Source_Code_Pro/OFL.txt +++ /dev/null @@ -1,92 +0,0 @@ -Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Black.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Black.ttf deleted file mode 100755 index ea73e60..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Black.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf deleted file mode 100755 index a56f1fa..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Bold.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf deleted file mode 100755 index f409b71..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-ExtraLight.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Light.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Light.ttf deleted file mode 100755 index 51eb963..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Light.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf deleted file mode 100755 index 1ee45eb..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Medium.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf deleted file mode 100755 index b2cff92..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Regular.ttf and /dev/null differ diff --git a/public/fonts/Source_Code_Pro/SourceCodePro-Semibold.ttf b/public/fonts/Source_Code_Pro/SourceCodePro-Semibold.ttf deleted file mode 100755 index b425f9c..0000000 Binary files a/public/fonts/Source_Code_Pro/SourceCodePro-Semibold.ttf and /dev/null differ diff --git a/public/images/close-button.svg b/public/images/close-button.svg new file mode 100644 index 0000000..96fd02b --- /dev/null +++ b/public/images/close-button.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/collapsed.svg b/public/images/collapsed.svg index 3aeed68..45940ac 100644 --- a/public/images/collapsed.svg +++ b/public/images/collapsed.svg @@ -1,13 +1,9 @@ - + - + viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"> - + diff --git a/public/images/consoleH.svg b/public/images/consoleH.svg new file mode 100644 index 0000000..adaa91d --- /dev/null +++ b/public/images/consoleH.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/consoleV.svg b/public/images/consoleV.svg new file mode 100644 index 0000000..60ffa29 --- /dev/null +++ b/public/images/consoleV.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/expanded.svg b/public/images/expanded.svg index 2304143..30b50a3 100644 --- a/public/images/expanded.svg +++ b/public/images/expanded.svg @@ -1,13 +1,9 @@ - + - + viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"> - + diff --git a/public/images/minus-button.svg b/public/images/minus-button.svg new file mode 100644 index 0000000..fee3645 --- /dev/null +++ b/public/images/minus-button.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/public/images/p5-logo.png b/public/images/p5-logo.png new file mode 100644 index 0000000..7af1a0d Binary files /dev/null and b/public/images/p5-logo.png differ diff --git a/public/images/play-button-flip.svg b/public/images/play-button-flip.svg new file mode 100644 index 0000000..a6d4893 --- /dev/null +++ b/public/images/play-button-flip.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/play-button.svg b/public/images/play-button.svg new file mode 100644 index 0000000..33338d2 --- /dev/null +++ b/public/images/play-button.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/play-gray.svg b/public/images/play-gray.svg deleted file mode 100644 index 26051ff..0000000 --- a/public/images/play-gray.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/public/images/play-green.svg b/public/images/play-green.svg deleted file mode 100644 index 2775c72..0000000 --- a/public/images/play-green.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/public/images/plus-button.svg b/public/images/plus-button.svg new file mode 100644 index 0000000..7229576 --- /dev/null +++ b/public/images/plus-button.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/public/images/settings-button-flip.svg b/public/images/settings-button-flip.svg new file mode 100644 index 0000000..def0c8f --- /dev/null +++ b/public/images/settings-button-flip.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/public/images/settings-button.svg b/public/images/settings-button.svg new file mode 100644 index 0000000..0cafdd8 --- /dev/null +++ b/public/images/settings-button.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/public/images/stop-button-flip.svg b/public/images/stop-button-flip.svg new file mode 100644 index 0000000..4ffcc6d --- /dev/null +++ b/public/images/stop-button-flip.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/images/stop-button.svg b/public/images/stop-button.svg new file mode 100644 index 0000000..fca61b3 --- /dev/null +++ b/public/images/stop-button.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/public/index.html b/public/index.html index d05f8c9..c52c96a 100755 --- a/public/index.html +++ b/public/index.html @@ -6,12 +6,24 @@ -
      -
      -
      -
      -
      -
      +
      + + +

      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      diff --git a/public/mode_assets/p5/empty_project/libraries/p5.dom.js b/public/mode_assets/p5/empty_project/libraries/p5.dom.js index a4985cb..d4b8460 100755 --- a/public/mode_assets/p5/empty_project/libraries/p5.dom.js +++ b/public/mode_assets/p5/empty_project/libraries/p5.dom.js @@ -1,4 +1,4 @@ -/*! p5.dom.js v0.2.6 November 24, 2015 */ +/*! p5.dom.js v0.2.9 March 3, 2016 */ /** *

      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, @@ -54,7 +54,7 @@ * function setup() { * createCanvas(100,100); * //translates canvas 50px down - * select('canvas').translate(0,50); + * select('canvas').position(100, 100); * } *

      *
      @@ -566,7 +566,7 @@ * 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 + * @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() { @@ -646,7 +646,7 @@ }; return self }; - + /** * Creates an <input></input> element in the DOM for text input. * Use .size() to set the display length of the box. @@ -729,7 +729,7 @@ } } } - + // Now let's handle when a file was selected elt.addEventListener('change', handleFileSelect, false); return addElement(elt, this); @@ -809,7 +809,7 @@ * 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 this - * page for further information about supported formats. + * page for further information about supported formats. * * @method createAudio * @param {String|Array} src path to an audio file, or array of paths for @@ -849,7 +849,7 @@ * @method createCapture * @param {String|Constant|Object} type type of capture, either VIDEO or * AUDIO if none specified, default both, - * or a Constraints boject + * or a Constraints object * @param {Function} callback function to be called once * stream has loaded * @return {Object/p5.Element} capture video p5.Element @@ -1160,91 +1160,51 @@ } }; - /** - * Translates an element with css transforms in either 2d (if 2 arguments given) - * or 3d (if 3 arguments given) space. - * @method translate - * @param {Number} x x-position in px - * @param {Number} y y-position in px - * @param {Number} [z] z-position in px - * @param {Number} [perspective] sets the perspective of the parent element in px, - * default value set to 1000px - * @return {Object/p5.Element} - * @example - *
      - * function setup() { - * var cnv = createCanvas(100,100); - * //translates canvas 50px down - * cnv.translate(0,50); - * } - *
      - */ - p5.Element.prototype.translate = function(){ + /* Helper method called by p5.Element.style() */ + p5.Element.prototype._translate = function(){ this.elt.style.position = 'absolute'; - if (arguments.length === 2){ - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); + // 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)'; - this.elt.style.transform += style; - }else if (arguments.length === 3){ - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); + } else if (arguments.length > 2) { this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)'; - this.elt.style.transform += style; - this.elt.parentElement.style.perspective = '1000px'; - }else if (arguments.length === 4){ - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)'; - this.elt.style.transform += style; - this.elt.parentElement.style.perspective = arguments[3]+'px'; + if (arguments.length === 3) { + this.elt.parentElement.style.perspective = '1000px'; + } else { + this.elt.parentElement.style.perspective = arguments[3]+'px'; + } } - return this; + // add any extra transform styling back on end + this.elt.style.transform += transform; + return this; }; - /** - * Rotates an element with css transforms in either 2d (if 2 arguments given) - * or 3d (if 3 arguments given) space. - * @method rotate - * @param {Number} x amount of degrees to rotate the element along the x-axis in deg - * @param {Number} [y] amount of degrees to rotate the element along the y-axis in deg - * @param {Number} [z] amount of degrees to rotate the element along the z-axis in deg - * @return {Object/p5.Element} - * @example - *
      - * var x = 0, - * y = 0, - * z = 0; - * - * function draw(){ - * x+=.5 % 360; - * y+=.5 % 360; - * z+=.5 % 360; - * //rotates p5.js logo .5 degrees on every axis each frame. - * select('canvas').rotate(x,y,z); - * } - *
      - */ - p5.Element.prototype.rotate = function(){ + /* 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){ - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); this.elt.style.transform = 'rotate('+arguments[0]+'deg)'; - this.elt.style.transform += style; }else if (arguments.length === 2){ - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)'; - this.elt.style.transform += style; }else if (arguments.length === 3){ - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); this.elt.style.transform = 'rotateX('+arguments[0]+'deg)'; this.elt.style.transform += 'rotateY('+arguments[1]+'deg)'; this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)'; - this.elt.style.transform += style; } - return this; + // add remaining transform back on + this.elt.style.transform += transform; + return this; }; /** @@ -1269,15 +1229,37 @@ * var myDiv = createDiv("I like pandas."); * myDiv.style("font-size", "18px"); * myDiv.style("color", "#ff0000"); + *
      + *
      * var col = color(25,23,200,50); - * createButton('button').style("background-color", col); + * var button = createButton("button"); + * button.style("background-color", col); + * button.position(10, 10); + *
      + *
      + * var myDiv = createDiv("I like lizards."); + * myDiv.style("position", 20, 20); + * myDiv.style("rotate", 45); + *
      + *
      + * var myDiv; + * function setup() { + * background(200); + * myDiv = createDiv("I like gray."); + * myDiv.position(20, 20); + * } + * + * function draw() { + * myDiv.style("font-size", mouseX+"px"); + * } *
      */ p5.Element.prototype.style = function(prop, val) { var self = this; - if (val instanceof p5.Color) + 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) { @@ -1294,54 +1276,15 @@ } } } else { - if (prop === 'rotate'){ - if (arguments.length === 2) { - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'rotate(' + arguments[0] + 'deg)'; - this.elt.style.transform += style; - } else if (arguments.length === 3) { - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'rotate(' + arguments[0] + 'deg, ' + arguments[1] + 'deg)'; - this.elt.style.transform += style; - } else if (arguments.length === 4) { - var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); - style = style.replace(/rotate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'rotateX(' + arguments[0] + 'deg)'; - this.elt.style.transform += 'rotateY(' + arguments[1] + 'deg)'; - this.elt.style.transform += 'rotateZ(' + arguments[2] + 'deg)'; - this.elt.style.transform += style; - } - } else if (prop === 'translate') { - if (arguments.length === 3) { - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'translate(' + arguments[0] + 'px, ' + arguments[1] + 'px)'; - this.elt.style.transform += style; - } else if (arguments.length === 4) { - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'translate3d(' + arguments[0] + 'px,' + arguments[1] + 'px,' + arguments[2] + 'px)'; - this.elt.style.transform += style; - this.elt.parentElement.style.perspective = '1000px'; - } else if (arguments.length === 5) { - var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); - style = style.replace(/translate[X-Z]?\(.*\)/g, ''); - this.elt.style.transform = 'translate3d(' + arguments[0] + 'px,' + arguments[1] + 'px,' + arguments[2] + 'px)'; - this.elt.style.transform += style; - this.elt.parentElement.style.perspective = arguments[3] + 'px'; - } - } else if (prop === 'position') { - this.elt.style.left = arguments[1] + 'px'; - this.elt.style.top = arguments[2] + 'px'; - this.x = arguments[1]; - this.y = arguments[2]; + 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); + this[prop] = parseInt(numVal, 10); // pend: is this necessary? } } } @@ -1363,7 +1306,7 @@ * @example *
      * var myDiv = createDiv("I like pandas."); - *myDiv.attribute("align", "center"); + * myDiv.attribute("align", "center"); *
      */ p5.Element.prototype.attribute = function(attr, value) { @@ -1383,6 +1326,29 @@ * @method value * @param {String|Number} [value] * @return {String|Object/p5.Element} value of element if no value is specified or p5.Element + * @example + *
      + * // gets the value + * var inp; + * function setup() { + * inp = createInput(''); + * } + * + * function mousePressed() { + * print(inp.value()); + * } + *
      + *
      + * // sets the value + * var inp; + * function setup() { + * inp = createInput('myValue'); + * } + * + * function mousePressed() { + * inp.value("myValue"); + * } + *
      */ p5.Element.prototype.value = function() { if (arguments.length > 0) { @@ -1402,6 +1368,12 @@ * * @method show * @return {Object/p5.Element} + * @example + *
      + * var div = createDiv('div'); + * div.attribute("display", "none"); + * div.show(); // turns display to block + *
      */ p5.Element.prototype.show = function() { this.elt.style.display = 'block'; @@ -1413,6 +1385,11 @@ * * @method hide * @return {Object/p5.Element} + * @example + *
      + * var div = createDiv('this is a div'); + * div.hide(); + *
      */ p5.Element.prototype.hide = function() { this.elt.style.display = 'none'; @@ -1429,6 +1406,11 @@ * @param {Number} [w] width of the element * @param {Number} [h] height of the element * @return {Object/p5.Element} + * @example + *
      + * var div = createDiv('this is a div'); + * div.size(100, 100); + *
      */ p5.Element.prototype.size = function(w, h) { if (arguments.length === 0){ @@ -1660,6 +1642,25 @@ } }; + /** + * 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. @@ -1688,12 +1689,12 @@ }; 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) { - this.canvas = document.createElement('canvas'); - this.drawingContext = this.canvas.getContext('2d'); - } - if (this.canvas.width !== this.elt.width) { + if (this.canvas.width !== this.elt.videoWidth) { this.canvas.width = this.elt.videoWidth; this.canvas.height = this.elt.videoHeight; this.width = this.canvas.width; @@ -1725,13 +1726,13 @@ * 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. + * callback. * @return {Object/p5.MediaElement} * @example *
      diff --git a/public/mode_assets/p5/empty_project/libraries/p5.js b/public/mode_assets/p5/empty_project/libraries/p5.js index 42a670d..43d7136 100644 --- a/public/mode_assets/p5/empty_project/libraries/p5.js +++ b/public/mode_assets/p5/empty_project/libraries/p5.js @@ -1,4 +1,4 @@ -/*! p5.js v0.4.20 December 11, 2015 */ +/*! 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 * - * // draw an ellipsoid with radius 200, 300 and 400 . + * // draw an ellipsoid with radius 200, 300 and 400 * function setup(){ * createCanvas(100, 100, WEBGL); * } @@ -5722,7 +5723,7 @@ p5.prototype.ellipsoid = function(radiusx, radiusy, radiusz, detail){ * @example *
      * - * //draw a spining sylinder with radius 200 and height 200 + * //draw a spinning cylinder with radius 200 and height 200 * function setup(){ * createCanvas(100, 100, WEBGL); * } @@ -5818,7 +5819,7 @@ p5.prototype.cylinder = function(radius, height, detail){ * @example *
      * - * //draw a spining cone with radius 200 and height 200 + * //draw a spinning cone with radius 200 and height 200 * function setup(){ * createCanvas(100, 100, WEBGL); * } @@ -5891,7 +5892,7 @@ p5.prototype.cone = function(radius, height, detail){ * @example *
      * - * //draw a spining torus with radius 200 and tube radius 60 + * //draw a spinning torus with radius 200 and tube radius 60 * function setup(){ * createCanvas(100, 100, WEBGL); * } @@ -5950,7 +5951,7 @@ p5.prototype.torus = function(radius, tubeRadius, detail){ * @example *
      * - * //draw a spining box with width, height and depth 200 + * //draw a spinning box with width, height and depth 200 * function setup(){ * createCanvas(100, 100, WEBGL); * } @@ -6327,7 +6328,7 @@ p5.Renderer3D.prototype.strokeWeight = function() { ////////////////////////////////////////////// p5.Renderer3D.prototype.fill = function(r, g, b, a) { - var color = this._pInst.color.apply(this._pInst, arguments); + var color = this._pInst.color.apply(this, arguments); var colorNormalized = color._array; this.curColor = colorNormalized; this.drawMode = 'fill'; @@ -6335,7 +6336,7 @@ p5.Renderer3D.prototype.fill = function(r, g, b, a) { }; p5.Renderer3D.prototype.stroke = function(r, g, b, a) { - var color = this._pInst.color.apply(this._pInst, arguments); + var color = this._pInst.color.apply(this, arguments); var colorNormalized = color._array; this.curColor = colorNormalized; this.drawMode = 'stroke'; @@ -8845,7 +8846,7 @@ p5.prototype.brightness = function(c) { * 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. - * + *

      * 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 @@ -8993,9 +8994,17 @@ 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) { - return new p5.Color(this, arguments[0]); + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments[0]); + } else { + return new p5.Color(this._renderer, arguments[0]); + } } else { - return new p5.Color(this, arguments); + if (this instanceof p5.Renderer) { + return new p5.Color(this, arguments); + } else { + return new p5.Color(this._renderer, arguments); + } } }; @@ -9067,7 +9076,7 @@ p5.prototype.hue = function(c) { * 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. - * + *

      * The way that colours are interpolated depends on the current color mode. * * @method lerpColor @@ -9271,11 +9280,11 @@ var color_conversion = _dereq_('./color_conversion'); * @class p5.Color * @constructor */ -p5.Color = function(pInst, vals) { +p5.Color = function(renderer, vals) { // Record color mode and maxes at time of construction. - this.mode = pInst._renderer._colorMode; - this.maxes = pInst._renderer._colorMaxes; + this.mode = renderer._colorMode; + this.maxes = renderer._colorMaxes; // Calculate normalized RGBA values. if (this.mode !== constants.RGB && @@ -9283,7 +9292,7 @@ p5.Color = function(pInst, vals) { this.mode !== constants.HSB) { throw new Error(this.mode + ' is an invalid colorMode.'); } else { - this._array = p5.Color._parseInputs.apply(pInst, vals); + this._array = p5.Color._parseInputs.apply(renderer, vals); } // Expose closest screen color. @@ -9668,8 +9677,8 @@ var colorPatterns = { */ p5.Color._parseInputs = function() { var numArgs = arguments.length; - var mode = this._renderer._colorMode; - var maxes = this._renderer._colorMaxes; + var mode = this._colorMode; + var maxes = this._colorMaxes; var results = []; if (numArgs >= 3) { // Argument is a list of component values. @@ -9977,6 +9986,18 @@ p5.prototype.background = function() { * @example *
      * + * // Clear the screen on mouse press. + * function setup() { + * createCanvas(100, 100); + * } + * + * function draw() { + * ellipse(mouseX, mouseY, 20, 20); + * } + * + * function mousePressed() { + * clear(); + * } * *
      */ @@ -9992,7 +10013,7 @@ p5.prototype.clear = function() { * 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. - * + *

      * Note: existing color objects remember the mode that they were created in, * so you can change modes as you like without affecting their appearance. * @@ -10093,9 +10114,11 @@ p5.prototype.colorMode = function() { * 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.) 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. + * in the range from 0 to 255). + *

      + * 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 @@ -10248,9 +10271,11 @@ p5.prototype.noStroke = function() { * 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). 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. + * the range from 0 to 255). + *

      + * 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 @@ -10693,14 +10718,14 @@ p5.prototype.point = function() { * clockwise or counter-clockwise around the defined shape. * * @method quad - * @param {type} x1 the x-coordinate of the first point - * @param {type} y1 the y-coordinate of the first point - * @param {type} x2 the x-coordinate of the second point - * @param {type} y2 the y-coordinate of the second point - * @param {type} x3 the x-coordinate of the third point - * @param {type} y3 the y-coordinate of the third point - * @param {type} x4 the x-coordinate of the fourth point - * @param {type} y4 the y-coordinate of the fourth point + * @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 *
      @@ -10770,11 +10795,12 @@ p5.prototype.quad = function() { * 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. If provided, 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. +* may be changed with the rectMode() function. +*

      +* 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. @@ -10920,24 +10946,24 @@ var constants = _dereq_('./constants'); /** * Modifies the location from which ellipses are drawn by changing the way * in which parameters given to ellipse() are interpreted. - * + *

      * 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. - * + *

      * 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. - * + *

      * 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. - * + *

      * 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. - * - * The parameter must be written in ALL CAPS because Processing is a + *

      + * The parameter must be written in ALL CAPS because Javascript is a * case-sensitive language. * * @method ellipseMode @@ -11005,24 +11031,24 @@ p5.prototype.noSmooth = function() { /** * Modifies the location from which rectangles are drawn by changing the way * in which parameters given to rect() are interpreted. - * + *

      * 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. - * + *

      * 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. - * + *

      * 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. - * + *

      * 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. - * - * The parameter must be written in ALL CAPS because Processing is a + *

      + * The parameter must be written in ALL CAPS because Javascript is a * case-sensitive language. * * @method rectMode @@ -11524,9 +11550,10 @@ var p5 = function(sketch, node, sync) { * 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. Note: Variables declared - * within setup() are not accessible within other functions, including - * draw(). + * be called again after its initial execution. + *

      + * Note: Variables declared within setup() are not accessible within other + * functions, including draw(). * * @method setup * @example @@ -11550,15 +11577,15 @@ var p5 = function(sketch, node, sync) { * 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. - * + *

      * 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. - * + *

      * The number of times draw() executes in each second may be controlled with * the frameRate() function. - * + *

      * 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 @@ -11730,6 +11757,9 @@ var p5 = function(sketch, node, sync) { 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); + } } } @@ -11778,8 +11808,8 @@ var p5 = function(sketch, node, sync) { } this._setProperty('frameCount', this.frameCount + 1); - this._updatePMouseCoords(); - this._updatePTouchCoords(); + this._updateMouseCoords(); + this._updateTouchCoords(); this.redraw(); this._frameRate = 1000.0/(now - this._lastFrameTime); this._lastFrameTime = now; @@ -12362,7 +12392,7 @@ p5.prototype.curvePoint = function(a, b, c, d, t) { /** * 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 + * and b and c are the control points. * * @method curveTangent * @param {Number} a coordinate of first point on the curve @@ -12430,7 +12460,7 @@ if (window.console && console.log) { * 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 (+). - * + *

      * 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 @@ -12442,7 +12472,7 @@ if (window.console && console.log) { * @example *
      * var x = 10; - * print("The value of x is "+x); + * print("The value of x is " + x); * // prints "The value of x is 10" *
      */ @@ -12574,12 +12604,13 @@ p5.prototype.cursor = function(type, x, y) { * 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). - * + *

      * Calling frameRate() with no arguments returns the current framerate. This * is the same as getFrameRate(). - * + *

      * 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 @@ -12782,8 +12813,9 @@ p5.prototype.height = 0; * be called on user input, for example, on mouse press like the example * below. * - * @method fullScreen - * @param {Boolean} [val] whether the sketch should be fullscreened or not + * @method fullscreen + * @param {Boolean} [val] whether the sketch should be in fullscreen mode + * or not * @return {Boolean} current fullscreen state * @example *
      @@ -12794,14 +12826,14 @@ p5.prototype.height = 0; * } * function mousePressed() { * if (mouseX > 0 && mouseX < 100 && mouseY > 0 && mouseY < 100) { - * var fs = fullScreen(); - * fullScreen(!fs); + * var fs = fullscreen(); + * fullscreen(!fs); * } * } * *
      */ -p5.prototype.fullScreen = function(val) { +p5.prototype.fullscreen = function(val) { // no arguments, return fullscreen or not if (typeof val === 'undefined') { return document.fullscreenElement || @@ -13257,28 +13289,111 @@ function friendlyWelcome() { report(str, 'println', '#4DB200'); // auto dark green } */ -// This is a list of p5 functions/variables that are commonly 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 +// 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 = [ - 'color', - 'random' -]; +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(); + } -function helpForMisusedAtTopLevelCode(e) { - misusedAtTopLevelCode.forEach(function(name) { - if (e.message && e.message.indexOf(name) !== -1) { - console.log('%c Did you just try to use p5.js\'s \'' + name + '\' ' + - 'function or variable? If so, you may want to ' + - 'move it into your sketch\'s setup() function.', - 'color: #B40033' /* Dark magenta */); + // 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); @@ -13293,7 +13408,7 @@ if (document.readyState !== 'complete') { module.exports = p5; -},{"./core":48}],52:[function(_dereq_,module,exports){ +},{"./constants":47,"./core":48}],52:[function(_dereq_,module,exports){ /** * @module DOM * @submodule DOM @@ -13477,6 +13592,41 @@ p5.Element.prototype.mousePressed = function (fxn) { * @param {Function} fxn function to be fired when mouse wheel is * scrolled over the element. * @return {p5.Element} + * @example + *
      + * 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; + * } + * } + *
      + * */ p5.Element.prototype.mouseWheel = function (fxn) { attachListener('wheel', fxn, this); @@ -13492,6 +13642,37 @@ p5.Element.prototype.mouseWheel = function (fxn) { * @param {Function} fxn function to be fired when mouse is * released over the element. * @return {p5.Element} + * @example + *
      + * 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); + * } + *
      + * */ p5.Element.prototype.mouseReleased = function (fxn) { attachListener('mouseup', fxn, this); @@ -13509,6 +13690,36 @@ p5.Element.prototype.mouseReleased = function (fxn) { * @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); + * } + *
      + * */ p5.Element.prototype.mouseClicked = function (fxn) { attachListener('click', fxn, this); @@ -13524,6 +13735,43 @@ p5.Element.prototype.mouseClicked = function (fxn) { * @param {Function} fxn function to be fired when mouse is * moved over the element. * @return {p5.Element} + * @example + *
      + * 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; + * } + * } + *
      + * */ p5.Element.prototype.mouseMoved = function (fxn) { attachListener('mousemove', fxn, this); @@ -13540,6 +13788,29 @@ p5.Element.prototype.mouseMoved = function (fxn) { * @param {Function} fxn function to be fired when mouse is * moved over the element. * @return {p5.Element} + * @example + *
      + * 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; + * } + * } + *
      + * */ p5.Element.prototype.mouseOver = function (fxn) { attachListener('mouseover', fxn, this); @@ -13556,6 +13827,52 @@ p5.Element.prototype.mouseOver = function (fxn) { * @param {Function} fxn function to be fired when the value of an * element changes. * @return {p5.Element} + * @example + *
      + * 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); + * } + *
      + *
      + * 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(); + * } + * } + *
      */ p5.Element.prototype.changed = function (fxn) { attachListener('change', fxn, this); @@ -13572,6 +13889,19 @@ p5.Element.prototype.changed = function (fxn) { * @method input * @param {Function} fxn function to be fired on user input. * @return {p5.Element} + * @example + *
      + * // Open your console to see the output + * function setup() { + * var inp = createInput(''); + * inp.input(myInputEvent); + * } + * + * function myInputEvent() { + * console.log('you are typing: ', this.value()); + * } + *
      + * */ p5.Element.prototype.input = function (fxn) { attachListener('input', fxn, this); @@ -13587,20 +13917,6 @@ p5.Element.prototype.input = function (fxn) { * @param {Function} fxn function to be fired when mouse is * moved off the element. * @return {p5.Element} - */ -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 *
      * var cnv; @@ -13608,18 +13924,55 @@ p5.Element.prototype.mouseOut = function (fxn) { * var g; * function setup() { * cnv = createCanvas(100, 100); - * cnv.touchStarted(changeGray); // attach listener for - * // canvas click only + * cnv.mouseOut(changeGray); * d = 10; - * g = 100; * } * * function draw() { - * background(g); * ellipse(width/2, height/2, d, d); * } * - * // this function fires with any touch anywhere + * function changeGray() { + * d = d + 10; + * if (d > 100) { + * d = 0; + * } + * } + *
      + * + */ +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 + *
      + * 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; * } @@ -14143,34 +14496,9 @@ var filters = _dereq_('../image/filters'); _dereq_('./p5.Renderer'); /** - * 2D graphics renderer class. Can also be used as an off-screen - * graphics buffer. A p5.Renderer2D object can be constructed - * with the createRenderer2D() function. The fields and methods - * for this class are extensive, but mirror the normal drawing API for p5. - * - * @class p5.Renderer2D - * @constructor - * @extends p5.Renderer - * @param {String} elt DOM node that is wrapped - * @param {Object} [pInst] pointer to p5 instance - * @example - *
      - * - * var pg; - * function setup() { - * createCanvas(100, 100); - * pg = createRenderer2D(40, 40); - * } - * function draw() { - * background(200); - * pg.background(100); - * pg.noStroke(); - * pg.ellipse(pg.width/2, pg.height/2, 50, 50); - * image(pg, 9, 30); - * image(pg, 51, 30); - * } - * - *
      + * p5.Renderer2D + * The 2D graphics canvas renderer class. + * extends p5.Renderer */ var styleEmpty = 'rgba(0,0,0,0)'; // var alphaThreshold = 0.00125; // minimum visible @@ -14212,7 +14540,7 @@ p5.Renderer2D.prototype.background = function() { } else { var curFill = this.drawingContext.fillStyle; // create background rect - var color = this._pInst.color.apply(this._pInst, arguments); + 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); @@ -14229,13 +14557,13 @@ p5.Renderer2D.prototype.clear = function() { p5.Renderer2D.prototype.fill = function() { var ctx = this.drawingContext; - var color = this._pInst.color.apply(this._pInst, arguments); + 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._pInst, arguments); + var color = this._pInst.color.apply(this, arguments); ctx.strokeStyle = color.toString(); }; @@ -14248,7 +14576,7 @@ p5.Renderer2D.prototype.image = var cnv; try { if (this._tint) { - if (img instanceof p5.MediaElement) { + if (p5.MediaElement && img instanceof p5.MediaElement) { img.loadPixels(); } if (img.canvas) { @@ -14420,6 +14748,9 @@ p5.Renderer2D.prototype.loadPixels = function () { }; 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); @@ -15440,7 +15771,8 @@ var defaultId = 'defaultCanvas0'; // this gets set again in createCanvas * 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).
      + * you could use createGraphics (hidden by default but it can be shown). + *

      * 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. @@ -15448,7 +15780,7 @@ var defaultId = 'defaultCanvas0'; // this gets set again in createCanvas * @method createCanvas * @param {Number} w width of the canvas * @param {Number} h height of the canvas - * @param optional:{String} renderer 'p2d' | 'webgl' + * @param {String} [renderer] 'p2d' | 'webgl' * @return {Object} canvas generated * @example *
      @@ -15771,17 +16103,20 @@ window.performance.now = (function(){ * 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') { - //Firefox and Chrome - Uint8ClampedArray.prototype.slice = Array.prototype.slice; + 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 @@ -15798,21 +16133,20 @@ p5.prototype.exit = function() { throw 'exit() not implemented, see remove()'; }; /** - *

      Stops p5.js from continuously executing the code within draw(). + * 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. - *

      - * - *

      When noLoop() is used, it's not possible to manipulate or access the + *

      + * 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.

      - * - *

      Note that if the sketch is resized, redraw() will be called to update + * like saveFrame() or loadPixels() may not be used. + *

      + * 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.

      + * would enter an odd state until loop() was called. * * @method noLoop * @example @@ -16033,12 +16367,12 @@ p5.prototype.popStyle = function() { * 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. - * + *

      * 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). - * + *

      * The redraw() function does not work properly when called inside draw(). * To enable/disable animations, use loop() and noLoop(). * @@ -16075,11 +16409,11 @@ p5.prototype.redraw = function () { this._registeredMethods.pre.forEach(function (f) { f.call(self); }); - this.pop(); userDraw(); this._registeredMethods.post.forEach(function (f) { f.call(self); }); + this.pop(); } }; @@ -16165,14 +16499,14 @@ p5.prototype.resetMatrix = function() { * 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. - * + *

      * 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. - * + *

      * Technically, rotate() multiplies the current transformation matrix * by a rotation matrix. This function can be further controlled by * the push() and pop(). @@ -16286,12 +16620,12 @@ p5.prototype.rotateZ = function(rad) { * 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%. - * + *

      * 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. - * + *

      * 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(). @@ -16362,13 +16696,13 @@ p5.prototype.scale = function() { * 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. - * + *

      * 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. - * + *

      * Technically, shearX() multiplies the current transformation matrix by a * rotation matrix. This function can be further controlled by the * push() and pop() functions. @@ -16399,13 +16733,13 @@ p5.prototype.shearX = function(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. - * + *

      * 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. - * + *

      * Technically, shearY() multiplies the current transformation matrix by a * rotation matrix. This function can be further controlled by the * push() and pop() functions. @@ -16435,7 +16769,7 @@ p5.prototype.shearY = function(angle) { * Specifies an amount to displace objects within the display window. * The x parameter specifies left/right translation, the y parameter * specifies up/down translation. - * + *

      * 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 @@ -16567,13 +16901,14 @@ p5.prototype.beginContour = function() { * 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. 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. - * + * specified, the shape can be any irregular polygon. + *

      + * 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. + *

      * 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(). @@ -16750,7 +17085,9 @@ p5.prototype.beginShape = function(kind) { * 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. The first time bezierVertex() is used within a + * line or shape. + *

      + * 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 @@ -16807,8 +17144,9 @@ p5.prototype.bezierVertex = function(x2, y2, x3, y3, x4, y4) { /** * 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(). The first and - * last points in a series of curveVertex() lines will be used to + * is no MODE parameter specified to beginShape(). + *

      + * 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 @@ -17185,6 +17523,27 @@ p5.prototype._updatePAccelerations = function(){ /** * The system variable rotationX always contains the rotation of the * device along the x axis. Value is represented as 0 to +/-180 degrees. + *

      + * 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 + *
      + * + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * + *
      * * @property rotationX */ @@ -17192,7 +17551,28 @@ 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 +/-180 degrees. + * device along the y axis. Value is represented as 0 to +/-90 degrees. + *

      + * 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 + *
      + * + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * //rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * + *
      * * @property rotationY */ @@ -17204,6 +17584,27 @@ p5.prototype.rotationY = 0; *

      * Unlike rotationX and rotationY, this variable is available for devices * with a built-in compass only. + *

      + * 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 + *
      + * + * function setup(){ + * createCanvas(100, 100, WEBGL); + * } + * + * function draw(){ + * background(200); + * rotateZ(radians(rotationZ)); + * //rotateX(radians(rotationX)); + * //rotateY(radians(rotationY)); + * box(200, 200, 200); + * } + * + *
      * * @property rotationZ */ @@ -17250,7 +17651,7 @@ 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 +/-180 degrees. + * is represented as 0 to +/-90 degrees. *

      * pRotationY can also be used with rotationY to determine the rotate * direction of the device along the Y-axis. @@ -17750,6 +18151,9 @@ p5.prototype.keyCode = 0; *
      */ 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); @@ -17798,6 +18202,7 @@ 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); @@ -17820,11 +18225,12 @@ p5.prototype._onkeyup = function (e) { * key pressed will be stored in the key variable. *

      * Because of how operating systems handle key repeats, holding down a key - * will cause multiple calls to keyTyped(), the rate is set by the operating - * system and how each computer is configured.

      - * 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. + * 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.

      + * 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 @@ -17847,7 +18253,11 @@ p5.prototype._onkeyup = function (e) { *
      */ 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') { @@ -17868,7 +18278,7 @@ p5.prototype._onblur = function (e) { }; /** - * The keyIsDown function checks if the key is currently down, i.e. pressed. + * 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 @@ -17927,6 +18337,17 @@ module.exports = p5; 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. @@ -18215,26 +18636,28 @@ p5.prototype.mouseButton = 0; p5.prototype.mouseIsPressed = false; p5.prototype.isMousePressed = false; // both are supported -p5.prototype._updateMouseCoords = function(e) { +p5.prototype._updateNextMouseCoords = function(e) { if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend') { - this._setProperty('mouseX', this.touchX); - this._setProperty('mouseY', this.touchY); + this._setProperty('_nextMouseX', this._nextTouchX); + this._setProperty('_nextMouseY', this._nextTouchY); } else { if(this._curElement !== null) { var mousePos = getMousePos(this._curElement.elt, e); - this._setProperty('mouseX', mousePos.x); - this._setProperty('mouseY', mousePos.y); + this._setProperty('_nextMouseX', mousePos.x); + this._setProperty('_nextMouseY', mousePos.y); } } this._setProperty('winMouseX', e.pageX); this._setProperty('winMouseY', e.pageY); }; -p5.prototype._updatePMouseCoords = function() { +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); }; @@ -18254,10 +18677,6 @@ p5.prototype._setMouseButton = function(e) { this._setProperty('mouseButton', constants.RIGHT); } else { this._setProperty('mouseButton', constants.LEFT); - if(e.type === 'touchstart' || e.type === 'touchmove') { - this._setProperty('mouseX', this.touchX); - this._setProperty('mouseY', this.touchY); - } } }; @@ -18266,7 +18685,7 @@ p5.prototype._setMouseButton = function(e) { * button is not pressed.

      * 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. + * behavior for this event, add "return false" to the end of the method. * * @method mouseMoved * @example @@ -18306,7 +18725,7 @@ p5.prototype._setMouseButton = function(e) { * touchMoved() function will be called instead if it is defined.

      * 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. + * behavior for this event, add "return false" to the end of the method. * * @method mouseDragged * @example @@ -18342,8 +18761,8 @@ p5.prototype._setMouseButton = function(e) { p5.prototype._onmousemove = function(e){ var context = this._isGlobal ? window : this; var executeDefault; - this._updateMouseCoords(e); - this._updateTouchCoords(e); + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); if (!this.isMousePressed) { if (typeof context.mouseMoved === 'function') { executeDefault = context.mouseMoved(e); @@ -18375,7 +18794,7 @@ p5.prototype._onmousemove = function(e){ * called instead if it is defined.

      * 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. + * behavior for this event, add "return false" to the end of the method. * * @method mousePressed * @example @@ -18415,8 +18834,8 @@ p5.prototype._onmousedown = function(e) { this._setProperty('isMousePressed', true); this._setProperty('mouseIsPressed', true); this._setMouseButton(e); - this._updateMouseCoords(e); - this._updateTouchCoords(e); + this._updateNextMouseCoords(e); + this._updateNextTouchCoords(e); if (typeof context.mousePressed === 'function') { executeDefault = context.mousePressed(e); if(executeDefault === false) { @@ -18436,7 +18855,7 @@ p5.prototype._onmousedown = function(e) { * function will be called instead if it is defined.

      * 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. + * behavior for this event, add "return false" to the end of the method. * * * @method mouseReleased @@ -18498,7 +18917,7 @@ p5.prototype._ondragover = p5.prototype._onmousemove; * pressed and then released.

      * 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. + * behavior for this event, add "return false" to the end of the method. * * @method mouseClicked * @example @@ -18544,7 +18963,7 @@ p5.prototype._onclick = function(e) { }; /** - * The function mouseWheel is executed every time a vertical mouse wheel + * 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.

      * The event.delta property returns the amount the mouse wheel @@ -18553,32 +18972,32 @@ p5.prototype._onclick = function(e) { * are inverted).

      * 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.

      - * 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. + * "return false" to the end of the method.

      + * 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 - *
      - * - * 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; - * } - * - *
      + * @example + *
      + * + * 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; + * } + * + *
      */ p5.prototype._onwheel = function(e) { var context = this._isGlobal ? window : this; @@ -18605,6 +19024,17 @@ module.exports = p5; 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 @@ -18661,17 +19091,17 @@ p5.prototype.touches = []; */ p5.prototype.touchIsDown = false; -p5.prototype._updateTouchCoords = function(e) { +p5.prototype._updateNextTouchCoords = function(e) { if(e.type === 'mousedown' || e.type === 'mousemove' || e.type === 'mouseup'){ - this._setProperty('touchX', this.mouseX); - this._setProperty('touchY', this.mouseY); + 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('touchX', touchInfo.x); - this._setProperty('touchY', touchInfo.y); + this._setProperty('_nextTouchX', touchInfo.x); + this._setProperty('_nextTouchY', touchInfo.y); var touches = []; for(var i = 0; i < e.touches.length; i++){ @@ -18682,9 +19112,11 @@ p5.prototype._updateTouchCoords = function(e) { } }; -p5.prototype._updatePTouchCoords = function() { +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) { @@ -18701,10 +19133,10 @@ function getTouchInfo(canvas, e, i) { /** * 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. 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. + * function will be called instead if it is defined.

      + * 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 @@ -18741,8 +19173,8 @@ function getTouchInfo(canvas, e, i) { p5.prototype._ontouchstart = function(e) { var context = this._isGlobal ? window : this; var executeDefault; - this._updateTouchCoords(e); - this._updateMouseCoords(e); + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); this._setProperty('touchIsDown', true); if(typeof context.touchStarted === 'function') { executeDefault = context.touchStarted(e); @@ -18760,10 +19192,11 @@ p5.prototype._ontouchstart = function(e) { /** * The touchMoved() function is called every time a touch move is registered. - * If no touchStarted() function is defined, the mouseDragged() function will - * be called instead if it is defined. 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. + * If no touchMoved() function is defined, the mouseDragged() function will + * be called instead if it is defined.

      + * 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 @@ -18799,8 +19232,8 @@ p5.prototype._ontouchstart = function(e) { p5.prototype._ontouchmove = function(e) { var context = this._isGlobal ? window : this; var executeDefault; - this._updateTouchCoords(e); - this._updateMouseCoords(e); + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); if (typeof context.touchMoved === 'function') { executeDefault = context.touchMoved(e); if(executeDefault === false) { @@ -18816,10 +19249,11 @@ p5.prototype._ontouchmove = function(e) { /** * The touchEnded() function is called every time a touch ends. If no - * touchStarted() function is defined, the mouseReleased() function will be - * called instead if it is defined. 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. + * touchEnded() function is defined, the mouseReleased() function will be + * called instead if it is defined.

      + * 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 @@ -18854,8 +19288,8 @@ p5.prototype._ontouchmove = function(e) { *
      */ p5.prototype._ontouchend = function(e) { - this._updateTouchCoords(e); - this._updateMouseCoords(e); + this._updateNextTouchCoords(e); + this._updateNextMouseCoords(e); if (this.touches.length === 0) { this._setProperty('touchIsDown', false); } @@ -19507,7 +19941,7 @@ 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. - * + *

      * .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 @@ -19517,9 +19951,8 @@ var frames = []; * more info. It may also be simpler to use set() or get(). *

      * 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. + * 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 @@ -19578,7 +20011,7 @@ p5.prototype.createImage = function(width, height) { }; /** - * Save the current canvas as an image. In Safari, will open the + * 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. @@ -19703,12 +20136,17 @@ p5.prototype.saveCanvas = function() { * all of the images that have just been created. * * @method saveFrames - * @param {[type]} filename [description] - * @param {[type]} extension [description] - * @param {[type]} _duration [description] - * @param {[type]} _fps [description] - * @param {[Function]} callback [description] - * @return {[type]} [description] + * @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; @@ -19980,8 +20418,8 @@ p5.prototype.image = if (img.elt && img.elt.videoWidth && !img.canvas) { // video no canvas var actualW = img.elt.videoWidth; var actualH = img.elt.videoHeight; - dWidth = sWidth || img.width; - dHeight = sHeight || img.width*actualH/actualW; + dWidth = sWidth || img.elt.width; + dHeight = sHeight || img.elt.width*actualH/actualW; sWidth = actualW; sHeight = actualH; } else { @@ -20015,12 +20453,12 @@ p5.prototype.image = /** * Sets the fill value for displaying images. Images can be tinted to * specified colors or made transparent by including an alpha value. - * + *

      * 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()). - * + *

      * 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. @@ -20149,10 +20587,11 @@ p5.prototype._getTintedImageCanvas = function(img) { * 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. - * + *

      * 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. + *

      * 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. @@ -20236,14 +20675,17 @@ var Filters = _dereq_('./filters'); /** * Creates a new p5.Image. A p5.Image is a canvas backed representation of an - * image. p5 can display .gif, .jpg and .png images. Images may be displayed + * image. + *

      + * 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. The methods described below allow - * easy access to the image's pixels and alpha channel and simplify the - * process of compositing. - * + * values for every pixel in the image. + *

      + * The methods described below allow easy access to the image's pixels and + * alpha channel and simplify the process of compositing. + *

      * 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. * @@ -20381,6 +20823,28 @@ p5.Image.prototype._setProperty = function (prop, value) { * Loads the pixels data for this image into the [pixels] attribute. * * @method loadPixels + * @example + *
      + * 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); + * } + *
      */ p5.Image.prototype.loadPixels = function(){ p5.Renderer2D.prototype.loadPixels.call(this); @@ -20399,6 +20863,28 @@ p5.Image.prototype.loadPixels = function(){ * underlying canvas * @param {Integer|undefined} h height of the target update area for the * underlying canvas + * @example + *
      + * 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); + * } + *
      */ p5.Image.prototype.updatePixels = function(x, y, w, h){ p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); @@ -20421,6 +20907,25 @@ p5.Image.prototype.updatePixels = function(x, y, w, h){ * @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 + *
      + * 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 + *
      */ p5.Image.prototype.get = function(x, y, w, h){ return p5.Renderer2D.prototype.get.call(this, x, y, w, h); @@ -20496,8 +21001,16 @@ p5.Image.prototype.resize = function(width, height){ // 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. - width = width || this.canvas.width; - height = height || this.canvas.height; + + // 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; @@ -20541,6 +21054,25 @@ p5.Image.prototype.resize = function(width, height){ * @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 + *
      + * 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); + * } + *
      */ p5.Image.prototype.copy = function () { p5.prototype.copy.apply(this, arguments); @@ -20612,6 +21144,22 @@ p5.Image.prototype.mask = function(p5Image) { * opaque see Filters.js for docs on each available * filter * @param {Number|undefined} value + * @example + *
      + * 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); + * } + *
      */ p5.Image.prototype.filter = function(operation, value) { Filters.apply(this.canvas, Filters[operation.toLowerCase()], value); @@ -20651,9 +21199,27 @@ p5.Image.prototype.blend = function() { * 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' + * @method save + * @param {String} filename give your file a name + * @param {String} extension 'png' or 'jpg' + * @example + *
      + * var photo; + * + * function preload() { + * photo = loadImage("assets/rockies.jpg"); + * } + * + * function draw() { + * image(photo, 0, 0); + * } + * + * function keyTyped() { + * if (key == 's') { + * photo.save("photo", "png"); + * } + * } + *
      */ p5.Image.prototype.save = function(filename, extension) { var mimeType; @@ -20724,16 +21290,18 @@ _dereq_('../color/p5.Color'); * 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. 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): - *
      var d = pixelDensity;
      + * retina display, 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): + *
      + * 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));
      + *     idx = 4 * ((y * d + j) * width * d + (x * d + i));
        *     pixels[idx] = r;
        *     pixels[idx+1] = g;
        *     pixels[idx+2] = b;
      @@ -20741,7 +21309,8 @@ _dereq_('../color/p5.Color');
        *   }
        * }
        * 
      - * While the above method is complex, it is flexible enough to work with + * + *

      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 @@ -20754,7 +21323,7 @@ _dereq_('../color/p5.Color'); * Note that this is not a standard javascript array. This means that * standard javascript functions such as slice() or * arrayCopy() do not - * work. + * work.

      * * @property pixels[] * @example @@ -20762,7 +21331,7 @@ _dereq_('../color/p5.Color'); * * var pink = color(255, 102, 204); * loadPixels(); - * var d = pixelDensity; + * var d = pixelDensity(); * var halfImage = 4 * (width * d) * (height/2 * d); * for (var i = 0; i < halfImage; i+=4) { * pixels[i] = red(pink); @@ -21055,18 +21624,19 @@ p5.prototype.filter = function(operation, value) { * 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(). - * + *

      * 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. - * + *

      * 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 - * [pixels[(y*width*d+x)*d], + * [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] ]. + * pixels[(y*width*d+x)*d+3]]. + *

      * See the reference for pixels[] for more information. * * @method get @@ -21126,7 +21696,7 @@ p5.prototype.get = function(x, y, w, h){ * * function setup() { * image(img, 0, 0); - * var d = pixelDensity; + * var d = pixelDensity(); * var halfImage = 4 * (img.width * d) * (img.height/2 * d); * loadPixels(); @@ -21234,8 +21804,8 @@ p5.prototype.set = function (x, y, imgOrCol) { * * function setup() { * image(img, 0, 0); - * var halfImage = 4 * (img.width * pixelDensity) * - * (img.height * pixelDensity/2); + * var halfImage = 4 * (img.width * pixelDensity()) * + * (img.height * pixelDensity()/2); * loadPixels(); * for (var i = 0; i < halfImage; i++) { * pixels[i+halfImage] = pixels[i]; @@ -21540,12 +22110,12 @@ p5.prototype.loadJSON = function () { * 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. - * + *

      * 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. - * + *

      * This method is asynchronous, meaning it may not finish before the next * line in your sketch is executed. * @@ -21860,8 +22430,8 @@ p5.prototype.loadTable = function (path) { if (header) { t.columns = records.shift(); } else { - for (i = 0; i < records.length; i++) { - t.columns[i] = i.toString(); + for (i = 0; i < records[0].length; i++) { + t.columns[i] = 'null'; } } var row; @@ -21917,18 +22487,18 @@ function makeObject(row, headers) { * 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. - * + *

      * 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. - * + *

      * 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. - * - *

      Outside of preload(), you may supply a callback function to handle the - * object:

      + *

      + * 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 @@ -22471,7 +23041,7 @@ function escapeHelper(content) { * @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" + * @param {String} [options] can be one of "tsv", "csv", or "html" * @example *
      * var table; @@ -24292,6 +24862,7 @@ p5.prototype.mag = function(x, y) { /** * Re-maps a number from one range to another. + *

      * 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). @@ -24305,24 +24876,22 @@ p5.prototype.mag = function(x, y) { * @return {Number} remapped number * @example *
      - * createCanvas(200, 200); * var value = 25; * var m = map(value, 0, 100, 0, width); - * ellipse(m, 200, 10, 10); + * ellipse(m, 50, 10, 10); *
      * *
      * function setup() { - * createCanvas(200, 200); * noStroke(); * } * * function draw() { * background(204); - * var x1 = map(mouseX, 0, width, 50, 150); - * ellipse(x1, 75, 50, 50); - * var x2 = map(mouseX, 0, width, 0, 200); - * ellipse(x2, 125, 50, 50); + * 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); * } *
      */ @@ -24556,7 +25125,7 @@ p5.prototype.round = Math.round; * noStroke(); * fill(0); * text("x = " + x1, 0, y1 + spacing); - * text("sqrt(x) = " + x2, 0, y2 + spacing); + * text("sq(x) = " + x2, 0, y2 + spacing); * } *
      */ @@ -24811,15 +25380,17 @@ p5.prototype.noise = function(x,y,z) { * 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. 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 + * create finer grained details in the noise sequence. + *

      + * 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 noise().

      By changing these parameters, the signal created by the noise() + * result in greater than 1.0 values returned by noise(). + *

      + * By changing these parameters, the signal created by the noise() * function can be adapted to fit very specific needs and characteristics. * * @method noiseDetail @@ -24944,18 +25515,20 @@ 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(). 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 + * 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(). + *

      + * 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). 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. + * unit, expressed as a vector). + *

      + * 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 @@ -26121,14 +26694,15 @@ p5.prototype.random = function (min, max) { * * 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 + * 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. - * Takes either 0, 1 or 2 arguments. - * If no args, returns a mean of 0 and standard deviation of 1 - * If one arg, that arg is the mean (standard deviation is 1) - * If two args, first is mean, second is standard deviation + *

      + * Takes either 0, 1 or 2 arguments.
      + * If no args, returns a mean of 0 and standard deviation of 1.
      + * If one arg, that arg is the mean (standard deviation is 1).
      + * If two args, first is mean, second is standard deviation. * * @method randomGaussian * @param {Number} mean the mean @@ -26332,9 +26906,11 @@ p5.prototype.atan = function(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. 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. + * for orienting geometry to the position of the cursor. + *

      + * 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 @@ -26768,10 +27344,10 @@ _dereq_('../core/error_helpers'); * with textSize(). Change the color of the text with the fill() function. * Change the outline of the text with the stroke() and strokeWeight() * functions. - * + *

      * The text displays in relation to the textAlign() function, which gives the * option to draw to the left, right, and center of the coordinates. - * + *

      * 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 @@ -27037,6 +27613,53 @@ p5.Font.prototype.textBounds = function(str, x, y, fontSize, options) { 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: + * + *
      sampleFactor - the ratio of path-length to number of samples + * (default=.25); higher values yield more points and are therefore + * more precise + * + *
      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 ------------------------------ /** @@ -27273,6 +27896,602 @@ p5.Font.prototype._handleAlignment = function(p, ctx, line, x, y) { 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) { @@ -27280,7 +28499,6 @@ function cacheKey() { } i = args.length; var hash = ''; - while (i--) { hash += (args[i] === Object(args[i])) ? JSON.stringify(args[i]) : args[i]; @@ -27334,11 +28552,11 @@ p5.prototype.append = function(array, value) { * 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(). - * - * The simplified version with only two arguments — arrayCopy(src, dst) — + *

      + * 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). - * + *

      * Using this function is far more efficient for copying array data than * iterating through a for() loop and copying each element individually. * @@ -27347,7 +28565,7 @@ p5.prototype.append = function(array, value) { * @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 {Nimber} [length] number of Array elements to be copied + * @param {Number} [length] number of Array elements to be copied * * @example *
      @@ -27484,10 +28702,9 @@ p5.prototype.shorten = function(list) { }; /** - * Randomizes the order of the elements of an array. - * Implements Fisher-Yates Shuffle Algorithm - * http://Bost.Ocks.org/mike/shuffle/ - * http://en.Wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + * Randomizes the order of the elements of an array. Implements + * + * Fisher-Yates Shuffle Algorithm. * * @method shuffle * @param {Array} array Array to shuffle @@ -27509,7 +28726,8 @@ p5.prototype.shorten = function(list) { *
      */ p5.prototype.shuffle = function(arr, bool) { - arr = bool || ArrayBuffer.isView(arr)? arr : arr.slice(); + var isView = ArrayBuffer && ArrayBuffer.isView && ArrayBuffer.isView(arr); + arr = bool || isView ? arr : arr.slice(); var rnd, tmp, idx = arr.length; while (idx > 1) { @@ -27944,11 +29162,11 @@ p5.prototype.join = function(list, separator) { * 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. - * + *

      * 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. - * + *

      * 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 @@ -27980,11 +29198,11 @@ p5.prototype.match = function(str, reg) { * 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. - * + *

      * 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. - * + *

      * 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 @@ -28308,11 +29526,11 @@ function addNfs() { * @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 + * @return {Array} Array of Strings * @example *
      * - *var names = "Pat,Xio,Alex" + * var names = "Pat,Xio,Alex" * var splitString = split(names, ","); * text(splitString[0], 5, 30); * text(splitString[1], 5, 50); @@ -28328,7 +29546,7 @@ p5.prototype.split = function(str, 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. - * + *

      * 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. @@ -28347,8 +29565,8 @@ p5.prototype.split = function(str, delim) { * * print(myStrArr); // prints : ["Mango"," Banana"," Lime"] * } - *
      * + *
      */ p5.prototype.splitTokens = function() { var d,sqo,sqc,str; @@ -28424,7 +29642,7 @@ var p5 = _dereq_('../core/core'); *
      * * var day = day(); - * text("Current day: \n"+day, 5, 50); + * text("Current day: \n" + day, 5, 50); * *
      */ @@ -28442,7 +29660,7 @@ p5.prototype.day = function() { *
      * * var hour = hour(); - * text("Current hour:\n"+hour, 5, 50); + * text("Current hour:\n" + hour, 5, 50); * *
      */ @@ -28460,7 +29678,7 @@ p5.prototype.hour = function() { *
      * * var minute = minute(); - * text("Current minute: \n:"+minute, 5, 50); + * text("Current minute: \n" + minute, 5, 50); * *
      */ @@ -28479,7 +29697,7 @@ p5.prototype.minute = function() { *
      * * var millisecond = millis(); - * text("Milliseconds \nrunning: "+millisecond, 5, 50); + * text("Milliseconds \nrunning: \n" + millisecond, 5, 40); * *
      */ @@ -28497,7 +29715,7 @@ p5.prototype.millis = function() { *
      * * var month = month(); - * text("Current month: \n"+month, 5, 50); + * text("Current month: \n" + month, 5, 50); * *
      */ @@ -28515,7 +29733,7 @@ p5.prototype.month = function() { *
      * * var second = second(); - * text("Current second: \n" +second, 5, 50); + * text("Current second: \n" + second, 5, 50); * *
      */ @@ -28533,7 +29751,7 @@ p5.prototype.second = function() { *
      * * var year = year(); - * text("Current year: \n" +year, 5, 50); + * text("Current year: \n" + year, 5, 50); * *
      */ diff --git a/public/mode_assets/p5/empty_project/libraries/p5.sound.js b/public/mode_assets/p5/empty_project/libraries/p5.sound.js index 2c267c5..65b358a 100644 --- a/public/mode_assets/p5/empty_project/libraries/p5.sound.js +++ b/public/mode_assets/p5/empty_project/libraries/p5.sound.js @@ -1,4 +1,4 @@ -/*! p5.sound.js v0.2.16 2015-11-13 */ +/*! 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));}); @@ -593,6 +593,44 @@ helpers = function () { 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'; @@ -694,20 +732,23 @@ panner = function () { var soundfile; soundfile = function () { 'use strict'; + var CustomError = errorHandler; var p5sound = master; var ac = p5sound.audiocontext; /** - *

      p5.SoundFile loads all of the data - * from a soundfile (i.e. an mp3) into your sketch so that you can play it, manipulate - * it, and visualize it.

      - *

      Loading happens asynchronously, so the p5.SoundFile will not be available - * for playback immediately after it is created. Use the loadSound - * method in preload to ensure that it will be ready by the time - * setup is called. Or, use a callback function to get notified - * when the sound is ready.

      - *

      Using the a - * - * local server like the p5 Editor is recommended when loading external files.

      + *

      SoundFile object with a path to a file.

      + * + *

      The p5.SoundFile may not be available immediately because + * it loads the file information asynchronously.

      + * + *

      To do something with the sound as soon as it loads + * pass the name of a function as the second parameter.

      + * + *

      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.

      * * @class p5.SoundFile * @constructor @@ -715,34 +756,33 @@ soundfile = function () { * you may include multiple file formats in * an array. Alternately, accepts an object * from the HTML5 File API, or a p5.File. - * @param {Function} [callback] Name of a function to call once file loads + * @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 *
      + * * function preload() { * mySound = loadSound('assets/doorbell.mp3'); * } - * - * function setup() { - * createCanvas(100, 100); - * background(0, 255, 0); - * - * textAlign(CENTER); - * text('click here to play', width/2, height/2); * + * function setup() { * mySound.setVolume(0.1); - * } - * - * // play sound on mouse press over canvas - * function mousePressed() { - * if (mouseX < width && mouseY < height && mouseX > 0 && mouseY > 0) { - * mySound.play(); - * } + * mySound.play(); * } * *
      */ - p5.SoundFile = function (paths, onload, whileLoading) { + 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); @@ -759,6 +799,9 @@ soundfile = function () { } this.file = paths; } + // private _onended callback, set by the method: onended(callback) + this._onended = function () { + }; this._looping = false; this._playing = false; this._paused = false; @@ -787,21 +830,19 @@ soundfile = function () { this.mode = 'sustain'; // time that playback was started, in millis this.startMillis = null; - this.amplitude = new p5.Amplitude(); - this.output.connect(this.amplitude.input); // 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); + this.load(onload, onerror); } // add this p5.SoundFile to the soundArray p5sound.soundArray.push(this); if (typeof whileLoading === 'function') { - this.whileLoading = whileLoading; + this._whileLoading = whileLoading; } else { - this.whileLoading = function () { + this._whileLoading = function () { }; } }; @@ -822,10 +863,12 @@ soundfile = function () { * i.e. ['sound.ogg', 'sound.mp3']. * Alternately, accepts an object: either * from the HTML5 File API, or a p5.File. - * @param {Function} [callback] Name of a function to call once file loads - * @param {Function} [callback] Name of a function to call while file is loading. - * This function will receive a percentage from 0.0 - * to 1.0. + * @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 *
      @@ -839,12 +882,12 @@ soundfile = function () { * } *
      */ - p5.prototype.loadSound = function (path, callback, whileLoading) { + 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, whileLoading); + var s = new p5.SoundFile(path, callback, onerror, whileLoading); return s; }; /** @@ -853,27 +896,62 @@ soundfile = function () { * as an optional parameter. * * @private - * @param {Function} [callback] Name of a function to call once file loads + * @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) { + 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 sf = this; var request = new XMLHttpRequest(); request.addEventListener('progress', function (evt) { - sf._updateProgress(evt); + self._updateProgress(evt); }, false); request.open('GET', this.url, true); request.responseType = 'arraybuffer'; - // decode asyncrohonously - var self = this; request.onload = function () { - ac.decodeAudioData(request.response, function (buff) { - self.buffer = buff; - self.panner.inputChannels(buff.numberOfChannels); - if (callback) { - callback(self); + 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) { @@ -888,6 +966,10 @@ soundfile = function () { } }); }; + reader.onerror = function (e) { + if (onerror) + onerror(e); + }; reader.readAsArrayBuffer(this.file); } }; @@ -895,28 +977,17 @@ soundfile = function () { p5.SoundFile.prototype._updateProgress = function (evt) { if (evt.lengthComputable) { var percentComplete = Math.log(evt.loaded / evt.total * 9.9); - this.whileLoading(percentComplete); + this._whileLoading(percentComplete); } else { - console.log('size unknown'); + // Unable to compute progress information since the total size is unknown + this._whileLoading('size unknown'); } }; /** - * Returns true when the sound file finishes loading successfully. + * Returns true if the sound file finished loading successfully. * * @method isLoaded * @return {Boolean} - * @example - *
      - * function setup() { - * mySound = loadSound('assets/doorbell.mp3'); - * } - * - * function draw() { - * background(255); - * frameRate(1); - * text('loaded: ' + mySound.isLoaded(), 5, height/2); - * } - *
      */ p5.SoundFile.prototype.isLoaded = function () { if (this.buffer) { @@ -935,17 +1006,6 @@ soundfile = function () { * of playback * @param {Number} [cueStart] (optional) cue start time in seconds * @param {Number} [duration] (optional) duration of playback in seconds - * @example - *
      - * function preload() { - * mySound = loadSound('assets/doorbell.mp3'); - * } - * - * function setup() { - * mySound.setVolume(0.1); - * mySound.play(); - * } - *
      */ p5.SoundFile.prototype.play = function (time, rate, amp, _cueStart, duration) { var self = this; @@ -968,6 +1028,9 @@ soundfile = function () { } // 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) { @@ -1016,18 +1079,21 @@ soundfile = function () { this.bufferSourceNodes.push(this.bufferSourceNode); this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1; // delete this.bufferSourceNode from the sources array when it is done playing: - this.bufferSourceNode.onended = function (e) { - var theNode = this; - // if (self.bufferSourceNodes.length === 1) { + var clearOnEnd = function (e) { this._playing = false; - // } - setTimeout(function () { - self.bufferSourceNodes.splice(theNode._arrayIndex, 1); - if (self.bufferSourceNodes.length === 0) { - self._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); } - }, 1); + }); + 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()'; } @@ -1140,49 +1206,16 @@ soundfile = function () { } }; /** - * Loop the p5.SoundFile - play it over and over again. You can .stop() - * at any time, or turn off looping with .setLoop(false). - * The loop method accepts optional parameters to schedule the looping in the future, - * and to set the playback rate, volume, loopStart, loopEnd. + * 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. Defaults to 0. - * @param {Number} [rate] (optional) playback rate. Defaults to 1. - * @param {Number} [amp] (optional) playback volume (max amplitude). Defaults to 1. + * 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 - * @example - *
      - * function preload() { - * mySound = loadSound('assets/beat.mp3'); - * } - * - * function setup() { - * var cnv = createCanvas(100, 100); - * - * // schedule mouse events on mouse over/out - * cnv.mouseOver(startLooping); - * cnv.mouseOut(stopLooping); - * - * textAlign(CENTER); - * text('hover to loop', width/2, height/2); - * } - * - * function startLooping() { - * background(255, 0, 0); - * text('looping', width/2, height/2); - * - * // start loop now, at playback rate of 3x, volume of 0.1 - * mySound.loop(0, 3, 0.1); - * } - * - * function stopLooping() { - * background(0, 255, 0); - * text('stopped', width/2, height/2); - * mySound.stop(); - * } - *
      */ p5.SoundFile.prototype.loop = function (startTime, rate, amp, loopStart, duration) { this._looping = true; @@ -1277,21 +1310,24 @@ soundfile = function () { 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 + * 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 rampTime parameter. For more - * complex fades, see the Env class.

      + * by greater than 1.0 may cause digital distortion. To + * fade, provide a rampTime 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. @@ -1302,43 +1338,6 @@ soundfile = function () { * @param {Number} [rampTime] Fade for t seconds * @param {Number} [timeFromNow] Schedule this event to happen at * t seconds in the future - * @example - *
      - * function preload() { - * mySound = loadSound('assets/drum.mp3'); - * } - * - * function setup() { - * var cnv = createCanvas(100, 100); - * background(205, 255, 0); - * cnv.mouseOver(fadeIn); - * cnv.mouseOut(fadeOut); - * - * // mute to start - * mySound.setVolume(0); - * mySound.rate(5) - * mySound.loop(); - * - * textAlign(CENTER); - * text('hover to fade in', width/2, height/2); - * } - * - * function fadeIn() { - * background(200, 0, 255); - * text('hover to fade out', width/2, height/2); - * - * // fade in to full volume over 0.5 seconds - * mySound.setVolume(1, 0.5); - * } - * - * function fadeOut() { - * background(205, 255, 0); - * text('hover to fade in', width/2, height/2); - * - * // fade to 0 over 2 seconds - * mySound.setVolume(0, 2); - * } - *
      */ p5.SoundFile.prototype.setVolume = function (vol, rampTime, tFromNow) { if (typeof vol === 'number') { @@ -1660,17 +1659,28 @@ soundfile = function () { this.setVolume(curVol, 0.01, 0.0101); this.play(); }; - // private function for onended behavior - p5.SoundFile.prototype._onEnded = function (s) { - s.onended = function (s) { - var now = p5sound.audiocontext.currentTime; - s.stop(now); - }; + /** + * 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++) { @@ -1731,21 +1741,9 @@ soundfile = function () { this.panner.disconnect(unit); }; /** - * Read the Amplitude (volume level) of a p5.SoundFile. The - * p5.SoundFile class contains its own instance of the Amplitude - * class to help make it easy to get a SoundFile's volume level. - * Accepts an optional smoothing value (0.0 < 1.0). - * - * @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.SoundFile.prototype.getLevel = function (smoothing) { - if (smoothing) { - this.amplitude.smoothing = smoothing; - } - return this.amplitude.getLevel(); + console.warn('p5.SoundFile.getLevel has been removed from the library. Use p5.Amplitude instead'); }; /** * Reset the source for this SoundFile to a @@ -1797,6 +1795,7 @@ soundfile = function () { // 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); @@ -2109,7 +2108,8 @@ soundfile = function () { * @param {Number} id ID of the cue, as returned by addCue */ p5.SoundFile.prototype.removeCue = function (id) { - for (var i = 0; i < this._cues.length; i++) { + 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); @@ -2131,12 +2131,14 @@ soundfile = function () { // have been scheduled using addCue(callback, time). p5.SoundFile.prototype._onTimeUpdate = function (position) { var playbackTime = position / this.buffer.sampleRate; - for (var i = 0; i < this._cues.length; i++) { - var callbackTime = this._cues[i].time; - var val = this._cues[i].val; + 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 - this._cues[i].callback(val); + cue.callback(val); } } this._prevTime = playbackTime; @@ -2149,7 +2151,7 @@ soundfile = function () { this.id = id; this.val = val; }; -}(sndcore, master); +}(sndcore, errorHandler, master); var amplitude; amplitude = function () { 'use strict'; @@ -2230,6 +2232,8 @@ amplitude = function () { 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. @@ -2411,6 +2415,15 @@ amplitude = function () { 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 () { @@ -2458,17 +2471,17 @@ fft = function () { * function preload(){ * sound = loadSound('assets/Damscray_DancingTiger.mp3'); * } - * + * * function setup(){ - * cnv = createCanvas(100,100); - * sound.amp(0); - * sound.loop(); + * 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 @@ -2477,7 +2490,7 @@ fft = function () { * var h = -height + map(spectrum[i], 0, 255, height, 0); * rect(x, height, width / spectrum.length, h ) * } - * + * * var waveform = fft.waveform(); * noFill(); * beginShape(); @@ -2489,17 +2502,16 @@ fft = function () { * vertex(x,y); * } * endShape(); - * - * isMouseOverCanvas(); + * + * text('click to play/pause', 4, 10); * } - * + * * // fade sound if mouse is over canvas - * function isMouseOverCanvas() { - * var mX = mouseX, mY = mouseY; - * if (mX > 0 && mX < width && mY < height && mY > 0) { - * sound.amp(0.5, 0.2); + * function togglePlay() { + * if (sound.isPlaying()) { + * sound.pause(); * } else { - * sound.amp(0, 0.2); + * sound.loop(); * } * } *
      @@ -2536,6 +2548,8 @@ fft = function () { 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 @@ -2771,6 +2785,87 @@ fft = function () { var x = this.getEnergy(freq1, freq2); return x; }; + /** + * Returns the + * + * spectral centroid of the input signal. + * 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.

      + * + * @method getCentroid + * @return {Number} Spectral Centroid Frequency Frequency of the spectral centroid in Hz. + * + * + * @example + *
      + * + * + *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); + *} + *
      + */ + 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. * @@ -2784,6 +2879,13 @@ fft = function () { } 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) { @@ -2806,13 +2908,16 @@ fft = function () { } }; }(master); -/** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +/** 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; @@ -2825,55 +2930,53 @@ Tone_core_Tone = function () { } else { throw new Error('Web Audio is not supported in this browser'); } - if (typeof AudioContext.prototype.createGain !== 'function') { + if (!isFunction(AudioContext.prototype.createGain)) { AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; } - if (typeof AudioContext.prototype.createDelay !== 'function') { + if (!isFunction(AudioContext.prototype.createDelay)) { AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; } - if (typeof AudioContext.prototype.createPeriodicWave !== 'function') { + if (!isFunction(AudioContext.prototype.createPeriodicWave)) { AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; } - if (typeof AudioBufferSourceNode.prototype.start !== 'function') { + if (!isFunction(AudioBufferSourceNode.prototype.start)) { AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn; } - if (typeof AudioBufferSourceNode.prototype.stop !== 'function') { + if (!isFunction(AudioBufferSourceNode.prototype.stop)) { AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff; } - if (typeof OscillatorNode.prototype.start !== 'function') { + if (!isFunction(OscillatorNode.prototype.start)) { OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn; } - if (typeof OscillatorNode.prototype.stop !== 'function') { + if (!isFunction(OscillatorNode.prototype.stop)) { OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff; } - if (typeof OscillatorNode.prototype.setPeriodicWave !== 'function') { + if (!isFunction(OscillatorNode.prototype.setPeriodicWave)) { OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; } - if (!window.Tone) { - 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); + 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 { - 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); + 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(); @@ -2886,10 +2989,139 @@ Tone_core_Tone = function () { 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.bufferTime = Tone.prototype.bufferSize / Tone.context.sampleRate; + 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); @@ -2897,6 +3129,7 @@ Tone_core_Tone = function () { } else { this.output.connect(unit, outputNum, inputNum); } + return this; }; Tone.prototype.disconnect = function (outputNum) { if (Array.isArray(this.output)) { @@ -2905,6 +3138,7 @@ Tone_core_Tone = function () { } else { this.output.disconnect(); } + return this; }; Tone.prototype.connectSeries = function () { if (arguments.length > 1) { @@ -2915,6 +3149,7 @@ Tone_core_Tone = function () { currentUnit = toUnit; } } + return this; }; Tone.prototype.connectParallel = function () { var connectFrom = arguments[0]; @@ -2924,6 +3159,7 @@ Tone_core_Tone = function () { connectFrom.connect(connectTo); } } + return this; }; Tone.prototype.chain = function () { if (arguments.length > 0) { @@ -2934,24 +3170,26 @@ Tone_core_Tone = function () { currentUnit = toUnit; } } + return this; }; Tone.prototype.fan = function () { if (arguments.length > 0) { - for (var i = 1; i < arguments.length; i++) { + 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 (typeof given === 'object' && typeof fallback === 'object') { + if (this.isObject(given) && this.isObject(fallback)) { var ret = {}; for (var givenProp in given) { - ret[givenProp] = this.defaultArg(given[givenProp], given[givenProp]); + ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); } - for (var prop in fallback) { - ret[prop] = this.defaultArg(given[prop], fallback[prop]); + for (var fallbackProp in fallback) { + ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); } return ret; } else { @@ -2960,7 +3198,7 @@ Tone_core_Tone = function () { }; Tone.prototype.optionsObject = function (values, keys, defaults) { var options = {}; - if (values.length === 1 && typeof values[0] === 'object') { + if (values.length === 1 && this.isObject(values[0])) { options = values[0]; } else { for (var i = 0; i < keys.length; i++) { @@ -2974,86 +3212,73 @@ Tone_core_Tone = function () { } }; Tone.prototype.isUndef = isUndef; - Tone.prototype.equalPowerScale = function (percent) { - var piFactor = 0.5 * Math.PI; - return Math.sin(percent * piFactor); + Tone.prototype.isFunction = isFunction; + Tone.prototype.isNumber = function (arg) { + return typeof arg === 'number'; }; - Tone.prototype.logScale = function (gain) { - return Math.max(this.normalize(this.gainToDb(gain), -100, 0), 0); + Tone.prototype.isObject = function (arg) { + return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object; }; - Tone.prototype.expScale = function (gain) { - return this.dbToGain(this.interpolate(gain, -100, 0)); + Tone.prototype.isBoolean = function (arg) { + return typeof arg === 'boolean'; }; - 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.isArray = function (arg) { + return Array.isArray(arg); }; - Tone.prototype.interpolate = function (input, outputMin, outputMax) { - return input * (outputMax - outputMin) + outputMin; + Tone.prototype.isString = function (arg) { + return typeof arg === 'string'; }; - Tone.prototype.normalize = function (input, inputMin, inputMax) { - if (inputMin > inputMax) { - var tmp = inputMax; - inputMax = inputMin; - inputMin = tmp; - } else if (inputMin == inputMax) { - return 0; - } - return (input - inputMin) / (inputMax - inputMin); + Tone.noOp = function () { }; - Tone.prototype.dispose = function () { - if (!this.isUndef(this.input)) { - if (this.input instanceof AudioNode) { - this.input.disconnect(); + Tone.prototype._readOnly = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._readOnly(property[i]); } - this.input = null; + } else { + Object.defineProperty(this, property, { + writable: false, + enumerable: true + }); } - if (!this.isUndef(this.output)) { - if (this.output instanceof AudioNode) { - this.output.disconnect(); + }; + Tone.prototype._writable = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._writable(property[i]); } - this.output = null; + } else { + Object.defineProperty(this, property, { writable: true }); } }; - var _silentNode = null; - Tone.prototype.noGC = function () { - this.output.connect(_silentNode); + Tone.State = { + Started: 'started', + Stopped: 'stopped', + Paused: 'paused' }; - AudioNode.prototype.noGC = function () { - this.connect(_silentNode); + Tone.prototype.equalPowerScale = function (percent) { + var piFactor = 0.5 * Math.PI; + return Math.sin(percent * piFactor); }; - Tone.prototype.now = function () { - return this.context.currentTime; + Tone.prototype.dbToGain = function (db) { + return Math.pow(2, db / 6); }; - Tone.prototype.samplesToSeconds = function (samples) { - return samples / this.context.sampleRate; + Tone.prototype.gainToDb = function (gain) { + return 20 * (Math.log(gain) / Math.LN10); }; - Tone.prototype.toSamples = function (time) { - var seconds = this.toSeconds(time); - return Math.round(seconds * this.context.sampleRate); + Tone.prototype.now = function () { + return this.context.currentTime; }; - Tone.prototype.toSeconds = function (time, now) { - now = this.defaultArg(now, this.now()); - if (typeof time === 'number') { - return time; - } else if (typeof time === 'string') { - var plusTime = 0; - if (time.charAt(0) === '+') { - time = time.slice(1); - plusTime = now; - } - return parseFloat(time) + plusTime; - } else { - return now; + Tone.extend = function (child, parent) { + if (isUndef(parent)) { + parent = Tone; } - }; - Tone.prototype.frequencyToSeconds = function (freq) { - return 1 / parseFloat(freq); - }; - Tone.prototype.secondsToFrequency = function (seconds) { - return 1 / seconds; + function TempConstructor() { + } + TempConstructor.prototype = parent.prototype; + child.prototype = new TempConstructor(); + child.prototype.constructor = child; + child._super = parent; }; var newContextCallbacks = []; Tone._initAudioContext = function (callback) { @@ -3067,16 +3292,6 @@ Tone_core_Tone = function () { newContextCallbacks[i](ctx); } }; - Tone.extend = function (child, parent) { - if (isUndef(parent)) { - parent = Tone; - } - function TempConstructor() { - } - TempConstructor.prototype = parent.prototype; - child.prototype = new TempConstructor(); - child.prototype.constructor = child; - }; Tone.startMobile = function () { var osc = Tone.context.createOscillator(); var silent = Tone.context.createGain(); @@ -3088,14 +3303,15 @@ Tone_core_Tone = function () { osc.stop(now + 1); }; Tone._initAudioContext(function (audioContext) { - Tone.prototype.bufferTime = Tone.prototype.bufferSize / audioContext.sampleRate; + 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 2014 http://opensource.org/licenses/MIT **/ +/** 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'; @@ -3103,19 +3319,20 @@ Tone_signal_SignalBase = function (Tone) { }; Tone.extend(Tone.SignalBase); Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) { - if (node instanceof Tone.Signal) { - node.setValue(0); + 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); - }; - Tone.SignalBase.prototype.dispose = function () { - Tone.prototype.dispose.call(this); + return this; }; return Tone.SignalBase; }(Tone_core_Tone); -/** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +/** 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'; @@ -3123,10 +3340,10 @@ Tone_signal_WaveShaper = function (Tone) { this._shaper = this.input = this.output = this.context.createWaveShaper(); this._curve = null; if (Array.isArray(mapping)) { - this.setCurve(mapping); + this.curve = mapping; } else if (isFinite(mapping) || this.isUndef(mapping)) { this._curve = new Float32Array(this.defaultArg(mapping, 1024)); - } else if (typeof mapping === 'function') { + } else if (this.isFunction(mapping)) { this._curve = new Float32Array(this.defaultArg(bufferLen, 1024)); this.setMap(mapping); } @@ -3135,213 +3352,756 @@ Tone_signal_WaveShaper = function (Tone) { Tone.WaveShaper.prototype.setMap = function (mapping) { for (var i = 0, len = this._curve.length; i < len; i++) { var normalized = i / len * 2 - 1; - var normOffOne = i / (len - 1) * 2 - 1; - this._curve[i] = mapping(normalized, i, normOffOne); + this._curve[i] = mapping(normalized, i); } this._shaper.curve = this._curve; + return this; }; - Tone.WaveShaper.prototype.setCurve = function (mapping) { - if (this._isSafari()) { - var first = mapping[0]; - mapping.unshift(first); + 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; } - this._curve = new Float32Array(mapping); - this._shaper.curve = this._curve; - }; - Tone.WaveShaper.prototype.setOversample = function (oversampling) { - this._shaper.oversample = oversampling; - }; - Tone.WaveShaper.prototype._isSafari = function () { - var ua = navigator.userAgent.toLowerCase(); - return ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1; - }; + }); + 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 2014 http://opensource.org/licenses/MIT **/ -var Tone_signal_Signal; -Tone_signal_Signal = function (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.Signal = function (value) { - this._scalar = this.context.createGain(); - this.input = this.output = this.context.createGain(); - this._syncRatio = 1; - this.value = this.defaultArg(value, 0); - Tone.Signal._constant.chain(this._scalar, this.output); - }; - Tone.extend(Tone.Signal, Tone.SignalBase); - Tone.Signal.prototype.getValue = function () { - return this._scalar.gain.value; - }; - Tone.Signal.prototype.setValue = function (value) { - if (this._syncRatio === 0) { - value = 0; + 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 { - value *= this._syncRatio; + 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; } - this._scalar.gain.value = value; + ticks = parseFloat(ticks); + bpm = this.defaultArg(bpm, getTransportBpm()); + var tickTime = 60 / bpm / Tone.Transport.PPQ; + return tickTime * ticks; }; - Tone.Signal.prototype.setValueAtTime = function (value, time) { - value *= this._syncRatio; - this._scalar.gain.setValueAtTime(value, this.toSeconds(time)); + Tone.prototype.frequencyToSeconds = function (freq) { + return 1 / parseFloat(freq); }; - Tone.Signal.prototype.setCurrentValueNow = function (now) { - now = this.defaultArg(now, this.now()); - var currentVal = this.getValue(); - this.cancelScheduledValues(now); - this._scalar.gain.setValueAtTime(currentVal, now); - return currentVal; + Tone.prototype.samplesToSeconds = function (samples) { + return samples / this.context.sampleRate; }; - Tone.Signal.prototype.linearRampToValueAtTime = function (value, endTime) { - value *= this._syncRatio; - this._scalar.gain.linearRampToValueAtTime(value, this.toSeconds(endTime)); + 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.Signal.prototype.exponentialRampToValueAtTime = function (value, endTime) { - value *= this._syncRatio; - try { - this._scalar.gain.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); - } catch (e) { - this._scalar.gain.linearRampToValueAtTime(value, this.toSeconds(endTime)); - } + Tone.prototype.secondsToFrequency = function (seconds) { + return 1 / seconds; }; - Tone.Signal.prototype.exponentialRampToValueNow = function (value, endTime) { - var now = this.now(); - this.setCurrentValueNow(now); - if (endTime.toString().charAt(0) === '+') { - endTime = endTime.substr(1); + 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; } - this.exponentialRampToValueAtTime(value, now + this.toSeconds(endTime)); }; - Tone.Signal.prototype.linearRampToValueNow = function (value, endTime) { - var now = this.now(); - this.setCurrentValueNow(now); - value *= this._syncRatio; - if (endTime.toString().charAt(0) === '+') { - endTime = endTime.substr(1); + 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; } - this._scalar.gain.linearRampToValueAtTime(value, now + this.toSeconds(endTime)); + 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.Signal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { - value *= this._syncRatio; - this._scalar.gain.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); + Tone.prototype.toSamples = function (time) { + var seconds = this.toSeconds(time); + return Math.round(seconds * this.context.sampleRate); }; - Tone.Signal.prototype.setValueCurveAtTime = function (values, startTime, duration) { - for (var i = 0; i < values.length; i++) { - values[i] *= this._syncRatio; + 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; } - this._scalar.gain.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); }; - Tone.Signal.prototype.cancelScheduledValues = function (startTime) { - this._scalar.gain.cancelScheduledValues(this.toSeconds(startTime)); + 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; + } }; - Tone.Signal.prototype.sync = function (signal, ratio) { - if (ratio) { - this._syncRatio = ratio; + 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 { - if (signal.getValue() !== 0) { - this._syncRatio = this.getValue() / signal.getValue(); - } else { - this._syncRatio = 0; + 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; } - this._scalar.disconnect(); - this._scalar = this.context.createGain(); - this.connectSeries(signal, this._scalar, this.output); - this._scalar.gain.value = this._syncRatio; }; - Tone.Signal.prototype.unsync = function () { - var currentGain = this.getValue(); - this._scalar.disconnect(); - this._scalar = this.context.createGain(); - this._scalar.gain.value = currentGain / this._syncRatio; - this._syncRatio = 1; - Tone.Signal._constant.chain(this._scalar, this.output); + 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.Signal.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._scalar.disconnect(); - this._scalar = null; + Tone.prototype.intervalToFrequencyRatio = function (interval) { + return Math.pow(2, interval / 12); }; - Object.defineProperty(Tone.Signal.prototype, 'value', { + 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.getValue(); + return this._toUnits(this._param.value); }, - set: function (val) { - this.setValue(val); + set: function (value) { + var convertedVal = this._fromUnits(value); + this._param.value = convertedVal; } }); - Tone.Signal._generator = null; + 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) { - Tone.Signal._generator = audioContext.createOscillator(); - Tone.Signal._constant = new Tone.WaveShaper([ - 1, - 1 - ]); - Tone.Signal._generator.connect(Tone.Signal._constant); - Tone.Signal._generator.start(0); - Tone.Signal._generator.noGC(); + 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.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +}(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._value = null; - if (isFinite(value)) { - this._value = new Tone.Signal(value); - this._value.connect(this._sum); - } - }; - Tone.extend(Tone.Add, Tone.SignalBase); - Tone.Add.prototype.setValue = function (value) { - if (this._value !== null) { - this._value.setValue(value); - } else { - throw new Error('cannot switch from signal to number'); - } + 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; - if (this._value) { - this._value.dispose(); - this._value = null; - } + this._param.dispose(); + this._param = null; + return this; }; return Tone.Add; }(Tone_core_Tone); -/** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +/** 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._factor = this.input[1] = this.output.gain; - this._factor.value = this.defaultArg(value, 0); - }; - Tone.extend(Tone.Multiply, Tone.SignalBase); - Tone.Multiply.prototype.setValue = function (value) { - this._factor.value = value; + 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._factor = null; + this._param = null; + return this; }; return Tone.Multiply; }(Tone_core_Tone); -/** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +/** 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'; @@ -3354,17 +4114,27 @@ Tone_signal_Scale = function (Tone) { this._setRange(); }; Tone.extend(Tone.Scale, Tone.SignalBase); - Tone.Scale.prototype.setMin = function (min) { - this._outputMin = min; - this._setRange(); - }; - Tone.Scale.prototype.setMax = function (max) { - this._outputMax = max; - this._setRange(); - }; + 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.setValue(this._outputMin); - this._scale.setValue(this._outputMax - this._outputMin); + this._add.value = this._outputMin; + this._scale.value = this._outputMax - this._outputMin; }; Tone.Scale.prototype.dispose = function () { Tone.prototype.dispose.call(this); @@ -3372,6 +4142,7 @@ Tone_signal_Scale = function (Tone) { this._add = null; this._scale.dispose(); this._scale = null; + return this; }; return Tone.Scale; }(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply); @@ -3607,6 +4378,7 @@ oscillator = function () { } this.started = false; // components + this.phaseAmount = undefined; this.oscillator = p5sound.audiocontext.createOscillator(); this.f = freq || 440; // frequency @@ -3710,7 +4482,6 @@ oscillator = function () { this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); } else if (vol) { - console.log(vol); vol.connect(self.output.gain); } else { // return the Gain Node @@ -3749,13 +4520,21 @@ oscillator = function () { 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); - if (val > 0) { - this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + // 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 { - this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now); + 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) { @@ -3830,6 +4609,9 @@ oscillator = function () { }; // 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); @@ -3844,23 +4626,27 @@ oscillator = function () { } }; /** - * Set the phase of an oscillator between 0.0 and 1.0 + * 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.output.disconnect(); - this.output.connect(this.dNode); - this.dNode.connect(this.panner); + this.oscillator.disconnect(); + this.oscillator.connect(this.dNode); + this.dNode.connect(this.output); } - // set delay time based on PWM width - var now = p5sound.audiocontext.currentTime; - this.dNode.delayTime.linearRampToValueAtTime(p5.prototype.map(p, 0, 1, 0, 1 / this.oscillator.frequency.value), now); + // set delay time to match phase: + this.dNode.delayTime.setValueAtTime(delayAmt, now); }; // ========================== // // SIGNAL MATH FOR MODULATION // @@ -4017,6 +4803,353 @@ oscillator = function () { }; 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'; @@ -4024,140 +5157,291 @@ env = function () { 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); - // oscillator or buffer source to clear on env complete - // to save resources if/when it is retriggered - var sourceToClear = null; - // set to true if attack is set, then false on release - var wasTriggered = false; - /** - *

      Envelopes are pre-defined amplitude distribution over time. - * The p5.Env accepts up to four time/level pairs, where time - * determines how long of a ramp before value reaches level. + /** + *

      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 (ADSR). But p5.Env can control any - * Web Audio Param, for example it can be passed to an Oscillator - * frequency like osc.freq(env)

      + * Sustain and Release ( + * ADSR + * ). Envelopes can also control other Web Audio Parameters—for example, a p5.Env can + * control an Oscillator's frequency like this: osc.freq(env).

      + *

      Use setRange to change the attack/release level. + * Use setADSR to change attackTime, decayTime, sustainPercent and releaseTime.

      + *

      Use the play method to play the entire envelope, + * the ramp method for a pingable trigger, + * or triggerAttack/ + * triggerRelease to trigger noteOn/noteOff.

      * * @class p5.Env * @constructor - * @param {Number} aTime Time (in seconds) before level - * reaches attackLevel - * @param {Number} aLevel Typically an amplitude between - * 0.0 and 1.0 - * @param {Number} dTime Time - * @param {Number} [dLevel] Amplitude (In a standard ADSR envelope, - * decayLevel = sustainLevel) - * @param {Number} [sTime] Time (in seconds) - * @param {Number} [sLevel] Amplitude 0.0 to 1.0 - * @param {Number} [rTime] Time (in seconds) - * @param {Number} [rLevel] Amplitude 0.0 to 1.0 * @example *
      - * var aT = 0.1; // attack time in seconds - * var aL = 0.7; // attack level 0.0 to 1.0 - * var dT = 0.3; // decay time in seconds - * var dL = 0.1; // decay level 0.0 to 1.0 - * var sT = 0.2; // sustain time in seconds - * var sL = dL; // sustain level 0.0 to 1.0 - * var rT = 0.5; // release time in seconds - * // release level defaults to zero - * - * var env; - * var triOsc; - * + * 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() { - * background(0); - * noStroke(); - * fill(255); + * var cnv = createCanvas(100, 100); + * * textAlign(CENTER); * text('click to play', width/2, height/2); * - * env = new p5.Env(aT, aL, dT, dL, sT, sL, rT); + * env = new p5.Env(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * * triOsc = new p5.Oscillator('triangle'); - * triOsc.amp(env); // give the env control of the triOsc's amp + * triOsc.amp(env); * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); * } * - * // mouseClick triggers envelope if over canvas - * function mouseClicked() { - * // is mouse over canvas? - * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - * env.play(triOsc); - * } + * function playEnv(){ + * env.play(); * } *
      */ - p5.Env = function (t1, l1, t2, l2, t3, l3, t4, l4) { + p5.Env = function (t1, l1, t2, l2, t3, l3) { + var now = p5sound.audiocontext.currentTime; /** + * Time until envelope reaches attackLevel * @property attackTime */ - this.aTime = t1; + this.aTime = t1 || 0.1; /** + * Level once attack is complete. * @property attackLevel */ - this.aLevel = l1; + this.aLevel = l1 || 1; /** + * Time until envelope reaches decayLevel. * @property decayTime */ - this.dTime = t2 || 0; + this.dTime = t2 || 0.5; /** + * Level after decay. The envelope will sustain here until it is released. * @property decayLevel */ this.dLevel = l2 || 0; /** - * @property sustainTime - */ - this.sTime = t3 || 0; - /** - * @property sustainLevel - */ - this.sLevel = l3 || 0; - /** + * Duration of the release portion of the envelope. * @property releaseTime */ - this.rTime = t4 || 0; + this.rTime = t3 || 0; /** + * Level at the end of the release. * @property releaseLevel */ - this.rLevel = l4 || 0; + this.rLevel = l3 || 0; + this._rampHighPercentage = 0.98; + this._rampLowPercentage = 0.02; this.output = p5sound.audiocontext.createGain(); - this.control = new p5.Signal(); + 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} aTime Time (in seconds) before level + * @param {Number} attackTime Time (in seconds) before level * reaches attackLevel - * @param {Number} aLevel Typically an amplitude between + * @param {Number} attackLevel Typically an amplitude between * 0.0 and 1.0 - * @param {Number} dTime Time - * @param {Number} [dLevel] Amplitude (In a standard ADSR envelope, + * @param {Number} decayTime Time + * @param {Number} decayLevel Amplitude (In a standard ADSR envelope, * decayLevel = sustainLevel) - * @param {Number} [sTime] Time (in seconds) - * @param {Number} [sLevel] Amplitude 0.0 to 1.0 - * @param {Number} [rTime] Time (in seconds) - * @param {Number} [rLevel] Amplitude 0.0 to 1.0 + * @param {Number} releaseTime Release Time (in seconds) + * @param {Number} releaseLevel Amplitude */ - p5.Env.prototype.set = function (t1, l1, t2, l2, t3, l3, t4, l4) { + 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.sTime = t3 || 0; - this.sLevel = l3 || 0; this.rTime = t4 || 0; this.rLevel = l4 || 0; + // set time constants for ramp + this._setRampAD(t1, t2); + }; + /** + * Set values like a traditional + * + * ADSR envelope + * . + * + * @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 setRange), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + * @example + *
      + * 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(); + * } + *
      + */ + 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 + *
      + * 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(); + * } + *
      + */ + 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 + // time constants 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. @@ -4174,8 +5458,23 @@ env = function () { this.connect(arguments[i]); } }; - p5.Env.prototype.ctrl = function (unit) { - this.connect(unit); + /** + * 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. @@ -4188,32 +5487,59 @@ env = function () { * @method play * @param {Object} unit A p5.sound object or * Web Audio Param. - * @param {Number} secondsFromNow time from now (in seconds) + * @param {Number} [startTime] time from now (in seconds) at which to play + * @param {Number} [sustainTime] time to sustain before releasing the envelope + * @example + *
      + * 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); + * } + *
      */ - p5.Env.prototype.play = function (unit, secondsFromNow) { + p5.Env.prototype.play = function (unit, secondsFromNow, susTime) { var now = p5sound.audiocontext.currentTime; var tFromNow = secondsFromNow || 0; - var t = now + tFromNow; + var susTime = susTime || 0; if (unit) { if (this.connection !== unit) { this.connect(unit); } } - var currentVal = this.control.getValue(); - this.control.cancelScheduledValues(t); - this.control.linearRampToValueAtTime(currentVal, t); - // attack - this.control.linearRampToValueAtTime(this.aLevel, t + this.aTime); - // decay to decay level - this.control.linearRampToValueAtTime(this.dLevel, t + this.aTime + this.dTime); - // hold sustain level - this.control.linearRampToValueAtTime(this.sLevel, t + this.aTime + this.dTime + this.sTime); - // release - this.control.linearRampToValueAtTime(this.rLevel, t + this.aTime + this.dTime + this.sTime + this.rTime); - var clearTime = t + this.aTime + this.dTime + this.sTime + this.rTime; + this.triggerAttack(unit, tFromNow); + this.triggerRelease(unit, tFromNow + this.aTime + this.dTime + susTime); }; /** - * Trigger the Attack, Decay, and Sustain of the Envelope. + * 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 setADSR(attackTime, decayTime)
      + * as + * time constants 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 + *
      + * 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); + * } + *
      + */ + 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; @@ -4383,6 +5904,9 @@ env = function () { }; // 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 { @@ -4394,7 +5918,7 @@ env = function () { mathOps[i].dispose(); } }; -}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone); +}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_signal_TimelineSignal, Tone_core_Tone); var pulse; pulse = function () { 'use strict'; @@ -4730,6 +6254,9 @@ noise = function () { */ 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); @@ -4750,6 +6277,7 @@ var audioin; audioin = function () { 'use strict'; var p5sound = master; + var CustomError = errorHandler; /** *

      Get audio from an input, i.e. your computer's microphone.

      * @@ -4816,24 +6344,35 @@ audioin = function () { * anything unless you use the connect() method.
      * * @method start - */ - p5.AudioIn.prototype.start = function () { + * @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 }] } }; - navigator.getUserMedia(constraints, this._onStream = function (stream) { + 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 (stream) { - console.error(stream); + }, this._onStreamError = function (e) { + if (errorCallback) + errorCallback(e); + else + console.error(e); }); } else { // if Firefox where users select their source via browser @@ -4847,8 +6386,13 @@ audioin = function () { 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); - }, this._onStreamError = function (stream) { - console.error(stream); + if (successCallback) + successCallback(); + }, this._onStreamError = function (e) { + if (errorCallback) + errorCallback(e); + else + console.error(e); }); } }; @@ -4987,8 +6531,6 @@ audioin = function () { * audioGrab.setSource(0); * }); * } - * function draw(){ - * } *
      */ p5.AudioIn.prototype.getSources = function (callback) { @@ -5030,6 +6572,9 @@ audioin = function () { }; // 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(); @@ -5040,7 +6585,7 @@ audioin = function () { this.amplitude = null; this.output = null; }; -}(master); +}(master, errorHandler); var filter; filter = function () { 'use strict'; @@ -5134,6 +6679,8 @@ filter = function () { if (type) { this.setType(type); } + // add to the soundArray + p5sound.soundArray.push(this); }; /** * Filter an audio signal according to a set @@ -5263,6 +6810,17 @@ filter = function () { 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: new p5.LowPass() Filter. * This is the same as creating a p5.Filter and then calling @@ -5383,15 +6941,6 @@ delay = function () { this._rightFilter = new p5.Filter(); this._leftFilter.disconnect(); this._rightFilter.disconnect(); - /** - * Internal filter. Set to lowPass by default, but can be accessed directly. - * See p5.Filter for methods. Or use the p5.Delay.filter() method to change - * frequency and q. - * - * @property lowPass - * @type {p5.Filter} - */ - this.lowPass = this._leftFilter; 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); @@ -5409,6 +6958,8 @@ delay = function () { // 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 @@ -5573,11 +7124,37 @@ delay = function () { 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 @@ -5741,6 +7318,9 @@ reverb = function () { 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; @@ -5776,7 +7356,11 @@ reverb = function () { * @class p5.Convolver * @constructor * @param {String} path path to a sound file - * @param {[Function]} callback function (optional) + * @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 *
      * var cVerb, sound; @@ -5805,7 +7389,7 @@ reverb = function () { * } *
      */ - p5.Convolver = function (path, callback) { + p5.Convolver = function (path, callback, errorCallback) { this.ac = p5sound.audiocontext; /** * Internally, the p5.Convolver uses the a @@ -5824,7 +7408,7 @@ reverb = function () { this.convolverNode.connect(this.output); if (path) { this.impulses = []; - this._loadBuffer(path, callback); + this._loadBuffer(path, callback, errorCallback); } else { // parameters this._seconds = 3; @@ -5843,7 +7427,12 @@ reverb = function () { * * @method createConvolver * @param {String} path path to a sound file - * @param {[Function]} callback function (optional) + * @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 *
      @@ -5873,12 +7462,12 @@ reverb = function () { * } *
      */ - p5.prototype.createConvolver = function (path, callback) { + 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); + var cReverb = new p5.Convolver(path, callback, errorCallback); cReverb.impulses = []; return cReverb; }; @@ -5888,28 +7477,62 @@ reverb = function () { * * @param {String} path * @param {Function} callback + * @param {Function} errorCallback * @private */ - p5.Convolver.prototype._loadBuffer = function (path, callback) { - path = p5.prototype._checkFileFormats(path); + 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'; - // decode asyncrohonously - var self = this; request.onload = function () { - var ac = p5.prototype.getAudioContext(); - 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); + 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(); }; @@ -5963,14 +7586,15 @@ reverb = function () { * * @method addImpulse * @param {String} path path to a sound file - * @param {[Function]} callback function (optional) + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) */ - p5.Convolver.prototype.addImpulse = function (path, callback) { + 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); + this._loadBuffer(path, callback, errorCallback); }; /** * Similar to .addImpulse, except that the .impulses @@ -5979,15 +7603,16 @@ reverb = function () { * * @method resetImpulse * @param {String} path path to a sound file - * @param {[Function]} callback function (optional) + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) */ - p5.Convolver.prototype.resetImpulse = function (path, callback) { + 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); + this._loadBuffer(path, callback, errorCallback); }; /** * If you have used .addImpulse() to add multiple impulses @@ -6037,88 +7662,161 @@ reverb = function () { this.panner = null; } }; -}(master, sndcore); -/** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ +}(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 (rate, callback) { - this._oscillator = null; - this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); - this._jsNode.onaudioprocess = this._processBuffer.bind(this); - this._controlSignal = new Tone.Signal(1); - this._upTick = false; - this.tick = this.defaultArg(callback, function () { - }); - this._jsNode.noGC(); - this.setRate(rate); + 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.prototype.setRate = function (rate, rampTime) { - var freqVal = this.secondsToFrequency(this.toSeconds(rate)); - if (!rampTime) { - this._controlSignal.cancelScheduledValues(0); - this._controlSignal.setValue(freqVal); - } else { - this._controlSignal.exponentialRampToValueNow(freqVal, rampTime); - } + Tone.Clock.defaults = { + 'callback': Tone.noOp, + 'frequency': 1, + 'lookAhead': 'auto' }; - Tone.Clock.prototype.getRate = function () { - return this._controlSignal.getValue(); + 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.start = function (time) { - this._oscillator = this.context.createOscillator(); - this._oscillator.type = 'square'; - this._oscillator.connect(this._jsNode); - this._controlSignal.connect(this._oscillator.frequency); - this._upTick = false; - var startTime = this.toSeconds(time); - this._oscillator.start(startTime); - this._oscillator.onended = function () { - }; + 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.stop = function (time, onend) { - var stopTime = this.toSeconds(time); - this._oscillator.onended = onend; - this._oscillator.stop(stopTime); + 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._processBuffer = function (event) { - var now = this.defaultArg(event.playbackTime, this.now()); - var bufferSize = this._jsNode.bufferSize; - var incomingBuffer = event.inputBuffer.getChannelData(0); - var upTick = this._upTick; - var self = this; - for (var i = 0; i < bufferSize; i++) { - var sample = incomingBuffer[i]; - if (sample > 0 && !upTick) { - upTick = true; - setTimeout(function () { - var tickTime = now + self.samplesToSeconds(i + bufferSize * 2); - return function () { - self.tick(tickTime); - }; - }(), 0); - } else if (sample < 0 && upTick) { - upTick = false; + 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; } - this._upTick = upTick; + }; + Tone.Clock.prototype.getStateAtTime = function (time) { + return this._state.getStateAtTime(time); }; Tone.Clock.prototype.dispose = function () { - this._jsNode.disconnect(); - this._controlSignal.dispose(); - if (this._oscillator) { - this._oscillator.onended(); - this._oscillator.disconnect(); - } - this._jsNode.onaudioprocess = function () { - }; - this._jsNode = null; - this._controlSignal = null; - this._oscillator = null; + 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_core_Tone, Tone_signal_TimelineSignal); var metro; metro = function () { 'use strict'; @@ -6129,7 +7827,7 @@ metro = function () { var ac = p5sound.audiocontext; // var upTick = false; p5.Metro = function () { - this.clock = new Clock(ac.sampleRate, this.ontick.bind(this)); + this.clock = new Clock({ 'callback': this.ontick.bind(this) }); this.syncedParts = []; this.bpm = 120; // gets overridden by p5.Part @@ -6166,9 +7864,11 @@ metro = function () { }; p5.Metro.prototype.setBPM = function (bpm, rampTime) { var beatTime = 60 / (bpm * this.tatums); + var now = p5sound.audiocontext.currentTime; tatumTime = beatTime; - var ramp = rampTime || 0; - this.clock.setRate(beatTime, rampTime + p5sound.audiocontext.currentTime); + 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) { @@ -6185,15 +7885,17 @@ metro = function () { p5.Metro.prototype.pushSync = function (part) { this.syncedParts.push(part); }; - p5.Metro.prototype.start = function (time) { - var t = time || 0; - this.clock.start(t); + 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 (time) { - var t = time || 0; + p5.Metro.prototype.stop = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; if (this.clock._oscillator) { - this.clock.stop(t); + this.clock.stop(now + t); } }; p5.Metro.prototype.beatLength = function (tatums) { @@ -6893,6 +8595,9 @@ soundRecorder = function () { }; 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) { @@ -7261,6 +8966,8 @@ gain = function () { // 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. @@ -7308,11 +9015,20 @@ gain = function () { 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, panner, soundfile, amplitude, fft, signal, oscillator, env, pulse, noise, audioin, filter, delay, reverb, metro, looper, soundRecorder, peakdetect, gain); +}(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/public/mode_assets/p5/example_assets/moonwalk.jpg b/public/mode_assets/p5/example_assets/moonwalk.jpg index c418e6f..36cc6c0 100644 Binary files a/public/mode_assets/p5/example_assets/moonwalk.jpg and b/public/mode_assets/p5/example_assets/moonwalk.jpg differ diff --git a/public/mode_assets/p5/examples/Color/02_Brightness.js b/public/mode_assets/p5/examples/Color/02_Brightness.js index 1f819fc..23089fe 100644 --- a/public/mode_assets/p5/examples/Color/02_Brightness.js +++ b/public/mode_assets/p5/examples/Color/02_Brightness.js @@ -13,23 +13,34 @@ function preload() { function setup() { createCanvas(720, 200); + pixelDensity(1); img.loadPixels(); loadPixels(); } function draw() { - for (var x = 0; x < width; x++) { - for (var y = 0; y < height; y++ ) { + for (var x = 0; x < img.width; x++) { + for (var y = 0; y < img.height; y++ ) { // Calculate the 1D location from a 2D grid - var loc = 4*(x + y*width); - var r = img.pixels[loc]; + var loc = (x + y*img.width)*4; + // Get the R,G,B values from image + var r,g,b; + r = img.pixels[loc]; + // Calculate an amount to change brightness based on proximity to the mouse var maxdist = 50; var d = dist(x, y, mouseX, mouseY); var adjustbrightness = 255*(maxdist-d)/maxdist; r += adjustbrightness; + // Constrain RGB to make sure they are within 0-255 color range r = constrain(r, 0, 255); - pixels[4*(y*width + x)+3] = 255-r; + // Make a new color and set pixel in the window + //color c = color(r, g, b); + var pixloc = (y*width + x)*4; + pixels[pixloc] = r; + pixels[pixloc+1] = r; + pixels[pixloc+2] = r; + pixels[pixloc+3] = 255; } } updatePixels(); -} +} \ No newline at end of file diff --git a/public/mode_assets/p5/examples/Color/07_Lerp_Color.js b/public/mode_assets/p5/examples/Color/07_Lerp_Color.js new file mode 100644 index 0000000..f9f9198 --- /dev/null +++ b/public/mode_assets/p5/examples/Color/07_Lerp_Color.js @@ -0,0 +1,41 @@ +/* + * @name Lerp Color + * @description Loop random shapes, + * lerp color from red to blue. + */ +function setup() { + createCanvas(720, 400); + background(255); + noStroke(); +} + +function draw() { + background(255); + from = color(255, 0, 0, 0.2 * 255); + to = color(0, 0, 255, 0.2 * 255); + c1 = lerpColor(from, to, .33); + c2 = lerpColor(from, to, .66); + for (var i = 0; i < 15; i++) { + fill(from); + quad(random(-40, 220), random(height), + random(-40, 220), random(height), + random(-40, 220), random(height), + random(-40, 220), random(height)); + fill(c1); + quad(random(140, 380), random(height), + random(140, 380), random(height), + random(140, 380), random(height), + random(140, 380), random(height)); + fill(c2); + quad(random(320, 580), random(height), + random(320, 580), random(height), + random(320, 580), random(height), + random(320, 580), random(height)); + fill(to); + quad(random(500, 760), random(height), + random(500, 760), random(height), + random(500, 760), random(height), + random(500, 760), random(height)); + } + frameRate(5); +} \ No newline at end of file diff --git a/public/mode_assets/p5/examples/Form/07_3D_Primitives.js b/public/mode_assets/p5/examples/Form/07_3D_Primitives.js new file mode 100644 index 0000000..2585689 --- /dev/null +++ b/public/mode_assets/p5/examples/Form/07_3D_Primitives.js @@ -0,0 +1,29 @@ +/* + * @name 3D Primitives + * @frame 720,400 (optional) + * @description Placing mathematically 3D objects in synthetic space. + * The box() and sphere() functions take at least one parameter to specify their + * size. These shapes are positioned using the translate() function. + */ +function setup() { + createCanvas(710, 400, WEBGL); +} + +function draw() { + background(100); + noStroke(); + + push(); + translate(-300, 200); + rotateY(1.25); + rotateX(-0.9); + box(100); + pop(); + + noFill(); + stroke(255); + push(); + translate(500, height*0.35, -200); + sphere(300); + pop(); +} \ No newline at end of file diff --git a/public/mode_assets/p5/examples/Lights/02_Directional.js b/public/mode_assets/p5/examples/Lights/02_Directional.js new file mode 100644 index 0000000..0473b98 --- /dev/null +++ b/public/mode_assets/p5/examples/Lights/02_Directional.js @@ -0,0 +1,27 @@ +/* + * @name Directional + * @frame 710,400 + * @description Move the mouse the change the direction of the light. + * Directional light comes from one direction and is stronger when hitting a + * surface squarely and weaker if it hits at a a gentle angle. After hitting a + * surface, a directional lights scatters in all directions. + */ +var radius = 200; + +function setup() { + createCanvas(710, 400, WEBGL); + noStroke(); + fill(200); +} + +function draw() { + noStroke(); + background(0); + var dirY = (mouseY / height - 0.5) * 4; + var dirX = (mouseX / width - 0.5) * 4; + directionalLight(204, 204, 204, dirX, dirY, 1); + translate(-1.5 * radius, 0, 0); + sphere(radius); + translate(3 * radius, 0, 0); + sphere(radius); +} \ No newline at end of file diff --git a/public/mode_assets/p5/examples/Lights/05_Mixture.js b/public/mode_assets/p5/examples/Lights/05_Mixture.js new file mode 100644 index 0000000..51ceee0 --- /dev/null +++ b/public/mode_assets/p5/examples/Lights/05_Mixture.js @@ -0,0 +1,26 @@ +/* + * @name Mixture + * @frame 710,400 (optional) + * @description Display a box with three different kinds of lights. + */ +function setup() { + createCanvas(710, 400, WEBGL); + noStroke(); +} + +function draw() { + background(0); + + // Orange point light on the right + pointLight(150, 100, 0, 500, 0, 200); + + // Blue directional light from the left + directionalLight(0, 102, 255, -1, 0, 0); + + // Yellow spotlight from the front + pointLight(255, 255, 109, 0, 0, 300); + + rotateY(map(mouseX, 0, width, 0, PI)); + rotateX(map(mouseY, 0, height, 0, PI)); + box(200); +} \ No newline at end of file diff --git a/public/mode_assets/p5/examples/Math/14_noisewave.js b/public/mode_assets/p5/examples/Math/14_noisewave.js index 4c383a5..846f04f 100644 --- a/public/mode_assets/p5/examples/Math/14_noisewave.js +++ b/public/mode_assets/p5/examples/Math/14_noisewave.js @@ -17,7 +17,7 @@ function draw() { beginShape(); var xoff = 0; // Option #1: 2D Noise - // float xoff = yoff; // Option #2: 1D Noise + // var xoff = yoff; // Option #2: 1D Noise // Iterate over horizontal pixels for (var x = 0; x <= width; x += 10) { @@ -27,7 +27,7 @@ function draw() { var y = map(noise(xoff, yoff), 0, 1, 200,300); // Option #2: 1D Noise - // float y = map(noise(xoff), 0, 1, 200,300); + // var y = map(noise(xoff), 0, 1, 200,300); // Set the vertex vertex(x, y); @@ -39,4 +39,4 @@ function draw() { vertex(width, height); vertex(0, height); endShape(CLOSE); -} \ No newline at end of file +} diff --git a/public/mode_assets/p5/examples/Simulate/10_SoftBody.js b/public/mode_assets/p5/examples/Simulate/10_SoftBody.js new file mode 100644 index 0000000..33f2e1a --- /dev/null +++ b/public/mode_assets/p5/examples/Simulate/10_SoftBody.js @@ -0,0 +1,110 @@ +/* + * @name Soft Body + * @description Original example by Ira Greenberg. + *

      Softbody dynamics simulation using curveVertex() and curveTightness(). + */ +// center point +var centerX = 0.0, centerY = 0.0; + +var radius = 45, rotAngle = -90; +var accelX = 0.0, accelY = 0.0; +var deltaX = 0.0, deltaY = 0.0; +var springing = 0.0009, damping = 0.98; + +//corner nodes +var nodes = 5; + +//zero fill arrays +var nodeStartX = []; +var nodeStartY = []; +var nodeX = []; +var nodeY = []; +var angle = []; +var frequency = []; + +// soft-body dynamics +var organicConstant = 1.0; + +function setup() { + createCanvas(710, 400); + + //center shape in window + centerX = width/2; + centerY = height/2; + + //initialize arrays to 0 + for (var i=0; i