1/*! p5.sound.js v0.3.0 2016-01-31 */ 2(function (root, factory) { 3 if (typeof define === 'function' && define.amd) 4 define('p5.sound', ['p5'], function (p5) { (factory(p5));}); 5 else if (typeof exports === 'object') 6 factory(require('../p5')); 7 else 8 factory(root['p5']); 9}(this, function (p5) { 10 /** 11 * p5.sound extends p5 with <a href="http://caniuse.com/audio-api" 12 * target="_blank">Web Audio</a> functionality including audio input, 13 * playback, analysis and synthesis. 14 * <br/><br/> 15 * <a href="#/p5.SoundFile"><b>p5.SoundFile</b></a>: Load and play sound files.<br/> 16 * <a href="#/p5.Amplitude"><b>p5.Amplitude</b></a>: Get the current volume of a sound.<br/> 17 * <a href="#/p5.AudioIn"><b>p5.AudioIn</b></a>: Get sound from an input source, typically 18 * a computer microphone.<br/> 19 * <a href="#/p5.FFT"><b>p5.FFT</b></a>: Analyze the frequency of sound. Returns 20 * results from the frequency spectrum or time domain (waveform).<br/> 21 * <a href="#/p5.Oscillator"><b>p5.Oscillator</b></a>: Generate Sine, 22 * Triangle, Square and Sawtooth waveforms. Base class of 23 * <a href="#/p5.Noise">p5.Noise</a> and <a href="#/p5.Pulse">p5.Pulse</a>. 24 * <br/> 25 * <a href="#/p5.Env"><b>p5.Env</b></a>: An Envelope is a series 26 * of fades over time. Often used to control an object's 27 * output gain level as an "ADSR Envelope" (Attack, Decay, 28 * Sustain, Release). Can also modulate other parameters.<br/> 29 * <a href="#/p5.Delay"><b>p5.Delay</b></a>: A delay effect with 30 * parameters for feedback, delayTime, and lowpass filter.<br/> 31 * <a href="#/p5.Filter"><b>p5.Filter</b></a>: Filter the frequency range of a 32 * sound. 33 * <br/> 34 * <a href="#/p5.Reverb"><b>p5.Reverb</b></a>: Add reverb to a sound by specifying 35 * duration and decay. <br/> 36 * <b><a href="#/p5.Convolver">p5.Convolver</a>:</b> Extends 37 * <a href="#/p5.Reverb">p5.Reverb</a> to simulate the sound of real 38 * physical spaces through convolution.<br/> 39 * <b><a href="#/p5.SoundRecorder">p5.SoundRecorder</a></b>: Record sound for playback 40 * / save the .wav file. 41 * <b><a href="#/p5.Phrase">p5.Phrase</a></b>, <b><a href="#/p5.Part">p5.Part</a></b> and 42 * <b><a href="#/p5.Score">p5.Score</a></b>: Compose musical sequences. 43 * <br/><br/> 44 * p5.sound is on <a href="https://github.com/therewasaguy/p5.sound/">GitHub</a>. 45 * Download the latest version 46 * <a href="https://github.com/therewasaguy/p5.sound/blob/master/lib/p5.sound.js">here</a>. 47 * 48 * @module p5.sound 49 * @submodule p5.sound 50 * @for p5.sound 51 * @main 52 */ 53/** 54 * p5.sound developed by Jason Sigal for the Processing Foundation, Google Summer of Code 2014. The MIT License (MIT). 55 * 56 * http://github.com/therewasaguy/p5.sound 57 * 58 * Some of the many audio libraries & resources that inspire p5.sound: 59 * - TONE.js (c) Yotam Mann, 2014. Licensed under The MIT License (MIT). https://github.com/TONEnoTONE/Tone.js 60 * - buzz.js (c) Jay Salvat, 2013. Licensed under The MIT License (MIT). http://buzz.jaysalvat.com/ 61 * - Boris Smus Web Audio API book, 2013. Licensed under the Apache License http://www.apache.org/licenses/LICENSE-2.0 62 * - wavesurfer.js https://github.com/katspaugh/wavesurfer.js 63 * - Web Audio Components by Jordan Santell https://github.com/web-audio-components 64 * - Wilm Thoben's Sound library for Processing https://github.com/processing/processing/tree/master/java/libraries/sound 65 * 66 * Web Audio API: http://w3.org/TR/webaudio/ 67 */ 68var sndcore; 69sndcore = function () { 70 'use strict'; 71 /* AudioContext Monkeypatch 72 Copyright 2013 Chris Wilson 73 Licensed under the Apache License, Version 2.0 (the "License"); 74 you may not use this file except in compliance with the License. 75 You may obtain a copy of the License at 76 http://www.apache.org/licenses/LICENSE-2.0 77 Unless required by applicable law or agreed to in writing, software 78 distributed under the License is distributed on an "AS IS" BASIS, 79 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 See the License for the specific language governing permissions and 81 limitations under the License. 82 */
83 (function (global, exports, perf) { 84 exports = exports || {}; 85 'use strict'; 86 function fixSetTarget(param) { 87 if (!param) 88 // if NYI, just return 89 return; 90 if (!param.setTargetAtTime) 91 param.setTargetAtTime = param.setTargetValueAtTime; 92 } 93 if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) { 94 window.AudioContext = webkitAudioContext; 95 if (!AudioContext.prototype.hasOwnProperty('createGain')) 96 AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; 97 if (!AudioContext.prototype.hasOwnProperty('createDelay')) 98 AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; 99 if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor')) 100 AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode; 101 if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave')) 102 AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; 103 AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain; 104 AudioContext.prototype.createGain = function () { 105 var node = this.internal_createGain(); 106 fixSetTarget(node.gain); 107 return node; 108 }; 109 AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay; 110 AudioContext.prototype.createDelay = function (maxDelayTime) { 111 var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay(); 112 fixSetTarget(node.delayTime); 113 return node; 114 }; 115 AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource; 116 AudioContext.prototype.createBufferSource = function () { 117 var node = this.internal_createBufferSource(); 118 if (!node.start) { 119 node.start = function (when, offset, duration) { 120 if (offset || duration) 121 this.noteGrainOn(when || 0, offset, duration); 122 else 123 this.noteOn(when || 0); 124 }; 125 } else { 126 node.internal_start = node.start; 127 node.start = function (when, offset, duration) { 128 if (typeof duration !== 'undefined') 129 node.internal_start(when || 0, offset, duration); 130 else 131 node.internal_start(when || 0, offset || 0); 132 }; 133 } 134 if (!node.stop) { 135 node.stop = function (when) { 136 this.noteOff(when || 0); 137 }; 138 } else { 139 node.internal_stop = node.stop; 140 node.stop = function (when) { 141 node.internal_stop(when || 0); 142 }; 143 } 144 fixSetTarget(node.playbackRate); 145 return node; 146 }; 147 AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor; 148 AudioContext.prototype.createDynamicsCompressor = function () { 149 var node = this.internal_createDynamicsCompressor(); 150 fixSetTarget(node.threshold); 151 fixSetTarget(node.knee); 152 fixSetTarget(node.ratio); 153 fixSetTarget(node.reduction); 154 fixSetTarget(node.attack); 155 fixSetTarget(node.release); 156 return node; 157 }; 158 AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter; 159 AudioContext.prototype.createBiquadFilter = function () { 160 var node = this.internal_createBiquadFilter(); 161 fixSetTarget(node.frequency); 162 fixSetTarget(node.detune); 163 fixSetTarget(node.Q); 164 fixSetTarget(node.gain); 165 return node; 166 }; 167 if (AudioContext.prototype.hasOwnProperty('createOscillator')) { 168 AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator; 169 AudioContext.prototype.createOscillator = function () { 170 var node = this.internal_createOscillator(); 171 if (!node.start) { 172 node.start = function (when) { 173 this.noteOn(when || 0); 174 }; 175 } else { 176 node.internal_start = node.start; 177 node.start = function (when) { 178 node.internal_start(when || 0); 179 }; 180 } 181 if (!node.stop) { 182 node.stop = function (when) { 183 this.noteOff(when || 0); 184 }; 185 } else { 186 node.internal_stop = node.stop; 187 node.stop = function (when) { 188 node.internal_stop(when || 0); 189 }; 190 } 191 if (!node.setPeriodicWave) 192 node.setPeriodicWave = node.setWaveTable; 193 fixSetTarget(node.frequency); 194 fixSetTarget(node.detune); 195 return node; 196 }; 197 } 198 } 199 if (window.hasOwnProperty('webkitOfflineAudioContext') && !window.hasOwnProperty('OfflineAudioContext')) { 200 window.OfflineAudioContext = webkitOfflineAudioContext; 201 } 202 return exports; 203 }(window)); 204 // <-- end MonkeyPatch. 205 // Create the Audio Context 206 var audiocontext = new window.AudioContext(); 207 /** 208 * <p>Returns the Audio Context for this sketch. Useful for users 209 * who would like to dig deeper into the <a target='_blank' href= 210 * 'http://webaudio.github.io/web-audio-api/'>Web Audio API 211 * </a>.</p> 212 * 213 * @method getAudioContext 214 * @return {Object} AudioContext for this sketch 215 */ 216 p5.prototype.getAudioContext = function () { 217 return audiocontext; 218 }; 219 // Polyfill for AudioIn, also handled by p5.dom createCapture 220 navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; 221 /** 222 * Determine which filetypes are supported (inspired by buzz.js) 223 * The audio element (el) will only be used to test browser support for various audio formats 224 */ 225 var el = document.createElement('audio'); 226 p5.prototype.isSupported = function () { 227 return !!el.canPlayType; 228 }; 229 var isOGGSupported = function () { 230 return !!el.canPlayType && el.canPlayType('audio/ogg; codecs="vorbis"'); 231 }; 232 var isMP3Supported = function () { 233 return !!el.canPlayType && el.canPlayType('audio/mpeg;'); 234 }; 235 var isWAVSupported = function () { 236 return !!el.canPlayType && el.canPlayType('audio/wav; codecs="1"'); 237 }; 238 var isAACSupported = function () { 239 return !!el.canPlayType && (el.canPlayType('audio/x-m4a;') || el.canPlayType('audio/aac;')); 240 }; 241 var isAIFSupported = function () { 242 return !!el.canPlayType && el.canPlayType('audio/x-aiff;'); 243 }; 244 p5.prototype.isFileSupported = function (extension) { 245 switch (extension.toLowerCase()) { 246 case 'mp3': 247 return isMP3Supported(); 248 case 'wav': 249 return isWAVSupported(); 250 case 'ogg': 251 return isOGGSupported(); 252 case 'aac', 'm4a', 'mp4': 253 return isAACSupported(); 254 case 'aif', 'aiff': 255 return isAIFSupported(); 256 default: 257 return false;
258 } 259 }; 260 // if it is iOS, we have to have a user interaction to start Web Audio 261 // http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ 262 var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false; 263 if (iOS) { 264 var iosStarted = false; 265 var startIOS = function () { 266 if (iosStarted) 267 return; 268 // create empty buffer 269 var buffer = audiocontext.createBuffer(1, 1, 22050); 270 var source = audiocontext.createBufferSource(); 271 source.buffer = buffer; 272 // connect to output (your speakers) 273 source.connect(audiocontext.destination); 274 // play the file 275 source.start(0); 276 console.log('start ios!'); 277 if (audiocontext.state === 'running') { 278 iosStarted = true; 279 } 280 }; 281 document.addEventListener('touchend', startIOS, false); 282 document.addEventListener('touchstart', startIOS, false); 283 } 284}(); 285var master; 286master = function () { 287 'use strict'; 288 /** 289 * Master contains AudioContext and the master sound output. 290 */ 291 var Master = function () { 292 var audiocontext = p5.prototype.getAudioContext(); 293 this.input = audiocontext.createGain(); 294 this.output = audiocontext.createGain(); 295 //put a hard limiter on the output 296 this.limiter = audiocontext.createDynamicsCompressor(); 297 this.limiter.threshold.value = 0; 298 this.limiter.ratio.value = 100; 299 this.audiocontext = audiocontext; 300 this.output.disconnect(); 301 // an array of input sources 302 this.inputSources = []; 303 // connect input to limiter 304 this.input.connect(this.limiter); 305 // connect limiter to output 306 this.limiter.connect(this.output); 307 // meter is just for global Amplitude / FFT analysis 308 this.meter = audiocontext.createGain(); 309 this.fftMeter = audiocontext.createGain(); 310 this.output.connect(this.meter); 311 this.output.connect(this.fftMeter); 312 // connect output to destination 313 this.output.connect(this.audiocontext.destination); 314 // an array of all sounds in the sketch 315 this.soundArray = []; 316 // an array of all musical parts in the sketch 317 this.parts = []; 318 // file extensions to search for 319 this.extensions = []; 320 }; 321 // create a single instance of the p5Sound / master output for use within this sketch 322 var p5sound = new Master(); 323 /** 324 * Returns a number representing the master amplitude (volume) for sound 325 * in this sketch. 326 * 327 * @method getMasterVolume 328 * @return {Number} Master amplitude (volume) for sound in this sketch. 329 * Should be between 0.0 (silence) and 1.0. 330 */ 331 p5.prototype.getMasterVolume = function () { 332 return p5sound.output.gain.value; 333 }; 334 /** 335 * <p>Scale the output of all sound in this sketch</p> 336 * Scaled between 0.0 (silence) and 1.0 (full volume). 337 * 1.0 is the maximum amplitude of a digital sound, so multiplying 338 * by greater than 1.0 may cause digital distortion. To 339 * fade, provide a <code>rampTime</code> parameter. For more 340 * complex fades, see the Env class. 341 * 342 * Alternately, you can pass in a signal source such as an 343 * oscillator to modulate the amplitude with an audio signal. 344 * 345 * <p><b>How This Works</b>: When you load the p5.sound module, it 346 * creates a single instance of p5sound. All sound objects in this 347 * module output to p5sound before reaching your computer's output. 348 * So if you change the amplitude of p5sound, it impacts all of the 349 * sound in this module.</p> 350 * 351 * <p>If no value is provided, returns a Web Audio API Gain Node</p> 352 * 353 * @method masterVolume 354 * @param {Number|Object} volume Volume (amplitude) between 0.0 355 * and 1.0 or modulating signal/oscillator 356 * @param {Number} [rampTime] Fade for t seconds 357 * @param {Number} [timeFromNow] Schedule this event to happen at 358 * t seconds in the future 359 */ 360 p5.prototype.masterVolume = function (vol, rampTime, tFromNow) { 361 if (typeof vol === 'number') { 362 var rampTime = rampTime || 0; 363 var tFromNow = tFromNow || 0; 364 var now = p5sound.audiocontext.currentTime; 365 var currentVol = p5sound.output.gain.value; 366 p5sound.output.gain.cancelScheduledValues(now + tFromNow); 367 p5sound.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); 368 p5sound.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); 369 } else if (vol) { 370 vol.connect(p5sound.output.gain); 371 } else { 372 // return the Gain Node 373 return p5sound.output.gain; 374 } 375 }; 376 /** 377 * p5.soundOut is the p5.sound master output. It sends output to 378 * the destination of this window's web audio context. It contains 379 * Web Audio API nodes including a dyanmicsCompressor (<code>.limiter</code>), 380 * and Gain Nodes for <code>.input</code> and <code>.output</code>. 381 * 382 * @property p5.soundOut 383 * @type {Object} 384 */ 385 p5.soundOut = p5sound; 386 /**
387 * a silent connection to the DesinationNode 388 * which will ensure that anything connected to it 389 * will not be garbage collected 390 * 391 * @private 392 */ 393 p5.soundOut._silentNode = p5sound.audiocontext.createGain(); 394 p5.soundOut._silentNode.gain.value = 0; 395 p5.soundOut._silentNode.connect(p5sound.audiocontext.destination); 396 return p5sound; 397}(sndcore); 398var helpers; 399helpers = function () { 400 'use strict'; 401 var p5sound = master; 402 /** 403 * Returns a number representing the sample rate, in samples per second, 404 * of all sound objects in this audio context. It is determined by the 405 * sampling rate of your operating system's sound card, and it is not 406 * currently possile to change. 407 * It is often 44100, or twice the range of human hearing. 408 * 409 * @method sampleRate 410 * @return {Number} samplerate samples per second 411 */ 412 p5.prototype.sampleRate = function () { 413 return p5sound.audiocontext.sampleRate; 414 }; 415 /** 416 * Returns the closest MIDI note value for 417 * a given frequency. 418 * 419 * @param {Number} frequency A freqeuncy, for example, the "A" 420 * above Middle C is 440Hz 421 * @return {Number} MIDI note value 422 */ 423 p5.prototype.freqToMidi = function (f) { 424 var mathlog2 = Math.log(f / 440) / Math.log(2); 425 var m = Math.round(12 * mathlog2) + 57; 426 return m; 427 }; 428 /** 429 * Returns the frequency value of a MIDI note value. 430 * General MIDI treats notes as integers where middle C 431 * is 60, C# is 61, D is 62 etc. Useful for generating 432 * musical frequencies with oscillators. 433 * 434 * @method midiToFreq 435 * @param {Number} midiNote The number of a MIDI note 436 * @return {Number} Frequency value of the given MIDI note 437 * @example 438 * <div><code> 439 * var notes = [60, 64, 67, 72]; 440 * var i = 0; 441 * 442 * function setup() { 443 * osc = new p5.Oscillator('Triangle'); 444 * osc.start(); 445 * frameRate(1); 446 * } 447 * 448 * function draw() { 449 * var freq = midiToFreq(notes[i]); 450 * osc.freq(freq); 451 * i++; 452 * if (i >= notes.length){ 453 * i = 0; 454 * } 455 * } 456 * </code></div> 457 */ 458 p5.prototype.midiToFreq = function (m) { 459 return 440 * Math.pow(2, (m - 69) / 12); 460 }; 461 /** 462 * List the SoundFile formats that you will include. LoadSound 463 * will search your directory for these extensions, and will pick 464 * a format that is compatable with the client's web browser. 465 * <a href="http://media.io/">Here</a> is a free online file 466 * converter. 467 * 468 * @method soundFormats 469 * @param {String|Strings} formats i.e. 'mp3', 'wav', 'ogg' 470 * @example 471 * <div><code> 472 * function preload() { 473 * // set the global sound formats 474 * soundFormats('mp3', 'ogg'); 475 * 476 * // load either beatbox.mp3, or .ogg, depending on browser 477 * mySound = loadSound('../sounds/beatbox.mp3'); 478 * } 479 * 480 * function setup() { 481 * mySound.play(); 482 * } 483 * </code></div> 484 */ 485 p5.prototype.soundFormats = function () { 486 // reset extensions array 487 p5sound.extensions = []; 488 // add extensions 489 for (var i = 0; i < arguments.length; i++) { 490 arguments[i] = arguments[i].toLowerCase(); 491 if ([ 492 'mp3', 493 'wav', 494 'ogg', 495 'm4a', 496 'aac' 497 ].indexOf(arguments[i]) > -1) { 498 p5sound.extensions.push(arguments[i]); 499 } else { 500 throw arguments[i] + ' is not a valid sound format!'; 501 } 502 } 503 }; 504 p5.prototype.disposeSound = function () { 505 for (var i = 0; i < p5sound.soundArray.length; i++) { 506 p5sound.soundArray[i].dispose(); 507 } 508 }; 509 // register removeSound to dispose of p5sound SoundFiles, Convolvers, 510 // Oscillators etc when sketch ends 511 p5.prototype.registerMethod('remove', p5.prototype.disposeSound); 512 p5.prototype._checkFileFormats = function (paths) { 513 var path; 514 // if path is a single string, check to see if extension is provided 515 if (typeof paths === 'string') { 516 path = paths; 517 // see if extension is provided 518 var extTest = path.split('.').pop(); 519 // if an extension is provided... 520 if ([ 521 'mp3', 522 'wav', 523 'ogg', 524 'm4a', 525 'aac' 526 ].indexOf(extTest) > -1) { 527 var supported = p5.prototype.isFileSupported(extTest); 528 if (supported) { 529 path = path; 530 } else { 531 var pathSplit = path.split('.'); 532 var pathCore = pathSplit[pathSplit.length - 1]; 533 for (var i = 0; i < p5sound.extensions.length; i++) { 534 var extension = p5sound.extensions[i]; 535 var supported = p5.prototype.isFileSupported(extension); 536 if (supported) { 537 pathCore = ''; 538 if (pathSplit.length === 2) { 539 pathCore += pathSplit[0]; 540 } 541 for (var i = 1; i <= pathSplit.length - 2; i++) { 542 var p = pathSplit[i]; 543 pathCore += '.' + p; 544 } 545 path = pathCore += '.'; 546 path = path += extension; 547 break; 548 } 549 } 550 } 551 } else { 552 for (var i = 0; i < p5sound.extensions.length; i++) { 553 var extension = p5sound.extensions[i]; 554 var supported = p5.prototype.isFileSupported(extension); 555 if (supported) { 556 path = path + '.' + extension; 557 break; 558 } 559 } 560 } 561 } else if (typeof paths === 'object') { 562 for (var i = 0; i < paths.length; i++) { 563 var extension = paths[i].split('.').pop(); 564 var supported = p5.prototype.isFileSupported(extension); 565 if (supported) { 566 // console.log('.'+extension + ' is ' + supported + 567 // ' supported by your browser.'); 568 path = paths[i]; 569 break; 570 } 571 } 572 } 573 return path; 574 }; 575 /** 576 * Used by Osc and Env to chain signal math 577 */ 578 p5.prototype._mathChain = function (o, math, thisChain, nextChain, type) { 579 // if this type of math already exists in the chain, replace it 580 for (var i in o.mathOps) { 581 if (o.mathOps[i] instanceof type) { 582 o.mathOps[i].dispose(); 583 thisChain = i; 584 if (thisChain < o.mathOps.length - 1) { 585 nextChain = o.mathOps[i + 1]; 586 } 587 } 588 } 589 o.mathOps[thisChain - 1].disconnect(); 590 o.mathOps[thisChain - 1].connect(math); 591 math.connect(nextChain); 592 o.mathOps[thisChain] = math; 593 return o; 594 }; 595}(master); 596var errorHandler; 597errorHandler = function () { 598 'use strict'; 599 /** 600 * Helper function to generate an error 601 * with a custom stack trace that points to the sketch 602 * and removes other parts of the stack trace. 603 * 604 * @private 605 * 606 * @param {String} name custom error name 607 * @param {String} errorTrace custom error trace 608 * @param {String} failedPath path to the file that failed to load 609 * @property {String} name custom error name 610 * @property {String} message custom error message 611 * @property {String} stack trace the error back to a line in the user's sketch. 612 * Note: this edits out stack trace within p5.js and p5.sound. 613 * @property {String} originalStack unedited, original stack trace 614 * @property {String} failedPath path to the file that failed to load 615 * @return {Error} returns a custom Error object 616 */ 617 var CustomError = function (name, errorTrace, failedPath) { 618 var err = new Error(); 619 var tempStack, splitStack; 620 err.name = name; 621 err.originalStack = err.stack + errorTrace; 622 tempStack = err.stack + errorTrace; 623 err.failedPath = failedPath; 624 // only print the part of the stack trace that refers to the user co
624de: 625 var splitStack = tempStack.split('\n'); 626 splitStack = splitStack.filter(function (ln) { 627 return !ln.match(/(p5.|native code|globalInit)/g); 628 }); 629 err.stack = splitStack.join('\n'); 630 return err; 631 }; 632 return CustomError; 633}(); 634var panner; 635panner = function () { 636 'use strict'; 637 var p5sound = master; 638 var ac = p5sound.audiocontext; 639 // Stereo panner 640 // if there is a stereo panner node use it 641 if (typeof ac.createStereoPanner !== 'undefined') { 642 p5.Panner = function (input, output, numInputChannels) { 643 this.stereoPanner = this.input = ac.createStereoPanner(); 644 input.connect(this.stereoPanner); 645 this.stereoPanner.connect(output); 646 }; 647 p5.Panner.prototype.pan = function (val, tFromNow) { 648 var time = tFromNow || 0; 649 var t = ac.currentTime + time; 650 this.stereoPanner.pan.linearRampToValueAtTime(val, t); 651 }; 652 p5.Panner.prototype.inputChannels = function (numChannels) { 653 }; 654 p5.Panner.prototype.connect = function (obj) { 655 this.stereoPanner.connect(obj); 656 }; 657 p5.Panner.prototype.disconnect = function (obj) { 658 this.stereoPanner.disconnect(); 659 }; 660 } else { 661 // if there is no createStereoPanner object 662 // such as in safari 7.1.7 at the time of writing this 663 // use this method to create the effect 664 p5.Panner = function (input, output, numInputChannels) { 665 this.input = ac.createGain(); 666 input.connect(this.input); 667 this.left = ac.createGain(); 668 this.right = ac.createGain(); 669 this.left.channelInterpretation = 'discrete'; 670 this.right.channelInterpretation = 'discrete'; 671 // if input is stereo 672 if (numInputChannels > 1) { 673 this.splitter = ac.createChannelSplitter(2); 674 this.input.connect(this.splitter); 675 this.splitter.connect(this.left, 1); 676 this.splitter.connect(this.right, 0); 677 } else { 678 this.input.connect(this.left); 679 this.input.connect(this.right); 680 } 681 this.output = ac.createChannelMerger(2); 682 this.left.connect(this.output, 0, 1); 683 this.right.connect(this.output, 0, 0); 684 this.output.connect(output); 685 }; 686 // -1 is left, +1 is right 687 p5.Panner.prototype.pan = function (val, tFromNow) { 688 var time = tFromNow || 0; 689 var t = ac.currentTime + time; 690 var v = (val + 1) / 2; 691 var rightVal = Math.cos(v * Math.PI / 2); 692 var leftVal = Math.sin(v * Math.PI / 2); 693 this.left.gain.linearRampToValueAtTime(leftVal, t); 694 this.right.gain.linearRampToValueAtTime(rightVal, t); 695 }; 696 p5.Panner.prototype.inputChannels = function (numChannels) { 697 if (numChannels === 1) { 698 this.input.disconnect(); 699 this.input.connect(this.left); 700 this.input.connect(this.right); 701 } else if (numChannels === 2) { 702 if (typeof (this.splitter === 'undefined')) { 703 this.splitter = ac.createChannelSplitter(2); 704 } 705 this.input.disconnect(); 706 this.input.connect(this.splitter); 707 this.splitter.connect(this.left, 1); 708 this.splitter.connect(this.right, 0); 709 } 710 }; 711 p5.Panner.prototype.connect = function (obj) { 712 this.output.connect(obj); 713 }; 714 p5.Panner.prototype.disconnect = function (obj) { 715 this.output.disconnect(); 716 }; 717 } 718 // 3D panner 719 p5.Panner3D = function (input, output) { 720 var panner3D = ac.createPanner(); 721 panner3D.panningModel = 'HRTF'; 722 panner3D.distanceModel = 'linear'; 723 panner3D.setPosition(0, 0, 0); 724 input.connect(panner3D); 725 panner3D.connect(output); 726 panner3D.pan = function (xVal, yVal, zVal) { 727 panner3D.setPosition(xVal, yVal, zVal); 728 }; 729 return panner3D; 730 }; 731}(master); 732var soundfile; 733soundfile = function () { 734 'use strict'; 735 var CustomError = errorHandler; 736 var p5sound = master; 737 var ac = p5sound.audiocontext; 738 /** 739 * <p>SoundFile object with a path to a file.</p> 740 * 741 * <p>The p5.SoundFile may not be available immediately because 742 * it loads the file information asynchronously.</p> 743 * 744 * <p>To do something with the sound as soon as it loads 745 * pass the name of a function as the second parameter.</p> 746 * 747 * <p>Only one file path is required. However, audio file formats 748 * (i.e. mp3, ogg, wav and m4a/aac) are not supported by all 749 * web browsers. If you want to ensure compatability, instead of a single 750 * file path, you may include an Array of filepaths, and the browser will 751 * choose a format that works.</p> 752 * 753 * @class p5.SoundFile 754 * @constructor 755 * @param {String/Array} path path to a sound file (String). Optionally, 756 * you may include multiple file formats in 757 * an array. Alternately, accepts an object 758 * from the HTML5 File API, or a p5.File. 759 * @param {Function} [successCallback] Name of a function to call once file loads 760 * @param {Function} [errorCallback] Name of a function to call if file fails to 761 * load. This function will receive an error or 762 * XMLHttpRequest object with information 763 * about what went wrong. 764 * @param {Function} [whileLoadingCallback] Name of a function to call while file 765 * is loading. That function will 766 * receive percentage loaded 767 * (between 0 and 1) as a 768 * parameter. 769 * 770 * @return {Object} p5.SoundFile Object 771 * @example 772 * <div><code> 773 * 774 * function preload() { 775 * mySound = loadSound('assets/doorbell.mp3'); 776 * } 777 * 778 * function setup() { 779 * mySound.setVolume(0.1); 780 * mySound.play(); 781 * } 782 * 783 * </code></div> 784 */ 785 p5.SoundFile = function (paths, onload, onerror, whileLoading) { 786 if (typeof paths !== 'undefined') { 787 if (typeof paths == 'string' || typeof paths[0] == 'string') { 788 var path = p5.prototype._checkFileFormats(paths); 789 this.url = path; 790 } else if (typeof paths == 'object') { 791 if (!(window.File && window.FileReader && window.FileList && window.Blob)) { 792 // The File API isn't supported in this browser 793 throw 'Unable to load file because the File API is not supported'; 794 } 795 } 796 // if type is a p5.File...get the actual file 797 if (paths.file) { 798 paths = paths.file; 799 } 800 this.file = paths; 801 } 802 // private _onended callback, set by the method: onended(callback) 803 this._onended = function () { 804 }; 805 this._looping = false;
806 this._playing = false; 807 this._paused = false; 808 this._pauseTime = 0; 809 // cues for scheduling events with addCue() removeCue() 810 this._cues = []; 811 // position of the most recently played sample 812 this._lastPos = 0; 813 this._counterNode; 814 this._scopeNode; 815 // array of sources so that they can all be stopped! 816 this.bufferSourceNodes = []; 817 // current source 818 this.bufferSourceNode = null; 819 this.buffer = null; 820 this.playbackRate = 1; 821 this.gain = 1; 822 this.input = p5sound.audiocontext.createGain(); 823 this.output = p5sound.audiocontext.createGain(); 824 this.reversed = false; 825 // start and end of playback / loop 826 this.startTime = 0; 827 this.endTime = null; 828 this.pauseTime = 0; 829 // "restart" would stop playback before retriggering 830 this.mode = 'sustain'; 831 // time that playback was started, in millis 832 this.startMillis = null; 833 // stereo panning 834 this.panPosition = 0; 835 this.panner = new p5.Panner(this.output, p5sound.input, 2); 836 // it is possible to instantiate a soundfile with no path 837 if (this.url || this.file) { 838 this.load(onload, onerror); 839 } 840 // add this p5.SoundFile to the soundArray 841 p5sound.soundArray.push(this); 842 if (typeof whileLoading === 'function') { 843 this._whileLoading = whileLoading; 844 } else { 845 this._whileLoading = function () { 846 }; 847 } 848 }; 849 // register preload handling of loadSound 850 p5.prototype.registerPreloadMethod('loadSound', p5.prototype); 851 /** 852 * loadSound() returns a new p5.SoundFile from a specified 853 * path. If called during preload(), the p5.SoundFile will be ready 854 * to play in time for setup() and draw(). If called outside of 855 * preload, the p5.SoundFile will not be ready immediately, so 856 * loadSound accepts a callback as the second parameter. Using a 857 * <a href="https://github.com/processing/p5.js/wiki/Local-server"> 858 * local server</a> is recommended when loading external files. 859 * 860 * @method loadSound 861 * @param {String/Array} path Path to the sound file, or an array with 862 * paths to soundfiles in multiple formats 863 * i.e. ['sound.ogg', 'sound.mp3']. 864 * Alternately, accepts an object: either 865 * from the HTML5 File API, or a p5.File. 866 * @param {Function} [successCallback] Name of a function to call once file loads 867 * @param {Function} [errorCallback] Name of a function to call if there is 868 * an error loading the file. 869 * @param {Function} [whileLoading] Name of a function to call while file is loading. 870 * This function will receive the percentage loaded 871 * so far, from 0.0 to 1.0. 872 * @return {SoundFile} Returns a p5.SoundFile 873 * @example 874 * <div><code> 875 * function preload() { 876 * mySound = loadSound('assets/doorbell.mp3'); 877 * } 878 * 879 * function setup() { 880 * mySound.setVolume(0.1); 881 * mySound.play(); 882 * } 883 * </code></div> 884 */ 885 p5.prototype.loadSound = function (path, callback, onerror, whileLoading) { 886 // if loading locally without a server 887 if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { 888 alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); 889 } 890 var s = new p5.SoundFile(path, callback, onerror, whileLoading); 891 return s; 892 }; 893 /** 894 * This is a helper function that the p5.SoundFile calls to load 895 * itself. Accepts a callback (the name of another function) 896 * as an optional parameter. 897 * 898 * @private 899 * @param {Function} [successCallback] Name of a function to call once file loads 900 * @param {Function} [errorCallback] Name of a function to call if there is an error 901 */ 902 p5.SoundFile.prototype.load = function (callback, errorCallback) { 903 var loggedError = false; 904 var self = this; 905 var errorTrace = new Error().stack; 906 if (this.url != undefined && this.url != '') { 907 var request = new XMLHttpRequest(); 908 request.addEventListener('progress', function (evt) { 909 self._updateProgress(evt); 910 }, false); 911 request.open('GET', this.url, true); 912 request.responseType = 'arraybuffer'; 913 request.onload = function () { 914 if (request.status == 200) { 915 // on sucess loading file: 916 ac.decodeAudioData(request.response, // success decoding buffer: 917 function (buff) { 918 self.buffer = buff; 919 self.panner.inputChannels(buff.numberOfChannels); 920 if (callback) { 921 callback(self); 922 } 923 }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 924 function (e) { 925 var err = new CustomError('decodeAudioData', errorTrace, self.url); 926 var msg = 'AudioContext error at decodeAudioData for ' + self.url; 927 if (errorCallback) { 928 err.msg = msg; 929 errorCallback(err); 930 } else { 931 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 932 } 933 }); 934 } else { 935 var err = new CustomError('loadSound', errorTrace, self.url); 936 var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; 937 if (errorCallback) { 938 err.message = msg; 939 errorCallback(err); 940 } else { 941 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 942 } 943 } 944 }; 945 // if there is another error, aside from 404... 946 request.onerror = function (e) { 947 var err = new CustomError('loadSound', errorTrace, self.url); 948 var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.';
949 if (errorCallback) { 950 err.message = msg; 951 errorCallback(err); 952 } else { 953 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 954 } 955 }; 956 request.send(); 957 } else if (this.file != undefined) { 958 var reader = new FileReader(); 959 var self = this; 960 reader.onload = function () { 961 ac.decodeAudioData(reader.result, function (buff) { 962 self.buffer = buff; 963 self.panner.inputChannels(buff.numberOfChannels); 964 if (callback) { 965 callback(self); 966 } 967 }); 968 }; 969 reader.onerror = function (e) { 970 if (onerror) 971 onerror(e); 972 }; 973 reader.readAsArrayBuffer(this.file); 974 } 975 }; 976 // TO DO: use this method to create a loading bar that shows progress during file upload/decode. 977 p5.SoundFile.prototype._updateProgress = function (evt) { 978 if (evt.lengthComputable) { 979 var percentComplete = Math.log(evt.loaded / evt.total * 9.9); 980 this._whileLoading(percentComplete); 981 } else { 982 // Unable to compute progress information since the total size is unknown 983 this._whileLoading('size unknown'); 984 } 985 }; 986 /** 987 * Returns true if the sound file finished loading successfully. 988 * 989 * @method isLoaded 990 * @return {Boolean} 991 */ 992 p5.SoundFile.prototype.isLoaded = function () { 993 if (this.buffer) { 994 return true; 995 } else { 996 return false; 997 } 998 }; 999 /** 1000 * Play the p5.SoundFile 1001 * 1002 * @method play 1003 * @param {Number} [startTime] (optional) schedule playback to start (in seconds from now). 1004 * @param {Number} [rate] (optional) playback rate 1005 * @param {Number} [amp] (optional) amplitude (volume) 1006 * of playback 1007 * @param {Number} [cueStart] (optional) cue start time in seconds 1008 * @param {Number} [duration] (optional) duration of playback in seconds 1009 */ 1010 p5.SoundFile.prototype.play = function (time, rate, amp, _cueStart, duration) { 1011 var self = this; 1012 var now = p5sound.audiocontext.currentTime; 1013 var cueStart, cueEnd; 1014 var time = time || 0; 1015 if (time < 0) { 1016 time = 0; 1017 } 1018 time = time + now; 1019 // TO DO: if already playing, create array of buffers for easy stop() 1020 if (this.buffer) { 1021 // reset the pause time (if it was paused) 1022 this._pauseTime = 0; 1023 // handle restart playmode 1024 if (this.mode === 'restart' && this.buffer && this.bufferSourceNode) { 1025 var now = p5sound.audiocontext.currentTime; 1026 this.bufferSourceNode.stop(time); 1027 this._counterNode.stop(time); 1028 } 1029 // make a new source and counter. They are automatically assigned playbackRate and buffer 1030 this.bufferSourceNode = this._initSourceNode(); 1031 // garbage collect counterNode and create a new one 1032 if (this._counterNode) 1033 this._counterNode = undefined; 1034 this._counterNode = this._initCounterNode(); 1035 if (_cueStart) { 1036 if (_cueStart >= 0 && _cueStart < this.buffer.duration) { 1037 // this.startTime = cueStart; 1038 cueStart = _cueStart; 1039 } else { 1040 throw 'start time out of range'; 1041 } 1042 } else { 1043 cueStart = 0; 1044 } 1045 if (duration) { 1046 // if duration is greater than buffer.duration, just play entire file anyway rather than throw an error 1047 duration = duration <= this.buffer.duration - cueStart ? duration : this.buffer.duration; 1048 } else { 1049 duration = this.buffer.duration - cueStart; 1050 } 1051 // TO DO: Fix this. It broke in Safari 1052 // 1053 // method of controlling gain for individual bufferSourceNodes, without resetting overall soundfile volume 1054 // if (typeof(this.bufferSourceNode.gain === 'undefined' ) ) { 1055 // this.bufferSourceNode.gain = p5sound.audiocontext.createGain(); 1056 // } 1057 // this.bufferSourceNode.connect(this.bufferSourceNode.gain); 1058 // set local amp if provided, otherwise 1 1059 var a = amp || 1; 1060 // this.bufferSourceNode.gain.gain.setValueAtTime(a, p5sound.audiocontext.currentTime); 1061 // this.bufferSourceNode.gain.connect(this.output); 1062 this.bufferSourceNode.connect(this.output); 1063 this.output.gain.value = a; 1064 // not necessary with _initBufferSource ? 1065 // this.bufferSourceNode.playbackRate.cancelScheduledValues(now); 1066 rate = rate || Math.abs(this.playbackRate); 1067 this.bufferSourceNode.playbackRate.setValueAtTime(rate, now); 1068 // if it was paused, play at the pause position 1069 if (this._paused) { 1070 this.bufferSourceNode.start(time, this.pauseTime, duration); 1071 this._counterNode.start(time, this.pauseTime, duration); 1072 } else { 1073 this.bufferSourceNode.start(time, cueStart, duration); 1074 this._counterNode.start(time, cueStart, duration); 1075 } 1076 this._playing = true; 1077 this._paused = false;
1078 // add source to sources array, which is used in stopAll() 1079 this.bufferSourceNodes.push(this.bufferSourceNode); 1080 this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1; 1081 // delete this.bufferSourceNode from the sources array when it is done playing: 1082 var clearOnEnd = function (e) { 1083 this._playing = false; 1084 this.removeEventListener('ended', clearOnEnd, false); 1085 // call the onended callback 1086 self._onended(self); 1087 self.bufferSourceNodes.forEach(function (n, i) { 1088 if (n._playing === false) { 1089 self.bufferSourceNodes.splice(i); 1090 } 1091 }); 1092 if (self.bufferSourceNodes.length === 0) { 1093 self._playing = false; 1094 } 1095 }; 1096 this.bufferSourceNode.onended = clearOnEnd; 1097 } else { 1098 throw 'not ready to play file, buffer has yet to load. Try preload()'; 1099 } 1100 // if looping, will restart at original time 1101 this.bufferSourceNode.loop = this._looping; 1102 this._counterNode.loop = this._looping; 1103 if (this._looping === true) { 1104 var cueEnd = cueStart + duration; 1105 this.bufferSourceNode.loopStart = cueStart; 1106 this.bufferSourceNode.loopEnd = cueEnd; 1107 this._counterNode.loopStart = cueStart; 1108 this._counterNode.loopEnd = cueEnd; 1109 } 1110 }; 1111 /** 1112 * p5.SoundFile has two play modes: <code>restart</code> and 1113 * <code>sustain</code>. Play Mode determines what happens to a 1114 * p5.SoundFile if it is triggered while in the middle of playback. 1115 * In sustain mode, playback will continue simultaneous to the 1116 * new playback. In restart mode, play() will stop playback 1117 * and start over. Sustain is the default mode. 1118 * 1119 * @method playMode 1120 * @param {String} str 'restart' or 'sustain' 1121 * @example 1122 * <div><code> 1123 * function setup(){ 1124 * mySound = loadSound('assets/Damscray_DancingTiger.mp3'); 1125 * } 1126 * function mouseClicked() { 1127 * mySound.playMode('sustain'); 1128 * mySound.play(); 1129 * } 1130 * function keyPressed() { 1131 * mySound.playMode('restart'); 1132 * mySound.play(); 1133 * } 1134 * 1135 * </code></div> 1136 */ 1137 p5.SoundFile.prototype.playMode = function (str) { 1138 var s = str.toLowerCase(); 1139 // if restart, stop all other sounds from playing 1140 if (s === 'restart' && this.buffer && this.bufferSourceNode) { 1141 for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { 1142 var now = p5sound.audiocontext.currentTime; 1143 this.bufferSourceNodes[i].stop(now); 1144 } 1145 } 1146 // set play mode to effect future playback 1147 if (s === 'restart' || s === 'sustain') { 1148 this.mode = s; 1149 } else { 1150 throw 'Invalid play mode. Must be either "restart" or "sustain"'; 1151 } 1152 }; 1153 /** 1154 * Pauses a file that is currently playing. If the file is not 1155 * playing, then nothing will happen. 1156 * 1157 * After pausing, .play() will resume from the paused 1158 * position. 1159 * If p5.SoundFile had been set to loop before it was paused, 1160 * it will continue to loop after it is unpaused with .play(). 1161 * 1162 * @method pause 1163 * @param {Number} [startTime] (optional) schedule event to occur 1164 * seconds from now 1165 * @example 1166 * <div><code> 1167 * var soundFile; 1168 * 1169 * function preload() { 1170 * soundFormats('ogg', 'mp3'); 1171 * soundFile = loadSound('assets/Damscray_-_Dancing_Tiger_02.mp3'); 1172 * } 1173 * function setup() { 1174 * background(0, 255, 0); 1175 * soundFile.setVolume(0.1); 1176 * soundFile.loop(); 1177 * } 1178 * function keyTyped() { 1179 * if (key == 'p') { 1180 * soundFile.pause(); 1181 * background(255, 0, 0); 1182 * } 1183 * } 1184 * 1185 * function keyReleased() { 1186 * if (key == 'p') { 1187 * soundFile.play(); 1188 * background(0, 255, 0); 1189 * } 1190 * </code> 1191 * </div> 1192 */ 1193 p5.SoundFile.prototype.pause = function (time) { 1194 var now = p5sound.audiocontext.currentTime; 1195 var time = time || 0; 1196 var pTime = time + now; 1197 if (this.isPlaying() && this.buffer && this.bufferSourceNode) { 1198 this.pauseTime = this.currentTime(); 1199 this.bufferSourceNode.stop(pTime); 1200 this._counterNode.stop(pTime); 1201 this._paused = true; 1202 this._playing = false;
1203 this._pauseTime = this.currentTime(); 1204 } else { 1205 this._pauseTime = 0; 1206 } 1207 }; 1208 /** 1209 * Loop the p5.SoundFile. Accepts optional parameters to set the 1210 * playback rate, playback volume, loopStart, loopEnd. 1211 * 1212 * @method loop 1213 * @param {Number} [startTime] (optional) schedule event to occur 1214 * seconds from now 1215 * @param {Number} [rate] (optional) playback rate 1216 * @param {Number} [amp] (optional) playback volume 1217 * @param {Number} [cueLoopStart](optional) startTime in seconds 1218 * @param {Number} [duration] (optional) loop duration in seconds 1219 */ 1220 p5.SoundFile.prototype.loop = function (startTime, rate, amp, loopStart, duration) { 1221 this._looping = true; 1222 this.play(startTime, rate, amp, loopStart, duration); 1223 }; 1224 /** 1225 * Set a p5.SoundFile's looping flag to true or false. If the sound 1226 * is currently playing, this change will take effect when it 1227 * reaches the end of the current playback. 1228 * 1229 * @param {Boolean} Boolean set looping to true or false 1230 */ 1231 p5.SoundFile.prototype.setLoop = function (bool) { 1232 if (bool === true) { 1233 this._looping = true; 1234 } else if (bool === false) { 1235 this._looping = false; 1236 } else { 1237 throw 'Error: setLoop accepts either true or false'; 1238 } 1239 if (this.bufferSourceNode) { 1240 this.bufferSourceNode.loop = this._looping; 1241 this._counterNode.loop = this._looping; 1242 } 1243 }; 1244 /** 1245 * Returns 'true' if a p5.SoundFile is currently looping and playing, 'false' if not. 1246 * 1247 * @return {Boolean} 1248 */ 1249 p5.SoundFile.prototype.isLooping = function () { 1250 if (!this.bufferSourceNode) { 1251 return false; 1252 } 1253 if (this._looping === true && this.isPlaying() === true) { 1254 return true; 1255 } 1256 return false; 1257 }; 1258 /** 1259 * Returns true if a p5.SoundFile is playing, false if not (i.e. 1260 * paused or stopped). 1261 * 1262 * @method isPlaying 1263 * @return {Boolean} 1264 */ 1265 p5.SoundFile.prototype.isPlaying = function () { 1266 return this._playing; 1267 }; 1268 /** 1269 * Returns true if a p5.SoundFile is paused, false if not (i.e. 1270 * playing or stopped). 1271 * 1272 * @method isPaused 1273 * @return {Boolean} 1274 */ 1275 p5.SoundFile.prototype.isPaused = function () { 1276 return this._paused; 1277 }; 1278 /** 1279 * Stop soundfile playback. 1280 * 1281 * @method stop 1282 * @param {Number} [startTime] (optional) schedule event to occur 1283 * in seconds from now 1284 */ 1285 p5.SoundFile.prototype.stop = function (timeFromNow) { 1286 var time = timeFromNow || 0; 1287 if (this.mode == 'sustain') { 1288 this.stopAll(time); 1289 this._playing = false; 1290 this.pauseTime = 0; 1291 this._paused = false; 1292 } else if (this.buffer && this.bufferSourceNode) { 1293 var now = p5sound.audiocontext.currentTime; 1294 var t = time || 0; 1295 this.pauseTime = 0; 1296 this.bufferSourceNode.stop(now + t); 1297 this._counterNode.stop(now + t); 1298 this._playing = false; 1299 this._paused = false; 1300 } 1301 }; 1302 /** 1303 * Stop playback on all of this soundfile's sources. 1304 * @private 1305 */ 1306 p5.SoundFile.prototype.stopAll = function (_time) { 1307 var now = p5sound.audiocontext.currentTime; 1308 var time = _time || 0; 1309 if (this.buffer && this.bufferSourceNode) { 1310 for (var i = 0; i < this.bufferSourceNodes.length; i++) { 1311 if (typeof this.bufferSourceNodes[i] != undefined) { 1312 try { 1313 this.bufferSourceNodes[i].onended = function () { 1314 }; 1315 this.bufferSourceNodes[i].stop(now + time); 1316 } catch (e) { 1317 } 1318 } 1319 } 1320 this._counterNode.stop(now + time); 1321 this._onended(this); 1322 } 1323 }; 1324 /** 1325 * Multiply the output volume (amplitude) of a sound file 1326 * between 0.0 (silence) and 1.0 (full volume). 1327 * 1.0 is the maximum amplitude of a digital sound, so multiplying 1328 * by greater than 1.0 may cause digital distortion. To 1329 * fade, provide a <code>rampTime</code> parameter. For more 1330 * complex fades, see the Env class. 1331 * 1332 * Alternately, you can pass in a signal source such as an 1333 * oscillator to modulate the amplitude with an audio signal. 1334 * 1335 * @method setVolume 1336 * @param {Number|Object} volume Volume (amplitude) between 0.0 1337 * and 1.0 or modulating signal/oscillator 1338 * @param {Number} [rampTime] Fade for t seconds 1339 * @param {Number} [timeFromNow] Schedule this event to happen at
1340 * t seconds in the future 1341 */ 1342 p5.SoundFile.prototype.setVolume = function (vol, rampTime, tFromNow) { 1343 if (typeof vol === 'number') { 1344 var rampTime = rampTime || 0; 1345 var tFromNow = tFromNow || 0; 1346 var now = p5sound.audiocontext.currentTime; 1347 var currentVol = this.output.gain.value; 1348 this.output.gain.cancelScheduledValues(now + tFromNow); 1349 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); 1350 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); 1351 } else if (vol) { 1352 vol.connect(this.output.gain); 1353 } else { 1354 // return the Gain Node 1355 return this.output.gain; 1356 } 1357 }; 1358 // same as setVolume, to match Processing Sound 1359 p5.SoundFile.prototype.amp = p5.SoundFile.prototype.setVolume; 1360 // these are the same thing 1361 p5.SoundFile.prototype.fade = p5.SoundFile.prototype.setVolume; 1362 p5.SoundFile.prototype.getVolume = function () { 1363 return this.output.gain.value; 1364 }; 1365 /** 1366 * Set the stereo panning of a p5.sound object to 1367 * a floating point number between -1.0 (left) and 1.0 (right). 1368 * Default is 0.0 (center). 1369 * 1370 * @method pan 1371 * @param {Number} [panValue] Set the stereo panner 1372 * @param {Number} timeFromNow schedule this event to happen 1373 * seconds from now 1374 * @example 1375 * <div><code> 1376 * 1377 * var ball = {}; 1378 * var soundFile; 1379 * 1380 * function setup() { 1381 * soundFormats('ogg', 'mp3'); 1382 * soundFile = loadSound('assets/beatbox.mp3'); 1383 * } 1384 * 1385 * function draw() { 1386 * background(0); 1387 * ball.x = constrain(mouseX, 0, width); 1388 * ellipse(ball.x, height/2, 20, 20) 1389 * } 1390 * 1391 * function mousePressed(){ 1392 * // map the ball's x location to a panning degree 1393 * // between -1.0 (left) and 1.0 (right) 1394 * var panning = map(ball.x, 0., width,-1.0, 1.0); 1395 * soundFile.pan(panning); 1396 * soundFile.play(); 1397 * } 1398 * </div></code> 1399 */ 1400 p5.SoundFile.prototype.pan = function (pval, tFromNow) { 1401 this.panPosition = pval; 1402 this.panner.pan(pval, tFromNow); 1403 }; 1404 /** 1405 * Returns the current stereo pan position (-1.0 to 1.0) 1406 * 1407 * @return {Number} Returns the stereo pan setting of the Oscillator 1408 * as a number between -1.0 (left) and 1.0 (right). 1409 * 0.0 is center and default. 1410 */ 1411 p5.SoundFile.prototype.getPan = function () { 1412 return this.panPosition; 1413 }; 1414 /** 1415 * Set the playback rate of a sound file. Will change the speed and the pitch. 1416 * Values less than zero will reverse the audio buffer. 1417 * 1418 * @method rate 1419 * @param {Number} [playbackRate] Set the playback rate. 1.0 is normal, 1420 * .5 is half-speed, 2.0 is twice as fast. 1421 * Must be greater than zero. 1422 * @example 1423 * <div><code> 1424 * var song; 1425 * 1426 * function preload() { 1427 * song = loadSound('assets/Damscray_DancingTiger.mp3'); 1428 * } 1429 * 1430 * function setup() { 1431 * song.loop(); 1432 * } 1433 * 1434 * function draw() { 1435 * background(200); 1436 * 1437 * // Set the rate to a range between 0.1 and 4 1438 * // Changing the rate also alters the pitch 1439 * var speed = map(mouseY, 0.1, height, 0, 2); 1440 * speed = constrain(speed, 0.01, 4); 1441 * song.rate(speed); 1442 * 1443 * // Draw a circle to show what is going on 1444 * stroke(0); 1445 * fill(51, 100); 1446 * ellipse(mouseX, 100, 48, 48); 1447 * } 1448 * 1449 * </code> 1450 * </div> 1451 * 1452 */ 1453 p5.SoundFile.prototype.rate = function (playbackRate) { 1454 if (this.playbackRate === playbackRate && this.bufferSourceNode) { 1455 if (this.bufferSourceNode.playbackRate.value === playbackRate) { 1456 return; 1457 } 1458 } 1459 this.playbackRate = playbackRate; 1460 var rate = playbackRate; 1461 if (this.playbackRate === 0 && this._playing) { 1462 this.pause(); 1463 } 1464 if (this.playbackRate < 0 && !this.reversed) { 1465 var cPos = this.currentTime(); 1466 var cRate = this.bufferSourceNode.playbackRate.value; 1467 // this.pause(); 1468 this.reverseBuffer(); 1469 rate = Math.abs(playbackRate); 1470 var newPos = (cPos - this.duration()) / rate; 1471 this.pauseTime = newPos; 1472 } else if (this.playbackRate > 0 && this.reversed) { 1473 this.reverseBuffer(); 1474 } 1475 if (this.bufferSourceNode) { 1476 var now = p5sound.audiocontext.currentTime; 1477 this.bufferSourceNode.playbackRate.cancelScheduledValues(now); 1478 this.bufferSourceNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now); 1479 this._counterNode.playbackRate.cancelScheduledValues(now); 1480 this._counterNode.playbackRate.linearRampToValueAtTime(Math.abs(rate), now); 1481 } 1482 }; 1483 // TO DO: document this 1484 p5.SoundFile.prototype.setPitch = function (num) { 1485 var newPlaybackRate = midiToFreq(num) / midiToFreq(60); 1486 this.rate(newPlaybackRate); 1487 }; 1488 p5.SoundFile.prototype.getPlaybackRate = function () { 1489 return this.playbackRate; 1490 }; 1491 /** 1492 * Returns the duration of a sound file in seconds. 1493 * 1494 * @method duration 1495 * @return {Number} The duration of the soundFile in seconds. 1496 */ 1497 p5.SoundFile.prototype.duration = function () { 1498 // Return Duration 1499 if (this.buffer) { 1500 return this.buffer.duration; 1501 } else { 1502 return 0; 1503 } 1504 }; 1505 /** 1506 * Return the current position of the p5.SoundFile playhead, in seconds. 1507 * Note that if you change the playbackRate while the p5.SoundFile is 1508 * playing, the results may not be accurate. 1509 * 1510 * @method currentTime 1511 * @return {Number} currentTime of the soundFile in seconds. 1512 */ 1513 p5.SoundFile.prototype.currentTime = function () { 1514 // TO DO --> make reverse() flip these values appropriately 1515 if (this._pauseTime > 0) { 1516 return this._pauseTime; 1517 } else { 1518 return this._lastPos / ac.sampleRate; 1519 } 1520 }; 1521 /** 1522 * Move the playhead of the song to a position, in seconds. Start 1523 * and Stop time. If none are given, will reset the file to play 1524 * entire duration from start to finish. 1525 * 1526 * @method jump 1527 * @param {Number} cueTime cueTime of the soundFile in seconds. 1528 * @param {Number} uuration duration in seconds. 1529 */ 1530 p5.SoundFile.prototype.jump = function (cueTime, duration) { 1531 if (cueTime < 0 || cueTime > this.buffer.duration) { 1532 throw 'jump time out of range'; 1533 } 1534 if (duration > this.buffer.duration - cueTime) { 1535 throw 'end time out of range'; 1536 } 1537 var cTime = cueTime || 0; 1538 var eTime = duration || this.buffer.duration - cueTime; 1539 if (this.isPlaying()) { 1540 this.stop(); 1541 } 1542 this.play(0, this.playbackRate, this.output.gain.value, cTime, eTime); 1543 }; 1544 /** 1545 * Return the number of channels in a sound file. 1546 * For example, Mono = 1, Stereo = 2. 1547 * 1548 * @method channels 1549 * @return {Number} [channels] 1550 */ 1551 p5.SoundFile.prototype.channels = function () { 1552 return this.buffer.numberOfChannels; 1553 }; 1554 /** 1555 * Return the sample rate of the sound file. 1556 * 1557 * @method sampleRate 1558 * @return {Number} [sampleRate] 1559 */ 1560 p5.SoundFile.prototype.sampleRate = function () { 1561 return this.buffer.sampleRate; 1562 }; 1563 /** 1564 * Return the number of samples in a sound file. 1565 * Equal to sampleRate * duration. 1566 * 1567 * @method frames 1568 * @return {Number}
1568 [sampleCount] 1569 */ 1570 p5.SoundFile.prototype.frames = function () { 1571 return this.buffer.length; 1572 }; 1573 /** 1574 * Returns an array of amplitude peaks in a p5.SoundFile that can be 1575 * used to draw a static waveform. Scans through the p5.SoundFile's 1576 * audio buffer to find the greatest amplitudes. Accepts one 1577 * parameter, 'length', which determines size of the array. 1578 * Larger arrays result in more precise waveform visualizations. 1579 * 1580 * Inspired by Wavesurfer.js. 1581 * 1582 * @method getPeaks 1583 * @params {Number} [length] length is the size of the returned array. 1584 * Larger length results in more precision. 1585 * Defaults to 5*width of the browser window. 1586 * @returns {Float32Array} Array of peaks. 1587 */ 1588 p5.SoundFile.prototype.getPeaks = function (length) { 1589 if (this.buffer) { 1590 // set length to window's width if no length is provided 1591 if (!length) { 1592 length = window.width * 5; 1593 } 1594 if (this.buffer) { 1595 var buffer = this.buffer; 1596 var sampleSize = buffer.length / length; 1597 var sampleStep = ~~(sampleSize / 10) || 1; 1598 var channels = buffer.numberOfChannels; 1599 var peaks = new Float32Array(Math.round(length)); 1600 for (var c = 0; c < channels; c++) { 1601 var chan = buffer.getChannelData(c); 1602 for (var i = 0; i < length; i++) { 1603 var start = ~~(i * sampleSize); 1604 var end = ~~(start + sampleSize); 1605 var max = 0; 1606 for (var j = start; j < end; j += sampleStep) { 1607 var value = chan[j]; 1608 if (value > max) { 1609 max = value; 1610 } else if (-value > max) { 1611 max = value; 1612 } 1613 } 1614 if (c === 0 || Math.abs(max) > peaks[i]) { 1615 peaks[i] = max; 1616 } 1617 } 1618 } 1619 return peaks; 1620 } 1621 } else { 1622 throw 'Cannot load peaks yet, buffer is not loaded'; 1623 } 1624 }; 1625 /** 1626 * Reverses the p5.SoundFile's buffer source. 1627 * Playback must be handled separately (see example). 1628 * 1629 * @method reverseBuffer 1630 * @example 1631 * <div><code> 1632 * var drum; 1633 * 1634 * function preload() { 1635 * drum = loadSound('assets/drum.mp3'); 1636 * } 1637 * 1638 * function setup() { 1639 * drum.reverseBuffer(); 1640 * drum.play(); 1641 * } 1642 * 1643 * </code> 1644 * </div> 1645 */ 1646 p5.SoundFile.prototype.reverseBuffer = function () { 1647 var curVol = this.getVolume(); 1648 this.setVolume(0, 0.01, 0); 1649 this.pause(); 1650 if (this.buffer) { 1651 for (var i = 0; i < this.buffer.numberOfChannels; i++) { 1652 Array.prototype.reverse.call(this.buffer.getChannelData(i)); 1653 } 1654 // set reversed flag 1655 this.reversed = !this.reversed; 1656 } else { 1657 throw 'SoundFile is not done loading'; 1658 } 1659 this.setVolume(curVol, 0.01, 0.0101); 1660 this.play(); 1661 }; 1662 /** 1663 * Schedule an event to be called when the soundfile 1664 * reaches the end of a buffer. If the soundfile is 1665 * playing through once, this will be called when it 1666 * ends. If it is looping, it will be called when 1667 * stop is called. 1668 * 1669 * @method onended 1670 * @param {Function} callback function to call when the 1671 * soundfile has ended. 1672 */ 1673 p5.SoundFile.prototype.onended = function (callback) { 1674 this._onended = callback; 1675 return this; 1676 }; 1677 p5.SoundFile.prototype.add = function () { 1678 }; 1679 p5.SoundFile.prototype.dispose = function () { 1680 var now = p5sound.audiocontext.currentTime; 1681 // remove reference to soundfile 1682 var index = p5sound.soundArray.indexOf(this); 1683 p5sound.soundArray.splice(index, 1); 1684 this.stop(now); 1685 if (this.buffer && this.bufferSourceNode) { 1686 for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { 1687 if (this.bufferSourceNodes[i] !== null) { 1688 this.bufferSourceNodes[i].disconnect(); 1689 try { 1690 this.bufferSourceNodes[i].stop(now); 1691 } catch (e) { 1692 } 1693 this.bufferSourceNodes[i] = null; 1694 } 1695 } 1696 if (this.isPlaying()) { 1697 try { 1698 this._counterNode.stop(now); 1699 } catch (e) { 1700 console.log(e); 1701 } 1702 this._counterNode = null; 1703 } 1704 } 1705 if (this.output) { 1706 this.output.disconnect(); 1707 this.output = null; 1708 } 1709 if (this.panner) { 1710 this.panner.disconnect(); 1711 this.panner = null; 1712 } 1713 }; 1714 /** 1715 * Connects the output of a p5sound object to input of another 1716 * p5.sound object. For example, you may connect a p5.SoundFile to an 1717 * FFT or an Effect. If no parameter is given, it will connect to 1718 * the master output. Most p5sound objects connect to the master 1719 * output when they are created. 1720 * 1721 * @method connect 1722 * @param {Object} [object] Audio object that accepts an input 1723 */ 1724 p5.SoundFile.prototype.connect = function (unit) { 1725 if (!unit) { 1726 this.panner.connect(p5sound.input); 1727 } else { 1728 if (unit.hasOwnProperty('input')) {
1729 this.panner.connect(unit.input); 1730 } else { 1731 this.panner.connect(unit); 1732 } 1733 } 1734 }; 1735 /** 1736 * Disconnects the output of this p5sound object. 1737 * 1738 * @method disconnect 1739 */ 1740 p5.SoundFile.prototype.disconnect = function (unit) { 1741 this.panner.disconnect(unit); 1742 }; 1743 /** 1744 */ 1745 p5.SoundFile.prototype.getLevel = function (smoothing) { 1746 console.warn('p5.SoundFile.getLevel has been removed from the library. Use p5.Amplitude instead'); 1747 }; 1748 /** 1749 * Reset the source for this SoundFile to a 1750 * new path (URL). 1751 * 1752 * @method setPath 1753 * @param {String} path path to audio file 1754 * @param {Function} callback Callback 1755 */ 1756 p5.SoundFile.prototype.setPath = function (p, callback) { 1757 var path = p5.prototype._checkFileFormats(p); 1758 this.url = path; 1759 this.load(callback); 1760 }; 1761 /** 1762 * Replace the current Audio Buffer with a new Buffer. 1763 * 1764 * @param {Array} buf Array of Float32 Array(s). 2 Float32 Arrays 1765 * will create a stereo source. 1 will create 1766 * a mono source. 1767 */ 1768 p5.SoundFile.prototype.setBuffer = function (buf) { 1769 var numChannels = buf.length; 1770 var size = buf[0].length; 1771 var newBuffer = ac.createBuffer(numChannels, size, ac.sampleRate); 1772 if (!buf[0] instanceof Float32Array) { 1773 buf[0] = new Float32Array(buf[0]); 1774 } 1775 for (var channelNum = 0; channelNum < numChannels; channelNum++) { 1776 var channel = newBuffer.getChannelData(channelNum); 1777 channel.set(buf[channelNum]); 1778 } 1779 this.buffer = newBuffer; 1780 // set numbers of channels on input to the panner 1781 this.panner.inputChannels(numChannels); 1782 }; 1783 ////////////////////////////////////////////////// 1784 // script processor node with an empty buffer to help 1785 // keep a sample-accurate position in playback buffer. 1786 // Inspired by Chinmay Pendharkar's technique for Sonoport --> http://bit.ly/1HwdCsV 1787 // Copyright [2015] [Sonoport (Asia) Pte. Ltd.], 1788 // Licensed under the Apache License http://apache.org/licenses/LICENSE-2.0 1789 //////////////////////////////////////////////////////////////////////////////////// 1790 // initialize counterNode, set its initial buffer and playbackRate 1791 p5.SoundFile.prototype._initCounterNode = function () { 1792 var self = this; 1793 var now = ac.currentTime; 1794 var cNode = ac.createBufferSource(); 1795 // dispose of scope node if it already exists 1796 if (self._scopeNode) { 1797 self._scopeNode.disconnect(); 1798 self._scopeNode.onaudioprocess = undefined; 1799 self._scopeNode = null; 1800 } 1801 self._scopeNode = ac.createScriptProcessor(256, 1, 1); 1802 // create counter buffer of the same length as self.buffer 1803 cNode.buffer = _createCounterBuffer(self.buffer); 1804 cNode.playbackRate.setValueAtTime(self.playbackRate, now); 1805 cNode.connect(self._scopeNode); 1806 self._scopeNode.connect(p5.soundOut._silentNode); 1807 self._scopeNode.onaudioprocess = function (processEvent) { 1808 var inputBuffer = processEvent.inputBuffer.getChannelData(0); 1809 // update the lastPos 1810 self._lastPos = inputBuffer[inputBuffer.length - 1] || 0; 1811 // do any callbacks that have been scheduled 1812 self._onTimeUpdate(self._lastPos); 1813 }; 1814 return cNode; 1815 }; 1816 // initialize sourceNode, set its initial buffer and playbackRate 1817 p5.SoundFile.prototype._initSourceNode = function () { 1818 var self = this; 1819 var now = ac.currentTime; 1820 var bufferSourceNode = ac.createBufferSource(); 1821 bufferSourceNode.buffer = self.buffer; 1822 bufferSourceNode.playbackRate.setValueAtTime(self.playbackRate, now); 1823 return bufferSourceNode; 1824 }; 1825 var _createCounterBuffer = function (buffer) { 1826 var array = new Float32Array(buffer.length); 1827 var audioBuf = ac.createBuffer(1, buffer.length, 44100); 1828 for (var index = 0; index < buffer.length; index++) { 1829 array[index] = index; 1830 } 1831 audioBuf.getChannelData(0).set(array); 1832 return audioBuf; 1833 }; 1834 /** 1835 * processPeaks returns an array of timestamps where it thinks there is a beat. 1836 * 1837 * This is an asynchronous function that processes the soundfile in an offline audio context, 1838 * and sends the results to your callback function. 1839 * 1840 * The process involves running the soundfile through a lowpass filter, and finding all of the 1841 * peaks above the initial threshold. If the total number of peaks are below the minimum number of peaks, 1842 * it decreases the threshold and re-runs the analysis until either minPeaks or minThreshold are reached. 1843 * 1844 * @method processPeaks 1845 * @param {Function} callback a function to call once this data is returned 1846 * @param {Number} [initThreshold] initial threshold defaults to 0.9 1847 * @param {Number} [minThreshold] minimum threshold defaults to 0.22 1848 * @param {Number} [minPeaks] minimum number of peaks defaults to 200 1849 * @return {Array} Array of timestamped peaks 1850 */ 1851 p5.SoundFile.prototype.processPeaks = function (callback, _initThreshold, _minThreshold, _minPeaks) { 1852 var bufLen = this.buffer.length; 1853 var sampleRate = this.buffer.sampleRate; 1854 var buffer = this.buffer; 1855 var initialThreshold = _initThreshold || 0.9, threshold = initialThreshold, minThreshold = _minThreshold || 0.22, minPeaks = _minPeaks || 200; 1856 // Create offline context 1857 var offlineContext = new OfflineAudioContext(1, bufLen, sampleRate); 1858 // create buffer source 1859 var source = offlineContext.createBufferSource(); 1860 source.buffer = buffer; 1861 // Create filter. TO DO: allow custom setting of filter 1862 var filter = offlineContext.createBiquadFilter(); 1863 filter.type = 'lowpass'; 1864 source.connect(filter); 1865 filter.connect(offlineContext.destination); 1866 // start playing at time:0 1867 source.start(0); 1868 offlineContext.startRendering(); 1869 // Render the song 1870 // act on the result 1871 offlineContext.oncomplete = function (e) { 1872 var data = {}; 1873 var filteredBuffer = e.renderedBuffer; 1874 var bufferData = filteredBuffer.getChannelData(0); 1875 // step 1: 1876 // create Peak instances, add them to array, with strength and sampleIndex 1877 do { 1878 allPeaks = getPeaksAtThreshold(bufferData, threshold); 1879 threshold -= 0.005; 1880 } while (Object.keys(allPeaks).length < minPeaks && threshold >= minThreshold); 1881 // step 2: 1882 // find intervals for each peak in the sampleIndex, add tempos array 1883 var intervalCounts = countIntervalsBetweenNearbyPeaks(allPeaks); 1884 // step 3: find top tempos 1885 var groups = groupNeighborsByTempo(intervalCounts, filteredBuffer.sampleRate); 1886 // sort top intervals 1887 var topTempos = groups.sort(function (intA, intB) { 1888 return intB.count - intA.count; 1889 }).splice(0, 5); 1890 // set this SoundFile's tempo to the top tempo ?? 1891 this.tempo = topTempos[0].tempo; 1892 // step 4: 1893 // new array of peaks at top tempo within a bpmVariance 1894 var bpmVariance = 5; 1895 var tempoPeaks = getPeaksAtTopTempo(allPeaks, topTempos[0].tempo, filteredBuffer.sampleRate, bpmVariance); 1896 callback(tempoPeaks); 1897 }; 1898 }; 1899 // process peaks 1900 var Peak = function (amp, i) { 1901 this.sampleIndex = i; 1902 this.amplitude = amp; 1903 this.tempos = []; 1904 this.intervals = []; 1905 }; 1906 var allPeaks = [];
1907 // 1. for processPeaks() Function to identify peaks above a threshold 1908 // returns an array of peak indexes as frames (samples) of the original soundfile 1909 function getPeaksAtThreshold(data, threshold) { 1910 var peaksObj = {}; 1911 var length = data.length; 1912 for (var i = 0; i < length; i++) { 1913 if (data[i] > threshold) { 1914 var amp = data[i]; 1915 var peak = new Peak(amp, i); 1916 peaksObj[i] = peak; 1917 // Skip forward ~ 1/8s to get past this peak. 1918 i += 6000; 1919 } 1920 i++; 1921 } 1922 return peaksObj; 1923 } 1924 // 2. for processPeaks() 1925 function countIntervalsBetweenNearbyPeaks(peaksObj) { 1926 var intervalCounts = []; 1927 var peaksArray = Object.keys(peaksObj).sort(); 1928 for (var index = 0; index < peaksArray.length; index++) { 1929 // find intervals in comparison to nearby peaks 1930 for (var i = 0; i < 10; i++) { 1931 var startPeak = peaksObj[peaksArray[index]]; 1932 var endPeak = peaksObj[peaksArray[index + i]]; 1933 if (startPeak && endPeak) { 1934 var startPos = startPeak.sampleIndex; 1935 var endPos = endPeak.sampleIndex; 1936 var interval = endPos - startPos; 1937 // add a sample interval to the startPeek in the allPeaks array 1938 if (interval > 0) { 1939 startPeak.intervals.push(interval); 1940 } 1941 // tally the intervals and return interval counts 1942 var foundInterval = intervalCounts.some(function (intervalCount, p) { 1943 if (intervalCount.interval === interval) { 1944 intervalCount.count++; 1945 return intervalCount; 1946 } 1947 }); 1948 // store with JSON like formatting 1949 if (!foundInterval) { 1950 intervalCounts.push({ 1951 interval: interval, 1952 count: 1 1953 }); 1954 } 1955 } 1956 } 1957 } 1958 return intervalCounts; 1959 } 1960 // 3. for processPeaks --> find tempo 1961 function groupNeighborsByTempo(intervalCounts, sampleRate) { 1962 var tempoCounts = []; 1963 intervalCounts.forEach(function (intervalCount, i) { 1964 try { 1965 // Convert an interval to tempo 1966 var theoreticalTempo = Math.abs(60 / (intervalCount.interval / sampleRate)); 1967 theoreticalTempo = mapTempo(theoreticalTempo); 1968 var foundTempo = tempoCounts.some(function (tempoCount) { 1969 if (tempoCount.tempo === theoreticalTempo) 1970 return tempoCount.count += intervalCount.count; 1971 }); 1972 if (!foundTempo) { 1973 if (isNaN(theoreticalTempo)) { 1974 return; 1975 } 1976 tempoCounts.push({ 1977 tempo: Math.round(theoreticalTempo), 1978 count: intervalCount.count 1979 }); 1980 } 1981 } catch (e) { 1982 throw e; 1983 } 1984 }); 1985 return tempoCounts; 1986 } 1987 // 4. for processPeaks - get peaks at top tempo 1988 function getPeaksAtTopTempo(peaksObj, tempo, sampleRate, bpmVariance) { 1989 var peaksAtTopTempo = []; 1990 var peaksArray = Object.keys(peaksObj).sort(); 1991 // TO DO: filter out peaks that have the tempo and return 1992 for (var i = 0; i < peaksArray.length; i++) { 1993 var key = peaksArray[i]; 1994 var peak = peaksObj[key]; 1995 for (var j = 0; j < peak.intervals.length; j++) { 1996 var intervalBPM = Math.round(Math.abs(60 / (peak.intervals[j] / sampleRate))); 1997 intervalBPM = mapTempo(intervalBPM); 1998 var dif = intervalBPM - tempo; 1999 if (Math.abs(intervalBPM - tempo) < bpmVariance) { 2000 // convert sampleIndex to seconds 2001 peaksAtTopTempo.push(peak.sampleIndex / 44100); 2002 } 2003 } 2004 } 2005 // filter out peaks that are very close to each other 2006 peaksAtTopTempo = peaksAtTopTempo.filter(function (peakTime, index, arr) { 2007 var dif = arr[index + 1] - peakTime; 2008 if (dif > 0.01) { 2009 return true; 2010 } 2011 }); 2012 return peaksAtTopTempo; 2013 } 2014 // helper function for processPeaks 2015 function mapTempo(theoreticalTempo) { 2016 // these scenarios create infinite while loop 2017 if (!isFinite(theoreticalTempo) || theoreticalTempo == 0) { 2018 return; 2019 } 2020 // Adjust the tempo to fit within the 90-180 BPM range 2021 while (theoreticalTempo < 90) 2022 theoreticalTempo *= 2; 2023 while (theoreticalTempo > 180 && theoreticalTempo > 90) 2024 theoreticalTempo /= 2; 2025 return theoreticalTempo; 2026 } 2027 /*** SCHEDULE EVENTS ***/ 2028 /** 2029 * Schedule events to trigger every time a MediaElement 2030 * (audio/video) reaches a playback cue point. 2031 * 2032 * Accepts a callback function, a time (in seconds) at which to trigger 2033 * the callback, and an optional parameter for the callback. 2034 * 2035 * Time will be passed as the first parameter to the callback function, 2036 * and param will be the second parameter. 2037 * 2038 * 2039 * @method addCue 2040 * @param {Number} time Time in seconds, relative to this media 2041 * element's playback. For example, to trigger 2042 * an event every time playback reaches two 2043 * seconds, pass in the number 2. This will be 2044 * passed as the first parameter to 2045 * the callback function. 2046 * @param {Function} callback Name of a function that will be 2047 * called at the given time. The callback will 2048 * receive time and (optionally) param as its 2049 * two parameters. 2050 * @param {Object} [value] An object to be passed as the 2051 * second parameter to the 2052 * callback function. 2053 * @return {Number} id ID of this cue, 2054 * useful for removeCue(id) 2055 * @example 2056 * <div><code> 2057 * function setup() { 2058 * background(0); 2059 * noStroke(); 2060 * fill(255); 2061 * textAlign(CENTER); 2062 * text('click to play', width/2, height/2); 2063 * 2064 * mySound = loadSound('assets/beat.mp3'); 2065 * 2066 * // schedule calls to changeText 2067 * mySound.addCue(0.50, changeText, "hello" ); 2068 * mySound.addCue(1.00, changeText, "p5" ); 2069 * mySound.addCue(1.50, changeText, "what" ); 2070 * mySound.addCue(2.00, changeText, "do" ); 2071 * mySound.addCue(2.50, changeText, "you" ); 2072 * mySound.addCue(3.00, changeText, "want" ); 2073 * mySound.addCue(4.00, changeText, "to" ); 2074 * mySound.addCue(5.00, changeText, "make" );
2075 * mySound.addCue(6.00, changeText, "?" ); 2076 * } 2077 * 2078 * function changeText(val) { 2079 * background(0); 2080 * text(val, width/2, height/2); 2081 * } 2082 * 2083 * function mouseClicked() { 2084 * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { 2085 * if (mySound.isPlaying() ) { 2086 * mySound.stop(); 2087 * } else { 2088 * mySound.play(); 2089 * } 2090 * } 2091 * } 2092 * </code></div> 2093 */ 2094 p5.SoundFile.prototype.addCue = function (time, callback, val) { 2095 var id = this._cueIDCounter++; 2096 var cue = new Cue(callback, time, id, val); 2097 this._cues.push(cue); 2098 // if (!this.elt.ontimeupdate) { 2099 // this.elt.ontimeupdate = this._onTimeUpdate.bind(this); 2100 // } 2101 return id; 2102 }; 2103 /** 2104 * Remove a callback based on its ID. The ID is returned by the 2105 * addCue method. 2106 * 2107 * @method removeCue 2108 * @param {Number} id ID of the cue, as returned by addCue 2109 */ 2110 p5.SoundFile.prototype.removeCue = function (id) { 2111 var cueLength = this._cues.length; 2112 for (var i = 0; i < cueLength; i++) { 2113 var cue = this._cues[i]; 2114 if (cue.id === id) { 2115 this.cues.splice(i, 1); 2116 } 2117 } 2118 if (this._cues.length === 0) { 2119 } 2120 }; 2121 /** 2122 * Remove all of the callbacks that had originally been scheduled 2123 * via the addCue method. 2124 * 2125 * @method clearCues 2126 */ 2127 p5.SoundFile.prototype.clearCues = function () { 2128 this._cues = []; 2129 }; 2130 // private method that checks for cues to be fired if events 2131 // have been scheduled using addCue(callback, time).
2132 p5.SoundFile.prototype._onTimeUpdate = function (position) { 2133 var playbackTime = position / this.buffer.sampleRate; 2134 var cueLength = this._cues.length; 2135 for (var i = 0; i < cueLength; i++) { 2136 var cue = this._cues[i]; 2137 var callbackTime = cue.time; 2138 var val = cue.val; 2139 if (this._prevTime < callbackTime && callbackTime <= playbackTime) { 2140 // pass the scheduled callbackTime as parameter to the callback 2141 cue.callback(val); 2142 } 2143 } 2144 this._prevTime = playbackTime; 2145 }; 2146 // Cue inspired by JavaScript setTimeout, and the 2147 // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org 2148 var Cue = function (callback, time, id, val) { 2149 this.callback = callback; 2150 this.time = time; 2151 this.id = id; 2152 this.val = val; 2153 }; 2154}(sndcore, errorHandler, master); 2155var amplitude; 2156amplitude = function () { 2157 'use strict'; 2158 var p5sound = master; 2159 /** 2160 * Amplitude measures volume between 0.0 and 1.0. 2161 * Listens to all p5sound by default, or use setInput() 2162 * to listen to a specific sound source. Accepts an optional 2163 * smoothing value, which defaults to 0. 2164 * 2165 * @class p5.Amplitude 2166 * @constructor 2167 * @param {Number} [smoothing] between 0.0 and .999 to smooth 2168 * amplitude readings (defaults to 0) 2169 * @return {Object} Amplitude Object 2170 * @example 2171 * <div><code> 2172 * var sound, amplitude, cnv; 2173 * 2174 * function preload(){ 2175 * sound = loadSound('assets/beat.mp3'); 2176 * } 2177 * function setup() { 2178 * cnv = createCanvas(100,100); 2179 * amplitude = new p5.Amplitude(); 2180 * 2181 * // start / stop the sound when canvas is clicked 2182 * cnv.mouseClicked(function() { 2183 * if (sound.isPlaying() ){ 2184 * sound.stop(); 2185 * } else { 2186 * sound.play(); 2187 * } 2188 * }); 2189 * } 2190 * function draw() { 2191 * background(0); 2192 * fill(255); 2193 * var level = amplitude.getLevel(); 2194 * var size = map(level, 0, 1, 0, 200); 2195 * ellipse(width/2, height/2, size, size); 2196 * } 2197 * 2198 * </code></div> 2199 */ 2200 p5.Amplitude = function (smoothing) { 2201 // Set to 2048 for now. In future iterations, this should be inherited or parsed from p5sound's default 2202 this.bufferSize = 2048; 2203 // set audio context 2204 this.audiocontext = p5sound.audiocontext; 2205 this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1); 2206 // for connections 2207 this.input = this.processor; 2208 this.output = this.audiocontext.createGain(); 2209 // smoothing defaults to 0 2210 this.smoothing = smoothing || 0; 2211 // the variables to return 2212 this.volume = 0; 2213 this.average = 0; 2214 this.stereoVol = [ 2215 0, 2216 0 2217 ]; 2218 this.stereoAvg = [ 2219 0, 2220 0 2221 ]; 2222 this.stereoVolNorm = [ 2223 0, 2224 0 2225 ]; 2226 this.volMax = 0.001; 2227 this.normalize = false; 2228 this.processor.onaudioprocess = this._audioProcess.bind(this); 2229 this.processor.connect(this.output); 2230 this.output.gain.value = 0; 2231 // this may only be necessary because of a Chrome bug 2232 this.output.connect(this.audiocontext.destination); 2233 // connect to p5sound master output by default, unless set by input() 2234 p5sound.meter.connect(this.processor); 2235 // add this p5.SoundFile to the soundArray 2236 p5sound.soundArray.push(this); 2237 }; 2238 /** 2239 * Connects to the p5sound instance (master output) by default. 2240 * Optionally, you can pass in a specific source (i.e. a soundfile). 2241 * 2242 * @method setInput 2243 * @param {soundObject|undefined} [snd] set the sound source 2244 * (optional, defaults to 2245 * master output) 2246 * @param {Number|undefined} [smoothing] a range between 0.0 and 1.0 2247 * to smooth amplitude readings 2248 * @example 2249 * <div><code> 2250 * function preload(){ 2251 * sound1 = loadSound('assets/beat.mp3'); 2252 * sound2 = loadSound('assets/drum.mp3'); 2253 * } 2254 * function setup(){ 2255 * amplitude = new p5.Amplitude(); 2256 * sound1.play(); 2257 * sound2.play(); 2258 * amplitude.setInput(sound2); 2259 * } 2260 * function draw() { 2261 * background(0); 2262 * fill(255); 2263 * var level = amplitude.getLevel(); 2264 * var size = map(level, 0, 1, 0, 200); 2265 * ellipse(width/2, height/2, size, size); 2266 * } 2267 * function mouseClicked(){ 2268 * sound1.stop(); 2269 * sound2.stop(); 2270 * } 2271 * </code></div> 2272 */ 2273 p5.Amplitude.prototype.setInput = function (source, smoothing) { 2274 p5sound.meter.disconnect(); 2275 if (smoothing) { 2276 this.smoothing = smoothing; 2277 } 2278 // connect to the master out of p5s instance if no snd is provided 2279 if (source == null) { 2280 console.log('Amplitude input source is not ready! Connecting to master output instead'); 2281 p5sound.meter.connect(this.processor); 2282 } else if (source instanceof p5.Signal) { 2283 source.output.connect(this.processor); 2284 } else if (source) { 2285 source.connect(this.processor); 2286 this.processor.disconnect(); 2287 this.processor.connect(this.output); 2288 } else { 2289 p5sound.meter.connect(this.processor); 2290 } 2291 }; 2292 p5.Amplitude.prototype.connect = function (unit) { 2293 if (unit) { 2294 if (unit.hasOwnProperty('input')) {
2295 this.output.connect(unit.input); 2296 } else { 2297 this.output.connect(unit); 2298 } 2299 } else { 2300 this.output.connect(this.panner.connect(p5sound.input)); 2301 } 2302 }; 2303 p5.Amplitude.prototype.disconnect = function (unit) { 2304 this.output.disconnect(); 2305 }; 2306 // TO DO make this stereo / dependent on # of audio channels 2307 p5.Amplitude.prototype._audioProcess = function (event) { 2308 for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) { 2309 var inputBuffer = event.inputBuffer.getChannelData(channel); 2310 var bufLength = inputBuffer.length; 2311 var total = 0; 2312 var sum = 0; 2313 var x; 2314 for (var i = 0; i < bufLength; i++) { 2315 x = inputBuffer[i]; 2316 if (this.normalize) { 2317 total += Math.max(Math.min(x / this.volMax, 1), -1); 2318 sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1); 2319 } else { 2320 total += x; 2321 sum += x * x; 2322 } 2323 } 2324 var average = total / bufLength; 2325 // ... then take the square root of the sum. 2326 var rms = Math.sqrt(sum / bufLength); 2327 this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing); 2328 this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing); 2329 this.volMax = Math.max(this.stereoVol[channel], this.volMax); 2330 } 2331 // add volume from all channels together 2332 var self = this; 2333 var volSum = this.stereoVol.reduce(function (previousValue, currentValue, index) { 2334 self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1] / self.volMax, 1), 0); 2335 self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index] / self.volMax, 1), 0); 2336 return previousValue + currentValue; 2337 }); 2338 // volume is average of channels 2339 this.volume = volSum / this.stereoVol.length; 2340 // normalized value 2341 this.volNorm = Math.max(Math.min(this.volume / this.volMax, 1), 0); 2342 }; 2343 /** 2344 * Returns a single Amplitude reading at the moment it is called. 2345 * For continuous readings, run in the draw loop. 2346 * 2347 * @method getLevel 2348 * @param {Number} [channel] Optionally return only channel 0 (left) or 1 (right) 2349 * @return {Number} Amplitude as a number between 0.0 and 1.0 2350 * @example 2351 * <div><code> 2352 * function preload(){ 2353 * sound = loadSound('assets/beat.mp3'); 2354 * } 2355 * function setup() { 2356 * amplitude = new p5.Amplitude(); 2357 * sound.play(); 2358 * } 2359 * function draw() { 2360 * background(0); 2361 * fill(255); 2362 * var level = amplitude.getLevel(); 2363 * var size = map(level, 0, 1, 0, 200); 2364 * ellipse(width/2, height/2, size, size); 2365 * } 2366 * function mouseClicked(){ 2367 * sound.stop(); 2368 * } 2369 * </code></div> 2370 */ 2371 p5.Amplitude.prototype.getLevel = function (channel) { 2372 if (typeof channel !== 'undefined') { 2373 if (this.normalize) { 2374 return this.stereoVolNorm[channel]; 2375 } else { 2376 return this.stereoVol[channel]; 2377 } 2378 } else if (this.normalize) { 2379 return this.volNorm; 2380 } else { 2381 return this.volume; 2382 } 2383 }; 2384 /** 2385 * Determines whether the results of Amplitude.process() will be 2386 * Normalized. To normalize, Amplitude finds the difference the 2387 * loudest reading it has processed and the maximum amplitude of 2388 * 1.0. Amplitude adds this difference to all values to produce 2389 * results that will reliably map between 0.0 and 1.0. However, 2390 * if a louder moment occurs, the amount that Normalize adds to 2391 * all the values will change. Accepts an optional boolean parameter 2392 * (true or false). Normalizing is off by default. 2393 * 2394 * @method toggleNormalize 2395 * @param {boolean} [boolean] set normalize to true (1) or false (0) 2396 */ 2397 p5.Amplitude.prototype.toggleNormalize = function (bool) { 2398 if (typeof bool === 'boolean') { 2399 this.normalize = bool; 2400 } else { 2401 this.normalize = !this.normalize; 2402 } 2403 }; 2404 /** 2405 * Smooth Amplitude analysis by averaging with the last analysis 2406 * frame. Off by default. 2407 * 2408 * @method smooth 2409 * @param {Number} set smoothing from 0.0 <= 1 2410 */ 2411 p5.Amplitude.prototype.smooth = function (s) { 2412 if (s >= 0 && s < 1) { 2413 this.smoothing = s; 2414 } else { 2415 console.log('Error: smoothing must be between 0 and 1'); 2416 } 2417 }; 2418 p5.Amplitude.prototype.dispose = function () { 2419 // remove reference from soundArray 2420 var index = p5sound.soundArray.indexOf(this); 2421 p5sound.soundArray.splice(index, 1); 2422 this.input.disconnect(); 2423 this.output.disconnect(); 2424 this.input = this.processor = undefined; 2425 this.output = undefined; 2426 }; 2427}(master); 2428var fft; 2429fft = function () { 2430 'use strict'; 2431 var p5sound = master; 2432 /** 2433 * <p>
2433FFT (Fast Fourier Transform) is an analysis algorithm that 2434 * isolates individual 2435 * <a href="https://en.wikipedia.org/wiki/Audio_frequency"> 2436 * audio frequencies</a> within a waveform.</p> 2437 * 2438 * <p>Once instantiated, a p5.FFT object can return an array based on 2439 * two types of analyses: <br> • <code>FFT.waveform()</code> computes 2440 * amplitude values along the time domain. The array indices correspond 2441 * to samples across a brief moment in time. Each value represents 2442 * amplitude of the waveform at that sample of time.<br> 2443 * • <code>FFT.analyze() </code> computes amplitude values along the 2444 * frequency domain. The array indices correspond to frequencies (i.e. 2445 * pitches), from the lowest to the highest that humans can hear. Each 2446 * value represents amplitude at that slice of the frequency spectrum. 2447 * Use with <code>getEnergy()</code> to measure amplitude at specific 2448 * frequencies, or within a range of frequencies. </p> 2449 * 2450 * <p>FFT analyzes a very short snapshot of sound called a sample 2451 * buffer. It returns an array of amplitude measurements, referred 2452 * to as <code>bins</code>. The array is 1024 bins long by default. 2453 * You can change the bin array length, but it must be a power of 2 2454 * between 16 and 1024 in order for the FFT algorithm to function 2455 * correctly. The actual size of the FFT buffer is twice the 2456 * number of bins, so given a standard sample rate, the buffer is 2457 * 2048/44100 seconds long.</p> 2458 * 2459 * 2460 * @class p5.FFT 2461 * @constructor 2462 * @param {Number} [smoothing] Smooth results of Freq Spectrum. 2463 * 0.0 < smoothing < 1.0. 2464 * Defaults to 0.8. 2465 * @param {Number} [bins] Length of resulting array. 2466 * Must be a power of two between 2467 * 16 and 1024. Defaults to 1024. 2468 * @return {Object} FFT Object 2469 * @example 2470 * <div><code> 2471 * function preload(){ 2472 * sound = loadSound('assets/Damscray_DancingTiger.mp3'); 2473 * } 2474 * 2475 * function setup(){ 2476 * var cnv = createCanvas(100,100); 2477 * cnv.mouseClicked(togglePlay); 2478 * fft = new p5.FFT(); 2479 * sound.amp(0.2); 2480 * } 2481 * 2482 * function draw(){ 2483 * background(0); 2484 * 2485 * var spectrum = fft.analyze(); 2486 * noStroke(); 2487 * fill(0,255,0); // spectrum is green 2488 * for (var i = 0; i< spectrum.length; i++){ 2489 * var x = map(i, 0, spectrum.length, 0, width); 2490 * var h = -height + map(spectrum[i], 0, 255, height, 0); 2491 * rect(x, height, width / spectrum.length, h ) 2492 * } 2493 * 2494 * var waveform = fft.waveform(); 2495 * noFill(); 2496 * beginShape(); 2497 * stroke(255,0,0); // waveform is red 2498 * strokeWeight(1); 2499 * for (var i = 0; i< waveform.length; i++){ 2500 * var x = map(i, 0, waveform.length, 0, width); 2501 * var y = map( waveform[i], -1, 1, 0, height); 2502 * vertex(x,y); 2503 * } 2504 * endShape(); 2505 * 2506 * text('click to play/pause', 4, 10); 2507 * } 2508 * 2509 * // fade sound if mouse is over canvas 2510 * function togglePlay() { 2511 * if (sound.isPlaying()) { 2512 * sound.pause(); 2513 * } else { 2514 * sound.loop(); 2515 * } 2516 * } 2517 * </code></div> 2518 */ 2519 p5.FFT = function (smoothing, bins) { 2520 this.smoothing = smoothing || 0.8; 2521 this.bins = bins || 1024; 2522 var FFT_SIZE = bins * 2 || 2048; 2523 this.input = this.analyser = p5sound.audiocontext.createAnalyser(); 2524 // default connections to p5sound fftMeter 2525 p5sound.fftMeter.connect(this.analyser); 2526 this.analyser.smoothingTimeConstant = this.smoothing; 2527 this.analyser.fftSize = FFT_SIZE; 2528 this.freqDomain = new Uint8Array(this.analyser.frequencyBinCount); 2529 this.timeDomain = new Uint8Array(this.analyser.frequencyBinCount); 2530 // predefined frequency ranages, these will be tweakable 2531 this.bass = [ 2532 20, 2533 140 2534 ]; 2535 this.lowMid = [ 2536 140, 2537 400 2538 ]; 2539 this.mid = [ 2540 400, 2541 2600 2542 ]; 2543 this.highMid = [ 2544 2600, 2545 5200 2546 ]; 2547 this.treble = [ 2548 5200, 2549 14000 2550 ]; 2551 // add this p5.SoundFile to the soundArray 2552 p5sound.soundArray.push(this); 2553 }; 2554 /** 2555 * Set the input source for the FFT analysis. If no source is 2556 * provided, FFT will analyze all sound in the sketch. 2557 * 2558 * @method setInput 2559 * @param {Object} [source] p5.sound object (or web audio API source node) 2560 */ 2561 p5.FFT.prototype.setInput = function (source) { 2562 if (!source) { 2563 p5sound.fftMeter.connect(this.analyser); 2564 } else { 2565 if (source.output) { 2566 source.output.connect(this.analyser); 2567 } else if (source.connect) { 2568 source.connect(this.analyser); 2569 } 2570 p5sound.fftMeter.disconnect(); 2571 } 2572 }; 2573 /** 2574 * Returns an array of amplitude values (between -1.0 and +1.0) that represent 2575 * a snapshot of amplitude readings in a single buffer. Length will be 2576 * equal to bins (defaults to 1024). Can be used to draw the waveform 2577 * of a sound. 2578 * 2579 * @method waveform 2580 * @param {Number} [bins] Must be a power of two between 2581 * 16 and 1024. Defaults to 1024. 2582 * @param {String} [precision] If any value is provided, will return results 2583 * in a Float32 Array which is more precise 2584 * than a regular array. 2585 * @return {Array} Array Array of amplitude values (-1 to 1) 2586 * over time. Array length = bins. 2587 * 2588 */ 2589 p5.FFT.prototype.waveform = function () { 2590 var bins, mode, normalArray; 2591 for (var i = 0; i < arguments.length; i++) { 2592 if (typeof arguments[i] === 'number') { 2593 bins = arguments[i]; 2594 this.analyser.fftSize = bins * 2; 2595 } 2596 if (typeof arguments[i] === 'string') { 2597 mode = arguments[i]; 2598 } 2599 } 2600 // getFloatFrequencyData doesnt work in Safari as of 5/2015 2601 if (mode && !p5.prototype._isSafari()) { 2602 timeToFloat(this, this.timeDomain); 2603 this.analyser.getFloatTimeDomainData(this.timeDomain); 2604 return this.timeDomain; 2605 } else { 2606 timeToInt(this, this.timeDomain); 2607 this.analyser.getByteTimeDomainData(this.timeDomain); 2608 var normalArray = new Array(); 2609 for (var i = 0; i < this.timeDomain.length; i++) { 2610 var scaled = p5.prototype.map(this.timeDomain[i], 0, 255, -1, 1); 2611 normalArray.push(scaled); 2612 } 2613 return normalArray; 2614 } 2615 }; 2616 /** 2617 * Returns an array of amplitude values (between 0 and 255) 2618 * across the frequency spectrum. Length is equal to FFT bins 2619 * (1024 by default). The array indices correspond to frequencies 2620 * (i.e. pitches), from the lowest to the highest that humans can 2621 * hear. Each value represents amplitude at that slice of the 2622 * frequency spectrum. Must be called prior to using 2623 * <code>getEnergy()</code>. 2624 *
2625 * @method analyze 2626 * @param {Number} [bins] Must be a power of two between 2627 * 16 and 1024. Defaults to 1024. 2628 * @param {Number} [scale] If "dB," returns decibel 2629 * float measurements between 2630 * -140 and 0 (max). 2631 * Otherwise returns integers from 0-255. 2632 * @return {Array} spectrum Array of energy (amplitude/volume) 2633 * values across the frequency spectrum. 2634 * Lowest energy (silence) = 0, highest 2635 * possible is 255. 2636 * @example 2637 * <div><code> 2638 * var osc; 2639 * var fft; 2640 * 2641 * function setup(){ 2642 * createCanvas(100,100); 2643 * osc = new p5.Oscillator(); 2644 * osc.amp(0); 2645 * osc.start(); 2646 * fft = new p5.FFT(); 2647 * } 2648 * 2649 * function draw(){ 2650 * background(0); 2651 * 2652 * var freq = map(mouseX, 0, 800, 20, 15000); 2653 * freq = constrain(freq, 1, 20000); 2654 * osc.freq(freq); 2655 * 2656 * var spectrum = fft.analyze(); 2657 * noStroke(); 2658 * fill(0,255,0); // spectrum is green 2659 * for (var i = 0; i< spectrum.length; i++){ 2660 * var x = map(i, 0, spectrum.length, 0, width); 2661 * var h = -height + map(spectrum[i], 0, 255, height, 0); 2662 * rect(x, height, width / spectrum.length, h ); 2663 * } 2664 * 2665 * stroke(255); 2666 * text('Freq: ' + round(freq)+'Hz', 10, 10); 2667 * 2668 * isMouseOverCanvas(); 2669 * } 2670 * 2671 * // only play sound when mouse is over canvas 2672 * function isMouseOverCanvas() { 2673 * var mX = mouseX, mY = mouseY; 2674 * if (mX > 0 && mX < width && mY < height && mY > 0) { 2675 * osc.amp(0.5, 0.2); 2676 * } else { 2677 * osc.amp(0, 0.2); 2678 * } 2679 * } 2680 * </code></div> 2681 * 2682 * 2683 */ 2684 p5.FFT.prototype.analyze = function () { 2685 var bins, mode; 2686 for (var i = 0; i < arguments.length; i++) { 2687 if (typeof arguments[i] === 'number') { 2688 bins = this.bins = arguments[i]; 2689 this.analyser.fftSize = this.bins * 2; 2690 } 2691 if (typeof arguments[i] === 'string') { 2692 mode = arguments[i]; 2693 } 2694 } 2695 if (mode && mode.toLowerCase() === 'db') { 2696 freqToFloat(this); 2697 this.analyser.getFloatFrequencyData(this.freqDomain); 2698 return this.freqDomain; 2699 } else { 2700 freqToInt(this, this.freqDomain); 2701 this.analyser.getByteFrequencyData(this.freqDomain); 2702 var normalArray = Array.apply([], this.freqDomain); 2703 normalArray.length === this.analyser.fftSize; 2704 normalArray.constructor === Array; 2705 return normalArray; 2706 } 2707 }; 2708 /** 2709 * Returns the amount of energy (volume) at a specific 2710 * <a href="en.wikipedia.org/wiki/Audio_frequency" target="_blank"> 2711 * frequency</a>, or the average amount of energy between two 2712 * frequencies. Accepts Number(s) corresponding 2713 * to frequency (in Hz), or a String corresponding to predefined 2714 * frequency ranges ("bass", "lowMid", "mid", "highMid", "treble"). 2715 * Returns a range between 0 (no energy/volume at that frequency) and 2716 * 255 (maximum energy). 2717 * <em>NOTE: analyze() must be called prior to getEnergy(). Analyze() 2718 * tells the FFT to analyze frequency data, and getEnergy() uses 2719 * the results determine the value at a specific frequency or 2720 * range of frequencies.</em></p> 2721 * 2722 * @method getEnergy 2723 * @param {Number|String} frequency1 Will return a value representing 2724 * energy at this frequency. Alternately, 2725 * the strings "bass", "lowMid" "mid", 2726 * "highMid", and "treble" will return 2727 * predefined frequency ranges. 2728 * @param {Number} [frequency2] If a second frequency is given, 2729 * will return average amount of 2730 * energy that exists between the 2731 * two frequencies. 2732 * @return {Number} Energy Energy (volume/amplitude) from 2733 * 0 and 255. 2734 * 2735 */ 2736 p5.FFT.prototype.getEnergy = function (frequency1, frequency2) { 2737 var nyquist = p5sound.audiocontext.sampleRate / 2; 2738 if (frequency1 === 'bass') { 2739 frequency1 = this.bass[0]; 2740 frequency2 = this.bass[1]; 2741 } else if (frequency1 === 'lowMid') { 2742 frequency1 = this.lowMid[0]; 2743 frequency2 = this.lowMid[1]; 2744 } else if (frequency1 === 'mid') { 2745 frequency1 = this.mid[0]; 2746 frequency2 = this.mid[1]; 2747 } else if (frequency1 === 'highMid') { 2748 frequency1 = this.highMid[0]; 2749 frequency2 = this.highMid[1]; 2750 } else if (frequency1 === 'treble') { 2751 frequency1 = this.treble[0]; 2752 frequency2 = this.treble[1]; 2753 } 2754 if (typeof frequency1 !== 'number') {
2755 throw 'invalid input for getEnergy()'; 2756 } else if (!frequency2) { 2757 var index = Math.round(frequency1 / nyquist * this.freqDomain.length); 2758 return this.freqDomain[index]; 2759 } else if (frequency1 && frequency2) { 2760 // if second is higher than first 2761 if (frequency1 > frequency2) { 2762 var swap = frequency2; 2763 frequency2 = frequency1; 2764 frequency1 = swap; 2765 } 2766 var lowIndex = Math.round(frequency1 / nyquist * this.freqDomain.length); 2767 var highIndex = Math.round(frequency2 / nyquist * this.freqDomain.length); 2768 var total = 0; 2769 var numFrequencies = 0; 2770 // add up all of the values for the frequencies 2771 for (var i = lowIndex; i <= highIndex; i++) { 2772 total += this.freqDomain[i]; 2773 numFrequencies += 1; 2774 } 2775 // divide by total number of frequencies 2776 var toReturn = total / numFrequencies; 2777 return toReturn; 2778 } else { 2779 throw 'invalid input for getEnergy()'; 2780 } 2781 }; 2782 // compatability with v.012, changed to getEnergy in v.0121. Will be deprecated... 2783 p5.FFT.prototype.getFreq = function (freq1, freq2) { 2784 console.log('getFreq() is deprecated. Please use getEnergy() instead.'); 2785 var x = this.getEnergy(freq1, freq2); 2786 return x; 2787 }; 2788 /** 2789 * Returns the 2790 * <a href="http://en.wikipedia.org/wiki/Spectral_centroid" target="_blank"> 2791 * spectral centroid</a> of the input signal. 2792 * <em>NOTE: analyze() must be called prior to getCentroid(). Analyze() 2793 * tells the FFT to analyze frequency data, and getCentroid() uses 2794 * the results determine the spectral centroid.</em></p> 2795 * 2796 * @method getCentroid 2797 * @return {Number} Spectral Centroid Frequency Frequency of the spectral centroid in Hz. 2798 * 2799 * 2800 * @example 2801 * <div><code> 2802 * 2803 * 2804 *function setup(){ 2805 * cnv = createCanvas(800,400); 2806 * sound = new p5.AudioIn(); 2807 * sound.start(); 2808 * fft = new p5.FFT(); 2809 * sound.connect(fft); 2810 *} 2811 * 2812 * 2813 *function draw(){ 2814 * 2815 * var centroidplot = 0.0; 2816 * var spectralCentroid = 0; 2817 * 2818 * 2819 * background(0); 2820 * stroke(0,255,0); 2821 * var spectrum = fft.analyze(); 2822 * fill(0,255,0); // spectrum is green 2823 * 2824 * //draw the spectrum 2825 * 2826 * for (var i = 0; i< spectrum.length; i++){ 2827 * var x = map(log(i), 0, log(spectrum.length), 0, width); 2828 * var h = map(spectrum[i], 0, 255, 0, height); 2829 * var rectangle_width = (log(i+1)-log(i))*(width/log(spectrum.length)); 2830 * rect(x, height, rectangle_width, -h ) 2831 * } 2832 2833 * var nyquist = 22050; 2834 * 2835 * // get the centroid 2836 * spectralCentroid = fft.getCentroid(); 2837 * 2838 * // the mean_freq_index calculation is for the display. 2839 * var mean_freq_index = spectralCentroid/(nyquist/spectrum.length); 2840 * 2841 * centroidplot = map(log(mean_freq_index), 0, log(spectrum.length), 0, width); 2842 * 2843 * 2844 * stroke(255,0,0); // the line showing where the centroid is will be red 2845 * 2846 * rect(centroidplot, 0, width / spectrum.length, height) 2847 * noStroke(); 2848 * fill(255,255,255); // text is white 2849 * textSize(40); 2850 * text("centroid: "+round(spectralCentroid)+" Hz", 10, 40); 2851 *} 2852 * </code></div> 2853 */ 2854 p5.FFT.prototype.getCentroid = function () { 2855 var nyquist = p5sound.audiocontext.sampleRate / 2; 2856 var cumulative_sum = 0; 2857 var centroid_normalization = 0; 2858 for (var i = 0; i < this.freqDomain.length; i++) { 2859 cumulative_sum += i * this.freqDomain[i]; 2860 centroid_normalization += this.freqDomain[i]; 2861 } 2862 var mean_freq_index = 0; 2863 if (centroid_normalization != 0) { 2864 mean_freq_index = cumulative_sum / centroid_normalization; 2865 } 2866 var spec_centroid_freq = mean_freq_index * (nyquist / this.freqDomain.length); 2867 return spec_centroid_freq; 2868 }; 2869 /** 2870 * Smooth FFT analysis by averaging with the last analysis frame. 2871 * 2872 * @method smooth 2873 * @param {Number} smoothing 0.0 < smoothing < 1.0. 2874 * Defaults to 0.8. 2875 */ 2876 p5.FFT.prototype.smooth = function (s) { 2877 if (s) { 2878 this.smoothing = s; 2879 } 2880 this.analyser.smoothingTimeConstant = s; 2881 }; 2882 p5.FFT.prototype.dispose = function () { 2883 // remove reference from soundArray 2884 var index = p5sound.soundArray.indexOf(this); 2885 p5sound.soundArray.splice(index, 1); 2886 this.analyser.disconnect(); 2887 this.analyser = undefined; 2888 }; 2889 // helper methods to convert type from float (dB) to int (0-255) 2890 var freqToFloat = function (fft) { 2891 if (fft.freqDomain instanceof Float32Array === false) { 2892 fft.freqDomain = new Float32Array(fft.analyser.frequencyBinCount); 2893 } 2894 }; 2895 var freqToInt = function (fft) { 2896 if (fft.freqDomain instanceof Uint8Array === false) { 2897 fft.freqDomain = new Uint8Array(fft.analyser.frequencyBinCount); 2898 } 2899 }; 2900 var timeToFloat = function (fft) { 2901 if (fft.timeDomain instanceof Float32Array === false) { 2902 fft.timeDomain = new Float32Array(fft.analyser.frequencyBinCount); 2903 } 2904 }; 2905 var timeToInt = function (fft) { 2906 if (fft.timeDomain instanceof Uint8Array === false) { 2907 fft.timeDomain = new Uint8Array(fft.analyser.frequencyBinCount); 2908 } 2909 }; 2910}(master); 2911/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 2912var Tone_core_Tone; 2913Tone_core_Tone = function () { 2914 'use strict'; 2915 function isUndef(val) { 2916 return val === void 0; 2917 } 2918 function isFunction(val) { 2919 return typeof val === 'function'; 2920 } 2921 var audioContext; 2922 if (isUndef(window.AudioContext)) { 2923 window.AudioContext = window.webkitAudioContext; 2924 } 2925 if (isUndef(window.OfflineAudioContext)) { 2926 window.OfflineAudioContext = window.webkitOfflineAudioContext; 2927 } 2928 if (!isUndef(AudioContext)) { 2929 audioContext = new AudioContext(); 2930 } else { 2931 throw new Error('Web Audio is not supported in this browser'); 2932 } 2933 if (!isFunction(AudioContext.prototype.createGain)) { 2934 AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; 2935 } 2936 if (!isFunction(AudioContext.prototype.createDelay)) { 2937 AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; 2938 }
2939 if (!isFunction(AudioContext.prototype.createPeriodicWave)) { 2940 AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; 2941 } 2942 if (!isFunction(AudioBufferSourceNode.prototype.start)) { 2943 AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn; 2944 } 2945 if (!isFunction(AudioBufferSourceNode.prototype.stop)) { 2946 AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff; 2947 } 2948 if (!isFunction(OscillatorNode.prototype.start)) { 2949 OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn; 2950 } 2951 if (!isFunction(OscillatorNode.prototype.stop)) { 2952 OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff; 2953 } 2954 if (!isFunction(OscillatorNode.prototype.setPeriodicWave)) { 2955 OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; 2956 } 2957 AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; 2958 AudioNode.prototype.connect = function (B, outNum, inNum) { 2959 if (B.input) { 2960 if (Array.isArray(B.input)) { 2961 if (isUndef(inNum)) { 2962 inNum = 0; 2963 } 2964 this.connect(B.input[inNum]); 2965 } else { 2966 this.connect(B.input, outNum, inNum); 2967 } 2968 } else { 2969 try { 2970 if (B instanceof AudioNode) { 2971 this._nativeConnect(B, outNum, inNum); 2972 } else { 2973 this._nativeConnect(B, outNum); 2974 } 2975 } catch (e) { 2976 throw new Error('error connecting to node: ' + B); 2977 } 2978 } 2979 }; 2980 var Tone = function (inputs, outputs) { 2981 if (isUndef(inputs) || inputs === 1) { 2982 this.input = this.context.createGain(); 2983 } else if (inputs > 1) { 2984 this.input = new Array(inputs); 2985 } 2986 if (isUndef(outputs) || outputs === 1) { 2987 this.output = this.context.createGain(); 2988 } else if (outputs > 1) { 2989 this.output = new Array(inputs); 2990 } 2991 }; 2992 Tone.prototype.set = function (params, value, rampTime) { 2993 if (this.isObject(params)) { 2994 rampTime = value; 2995 } else if (this.isString(params)) { 2996 var tmpObj = {}; 2997 tmpObj[params] = value; 2998 params = tmpObj; 2999 } 3000 for (var attr in params) { 3001 value = params[attr]; 3002 var parent = this; 3003 if (attr.indexOf('.') !== -1) { 3004 var attrSplit = attr.split('.'); 3005 for (var i = 0; i < attrSplit.length - 1; i++) { 3006 parent = parent[attrSplit[i]]; 3007 } 3008 attr = attrSplit[attrSplit.length - 1]; 3009 } 3010 var param = parent[attr]; 3011 if (isUndef(param)) { 3012 continue; 3013 } 3014 if (Tone.Signal && param instanceof Tone.Signal || Tone.Param && param instanceof Tone.Param) { 3015 if (param.value !== value) { 3016 if (isUndef(rampTime)) { 3017 param.value = value; 3018 } else { 3019 param.rampTo(value, rampTime); 3020 } 3021 } 3022 } else if (param instanceof AudioParam) { 3023 if (param.value !== value) { 3024 param.value = value; 3025 } 3026 } else if (param instanceof Tone) { 3027 param.set(value); 3028 } else if (param !== value) { 3029 parent[attr] = value; 3030 } 3031 } 3032 return this; 3033 }; 3034 Tone.prototype.get = function (params) { 3035 if (isUndef(params)) { 3036 params = this._collectDefaults(this.constructor); 3037 } else if (this.isString(params)) { 3038 params = [params]; 3039 } 3040 var ret = {}; 3041 for (var i = 0; i < params.length; i++) { 3042 var attr = params[i]; 3043 var parent = this; 3044 var subRet = ret; 3045 if (attr.indexOf('.') !== -1) { 3046 var attrSplit = attr.split('.'); 3047 for (var j = 0; j < attrSplit.length - 1; j++) { 3048 var subAttr = attrSplit[j]; 3049 subRet[subAttr] = subRet[subAttr] || {}; 3050 subRet = subRet[subAttr]; 3051 parent = parent[subAttr]; 3052 } 3053 attr = attrSplit[attrSplit.length - 1]; 3054 } 3055 var param = parent[attr]; 3056 if (this.isObject(params[attr])) { 3057 subRet[attr] = param.get(); 3058 } else if (Tone.Signal && param instanceof Tone.Signal) { 3059 subRet[attr] = param.value; 3060 } else if (Tone.Param && param instanceof Tone.Param) { 3061 subRet[attr] = param.value; 3062 } else if (param instanceof AudioParam) { 3063 subRet[attr] = param.value; 3064 } else if (param instanceof Tone) { 3065 subRet[attr] = param.get(); 3066 } else if (!isFunction(param) && !isUndef(param)) { 3067 subRet[attr] = param; 3068 } 3069 } 3070 return ret; 3071 }; 3072 Tone.prototype._collectDefaults = function (constr) { 3073 var ret = []; 3074 if (!isUndef(constr.defaults)) { 3075 ret = Object.keys(constr.defaults); 3076 } 3077 if (!isUndef(constr._super)) { 3078 var superDefs = this._collectDefaults(constr._super); 3079 for (var i = 0; i < superDefs.length; i++) { 3080 if (ret.indexOf(superDefs[i]) === -1) {
3081 ret.push(superDefs[i]); 3082 } 3083 } 3084 } 3085 return ret; 3086 }; 3087 Tone.prototype.toString = function () { 3088 for (var className in Tone) { 3089 var isLetter = className[0].match(/^[A-Z]$/); 3090 var sameConstructor = Tone[className] === this.constructor; 3091 if (isFunction(Tone[className]) && isLetter && sameConstructor) { 3092 return className; 3093 } 3094 } 3095 return 'Tone'; 3096 }; 3097 Tone.context = audioContext; 3098 Tone.prototype.context = Tone.context; 3099 Tone.prototype.bufferSize = 2048; 3100 Tone.prototype.blockTime = 128 / Tone.context.sampleRate; 3101 Tone.prototype.dispose = function () { 3102 if (!this.isUndef(this.input)) { 3103 if (this.input instanceof AudioNode) { 3104 this.input.disconnect(); 3105 } 3106 this.input = null; 3107 } 3108 if (!this.isUndef(this.output)) { 3109 if (this.output instanceof AudioNode) { 3110 this.output.disconnect(); 3111 } 3112 this.output = null; 3113 } 3114 return this; 3115 }; 3116 var _silentNode = null; 3117 Tone.prototype.noGC = function () { 3118 this.output.connect(_silentNode); 3119 return this; 3120 }; 3121 AudioNode.prototype.noGC = function () { 3122 this.connect(_silentNode); 3123 return this; 3124 }; 3125 Tone.prototype.connect = function (unit, outputNum, inputNum) { 3126 if (Array.isArray(this.output)) { 3127 outputNum = this.defaultArg(outputNum, 0); 3128 this.output[outputNum].connect(unit, 0, inputNum); 3129 } else { 3130 this.output.connect(unit, outputNum, inputNum); 3131 } 3132 return this; 3133 }; 3134 Tone.prototype.disconnect = function (outputNum) { 3135 if (Array.isArray(this.output)) { 3136 outputNum = this.defaultArg(outputNum, 0); 3137 this.output[outputNum].disconnect(); 3138 } else { 3139 this.output.disconnect(); 3140 } 3141 return this; 3142 }; 3143 Tone.prototype.connectSeries = function () { 3144 if (arguments.length > 1) { 3145 var currentUnit = arguments[0]; 3146 for (var i = 1; i < arguments.length; i++) { 3147 var toUnit = arguments[i]; 3148 currentUnit.connect(toUnit); 3149 currentUnit = toUnit; 3150 } 3151 } 3152 return this; 3153 }; 3154 Tone.prototype.connectParallel = function () { 3155 var connectFrom = arguments[0]; 3156 if (arguments.length > 1) { 3157 for (var i = 1; i < arguments.length; i++) { 3158 var connectTo = arguments[i]; 3159 connectFrom.connect(connectTo); 3160 } 3161 } 3162 return this; 3163 }; 3164 Tone.prototype.chain = function () { 3165 if (arguments.length > 0) { 3166 var currentUnit = this; 3167 for (var i = 0; i < arguments.length; i++) { 3168 var toUnit = arguments[i]; 3169 currentUnit.connect(toUnit); 3170 currentUnit = toUnit; 3171 } 3172 } 3173 return this; 3174 }; 3175 Tone.prototype.fan = function () { 3176 if (arguments.length > 0) { 3177 for (var i = 0; i < arguments.length; i++) { 3178 this.connect(arguments[i]); 3179 } 3180 } 3181 return this; 3182 }; 3183 AudioNode.prototype.chain = Tone.prototype.chain; 3184 AudioNode.prototype.fan = Tone.prototype.fan; 3185 Tone.prototype.defaultArg = function (given, fallback) { 3186 if (this.isObject(given) && this.isObject(fallback)) { 3187 var ret = {}; 3188 for (var givenProp in given) { 3189 ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); 3190 } 3191 for (var fallbackProp in fallback) { 3192 ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); 3193 } 3194 return ret; 3195 } else { 3196 return isUndef(given) ? fallback : given; 3197 } 3198 }; 3199 Tone.prototype.optionsObject = function (values, keys, defaults) { 3200 var options = {}; 3201 if (values.length === 1 && this.isObject(values[0])) { 3202 options = values[0]; 3203 } else { 3204 for (var i = 0; i < keys.length; i++) { 3205 options[keys[i]] = values[i]; 3206 } 3207 } 3208 if (!this.isUndef(defaults)) { 3209 return this.defaultArg(options, defaults); 3210 } else { 3211 return options; 3212 } 3213 }; 3214 Tone.prototype.isUndef = isUndef; 3215 Tone.prototype.isFunction = isFunction; 3216 Tone.prototype.isNumber = function (arg) { 3217 return typeof arg === 'number'; 3218 }; 3219 Tone.prototype.isObject = function (arg) { 3220 return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object; 3221 }; 3222 Tone.prototype.isBoolean = function (arg) { 3223 return typeof arg === 'boolean'; 3224 }; 3225 Tone.prototype.isArray = function (arg) { 3226 return Array.isArray(arg); 3227 }; 3228 Tone.prototype.isString = function (arg) { 3229 return typeof arg === 'string'; 3230 }; 3231 Tone.noOp = function () { 3232 }; 3233 Tone.prototype._readOnly = function (property) { 3234 if (Array.isArray(property)) { 3235 for (var i = 0; i < property.length; i++) { 3236 this._readOnly(property[i]); 3237 } 3238 } else { 3239 Object.defineProperty(this, property, { 3240 writable: false, 3241 enumerable: true 3242 }); 3243 } 3244 }; 3245 Tone.prototype._writable = function (property) { 3246 if (Array.isArray(property)) { 3247 for (var i = 0; i < property.length; i++) { 3248 this._writable(property[i]); 3249 } 3250 } else { 3251 Object.defineProperty(this, property, { writable: true }); 3252 } 3253 }; 3254 Tone.State = { 3255 Started: 'started', 3256 Stopped: 'stopped', 3257 Paused: 'paused' 3258 }; 3259 Tone.prototype.equalPowerScale = function (percent) { 3260 var piFactor = 0.5 * Math.PI; 3261 return Math.sin(percent * piFactor); 3262 }; 3263 Tone.prototype.dbToGain = function (db) { 3264 return Math.pow(2, db / 6); 3265 }; 3266 Tone.prototype.gainToDb = function (gain) { 3267 return 20 * (Math.log(gain) / Math.LN10); 3268 }; 3269 Tone.prototype.now = function () { 3270 return this.context.currentTime; 3271 }; 3272 Tone.extend = function (child, parent) { 3273 if (isUndef(parent)) { 3274 parent = Tone; 3275 } 3276 function TempConstructor() { 3277 } 3278 TempConstructor.prototype = parent.prototype; 3279 child.prototype = new TempConstructor(); 3280 child.prototype.constructor = child; 3281 child._super = parent; 3282 }; 3283 var newContextCallbacks = []; 3284 Tone._initAudioContext = function (callback) { 3285 callback(Tone.context); 3286 newContextCallbacks.push(callback); 3287 }; 3288 Tone.setContext = function (ctx) { 3289 Tone.prototype.context = ctx; 3290 Tone.context = ctx; 3291 for (var i = 0; i < newContextCallbacks.length; i++) { 3292 newContextCallbacks[i](ctx); 3293 } 3294 }; 3295 Tone.startMobile = function () { 3296 var osc = Tone.context.createOscillator(); 3297 var silent = Tone.context.createGain(); 3298 silent.gain.value = 0; 3299 osc.connect(silent); 3300 silent.connect(Tone.context.destination); 3301 var now = Tone.context.currentTime; 3302 osc.start(now); 3303 osc.stop(now + 1); 3304 }; 3305 Tone._initAudioContext(function (audioContext) { 3306 Tone.prototype.blockTime = 128 / audioContext.sampleRate; 3307 _silentNode = audioContext.createGain(); 3308 _silentNode.gain.value = 0; 3309 _silentNode.connect(audioContext.destination); 3310 }); 3311 Tone.version = 'r7-dev'; 3312 return Tone; 3313}(); 3314/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 3315var Tone_signal_SignalBase; 3316Tone_signal_SignalBase = function (Tone) { 3317 'use strict'; 3318 Tone.SignalBase = function () { 3319 }; 3320 Tone.extend(Tone.SignalBase); 3321 Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) { 3322 if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor || Tone.TimelineSignal && Tone.TimelineSignal === node.constructor) { 3323 node._param.cancelScheduledValues(0); 3324 node._param.value = 0; 3325 node.overridden = true; 3326 } else if (node instanceof AudioParam) { 3327 node.cancelScheduledValues(0); 3328 node.value = 0; 3329 } 3330 Tone.prototype.connect.call(this, node, outputNumber, inputNumber); 3331 return this; 3332 }; 3333 return Tone.SignalBase; 3334}(Tone_core_Tone); 3335/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 3336var Tone_signal_WaveShaper; 3337Tone_signal_WaveShaper = function (Tone) { 3338 'use strict'; 3339 Tone.WaveShaper = function (mapping, bufferLen) { 3340 this._shaper = this.input = this.output = this.context.createWaveShaper(); 3341 this._curve = null; 3342 if (Array.isArray(mapping)) { 3343 this.curve = mapping; 3344 } else if (isFinite(mapping) || this.isUndef(mapping)) { 3345 this._curve = new Float32Array(this.defaultArg(mapping, 1024)); 3346 } else if (this.isFunction(mapping)) { 3347 this._curve = new Float32Array(this.defaultArg(bufferLen, 1024));
3348 this.setMap(mapping); 3349 } 3350 }; 3351 Tone.extend(Tone.WaveShaper, Tone.SignalBase); 3352 Tone.WaveShaper.prototype.setMap = function (mapping) { 3353 for (var i = 0, len = this._curve.length; i < len; i++) { 3354 var normalized = i / len * 2 - 1; 3355 this._curve[i] = mapping(normalized, i); 3356 } 3357 this._shaper.curve = this._curve; 3358 return this; 3359 }; 3360 Object.defineProperty(Tone.WaveShaper.prototype, 'curve', { 3361 get: function () { 3362 return this._shaper.curve; 3363 }, 3364 set: function (mapping) { 3365 this._curve = new Float32Array(mapping); 3366 this._shaper.curve = this._curve; 3367 } 3368 }); 3369 Object.defineProperty(Tone.WaveShaper.prototype, 'oversample', { 3370 get: function () { 3371 return this._shaper.oversample; 3372 }, 3373 set: function (oversampling) { 3374 if ([ 3375 'none', 3376 '2x', 3377 '4x' 3378 ].indexOf(oversampling) !== -1) { 3379 this._shaper.oversample = oversampling; 3380 } else { 3381 throw new Error('invalid oversampling: ' + oversampling); 3382 } 3383 } 3384 }); 3385 Tone.WaveShaper.prototype.dispose = function () { 3386 Tone.prototype.dispose.call(this); 3387 this._shaper.disconnect(); 3388 this._shaper = null; 3389 this._curve = null; 3390 return this; 3391 }; 3392 return Tone.WaveShaper; 3393}(Tone_core_Tone); 3394/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 3395var Tone_core_Type; 3396Tone_core_Type = function (Tone) { 3397 'use strict'; 3398 Tone.Type = { 3399 Default: 'number', 3400 Time: 'time', 3401 Frequency: 'frequency', 3402 NormalRange: 'normalRange', 3403 AudioRange: 'audioRange', 3404 Decibels: 'db', 3405 Interval: 'interval', 3406 BPM: 'bpm', 3407 Positive: 'positive', 3408 Cents: 'cents', 3409 Degrees: 'degrees', 3410 MIDI: 'midi', 3411 TransportTime: 'transportTime', 3412 Ticks: 'tick', 3413 Note: 'note', 3414 Milliseconds: 'milliseconds', 3415 Notation: 'notation' 3416 }; 3417 Tone.prototype.isNowRelative = function () { 3418 var nowRelative = new RegExp(/^\s*\+(.)+/i); 3419 return function (note) { 3420 return nowRelative.test(note); 3421 }; 3422 }(); 3423 Tone.prototype.isTicks = function () { 3424 var tickFormat = new RegExp(/^\d+i$/i); 3425 return function (note) { 3426 return tickFormat.test(note); 3427 }; 3428 }(); 3429 Tone.prototype.isNotation = function () { 3430 var notationFormat = new RegExp(/^[0-9]+[mnt]$/i); 3431 return function (note) { 3432 return notationFormat.test(note); 3433 }; 3434 }(); 3435 Tone.prototype.isTransportTime = function () { 3436 var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i); 3437 return function (transportTime) { 3438 return transportTimeFormat.test(transportTime); 3439 }; 3440 }(); 3441 Tone.prototype.isNote = function () { 3442 var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i); 3443 return function (note) { 3444 return noteFormat.test(note); 3445 }; 3446 }(); 3447 Tone.prototype.isFrequency = function () { 3448 var freqFormat = new RegExp(/^\d*\.?\d+hz$/i); 3449 return function (freq) { 3450 return freqFormat.test(freq); 3451 }; 3452 }(); 3453 function getTransportBpm() { 3454 if (Tone.Transport && Tone.Transport.bpm) { 3455 return Tone.Transport.bpm.value; 3456 } else { 3457 return 120; 3458 } 3459 } 3460 function getTransportTimeSignature() { 3461 if (Tone.Transport && Tone.Transport.timeSignature) { 3462 return Tone.Transport.timeSignature; 3463 } else { 3464 return 4; 3465 } 3466 } 3467 Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) { 3468 bpm = this.defaultArg(bpm, getTransportBpm()); 3469 timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); 3470 var beatTime = 60 / bpm; 3471 if (notation === '1n') { 3472 notation = '1m'; 3473 } 3474 var subdivision = parseInt(notation, 10); 3475 var beats = 0; 3476 if (subdivision === 0) { 3477 beats = 0; 3478 } 3479 var lastLetter = notation.slice(-1); 3480 if (lastLetter === 't') { 3481 beats = 4 / subdivision * 2 / 3; 3482 } else if (lastLetter === 'n') { 3483 beats = 4 / subdivision; 3484 } else if (lastLetter === 'm') { 3485 beats = subdivision * timeSignature; 3486 } else { 3487 beats = 0; 3488 } 3489 return beatTime * beats; 3490 }; 3491 Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) { 3492 bpm = this.defaultArg(bpm, getTransportBpm()); 3493 timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); 3494 var measures = 0; 3495 var quarters = 0; 3496 var sixteenths = 0; 3497 var split = transportTime.split(':'); 3498 if (split.length === 2) { 3499 measures = parseFloat(split[0]); 3500 quarters = parseFloat(split[1]); 3501 } else if (split.length === 1) { 3502 quarters = parseFloat(split[0]); 3503 } else if (split.length === 3) { 3504 measures = parseFloat(split[0]); 3505 quarters = parseFloat(split[1]); 3506 sixteenths = parseFloat(split[2]); 3507 } 3508 var beats = measures * timeSignature + quarters + sixteenths / 4; 3509 return beats * (60 / bpm); 3510 }; 3511 Tone.prototype.ticksToSeconds = function (ticks, bpm) { 3512 if (this.isUndef(Tone.Transport)) { 3513 return 0; 3514 } 3515 ticks = parseFloat(ticks); 3516 bpm = this.defaultArg(bpm, getTransportBpm()); 3517 var tickTime = 60 / bpm / Tone.Transport.PPQ; 3518 return tickTime * ticks; 3519 }; 3520 Tone.prototype.frequencyToSeconds = function (freq) { 3521 return 1 / parseFloat(freq); 3522 }; 3523 Tone.prototype.samplesToSeconds = function (samples) { 3524 return samples / this.context.sampleRate; 3525 }; 3526 Tone.prototype.secondsToSamples = function (seconds) { 3527 return seconds * this.context.sampleRate; 3528 }; 3529 Tone.prototype.secondsToTransportTime = function (seconds, bpm, timeSignature) { 3530 bpm = this.defaultArg(bpm, getTransportBpm()); 3531 timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); 3532 var quarterTime = 60 / bpm; 3533 var quarters = seconds / quarterTime; 3534 var measures = Math.floor(quarters / timeSignature); 3535 var sixteenths = quarters % 1 * 4; 3536 quarters = Math.floor(quarters) % timeSignature; 3537 var progress = [ 3538 measures, 3539 quarters, 3540 sixteenths 3541 ]; 3542 return progress.join(':'); 3543 }; 3544 Tone.prototype.secondsToFrequency = function (seconds) { 3545 return 1 / seconds; 3546 }; 3547 Tone.prototype.toTransportTime = function (time, bpm, timeSignature) { 3548 var seconds = this.toSeconds(time); 3549 return this.secondsToTransportTime(seconds, bpm, timeSignature); 3550 }; 3551 Tone.prototype.toFrequency = function (freq, now) { 3552 if (this.isFrequency(freq)) { 3553 return parseFloat(freq); 3554 } else if (this.isNotation(freq) || this.isTransportTime(freq)) { 3555 return this.secondsToFrequency(this.toSeconds(freq, now)); 3556 } else if (this.isNote(freq)) { 3557 return this.noteToFrequency(freq); 3558 } else { 3559 return freq; 3560 } 3561 }; 3562 Tone.prototype.toTicks = function (time) { 3563 if (this.isUndef(Tone.Transport)) { 3564 return 0; 3565 } 3566 var bpm = Tone.Transport.bpm.value; 3567 var plusNow = 0; 3568 if (this.isNowRelative(time)) { 3569 time = time.replace('+', ''); 3570 plusNow = Tone.Transport.ticks; 3571 } else if (this.isUndef(time)) { 3572 return Tone.Transport.ticks; 3573 } 3574 var seconds = this.toSeconds(time); 3575 var quarter = 60 / bpm; 3576 var quarters = seconds / quarter; 3577 var tickNum = quarters * Tone.Transport.PPQ; 3578 return Math.round(tickNum + plusNow); 3579 }; 3580 Tone.prototype.toSamples = function (time) { 3581 var seconds = this.toSeconds(time); 3582 return Math.round(seconds * this.context.sampleRate); 3583 }; 3584 Tone.prototype.toSeconds = function (time, now) { 3585 now = this.defaultArg(now, this.now()); 3586 if (this.isNumber(time)) { 3587 return time; 3588 } else if (this.isString(time)) { 3589 var plusTime = 0; 3590 if (this.isNowRelative(time)) { 3591 time = time.replace('+', ''); 3592 plusTime = now; 3593 } 3594 var betweenParens = time.match(/\(([^)(]+)\)/g); 3595 if (betweenParens) { 3596 for (var j = 0; j < betweenParens.length; j++) { 3597 var symbol = betweenParens[j].replace(/[\(\)]/g, ''); 3598 var symbolVal = this.toSeconds(symbol); 3599 time = time.replace(betweenParens[j], symbolVal); 3600 } 3601 } 3602 if (time.indexOf('@') !== -1) { 3603 var quantizationSplit = time.split('@'); 3604 if (!this.isUndef(Tone.Transport)) { 3605 var toQuantize = quantizationSplit[0].trim(); 3606 if (toQuantize === '') { 3607 toQuantize = undefined; 3608 } 3609 if (plusTime > 0) { 3610 toQuantize = '+' + toQuantize; 3611 plusTime = 0; 3612 } 3613 var subdivision = quantizationSplit[1].trim(); 3614 time = Tone.Transport.quantize(toQuantize, subdivision); 3615 } else { 3616 throw new Error('quantization requires Tone.Transport'); 3617 } 3618 } else { 3619 var components = time.split(/[\(\)\-\+\/\*]/); 3620 if (components.length > 1) { 3621 var originalTime = time; 3622 for (var i = 0; i < components.length; i++) { 3623 var symb = components[i].trim(); 3624 if (symb !== '') { 3625 var val = this.toSeconds(symb); 3626 time = time.replace(symb, val); 3627 } 3628 } 3629 try { 3630 time = eval(time); 3631 } catch (e) { 3632 throw new EvalError('cannot evaluate Time: ' + originalTime); 3633 } 3634 } else if (this.isNotation(time)) { 3635 time = this.notationToSeconds(time); 3636 } else if (this.isTransportTime(time)) { 3637 time = this.transportTimeToSeconds(time); 3638 } else if (this.isFrequency(time)) { 3639 time = this.frequencyToSeconds(time); 3640 } else if (this.isTicks(time)) { 3641 time = this.ticksToSeconds(time); 3642 } else { 3643 time = parseFloat(time); 3644 } 3645 } 3646 return time + plusTime; 3647 } else { 3648 return now; 3649 } 3650 }; 3651 Tone.prototype.toNotation = function (time, bpm, timeSignature) { 3652 var testNotations = [ 3653 '1m', 3654 '2n', 3655 '4n', 3656 '8n', 3657 '16n', 3658 '32n', 3659 '64n', 3660 '128n' 3661 ]; 3662 var retNotation = toNotationHelper.call(this, time, bpm, timeSignature, testNotations); 3663 var testTripletNotations = [ 3664 '1m', 3665 '2n', 3666 '2t', 3667 '4n', 3668 '4t', 3669 '8n', 3670 '8t', 3671 '16n', 3672 '16t', 3673 '32n', 3674 '32t', 3675 '64n', 3676 '64t', 3677 '128n' 3678 ]; 3679 var retTripletNotation = toNotationHelper.call(this, time, bpm, timeSignature, testTripletNotations); 3680 if (retTripletNotation.split('+').length < retNotation.split('+').length) { 3681 return retTripletNotation; 3682 } else { 3683 return retNotation; 3684 } 3685 }; 3686 function toNotationHelper(time, bpm, timeSignature, testNotations) { 3687 var seconds = this.toSeconds(time); 3688 var threshold = this.notationToSeconds(testNotations[testNotations.length - 1], bpm, timeSignature); 3689 var retNotation = ''; 3690 for (var i = 0; i < testNotations.length; i++) { 3691 var notationTime = this.notationToSeconds(testNotations[i], bpm, timeSignature); 3692 var multiple = seconds / notationTime; 3693 var floatingPointError = 0.000001; 3694 if (1 - multiple % 1 < floatingPointError) { 3695 multiple += floatingPointError; 3696 } 3697 multiple = Math.floor(multiple); 3698 if (multiple > 0) { 3699 if (multiple === 1) { 3700 retNotation += testNotations[i]; 3701 } else { 3702 retNotation += multiple.toString() + '*' + testNotations[i]; 3703 } 3704 seconds -= multiple * notationTime; 3705 if (seconds < threshold) { 3706 break; 3707 } else { 3708 retNotation += ' + '; 3709 } 3710 } 3711 } 3712 if (retNotation === '') { 3713 retNotation = '0'; 3714 } 3715 return retNotation; 3716 } 3717 Tone.prototype.fromUnits = function (val, units) { 3718 if (this.convert || this.isUndef(this.convert)) { 3719 switch (units) { 3720 case Tone.Type.Time: 3721 return this.toSeconds(val); 3722 case Tone.Type.Frequency: 3723 return this.toFrequency(val); 3724 case Tone.Type.Decibels: 3725 return this.dbToGain(val); 3726 case Tone.Type.NormalRange: 3727 return Math.min(Math.max(val, 0), 1); 3728 case Tone.Type.AudioRange:
3729 return Math.min(Math.max(val, -1), 1); 3730 case Tone.Type.Positive: 3731 return Math.max(val, 0); 3732 default: 3733 return val; 3734 } 3735 } else { 3736 return val; 3737 } 3738 }; 3739 Tone.prototype.toUnits = function (val, units) { 3740 if (this.convert || this.isUndef(this.convert)) { 3741 switch (units) { 3742 case Tone.Type.Decibels: 3743 return this.gainToDb(val); 3744 default: 3745 return val; 3746 } 3747 } else { 3748 return val; 3749 } 3750 }; 3751 var noteToScaleIndex = { 3752 'cbb': -2, 3753 'cb': -1, 3754 'c': 0, 3755 'c#': 1, 3756 'cx': 2, 3757 'dbb': 0, 3758 'db': 1, 3759 'd': 2, 3760 'd#': 3, 3761 'dx': 4, 3762 'ebb': 2, 3763 'eb': 3, 3764 'e': 4, 3765 'e#': 5, 3766 'ex': 6, 3767 'fbb': 3, 3768 'fb': 4, 3769 'f': 5, 3770 'f#': 6, 3771 'fx': 7, 3772 'gbb': 5, 3773 'gb': 6, 3774 'g': 7, 3775 'g#': 8, 3776 'gx': 9, 3777 'abb': 7, 3778 'ab': 8, 3779 'a': 9, 3780 'a#': 10, 3781 'ax': 11, 3782 'bbb': 9, 3783 'bb': 10, 3784 'b': 11, 3785 'b#': 12, 3786 'bx': 13 3787 }; 3788 var scaleIndexToNote = [ 3789 'C', 3790 'C#', 3791 'D', 3792 'D#', 3793 'E', 3794 'F', 3795 'F#', 3796 'G', 3797 'G#', 3798 'A', 3799 'A#', 3800 'B' 3801 ]; 3802 Tone.A4 = 440; 3803 Tone.prototype.noteToFrequency = function (note) { 3804 var parts = note.split(/(-?\d+)/); 3805 if (parts.length === 3) { 3806 var index = noteToScaleIndex[parts[0].toLowerCase()]; 3807 var octave = parts[1]; 3808 var noteNumber = index + (parseInt(octave, 10) + 1) * 12; 3809 return this.midiToFrequency(noteNumber); 3810 } else { 3811 return 0; 3812 } 3813 }; 3814 Tone.prototype.frequencyToNote = function (freq) { 3815 var log = Math.log(freq / Tone.A4) / Math.LN2; 3816 var noteNumber = Math.round(12 * log) + 57; 3817 var octave = Math.floor(noteNumber / 12); 3818 if (octave < 0) { 3819 noteNumber += -12 * octave; 3820 } 3821 var noteName = scaleIndexToNote[noteNumber % 12]; 3822 return noteName + octave.toString(); 3823 }; 3824 Tone.prototype.intervalToFrequencyRatio = function (interval) { 3825 return Math.pow(2, interval / 12); 3826 }; 3827 Tone.prototype.midiToNote = function (midiNumber) { 3828 var octave = Math.floor(midiNumber / 12) - 1; 3829 var note = midiNumber % 12; 3830 return scaleIndexToNote[note] + octave; 3831 }; 3832 Tone.prototype.noteToMidi = function (note) { 3833 var parts = note.split(/(\d+)/); 3834 if (parts.length === 3) { 3835 var index = noteToScaleIndex[parts[0].toLowerCase()]; 3836 var octave = parts[1]; 3837 return index + (parseInt(octave, 10) + 1) * 12; 3838 } else { 3839 return 0; 3840 } 3841 }; 3842 Tone.prototype.midiToFrequency = function (midi) { 3843 return Tone.A4 * Math.pow(2, (midi - 69) / 12); 3844 }; 3845 return Tone; 3846}(Tone_core_Tone); 3847/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 3848var Tone_core_Param; 3849Tone_core_Param = function (Tone) { 3850 'use strict'; 3851 Tone.Param = function () { 3852 var options = this.optionsObject(arguments, [ 3853 'param', 3854 'units', 3855 'convert' 3856 ], Tone.Param.defaults); 3857 this._param = this.input = options.param; 3858 this.units = options.units; 3859 this.convert = options.convert; 3860 this.overridden = false; 3861 if (!this.isUndef(options.value)) { 3862 this.value = options.value; 3863 } 3864 }; 3865 Tone.extend(Tone.Param); 3866 Tone.Param.defaults = { 3867 'units': Tone.Type.Default, 3868 'convert': true, 3869 'param': undefined 3870 }; 3871 Object.defineProperty(Tone.Param.prototype, 'value', { 3872 get: function () { 3873 return this._toUnits(this._param.value); 3874 }, 3875 set: function (value) { 3876 var convertedVal = this._fromUnits(value); 3877 this._param.value = convertedVal; 3878 } 3879 }); 3880 Tone.Param.prototype._fromUnits = function (val) { 3881 if (this.convert || this.isUndef(this.convert)) { 3882 switch (this.units) { 3883 case Tone.Type.Time: 3884 return this.toSeconds(val); 3885 case Tone.Type.Frequency: 3886 return this.toFrequency(val); 3887 case Tone.Type.Decibels: 3888 return this.dbToGain(val); 3889 case Tone.Type.NormalRange: 3890 return Math.min(Math.max(val, 0), 1); 3891 case Tone.Type.AudioRange: 3892 return Math.min(Math.max(val, -1), 1); 3893 case Tone.Type.Positive: 3894 return Math.max(val, 0); 3895 default: 3896 return val; 3897 } 3898 } else { 3899 return val; 3900 } 3901 }; 3902 Tone.Param.prototype._toUnits = function (val) { 3903 if (this.convert || this.isUndef(this.convert)) { 3904 switch (this.units) { 3905 case Tone.Type.Decibels: 3906 return this.gainToDb(val); 3907 default: 3908 return val; 3909 } 3910 } else { 3911 return val; 3912 } 3913 }; 3914 Tone.Param.prototype._minOutput = 0.00001; 3915 Tone.Param.prototype.setValueAtTime = function (value, time) { 3916 value = this._fromUnits(value); 3917 this._param.setValueAtTime(value, this.toSeconds(time)); 3918 return this; 3919 }; 3920 Tone.Param.prototype.setRampPoint = function (now) { 3921 now = this.defaultArg(now, this.now()); 3922 var currentVal = this._param.value; 3923 this._param.setValueAtTime(currentVal, now); 3924 return this; 3925 }; 3926 Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) { 3927 value = this._fromUnits(value); 3928 this._param.linearRampToValueAtTime(value, this.toSeconds(endTime)); 3929 return this; 3930 };
3931 Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) { 3932 value = this._fromUnits(value); 3933 value = Math.max(this._minOutput, value); 3934 this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); 3935 return this; 3936 }; 3937 Tone.Param.prototype.exponentialRampToValue = function (value, rampTime) { 3938 var now = this.now(); 3939 var currentVal = this.value; 3940 this.setValueAtTime(Math.max(currentVal, this._minOutput), now); 3941 this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); 3942 return this; 3943 }; 3944 Tone.Param.prototype.linearRampToValue = function (value, rampTime) { 3945 var now = this.now(); 3946 this.setRampPoint(now); 3947 this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); 3948 return this; 3949 }; 3950 Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) { 3951 value = this._fromUnits(value); 3952 value = Math.max(this._minOutput, value); 3953 timeConstant = Math.max(this._minOutput, timeConstant); 3954 this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); 3955 return this; 3956 }; 3957 Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration) { 3958 for (var i = 0; i < values.length; i++) { 3959 values[i] = this._fromUnits(values[i]); 3960 } 3961 this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); 3962 return this; 3963 }; 3964 Tone.Param.prototype.cancelScheduledValues = function (startTime) { 3965 this._param.cancelScheduledValues(this.toSeconds(startTime)); 3966 return this; 3967 }; 3968 Tone.Param.prototype.rampTo = function (value, rampTime) { 3969 rampTime = this.defaultArg(rampTime, 0); 3970 if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) { 3971 this.exponentialRampToValue(value, rampTime); 3972 } else { 3973 this.linearRampToValue(value, rampTime); 3974 } 3975 return this; 3976 }; 3977 Tone.Param.prototype.dispose = function () { 3978 Tone.prototype.dispose.call(this); 3979 this._param = null; 3980 return this; 3981 }; 3982 return Tone.Param; 3983}(Tone_core_Tone); 3984/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 3985var Tone_core_Gain; 3986Tone_core_Gain = function (Tone) { 3987 'use strict'; 3988 Tone.Gain = function () { 3989 var options = this.optionsObject(arguments, [ 3990 'gain', 3991 'units' 3992 ], Tone.Gain.defaults); 3993 this.input = this.output = this._gainNode = this.context.createGain(); 3994 this.gain = new Tone.Param({ 3995 'param': this._gainNode.gain, 3996 'units': options.units, 3997 'value': options.gain, 3998 'convert': options.convert 3999 }); 4000 this._readOnly('gain'); 4001 }; 4002 Tone.extend(Tone.Gain); 4003 Tone.Gain.defaults = { 4004 'gain': 1, 4005 'convert': true 4006 }; 4007 Tone.Gain.prototype.dispose = function () { 4008 Tone.Param.prototype.dispose.call(this); 4009 this._gainNode.disconnect(); 4010 this._gainNode = null; 4011 this._writable('gain'); 4012 this.gain.dispose(); 4013 this.gain = null; 4014 }; 4015 return Tone.Gain; 4016}(Tone_core_Tone, Tone_core_Param); 4017/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4018var Tone_signal_Signal; 4019Tone_signal_Signal = function (Tone) { 4020 'use strict'; 4021 Tone.Signal = function () { 4022 var options = this.optionsObject(arguments, [ 4023 'value', 4024 'units' 4025 ], Tone.Signal.defaults); 4026 this.output = this._gain = this.context.createGain(); 4027 options.param = this._gain.gain; 4028 Tone.Param.call(this, options); 4029 this.input = this._param = this._gain.gain; 4030 Tone.Signal._constant.chain(this._gain); 4031 }; 4032 Tone.extend(Tone.Signal, Tone.Param); 4033 Tone.Signal.defaults = { 4034 'value': 0, 4035 'units': Tone.Type.Default, 4036 'convert': true 4037 }; 4038 Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect; 4039 Tone.Signal.prototype.dispose = function () { 4040 Tone.Param.prototype.dispose.call(this); 4041 this._param = null; 4042 this._gain.disconnect(); 4043 this._gain = null; 4044 return this; 4045 }; 4046 Tone.Signal._constant = null; 4047 Tone._initAudioContext(function (audioContext) { 4048 var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate); 4049 var arr = buffer.getChannelData(0); 4050 for (var i = 0; i < arr.length; i++) { 4051 arr[i] = 1; 4052 } 4053 Tone.Signal._constant = audioContext.createBufferSource(); 4054 Tone.Signal._constant.channelCount = 1; 4055 Tone.Signal._constant.channelCountMode = 'explicit'; 4056 Tone.Signal._constant.buffer = buffer; 4057 Tone.Signal._constant.loop = true; 4058 Tone.Signal._constant.start(0); 4059 Tone.Signal._constant.noGC(); 4060 }); 4061 return Tone.Signal; 4062}(Tone_core_Tone, Tone_signal_WaveShaper, Tone_core_Type, Tone_core_Param); 4063/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4064var Tone_signal_Add; 4065Tone_signal_Add = function (Tone) { 4066 'use strict'; 4067 Tone.Add = function (value) { 4068 Tone.call(this, 2, 0); 4069 this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain(); 4070 this._param = this.input[1] = new Tone.Signal(value); 4071 this._param.connect(this._sum); 4072 }; 4073 Tone.extend(Tone.Add, Tone.Signal); 4074 Tone.Add.prototype.dispose = function () { 4075 Tone.prototype.dispose.call(this); 4076 this._sum.disconnect(); 4077 this._sum = null; 4078 this._param.dispose(); 4079 this._param = null; 4080 return this; 4081 }; 4082 return Tone.Add; 4083}(Tone_core_Tone); 4084/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4085var Tone_signal_Multiply; 4086Tone_signal_Multiply = function (Tone) { 4087 'use strict'; 4088 Tone.Multiply = function (value) { 4089 Tone.call(this, 2, 0); 4090 this._mult = this.input[0] = this.output = this.context.createGain(); 4091 this._param = this.input[1] = this.output.gain; 4092 this._param.value = this.defaultArg(value, 0); 4093 }; 4094 Tone.extend(Tone.Multiply, Tone.Signal); 4095 Tone.Multiply.prototype.dispose = function () { 4096 Tone.prototype.dispose.call(this); 4097 this._mult.disconnect(); 4098 this._mult = null; 4099 this._param = null; 4100 return this; 4101 }; 4102 return Tone.Multiply; 4103}(Tone_core_Tone); 4104/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4105var Tone_signal_Scale; 4106Tone_signal_Scale = function (Tone) { 4107 'use strict'; 4108 Tone.Scale = function (outputMin, outputMax) { 4109 this._outputMin = this.defaultArg(outputMin, 0); 4110 this._outputMax = this.defaultArg(outputMax, 1); 4111 this._scale = this.input = new Tone.Multiply(1); 4112 this._add = this.output = new Tone.Add(0); 4113 this._scale.connect(this._add); 4114 this._setRange(); 4115 }; 4116 Tone.extend(Tone.Scale, Tone.SignalBase); 4117 Object.defineProperty(Tone.Scale.prototype, 'min', { 4118 get: function () { 4119 return this._outputMin; 4120 }, 4121 set: function (min) { 4122 this._outputMin = min; 4123 this._setRange(); 4124 } 4125 }); 4126 Object.defineProperty(Tone.Scale.prototype, 'max', { 4127 get: function () { 4128 return this._outputMax; 4129 }, 4130 set: function (max) { 4131 this._outputMax = max; 4132 this._setRange(); 4133 } 4134 }); 4135 Tone.Scale.prototype._setRange = function () { 4136 this._add.value = this._outputMin; 4137 this._scale.value = this._outputMax - this._outputMin; 4138 }; 4139 Tone.Scale.prototype.dispose = function () { 4140 Tone.prototype.dispose.call(this); 4141 this._add.dispose(); 4142 this._add = null; 4143 this._scale.dispose(); 4144 this._scale = null; 4145 return this; 4146 }; 4147 return Tone.Scale; 4148}(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply); 4149var signal; 4150signal = function () { 4151 'use strict'; 4152 // Signal is built with the Tone.js signal by Yotam Mann 4153 // https://github.com/TONEnoTONE/Tone.js/ 4154 var Signal = Tone_signal_Signal; 4155 var Add = Tone_signal_Add; 4156 var Mult = Tone_signal_Multiply; 4157 var Scale = Tone_signal_Scale; 4158 var Tone = Tone_core_Tone; 4159 var p5sound = master; 4160 Tone.setContext(p5sound.audiocontext); 4161 /** 4162 * <p>p5.Signal is a constant audio-rate signal used by p5.Oscillator 4163 * and p5.Envelope for modulation math.</p> 4164 * 4165 * <p>This is necessary because Web Audio is processed on a seprate clock. 4166 * For example, the p5 draw loop runs about 60 times per second. But 4167 * the audio clock must process samples 44100 times per second. If we 4168 * want to add a value to each of those samples, we can't do it in the 4169 * draw loop, but we can do it by adding a constant-rate audio signal.</p. 4170 * 4171 * <p>This class mostly functions behind the scenes in p5.sound, and returns 4172 * a Tone.Signal from the Tone.js library by Yotam Mann. 4173 * If you want to work directly with audio signals for modular 4174 * synthesis, check out 4175 * <a href='http://bit.ly/1oIoEng' target=_'blank'>tone.js.</a></p> 4176 * 4177 * @class p5.Signal 4178 * @constructor 4179 * @return {Tone.Signal} A Signal object from the Tone.js library 4180 * @example 4181 * <div><code> 4182 * function setup() { 4183 * carrier = new p5.Oscillator('sine'); 4184 * carrier.amp(1); // set amplitude 4185 * carrier.freq(220); // set frequency 4186 * carrier.start(); // start oscillating 4187 * 4188 * modulator = new p5.Oscillator('sawtooth'); 4189 * modulator.disconnect(); 4190 * modulator.amp(1); 4191 * modulator.freq(4); 4192 * modulator.start(); 4193 * 4194 * // Modulator's default amplitude range is -1 to 1. 4195 * // Multiply it by -200, so the range is -200 to 200
4196 * // then add 220 so the range is 20 to 420 4197 * carrier.freq( modulator.mult(-200).add(220) ); 4198 * } 4199 * </code></div> 4200 */ 4201 p5.Signal = function (value) { 4202 var s = new Signal(value); 4203 // p5sound.soundArray.push(s); 4204 return s; 4205 }; 4206 /** 4207 * Fade to value, for smooth transitions 4208 * 4209 * @method fade 4210 * @param {Number} value Value to set this signal 4211 * @param {[Number]} secondsFromNow Length of fade, in seconds from now 4212 */ 4213 Signal.prototype.fade = Signal.prototype.linearRampToValueAtTime; 4214 Mult.prototype.fade = Signal.prototype.fade; 4215 Add.prototype.fade = Signal.prototype.fade; 4216 Scale.prototype.fade = Signal.prototype.fade; 4217 /** 4218 * Connect a p5.sound object or Web Audio node to this 4219 * p5.Signal so that its amplitude values can be scaled. 4220 * 4221 * @param {Object} input 4222 */ 4223 Signal.prototype.setInput = function (_input) { 4224 _input.connect(this); 4225 }; 4226 Mult.prototype.setInput = Signal.prototype.setInput; 4227 Add.prototype.setInput = Signal.prototype.setInput; 4228 Scale.prototype.setInput = Signal.prototype.setInput; 4229 // signals can add / mult / scale themselves 4230 /** 4231 * Add a constant value to this audio signal, 4232 * and return the resulting audio signal. Does 4233 * not change the value of the original signal, 4234 * instead it returns a new p5.SignalAdd. 4235 * 4236 * @method add 4237 * @param {Number} number 4238 * @return {p5.SignalAdd} object 4239 */ 4240 Signal.prototype.add = function (num) { 4241 var add = new Add(num); 4242 // add.setInput(this); 4243 this.connect(add); 4244 return add; 4245 }; 4246 Mult.prototype.add = Signal.prototype.add; 4247 Add.prototype.add = Signal.prototype.add; 4248 Scale.prototype.add = Signal.prototype.add; 4249 /** 4250 * Multiply this signal by a constant value, 4251 * and return the resulting audio signal. Does 4252 * not change the value of the original signal, 4253 * instead it returns a new p5.SignalMult. 4254 * 4255 * @method mult 4256 * @param {Number} number to multiply 4257 * @return {Tone.Multiply} object 4258 */ 4259 Signal.prototype.mult = function (num) { 4260 var mult = new Mult(num); 4261 // mult.setInput(this); 4262 this.connect(mult); 4263 return mult; 4264 }; 4265 Mult.prototype.mult = Signal.prototype.mult; 4266 Add.prototype.mult = Signal.prototype.mult; 4267 Scale.prototype.mult = Signal.prototype.mult; 4268 /** 4269 * Scale this signal value to a given range, 4270 * and return the result as an audio signal. Does 4271 * not change the value of the original signal, 4272 * instead it returns a new p5.SignalScale. 4273 * 4274 * @method scale 4275 * @param {Number} number to multiply 4276 * @param {Number} inMin input range minumum 4277 * @param {Number} inMax input range maximum 4278 * @param {Number} outMin input range minumum 4279 * @param {Number} outMax input range maximum 4280 * @return {p5.SignalScale} object 4281 */ 4282 Signal.prototype.scale = function (inMin, inMax, outMin, outMax) { 4283 var mapOutMin, mapOutMax; 4284 if (arguments.length === 4) { 4285 mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; 4286 mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; 4287 } else { 4288 mapOutMin = arguments[0]; 4289 mapOutMax = arguments[1]; 4290 } 4291 var scale = new Scale(mapOutMin, mapOutMax); 4292 this.connect(scale); 4293 return scale; 4294 }; 4295 Mult.prototype.scale = Signal.prototype.scale; 4296 Add.prototype.scale = Signal.prototype.scale; 4297 Scale.prototype.scale = Signal.prototype.scale; 4298}(Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone, master); 4299var oscillator; 4300oscillator = function () { 4301 'use strict'; 4302 var p5sound = master; 4303 var Signal = Tone_signal_Signal; 4304 var Add = Tone_signal_Add; 4305 var Mult = Tone_signal_Multiply; 4306 var Scale = Tone_signal_Scale; 4307 /** 4308 * <p>Creates a signal that oscillates between -1.0 and 1.0. 4309 * By default, the oscillation takes the form of a sinusoidal 4310 * shape ('sine'). Additional types include 'triangle', 4311 * 'sawtooth' and 'square'. The frequency defaults to 4312 * 440 oscillations per second (440Hz, equal to the pitch of an 4313 * 'A' note).</p> 4314 * 4315 * <p>Set the type of oscillation with setType(), or by creating a 4316 * specific oscillator.</p> For example: 4317 * <code>new p5.SinOsc(freq)</code> 4318 * <code>new p5.TriOsc(freq)</code> 4319 * <code>new p5.SqrOsc(freq)</code> 4320 * <code>new p5.SawOsc(freq)</code>. 4321 * </p> 4322 * 4323 * @class p5.Oscillator 4324 * @constructor 4325 * @param {Number} [freq] frequency defaults to 440Hz 4326 * @param {String} [type] type of oscillator. Options: 4327 * 'sine' (default), 'triangle',
4328 * 'sawtooth', 'square' 4329 * @return {Object} Oscillator object 4330 * @example 4331 * <div><code> 4332 * var osc; 4333 * var playing = false; 4334 * 4335 * function setup() { 4336 * backgroundColor = color(255,0,255); 4337 * textAlign(CENTER); 4338 * 4339 * osc = new p5.Oscillator(); 4340 * osc.setType('sine'); 4341 * osc.freq(240); 4342 * osc.amp(0); 4343 * osc.start(); 4344 * } 4345 * 4346 * function draw() { 4347 * background(backgroundColor) 4348 * text('click to play', width/2, height/2); 4349 * } 4350 * 4351 * function mouseClicked() { 4352 * if (mouseX > 0 && mouseX < width && mouseY < height && mouseY > 0) { 4353 * if (!playing) { 4354 * // ramp amplitude to 0.5 over 0.1 seconds 4355 * osc.amp(0.5, 0.05); 4356 * playing = true; 4357 * backgroundColor = color(0,255,255); 4358 * } else { 4359 * // ramp amplitude to 0 over 0.5 seconds 4360 * osc.amp(0, 0.5); 4361 * playing = false; 4362 * backgroundColor = color(255,0,255); 4363 * } 4364 * } 4365 * } 4366 * </code> </div> 4367 */ 4368 p5.Oscillator = function (freq, type) { 4369 if (typeof freq === 'string') { 4370 var f = type; 4371 type = freq; 4372 freq = f; 4373 } 4374 if (typeof type === 'number') { 4375 var f = type; 4376 type = freq; 4377 freq = f; 4378 } 4379 this.started = false; 4380 // components 4381 this.phaseAmount = undefined; 4382 this.oscillator = p5sound.audiocontext.createOscillator(); 4383 this.f = freq || 440; 4384 // frequency 4385 this.oscillator.frequency.setValueAtTime(this.f, p5sound.audiocontext.currentTime); 4386 this.oscillator.type = type || 'sine'; 4387 var o = this.oscillator; 4388 // connections 4389 this.input = p5sound.audiocontext.createGain(); 4390 this.output = p5sound.audiocontext.createGain(); 4391 this._freqMods = []; 4392 // modulators connected to this oscillator's frequency 4393 // set default output gain to 0.5 4394 this.output.gain.value = 0.5; 4395 this.output.gain.setValueAtTime(0.5, p5sound.audiocontext.currentTime); 4396 this.oscillator.connect(this.output); 4397 // stereo panning 4398 this.panPosition = 0; 4399 this.connection = p5sound.input; 4400 // connect to p5sound by default 4401 this.panner = new p5.Panner(this.output, this.connection, 1); 4402 //array of math operation signal chaining 4403 this.mathOps = [this.output]; 4404 // add to the soundArray so we can dispose of the osc later 4405 p5sound.soundArray.push(this); 4406 }; 4407 /** 4408 * Start an oscillator. Accepts an optional parameter to 4409 * determine how long (in seconds from now) until the 4410 * oscillator starts. 4411 * 4412 * @method start 4413 * @param {Number} [time] startTime in seconds from now. 4414 * @param {Number} [frequency] frequency in Hz. 4415 */ 4416 p5.Oscillator.prototype.start = function (time, f) { 4417 if (this.started) { 4418 var now = p5sound.audiocontext.currentTime; 4419 this.stop(now); 4420 } 4421 if (!this.started) { 4422 var freq = f || this.f; 4423 var type = this.oscillator.type; 4424 // var detune = this.oscillator.frequency.value; 4425 this.oscillator = p5sound.audiocontext.createOscillator(); 4426 this.oscillator.frequency.exponentialRampToValueAtTime(Math.abs(freq), p5sound.audiocontext.currentTime); 4427 this.oscillator.type = type; 4428 // this.oscillator.detune.value = detune; 4429 this.oscillator.connect(this.output); 4430 time = time || 0; 4431 this.oscillator.start(time + p5sound.audiocontext.currentTime); 4432 this.freqNode = this.oscillator.frequency; 4433 // if other oscillators are already connected to this osc's freq 4434 for (var i in this._freqMods) { 4435 if (typeof this._freqMods[i].connect !== 'undefined') { 4436 this._freqMods[i].connect(this.oscillator.frequency); 4437 } 4438 } 4439 this.started = true; 4440 } 4441 }; 4442 /** 4443 * Stop an oscillator. Accepts an optional parameter 4444 * to determine how long (in seconds from now) until the 4445 * oscillator stops. 4446 * 4447 * @method stop 4448 * @param {Number} secondsFromNow Time, in seconds from now. 4449 */ 4450 p5.Oscillator.prototype.stop = function (time) { 4451 if (this.started) { 4452 var t = time || 0; 4453 var now = p5sound.audiocontext.currentTime; 4454 this.oscillator.stop(t + now); 4455 this.started = false;
4456 } 4457 }; 4458 /** 4459 * Set the amplitude between 0 and 1.0. Or, pass in an object 4460 * such as an oscillator to modulate amplitude with an audio signal. 4461 * 4462 * @method amp 4463 * @param {Number|Object} vol between 0 and 1.0 4464 * or a modulating signal/oscillator 4465 * @param {Number} [rampTime] create a fade that lasts rampTime 4466 * @param {Number} [timeFromNow] schedule this event to happen 4467 * seconds from now 4468 * @return {AudioParam} gain If no value is provided, 4469 * returns the Web Audio API 4470 * AudioParam that controls 4471 * this oscillator's 4472 * gain/amplitude/volume) 4473 */ 4474 p5.Oscillator.prototype.amp = function (vol, rampTime, tFromNow) { 4475 var self = this; 4476 if (typeof vol === 'number') { 4477 var rampTime = rampTime || 0; 4478 var tFromNow = tFromNow || 0; 4479 var now = p5sound.audiocontext.currentTime; 4480 var currentVol = this.output.gain.value; 4481 this.output.gain.cancelScheduledValues(now); 4482 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); 4483 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); 4484 } else if (vol) { 4485 vol.connect(self.output.gain); 4486 } else { 4487 // return the Gain Node 4488 return this.output.gain; 4489 } 4490 }; 4491 // these are now the same thing 4492 p5.Oscillator.prototype.fade = p5.Oscillator.prototype.amp; 4493 p5.Oscillator.prototype.getAmp = function () { 4494 return this.output.gain.value; 4495 }; 4496 /** 4497 * Set frequency of an oscillator to a value. Or, pass in an object 4498 * such as an oscillator to modulate the frequency with an audio signal. 4499 * 4500 * @method freq 4501 * @param {Number|Object} Frequency Frequency in Hz 4502 * or modulating signal/oscillator 4503 * @param {Number} [rampTime] Ramp time (in seconds) 4504 * @param {Number} [timeFromNow] Schedule this event to happen 4505 * at x seconds from now 4506 * @return {AudioParam} Frequency If no value is provided, 4507 * returns the Web Audio API 4508 * AudioParam that controls 4509 * this oscillator's frequency 4510 * @example 4511 * <div><code> 4512 * var osc = new p5.Oscillator(300); 4513 * osc.start(); 4514 * osc.freq(40, 10); 4515 * </code></div> 4516 */ 4517 p5.Oscillator.prototype.freq = function (val, rampTime, tFromNow) { 4518 if (typeof val === 'number' && !isNaN(val)) { 4519 this.f = val; 4520 var now = p5sound.audiocontext.currentTime; 4521 var rampTime = rampTime || 0; 4522 var tFromNow = tFromNow || 0; 4523 // var currentFreq = this.oscillator.frequency.value; 4524 // this.oscillator.frequency.cancelScheduledValues(now); 4525 if (rampTime == 0) { 4526 this.oscillator.frequency.cancelScheduledValues(now); 4527 this.oscillator.frequency.setValueAtTime(val, tFromNow + now); 4528 } else { 4529 if (val > 0) { 4530 this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); 4531 } else { 4532 this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now); 4533 } 4534 } 4535 // reset phase if oscillator has a phase 4536 if (this.phaseAmount) { 4537 this.phase(this.phaseAmount); 4538 } 4539 } else if (val) { 4540 if (val.output) { 4541 val = val.output; 4542 } 4543 val.connect(this.oscillator.frequency); 4544 // keep track of what is modulating this param 4545 // so it can be re-connected if 4546 this._freqMods.push(val); 4547 } else { 4548 // return the Frequency Node 4549 return this.oscillator.frequency; 4550 } 4551 }; 4552 p5.Oscillator.prototype.getFreq = function () { 4553 return this.oscillator.frequency.value; 4554 }; 4555 /** 4556 * Set type to 'sine', 'triangle', 'sawtooth' or 'square'. 4557 * 4558 * @method setType 4559 * @param {String} type 'sine', 'triangle', 'sawtooth' or 'square'. 4560 */ 4561 p5.Oscillator.prototype.setType = function (type) { 4562 this.oscillator.type = type; 4563 }; 4564 p5.Oscillator.prototype.getType = function () { 4565 return this.oscillator.type; 4566 }; 4567 /** 4568 * Connect to a p5.sound / Web Audio object. 4569 * 4570 * @method connect 4571 * @param {Object} unit A p5.sound or Web Audio object 4572 */ 4573 p5.Oscillator.prototype.connect = function (unit) { 4574 if (!unit) { 4575 this.panner.connect(p5sound.input); 4576 } else if (unit.hasOwnProperty('input')) {
4577 this.panner.connect(unit.input); 4578 this.connection = unit.input; 4579 } else { 4580 this.panner.connect(unit); 4581 this.connection = unit; 4582 } 4583 }; 4584 /** 4585 * Disconnect all outputs 4586 * 4587 * @method disconnect 4588 */ 4589 p5.Oscillator.prototype.disconnect = function (unit) { 4590 this.output.disconnect(); 4591 this.panner.disconnect(); 4592 this.output.connect(this.panner); 4593 this.oscMods = []; 4594 }; 4595 /** 4596 * Pan between Left (-1) and Right (1) 4597 * 4598 * @method pan 4599 * @param {Number} panning Number between -1 and 1 4600 * @param {Number} timeFromNow schedule this event to happen 4601 * seconds from now 4602 */ 4603 p5.Oscillator.prototype.pan = function (pval, tFromNow) { 4604 this.panPosition = pval; 4605 this.panner.pan(pval, tFromNow); 4606 }; 4607 p5.Oscillator.prototype.getPan = function () { 4608 return this.panPosition; 4609 }; 4610 // get rid of the oscillator 4611 p5.Oscillator.prototype.dispose = function () { 4612 // remove reference from soundArray 4613 var index = p5sound.soundArray.indexOf(this); 4614 p5sound.soundArray.splice(index, 1); 4615 if (this.oscillator) { 4616 var now = p5sound.audiocontext.currentTime; 4617 this.stop(now); 4618 this.disconnect(); 4619 this.oscillator.disconnect(); 4620 this.panner = null; 4621 this.oscillator = null; 4622 } 4623 // if it is a Pulse 4624 if (this.osc2) { 4625 this.osc2.dispose(); 4626 } 4627 }; 4628 /** 4629 * Set the phase of an oscillator between 0.0 and 1.0. 4630 * In this implementation, phase is a delay time 4631 * based on the oscillator's current frequency. 4632 * 4633 * @method phase 4634 * @param {Number} phase float between 0.0 and 1.0 4635 */ 4636 p5.Oscillator.prototype.phase = function (p) { 4637 var delayAmt = p5.prototype.map(p, 0, 1, 0, 1 / this.f); 4638 var now = p5sound.audiocontext.currentTime; 4639 this.phaseAmount = p; 4640 if (!this.dNode) { 4641 // create a delay node 4642 this.dNode = p5sound.audiocontext.createDelay(); 4643 // put the delay node in between output and panner 4644 this.oscillator.disconnect(); 4645 this.oscillator.connect(this.dNode); 4646 this.dNode.connect(this.output); 4647 } 4648 // set delay time to match phase: 4649 this.dNode.delayTime.setValueAtTime(delayAmt, now); 4650 }; 4651 // ========================== // 4652 // SIGNAL MATH FOR MODULATION // 4653 // ========================== // 4654 // return sigChain(this, scale, thisChain, nextChain, Scale); 4655 var sigChain = function (o, mathObj, thisChain, nextChain, type) { 4656 var chainSource = o.oscillator; 4657 // if this type of math already exists in the chain, replace it 4658 for (var i in o.mathOps) { 4659 if (o.mathOps[i] instanceof type) { 4660 chainSource.disconnect(); 4661 o.mathOps[i].dispose(); 4662 thisChain = i; 4663 // assume nextChain is output gain node unless... 4664 if (thisChain < o.mathOps.length - 2) { 4665 nextChain = o.mathOps[i + 1]; 4666 } 4667 } 4668 } 4669 if (thisChain == o.mathOps.length - 1) { 4670 o.mathOps.push(nextChain); 4671 } 4672 // assume source is the oscillator unless i > 0 4673 if (i > 0) { 4674 chainSource = o.mathOps[i - 1]; 4675 } 4676 chainSource.disconnect(); 4677 chainSource.connect(mathObj); 4678 mathObj.connect(nextChain); 4679 o.mathOps[thisChain] = mathObj; 4680 return o; 4681 }; 4682 /** 4683 * Add a value to the p5.Oscillator's output amplitude, 4684 * and return the oscillator. Calling this method again 4685 * will override the initial add() with a new value. 4686 * 4687 * @method add 4688 * @param {Number} number Constant number to add 4689 * @return {p5.Oscillator} Oscillator Returns this oscillator 4690 * with scaled output 4691 * 4692 */ 4693 p5.Oscillator.prototype.add = function (num) { 4694 var add = new Add(num); 4695 var thisChain = this.mathOps.length - 1; 4696 var nextChain = this.output; 4697 return sigChain(this, add, thisChain, nextChain, Add); 4698 }; 4699 /** 4700 * Multiply the p5.Oscillator's output amplitude 4701 * by a fixed value (i.e. turn it up!). Calling this method 4702 * again will override the initial mult() with a new value. 4703 * 4704 * @method mult 4705 * @param {Number}
4705 number Constant number to multiply 4706 * @return {p5.Oscillator} Oscillator Returns this oscillator 4707 * with multiplied output 4708 */ 4709 p5.Oscillator.prototype.mult = function (num) { 4710 var mult = new Mult(num); 4711 var thisChain = this.mathOps.length - 1; 4712 var nextChain = this.output; 4713 return sigChain(this, mult, thisChain, nextChain, Mult); 4714 }; 4715 /** 4716 * Scale this oscillator's amplitude values to a given 4717 * range, and return the oscillator. Calling this method 4718 * again will override the initial scale() with new values. 4719 * 4720 * @method scale 4721 * @param {Number} inMin input range minumum 4722 * @param {Number} inMax input range maximum 4723 * @param {Number} outMin input range minumum 4724 * @param {Number} outMax input range maximum 4725 * @return {p5.Oscillator} Oscillator Returns this oscillator 4726 * with scaled output 4727 */ 4728 p5.Oscillator.prototype.scale = function (inMin, inMax, outMin, outMax) { 4729 var mapOutMin, mapOutMax; 4730 if (arguments.length === 4) { 4731 mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; 4732 mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; 4733 } else { 4734 mapOutMin = arguments[0]; 4735 mapOutMax = arguments[1]; 4736 } 4737 var scale = new Scale(mapOutMin, mapOutMax); 4738 var thisChain = this.mathOps.length - 1; 4739 var nextChain = this.output; 4740 return sigChain(this, scale, thisChain, nextChain, Scale); 4741 }; 4742 // ============================== // 4743 // SinOsc, TriOsc, SqrOsc, SawOsc // 4744 // ============================== // 4745 /** 4746 * Constructor: <code>new p5.SinOsc()</code>. 4747 * This creates a Sine Wave Oscillator and is 4748 * equivalent to <code> new p5.Oscillator('sine') 4749 * </code> or creating a p5.Oscillator and then calling 4750 * its method <code>setType('sine')</code>. 4751 * See p5.Oscillator for methods. 4752 * 4753 * @method p5.SinOsc 4754 * @param {[Number]} freq Set the frequency 4755 */ 4756 p5.SinOsc = function (freq) { 4757 p5.Oscillator.call(this, freq, 'sine'); 4758 }; 4759 p5.SinOsc.prototype = Object.create(p5.Oscillator.prototype); 4760 /** 4761 * Constructor: <code>new p5.TriOsc()</code>. 4762 * This creates a Triangle Wave Oscillator and is 4763 * equivalent to <code>new p5.Oscillator('triangle') 4764 * </code> or creating a p5.Oscillator and then calling 4765 * its method <code>setType('triangle')</code>. 4766 * See p5.Oscillator for methods. 4767 * 4768 * @method p5.TriOsc 4769 * @param {[Number]} freq Set the frequency 4770 */ 4771 p5.TriOsc = function (freq) { 4772 p5.Oscillator.call(this, freq, 'triangle'); 4773 }; 4774 p5.TriOsc.prototype = Object.create(p5.Oscillator.prototype); 4775 /** 4776 * Constructor: <code>new p5.SawOsc()</code>. 4777 * This creates a SawTooth Wave Oscillator and is 4778 * equivalent to <code> new p5.Oscillator('sawtooth') 4779 * </code> or creating a p5.Oscillator and then calling 4780 * its method <code>setType('sawtooth')</code>. 4781 * See p5.Oscillator for methods. 4782 * 4783 * @method p5.SawOsc 4784 * @param {[Number]} freq Set the frequency 4785 */ 4786 p5.SawOsc = function (freq) { 4787 p5.Oscillator.call(this, freq, 'sawtooth'); 4788 }; 4789 p5.SawOsc.prototype = Object.create(p5.Oscillator.prototype); 4790 /** 4791 * Constructor: <code>new p5.SqrOsc()</code>. 4792 * This creates a Square Wave Oscillator and is 4793 * equivalent to <code> new p5.Oscillator('square') 4794 * </code> or creating a p5.Oscillator and then calling 4795 * its method <code>setType('square')</code>. 4796 * See p5.Oscillator for methods. 4797 * 4798 * @method p5.SqrOsc 4799 * @param {[Number]} freq Set the frequency 4800 */ 4801 p5.SqrOsc = function (freq) { 4802 p5.Oscillator.call(this, freq, 'square'); 4803 }; 4804 p5.SqrOsc.prototype = Object.create(p5.Oscillator.prototype); 4805}(master, Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale); 4806/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4807var Tone_core_Timeline; 4808Tone_core_Timeline = function (Tone) { 4809 'use strict'; 4810 Tone.Timeline = function () { 4811 var options = this.optionsObject(arguments, ['memory'], Tone.Timeline.defaults); 4812 this._timeline = []; 4813 this._toRemove = []; 4814 this._iterating = false;
4815 this.memory = options.memory; 4816 }; 4817 Tone.extend(Tone.Timeline); 4818 Tone.Timeline.defaults = { 'memory': Infinity }; 4819 Object.defineProperty(Tone.Timeline.prototype, 'length', { 4820 get: function () { 4821 return this._timeline.length; 4822 } 4823 }); 4824 Tone.Timeline.prototype.addEvent = function (event) { 4825 if (this.isUndef(event.time)) { 4826 throw new Error('events must have a time attribute'); 4827 } 4828 event.time = this.toSeconds(event.time); 4829 if (this._timeline.length) { 4830 var index = this._search(event.time); 4831 this._timeline.splice(index + 1, 0, event); 4832 } else { 4833 this._timeline.push(event); 4834 } 4835 if (this.length > this.memory) { 4836 var diff = this.length - this.memory; 4837 this._timeline.splice(0, diff); 4838 } 4839 return this; 4840 }; 4841 Tone.Timeline.prototype.removeEvent = function (event) { 4842 if (this._iterating) { 4843 this._toRemove.push(event); 4844 } else { 4845 var index = this._timeline.indexOf(event); 4846 if (index !== -1) { 4847 this._timeline.splice(index, 1); 4848 } 4849 } 4850 return this; 4851 }; 4852 Tone.Timeline.prototype.getEvent = function (time) { 4853 time = this.toSeconds(time); 4854 var index = this._search(time); 4855 if (index !== -1) { 4856 return this._timeline[index]; 4857 } else { 4858 return null; 4859 } 4860 }; 4861 Tone.Timeline.prototype.getEventAfter = function (time) { 4862 time = this.toSeconds(time); 4863 var index = this._search(time); 4864 if (index + 1 < this._timeline.length) { 4865 return this._timeline[index + 1]; 4866 } else { 4867 return null; 4868 } 4869 }; 4870 Tone.Timeline.prototype.getEventBefore = function (time) { 4871 time = this.toSeconds(time); 4872 var index = this._search(time); 4873 if (index - 1 >= 0) { 4874 return this._timeline[index - 1]; 4875 } else { 4876 return null; 4877 } 4878 }; 4879 Tone.Timeline.prototype.cancel = function (after) { 4880 if (this._timeline.length > 1) { 4881 after = this.toSeconds(after); 4882 var index = this._search(after); 4883 if (index >= 0) { 4884 this._timeline = this._timeline.slice(0, index); 4885 } else { 4886 this._timeline = []; 4887 } 4888 } else if (this._timeline.length === 1) { 4889 if (this._timeline[0].time >= after) { 4890 this._timeline = []; 4891 } 4892 } 4893 return this; 4894 }; 4895 Tone.Timeline.prototype.cancelBefore = function (time) { 4896 if (this._timeline.length) { 4897 time = this.toSeconds(time); 4898 var index = this._search(time); 4899 if (index >= 0) { 4900 this._timeline = this._timeline.slice(index + 1); 4901 } 4902 } 4903 return this; 4904 }; 4905 Tone.Timeline.prototype._search = function (time) { 4906 var beginning = 0; 4907 var len = this._timeline.length; 4908 var end = len; 4909 while (beginning <= end && beginning < len) { 4910 var midPoint = Math.floor(beginning + (end - beginning) / 2); 4911 var event = this._timeline[midPoint]; 4912 if (event.time === time) { 4913 for (var i = midPoint; i < this._timeline.length; i++) { 4914 var testEvent = this._timeline[i]; 4915 if (testEvent.time === time) { 4916 midPoint = i; 4917 } 4918 } 4919 return midPoint; 4920 } else if (event.time > time) { 4921 end = midPoint - 1; 4922 } else if (event.time < time) { 4923 beginning = midPoint + 1; 4924 } 4925 } 4926 return beginning - 1; 4927 }; 4928 Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) { 4929 this._iterating = true; 4930 lowerBound = this.defaultArg(lowerBound, 0); 4931 upperBound = this.defaultArg(upperBound, this._timeline.length - 1); 4932 for (var i = lowerBound; i <= upperBound; i++) { 4933 callback(this._timeline[i]); 4934 } 4935 this._iterating = false; 4936 if (this._toRemove.length > 0) { 4937 for (var j = 0; j < this._toRemove.length; j++) { 4938 var index = this._timeline.indexOf(this._toRemove[j]); 4939 if (index !== -1) { 4940 this._timeline.splice(index, 1); 4941 } 4942 } 4943 this._toRemove = []; 4944 } 4945 }; 4946 Tone.Timeline.prototype.forEach = function (callback) { 4947 this._iterate(callback); 4948 return this; 4949 }; 4950 Tone.Timeline.prototype.forEachBefore = function (time, callback) { 4951 time = this.toSeconds(time); 4952 var upperBound = this._search(time); 4953 if (upperBound !== -1) { 4954 this._iterate(callback, 0, upperBound); 4955 } 4956 return this; 4957 }; 4958 Tone.Timeline.prototype.forEachAfter = function (time, callback) { 4959 time = this.toSeconds(time); 4960 var lowerBound = this._search(time); 4961 this._iterate(callback, lowerBound + 1); 4962 return this; 4963 }; 4964 Tone.Timeline.prototype.forEachFrom = function (time, callback) { 4965 time = this.toSeconds(time); 4966 var lowerBound = this._search(time); 4967 while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) { 4968 lowerBound--; 4969 } 4970 this._iterate(callback, lowerBound + 1); 4971 return this; 4972 }; 4973 Tone.Timeline.prototype.forEachAtTime = function (time, callback) { 4974 time = this.toSeconds(time); 4975 var upperBound = this._search(time); 4976 if (upperBound !== -1) { 4977 this._iterate(function (event) { 4978 if (event.time === time) { 4979 callback(event); 4980 } 4981 }, 0, upperBound); 4982 } 4983 return this; 4984 }; 4985 Tone.Timeline.prototype.dispose = function () { 4986 Tone.prototype.dispose.call(this); 4987 this._timeline = null; 4988 this._toRemove = null; 4989 }; 4990 return Tone.Timeline; 4991}(Tone_core_Tone); 4992/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 4993var Tone_signal_TimelineSignal; 4994Tone_signal_TimelineSignal = function (Tone) { 4995 'use strict'; 4996 Tone.TimelineSignal = function () { 4997 var options = this.optionsObject(arguments, [ 4998 'value', 4999 'units' 5000 ], Tone.Signal.defaults); 5001 Tone.Signal.apply(this, options); 5002 options.param = this._param; 5003 Tone.Param.call(this, options); 5004 this._events = new Tone.Timeline(10); 5005 this._initial = this._fromUnits(this._param.value); 5006 }; 5007 Tone.extend(Tone.TimelineSignal, Tone.Param); 5008 Tone.TimelineSignal.Type = { 5009 Linear: 'linear', 5010 Exponential: 'exponential', 5011 Target: 'target', 5012 Set: 'set' 5013 }; 5014 Object.defineProperty(Tone.TimelineSignal.prototype, 'value', { 5015 get: function () { 5016 return this._toUnits(this._param.value); 5017 }, 5018 set: function (value) { 5019 var convertedVal = this._fromUnits(value); 5020 this._initial = convertedVal; 5021 this._param.value = convertedVal; 5022 } 5023 }); 5024 Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) { 5025 value = this._fromUnits(value); 5026 startTime = this.toSeconds(startTime); 5027 this._events.addEvent({ 5028 'type': Tone.TimelineSignal.Type.Set, 5029 'value': value, 5030 'time': startTime 5031 }); 5032 this._param.setValueAtTime(value, startTime); 5033 return this; 5034 }; 5035 Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) { 5036 value = this._fromUnits(value); 5037 endTime = this.toSeconds(endTime); 5038 this._events.addEvent({ 5039 'type': Tone.TimelineSignal.Type.Linear, 5040 'value': value, 5041 'time': endTime 5042 }); 5043 this._param.linearRampToValueAtTime(value, endTime); 5044 return this; 5045 };
5046 Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { 5047 value = this._fromUnits(value); 5048 value = Math.max(this._minOutput, value); 5049 endTime = this.toSeconds(endTime); 5050 this._events.addEvent({ 5051 'type': Tone.TimelineSignal.Type.Exponential, 5052 'value': value, 5053 'time': endTime 5054 }); 5055 this._param.exponentialRampToValueAtTime(value, endTime); 5056 return this; 5057 }; 5058 Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { 5059 value = this._fromUnits(value); 5060 value = Math.max(this._minOutput, value); 5061 timeConstant = Math.max(this._minOutput, timeConstant); 5062 startTime = this.toSeconds(startTime); 5063 this._events.addEvent({ 5064 'type': Tone.TimelineSignal.Type.Target, 5065 'value': value, 5066 'time': startTime, 5067 'constant': timeConstant 5068 }); 5069 this._param.setTargetAtTime(value, startTime, timeConstant); 5070 return this; 5071 }; 5072 Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) { 5073 this._events.cancel(after); 5074 this._param.cancelScheduledValues(this.toSeconds(after)); 5075 return this; 5076 }; 5077 Tone.TimelineSignal.prototype.setRampPoint = function (time) { 5078 time = this.toSeconds(time); 5079 var val = this.getValueAtTime(time); 5080 var after = this._searchAfter(time); 5081 if (after) { 5082 this.cancelScheduledValues(time); 5083 if (after.type === Tone.TimelineSignal.Type.Linear) { 5084 this.linearRampToValueAtTime(val, time); 5085 } else if (after.type === Tone.TimelineSignal.Type.Exponential) { 5086 this.exponentialRampToValueAtTime(val, time); 5087 } 5088 } 5089 this.setValueAtTime(val, time); 5090 return this; 5091 }; 5092 Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) { 5093 this.setRampPoint(start); 5094 this.linearRampToValueAtTime(value, finish); 5095 return this; 5096 }; 5097 Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) { 5098 this.setRampPoint(start); 5099 this.exponentialRampToValueAtTime(value, finish); 5100 return this; 5101 }; 5102 Tone.TimelineSignal.prototype._searchBefore = function (time) { 5103 return this._events.getEvent(time); 5104 }; 5105 Tone.TimelineSignal.prototype._searchAfter = function (time) { 5106 return this._events.getEventAfter(time); 5107 }; 5108 Tone.TimelineSignal.prototype.getValueAtTime = function (time) { 5109 var after = this._searchAfter(time); 5110 var before = this._searchBefore(time); 5111 var value = this._initial; 5112 if (before === null) { 5113 value = this._initial; 5114 } else if (before.type === Tone.TimelineSignal.Type.Target) { 5115 var previous = this._events.getEventBefore(before.time); 5116 var previouVal; 5117 if (previous === null) { 5118 previouVal = this._initial; 5119 } else { 5120 previouVal = previous.value; 5121 } 5122 value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); 5123 } else if (after === null) { 5124 value = before.value; 5125 } else if (after.type === Tone.TimelineSignal.Type.Linear) { 5126 value = this._linearInterpolate(before.time, before.value, after.time, after.value, time); 5127 } else if (after.type === Tone.TimelineSignal.Type.Exponential) { 5128 value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time); 5129 } else { 5130 value = before.value; 5131 } 5132 return value; 5133 }; 5134 Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect; 5135 Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { 5136 return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); 5137 }; 5138 Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { 5139 return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); 5140 }; 5141 Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { 5142 v0 = Math.max(this._minOutput, v0); 5143 return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); 5144 }; 5145 Tone.TimelineSignal.prototype.dispose = function () { 5146 Tone.Signal.prototype.dispose.call(this); 5147 Tone.Param.prototype.dispose.call(this); 5148 this._events.dispose(); 5149 this._events = null; 5150 }; 5151 return Tone.TimelineSignal; 5152}(Tone_core_Tone, Tone_signal_Signal); 5153var env; 5154env = function () { 5155 'use strict'; 5156 var p5sound = master; 5157 var Add = Tone_signal_Add; 5158 var Mult = Tone_signal_Multiply; 5159 var Scale = Tone_signal_Scale; 5160 var TimelineSignal = Tone_signal_TimelineSignal; 5161 var Tone = Tone_core_Tone; 5162 Tone.setContext(p5sound.audiocontext); 5163 /** 5164 * <p>Envelopes are pre-defined amplitude distribution over time. 5165 * Typically, envelopes are used to control the output volume 5166 * of an object, a series of fades referred to as Attack, Decay,
5167 * Sustain and Release ( 5168 * <a href="https://upload.wikimedia.org/wikipedia/commons/e/ea/ADSR_parameter.svg">ADSR</a> 5169 * ). Envelopes can also control other Web Audio Parameters—for example, a p5.Env can 5170 * control an Oscillator's frequency like this: <code>osc.freq(env)</code>.</p> 5171 * <p>Use <code><a href="#/p5.Env/setRange">setRange</a></code> to change the attack/release level. 5172 * Use <code><a href="#/p5.Env/setADSR">setADSR</a></code> to change attackTime, decayTime, sustainPercent and releaseTime.</p> 5173 * <p>Use the <code><a href="#/p5.Env/play">play</a></code> method to play the entire envelope, 5174 * the <code><a href="#/p5.Env/ramp">ramp</a></code> method for a pingable trigger, 5175 * or <code><a href="#/p5.Env/triggerAttack">triggerAttack</a></code>/ 5176 * <code><a href="#/p5.Env/triggerRelease">triggerRelease</a></code> to trigger noteOn/noteOff.</p> 5177 * 5178 * @class p5.Env 5179 * @constructor 5180 * @example 5181 * <div><code> 5182 * var attackLevel = 1.0; 5183 * var releaseLevel = 0; 5184 * 5185 * var attackTime = 0.001 5186 * var decayTime = 0.2; 5187 * var susPercent = 0.2; 5188 * var releaseTime = 0.5; 5189 * 5190 * var env, triOsc; 5191 * 5192 * function setup() { 5193 * var cnv = createCanvas(100, 100); 5194 * 5195 * textAlign(CENTER); 5196 * text('click to play', width/2, height/2); 5197 * 5198 * env = new p5.Env(); 5199 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5200 * env.setRange(attackLevel, releaseLevel); 5201 * 5202 * triOsc = new p5.Oscillator('triangle'); 5203 * triOsc.amp(env); 5204 * triOsc.start(); 5205 * triOsc.freq(220); 5206 * 5207 * cnv.mousePressed(playEnv); 5208 * } 5209 * 5210 * function playEnv(){ 5211 * env.play(); 5212 * } 5213 * </code></div> 5214 */ 5215 p5.Env = function (t1, l1, t2, l2, t3, l3) { 5216 var now = p5sound.audiocontext.currentTime; 5217 /** 5218 * Time until envelope reaches attackLevel 5219 * @property attackTime 5220 */ 5221 this.aTime = t1 || 0.1; 5222 /** 5223 * Level once attack is complete. 5224 * @property attackLevel 5225 */ 5226 this.aLevel = l1 || 1; 5227 /** 5228 * Time until envelope reaches decayLevel. 5229 * @property decayTime 5230 */ 5231 this.dTime = t2 || 0.5; 5232 /** 5233 * Level after decay. The envelope will sustain here until it is released. 5234 * @property decayLevel 5235 */ 5236 this.dLevel = l2 || 0; 5237 /** 5238 * Duration of the release portion of the envelope. 5239 * @property releaseTime 5240 */ 5241 this.rTime = t3 || 0; 5242 /** 5243 * Level at the end of the release. 5244 * @property releaseLevel 5245 */ 5246 this.rLevel = l3 || 0; 5247 this._rampHighPercentage = 0.98; 5248 this._rampLowPercentage = 0.02; 5249 this.output = p5sound.audiocontext.createGain(); 5250 this.control = new TimelineSignal(); 5251 this._init(); 5252 // this makes sure the envelope starts at zero 5253 this.control.connect(this.output); 5254 // connect to the output 5255 this.connection = null; 5256 // store connection 5257 //array of math operation signal chaining 5258 this.mathOps = [this.control]; 5259 //whether envelope should be linear or exponential curve 5260 this.isExponential = false; 5261 // oscillator or buffer source to clear on env complete 5262 // to save resources if/when it is retriggered 5263 this.sourceToClear = null; 5264 // set to true if attack is set, then false on release 5265 this.wasTriggered = false; 5266 // add to the soundArray so we can dispose of the env later 5267 p5sound.soundArray.push(this); 5268 }; 5269 // this init function just smooths the starting value to zero and gives a start point for the timeline 5270 // - it was necessary to remove glitches at the beginning. 5271 p5.Env.prototype._init = function () { 5272 var now = p5sound.audiocontext.currentTime; 5273 var t = now; 5274 this.control.setTargetAtTime(0.00001, t, 0.001); 5275 //also, compute the correct time constants 5276 this._setRampAD(this.aTime, this.dTime); 5277 }; 5278 /** 5279 * Reset the envelope with a series of time/value pairs. 5280 * 5281 * @method set 5282 * @param {Number} attackTime Time (in seconds) before level 5283 * reaches attackLevel 5284 * @param {Number} attackLevel Typically an amplitude between 5285 * 0.0 and 1.0 5286 * @param {Number} decayTime Time 5287 * @param {Number} decayLevel Amplitude (In a standard ADSR envelope, 5288 * decayLevel = sustainLevel) 5289 * @param {Number} releaseTime Release Time (in seconds) 5290 * @param {Number} releaseLevel Amplitude 5291 */ 5292 p5.Env.prototype.set = function (t1, l1, t2, l2, t3, l3) { 5293 this.aTime = t1; 5294 this.aLevel = l1; 5295 this.dTime = t2 || 0; 5296 this.dLevel = l2 || 0; 5297 this.rTime = t4 || 0; 5298 this.rLevel = l4 || 0; 5299 // set time constants for ramp 5300 this._setRampAD(t1, t2); 5301 }; 5302 /** 5303 * Set values like a traditional 5304 * <a href="https://en.wikipedia.org/wiki/Synthesizer#/media/File:ADSR_parameter.svg"> 5305 * ADSR envelope 5306 * </a>. 5307 * 5308 * @method setADSR 5309 * @param {Number} attackTime Time (in seconds before envelope 5310 * reaches Attack Level 5311 * @param {Number} [decayTime] Time (in seconds) before envelope 5312 * reaches Decay/Sustain Level 5313 * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, 5314 * where 1.0 = attackLevel, 0.0 = releaseLevel. 5315 * The susRatio determines the decayLevel and the level at which the 5316 * sustain portion of the envelope will sustain. 5317 * For example, if attackLevel is 0.4, releaseLevel is 0, 5318 * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is 5319 * increased to 1.0 (using <code>setRange</code>), 5320 * then decayLevel would increase proportionally, to become 0.5. 5321 * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) 5322 * @example 5323 * <div><code> 5324 * var attackLevel = 1.0; 5325 * var releaseLevel = 0; 5326 * 5327 * var attackTime = 0.001 5328 * var decayTime = 0.2; 5329 * var susPercent = 0.2; 5330 * var releaseTime = 0.5; 5331 * 5332 * var env, triOsc; 5333 * 5334 * function setup() { 5335 * var cnv = createCanvas(100, 100); 5336 * 5337 * textAlign(CENTER); 5338 * text('click to play', width/2, height/2); 5339 * 5340 * env = new p5.Env(); 5341 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5342 * env.setRange(attackLevel, releaseLevel); 5343 * 5344 * triOsc = new p5.Oscillator('triangle'); 5345 * triOsc.amp(env); 5346 * triOsc.start(); 5347 * triOsc.freq(220); 5348 * 5349 * cnv.mousePressed(playEnv); 5350 * } 5351 * 5352 * function playEnv(){ 5353 * env.play(); 5354 * } 5355 * </code></div> 5356 */ 5357 p5.Env.prototype.setADSR = function (aTime, dTime, sPercent, rTime) { 5358 this.aTime = aTime; 5359 this.dTime = dTime || 0; 5360 // lerp 5361 this.sPercent = sPercent || 0; 5362 this.dLevel = typeof sPercent !== 'undefined' ? sPercent * (this.aLevel - this.rLevel) + this.rLevel : 0; 5363 this.rTime = rTime || 0; 5364 // also set time constants for ramp 5365 this._setRampAD(aTime, dTime); 5366 }; 5367 /** 5368 * Set max (attackLevel) and min (releaseLevel) of envelope. 5369 * 5370 * @method setRange 5371 * @param {Number} aLevel attack level (defaults to 1) 5372 * @param {Number} rLevel release level (defaults to 0) 5373 * @example 5374 * <div><code> 5375 * var attackLevel = 1.0; 5376 * var releaseLevel = 0; 5377 * 5378 * var attackTime = 0.001 5379 * var decayTime = 0.2; 5380 * var susPercent = 0.2; 5381 * var releaseTime = 0.5; 5382 * 5383 * var env, triOsc; 5384 * 5385 * function setup() { 5386 * var cnv = createCanvas(100, 100); 5387 * 5388 * textAlign(CENTER); 5389 * text('click to play', width/2, height/2); 5390 * 5391 * env = new p5.Env(); 5392 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5393 * env.setRange(attackLevel, releaseLevel); 5394 * 5395 * triOsc = new p5.Oscillator('triangle'); 5396 * triOsc.amp(env); 5397 * triOsc.start(); 5398 * triOsc.freq(220); 5399 * 5400 * cnv.mousePressed(playEnv); 5401 * } 5402 * 5403 * function playEnv(){ 5404 * env.play(); 5405 * } 5406 * </code></div> 5407 */ 5408 p5.Env.prototype.setRange = function (aLevel, rLevel) { 5409 this.aLevel = aLevel || 1; 5410 this.rLevel = rLevel || 0; 5411 }; 5412 // private (undocumented) method called when ADSR is set to set time constants for ramp 5413 // 5414 // Set the <a href="https://en.wikipedia.org/wiki/RC_time_constant"> 5415 // time constants</a> for simple exponential ramps. 5416 // The larger the time constant value, the slower the 5417 // transition will be. 5418 // 5419 // method _setRampAD 5420 // param {Number}
5420 attackTimeConstant attack time constant 5421 // param {Number} decayTimeConstant decay time constant 5422 // 5423 p5.Env.prototype._setRampAD = function (t1, t2) { 5424 this._rampAttackTime = this.checkExpInput(t1); 5425 this._rampDecayTime = this.checkExpInput(t2); 5426 var TCDenominator = 1; 5427 /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) 5428 TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); 5429 this._rampAttackTC = t1 / this.checkExpInput(TCDenominator); 5430 TCDenominator = Math.log(1 / this._rampLowPercentage); 5431 this._rampDecayTC = t2 / this.checkExpInput(TCDenominator); 5432 }; 5433 // private method 5434 p5.Env.prototype.setRampPercentages = function (p1, p2) { 5435 //set the percentages that the simple exponential ramps go to 5436 this._rampHighPercentage = this.checkExpInput(p1); 5437 this._rampLowPercentage = this.checkExpInput(p2); 5438 var TCDenominator = 1; 5439 //now re-compute the time constants based on those percentages 5440 /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) 5441 TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); 5442 this._rampAttackTC = this._rampAttackTime / this.checkExpInput(TCDenominator); 5443 TCDenominator = Math.log(1 / this._rampLowPercentage); 5444 this._rampDecayTC = this._rampDecayTime / this.checkExpInput(TCDenominator); 5445 }; 5446 /** 5447 * Assign a parameter to be controlled by this envelope. 5448 * If a p5.Sound object is given, then the p5.Env will control its 5449 * output gain. If multiple inputs are provided, the env will 5450 * control all of them. 5451 * 5452 * @method setInput 5453 * @param {Object} unit A p5.sound object or 5454 * Web Audio Param. 5455 */ 5456 p5.Env.prototype.setInput = function (unit) { 5457 for (var i = 0; i < arguments.length; i++) { 5458 this.connect(arguments[i]); 5459 } 5460 }; 5461 /** 5462 * Set whether the envelope ramp is linear (default) or exponential. 5463 * Exponential ramps can be useful because we perceive amplitude 5464 * and frequency logarithmically. 5465 * 5466 * @method setExp 5467 * @param {Boolean} isExp true is exponential, false is linear 5468 */ 5469 p5.Env.prototype.setExp = function (isExp) { 5470 this.isExponential = isExp; 5471 }; 5472 //helper method to protect against zero values being sent to exponential functions 5473 p5.Env.prototype.checkExpInput = function (value) { 5474 if (value <= 0) { 5475 value = 0.0001; 5476 } 5477 return value; 5478 }; 5479 /** 5480 * Play tells the envelope to start acting on a given input. 5481 * If the input is a p5.sound object (i.e. AudioIn, Oscillator, 5482 * SoundFile), then Env will control its output volume. 5483 * Envelopes can also be used to control any <a href=" 5484 * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> 5485 * Web Audio Audio Param.</a> 5486 * 5487 * @method play 5488 * @param {Object} unit A p5.sound object or 5489 * Web Audio Param. 5490 * @param {Number} [startTime] time from now (in seconds) at which to play 5491 * @param {Number} [sustainTime] time to sustain before releasing the envelope 5492 * @example 5493 * <div><code> 5494 * var attackLevel = 1.0; 5495 * var releaseLevel = 0; 5496 * 5497 * var attackTime = 0.001 5498 * var decayTime = 0.2; 5499 * var susPercent = 0.2; 5500 * var releaseTime = 0.5; 5501 * 5502 * var env, triOsc; 5503 * 5504 * function setup() { 5505 * var cnv = createCanvas(100, 100); 5506 * 5507 * textAlign(CENTER); 5508 * text('click to play', width/2, height/2); 5509 * 5510 * env = new p5.Env(); 5511 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5512 * env.setRange(attackLevel, releaseLevel); 5513 * 5514 * triOsc = new p5.Oscillator('triangle'); 5515 * triOsc.amp(env); 5516 * triOsc.start(); 5517 * triOsc.freq(220); 5518 * 5519 * cnv.mousePressed(playEnv); 5520 * } 5521 * 5522 * function playEnv(){ 5523 * // trigger env on triOsc, 0 seconds from now 5524 * // After decay, sustain for 0.2 seconds before release 5525 * env.play(triOsc, 0, 0.2); 5526 * } 5527 * </code></div> 5528 */ 5529 p5.Env.prototype.play = function (unit, secondsFromNow, susTime) { 5530 var now = p5sound.audiocontext.currentTime; 5531 var tFromNow = secondsFromNow || 0; 5532 var susTime = susTime || 0; 5533 if (unit) { 5534 if (this.connection !== unit) { 5535 this.connect(unit); 5536 } 5537 } 5538 this.triggerAttack(unit, tFromNow); 5539 this.triggerRelease(unit, tFromNow + this.aTime + this.dTime + susTime); 5540 }; 5541 /** 5542 * Trigger the Attack, and Decay portion of the Envelope. 5543 * Similar to holding down a key on a piano, but it will 5544 * hold the sustain level until you let go. Input can be 5545 * any p5.sound object, or a <a href=" 5546 * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> 5547 * Web Audio Param</a>. 5548 * 5549 * @method triggerAttack 5550 * @param {Object} unit p5.sound Object or Web Audio Param 5551 * @param {Number} secondsFromNow time from now (in seconds) 5552 * @example 5553 * <div><code> 5554 * 5555 * var attackLevel = 1.0; 5556 * var releaseLevel = 0; 5557 * 5558 * var attackTime = 0.001 5559 * var decayTime = 0.3; 5560 * var susPercent = 0.4; 5561 * var releaseTime = 0.5; 5562 * 5563 * var env, triOsc; 5564 * 5565 * function setup() { 5566 * var cnv = createCanvas(100, 100); 5567 * background(200); 5568 * textAlign(CENTER); 5569 * text('click to play', width/2, height/2); 5570 * 5571 * env = new p5.Env(); 5572 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5573 * env.setRange(attackLevel, releaseLevel); 5574 * 5575 * triOsc = new p5.Oscillator('triangle'); 5576 * triOsc.amp(env); 5577 * triOsc.start(); 5578 * triOsc.freq(220); 5579 * 5580 * cnv.mousePressed(envAttack); 5581 * } 5582 * 5583 * function envAttack(){ 5584 * console.log('trigger attack'); 5585 * env.triggerAttack(); 5586 * 5587 * background(0,255,0); 5588 * text('attack!', width/2, height/2); 5589 * } 5590 * 5591 * function mouseReleased() { 5592 * env.triggerRelease(); 5593 * 5594 * background(200); 5595 * text('click to play', width/2, height/2); 5596 * } 5597 * </code></div> 5598 */ 5599 p5.Env.prototype.triggerAttack = function (unit, secondsFromNow) { 5600 var now = p5sound.audiocontext.currentTime; 5601 var tFromNow = secondsFromNow || 0; 5602 var t = now + tFromNow; 5603 this.lastAttack = t; 5604 this.wasTriggered = true; 5605 if (unit) { 5606 if (this.connection !== unit) { 5607 this.connect(unit); 5608 } 5609 } 5610 // get and set value (with linear ramp) to anchor automation 5611 var valToSet = this.control.getValueAtTime(t); 5612 this.control.cancelScheduledValues(t); 5613 // not sure if this is necessary 5614 if (this.isExponential == true) { 5615 this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); 5616 } else { 5617 this.control.linearRampToValueAtTime(valToSet, t); 5618 } 5619 // after each ramp completes, cancel scheduled values 5620 // (so they can be overridden in case env has been re-triggered) 5621 // then, set current value (with linearRamp to avoid click) 5622 // then, schedule the next automation... 5623 // attack 5624 t += this.aTime; 5625 if (this.isExponential == true) { 5626 this.control.exponentialRampToValueAtTime(this.checkExpInput(this.aLevel), t); 5627 valToSet = this.checkExpInput(this.control.getValueAtTime(t)); 5628 this.control.cancelScheduledValues(t); 5629 this.control.exponentialRampToValueAtTime(valToSet, t); 5630 } else { 5631 this.control.linearRampToValueAtTime(this.aLevel, t); 5632 valToSet = this.control.getValueAtTime(t); 5633 this.control.cancelScheduledValues(t); 5634 this.control.linearRampToValueAtTime(valToSet, t); 5635 } 5636 // decay to decay level (if using ADSR, then decay level == sustain level) 5637 t += this.dTime; 5638 if (this.isExponential == true) { 5639 this.control.exponentialRampToValueAtTime(this.checkExpInput(this.dLevel), t); 5640 valToSet = this.checkExpInput(this.control.getValueAtTime(t)); 5641 this.control.cancelScheduledValues(t); 5642 this.control.exponentialRampToValueAtTime(valToSet, t); 5643 } else { 5644 this.control.linearRampToValueAtTime(this.dLevel, t); 5645 valToSet = this.control.getValueAtTime(t); 5646 this.control.cancelScheduledValues(t); 5647 this.control.linearRampToValueAtTime(valToSet, t); 5648 } 5649 }; 5650 /** 5651 * Trigger the Release of the Envelope. This is similar to releasing 5652 * the key on a piano and letting the sound fade according to the 5653 * release level and release time. 5654 * 5655 * @method triggerRelease 5656 * @param {Object} unit p5.sound Object or Web Audio Param 5657 * @param {Number} secondsFromNow time to trigger the release 5658 * @example 5659 * <div><code> 5660 * 5661 * var attackLevel = 1.0; 5662 * var releaseLevel = 0; 5663 * 5664 * var attackTime = 0.001 5665 * var decayTime = 0.3; 5666 * var susPercent = 0.4; 5667 * var releaseTime = 0.5; 5668 * 5669 * var env, triOsc; 5670 * 5671 * function setup() { 5672 * var cnv = createCanvas(100, 100); 5673 * background(200); 5674 * textAlign(CENTER); 5675 * text('click to play', width/2, height/2); 5676 * 5677 * env = new p5.Env(); 5678 * env.setADSR(attackTime, decayTime, susPercent, releaseTime); 5679 * env.setRange(attackLevel, releaseLevel); 5680 * 5681 * triOsc = new p5.Oscillator('triangle'); 5682 * triOsc.amp(env); 5683 * triOsc.start(); 5684 * triOsc.freq(220); 5685 * 5686 * cnv.mousePressed(envAttack); 5687 * } 5688 * 5689 * function envAttack(){ 5690 * console.log('trigger attack'); 5691 * env.triggerAttack(); 5692 * 5693 * background(0,255,0); 5694 * text('attack!', width/2, height/2); 5695 * } 5696 * 5697 * function mouseReleased() { 5698 * env.triggerRelease(); 5699 * 5700 * background(200); 5701 * text('click to play', width/2, height/2); 5702 * } 5703 * </code></div> 5704 */ 5705 p5.Env.prototype.triggerRelease = function (unit, secondsFromNow) { 5706 // only trigger a release if an attack was triggered 5707 if (!this.wasTriggered) { 5708 // this currently causes a bit of trouble: 5709 // if a later release has been scheduled (via the play function) 5710 // a new earlier release won't interrupt it, because 5711 // this.wasTriggered has already been set to false. 5712 // If we want new earlier releases to override, then we need to 5713 // keep track of the last release time, and if the new release time is 5714 // earlier, then use it. 5715 return; 5716 } 5717 var now = p5sound.audiocontext.currentTime; 5718 var tFromNow = secondsFromNow || 0; 5719 var t = now + tFromNow; 5720 if (unit) { 5721 if (this.connection !== unit) { 5722 this.connect(unit); 5723 } 5724 } 5725 // get and set value (with linear or exponential ramp) to anchor automation 5726 var valToSet = this.control.getValueAtTime(t); 5727 this.control.cancelScheduledValues(t); 5728 // not sure if this is necessary 5729 if (this.isExponential == true) { 5730 this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); 5731 } else { 5732 this.control.linearRampToValueAtTime(valToSet, t); 5733 } 5734 // release 5735 t += this.rTime; 5736 if (this.isExponential == true) { 5737 this.control.exponentialRampToValueAtTime(this.checkExpInput(this.rLevel), t); 5738 valToSet = this.checkExpInput(this.control.getValueAtTime(t)); 5739 this.control.cancelScheduledValues(t); 5740 this.control.exponentialRampToValueAtTime(valToSet, t); 5741 } else { 5742 this.control.linearRampToValueAtTime(this.rLevel, t); 5743 valToSet = this.control.getValueAtTime(t); 5744 this.control.cancelScheduledValues(t); 5745 this.control.linearRampToValueAtTime(valToSet, t); 5746 } 5747 this.wasTriggered = false;
5748 }; 5749 /** 5750 * Exponentially ramp to a value using the first two 5751 * values from <code><a href="#/p5.Env/setADSR">setADSR(attackTime, decayTime)</a></code> 5752 * as <a href="https://en.wikipedia.org/wiki/RC_time_constant"> 5753 * time constants</a> for simple exponential ramps. 5754 * If the value is higher than current value, it uses attackTime, 5755 * while a decrease uses decayTime. 5756 * 5757 * @method ramp 5758 * @param {Object} unit p5.sound Object or Web Audio Param 5759 * @param {Number} secondsFromNow When to trigger the ramp 5760 * @param {Number} v Target value 5761 * @param {Number} [v2] Second target value (optional) 5762 * @example 5763 * <div><code> 5764 * var env, osc, amp, cnv; 5765 * 5766 * var attackTime = 0.001; 5767 * var decayTime = 0.2; 5768 * var attackLevel = 1; 5769 * var decayLevel = 0; 5770 * 5771 * function setup() { 5772 * cnv = createCanvas(100, 100); 5773 * fill(0,255,0); 5774 * noStroke(); 5775 * 5776 * env = new p5.Env(); 5777 * env.setADSR(attackTime, decayTime); 5778 * 5779 * osc = new p5.Oscillator(); 5780 * osc.amp(env); 5781 * osc.start(); 5782 * 5783 * amp = new p5.Amplitude(); 5784 * 5785 * cnv.mousePressed(triggerRamp); 5786 * } 5787 * 5788 * function triggerRamp() { 5789 * env.ramp(osc, 0, attackLevel, decayLevel); 5790 * } 5791 * 5792 * function draw() { 5793 * background(20,20,20); 5794 * text('click me', 10, 20); 5795 * var h = map(amp.getLevel(), 0, 0.4, 0, height);; 5796 * 5797 * rect(0, height, width, -h); 5798 * } 5799 * </code></div> 5800 */ 5801 p5.Env.prototype.ramp = function (unit, secondsFromNow, v1, v2) { 5802 var now = p5sound.audiocontext.currentTime; 5803 var tFromNow = secondsFromNow || 0; 5804 var t = now + tFromNow; 5805 var destination1 = this.checkExpInput(v1); 5806 var destination2 = typeof v2 !== 'undefined' ? this.checkExpInput(v2) : undefined; 5807 // connect env to unit if not already connected 5808 if (unit) { 5809 if (this.connection !== unit) { 5810 this.connect(unit); 5811 } 5812 } 5813 //get current value 5814 var currentVal = this.checkExpInput(this.control.getValueAtTime(t)); 5815 this.control.cancelScheduledValues(t); 5816 //if it's going up 5817 if (destination1 > currentVal) { 5818 this.control.setTargetAtTime(destination1, t, this._rampAttackTC); 5819 t += this._rampAttackTime; 5820 } else if (destination1 < currentVal) { 5821 this.control.setTargetAtTime(destination1, t, this._rampDecayTC); 5822 t += this._rampDecayTime; 5823 } 5824 // Now the second part of envelope begins 5825 if (destination2 === undefined) 5826 return; 5827 //if it's going up 5828 if (destination2 > destination1) { 5829 this.control.setTargetAtTime(destination2, t, this._rampAttackTC); 5830 } else if (destination2 < destination1) { 5831 this.control.setTargetAtTime(destination2, t, this._rampDecayTC); 5832 } 5833 }; 5834 p5.Env.prototype.connect = function (unit) { 5835 this.connection = unit; 5836 // assume we're talking about output gain 5837 // unless given a different audio param 5838 if (unit instanceof p5.Oscillator || unit instanceof p5.SoundFile || unit instanceof p5.AudioIn || unit instanceof p5.Reverb || unit instanceof p5.Noise || unit instanceof p5.Filter || unit instanceof p5.Delay) { 5839 unit = unit.output.gain; 5840 } 5841 if (unit instanceof AudioParam) { 5842 //set the initial value 5843 unit.setValueAtTime(0, p5sound.audiocontext.currentTime); 5844 } 5845 if (unit instanceof p5.Signal) { 5846 unit.setValue(0); 5847 } 5848 this.output.connect(unit); 5849 }; 5850 p5.Env.prototype.disconnect = function (unit) { 5851 this.output.disconnect(); 5852 }; 5853 // Signal Math 5854 /** 5855 * Add a value to the p5.Oscillator's output amplitude, 5856 * and return the oscillator. Calling this method 5857 * again will override the initial add() with new values. 5858 * 5859 * @method add 5860 * @param {Number} number Constant number to add 5861 * @return {p5.Env} Envelope Returns this envelope 5862 * with scaled output 5863 */ 5864 p5.Env.prototype.add = function (num) { 5865 var add = new Add(num); 5866 var thisChain = this.mathOps.length; 5867 var nextChain = this.output; 5868 return p5.prototype._mathChain(this, add, thisChain, nextChain, Add); 5869 }; 5870 /** 5871 * Multiply the p5.Env's output amplitude 5872 * by a fixed value. Calling this method 5873 * again will override the initial mult() with new values. 5874 * 5875 * @method mult 5876 * @param {Number}
5876 number Constant number to multiply 5877 * @return {p5.Env} Envelope Returns this envelope 5878 * with scaled output 5879 */ 5880 p5.Env.prototype.mult = function (num) { 5881 var mult = new Mult(num); 5882 var thisChain = this.mathOps.length; 5883 var nextChain = this.output; 5884 return p5.prototype._mathChain(this, mult, thisChain, nextChain, Mult); 5885 }; 5886 /** 5887 * Scale this envelope's amplitude values to a given 5888 * range, and return the envelope. Calling this method 5889 * again will override the initial scale() with new values. 5890 * 5891 * @method scale 5892 * @param {Number} inMin input range minumum 5893 * @param {Number} inMax input range maximum 5894 * @param {Number} outMin input range minumum 5895 * @param {Number} outMax input range maximum 5896 * @return {p5.Env} Envelope Returns this envelope 5897 * with scaled output 5898 */ 5899 p5.Env.prototype.scale = function (inMin, inMax, outMin, outMax) { 5900 var scale = new Scale(inMin, inMax, outMin, outMax); 5901 var thisChain = this.mathOps.length; 5902 var nextChain = this.output; 5903 return p5.prototype._mathChain(this, scale, thisChain, nextChain, Scale); 5904 }; 5905 // get rid of the oscillator 5906 p5.Env.prototype.dispose = function () { 5907 // remove reference from soundArray 5908 var index = p5sound.soundArray.indexOf(this); 5909 p5sound.soundArray.splice(index, 1); 5910 var now = p5sound.audiocontext.currentTime; 5911 this.disconnect(); 5912 try { 5913 this.control.dispose(); 5914 this.control = null; 5915 } catch (e) { 5916 } 5917 for (var i = 1; i < this.mathOps.length; i++) { 5918 mathOps[i].dispose(); 5919 } 5920 }; 5921}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_signal_TimelineSignal, Tone_core_Tone); 5922var pulse; 5923pulse = function () { 5924 'use strict'; 5925 var p5sound = master; 5926 /** 5927 * Creates a Pulse object, an oscillator that implements 5928 * Pulse Width Modulation. 5929 * The pulse is created with two oscillators. 5930 * Accepts a parameter for frequency, and to set the 5931 * width between the pulses. See <a href=" 5932 * http://p5js.org/reference/#/p5.Oscillator"> 5933 * <code>p5.Oscillator</code> for a full list of methods. 5934 * 5935 * @class p5.Pulse 5936 * @constructor 5937 * @param {Number} [freq] Frequency in oscillations per second (Hz) 5938 * @param {Number} [w] Width between the pulses (0 to 1.0, 5939 * defaults to 0) 5940 * @example 5941 * <div><code> 5942 * var pulse; 5943 * function setup() { 5944 * background(0); 5945 * 5946 * // Create and start the pulse wave oscillator 5947 * pulse = new p5.Pulse(); 5948 * pulse.amp(0.5); 5949 * pulse.freq(220); 5950 * pulse.start(); 5951 * } 5952 * 5953 * function draw() { 5954 * var w = map(mouseX, 0, width, 0, 1); 5955 * w = constrain(w, 0, 1); 5956 * pulse.width(w) 5957 * } 5958 * </code></div> 5959 */ 5960 p5.Pulse = function (freq, w) { 5961 p5.Oscillator.call(this, freq, 'sawtooth'); 5962 // width of PWM, should be betw 0 to 1.0 5963 this.w = w || 0; 5964 // create a second oscillator with inverse frequency 5965 this.osc2 = new p5.SawOsc(freq); 5966 // create a delay node 5967 this.dNode = p5sound.audiocontext.createDelay(); 5968 // dc offset 5969 this.dcOffset = createDCOffset(); 5970 this.dcGain = p5sound.audiocontext.createGain(); 5971 this.dcOffset.connect(this.dcGain); 5972 this.dcGain.connect(this.output); 5973 // set delay time based on PWM width 5974 this.f = freq || 440; 5975 var mW = this.w / this.oscillator.frequency.value; 5976 this.dNode.delayTime.value = mW; 5977 this.dcGain.gain.value = 1.7 * (0.5 - this.w); 5978 // disconnect osc2 and connect it to delay, which is connected to output 5979 this.osc2.disconnect(); 5980 this.osc2.panner.disconnect(); 5981 this.osc2.amp(-1); 5982 // inverted amplitude 5983 this.osc2.output.connect(this.dNode); 5984 this.dNode.connect(this.output); 5985 this.output.gain.value = 1; 5986 this.output.connect(this.panner); 5987 }; 5988 p5.Pulse.prototype = Object.create(p5.Oscillator.prototype); 5989 /** 5990 * Set the width of a Pulse object (an oscillator that implements 5991 * Pulse Width Modulation). 5992 * 5993 * @method width 5994 * @param {Number} [width] Width between the pulses (0 to 1.0, 5995 * defaults to 0) 5996 */ 5997 p5.Pulse.prototype.width = function (w) { 5998 if (typeof w === 'number') { 5999 if (w <= 1 && w >= 0) { 6000 this.w = w; 6001 // set delay time based on PWM width 6002 // var mW = map(this.w, 0, 1.0, 0, 1/this.f); 6003 var mW = this.w / this.oscillator.frequency.value; 6004 this.dNode.delayTime.value = mW; 6005 } 6006 this.dcGain.gain.value = 1.7 * (0.5 - this.w); 6007 } else { 6008 w.connect(this.dNode.delayTime); 6009 var sig = new p5.SignalAdd(-0.5); 6010 sig.setInput(w); 6011 sig = sig.mult(-1); 6012 sig = sig.mult(1.7); 6013 sig.connect(this.dcGain.gain); 6014 } 6015 }; 6016 p5.Pulse.prototype.start = function (f, time) { 6017 var now = p5sound.audiocontext.currentTime; 6018 var t = time || 0; 6019 if (!this.started) { 6020 var freq = f || this.f; 6021 var type = this.oscillator.type; 6022 this.oscillator = p5sound.audiocontext.createOscillator(); 6023 this.oscillator.frequency.setValueAtTime(freq, now); 6024 this.oscillator.type = type; 6025 this.oscillator.connect(this.output); 6026 this.oscillator.start(t + now); 6027 // set up osc2 6028 this.osc2.oscillator = p5sound.audiocontext.createOscillator(); 6029 this.osc2.oscillator.frequency.setValueAtTime(freq, t + now); 6030 this.osc2.oscillator.type = type; 6031 this.osc2.oscillator.connect(this.osc2.output); 6032 this.osc2.start(t + now); 6033 this.freqNode = [ 6034 this.oscillator.frequency, 6035 this.osc2.oscillator.frequency 6036 ]; 6037 // start dcOffset, too 6038 this.dcOffset = createDCOffset(); 6039 this.dcOffset.connect(this.dcGain); 6040 this.dcOffset.start(t + now); 6041 // if LFO connections depend on these oscillators 6042 if (this.mods !== undefined && this.mods.frequency !== undefined) { 6043 this.mods.frequency.connect(this.freqNode[0]); 6044 this.mods.frequency.connect(this.freqNode[1]); 6045 } 6046 this.started = true; 6047 this.osc2.started = true; 6048 } 6049 }; 6050 p5.Pulse.prototype.stop = function (time) { 6051 if (this.started) { 6052 var t = time || 0; 6053 var now = p5sound.audiocontext.currentTime; 6054 this.oscillator.stop(t + now); 6055 this.osc2.oscillator.stop(t + now); 6056 this.dcOffset.stop(t + now); 6057 this.started = false;
6058 this.osc2.started = false; 6059 } 6060 }; 6061 p5.Pulse.prototype.freq = function (val, rampTime, tFromNow) { 6062 if (typeof val === 'number') { 6063 this.f = val; 6064 var now = p5sound.audiocontext.currentTime; 6065 var rampTime = rampTime || 0; 6066 var tFromNow = tFromNow || 0; 6067 var currentFreq = this.oscillator.frequency.value; 6068 this.oscillator.frequency.cancelScheduledValues(now); 6069 this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); 6070 this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); 6071 this.osc2.oscillator.frequency.cancelScheduledValues(now); 6072 this.osc2.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); 6073 this.osc2.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); 6074 if (this.freqMod) { 6075 this.freqMod.output.disconnect(); 6076 this.freqMod = null; 6077 } 6078 } else if (val.output) { 6079 val.output.disconnect(); 6080 val.output.connect(this.oscillator.frequency); 6081 val.output.connect(this.osc2.oscillator.frequency); 6082 this.freqMod = val; 6083 } 6084 }; 6085 // inspiration: http://webaudiodemos.appspot.com/oscilloscope/ 6086 function createDCOffset() { 6087 var ac = p5sound.audiocontext; 6088 var buffer = ac.createBuffer(1, 2048, ac.sampleRate); 6089 var data = buffer.getChannelData(0); 6090 for (var i = 0; i < 2048; i++) 6091 data[i] = 1; 6092 var bufferSource = ac.createBufferSource(); 6093 bufferSource.buffer = buffer; 6094 bufferSource.loop = true; 6095 return bufferSource; 6096 } 6097}(master, oscillator); 6098var noise; 6099noise = function () { 6100 'use strict'; 6101 var p5sound = master; 6102 /** 6103 * Noise is a type of oscillator that generates a buffer with random values. 6104 * 6105 * @class p5.Noise 6106 * @constructor 6107 * @param {String} type Type of noise can be 'white' (default), 6108 * 'brown' or 'pink'. 6109 * @return {Object} Noise Object 6110 */ 6111 p5.Noise = function (type) { 6112 p5.Oscillator.call(this); 6113 delete this.f; 6114 delete this.freq; 6115 delete this.oscillator; 6116 this.buffer = _whiteNoise; 6117 }; 6118 p5.Noise.prototype = Object.create(p5.Oscillator.prototype); 6119 // generate noise buffers 6120 var _whiteNoise = function () { 6121 var bufferSize = 2 * p5sound.audiocontext.sampleRate; 6122 var whiteBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); 6123 var noiseData = whiteBuffer.getChannelData(0); 6124 for (var i = 0; i < bufferSize; i++) { 6125 noiseData[i] = Math.random() * 2 - 1; 6126 } 6127 whiteBuffer.type = 'white'; 6128 return whiteBuffer; 6129 }(); 6130 var _pinkNoise = function () { 6131 var bufferSize = 2 * p5sound.audiocontext.sampleRate; 6132 var pinkBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); 6133 var noiseData = pinkBuffer.getChannelData(0); 6134 var b0, b1, b2, b3, b4, b5, b6; 6135 b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; 6136 for (var i = 0; i < bufferSize; i++) { 6137 var white = Math.random() * 2 - 1; 6138 b0 = 0.99886 * b0 + white * 0.0555179; 6139 b1 = 0.99332 * b1 + white * 0.0750759; 6140 b2 = 0.969 * b2 + white * 0.153852; 6141 b3 = 0.8665 * b3 + white * 0.3104856; 6142 b4 = 0.55 * b4 + white * 0.5329522; 6143 b5 = -0.7616 * b5 - white * 0.016898; 6144 noiseData[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; 6145 noiseData[i] *= 0.11; 6146 // (roughly) compensate for gain 6147 b6 = white * 0.115926; 6148 } 6149 pinkBuffer.type = 'pink'; 6150 return pinkBuffer; 6151 }(); 6152 var _brownNoise = function () { 6153 var bufferSize = 2 * p5sound.audiocontext.sampleRate; 6154 var brownBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); 6155 var noiseData = brownBuffer.getChannelData(0); 6156 var lastOut = 0; 6157 for (var i = 0; i < bufferSize; i++) { 6158 var white = Math.random() * 2 - 1; 6159 noiseData[i] = (lastOut + 0.02 * white) / 1.02; 6160 lastOut = noiseData[i]; 6161 noiseData[i] *= 3.5; 6162 } 6163 brownBuffer.type = 'brown'; 6164 return brownBuffer; 6165 }(); 6166 /** 6167 * Set type of noise to 'white', 'pink' or 'brown'. 6168 * White is the default. 6169 * 6170 * @method setType 6171 * @param {String} [type] 'white', 'pink' or 'brown' 6172 */ 6173 p5.Noise.prototype.setType = function (type) { 6174 switch (type) { 6175 case 'white': 6176 this.buffer = _whiteNoise; 6177 break; 6178 case 'pink': 6179 this.buffer = _pinkNoise; 6180 break; 6181 case 'brown': 6182 this.buffer = _brownNoise; 6183 break; 6184 default: 6185 this.buffer = _whiteNoise; 6186 } 6187 if (this.started) { 6188 var now = p5sound.audiocontext.currentTime; 6189 this.stop(now); 6190 this.start(now + 0.01); 6191 } 6192 }; 6193 p5.Noise.prototype.getType = function () { 6194 return this.buffer.type; 6195 }; 6196 /** 6197 * Start the noise 6198 * 6199 * @method start 6200 */ 6201 p5.Noise.prototype.start = function () { 6202 if (this.started) { 6203 this.stop(); 6204 } 6205 this.noise = p5sound.audiocontext.createBufferSource(); 6206 this.noise.buffer = this.buffer; 6207 this.noise.loop = true; 6208 this.noise.connect(this.output); 6209 var now = p5sound.audiocontext.currentTime; 6210 this.noise.start(now); 6211 this.started = true; 6212 }; 6213 /** 6214 * Stop the noise. 6215 * 6216 * @method stop 6217 */ 6218 p5.Noise.prototype.stop = function () { 6219 var now = p5sound.audiocontext.currentTime; 6220 if (this.noise) { 6221 this.noise.stop(now); 6222 this.started = false;
6223 } 6224 }; 6225 /** 6226 * Pan the noise. 6227 * 6228 * @method pan 6229 * @param {Number} panning Number between -1 (left) 6230 * and 1 (right) 6231 * @param {Number} timeFromNow schedule this event to happen 6232 * seconds from now 6233 */ 6234 /** 6235 * Set the amplitude of the noise between 0 and 1.0. Or, 6236 * modulate amplitude with an audio signal such as an oscillator. 6237 * 6238 * @param {Number|Object} volume amplitude between 0 and 1.0 6239 * or modulating signal/oscillator 6240 * @param {Number} [rampTime] create a fade that lasts rampTime 6241 * @param {Number} [timeFromNow] schedule this event to happen 6242 * seconds from now 6243 */ 6244 /** 6245 * Send output to a p5.sound or web audio object 6246 * 6247 * @method connect 6248 * @param {Object} unit 6249 */ 6250 /** 6251 * Disconnect all output. 6252 * 6253 * @method disconnect 6254 */ 6255 p5.Noise.prototype.dispose = function () { 6256 var now = p5sound.audiocontext.currentTime; 6257 // remove reference from soundArray 6258 var index = p5sound.soundArray.indexOf(this); 6259 p5sound.soundArray.splice(index, 1); 6260 if (this.noise) { 6261 this.noise.disconnect(); 6262 this.stop(now); 6263 } 6264 if (this.output) { 6265 this.output.disconnect(); 6266 } 6267 if (this.panner) { 6268 this.panner.disconnect(); 6269 } 6270 this.output = null; 6271 this.panner = null; 6272 this.buffer = null; 6273 this.noise = null; 6274 }; 6275}(master); 6276var audioin; 6277audioin = function () { 6278 'use strict'; 6279 var p5sound = master; 6280 var CustomError = errorHandler; 6281 /** 6282 * <p>Get audio from an input, i.e. your computer's microphone.</p> 6283 * 6284 * <p>Turn the mic on/off with the start() and stop() methods. When the mic 6285 * is on, its volume can be measured with getLevel or by connecting an 6286 * FFT object.</p> 6287 * 6288 * <p>If you want to hear the AudioIn, use the .connect() method. 6289 * AudioIn does not connect to p5.sound output by default to prevent 6290 * feedback.</p> 6291 * 6292 * <p><em>Note: This uses the <a href="http://caniuse.com/stream">getUserMedia/ 6293 * Stream</a> API, which is not supported by certain browsers.</em></p> 6294 * 6295 * @class p5.AudioIn 6296 * @constructor 6297 * @return {Object} AudioIn 6298 * @example 6299 * <div><code> 6300 * var mic; 6301 * function setup(){ 6302 * mic = new p5.AudioIn() 6303 * mic.start(); 6304 * } 6305 * function draw(){ 6306 * background(0); 6307 * micLevel = mic.getLevel(); 6308 * ellipse(width/2, constrain(height-micLevel*height*5, 0, height), 10, 10); 6309 * } 6310 * </code></div> 6311 */ 6312 p5.AudioIn = function () { 6313 // set up audio input 6314 this.input = p5sound.audiocontext.createGain(); 6315 this.output = p5sound.audiocontext.createGain(); 6316 this.stream = null; 6317 this.mediaStream = null; 6318 this.currentSource = 0; 6319 /** 6320 * Client must allow browser to access their microphone / audioin source. 6321 * Default: false. Will become true when the client enables acces. 6322 * 6323 * @property {Boolean} enabled 6324 */ 6325 this.enabled = false; 6326 // create an amplitude, connect to it by default but not to master out 6327 this.amplitude = new p5.Amplitude(); 6328 this.output.connect(this.amplitude.input); 6329 // Some browsers let developer determine their input sources 6330 if (typeof window.MediaStreamTrack === 'undefined') { 6331 window.alert('This browser does not support MediaStreamTrack'); 6332 } else if (typeof window.MediaStreamTrack.getSources === 'function') { 6333 // Chrome supports getSources to list inputs. Dev picks default 6334 window.MediaStreamTrack.getSources(this._gotSources); 6335 } else { 6336 } 6337 // add to soundArray so we can dispose on close 6338 p5sound.soundArray.push(this); 6339 }; 6340 /** 6341 * Start processing audio input. This enables the use of other 6342 * AudioIn methods like getLevel(). Note that by default, AudioIn 6343 * is not connected to p5.sound's output. So you won't hear 6344 * anything unless you use the connect() method.<br/> 6345 * 6346 * @method start 6347 * @param {Function} successCallback Name of a function to call on 6348 * success. 6349 * @param {Function} errorCallback Name of a function to call if 6350 * there was an error. For example, 6351 * some browsers do not support 6352 * getUserMedia. 6353 */ 6354 p5.AudioIn.prototype.start = function (successCallback, errorCallback) { 6355 var self = this; 6356 // if _gotSources() i.e. developers determine which source to use 6357 if (p5sound.inputSources[self.currentSource]) { 6358 // set the audio source 6359 var audioSource = p5sound.inputSources[self.currentSource].id; 6360 var constraints = { audio: { optional: [{ sourceId: audioSource }] } }; 6361 window.navigator.getUserMedia(constraints, this._onStream = function (stream) { 6362 self.stream = stream; 6363 self.enabled = true; 6364 // Wrap a MediaStreamSourceNode around the live input 6365 self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); 6366 self.mediaStream.connect(self.output); 6367 if (successCallback) 6368 successCallback(); 6369 // only send to the Amplitude reader, so we can see it but not hear it. 6370 self.amplitude.setInput(self.output); 6371 }, this._onStreamError = function (e) { 6372 if (errorCallback) 6373 errorCallback(e); 6374 else 6375 console.error(e); 6376 }); 6377 } else { 6378 // if Firefox where users select their source via browser 6379 // if (typeof MediaStreamTrack.getSources === 'undefined') { 6380 // Only get the audio stream. 6381 window.navigator.getUserMedia({ 'audio': true }, this._onStream = function (stream) { 6382 self.stream = stream; 6383 self.enabled = true; 6384 // Wrap a MediaStreamSourceNode around the live input 6385 self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); 6386 self.mediaStream.connect(self.output); 6387 // only send to the Amplitude reader, so we can see it but not hear it. 6388 self.amplitude.setInput(self.output); 6389 if (successCallback) 6390 successCallback(); 6391 }, this._onStreamError = function (e) { 6392 if (errorCallback) 6393 errorCallback(e); 6394 else 6395 console.error(e); 6396 }); 6397 } 6398 }; 6399 /** 6400 * Turn the AudioIn off. If the AudioIn is stopped, it cannot getLevel().<br/> 6401 * 6402 * @method stop 6403 */ 6404 p5.AudioIn.prototype.stop = function () { 6405 if (this.stream) { 6406 this.stream.stop(); 6407 } 6408 }; 6409 /** 6410 * Connect to an audio unit. If no parameter is provided, will 6411 * connect to the master output (i.e. your speakers).<br/> 6412 * 6413 * @method connect 6414 * @param {Object} [unit] An object that accepts audio input, 6415 * such as an FFT 6416 */ 6417 p5.AudioIn.prototype.connect = function (unit) { 6418 if (unit) { 6419 if (unit.hasOwnProperty('input')) {
6420 this.output.connect(unit.input); 6421 } else if (unit.hasOwnProperty('analyser')) { 6422 this.output.connect(unit.analyser); 6423 } else { 6424 this.output.connect(unit); 6425 } 6426 } else { 6427 this.output.connect(p5sound.input); 6428 } 6429 }; 6430 /** 6431 * Disconnect the AudioIn from all audio units. For example, if 6432 * connect() had been called, disconnect() will stop sending 6433 * signal to your speakers.<br/> 6434 * 6435 * @method disconnect 6436 */ 6437 p5.AudioIn.prototype.disconnect = function (unit) { 6438 this.output.disconnect(unit); 6439 // stay connected to amplitude even if not outputting to p5 6440 this.output.connect(this.amplitude.input); 6441 }; 6442 /** 6443 * Read the Amplitude (volume level) of an AudioIn. The AudioIn 6444 * class contains its own instance of the Amplitude class to help 6445 * make it easy to get a microphone's volume level. Accepts an 6446 * optional smoothing value (0.0 < 1.0). <em>NOTE: AudioIn must 6447 * .start() before using .getLevel().</em><br/> 6448 * 6449 * @method getLevel 6450 * @param {Number} [smoothing] Smoothing is 0.0 by default. 6451 * Smooths values based on previous values. 6452 * @return {Number} Volume level (between 0.0 and 1.0) 6453 */ 6454 p5.AudioIn.prototype.getLevel = function (smoothing) { 6455 if (smoothing) { 6456 this.amplitude.smoothing = smoothing; 6457 } 6458 return this.amplitude.getLevel(); 6459 }; 6460 /** 6461 * Add input sources to the list of available sources. 6462 * 6463 * @private 6464 */ 6465 p5.AudioIn.prototype._gotSources = function (sourceInfos) { 6466 for (var i = 0; i < sourceInfos.length; i++) { 6467 var sourceInfo = sourceInfos[i]; 6468 if (sourceInfo.kind === 'audio') { 6469 // add the inputs to inputSources 6470 //p5sound.inputSources.push(sourceInfo); 6471 return sourceInfo; 6472 } 6473 } 6474 }; 6475 /** 6476 * Set amplitude (volume) of a mic input between 0 and 1.0. <br/> 6477 * 6478 * @method amp 6479 * @param {Number} vol between 0 and 1.0 6480 * @param {Number} [time] ramp time (optional) 6481 */ 6482 p5.AudioIn.prototype.amp = function (vol, t) { 6483 if (t) { 6484 var rampTime = t || 0; 6485 var currentVol = this.output.gain.value; 6486 this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); 6487 this.output.gain.setValueAtTime(currentVol, p5sound.audiocontext.currentTime); 6488 this.output.gain.linearRampToValueAtTime(vol, rampTime + p5sound.audiocontext.currentTime); 6489 } else { 6490 this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); 6491 this.output.gain.setValueAtTime(vol, p5sound.audiocontext.currentTime); 6492 } 6493 }; 6494 p5.AudioIn.prototype.listSources = function () { 6495 console.log('listSources is deprecated - please use AudioIn.getSources'); 6496 console.log('input sources: '); 6497 if (p5sound.inputSources.length > 0) { 6498 return p5sound.inputSources; 6499 } else { 6500 return 'This browser does not support MediaStreamTrack.getSources()'; 6501 } 6502 }; 6503 /** 6504 * Chrome only. Returns a list of available input sources 6505 * and allows the user to set the media source. Firefox allows 6506 * the user to choose from input sources in the permissions dialogue 6507 * instead of enumerating available sources and selecting one. 6508 * Note: in order to have descriptive media names your page must be 6509 * served over a secure (HTTPS) connection and the page should 6510 * request user media before enumerating devices. Otherwise device 6511 * ID will be a long device ID number and does not specify device 6512 * type. For example see 6513 * https://simpl.info/getusermedia/sources/index.html vs. 6514 * http://simpl.info/getusermedia/sources/index.html 6515 * 6516 * @method getSources 6517 * @param {Function} callback a callback to handle the sources 6518 * when they have been enumerated 6519 * @example 6520 * <div><code> 6521 * var audiograb; 6522 * 6523 * function setup(){ 6524 * //new audioIn 6525 * audioGrab = new p5.AudioIn(); 6526 * 6527 * audioGrab.getSources(function(sourceList) { 6528 * //print out the array of available sources 6529 * console.log(sourceList); 6530 * //set the source to the first item in the inputSources array 6531 * audioGrab.setSource(0); 6532 * }); 6533 * } 6534 * </code></div> 6535 */ 6536 p5.AudioIn.prototype.getSources = function (callback) { 6537 if (typeof window.MediaStreamTrack.getSources === 'function') { 6538 window.MediaStreamTrack.getSources(function (data) { 6539 for (var i = 0, max = data.length; i < max; i++) { 6540 var sourceInfo = data[i]; 6541 if (sourceInfo.kind === 'audio') { 6542 // add the inputs to inputSources 6543 p5sound.inputSources.push(sourceInfo); 6544 } 6545 } 6546 callback(p5sound.inputSources); 6547 }); 6548 } else { 6549 console.log('This browser does not support MediaStreamTrack.getSources()'); 6550 } 6551 }; 6552 /** 6553 * Set the input source. Accepts a number representing a 6554 * position in the array returned by listSources(). 6555 * This is only available in browsers that support 6556 * MediaStreamTrack.getSources(). Instead, some browsers
6557 * give users the option to set their own media source.<br/> 6558 * 6559 * @method setSource 6560 * @param {number} num position of input source in the array 6561 */ 6562 p5.AudioIn.prototype.setSource = function (num) { 6563 // TO DO - set input by string or # (array position) 6564 var self = this; 6565 if (p5sound.inputSources.length > 0 && num < p5sound.inputSources.length) { 6566 // set the current source 6567 self.currentSource = num; 6568 console.log('set source to ' + p5sound.inputSources[self.currentSource].id); 6569 } else { 6570 console.log('unable to set input source'); 6571 } 6572 }; 6573 // private method 6574 p5.AudioIn.prototype.dispose = function () { 6575 // remove reference from soundArray 6576 var index = p5sound.soundArray.indexOf(this); 6577 p5sound.soundArray.splice(index, 1); 6578 this.stop(); 6579 if (this.output) { 6580 this.output.disconnect(); 6581 } 6582 if (this.amplitude) { 6583 this.amplitude.disconnect(); 6584 } 6585 this.amplitude = null; 6586 this.output = null; 6587 }; 6588}(master, errorHandler); 6589var filter; 6590filter = function () { 6591 'use strict'; 6592 var p5sound = master; 6593 /** 6594 * A p5.Filter uses a Web Audio Biquad Filter to filter 6595 * the frequency response of an input source. Inheriting 6596 * classes include:<br/> 6597 * * <code>p5.LowPass</code> - allows frequencies below 6598 * the cutoff frequency to pass through, and attenuates 6599 * frequencies above the cutoff.<br/> 6600 * * <code>p5.HighPass</code> - the opposite of a lowpass 6601 * filter. <br/> 6602 * * <code>p5.BandPass</code> - allows a range of 6603 * frequencies to pass through and attenuates the frequencies 6604 * below and above this frequency range.<br/> 6605 * 6606 * The <code>.res()</code> method controls either width of the 6607 * bandpass, or resonance of the low/highpass cutoff frequency. 6608 * 6609 * @class p5.Filter 6610 * @constructor 6611 * @param {[String]} type 'lowpass' (default), 'highpass', 'bandpass' 6612 * @return {Object} p5.Filter 6613 * @example 6614 * <div><code> 6615 * var fft, noise, filter; 6616 * 6617 * function setup() { 6618 * fill(255, 40, 255); 6619 * 6620 * filter = new p5.BandPass(); 6621 * 6622 * noise = new p5.Noise(); 6623 * // disconnect unfiltered noise, 6624 * // and connect to filter 6625 * noise.disconnect(); 6626 * noise.connect(filter); 6627 * noise.start(); 6628 * 6629 * fft = new p5.FFT(); 6630 * } 6631 * 6632 * function draw() { 6633 * background(30); 6634 * 6635 * // set the BandPass frequency based on mouseX 6636 * var freq = map(mouseX, 0, width, 20, 10000); 6637 * filter.freq(freq); 6638 * // give the filter a narrow band (lower res = wider bandpass) 6639 * filter.res(50); 6640 * 6641 * // draw filtered spectrum 6642 * var spectrum = fft.analyze(); 6643 * noStroke(); 6644 * for (var i = 0; i < spectrum.length; i++) { 6645 * var x = map(i, 0, spectrum.length, 0, width); 6646 * var h = -height + map(spectrum[i], 0, 255, height, 0); 6647 * rect(x, height, width/spectrum.length, h); 6648 * } 6649 * 6650 * isMouseOverCanvas(); 6651 * } 6652 * 6653 * function isMouseOverCanvas() { 6654 * var mX = mouseX, mY = mouseY; 6655 * if (mX > 0 && mX < width && mY < height && mY > 0) { 6656 * noise.amp(0.5, 0.2); 6657 * } else { 6658 * noise.amp(0, 0.2); 6659 * } 6660 * } 6661 * </code></div> 6662 */ 6663 p5.Filter = function (type) { 6664 this.ac = p5sound.audiocontext; 6665 this.input = this.ac.createGain(); 6666 this.output = this.ac.createGain(); 6667 /** 6668 * The p5.Filter is built with a 6669 * <a href="http://www.w3.org/TR/webaudio/#BiquadFilterNode"> 6670 * Web Audio BiquadFilter Node</a>. 6671 * 6672 * @property biquadFilter 6673 * @type {Object} Web Audio Delay Node 6674 */ 6675 this.biquad = this.ac.createBiquadFilter(); 6676 this.input.connect(this.biquad); 6677 this.biquad.connect(this.output); 6678 this.connect(); 6679 if (type) { 6680 this.setType(type); 6681 } 6682 // add to the soundArray 6683 p5sound.soundArray.push(this); 6684 }; 6685 /** 6686 * Filter an audio signal according to a set 6687 * of filter parameters. 6688 * 6689 * @method process 6690 * @param {Object} Signal An object that outputs audio 6691 * @param {[Number]} freq Frequency in Hz, from 10 to 22050 6692 * @param {[Number]} res Resonance/Width of the filter frequency 6693 * from 0.001 to 1000 6694 */ 6695 p5.Filter.prototype.process = function (src, freq, res) { 6696 src.connect(this.input); 6697 this.set(freq, res); 6698 }; 6699 /** 6700 * Set the frequency and the resonance of the filter. 6701 * 6702 * @method set 6703 * @param {Number} freq Frequency in Hz, from 10 to 22050 6704 * @param {Number} res Resonance (Q) from 0.001 to 1000 6705 * @param {Number} [timeFromNow] schedule this event to happen 6706 * seconds from now 6707 */ 6708 p5.Filter.prototype.set = function (freq, res, time) { 6709 if (freq) { 6710 this.freq(freq, time); 6711 } 6712 if (res) { 6713 this.res(res, time); 6714 } 6715 }; 6716 /** 6717 * Set the filter frequency, in Hz, from 10 to 22050 (the range of 6718 * human hearing, although in reality most people hear in a narrower 6719 * range). 6720 * 6721 * @method freq 6722 * @param {Number} freq Filter Frequency 6723 * @param {Number} [timeFromNow] schedule this event to happen 6724 * seconds from now 6725 * @return {Number} value Returns the current frequency value 6726 */ 6727 p5.Filter.prototype.freq = function (freq, time) { 6728 var self = this; 6729 var t = time || 0; 6730 if (freq <= 0) { 6731 freq = 1; 6732 } 6733 if (typeof freq === 'number') { 6734 self.biquad.frequency.value = freq; 6735 self.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t); 6736 self.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t); 6737 } else if (freq) { 6738 freq.connect(this.biquad.frequency); 6739 } 6740 return self.biquad.frequency.value; 6741 }; 6742 /** 6743 * Controls either width of a bandpass frequency, 6744 * or the resonance of a low/highpass cutoff frequency. 6745 * 6746 * @method res 6747 * @param {Number} res Resonance/Width of filter freq 6748 * from 0.001 to 1000 6749 * @param {Number} [timeFromNow] schedule this event to happen 6750 * seconds from now 6751 * @return {Number} value Returns the current res value 6752 */ 6753 p5.Filter.prototype.res = function (res, time) { 6754 var self = this; 6755 var t = time || 0; 6756 if (typeof res == 'number') { 6757 self.biquad.Q.value = res; 6758 self.biquad.Q.cancelScheduledValues(self.ac.currentTime + 0.01 + t); 6759 self.biquad.Q.linearRampToValueAtTime(res, self.ac.currentTime + 0.02 + t); 6760 } else if (res) { 6761 freq.connect(this.biquad.Q); 6762 } 6763 return self.biquad.Q.value; 6764 }; 6765 /** 6766 * Set the type of a p5.Filter. Possible types include: 6767 * "lowpass" (default), "highpass", "bandpass", 6768 * "lowshelf", "highshelf", "peaking", "notch", 6769 * "allpass". 6770 * 6771 * @method setType 6772 * @param {String} 6773 */ 6774 p5.Filter.prototype.setType = function (t) { 6775 this.biquad.type = t; 6776 }; 6777 /** 6778 * Set the output level of the filter. 6779 * 6780 * @method amp 6781 * @param {Number} volume amplitude between 0 and 1.0 6782 * @param {Number} [rampTime] create a fade that lasts rampTime 6783 * @param {Number} [timeFromNow] schedule this event to happen 6784 * seconds from now 6785 */ 6786 p5.Filter.prototype.amp = function (vol, rampTime, tFromNow) { 6787 var rampTime = rampTime || 0; 6788 var tFromNow = tFromNow || 0; 6789 var now = p5sound.audiocontext.currentTime; 6790 var currentVol = this.output.gain.value; 6791 this.output.gain.cancelScheduledValues(now); 6792 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); 6793 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); 6794 }; 6795 /** 6796 * Send output to a p5.sound or web audio object 6797 * 6798 * @method connect 6799 * @param {Object} unit 6800 */ 6801 p5.Filter.prototype.connect = function (unit) { 6802 var u = unit || p5.soundOut.input; 6803 this.output.connect(u); 6804 }; 6805 /** 6806 * Disconnect all output. 6807 * 6808 * @method disconnect 6809 */ 6810 p5.Filter.prototype.disconnect = function () { 6811 this.output.disconnect(); 6812 }; 6813 p5.Filter.prototype.dispose = function () { 6814 // remove reference from soundArray 6815 var index = p5sound.soundArray.indexOf(this); 6816 p5sound.soundArray.splice(index, 1); 6817 this.input.disconnect(); 6818 this.input = undefined; 6819 this.output.disconnect(); 6820 this.output = undefined; 6821 this.biquad.disconnect(); 6822 this.biquad = undefined; 6823 }; 6824 /** 6825 * Constructor: <code>new p5.LowPass()</code> Filter. 6826 * This is the same as creating a p5.Filter and then calling 6827 * its method <code>setType('lowpass')</code>. 6828 * See p5.Filter for methods. 6829 * 6830 * @method p5.LowPass 6831 */ 6832 p5.LowPass = function () { 6833 p5.Filter.call(this, 'lowpass'); 6834 }; 6835 p5.LowPass.prototype = Object.create(p5.Filter.prototype); 6836 /** 6837 * Constructor: <code>new p5.HighPass()</code> Filter. 6838 * This is the same as creating a p5.Filter and then calling 6839 * its method <code>setType('highpass')</code>. 6840 * See p5.Filter for methods. 6841 * 6842 * @method p5.HighPass 6843 */ 6844 p5.HighPass = function () { 6845 p5.Filter.call(this, 'highpass'); 6846 }; 6847 p5.HighPass.prototype = Object.create(p5.Filter.prototype); 6848 /** 6849 * Constructor: <code>new p5.BandPass()</code> Filter. 6850 * This is the same as creating a p5.Filter and then calling 6851 * its method <code>setType('bandpass')</code>. 6852 * See p5.Filter for methods. 6853 * 6854 * @method p5.BandPass 6855 */ 6856 p5.BandPass = function () { 6857 p5.Filter.call(this, 'bandpass'); 6858 }; 6859 p5.BandPass.prototype = Object.create(p5.Filter.prototype); 6860}(master); 6861var delay; 6862delay = function () { 6863 'use strict'; 6864 var p5sound = master;
6865 var Filter = filter; 6866 /** 6867 * Delay is an echo effect. It processes an existing sound source, 6868 * and outputs a delayed version of that sound. The p5.Delay can 6869 * produce different effects depending on the delayTime, feedback, 6870 * filter, and type. In the example below, a feedback of 0.5 will 6871 * produce a looping delay that decreases in volume by 6872 * 50% each repeat. A filter will cut out the high frequencies so 6873 * that the delay does not sound as piercing as the original source. 6874 * 6875 * @class p5.Delay 6876 * @constructor 6877 * @return {Object} Returns a p5.Delay object 6878 * @example 6879 * <div><code> 6880 * var noise, env, delay; 6881 * 6882 * function setup() { 6883 * background(0); 6884 * noStroke(); 6885 * fill(255); 6886 * textAlign(CENTER); 6887 * text('click to play', width/2, height/2); 6888 * 6889 * noise = new p5.Noise('brown'); 6890 * noise.amp(0); 6891 * noise.start(); 6892 * 6893 * delay = new p5.Delay(); 6894 * 6895 * // delay.process() accepts 4 parameters: 6896 * // source, delayTime, feedback, filter frequency 6897 * // play with these numbers!! 6898 * delay.process(noise, .12, .7, 2300); 6899 * 6900 * // play the noise with an envelope, 6901 * // a series of fades ( time / value pairs ) 6902 * env = new p5.Env(.01, 0.2, .2, .1); 6903 * } 6904 * 6905 * // mouseClick triggers envelope 6906 * function mouseClicked() { 6907 * // is mouse over canvas? 6908 * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { 6909 * env.play(noise); 6910 * } 6911 * } 6912 * </code></div> 6913 */ 6914 p5.Delay = function () { 6915 this.ac = p5sound.audiocontext; 6916 this.input = this.ac.createGain(); 6917 this.output = this.ac.createGain(); 6918 this._split = this.ac.createChannelSplitter(2); 6919 this._merge = this.ac.createChannelMerger(2); 6920 this._leftGain = this.ac.createGain(); 6921 this._rightGain = this.ac.createGain(); 6922 /** 6923 * The p5.Delay is built with two 6924 * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> 6925 * Web Audio Delay Nodes</a>, one for each stereo channel. 6926 * 6927 * @property leftDelay 6928 * @type {Object} Web Audio Delay Node 6929 */ 6930 this.leftDelay = this.ac.createDelay(); 6931 /** 6932 * The p5.Delay is built with two 6933 * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> 6934 * Web Audio Delay Nodes</a>, one for each stereo channel. 6935 * 6936 * @property rightDelay 6937 * @type {Object} Web Audio Delay Node 6938 */ 6939 this.rightDelay = this.ac.createDelay(); 6940 this._leftFilter = new p5.Filter(); 6941 this._rightFilter = new p5.Filter(); 6942 this._leftFilter.disconnect(); 6943 this._rightFilter.disconnect(); 6944 this._leftFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); 6945 this._rightFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); 6946 this._leftFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); 6947 this._rightFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); 6948 // graph routing 6949 this.input.connect(this._split); 6950 this.leftDelay.connect(this._leftGain); 6951 this.rightDelay.connect(this._rightGain); 6952 this._leftGain.connect(this._leftFilter.input); 6953 this._rightGain.connect(this._rightFilter.input); 6954 this._merge.connect(this.output); 6955 this.output.connect(p5.soundOut.input); 6956 this._leftFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); 6957 this._rightFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); 6958 // default routing 6959 this.setType(0); 6960 this._maxDelay = this.leftDelay.delayTime.maxValue; 6961 // add this p5.SoundFile to the soundArray 6962 p5sound.soundArray.push(this); 6963 }; 6964 /** 6965 * Add delay to an audio signal according to a set 6966 * of delay parameters. 6967 * 6968 * @method process 6969 * @param {Object} Signal An object that outputs audio 6970 * @param {Number} [delayTime] Time (in seconds) of the delay/echo. 6971 * Some browsers limit delayTime to 6972 * 1 second. 6973 * @param {Number} [feedback] sends the delay back through itself 6974 * in a loop that decreases in volume 6975 * each time. 6976 * @param {Number} [lowPass] Cutoff frequency. Only frequencies 6977 * below the lowPass will be part of the 6978 * delay. 6979 */ 6980 p5.Delay.prototype.process = function (src, _delayTime, _feedback, _filter) { 6981 var feedback = _feedback || 0; 6982 var delayTime = _delayTime || 0; 6983 if (feedback >= 1) { 6984 throw new Error('Feedback value will force a positive feedback loop.'); 6985 } 6986 if (delayTime >= this._maxDelay) { 6987 throw new Error('Delay Time exceeds maximum delay time of ' + this._maxDelay + ' second.'); 6988 } 6989 src.connect(this.input); 6990 this.leftDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); 6991 this.rightDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); 6992 this._leftGain.gain.setValueAtTime(feedback, this.ac.currentTime); 6993 this._rightGain.gain.setValueAtTime(feedback, this.ac.currentTime); 6994 if (_filter) { 6995 this._leftFilter.freq(_filter); 6996 this._rightFilter.freq(_filter); 6997 } 6998 }; 6999 /** 7000 * Set the delay (echo) time, in seconds. Usually this value will be 7001 * a floating point number between 0.0 and 1.0. 7002 * 7003 * @method delayTime 7004 * @param {Number} delayTime Time (in seconds) of the delay 7005 */ 7006 p5.Delay.prototype.delayTime = function (t) {
7007 // if t is an audio node... 7008 if (typeof t !== 'number') { 7009 t.connect(this.leftDelay.delayTime); 7010 t.connect(this.rightDelay.delayTime); 7011 } else { 7012 this.leftDelay.delayTime.cancelScheduledValues(this.ac.currentTime); 7013 this.rightDelay.delayTime.cancelScheduledValues(this.ac.currentTime); 7014 this.leftDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); 7015 this.rightDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); 7016 } 7017 }; 7018 /** 7019 * Feedback occurs when Delay sends its signal back through its input 7020 * in a loop. The feedback amount determines how much signal to send each 7021 * time through the loop. A feedback greater than 1.0 is not desirable because 7022 * it will increase the overall output each time through the loop, 7023 * creating an infinite feedback loop. 7024 * 7025 * @method feedback 7026 * @param {Number|Object} feedback 0.0 to 1.0, or an object such as an 7027 * Oscillator that can be used to 7028 * modulate this param 7029 */ 7030 p5.Delay.prototype.feedback = function (f) { 7031 // if f is an audio node... 7032 if (typeof f !== 'number') { 7033 f.connect(this._leftGain.gain); 7034 f.connect(this._rightGain.gain); 7035 } else if (f >= 1) { 7036 throw new Error('Feedback value will force a positive feedback loop.'); 7037 } else { 7038 this._leftGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); 7039 this._rightGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); 7040 } 7041 }; 7042 /** 7043 * Set a lowpass filter frequency for the delay. A lowpass filter 7044 * will cut off any frequencies higher than the filter frequency. 7045 * 7046 * @method filter 7047 * @param {Number|Object} cutoffFreq A lowpass filter will cut off any 7048 * frequencies higher than the filter frequency. 7049 * @param {Number|Object} res Resonance of the filter frequency 7050 * cutoff, or an object (i.e. a p5.Oscillator) 7051 * that can be used to modulate this parameter. 7052 * High numbers (i.e. 15) will produce a resonance, 7053 * low numbers (i.e. .2) will produce a slope. 7054 */ 7055 p5.Delay.prototype.filter = function (freq, q) { 7056 this._leftFilter.set(freq, q); 7057 this._rightFilter.set(freq, q); 7058 }; 7059 /** 7060 * Choose a preset type of delay. 'pingPong' bounces the signal 7061 * from the left to the right channel to produce a stereo effect. 7062 * Any other parameter will revert to the default delay setting. 7063 * 7064 * @method setType 7065 * @param {String|Number} type 'pingPong' (1) or 'default' (0) 7066 */ 7067 p5.Delay.prototype.setType = function (t) { 7068 if (t === 1) { 7069 t = 'pingPong'; 7070 } 7071 this._split.disconnect(); 7072 this._leftFilter.disconnect(); 7073 this._rightFilter.disconnect(); 7074 this._split.connect(this.leftDelay, 0); 7075 this._split.connect(this.rightDelay, 1); 7076 switch (t) { 7077 case 'pingPong': 7078 this._rightFilter.setType(this._leftFilter.biquad.type); 7079 this._leftFilter.output.connect(this._merge, 0, 0); 7080 this._rightFilter.output.connect(this._merge, 0, 1); 7081 this._leftFilter.output.connect(this.rightDelay); 7082 this._rightFilter.output.connect(this.leftDelay); 7083 break; 7084 default: 7085 this._leftFilter.output.connect(this._merge, 0, 0); 7086 this._leftFilter.output.connect(this._merge, 0, 1); 7087 this._leftFilter.output.connect(this.leftDelay); 7088 this._leftFilter.output.connect(this.rightDelay); 7089 } 7090 }; 7091 /** 7092 * Set the output level of the delay effect. 7093 * 7094 * @method amp 7095 * @param {Number} volume amplitude between 0 and 1.0 7096 * @param {Number} [rampTime] create a fade that lasts rampTime 7097 * @param {Number} [timeFromNow] schedule this event to happen 7098 * seconds from now 7099 */ 7100 p5.Delay.prototype.amp = function (vol, rampTime, tFromNow) { 7101 var rampTime = rampTime || 0; 7102 var tFromNow = tFromNow || 0; 7103 var now = p5sound.audiocontext.currentTime; 7104 var currentVol = this.output.gain.value; 7105 this.output.gain.cancelScheduledValues(now); 7106 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); 7107 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); 7108 }; 7109 /** 7110 * Send output to a p5.sound or web audio object 7111 * 7112 * @method connect 7113 * @param {Object} unit 7114 */ 7115 p5.Delay.prototype.connect = function (unit) { 7116 var u = unit || p5.soundOut.input; 7117 this.output.connect(u); 7118 }; 7119 /** 7120 * Disconnect all output. 7121 * 7122 * @method disconnect 7123 */ 7124 p5.Delay.prototype.disconnect = function () { 7125 this.output.disconnect(); 7126 }; 7127 p5.Delay.prototype.dispose = function () { 7128 // remove reference from soundArray 7129 var index = p5sound.soundArray.indexOf(this); 7130 p5sound.soundArray.splice(index, 1); 7131 this.input.disconnect(); 7132 this.output.disconnect(); 7133 this._split.disconnect(); 7134 this._leftFilter.disconnect(); 7135 this._rightFilter.disconnect(); 7136 this._merge.disconnect(); 7137 this._leftGain.disconnect(); 7138 this._rightGain.disconnect(); 7139 this.leftDelay.disconnect(); 7140 this.rightDelay.disconnect(); 7141 this.input = undefined; 7142 this.output = undefined; 7143 this._split = undefined; 7144 this._leftFilter = undefined; 7145 this._rightFilter = undefined; 7146 this._merge = undefined; 7147 this._leftGain = undefined; 7148 this._rightGain = undefined; 7149 this.leftDelay = undefined; 7150 this.rightDelay = undefined; 7151 }; 7152}(master, filter); 7153var reverb; 7154reverb = function () { 7155 'use strict'; 7156 var p5sound = master; 7157 var CustomError = errorHandler; 7158 /** 7159 * Reverb adds depth to a sound through a large number of decaying 7160 * echoes. It creates the perception that sound is occurring in a 7161 * physical space. The p5.Reverb has paramters for Time (how long does the 7162 * reverb last) and decayRate (how much the sound decays with each echo) 7163 * that can be set with the .set() or .process() methods. The p5.Convolver 7164 * extends p5.Reverb allowing you to recreate the sound of actual physical 7165 * spaces through convolution. 7166 * 7167 * @class p5.Reverb 7168 * @constructor 7169 * @example 7170 * <div><code> 7171 * var soundFile, reverb; 7172 * function preload() { 7173 * soundFile = loadSound('assets/Damscray_DancingTiger.mp3'); 7174 * } 7175 * 7176 * function setup() { 7177 * reverb = new p5.Reverb(); 7178 * soundFile.disconnect(); // so we'll only hear reverb... 7179 * 7180 * // connect soundFile to reverb, process w/ 7181 * // 3 second reverbTime, decayRate of 2% 7182 * reverb.process(soundFile, 3, 2); 7183 * soundFile.play(); 7184 * } 7185 * </code></div> 7186 */ 7187 p5.Reverb = function () { 7188 this.ac = p5sound.audiocontext; 7189 this.convolverNode = this.ac.createConvolver(); 7190 this.input = this.ac.createGain(); 7191 this.output = this.ac.createGain(); 7192 // otherwise, Safari distorts 7193 this.input.gain.value = 0.5; 7194 this.input.connect(this.convolverNode); 7195 this.convolverNode.connect(this.output); 7196 // default params 7197 this._seconds = 3; 7198 this._decay = 2; 7199 this._reverse = false;
7200 this._buildImpulse(); 7201 this.connect(); 7202 p5sound.soundArray.push(this); 7203 }; 7204 /** 7205 * Connect a source to the reverb, and assign reverb parameters. 7206 * 7207 * @method process 7208 * @param {Object} src p5.sound / Web Audio object with a sound 7209 * output. 7210 * @param {Number} [seconds] Duration of the reverb, in seconds. 7211 * Min: 0, Max: 10. Defaults to 3. 7212 * @param {Number} [decayRate] Percentage of decay with each echo. 7213 * Min: 0, Max: 100. Defaults to 2. 7214 * @param {Boolean} [reverse] Play the reverb backwards or forwards. 7215 */ 7216 p5.Reverb.prototype.process = function (src, seconds, decayRate, reverse) { 7217 src.connect(this.input); 7218 var rebuild = false; 7219 if (seconds) { 7220 this._seconds = seconds; 7221 rebuild = true; 7222 } 7223 if (decayRate) { 7224 this._decay = decayRate; 7225 } 7226 if (reverse) { 7227 this._reverse = reverse; 7228 } 7229 if (rebuild) { 7230 this._buildImpulse(); 7231 } 7232 }; 7233 /** 7234 * Set the reverb settings. Similar to .process(), but without 7235 * assigning a new input. 7236 * 7237 * @method set 7238 * @param {Number} [seconds] Duration of the reverb, in seconds. 7239 * Min: 0, Max: 10. Defaults to 3. 7240 * @param {Number} [decayRate] Percentage of decay with each echo. 7241 * Min: 0, Max: 100. Defaults to 2. 7242 * @param {Boolean} [reverse] Play the reverb backwards or forwards. 7243 */ 7244 p5.Reverb.prototype.set = function (seconds, decayRate, reverse) { 7245 var rebuild = false; 7246 if (seconds) { 7247 this._seconds = seconds; 7248 rebuild = true; 7249 } 7250 if (decayRate) { 7251 this._decay = decayRate; 7252 } 7253 if (reverse) { 7254 this._reverse = reverse; 7255 } 7256 if (rebuild) { 7257 this._buildImpulse(); 7258 } 7259 }; 7260 /** 7261 * Set the output level of the delay effect. 7262 * 7263 * @method amp 7264 * @param {Number} volume amplitude between 0 and 1.0 7265 * @param {Number} [rampTime] create a fade that lasts rampTime 7266 * @param {Number} [timeFromNow] schedule this event to happen 7267 * seconds from now 7268 */ 7269 p5.Reverb.prototype.amp = function (vol, rampTime, tFromNow) { 7270 var rampTime = rampTime || 0; 7271 var tFromNow = tFromNow || 0; 7272 var now = p5sound.audiocontext.currentTime; 7273 var currentVol = this.output.gain.value; 7274 this.output.gain.cancelScheduledValues(now); 7275 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); 7276 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); 7277 }; 7278 /** 7279 * Send output to a p5.sound or web audio object 7280 * 7281 * @method connect 7282 * @param {Object} unit 7283 */ 7284 p5.Reverb.prototype.connect = function (unit) { 7285 var u = unit || p5.soundOut.input; 7286 this.output.connect(u.input ? u.input : u); 7287 }; 7288 /** 7289 * Disconnect all output. 7290 * 7291 * @method disconnect 7292 */ 7293 p5.Reverb.prototype.disconnect = function () { 7294 this.output.disconnect(); 7295 }; 7296 /** 7297 * Inspired by Simple Reverb by Jordan Santell 7298 * https://github.com/web-audio-components/simple-reverb/blob/master/index.js 7299 * 7300 * Utility function for building an impulse response 7301 * based on the module parameters. 7302 * 7303 * @private 7304 */ 7305 p5.Reverb.prototype._buildImpulse = function () { 7306 var rate = this.ac.sampleRate; 7307 var length = rate * this._seconds; 7308 var decay = this._decay; 7309 var impulse = this.ac.createBuffer(2, length, rate); 7310 var impulseL = impulse.getChannelData(0); 7311 var impulseR = impulse.getChannelData(1); 7312 var n, i; 7313 for (i = 0; i < length; i++) { 7314 n = this.reverse ? length - i : i; 7315 impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); 7316 impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); 7317 } 7318 this.convolverNode.buffer = impulse; 7319 }; 7320 p5.Reverb.prototype.dispose = function () { 7321 // remove reference from soundArray 7322 var index = p5sound.soundArray.indexOf(this); 7323 p5sound.soundArray.splice(index, 1); 7324 if (this.convolverNode) { 7325 this.convolverNode.buffer = null; 7326 this.convolverNode = null; 7327 } 7328 if (typeof this.output !== 'undefined') { 7329 this.output.disconnect(); 7330 this.output = null; 7331 } 7332 if (typeof this.panner !== 'undefined') { 7333 this.panner.disconnect(); 7334 this.panner = null; 7335 } 7336 }; 7337 // ======================================================================= 7338 // *** p5.Convolver *** 7339 // ======================================================================= 7340 /** 7341 * <p>p5.Convolver extends p5.Reverb. It can emulate the sound of real 7342 * physical spaces through a process called <a href=" 7343 * https://en.wikipedia.org/wiki/Convolution_reverb#Real_space_simulation"> 7344 * convolution</a>.</p> 7345 * 7346 * <p>Convolution multiplies any audio input by an "impulse response" 7347 * to simulate the dispersion of sound over time. The impulse response is 7348 * generated from an audio file that you provide. One way to 7349 * generate an impulse response is to pop a balloon in a reverberant space 7350 * and record the echo. Convolution can also be used to experiment with 7351 * sound.</p> 7352 * 7353 * <p>Use the method <code>createConvolution(path)</code> to instantiate a 7354 * p5.Convolver with a path to your impulse response audio file.</p> 7355 * 7356 * @class p5.Convolver 7357 * @constructor 7358 * @param {String} path path to a sound file 7359 * @param {Function} [callback] function to call when loading succeeds 7360 * @param {Function} [errorCallback] function to call if loading fails. 7361 * This function will receive an error or 7362 * XMLHttpRequest object with information 7363 * about what went wrong. 7364 * @example 7365 * <div><code> 7366 * var cVerb, sound; 7367 * function preload() { 7368 * // We have both MP3 and OGG versions of all sound assets 7369 * soundFormats('ogg', 'mp3'); 7370 * 7371 * // Try replacing 'bx-spring' with other soundfiles like 7372 * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' 7373 * cVerb = createConvolver('assets/bx-spring.mp3'); 7374 * 7375 * // Try replacing 'Damscray_DancingTiger' with 7376 * // 'beat', 'doorbell', lucky_dragons_-_power_melody' 7377 * sound = loadSound('assets/Damscray_DancingTiger.mp3'); 7378 * } 7379 * 7380 * function setup() { 7381 * // disconnect from master output... 7382 * sound.disconnect(); 7383 * 7384 * // ...and process with cVerb 7385 * // so that we only hear the convolution 7386 * cVerb.process(sound); 7387 * 7388 * sound.play(); 7389 * } 7390 * </code></div> 7391 */ 7392 p5.Convolver = function (path, callback, errorCallback) { 7393 this.ac = p5sound.audiocontext; 7394 /** 7395 * Internally, the p5.Convolver uses the a 7396 * <a href="http://www.w3.org/TR/webaudio/#ConvolverNode"> 7397 * Web Audio Convolver Node</a>. 7398 * 7399 * @property convolverNode 7400 * @type {Object} Web Audio Convolver Node 7401 */ 7402 this.convolverNode = this.ac.createConvolver(); 7403 this.input = this.ac.createGain(); 7404 this.output = this.ac.createGain(); 7405 // otherwise, Safari distorts 7406 this.input.gain.value = 0.5; 7407 this.input.connect(this.convolverNode); 7408 this.convolverNode.connect(this.output); 7409 if (path) { 7410 this.impulses = []; 7411 this._loadBuffer(path, callback, errorCallback); 7412 } else { 7413 // parameters 7414 this._seconds = 3; 7415 this._decay = 2; 7416 this._reverse = false;
7417 this._buildImpulse(); 7418 } 7419 this.connect(); 7420 p5sound.soundArray.push(this); 7421 }; 7422 p5.Convolver.prototype = Object.create(p5.Reverb.prototype); 7423 p5.prototype.registerPreloadMethod('createConvolver', p5.prototype); 7424 /** 7425 * Create a p5.Convolver. Accepts a path to a soundfile 7426 * that will be used to generate an impulse response. 7427 * 7428 * @method createConvolver 7429 * @param {String} path path to a sound file 7430 * @param {Function} [callback] function to call if loading is successful. 7431 * The object will be passed in as the argument 7432 * to the callback function. 7433 * @param {Function} [errorCallback] function to call if loading is not successful. 7434 * A custom error will be passed in as the argument 7435 * to the callback function. 7436 * @return {p5.Convolver} 7437 * @example 7438 * <div><code> 7439 * var cVerb, sound; 7440 * function preload() { 7441 * // We have both MP3 and OGG versions of all sound assets 7442 * soundFormats('ogg', 'mp3'); 7443 * 7444 * // Try replacing 'bx-spring' with other soundfiles like 7445 * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' 7446 * cVerb = createConvolver('assets/bx-spring.mp3'); 7447 * 7448 * // Try replacing 'Damscray_DancingTiger' with 7449 * // 'beat', 'doorbell', lucky_dragons_-_power_melody' 7450 * sound = loadSound('assets/Damscray_DancingTiger.mp3'); 7451 * } 7452 * 7453 * function setup() { 7454 * // disconnect from master output... 7455 * sound.disconnect(); 7456 * 7457 * // ...and process with cVerb 7458 * // so that we only hear the convolution 7459 * cVerb.process(sound); 7460 * 7461 * sound.play(); 7462 * } 7463 * </code></div> 7464 */ 7465 p5.prototype.createConvolver = function (path, callback, errorCallback) { 7466 // if loading locally without a server 7467 if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { 7468 alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); 7469 } 7470 var cReverb = new p5.Convolver(path, callback, errorCallback); 7471 cReverb.impulses = []; 7472 return cReverb; 7473 }; 7474 /** 7475 * Private method to load a buffer as an Impulse Response, 7476 * assign it to the convolverNode, and add to the Array of .impulses. 7477 * 7478 * @param {String} path 7479 * @param {Function} callback 7480 * @param {Function} errorCallback 7481 * @private 7482 */ 7483 p5.Convolver.prototype._loadBuffer = function (path, callback, errorCallback) { 7484 var path = p5.prototype._checkFileFormats(path); 7485 var self = this; 7486 var errorTrace = new Error().stack; 7487 var ac = p5.prototype.getAudioContext(); 7488 var request = new XMLHttpRequest(); 7489 request.open('GET', path, true); 7490 request.responseType = 'arraybuffer'; 7491 request.onload = function () { 7492 if (request.status == 200) { 7493 // on success loading file: 7494 ac.decodeAudioData(request.response, function (buff) { 7495 var buffer = {}; 7496 var chunks = path.split('/'); 7497 buffer.name = chunks[chunks.length - 1]; 7498 buffer.audioBuffer = buff; 7499 self.impulses.push(buffer); 7500 self.convolverNode.buffer = buffer.audioBuffer; 7501 if (callback) { 7502 callback(buffer); 7503 } 7504 }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 7505 function (e) { 7506 var err = new CustomError('decodeAudioData', errorTrace, self.url); 7507 var msg = 'AudioContext error at decodeAudioData for ' + self.url; 7508 if (errorCallback) { 7509 err.msg = msg; 7510 errorCallback(err); 7511 } else { 7512 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 7513 } 7514 }); 7515 } else { 7516 var err = new CustomError('loadConvolver', errorTrace, self.url); 7517 var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; 7518 if (errorCallback) { 7519 err.message = msg; 7520 errorCallback(err); 7521 } else { 7522 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 7523 } 7524 } 7525 }; 7526 // if there is another error, aside from 404... 7527 request.onerror = function (e) { 7528 var err = new CustomError('loadConvolver', errorTrace, self.url); 7529 var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.';
7530 if (errorCallback) { 7531 err.message = msg; 7532 errorCallback(err); 7533 } else { 7534 console.error(msg + '\n The error stack trace includes: \n' + err.stack); 7535 } 7536 }; 7537 request.send(); 7538 }; 7539 p5.Convolver.prototype.set = null; 7540 /** 7541 * Connect a source to the reverb, and assign reverb parameters. 7542 * 7543 * @method process 7544 * @param {Object} src p5.sound / Web Audio object with a sound 7545 * output. 7546 * @example 7547 * <div><code> 7548 * var cVerb, sound; 7549 * function preload() { 7550 * soundFormats('ogg', 'mp3'); 7551 * 7552 * cVerb = createConvolver('assets/concrete-tunnel.mp3'); 7553 * 7554 * sound = loadSound('assets/beat.mp3'); 7555 * } 7556 * 7557 * function setup() { 7558 * // disconnect from master output... 7559 * sound.disconnect(); 7560 * 7561 * // ...and process with (i.e. connect to) cVerb 7562 * // so that we only hear the convolution 7563 * cVerb.process(sound); 7564 * 7565 * sound.play(); 7566 * } 7567 * </code></div> 7568 */ 7569 p5.Convolver.prototype.process = function (src) { 7570 src.connect(this.input); 7571 }; 7572 /** 7573 * If you load multiple impulse files using the .addImpulse method, 7574 * they will be stored as Objects in this Array. Toggle between them 7575 * with the <code>toggleImpulse(id)</code> method. 7576 * 7577 * @property impulses 7578 * @type {Array} Array of Web Audio Buffers 7579 */ 7580 p5.Convolver.prototype.impulses = []; 7581 /** 7582 * Load and assign a new Impulse Response to the p5.Convolver. 7583 * The impulse is added to the <code>.impulses</code> array. Previous 7584 * impulses can be accessed with the <code>.toggleImpulse(id)</code> 7585 * method. 7586 * 7587 * @method addImpulse 7588 * @param {String} path path to a sound file 7589 * @param {Function} callback function (optional) 7590 * @param {Function} errorCallback function (optional) 7591 */ 7592 p5.Convolver.prototype.addImpulse = function (path, callback, errorCallback) { 7593 // if loading locally without a server 7594 if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { 7595 alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); 7596 } 7597 this._loadBuffer(path, callback, errorCallback); 7598 }; 7599 /** 7600 * Similar to .addImpulse, except that the <code>.impulses</code> 7601 * Array is reset to save memory. A new <code>.impulses</code> 7602 * array is created with this impulse as the only item. 7603 * 7604 * @method resetImpulse 7605 * @param {String} path path to a sound file 7606 * @param {Function} callback function (optional) 7607 * @param {Function} errorCallback function (optional) 7608 */ 7609 p5.Convolver.prototype.resetImpulse = function (path, callback, errorCallback) { 7610 // if loading locally without a server 7611 if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { 7612 alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); 7613 } 7614 this.impulses = []; 7615 this._loadBuffer(path, callback, errorCallback); 7616 }; 7617 /** 7618 * If you have used <code>.addImpulse()</code> to add multiple impulses 7619 * to a p5.Convolver, then you can use this method to toggle between 7620 * the items in the <code>.impulses</code> Array. Accepts a parameter 7621 * to identify which impulse you wish to use, identified either by its 7622 * original filename (String) or by its position in the <code>.impulses 7623 * </code> Array (Number).<br/> 7624 * You can access the objects in the .impulses Array directly. Each 7625 * Object has two attributes: an <code>.audioBuffer</code> (type: 7626 * Web Audio <a href=" 7627 * http://webaudio.github.io/web-audio-api/#the-audiobuffer-interface"> 7628 * AudioBuffer)</a> and a <code>.name</code>, a String that corresponds 7629 * with the original filename. 7630 * 7631 * @method toggleImpulse 7632 * @param {String|Number} id Identify the impulse by its original filename 7633 * (String), or by its position in the 7634 * <code>.impulses</code> Array (Number). 7635 */ 7636 p5.Convolver.prototype.toggleImpulse = function (id) { 7637 if (typeof id === 'number' && id < this.impulses.length) { 7638 this.convolverNode.buffer = this.impulses[id].audioBuffer; 7639 } 7640 if (typeof id === 'string') { 7641 for (var i = 0; i < this.impulses.length; i++) { 7642 if (this.impulses[i].name === id) { 7643 this.convolverNode.buffer = this.impulses[i].audioBuffer; 7644 break; 7645 } 7646 } 7647 } 7648 }; 7649 p5.Convolver.prototype.dispose = function () { 7650 // remove all the Impulse Response buffers 7651 for (var i in this.impulses) { 7652 this.impulses[i] = null; 7653 } 7654 this.convolverNode.disconnect(); 7655 this.concolverNode = null; 7656 if (typeof this.output !== 'undefined') { 7657 this.output.disconnect(); 7658 this.output = null; 7659 } 7660 if (typeof this.panner !== 'undefined') { 7661 this.panner.disconnect(); 7662 this.panner = null; 7663 } 7664 }; 7665}(master, errorHandler, sndcore); 7666/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 7667var Tone_core_TimelineState; 7668Tone_core_TimelineState = function (Tone) { 7669 'use strict'; 7670 Tone.TimelineState = function (initial) { 7671 Tone.Timeline.call(this); 7672 this._initial = initial; 7673 }; 7674 Tone.extend(Tone.TimelineState, Tone.Timeline); 7675 Tone.TimelineState.prototype.getStateAtTime = function (time) { 7676 var event = this.getEvent(time); 7677 if (event !== null) { 7678 return event.state; 7679 } else { 7680 return this._initial; 7681 } 7682 }; 7683 Tone.TimelineState.prototype.setStateAtTime = function (state, time) { 7684 this.addEvent({ 7685 'state': state, 7686 'time': this.toSeconds(time) 7687 }); 7688 }; 7689 return Tone.TimelineState; 7690}(Tone_core_Tone, Tone_core_Timeline); 7691/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ 7692var Tone_core_Clock; 7693Tone_core_Clock = function (Tone) { 7694 'use strict'; 7695 Tone.Clock = function () { 7696 var options = this.optionsObject(arguments, [ 7697 'callback', 7698 'frequency' 7699 ], Tone.Clock.defaults); 7700 this.callback = options.callback; 7701 this._lookAhead = 'auto'; 7702 this._computedLookAhead = 1 / 60; 7703 this._threshold = 0.5; 7704 this._nextTick = -1; 7705 this._lastUpdate = 0; 7706 this._loopID = -1; 7707 this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency); 7708 this.ticks = 0; 7709 this._state = new Tone.TimelineState(Tone.State.Stopped); 7710 this._boundLoop = this._loop.bind(this); 7711 this._readOnly('frequency'); 7712 this._loop(); 7713 }; 7714 Tone.extend(Tone.Clock); 7715 Tone.Clock.defaults = { 7716 'callback': Tone.noOp, 7717 'frequency': 1, 7718 'lookAhead': 'auto' 7719 }; 7720 Object.defineProperty(Tone.Clock.prototype, 'state', { 7721 get: function () { 7722 return this._state.getStateAtTime(this.now()); 7723 } 7724 }); 7725 Object.defineProperty(Tone.Clock.prototype, 'lookAhead', { 7726 get: function () { 7727 return this._lookAhead; 7728 }, 7729 set: function (val) { 7730 if (val === 'auto') { 7731 this._lookAhead = 'auto'; 7732 } else { 7733 this._lookAhead = this.toSeconds(val); 7734 } 7735 } 7736 }); 7737 Tone.Clock.prototype.start = function (time, offset) { 7738 time = this.toSeconds(time); 7739 if (this._state.getStateAtTime(time) !== Tone.State.Started) { 7740 this._state.addEvent({ 7741 'state': Tone.State.Started, 7742 'time': time, 7743 'offset': offset 7744 }); 7745 } 7746 return this; 7747 }; 7748 Tone.Clock.prototype.stop = function (time) { 7749 time = this.toSeconds(time); 7750 if (this._state.getStateAtTime(time) !== Tone.State.Stopped) {
7751 this._state.setStateAtTime(Tone.State.Stopped, time); 7752 } 7753 return this; 7754 }; 7755 Tone.Clock.prototype.pause = function (time) { 7756 time = this.toSeconds(time); 7757 if (this._state.getStateAtTime(time) === Tone.State.Started) { 7758 this._state.setStateAtTime(Tone.State.Paused, time); 7759 } 7760 return this; 7761 }; 7762 Tone.Clock.prototype._loop = function (time) { 7763 this._loopID = requestAnimationFrame(this._boundLoop); 7764 if (this._lookAhead === 'auto') { 7765 if (!this.isUndef(time)) { 7766 var diff = (time - this._lastUpdate) / 1000; 7767 this._lastUpdate = time; 7768 if (diff < this._threshold) { 7769 this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; 7770 } 7771 } 7772 } else { 7773 this._computedLookAhead = this._lookAhead; 7774 } 7775 var now = this.now(); 7776 var lookAhead = this._computedLookAhead * 2; 7777 var event = this._state.getEvent(now + lookAhead); 7778 var state = Tone.State.Stopped; 7779 if (event) { 7780 state = event.state; 7781 if (this._nextTick === -1 && state === Tone.State.Started) { 7782 this._nextTick = event.time; 7783 if (!this.isUndef(event.offset)) { 7784 this.ticks = event.offset; 7785 } 7786 } 7787 } 7788 if (state === Tone.State.Started) { 7789 while (now + lookAhead > this._nextTick) { 7790 if (now > this._nextTick + this._threshold) { 7791 this._nextTick = now; 7792 } 7793 var tickTime = this._nextTick; 7794 this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick); 7795 this.callback(tickTime); 7796 this.ticks++; 7797 } 7798 } else if (state === Tone.State.Stopped) { 7799 this._nextTick = -1; 7800 this.ticks = 0; 7801 } 7802 }; 7803 Tone.Clock.prototype.getStateAtTime = function (time) { 7804 return this._state.getStateAtTime(time); 7805 }; 7806 Tone.Clock.prototype.dispose = function () { 7807 cancelAnimationFrame(this._loopID); 7808 Tone.TimelineState.prototype.dispose.call(this); 7809 this._writable('frequency'); 7810 this.frequency.dispose(); 7811 this.frequency = null; 7812 this._boundLoop = Tone.noOp; 7813 this._nextTick = Infinity; 7814 this.callback = null; 7815 this._state.dispose(); 7816 this._state = null; 7817 }; 7818 return Tone.Clock; 7819}(Tone_core_Tone, Tone_signal_TimelineSignal); 7820var metro; 7821metro = function () { 7822 'use strict'; 7823 var p5sound = master; 7824 // requires the Tone.js library's Clock (MIT license, Yotam Mann) 7825 // https://github.com/TONEnoTONE/Tone.js/ 7826 var Clock = Tone_core_Clock; 7827 var ac = p5sound.audiocontext; 7828 // var upTick = false; 7829 p5.Metro = function () { 7830 this.clock = new Clock({ 'callback': this.ontick.bind(this) }); 7831 this.syncedParts = []; 7832 this.bpm = 120; 7833 // gets overridden by p5.Part 7834 this._init(); 7835 this.tickCallback = function () { 7836 }; 7837 }; 7838 var prevTick = 0; 7839 var tatumTime = 0; 7840 p5.Metro.prototype.ontick = function (tickTime) { 7841 var elapsedTime = tickTime - prevTick; 7842 var secondsFromNow = tickTime - p5sound.audiocontext.currentTime; 7843 if (elapsedTime - tatumTime <= -0.02) { 7844 return; 7845 } else { 7846 prevTick = tickTime; 7847 // for all of the active things on the metro: 7848 for (var i in this.syncedParts) { 7849 var thisPart = this.syncedParts[i]; 7850 thisPart.incrementStep(secondsFromNow); 7851 // each synced source keeps track of its own beat number 7852 for (var j in thisPart.phrases) { 7853 var thisPhrase = thisPart.phrases[j]; 7854 var phraseArray = thisPhrase.sequence; 7855 var bNum = this.metroTicks % phraseArray.length; 7856 if (phraseArray[bNum] !== 0 && (this.metroTicks < phraseArray.length || !thisPhrase.looping)) { 7857 thisPhrase.callback(secondsFromNow, phraseArray[bNum]); 7858 } 7859 } 7860 } 7861 this.metroTicks += 1; 7862 this.tickCallback(secondsFromNow); 7863 } 7864 }; 7865 p5.Metro.prototype.setBPM = function (bpm, rampTime) { 7866 var beatTime = 60 / (bpm * this.tatums); 7867 var now = p5sound.audiocontext.currentTime; 7868 tatumTime = beatTime; 7869 var rampTime = rampTime || 0; 7870 this.clock.frequency.setValueAtTime(this.clock.frequency.value, now); 7871 this.clock.frequency.linearRampToValueAtTime(bpm, now + rampTime); 7872 this.bpm = bpm; 7873 }; 7874 p5.Metro.prototype.getBPM = function (tempo) { 7875 return this.clock.getRate() / this.tatums * 60; 7876 }; 7877 p5.Metro.prototype._init = function () {
7878 this.metroTicks = 0; 7879 }; 7880 // clear existing synced parts, add only this one 7881 p5.Metro.prototype.resetSync = function (part) { 7882 this.syncedParts = [part]; 7883 }; 7884 // push a new synced part to the array 7885 p5.Metro.prototype.pushSync = function (part) { 7886 this.syncedParts.push(part); 7887 }; 7888 p5.Metro.prototype.start = function (timeFromNow) { 7889 var t = timeFromNow || 0; 7890 var now = p5sound.audiocontext.currentTime; 7891 this.clock.start(now + t); 7892 this.setBPM(this.bpm); 7893 }; 7894 p5.Metro.prototype.stop = function (timeFromNow) { 7895 var t = timeFromNow || 0; 7896 var now = p5sound.audiocontext.currentTime; 7897 if (this.clock._oscillator) { 7898 this.clock.stop(now + t); 7899 } 7900 }; 7901 p5.Metro.prototype.beatLength = function (tatums) { 7902 this.tatums = 1 / tatums / 4; 7903 }; 7904}(master, Tone_core_Clock); 7905var looper; 7906looper = function () { 7907 'use strict'; 7908 var p5sound = master; 7909 var bpm = 120; 7910 /** 7911 * Set the global tempo, in beats per minute, for all 7912 * p5.Parts. This method will impact all active p5.Parts. 7913 * 7914 * @param {Number} BPM Beats Per Minute 7915 * @param {Number} rampTime Seconds from now 7916 */ 7917 p5.prototype.setBPM = function (BPM, rampTime) { 7918 bpm = BPM; 7919 for (var i in p5sound.parts) { 7920 p5sound.parts[i].setBPM(bpm, rampTime); 7921 } 7922 }; 7923 /** 7924 * <p>A phrase is a pattern of musical events over time, i.e. 7925 * a series of notes and rests.</p> 7926 * 7927 * <p>Phrases must be added to a p5.Part for playback, and 7928 * each part can play multiple phrases at the same time. 7929 * For example, one Phrase might be a kick drum, another 7930 * could be a snare, and another could be the bassline.</p> 7931 * 7932 * <p>The first parameter is a name so that the phrase can be 7933 * modified or deleted later. The callback is a a function that 7934 * this phrase will call at every step—for example it might be 7935 * called <code>playNote(value){}</code>. The array determines 7936 * which value is passed into the callback at each step of the 7937 * phrase. It can be numbers, an object with multiple numbers, 7938 * or a zero (0) indicates a rest so the callback won't be called).</p> 7939 * 7940 * @class p5.Phrase 7941 * @constructor 7942 * @param {String} name Name so that you can access the Phrase. 7943 * @param {Function} callback The name of a function that this phrase 7944 * will call. Typically it will play a sound, 7945 * and accept two parameters: a time at which 7946 * to play the sound (in seconds from now), 7947 * and a value from the sequence array. The 7948 * time should be passed into the play() or 7949 * start() method to ensure precision. 7950 * @param {Array} sequence Array of values to pass into the callback 7951 * at each step of the phrase. 7952 * @example 7953 * <div><code> 7954 * var mySound, myPhrase, myPart; 7955 * var pattern = [1,0,0,2,0,2,0,0]; 7956 * var msg = 'click to play'; 7957 * 7958 * function preload() { 7959 * mySound = loadSound('assets/beatbox.mp3'); 7960 * } 7961 * 7962 * function setup() { 7963 * noStroke(); 7964 * fill(255); 7965 * textAlign(CENTER); 7966 * masterVolume(0.1); 7967 * 7968 * myPhrase = new p5.Phrase('bbox', makeSound, pattern); 7969 * myPart = new p5.Part(); 7970 * myPart.addPhrase(myPhrase); 7971 * myPart.setBPM(60); 7972 * } 7973 * 7974 * function draw() { 7975 * background(0); 7976 * text(msg, width/2, height/2); 7977 * } 7978 * 7979 * function makeSound(time, playbackRate) { 7980 * mySound.rate(playbackRate); 7981 * mySound.play(time); 7982 * } 7983 * 7984 * function mouseClicked() { 7985 * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { 7986 * myPart.start(); 7987 * msg = 'playing pattern'; 7988 * } 7989 * } 7990 * 7991 * </code></div> 7992 */ 7993 p5.Phrase = function (name, callback, sequence) { 7994 this.phraseStep = 0; 7995 this.name = name; 7996 this.callback = callback; 7997 /** 7998 * Array of values to pass into the callback 7999 * at each step of the phrase. Depending on the callback 8000 * function's requirements, these values may be numbers, 8001 * strings, or an object with multiple parameters. 8002 * Zero (0) indicates a rest. 8003 * 8004 * @property sequence 8005 * @type {Array} 8006 */ 8007 this.sequence = sequence; 8008 }; 8009 /** 8010 * <p>A p5.Part plays back one or more p5.Phrases. Instantiate a part 8011 * with steps and tatums. By default, each step represents 1/16th note.</p> 8012 * 8013 * <p>See p5.Phrase for more about musical timing.</p> 8014 * 8015 * @class p5.Part 8016 * @constructor 8017 * @param {Number} [steps] Steps in the part 8018 * @param {Number} [tatums] Divisions of a beat (default is 1/16, a quarter note) 8019 * @example 8020 * <div><code> 8021 * var box, drum, myPart; 8022 * var boxPat = [1,0,0,2,0,2,0,0]; 8023 * var drumPat = [0,1,1,0,2,0,1,0]; 8024 * var msg = 'click to play'; 8025 * 8026 * function preload() { 8027 * box = loadSound('assets/beatbox.mp3'); 8028 * drum = loadSound('assets/drum.mp3'); 8029 * } 8030 * 8031 * function setup() { 8032 * noStroke(); 8033 * fill(255); 8034 * textAlign(CENTER); 8035 * masterVolume(0.1); 8036 * 8037 * var boxPhrase = new p5.Phrase('box', playBox, boxPat); 8038 * var drumPhrase = new p5.Phrase('drum', playDrum, drumPat); 8039 * myPart = new p5.Part(); 8040 * myPart.addPhrase(boxPhrase); 8041 * myPart.addPhrase(drumPhrase); 8042 * myPart.setBPM(60); 8043 * masterVolume(0.1); 8044 * } 8045 * 8046 * function draw() { 8047 * background(0); 8048 * text(msg, width/2, height/2); 8049 * } 8050 * 8051 * function playBox(time, playbackRate) { 8052 * box.rate(playbackRate); 8053 * box.play(time); 8054 * } 8055 * 8056 * function playDrum(time, playbackRate) { 8057 * drum.rate(playbackRate); 8058 * drum.play(time); 8059 * } 8060 * 8061 * function mouseClicked() { 8062 * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { 8063 * myPart.start(); 8064 * msg = 'playing part'; 8065 * } 8066 * } 8067 * </code></div> 8068 */ 8069 p5.Part = function (steps, bLength) { 8070 this.length = steps || 0; 8071 // how many beats
8072 this.partStep = 0; 8073 this.phrases = []; 8074 this.looping = false; 8075 this.isPlaying = false; 8076 // what does this looper do when it gets to the last step? 8077 this.onended = function () { 8078 this.stop(); 8079 }; 8080 this.tatums = bLength || 0.0625; 8081 // defaults to quarter note 8082 this.metro = new p5.Metro(); 8083 this.metro._init(); 8084 this.metro.beatLength(this.tatums); 8085 this.metro.setBPM(bpm); 8086 p5sound.parts.push(this); 8087 this.callback = function () { 8088 }; 8089 }; 8090 /** 8091 * Set the tempo of this part, in Beats Per Minute. 8092 * 8093 * @method setBPM 8094 * @param {Number} BPM Beats Per Minute 8095 * @param {Number} [rampTime] Seconds from now 8096 */ 8097 p5.Part.prototype.setBPM = function (tempo, rampTime) { 8098 this.metro.setBPM(tempo, rampTime); 8099 }; 8100 /** 8101 * Returns the Beats Per Minute of this currently part. 8102 * 8103 * @method getBPM 8104 * @return {Number} 8105 */ 8106 p5.Part.prototype.getBPM = function () { 8107 return this.metro.getBPM(); 8108 }; 8109 /** 8110 * Start playback of this part. It will play 8111 * through all of its phrases at a speed 8112 * determined by setBPM. 8113 * 8114 * @method start 8115 * @param {Number} [time] seconds from now 8116 */ 8117 p5.Part.prototype.start = function (time) { 8118 if (!this.isPlaying) { 8119 this.isPlaying = true; 8120 this.metro.resetSync(this); 8121 var t = time || 0; 8122 this.metro.start(t); 8123 } 8124 }; 8125 /** 8126 * Loop playback of this part. It will begin 8127 * looping through all of its phrases at a speed 8128 * determined by setBPM. 8129 * 8130 * @method loop 8131 * @param {Number} [time] seconds from now 8132 */ 8133 p5.Part.prototype.loop = function (time) { 8134 this.looping = true; 8135 // rest onended function 8136 this.onended = function () { 8137 this.partStep = 0; 8138 }; 8139 var t = time || 0; 8140 this.start(t); 8141 }; 8142 /** 8143 * Tell the part to stop looping. 8144 * 8145 * @method noLoop 8146 */ 8147 p5.Part.prototype.noLoop = function () { 8148 this.looping = false; 8149 // rest onended function 8150 this.onended = function () { 8151 this.stop(); 8152 }; 8153 }; 8154 /** 8155 * Stop the part and cue it to step 0. 8156 * 8157 * @method stop 8158 * @param {Number} [time] seconds from now 8159 */ 8160 p5.Part.prototype.stop = function (time) { 8161 this.partStep = 0; 8162 this.pause(time); 8163 }; 8164 /** 8165 * Pause the part. Playback will resume 8166 * from the current step. 8167 * 8168 * @method pause 8169 * @param {Number} time seconds from now 8170 */ 8171 p5.Part.prototype.pause = function (time) { 8172 this.isPlaying = false; 8173 var t = time || 0; 8174 this.metro.stop(t); 8175 }; 8176 /** 8177 * Add a p5.Phrase to this Part. 8178 * 8179 * @method addPhrase 8180 * @param {p5.Phrase} phrase reference to a p5.Phrase 8181 */ 8182 p5.Part.prototype.addPhrase = function (name, callback, array) { 8183 var p; 8184 if (arguments.length === 3) { 8185 p = new p5.Phrase(name, callback, array); 8186 } else if (arguments[0] instanceof p5.Phrase) { 8187 p = arguments[0]; 8188 } else { 8189 throw 'invalid input. addPhrase accepts name, callback, array or a p5.Phrase'; 8190 } 8191 this.phrases.push(p); 8192 // reset the length if phrase is longer than part's existing length 8193 if (p.sequence.length > this.length) { 8194 this.length = p.sequence.length; 8195 } 8196 }; 8197 /** 8198 * Remove a phrase from this part, based on the name it was 8199 * given when it was created. 8200 * 8201 * @method removePhrase 8202 * @param {String} phraseName 8203 */ 8204 p5.Part.prototype.removePhrase = function (name) { 8205 for (var i in this.phrases) { 8206 if (this.phrases[i].name === name) { 8207 this.phrases.split(i, 1); 8208 } 8209 } 8210 }; 8211 /** 8212 * Get a phrase from this part, based on the name it was 8213 * given when it was created. Now you can modify its array. 8214 * 8215 * @method getPhrase 8216 * @param {String} phraseName 8217 */ 8218 p5.Part.prototype.getPhrase = function (name) { 8219 for (var i in this.phrases) { 8220 if (this.phrases[i].name === name) { 8221 return this.phrases[i]; 8222 } 8223 } 8224 }; 8225 /** 8226 * Get a phrase from this part, based on the name it was 8227 * given when it was created. Now you can modify its array. 8228 * 8229 * @method replaceSequence 8230 * @param {String} phraseName 8231 * @param {Array} sequence Array of values to pass into the callback 8232 * at each step of the phrase. 8233 */ 8234 p5.Part.prototype.replaceSequence = function (name, array) { 8235 for (var i in this.phrases) { 8236 if (this.phrases[i].name === name) { 8237 this.phrases[i].sequence = array; 8238 } 8239 } 8240 }; 8241 p5.Part.prototype.incrementStep = function (time) { 8242 if (this.partStep < this.length - 1) { 8243 this.callback(time); 8244 this.partStep += 1; 8245 } else { 8246 if (this.looping) { 8247 this.callback(time); 8248 } 8249 this.onended();
8250 this.partStep = 0; 8251 } 8252 }; 8253 /** 8254 * Fire a callback function at every step. 8255 * 8256 * @method onStep 8257 * @param {Function} callback The name of the callback 8258 * you want to fire 8259 * on every beat/tatum. 8260 */ 8261 p5.Part.prototype.onStep = function (callback) { 8262 this.callback = callback; 8263 }; 8264 // =============== 8265 // p5.Score 8266 // =============== 8267 /** 8268 * A Score consists of a series of Parts. The parts will 8269 * be played back in order. For example, you could have an 8270 * A part, a B part, and a C part, and play them back in this order 8271 * <code>new p5.Score(a, a, b, a, c)</code> 8272 * 8273 * @class p5.Score 8274 * @constructor 8275 * @param {p5.Part} part(s) One or multiple parts, to be played in sequence. 8276 * @return {p5.Score} 8277 */ 8278 p5.Score = function () { 8279 // for all of the arguments 8280 this.parts = []; 8281 this.currentPart = 0; 8282 var thisScore = this; 8283 for (var i in arguments) { 8284 this.parts[i] = arguments[i]; 8285 this.parts[i].nextPart = this.parts[i + 1]; 8286 this.parts[i].onended = function () { 8287 thisScore.resetPart(i); 8288 playNextPart(thisScore); 8289 }; 8290 } 8291 this.looping = false; 8292 }; 8293 p5.Score.prototype.onended = function () { 8294 if (this.looping) { 8295 // this.resetParts(); 8296 this.parts[0].start(); 8297 } else { 8298 this.parts[this.parts.length - 1].onended = function () { 8299 this.stop(); 8300 this.resetParts(); 8301 }; 8302 } 8303 this.currentPart = 0; 8304 }; 8305 /** 8306 * Start playback of the score. 8307 * 8308 * @method start 8309 */ 8310 p5.Score.prototype.start = function () { 8311 this.parts[this.currentPart].start(); 8312 this.scoreStep = 0; 8313 }; 8314 /** 8315 * Stop playback of the score. 8316 * 8317 * @method stop 8318 */ 8319 p5.Score.prototype.stop = function () { 8320 this.parts[this.currentPart].stop(); 8321 this.currentPart = 0; 8322 this.scoreStep = 0; 8323 }; 8324 /** 8325 * Pause playback of the score. 8326 * 8327 * @method pause 8328 */ 8329 p5.Score.prototype.pause = function () { 8330 this.parts[this.currentPart].stop(); 8331 }; 8332 /** 8333 * Loop playback of the score. 8334 * 8335 * @method loop 8336 */ 8337 p5.Score.prototype.loop = function () { 8338 this.looping = true; 8339 this.start(); 8340 }; 8341 /** 8342 * Stop looping playback of the score. If it 8343 * is currently playing, this will go into effect 8344 * after the current round of playback completes. 8345 * 8346 * @method noLoop 8347 */ 8348 p5.Score.prototype.noLoop = function () { 8349 this.looping = false; 8350 }; 8351 p5.Score.prototype.resetParts = function () { 8352 for (var i in this.parts) { 8353 this.resetPart(i); 8354 } 8355 }; 8356 p5.Score.prototype.resetPart = function (i) { 8357 this.parts[i].stop(); 8358 this.parts[i].partStep = 0; 8359 for (var p in this.parts[i].phrases) { 8360 this.parts[i].phrases[p].phraseStep = 0; 8361 } 8362 }; 8363 /** 8364 * Set the tempo for all parts in the score 8365 * 8366 * @param {Number} BPM Beats Per Minute 8367 * @param {Number} rampTime Seconds from now 8368 */ 8369 p5.Score.prototype.setBPM = function (bpm, rampTime) { 8370 for (var i in this.parts) { 8371 this.parts[i].setBPM(bpm, rampTime); 8372 } 8373 }; 8374 function playNextPart(aScore) { 8375 aScore.currentPart++; 8376 if (aScore.currentPart >= aScore.parts.length) { 8377 aScore.scoreStep = 0; 8378 aScore.onended(); 8379 } else { 8380 aScore.scoreStep = 0; 8381 aScore.parts[aScore.currentPart - 1].stop(); 8382 aScore.parts[aScore.currentPart].start(); 8383 } 8384 } 8385}(master); 8386var soundRecorder; 8387soundRecorder = function () { 8388 'use strict'; 8389 var p5sound = master; 8390 var ac = p5sound.audiocontext; 8391 /** 8392 * <p>
8392Record sounds for playback and/or to save as a .wav file. 8393 * The p5.SoundRecorder records all sound output from your sketch, 8394 * or can be assigned a specific source with setInput().</p> 8395 * <p>The record() method accepts a p5.SoundFile as a parameter. 8396 * When playback is stopped (either after the given amount of time, 8397 * or with the stop() method), the p5.SoundRecorder will send its 8398 * recording to that p5.SoundFile for playback.</p> 8399 * 8400 * @class p5.SoundRecorder 8401 * @constructor 8402 * @example 8403 * <div><code> 8404 * var mic, recorder, soundFile; 8405 * var state = 0; 8406 * 8407 * function setup() { 8408 * background(200); 8409 * // create an audio in 8410 * mic = new p5.AudioIn(); 8411 * 8412 * // prompts user to enable their browser mic 8413 * mic.start(); 8414 * 8415 * // create a sound recorder 8416 * recorder = new p5.SoundRecorder(); 8417 * 8418 * // connect the mic to the recorder 8419 * recorder.setInput(mic); 8420 * 8421 * // this sound file will be used to 8422 * // playback & save the recording 8423 * soundFile = new p5.SoundFile(); 8424 * 8425 * text('keyPress to record', 20, 20); 8426 * } 8427 * 8428 * function keyPressed() { 8429 * // make sure user enabled the mic 8430 * if (state === 0 && mic.enabled) { 8431 * 8432 * // record to our p5.SoundFile 8433 * recorder.record(soundFile); 8434 * 8435 * background(255,0,0); 8436 * text('Recording!', 20, 20); 8437 * state++; 8438 * } 8439 * else if (state === 1) { 8440 * background(0,255,0); 8441 * 8442 * // stop recorder and 8443 * // send result to soundFile 8444 * recorder.stop(); 8445 * 8446 * text('Stopped', 20, 20); 8447 * state++; 8448 * } 8449 * 8450 * else if (state === 2) { 8451 * soundFile.play(); // play the result! 8452 * save(soundFile, 'mySound.wav'); 8453 * state++; 8454 * } 8455 * } 8456 * </div></code> 8457 */ 8458 p5.SoundRecorder = function () { 8459 this.input = ac.createGain(); 8460 this.output = ac.createGain(); 8461 this.recording = false; 8462 this.bufferSize = 1024; 8463 this._channels = 2; 8464 // stereo (default) 8465 this._clear(); 8466 // initialize variables 8467 this._jsNode = ac.createScriptProcessor(this.bufferSize, this._channels, 2); 8468 this._jsNode.onaudioprocess = this._audioprocess.bind(this); 8469 /** 8470 * callback invoked when the recording is over 8471 * @private 8472 * @type {function(Float32Array)} 8473 */ 8474 this._callback = function () { 8475 }; 8476 // connections 8477 this._jsNode.connect(p5.soundOut._silentNode); 8478 this.setInput(); 8479 // add this p5.SoundFile to the soundArray 8480 p5sound.soundArray.push(this); 8481 }; 8482 /** 8483 * Connect a specific device to the p5.SoundRecorder. 8484 * If no parameter is given, p5.SoundRecorer will record 8485 * all audible p5.sound from your sketch. 8486 * 8487 * @method setInput 8488 * @param {Object} [unit] p5.sound object or a web audio unit 8489 * that outputs sound 8490 */ 8491 p5.SoundRecorder.prototype.setInput = function (unit) { 8492 this.input.disconnect(); 8493 this.input = null; 8494 this.input = ac.createGain(); 8495 this.input.connect(this._jsNode); 8496 this.input.connect(this.output); 8497 if (unit) { 8498 unit.connect(this.input); 8499 } else { 8500 p5.soundOut.output.connect(this.input); 8501 } 8502 }; 8503 /** 8504 * Start recording. To access the recording, provide 8505 * a p5.SoundFile as the first parameter. The p5.SoundRecorder 8506 * will send its recording to that p5.SoundFile for playback once 8507 * recording is complete. Optional parameters include duration 8508 * (in seconds) of the recording, and a callback function that 8509 * will be called once the complete recording has been 8510 * transfered to the p5.SoundFile. 8511 * 8512 * @method record 8513 * @param {p5.SoundFile} soundFile p5.SoundFile 8514 * @param {Number} [duration] Time (in seconds) 8515 * @param {Function} [callback] The name of a function that will be 8516 * called once the recording completes 8517 */ 8518 p5.SoundRecorder.prototype.record = function (sFile, duration, callback) { 8519 this.recording = true; 8520 if (duration) { 8521 this.sampleLimit = Math.round(duration * ac.sampleRate); 8522 } 8523 if (sFile && callback) { 8524 this._callback = function () { 8525 this.buffer = this._getBuffer(); 8526 sFile.setBuffer(this.buffer); 8527 callback(); 8528 }; 8529 } else if (sFile) { 8530 this._callback = function () { 8531 this.buffer = this._getBuffer(); 8532 sFile.setBuffer(this.buffer); 8533 }; 8534 } 8535 }; 8536 /** 8537 * Stop the recording. Once the recording is stopped, 8538 * the results will be sent to the p5.SoundFile that 8539 * was given on .record(), and if a callback function 8540 * was provided on record, that function will be called. 8541 * 8542 * @method stop 8543 */ 8544 p5.SoundRecorder.prototype.stop = function () { 8545 this.recording = false;
8546 this._callback(); 8547 this._clear(); 8548 }; 8549 p5.SoundRecorder.prototype._clear = function () { 8550 this._leftBuffers = []; 8551 this._rightBuffers = []; 8552 this.recordedSamples = 0; 8553 this.sampleLimit = null; 8554 }; 8555 /** 8556 * internal method called on audio process 8557 * 8558 * @private 8559 * @param {AudioProcessorEvent} event 8560 */ 8561 p5.SoundRecorder.prototype._audioprocess = function (event) { 8562 if (this.recording === false) { 8563 return; 8564 } else if (this.recording === true) { 8565 // if we are past the duration, then stop... else: 8566 if (this.sampleLimit && this.recordedSamples >= this.sampleLimit) { 8567 this.stop(); 8568 } else { 8569 // get channel data 8570 var left = event.inputBuffer.getChannelData(0); 8571 var right = event.inputBuffer.getChannelData(1); 8572 // clone the samples 8573 this._leftBuffers.push(new Float32Array(left)); 8574 this._rightBuffers.push(new Float32Array(right)); 8575 this.recordedSamples += this.bufferSize; 8576 } 8577 } 8578 }; 8579 p5.SoundRecorder.prototype._getBuffer = function () { 8580 var buffers = []; 8581 buffers.push(this._mergeBuffers(this._leftBuffers)); 8582 buffers.push(this._mergeBuffers(this._rightBuffers)); 8583 return buffers; 8584 }; 8585 p5.SoundRecorder.prototype._mergeBuffers = function (channelBuffer) { 8586 var result = new Float32Array(this.recordedSamples); 8587 var offset = 0; 8588 var lng = channelBuffer.length; 8589 for (var i = 0; i < lng; i++) { 8590 var buffer = channelBuffer[i]; 8591 result.set(buffer, offset); 8592 offset += buffer.length; 8593 } 8594 return result; 8595 }; 8596 p5.SoundRecorder.prototype.dispose = function () { 8597 this._clear(); 8598 // remove reference from soundArray 8599 var index = p5sound.soundArray.indexOf(this); 8600 p5sound.soundArray.splice(index, 1); 8601 this._callback = function () { 8602 }; 8603 if (this.input) { 8604 this.input.disconnect(); 8605 } 8606 this.input = null; 8607 this._jsNode = null; 8608 }; 8609 /** 8610 * Save a p5.SoundFile as a .wav audio file. 8611 * 8612 * @method saveSound 8613 * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save 8614 * @param {String} name name of the resulting .wav file. 8615 */ 8616 p5.prototype.saveSound = function (soundFile, name) { 8617 var leftChannel = soundFile.buffer.getChannelData(0); 8618 var rightChannel = soundFile.buffer.getChannelData(1); 8619 var interleaved = interleave(leftChannel, rightChannel); 8620 // create the buffer and view to create the .WAV file 8621 var buffer = new ArrayBuffer(44 + interleaved.length * 2); 8622 var view = new DataView(buffer); 8623 // write the WAV container, 8624 // check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ 8625 // RIFF chunk descriptor 8626 writeUTFBytes(view, 0, 'RIFF'); 8627 view.setUint32(4, 44 + interleaved.length * 2, true); 8628 writeUTFBytes(view, 8, 'WAVE'); 8629 // FMT sub-chunk 8630 writeUTFBytes(view, 12, 'fmt '); 8631 view.setUint32(16, 16, true); 8632 view.setUint16(20, 1, true); 8633 // stereo (2 channels) 8634 view.setUint16(22, 2, true); 8635 view.setUint32(24, 44100, true); 8636 view.setUint32(28, 44100 * 4, true); 8637 view.setUint16(32, 4, true); 8638 view.setUint16(34, 16, true); 8639 // data sub-chunk 8640 writeUTFBytes(view, 36, 'data'); 8641 view.setUint32(40, interleaved.length * 2, true); 8642 // write the PCM samples 8643 var lng = interleaved.length; 8644 var index = 44; 8645 var volume = 1; 8646 for (var i = 0; i < lng; i++) { 8647 view.setInt16(index, interleaved[i] * (32767 * volume), true); 8648 index += 2; 8649 } 8650 p5.prototype.writeFile([view], name, 'wav'); 8651 }; 8652 // helper methods to save waves 8653 function interleave(leftChannel, rightChannel) { 8654 var length = leftChannel.length + rightChannel.length; 8655 var result = new Float32Array(length); 8656 var inputIndex = 0; 8657 for (var index = 0; index < length;) { 8658 result[index++] = leftChannel[inputIndex]; 8659 result[index++] = rightChannel[inputIndex]; 8660 inputIndex++; 8661 } 8662 return result; 8663 } 8664 function writeUTFBytes(view, offset, string) { 8665 var lng = string.length; 8666 for (var i = 0; i < lng; i++) { 8667 view.setUint8(offset + i, string.charCodeAt(i)); 8668 } 8669 } 8670}(sndcore, master); 8671var peakdetect; 8672peakdetect = function () { 8673 'use strict'; 8674 var p5sound = master; 8675 /** 8676 * <p>PeakDetect works in conjunction with p5.FFT to 8677 * look for onsets in some or all of the frequency spectrum. 8678 * </p> 8679 * <p> 8680 * To use p5.PeakDetect, call <code>update</code> in the draw loop 8681 * and pass in a p5.FFT object. 8682 * </p> 8683 * <p> 8684 * You can listen for a specific part of the frequency spectrum by 8685 * setting the range between <code>freq1</code> and <code>freq2</code>. 8686 * </p> 8687 * 8688 * <p><code>threshold</code> is the threshold for detecting a peak, 8689 * scaled between 0 and 1. It is logarithmic, so 0.1 is half as loud 8690 * as 1.0.</p> 8691 * 8692 * <p> 8693 * The update method is meant to be run in the draw loop, and 8694 * <b>frames</b> determines how many loops must pass before 8695 * another peak can be detected. 8696 * For example, if the frameRate() = 60, you could detect the beat of a 8697 * 120 beat-per-minute song with this equation: 8698 * <code> framesPerPeak = 60 / (estimatedBPM / 60 );</code> 8699 * </p> 8700 * 8701 * <p> 8702 * Based on example contribtued by @b2renger, and a simple beat detection 8703 * explanation by <a 8704 * href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/" 8705 * target="_blank">Felix Turner</a>. 8706 * </p> 8707 * 8708 * @class p5.PeakDetect 8709 * @constructor 8710 * @param {Number} [freq1] lowFrequency - defaults to 20Hz 8711 * @param {Number} [freq2] highFrequency - defaults to 20000 Hz 8712 * @param {Number} [threshold] Threshold for detecting a beat between 0 and 1 8713 * scaled logarithmically where 0.1 is 1/2 the loudness 8714 * of 1.0. Defaults to 0.35. 8715 * @param {Number} [framesPerPeak] Defaults to 20. 8716 * @example 8717 * <div><code> 8718 * 8719 * var cnv, soundFile, fft, peakDetect; 8720 * var ellipseWidth = 10; 8721 * 8722 * function setup() { 8723 * background(0); 8724 * noStroke(); 8725 * fill(255); 8726 * textAlign(CENTER); 8727 * 8728 * soundFile = loadSound('assets/beat.mp3'); 8729 * 8730 * // p5.PeakDetect requires a p5.FFT 8731 * fft = new p5.FFT(); 8732 * peakDetect = new p5.PeakDetect(); 8733 * 8734 * } 8735 * 8736 * function draw() { 8737 * background(0); 8738 * text('click to play/pause', width/2, height/2); 8739 * 8740 * // peakDetect accepts an fft post-analysis 8741 * fft.analyze(); 8742 * peakDetect.update(fft); 8743 * 8744 * if ( peakDetect.isDetected ) { 8745 * ellipseWidth = 50; 8746 * } else { 8747 * ellipseWidth *= 0.95; 8748 * } 8749 * 8750 * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); 8751 * } 8752 * 8753 * // toggle play/stop when canvas is clicked 8754 * function mouseClicked() { 8755 * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { 8756 * if (soundFile.isPlaying() ) { 8757 * soundFile.stop(); 8758 * } else { 8759 * soundFile.play(); 8760 * } 8761 * } 8762 * } 8763 * </code></div> 8764 */ 8765 p5.PeakDetect = function (freq1, freq2, threshold, _framesPerPeak) { 8766 var framesPerPeak; 8767 // framesPerPeak determines how often to look for a beat. 8768 // If a beat is provided, try to look for a beat based on bpm 8769 this.framesPerPeak = _framesPerPeak || 20; 8770 this.framesSinceLastPeak = 0; 8771 this.decayRate = 0.95; 8772 this.threshold = threshold || 0.35; 8773 this.cutoff = 0; 8774 // how much to increase the cutoff 8775 // TO DO: document this / figure out how to make it accessible 8776 this.cutoffMult = 1.5; 8777 this.energy = 0; 8778 this.penergy = 0; 8779 // TO DO: document this property / figure out how to make it accessible 8780 this.currentValue = 0; 8781 /** 8782 * isDetected is set to true when a peak is detected. 8783 * 8784 * @attribute isDetected 8785 * @type {Boolean} 8786 * @default false 8787 */ 8788 this.isDetected = false;
8789 this.f1 = freq1 || 40; 8790 this.f2 = freq2 || 20000; 8791 // function to call when a peak is detected 8792 this._onPeak = function () { 8793 }; 8794 }; 8795 /** 8796 * The update method is run in the draw loop. 8797 * 8798 * Accepts an FFT object. You must call .analyze() 8799 * on the FFT object prior to updating the peakDetect 8800 * because it relies on a completed FFT analysis. 8801 * 8802 * @method update 8803 * @param {p5.FFT} fftObject A p5.FFT object 8804 */ 8805 p5.PeakDetect.prototype.update = function (fftObject) { 8806 var nrg = this.energy = fftObject.getEnergy(this.f1, this.f2) / 255; 8807 if (nrg > this.cutoff && nrg > this.threshold && nrg - this.penergy > 0) { 8808 // trigger callback 8809 this._onPeak(); 8810 this.isDetected = true; 8811 // debounce 8812 this.cutoff = nrg * this.cutoffMult; 8813 this.framesSinceLastPeak = 0; 8814 } else { 8815 this.isDetected = false; 8816 if (this.framesSinceLastPeak <= this.framesPerPeak) { 8817 this.framesSinceLastPeak++; 8818 } else { 8819 this.cutoff *= this.decayRate; 8820 this.cutoff = Math.max(this.cutoff, this.threshold); 8821 } 8822 } 8823 this.currentValue = nrg; 8824 this.penergy = nrg; 8825 }; 8826 /** 8827 * onPeak accepts two arguments: a function to call when 8828 * a peak is detected. The value of the peak, 8829 * between 0.0 and 1.0, is passed to the callback. 8830 * 8831 * @method onPeak 8832 * @param {Function} callback Name of a function that will 8833 * be called when a peak is 8834 * detected. 8835 * @param {Object} [val] Optional value to pass 8836 * into the function when 8837 * a peak is detected. 8838 * @example 8839 * <div><code> 8840 * var cnv, soundFile, fft, peakDetect; 8841 * var ellipseWidth = 0; 8842 * 8843 * function setup() { 8844 * cnv = createCanvas(100,100); 8845 * textAlign(CENTER); 8846 * 8847 * soundFile = loadSound('assets/beat.mp3'); 8848 * fft = new p5.FFT(); 8849 * peakDetect = new p5.PeakDetect(); 8850 * 8851 * setupSound(); 8852 * 8853 * // when a beat is detected, call triggerBeat() 8854 * peakDetect.onPeak(triggerBeat); 8855 * } 8856 * 8857 * function draw() { 8858 * background(0); 8859 * fill(255); 8860 * text('click to play', width/2, height/2); 8861 * 8862 * fft.analyze(); 8863 * peakDetect.update(fft); 8864 * 8865 * ellipseWidth *= 0.95; 8866 * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); 8867 * } 8868 * 8869 * // this function is called by peakDetect.onPeak 8870 * function triggerBeat() { 8871 * ellipseWidth = 50; 8872 * } 8873 * 8874 * // mouseclick starts/stops sound 8875 * function setupSound() { 8876 * cnv.mouseClicked( function() { 8877 * if (soundFile.isPlaying() ) { 8878 * soundFile.stop(); 8879 * } else { 8880 * soundFile.play(); 8881 * } 8882 * }); 8883 * } 8884 * </code></div> 8885 */ 8886 p5.PeakDetect.prototype.onPeak = function (callback, val) { 8887 var self = this; 8888 self._onPeak = function () { 8889 callback(self.energy, val); 8890 }; 8891 }; 8892}(master); 8893var gain; 8894gain = function () { 8895 'use strict'; 8896 var p5sound = master; 8897 /** 8898 * A gain node is usefull to set the relative volume of sound. 8899 * It's typically used to build mixers. 8900 * 8901 * @class p5.Gain 8902 * @constructor 8903 * @example 8904 * <div><code> 8905 * 8906 * // load two soundfile and crossfade beetween them 8907 * var sound1,sound2; 8908 * var gain1, gain2, gain3; 8909 * 8910 * function preload(){ 8911 * soundFormats('ogg', 'mp3'); 8912 * sound1 = loadSound('../_files/Damscray_-_Dancing_Tiger_01'); 8913 * sound2 = loadSound('../_files/beat.mp3'); 8914 * } 8915 * 8916 * function setup() { 8917 * createCanvas(400,200); 8918 * 8919 * // create a 'master' gain to which we will connect both soundfiles 8920 * gain3 = new p5.Gain(); 8921 * gain3.connect(); 8922 * 8923 * // setup first sound for playing 8924 * sound1.rate(1); 8925 * sound1.loop(); 8926 * sound1.disconnect(); // diconnect from p5 output 8927 * 8928 * gain1 = new p5.Gain(); // setup a gain node 8929 * gain1.setInput(sound1); // connect the first sound to its input 8930 * gain1.connect(gain3); // connect its output to the 'master' 8931 * 8932 * sound2.rate(1); 8933 * sound2.disconnect(); 8934 * sound2.loop(); 8935 * 8936 * gain2 = new p5.Gain(); 8937 * gain2.setInput(sound2); 8938 * gain2.connect(gain3); 8939 * 8940 * } 8941 * 8942 * function draw(){ 8943 * background(180); 8944 * 8945 * // calculate the horizontal distance beetween the mouse and the right of the screen 8946 * var d = dist(mouseX,0,width,0); 8947 * 8948 * // map the horizontal position of the mouse to values useable for volume control of sound1 8949 * var vol1 = map(mouseX,0,width,0,1); 8950 * var vol2 = 1-vol1; // when sound1 is loud, sound2 is quiet and vice versa 8951 * 8952 * gain1.amp(vol1,0.5,0); 8953 * gain2.amp(vol2,0.5,0); 8954 * 8955 * // map the vertical position of the mouse to values useable for 'master volume control' 8956 * var vol3 = map(mouseY,0,height,0,1); 8957 * gain3.amp(vol3,0.5,0); 8958 * } 8959 *</code></div> 8960 * 8961 */ 8962 p5.Gain = function () { 8963 this.ac = p5sound.audiocontext; 8964 this.input = this.ac.createGain(); 8965 this.output = this.ac.createGain(); 8966 // otherwise, Safari distorts 8967 this.input.gain.value = 0.5; 8968 this.input.connect(this.output); 8969 // add to the soundArray 8970 p5sound.soundArray.push(this); 8971 }; 8972 /** 8973 * Connect a source to the gain node. 8974 * 8975 * @method setInput 8976 * @param {Object} src p5.sound / Web Audio object with a sound 8977 * output. 8978 */ 8979 p5.Gain.prototype.setInput = function (src) { 8980 src.connect(this.input); 8981 }; 8982 /** 8983 * Send output to a p5.sound or web audio object 8984 * 8985 * @method connect 8986 * @param {Object} unit 8987 */ 8988 p5.Gain.prototype.connect = function (unit) { 8989 var u = unit || p5.soundOut.input; 8990 this.output.connect(u.input ? u.input : u); 8991 }; 8992 /** 8993 * Disconnect all output. 8994 * 8995 * @method disconnect 8996 */ 8997 p5.Gain.prototype.disconnect = function () { 8998 this.output.disconnect(); 8999 }; 9000 /** 9001 * Set the output level of the gain node. 9002 * 9003 * @method amp 9004 * @param {Number} volume amplitude between 0 and 1.0 9005 * @param {Number} [rampTime] create a fade that lasts rampTime 9006 * @param {Number} [timeFromNow] schedule this event to happen 9007 * seconds from now 9008 */ 9009 p5.Gain.prototype.amp = function (vol, rampTime, tFromNow) { 9010 var rampTime = rampTime || 0; 9011 var tFromNow = tFromNow || 0; 9012 var now = p5sound.audiocontext.currentTime; 9013 var currentVol = this.output.gain.value; 9014 this.output.gain.cancelScheduledValues(now); 9015 this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); 9016 this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); 9017 }; 9018 p5.Gain.prototype.dispose = function () { 9019 // remove reference from soundArray 9020 var index = p5sound.soundArray.indexOf(this); 9021 p5sound.soundArray.splice(index, 1); 9022 this.output.disconnect(); 9023 this.input.disconnect(); 9024 this.output = undefined; 9025 this.input = undefined; 9026 }; 9027}(master, sndcore); 9028var src_app; 9029src_app = function () { 9030 'use strict'; 9031 var p5SOUND = sndcore; 9032 return p5SOUND; 9033}(sndcore, master, helpers, errorHandler, panner, soundfile, amplitude, fft, signal, oscillator, env, pulse, noise, audioin, filter, delay, reverb, metro, looper, soundRecorder, peakdetect, gain); 9034}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.