From f2385beabb0434f900cee687b4adc6f850d0c69c Mon Sep 17 00:00:00 2001 From: 12913378 <harry.buttenshaw@student.uts.edu.au> Date: Sun, 27 Oct 2019 19:14:48 +1100 Subject: [PATCH] Upload New File --- Code/p5.sound.js | 12879 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 12879 insertions(+) create mode 100644 Code/p5.sound.js diff --git a/Code/p5.sound.js b/Code/p5.sound.js new file mode 100644 index 0000000..33b1304 --- /dev/null +++ b/Code/p5.sound.js @@ -0,0 +1,12879 @@ +/*! p5.sound.js v0.3.11 2019-03-14 */ +/** + * p5.sound extends p5 with <a href="http://caniuse.com/audio-api" + * target="_blank">Web Audio</a> functionality including audio input, + * playback, analysis and synthesis. + * <br/><br/> + * <a href="#/p5.SoundFile"><b>p5.SoundFile</b></a>: Load and play sound files.<br/> + * <a href="#/p5.Amplitude"><b>p5.Amplitude</b></a>: Get the current volume of a sound.<br/> + * <a href="#/p5.AudioIn"><b>p5.AudioIn</b></a>: Get sound from an input source, typically + * a computer microphone.<br/> + * <a href="#/p5.FFT"><b>p5.FFT</b></a>: Analyze the frequency of sound. Returns + * results from the frequency spectrum or time domain (waveform).<br/> + * <a href="#/p5.Oscillator"><b>p5.Oscillator</b></a>: Generate Sine, + * Triangle, Square and Sawtooth waveforms. Base class of + * <a href="#/p5.Noise">p5.Noise</a> and <a href="#/p5.Pulse">p5.Pulse</a>. + * <br/> + * <a href="#/p5.Envelope"><b>p5.Envelope</b></a>: An Envelope is a series + * of fades over time. Often used to control an object's + * output gain level as an "ADSR Envelope" (Attack, Decay, + * Sustain, Release). Can also modulate other parameters.<br/> + * <a href="#/p5.Delay"><b>p5.Delay</b></a>: A delay effect with + * parameters for feedback, delayTime, and lowpass filter.<br/> + * <a href="#/p5.Filter"><b>p5.Filter</b></a>: Filter the frequency range of a + * sound. + * <br/> + * <a href="#/p5.Reverb"><b>p5.Reverb</b></a>: Add reverb to a sound by specifying + * duration and decay. <br/> + * <b><a href="#/p5.Convolver">p5.Convolver</a>:</b> Extends + * <a href="#/p5.Reverb">p5.Reverb</a> to simulate the sound of real + * physical spaces through convolution.<br/> + * <b><a href="#/p5.SoundRecorder">p5.SoundRecorder</a></b>: Record sound for playback + * / save the .wav file. + * <b><a href="#/p5.Phrase">p5.Phrase</a></b>, <b><a href="#/p5.Part">p5.Part</a></b> and + * <b><a href="#/p5.Score">p5.Score</a></b>: Compose musical sequences. + * <br/><br/> + * p5.sound is on <a href="https://github.com/therewasaguy/p5.sound/">GitHub</a>. + * Download the latest version + * <a href="https://github.com/therewasaguy/p5.sound/blob/master/lib/p5.sound.js">here</a>. + * + * @module p5.sound + * @submodule p5.sound + * @for p5.sound + * @main + */ + +/** + * p5.sound + * https://p5js.org/reference/#/libraries/p5.sound + * + * From the Processing Foundation and contributors + * https://github.com/processing/p5.js-sound/graphs/contributors + * + * MIT License (MIT) + * https://github.com/processing/p5.js-sound/blob/master/LICENSE + * + * Some of the many audio libraries & resources that inspire p5.sound: + * - TONE.js (c) Yotam Mann. Licensed under The MIT License (MIT). https://github.com/TONEnoTONE/Tone.js + * - buzz.js (c) Jay Salvat. Licensed under The MIT License (MIT). http://buzz.jaysalvat.com/ + * - Boris Smus Web Audio API book, 2013. Licensed under the Apache License http://www.apache.org/licenses/LICENSE-2.0 + * - wavesurfer.js https://github.com/katspaugh/wavesurfer.js + * - Web Audio Components by Jordan Santell https://github.com/web-audio-components + * - Wilm Thoben's Sound library for Processing https://github.com/processing/processing/tree/master/java/libraries/sound + * + * Web Audio API: http://w3.org/TR/webaudio/ + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) + define('p5.sound', ['p5'], function (p5) { (factory(p5));}); + else if (typeof exports === 'object') + factory(require('../p5')); + else + factory(root['p5']); +}(this, function (p5) { + +var shims; +'use strict'; /** + * This module has shims + */ +shims = function () { + /* AudioContext Monkeypatch + Copyright 2013 Chris Wilson + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + (function () { + function fixSetTarget(param) { + if (!param) + // if NYI, just return + return; + if (!param.setTargetAtTime) + param.setTargetAtTime = param.setTargetValueAtTime; + } + if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) { + window.AudioContext = window.webkitAudioContext; + if (typeof AudioContext.prototype.createGain !== 'function') + AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; + if (typeof AudioContext.prototype.createDelay !== 'function') + AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; + if (typeof AudioContext.prototype.createScriptProcessor !== 'function') + AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode; + if (typeof AudioContext.prototype.createPeriodicWave !== 'function') + AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; + AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain; + AudioContext.prototype.createGain = function () { + var node = this.internal_createGain(); + fixSetTarget(node.gain); + return node; + }; + AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay; + AudioContext.prototype.createDelay = function (maxDelayTime) { + var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay(); + fixSetTarget(node.delayTime); + return node; + }; + AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource; + AudioContext.prototype.createBufferSource = function () { + var node = this.internal_createBufferSource(); + if (!node.start) { + node.start = function (when, offset, duration) { + if (offset || duration) + this.noteGrainOn(when || 0, offset, duration); + else + this.noteOn(when || 0); + }; + } else { + node.internal_start = node.start; + node.start = function (when, offset, duration) { + if (typeof duration !== 'undefined') + node.internal_start(when || 0, offset, duration); + else + node.internal_start(when || 0, offset || 0); + }; + } + if (!node.stop) { + node.stop = function (when) { + this.noteOff(when || 0); + }; + } else { + node.internal_stop = node.stop; + node.stop = function (when) { + node.internal_stop(when || 0); + }; + } + fixSetTarget(node.playbackRate); + return node; + }; + AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor; + AudioContext.prototype.createDynamicsCompressor = function () { + var node = this.internal_createDynamicsCompressor(); + fixSetTarget(node.threshold); + fixSetTarget(node.knee); + fixSetTarget(node.ratio); + fixSetTarget(node.reduction); + fixSetTarget(node.attack); + fixSetTarget(node.release); + return node; + }; + AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter; + AudioContext.prototype.createBiquadFilter = function () { + var node = this.internal_createBiquadFilter(); + fixSetTarget(node.frequency); + fixSetTarget(node.detune); + fixSetTarget(node.Q); + fixSetTarget(node.gain); + return node; + }; + if (typeof AudioContext.prototype.createOscillator !== 'function') { + AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator; + AudioContext.prototype.createOscillator = function () { + var node = this.internal_createOscillator(); + if (!node.start) { + node.start = function (when) { + this.noteOn(when || 0); + }; + } else { + node.internal_start = node.start; + node.start = function (when) { + node.internal_start(when || 0); + }; + } + if (!node.stop) { + node.stop = function (when) { + this.noteOff(when || 0); + }; + } else { + node.internal_stop = node.stop; + node.stop = function (when) { + node.internal_stop(when || 0); + }; + } + if (!node.setPeriodicWave) + node.setPeriodicWave = node.setWaveTable; + fixSetTarget(node.frequency); + fixSetTarget(node.detune); + return node; + }; + } + } + if (window.hasOwnProperty('webkitOfflineAudioContext') && !window.hasOwnProperty('OfflineAudioContext')) { + window.OfflineAudioContext = window.webkitOfflineAudioContext; + } + }(window)); + // <-- end MonkeyPatch. + // Polyfill for AudioIn, also handled by p5.dom createCapture + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + /** + * Determine which filetypes are supported (inspired by buzz.js) + * The audio element (el) will only be used to test browser support for various audio formats + */ + var el = document.createElement('audio'); + p5.prototype.isSupported = function () { + return !!el.canPlayType; + }; + var isOGGSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/ogg; codecs="vorbis"'); + }; + var isMP3Supported = function () { + return !!el.canPlayType && el.canPlayType('audio/mpeg;'); + }; + var isWAVSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/wav; codecs="1"'); + }; + var isAACSupported = function () { + return !!el.canPlayType && (el.canPlayType('audio/x-m4a;') || el.canPlayType('audio/aac;')); + }; + var isAIFSupported = function () { + return !!el.canPlayType && el.canPlayType('audio/x-aiff;'); + }; + p5.prototype.isFileSupported = function (extension) { + switch (extension.toLowerCase()) { + case 'mp3': + return isMP3Supported(); + case 'wav': + return isWAVSupported(); + case 'ogg': + return isOGGSupported(); + case 'aac': + case 'm4a': + case 'mp4': + return isAACSupported(); + case 'aif': + case 'aiff': + return isAIFSupported(); + default: + return false; + } + }; +}(); +var StartAudioContext; +(function (root, factory) { + if (true) { + StartAudioContext = function () { + return factory(); + }(); + } else if (typeof module === 'object' && module.exports) { + module.exports = factory(); + } else { + root.StartAudioContext = factory(); + } +}(this, function () { + var TapListener = function (element, context) { + this._dragged = false; + this._element = element; + this._bindedMove = this._moved.bind(this); + this._bindedEnd = this._ended.bind(this, context); + element.addEventListener('touchstart', this._bindedEnd); + element.addEventListener('touchmove', this._bindedMove); + element.addEventListener('touchend', this._bindedEnd); + element.addEventListener('mouseup', this._bindedEnd); + }; + TapListener.prototype._moved = function (e) { + this._dragged = true; + }; + TapListener.prototype._ended = function (context) { + if (!this._dragged) { + startContext(context); + } + this._dragged = false; + }; + TapListener.prototype.dispose = function () { + this._element.removeEventListener('touchstart', this._bindedEnd); + this._element.removeEventListener('touchmove', this._bindedMove); + this._element.removeEventListener('touchend', this._bindedEnd); + this._element.removeEventListener('mouseup', this._bindedEnd); + this._bindedMove = null; + this._bindedEnd = null; + this._element = null; + }; + function startContext(context) { + var buffer = context.createBuffer(1, 1, context.sampleRate); + var source = context.createBufferSource(); + source.buffer = buffer; + source.connect(context.destination); + source.start(0); + if (context.resume) { + context.resume(); + } + } + function isStarted(context) { + return context.state === 'running'; + } + function onStarted(context, callback) { + function checkLoop() { + if (isStarted(context)) { + callback(); + } else { + requestAnimationFrame(checkLoop); + if (context.resume) { + context.resume(); + } + } + } + if (isStarted(context)) { + callback(); + } else { + checkLoop(); + } + } + function bindTapListener(element, tapListeners, context) { + if (Array.isArray(element) || NodeList && element instanceof NodeList) { + for (var i = 0; i < element.length; i++) { + bindTapListener(element[i], tapListeners, context); + } + } else if (typeof element === 'string') { + bindTapListener(document.querySelectorAll(element), tapListeners, context); + } else if (element.jquery && typeof element.toArray === 'function') { + bindTapListener(element.toArray(), tapListeners, context); + } else if (Element && element instanceof Element) { + var tap = new TapListener(element, context); + tapListeners.push(tap); + } + } + function StartAudioContext(context, elements, callback) { + var promise = new Promise(function (success) { + onStarted(context, success); + }); + var tapListeners = []; + if (!elements) { + elements = document.body; + } + bindTapListener(elements, tapListeners, context); + promise.then(function () { + for (var i = 0; i < tapListeners.length; i++) { + tapListeners[i].dispose(); + } + tapListeners = null; + if (callback) { + callback(); + } + }); + return promise; + } + return StartAudioContext; +})); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Tone; +Tone_core_Tone = function () { + 'use strict'; + var Tone = function (inputs, outputs) { + if (this.isUndef(inputs) || inputs === 1) { + this.input = this.context.createGain(); + } else if (inputs > 1) { + this.input = new Array(inputs); + } + if (this.isUndef(outputs) || outputs === 1) { + this.output = this.context.createGain(); + } else if (outputs > 1) { + this.output = new Array(inputs); + } + }; + Tone.prototype.set = function (params, value, rampTime) { + if (this.isObject(params)) { + rampTime = value; + } else if (this.isString(params)) { + var tmpObj = {}; + tmpObj[params] = value; + params = tmpObj; + } + paramLoop: + for (var attr in params) { + value = params[attr]; + var parent = this; + if (attr.indexOf('.') !== -1) { + var attrSplit = attr.split('.'); + for (var i = 0; i < attrSplit.length - 1; i++) { + parent = parent[attrSplit[i]]; + if (parent instanceof Tone) { + attrSplit.splice(0, i + 1); + var innerParam = attrSplit.join('.'); + parent.set(innerParam, value); + continue paramLoop; + } + } + attr = attrSplit[attrSplit.length - 1]; + } + var param = parent[attr]; + if (this.isUndef(param)) { + continue; + } + if (Tone.Signal && param instanceof Tone.Signal || Tone.Param && param instanceof Tone.Param) { + if (param.value !== value) { + if (this.isUndef(rampTime)) { + param.value = value; + } else { + param.rampTo(value, rampTime); + } + } + } else if (param instanceof AudioParam) { + if (param.value !== value) { + param.value = value; + } + } else if (param instanceof Tone) { + param.set(value); + } else if (param !== value) { + parent[attr] = value; + } + } + return this; + }; + Tone.prototype.get = function (params) { + if (this.isUndef(params)) { + params = this._collectDefaults(this.constructor); + } else if (this.isString(params)) { + params = [params]; + } + var ret = {}; + for (var i = 0; i < params.length; i++) { + var attr = params[i]; + var parent = this; + var subRet = ret; + if (attr.indexOf('.') !== -1) { + var attrSplit = attr.split('.'); + for (var j = 0; j < attrSplit.length - 1; j++) { + var subAttr = attrSplit[j]; + subRet[subAttr] = subRet[subAttr] || {}; + subRet = subRet[subAttr]; + parent = parent[subAttr]; + } + attr = attrSplit[attrSplit.length - 1]; + } + var param = parent[attr]; + if (this.isObject(params[attr])) { + subRet[attr] = param.get(); + } else if (Tone.Signal && param instanceof Tone.Signal) { + subRet[attr] = param.value; + } else if (Tone.Param && param instanceof Tone.Param) { + subRet[attr] = param.value; + } else if (param instanceof AudioParam) { + subRet[attr] = param.value; + } else if (param instanceof Tone) { + subRet[attr] = param.get(); + } else if (!this.isFunction(param) && !this.isUndef(param)) { + subRet[attr] = param; + } + } + return ret; + }; + Tone.prototype._collectDefaults = function (constr) { + var ret = []; + if (!this.isUndef(constr.defaults)) { + ret = Object.keys(constr.defaults); + } + if (!this.isUndef(constr._super)) { + var superDefs = this._collectDefaults(constr._super); + for (var i = 0; i < superDefs.length; i++) { + if (ret.indexOf(superDefs[i]) === -1) { + ret.push(superDefs[i]); + } + } + } + return ret; + }; + Tone.prototype.toString = function () { + for (var className in Tone) { + var isLetter = className[0].match(/^[A-Z]$/); + var sameConstructor = Tone[className] === this.constructor; + if (this.isFunction(Tone[className]) && isLetter && sameConstructor) { + return className; + } + } + return 'Tone'; + }; + Object.defineProperty(Tone.prototype, 'numberOfInputs', { + get: function () { + if (this.input) { + if (this.isArray(this.input)) { + return this.input.length; + } else { + return 1; + } + } else { + return 0; + } + } + }); + Object.defineProperty(Tone.prototype, 'numberOfOutputs', { + get: function () { + if (this.output) { + if (this.isArray(this.output)) { + return this.output.length; + } else { + return 1; + } + } else { + return 0; + } + } + }); + Tone.prototype.dispose = function () { + if (!this.isUndef(this.input)) { + if (this.input instanceof AudioNode) { + this.input.disconnect(); + } + this.input = null; + } + if (!this.isUndef(this.output)) { + if (this.output instanceof AudioNode) { + this.output.disconnect(); + } + this.output = null; + } + return this; + }; + Tone.prototype.connect = function (unit, outputNum, inputNum) { + if (Array.isArray(this.output)) { + outputNum = this.defaultArg(outputNum, 0); + this.output[outputNum].connect(unit, 0, inputNum); + } else { + this.output.connect(unit, outputNum, inputNum); + } + return this; + }; + Tone.prototype.disconnect = function (destination, outputNum, inputNum) { + if (this.isArray(this.output)) { + if (this.isNumber(destination)) { + this.output[destination].disconnect(); + } else { + outputNum = this.defaultArg(outputNum, 0); + this.output[outputNum].disconnect(destination, 0, inputNum); + } + } else { + this.output.disconnect.apply(this.output, arguments); + } + }; + Tone.prototype.connectSeries = function () { + if (arguments.length > 1) { + var currentUnit = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + var toUnit = arguments[i]; + currentUnit.connect(toUnit); + currentUnit = toUnit; + } + } + return this; + }; + Tone.prototype.chain = function () { + if (arguments.length > 0) { + var currentUnit = this; + for (var i = 0; i < arguments.length; i++) { + var toUnit = arguments[i]; + currentUnit.connect(toUnit); + currentUnit = toUnit; + } + } + return this; + }; + Tone.prototype.fan = function () { + if (arguments.length > 0) { + for (var i = 0; i < arguments.length; i++) { + this.connect(arguments[i]); + } + } + return this; + }; + AudioNode.prototype.chain = Tone.prototype.chain; + AudioNode.prototype.fan = Tone.prototype.fan; + Tone.prototype.defaultArg = function (given, fallback) { + if (this.isObject(given) && this.isObject(fallback)) { + var ret = {}; + for (var givenProp in given) { + ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); + } + for (var fallbackProp in fallback) { + ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); + } + return ret; + } else { + return this.isUndef(given) ? fallback : given; + } + }; + Tone.prototype.optionsObject = function (values, keys, defaults) { + var options = {}; + if (values.length === 1 && this.isObject(values[0])) { + options = values[0]; + } else { + for (var i = 0; i < keys.length; i++) { + options[keys[i]] = values[i]; + } + } + if (!this.isUndef(defaults)) { + return this.defaultArg(options, defaults); + } else { + return options; + } + }; + Tone.prototype.isUndef = function (val) { + return typeof val === 'undefined'; + }; + Tone.prototype.isFunction = function (val) { + return typeof val === 'function'; + }; + Tone.prototype.isNumber = function (arg) { + return typeof arg === 'number'; + }; + Tone.prototype.isObject = function (arg) { + return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object; + }; + Tone.prototype.isBoolean = function (arg) { + return typeof arg === 'boolean'; + }; + Tone.prototype.isArray = function (arg) { + return Array.isArray(arg); + }; + Tone.prototype.isString = function (arg) { + return typeof arg === 'string'; + }; + Tone.noOp = function () { + }; + Tone.prototype._readOnly = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._readOnly(property[i]); + } + } else { + Object.defineProperty(this, property, { + writable: false, + enumerable: true + }); + } + }; + Tone.prototype._writable = function (property) { + if (Array.isArray(property)) { + for (var i = 0; i < property.length; i++) { + this._writable(property[i]); + } + } else { + Object.defineProperty(this, property, { writable: true }); + } + }; + Tone.State = { + Started: 'started', + Stopped: 'stopped', + Paused: 'paused' + }; + Tone.prototype.equalPowerScale = function (percent) { + var piFactor = 0.5 * Math.PI; + return Math.sin(percent * piFactor); + }; + Tone.prototype.dbToGain = function (db) { + return Math.pow(2, db / 6); + }; + Tone.prototype.gainToDb = function (gain) { + return 20 * (Math.log(gain) / Math.LN10); + }; + Tone.prototype.intervalToFrequencyRatio = function (interval) { + return Math.pow(2, interval / 12); + }; + Tone.prototype.now = function () { + return Tone.context.now(); + }; + Tone.now = function () { + return Tone.context.now(); + }; + Tone.extend = function (child, parent) { + if (Tone.prototype.isUndef(parent)) { + parent = Tone; + } + function TempConstructor() { + } + TempConstructor.prototype = parent.prototype; + child.prototype = new TempConstructor(); + child.prototype.constructor = child; + child._super = parent; + }; + var audioContext; + Object.defineProperty(Tone, 'context', { + get: function () { + return audioContext; + }, + set: function (context) { + if (Tone.Context && context instanceof Tone.Context) { + audioContext = context; + } else { + audioContext = new Tone.Context(context); + } + if (Tone.Context) { + Tone.Context.emit('init', audioContext); + } + } + }); + Object.defineProperty(Tone.prototype, 'context', { + get: function () { + return Tone.context; + } + }); + Tone.setContext = function (ctx) { + Tone.context = ctx; + }; + Object.defineProperty(Tone.prototype, 'blockTime', { + get: function () { + return 128 / this.context.sampleRate; + } + }); + Object.defineProperty(Tone.prototype, 'sampleTime', { + get: function () { + return 1 / this.context.sampleRate; + } + }); + Object.defineProperty(Tone, 'supported', { + get: function () { + var hasAudioContext = window.hasOwnProperty('AudioContext') || window.hasOwnProperty('webkitAudioContext'); + var hasPromises = window.hasOwnProperty('Promise'); + var hasWorkers = window.hasOwnProperty('Worker'); + return hasAudioContext && hasPromises && hasWorkers; + } + }); + Tone.version = 'r10'; + if (!window.TONE_SILENCE_VERSION_LOGGING) { + } + return Tone; +}(); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Emitter; +Tone_core_Emitter = function (Tone) { + 'use strict'; + Tone.Emitter = function () { + this._events = {}; + }; + Tone.extend(Tone.Emitter); + Tone.Emitter.prototype.on = function (event, callback) { + var events = event.split(/\W+/); + for (var i = 0; i < events.length; i++) { + var eventName = events[i]; + if (!this._events.hasOwnProperty(eventName)) { + this._events[eventName] = []; + } + this._events[eventName].push(callback); + } + return this; + }; + Tone.Emitter.prototype.off = function (event, callback) { + var events = event.split(/\W+/); + for (var ev = 0; ev < events.length; ev++) { + event = events[ev]; + if (this._events.hasOwnProperty(event)) { + if (Tone.prototype.isUndef(callback)) { + this._events[event] = []; + } else { + var eventList = this._events[event]; + for (var i = 0; i < eventList.length; i++) { + if (eventList[i] === callback) { + eventList.splice(i, 1); + } + } + } + } + } + return this; + }; + Tone.Emitter.prototype.emit = function (event) { + if (this._events) { + var args = Array.apply(null, arguments).slice(1); + if (this._events.hasOwnProperty(event)) { + var eventList = this._events[event]; + for (var i = 0, len = eventList.length; i < len; i++) { + eventList[i].apply(this, args); + } + } + } + return this; + }; + Tone.Emitter.mixin = function (object) { + var functions = [ + 'on', + 'off', + 'emit' + ]; + object._events = {}; + for (var i = 0; i < functions.length; i++) { + var func = functions[i]; + var emitterFunc = Tone.Emitter.prototype[func]; + object[func] = emitterFunc; + } + }; + Tone.Emitter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._events = null; + return this; + }; + return Tone.Emitter; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Context; +Tone_core_Context = function (Tone) { + if (!window.hasOwnProperty('AudioContext') && window.hasOwnProperty('webkitAudioContext')) { + window.AudioContext = window.webkitAudioContext; + } + Tone.Context = function (context) { + Tone.Emitter.call(this); + if (!context) { + context = new window.AudioContext(); + } + this._context = context; + for (var prop in this._context) { + this._defineProperty(this._context, prop); + } + this._latencyHint = 'interactive'; + this._lookAhead = 0.1; + this._updateInterval = this._lookAhead / 3; + this._computedUpdateInterval = 0; + this._worker = this._createWorker(); + this._constants = {}; + }; + Tone.extend(Tone.Context, Tone.Emitter); + Tone.Emitter.mixin(Tone.Context); + Tone.Context.prototype._defineProperty = function (context, prop) { + if (this.isUndef(this[prop])) { + Object.defineProperty(this, prop, { + get: function () { + if (typeof context[prop] === 'function') { + return context[prop].bind(context); + } else { + return context[prop]; + } + }, + set: function (val) { + context[prop] = val; + } + }); + } + }; + Tone.Context.prototype.now = function () { + return this._context.currentTime; + }; + Tone.Context.prototype._createWorker = function () { + window.URL = window.URL || window.webkitURL; + var blob = new Blob(['var timeoutTime = ' + (this._updateInterval * 1000).toFixed(1) + ';' + 'self.onmessage = function(msg){' + '\ttimeoutTime = parseInt(msg.data);' + '};' + 'function tick(){' + '\tsetTimeout(tick, timeoutTime);' + '\tself.postMessage(\'tick\');' + '}' + 'tick();']); + var blobUrl = URL.createObjectURL(blob); + var worker = new Worker(blobUrl); + worker.addEventListener('message', function () { + this.emit('tick'); + }.bind(this)); + worker.addEventListener('message', function () { + var now = this.now(); + if (this.isNumber(this._lastUpdate)) { + var diff = now - this._lastUpdate; + this._computedUpdateInterval = Math.max(diff, this._computedUpdateInterval * 0.97); + } + this._lastUpdate = now; + }.bind(this)); + return worker; + }; + Tone.Context.prototype.getConstant = function (val) { + if (this._constants[val]) { + return this._constants[val]; + } else { + var buffer = this._context.createBuffer(1, 128, this._context.sampleRate); + var arr = buffer.getChannelData(0); + for (var i = 0; i < arr.length; i++) { + arr[i] = val; + } + var constant = this._context.createBufferSource(); + constant.channelCount = 1; + constant.channelCountMode = 'explicit'; + constant.buffer = buffer; + constant.loop = true; + constant.start(0); + this._constants[val] = constant; + return constant; + } + }; + Object.defineProperty(Tone.Context.prototype, 'lag', { + get: function () { + var diff = this._computedUpdateInterval - this._updateInterval; + diff = Math.max(diff, 0); + return diff; + } + }); + Object.defineProperty(Tone.Context.prototype, 'lookAhead', { + get: function () { + return this._lookAhead; + }, + set: function (lA) { + this._lookAhead = lA; + } + }); + Object.defineProperty(Tone.Context.prototype, 'updateInterval', { + get: function () { + return this._updateInterval; + }, + set: function (interval) { + this._updateInterval = Math.max(interval, Tone.prototype.blockTime); + this._worker.postMessage(Math.max(interval * 1000, 1)); + } + }); + Object.defineProperty(Tone.Context.prototype, 'latencyHint', { + get: function () { + return this._latencyHint; + }, + set: function (hint) { + var lookAhead = hint; + this._latencyHint = hint; + if (this.isString(hint)) { + switch (hint) { + case 'interactive': + lookAhead = 0.1; + this._context.latencyHint = hint; + break; + case 'playback': + lookAhead = 0.8; + this._context.latencyHint = hint; + break; + case 'balanced': + lookAhead = 0.25; + this._context.latencyHint = hint; + break; + case 'fastest': + lookAhead = 0.01; + break; + } + } + this.lookAhead = lookAhead; + this.updateInterval = lookAhead / 3; + } + }); + function shimConnect() { + var nativeConnect = AudioNode.prototype.connect; + var nativeDisconnect = AudioNode.prototype.disconnect; + function toneConnect(B, outNum, inNum) { + if (B.input) { + if (Array.isArray(B.input)) { + if (Tone.prototype.isUndef(inNum)) { + inNum = 0; + } + this.connect(B.input[inNum]); + } else { + this.connect(B.input, outNum, inNum); + } + } else { + try { + if (B instanceof AudioNode) { + nativeConnect.call(this, B, outNum, inNum); + } else { + nativeConnect.call(this, B, outNum); + } + } catch (e) { + throw new Error('error connecting to node: ' + B + '\n' + e); + } + } + } + function toneDisconnect(B, outNum, inNum) { + if (B && B.input && Array.isArray(B.input)) { + if (Tone.prototype.isUndef(inNum)) { + inNum = 0; + } + this.disconnect(B.input[inNum], outNum, inNum); + } else if (B && B.input) { + this.disconnect(B.input, outNum, inNum); + } else { + try { + nativeDisconnect.apply(this, arguments); + } catch (e) { + throw new Error('error disconnecting node: ' + B + '\n' + e); + } + } + } + if (AudioNode.prototype.connect !== toneConnect) { + AudioNode.prototype.connect = toneConnect; + AudioNode.prototype.disconnect = toneDisconnect; + } + } + if (Tone.supported) { + shimConnect(); + Tone.context = new Tone.Context(); + } else { + console.warn('This browser does not support Tone.js'); + } + return Tone.Context; +}(Tone_core_Tone); +var audiocontext; +'use strict'; +audiocontext = function (StartAudioContext, Context, Tone) { + // Create the Audio Context + const audiocontext = new window.AudioContext(); + Tone.context.dispose(); + Tone.setContext(audiocontext); + /** + * <p>Returns the Audio Context for this sketch. Useful for users + * who would like to dig deeper into the <a target='_blank' href= + * 'http://webaudio.github.io/web-audio-api/'>Web Audio API + * </a>.</p> + * + * <p>Some browsers require users to startAudioContext + * with a user gesture, such as touchStarted in the example below.</p> + * + * @method getAudioContext + * @return {Object} AudioContext for this sketch + * @example + * <div><code> + * function draw() { + * background(255); + * textAlign(CENTER); + * + * if (getAudioContext().state !== 'running') { + * text('click to start audio', width/2, height/2); + * } else { + * text('audio is enabled', width/2, height/2); + * } + * } + * + * function touchStarted() { + * if (getAudioContext().state !== 'running') { + * getAudioContext().resume(); + * } + * var synth = new p5.MonoSynth(); + * synth.play('A4', 0.5, 0, 0.2); + * } + * + * </div></code> + */ + p5.prototype.getAudioContext = function () { + return audiocontext; + }; + /** + * <p>It is a good practice to give users control over starting audio playback. + * This practice is enforced by Google Chrome's autoplay policy as of r70 + * (<a href="https://goo.gl/7K7WLu">info</a>), iOS Safari, and other browsers. + * </p> + * + * <p> + * userStartAudio() starts the <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext" + * target="_blank" title="Audio Context @ MDN">Audio Context</a> on a user gesture. It utilizes + * the <a href="https://github.com/tambien/StartAudioContext">StartAudioContext</a> library by + * Yotam Mann (MIT Licence, 2016). Read more at https://github.com/tambien/StartAudioContext. + * </p> + * + * <p>Starting the audio context on a user gesture can be as simple as <code>userStartAudio()</code>. + * Optional parameters let you decide on a specific element that will start the audio context, + * and/or call a function once the audio context is started.</p> + * @param {Element|Array} [element(s)] This argument can be an Element, + * Selector String, NodeList, p5.Element, + * jQuery Element, or an Array of any of those. + * @param {Function} [callback] Callback to invoke when the AudioContext has started + * @return {Promise} Returns a Promise which is resolved when + * the AudioContext state is 'running' + * @method userStartAudio + * @example + * <div><code> + * function setup() { + * var myDiv = createDiv('click to start audio'); + * myDiv.position(0, 0); + * + * var mySynth = new p5.MonoSynth(); + * + * // This won't play until the context has started + * mySynth.play('A6'); + * + * // Start the audio context on a click/touch event + * userStartAudio().then(function() { + * myDiv.remove(); + * }); + * } + * </code></div> + */ + p5.prototype.userStartAudio = function (elements, callback) { + var elt = elements; + if (elements instanceof p5.Element) { + elt = elements.elt; + } else if (elements instanceof Array && elements[0] instanceof p5.Element) { + elt = elements.map(function (e) { + return e.elt; + }); + } + return StartAudioContext(audiocontext, elt, callback); + }; + return audiocontext; +}(StartAudioContext, Tone_core_Context, Tone_core_Tone); +var master; +'use strict'; +master = function (audiocontext) { + /** + * Master contains AudioContext and the master sound output. + */ + var Master = function () { + this.input = audiocontext.createGain(); + this.output = audiocontext.createGain(); + //put a hard limiter on the output + this.limiter = audiocontext.createDynamicsCompressor(); + this.limiter.threshold.value = -3; + this.limiter.ratio.value = 20; + this.limiter.knee.value = 1; + this.audiocontext = audiocontext; + this.output.disconnect(); + // connect input to limiter + this.input.connect(this.limiter); + // connect limiter to output + this.limiter.connect(this.output); + // meter is just for global Amplitude / FFT analysis + this.meter = audiocontext.createGain(); + this.fftMeter = audiocontext.createGain(); + this.output.connect(this.meter); + this.output.connect(this.fftMeter); + // connect output to destination + this.output.connect(this.audiocontext.destination); + // an array of all sounds in the sketch + this.soundArray = []; + // an array of all musical parts in the sketch + this.parts = []; + // file extensions to search for + this.extensions = []; + }; + // create a single instance of the p5Sound / master output for use within this sketch + var p5sound = new Master(); + /** + * Returns a number representing the master amplitude (volume) for sound + * in this sketch. + * + * @method getMasterVolume + * @return {Number} Master amplitude (volume) for sound in this sketch. + * Should be between 0.0 (silence) and 1.0. + */ + p5.prototype.getMasterVolume = function () { + return p5sound.output.gain.value; + }; + /** + * <p>Scale the output of all sound in this sketch</p> + * Scaled between 0.0 (silence) and 1.0 (full volume). + * 1.0 is the maximum amplitude of a digital sound, so multiplying + * by greater than 1.0 may cause digital distortion. To + * fade, provide a <code>rampTime</code> parameter. For more + * complex fades, see the Envelope class. + * + * Alternately, you can pass in a signal source such as an + * oscillator to modulate the amplitude with an audio signal. + * + * <p><b>How This Works</b>: When you load the p5.sound module, it + * creates a single instance of p5sound. All sound objects in this + * module output to p5sound before reaching your computer's output. + * So if you change the amplitude of p5sound, it impacts all of the + * sound in this module.</p> + * + * <p>If no value is provided, returns a Web Audio API Gain Node</p> + * + * @method masterVolume + * @param {Number|Object} volume Volume (amplitude) between 0.0 + * and 1.0 or modulating signal/oscillator + * @param {Number} [rampTime] Fade for t seconds + * @param {Number} [timeFromNow] Schedule this event to happen at + * t seconds in the future + */ + p5.prototype.masterVolume = function (vol, rampTime, tFromNow) { + if (typeof vol === 'number') { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = p5sound.output.gain.value; + p5sound.output.gain.cancelScheduledValues(now + tFromNow); + p5sound.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + p5sound.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(p5sound.output.gain); + } else { + // return the Gain Node + return p5sound.output.gain; + } + }; + /** + * `p5.soundOut` is the p5.sound master output. It sends output to + * the destination of this window's web audio context. It contains + * Web Audio API nodes including a dyanmicsCompressor (<code>.limiter</code>), + * and Gain Nodes for <code>.input</code> and <code>.output</code>. + * + * @property {Object} soundOut + */ + p5.prototype.soundOut = p5.soundOut = p5sound; + /** + * a silent connection to the DesinationNode + * which will ensure that anything connected to it + * will not be garbage collected + * + * @private + */ + p5.soundOut._silentNode = p5sound.audiocontext.createGain(); + p5.soundOut._silentNode.gain.value = 0; + p5.soundOut._silentNode.connect(p5sound.audiocontext.destination); + return p5sound; +}(audiocontext); +var helpers; +'use strict'; +helpers = function () { + var p5sound = master; + /** + * @for p5 + */ + /** + * Returns a number representing the sample rate, in samples per second, + * of all sound objects in this audio context. It is determined by the + * sampling rate of your operating system's sound card, and it is not + * currently possile to change. + * It is often 44100, or twice the range of human hearing. + * + * @method sampleRate + * @return {Number} samplerate samples per second + */ + p5.prototype.sampleRate = function () { + return p5sound.audiocontext.sampleRate; + }; + /** + * Returns the closest MIDI note value for + * a given frequency. + * + * @method freqToMidi + * @param {Number} frequency A freqeuncy, for example, the "A" + * above Middle C is 440Hz + * @return {Number} MIDI note value + */ + p5.prototype.freqToMidi = function (f) { + var mathlog2 = Math.log(f / 440) / Math.log(2); + var m = Math.round(12 * mathlog2) + 69; + return m; + }; + /** + * Returns the frequency value of a MIDI note value. + * General MIDI treats notes as integers where middle C + * is 60, C# is 61, D is 62 etc. Useful for generating + * musical frequencies with oscillators. + * + * @method midiToFreq + * @param {Number} midiNote The number of a MIDI note + * @return {Number} Frequency value of the given MIDI note + * @example + * <div><code> + * var notes = [60, 64, 67, 72]; + * var i = 0; + * + * function setup() { + * osc = new p5.Oscillator('Triangle'); + * osc.start(); + * frameRate(1); + * } + * + * function draw() { + * var freq = midiToFreq(notes[i]); + * osc.freq(freq); + * i++; + * if (i >= notes.length){ + * i = 0; + * } + * } + * </code></div> + */ + var midiToFreq = p5.prototype.midiToFreq = function (m) { + return 440 * Math.pow(2, (m - 69) / 12); + }; + // This method converts ANSI notes specified as a string "C4", "Eb3" to a frequency + var noteToFreq = function (note) { + if (typeof note !== 'string') { + return note; + } + var wholeNotes = { + A: 21, + B: 23, + C: 24, + D: 26, + E: 28, + F: 29, + G: 31 + }; + var value = wholeNotes[note[0].toUpperCase()]; + var octave = ~~note.slice(-1); + value += 12 * (octave - 1); + switch (note[1]) { + case '#': + value += 1; + break; + case 'b': + value -= 1; + break; + default: + break; + } + return midiToFreq(value); + }; + /** + * List the SoundFile formats that you will include. LoadSound + * will search your directory for these extensions, and will pick + * a format that is compatable with the client's web browser. + * <a href="http://media.io/">Here</a> is a free online file + * converter. + * + * @method soundFormats + * @param {String} [...formats] i.e. 'mp3', 'wav', 'ogg' + * @example + * <div><code> + * function preload() { + * // set the global sound formats + * soundFormats('mp3', 'ogg'); + * + * // load either beatbox.mp3, or .ogg, depending on browser + * mySound = loadSound('assets/beatbox.mp3'); + * } + * + * function setup() { + * mySound.play(); + * } + * </code></div> + */ + p5.prototype.soundFormats = function () { + // reset extensions array + p5sound.extensions = []; + // add extensions + for (var i = 0; i < arguments.length; i++) { + arguments[i] = arguments[i].toLowerCase(); + if ([ + 'mp3', + 'wav', + 'ogg', + 'm4a', + 'aac' + ].indexOf(arguments[i]) > -1) { + p5sound.extensions.push(arguments[i]); + } else { + throw arguments[i] + ' is not a valid sound format!'; + } + } + }; + p5.prototype.disposeSound = function () { + for (var i = 0; i < p5sound.soundArray.length; i++) { + p5sound.soundArray[i].dispose(); + } + }; + // register removeSound to dispose of p5sound SoundFiles, Convolvers, + // Oscillators etc when sketch ends + p5.prototype.registerMethod('remove', p5.prototype.disposeSound); + p5.prototype._checkFileFormats = function (paths) { + var path; + // if path is a single string, check to see if extension is provided + if (typeof paths === 'string') { + path = paths; + // see if extension is provided + var extTest = path.split('.').pop(); + // if an extension is provided... + if ([ + 'mp3', + 'wav', + 'ogg', + 'm4a', + 'aac' + ].indexOf(extTest) > -1) { + if (p5.prototype.isFileSupported(extTest)) { + path = path; + } else { + var pathSplit = path.split('.'); + var pathCore = pathSplit[pathSplit.length - 1]; + for (var i = 0; i < p5sound.extensions.length; i++) { + var extension = p5sound.extensions[i]; + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + pathCore = ''; + if (pathSplit.length === 2) { + pathCore += pathSplit[0]; + } + for (var i = 1; i <= pathSplit.length - 2; i++) { + var p = pathSplit[i]; + pathCore += '.' + p; + } + path = pathCore += '.'; + path = path += extension; + break; + } + } + } + } else { + for (var i = 0; i < p5sound.extensions.length; i++) { + var extension = p5sound.extensions[i]; + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + path = path + '.' + extension; + break; + } + } + } + } else if (typeof paths === 'object') { + for (var i = 0; i < paths.length; i++) { + var extension = paths[i].split('.').pop(); + var supported = p5.prototype.isFileSupported(extension); + if (supported) { + // console.log('.'+extension + ' is ' + supported + + // ' supported by your browser.'); + path = paths[i]; + break; + } + } + } + return path; + }; + /** + * Used by Osc and Envelope to chain signal math + */ + p5.prototype._mathChain = function (o, math, thisChain, nextChain, type) { + // if this type of math already exists in the chain, replace it + for (var i in o.mathOps) { + if (o.mathOps[i] instanceof type) { + o.mathOps[i].dispose(); + thisChain = i; + if (thisChain < o.mathOps.length - 1) { + nextChain = o.mathOps[i + 1]; + } + } + } + o.mathOps[thisChain - 1].disconnect(); + o.mathOps[thisChain - 1].connect(math); + math.connect(nextChain); + o.mathOps[thisChain] = math; + return o; + }; + // helper methods to convert audio file as .wav format, + // will use as saving .wav file and saving blob object + // Thank you to Matt Diamond's RecorderJS (MIT License) + // https://github.com/mattdiamond/Recorderjs + function convertToWav(audioBuffer) { + var leftChannel, rightChannel; + leftChannel = audioBuffer.getChannelData(0); + // handle mono files + if (audioBuffer.numberOfChannels > 1) { + rightChannel = audioBuffer.getChannelData(1); + } else { + rightChannel = leftChannel; + } + var interleaved = interleave(leftChannel, rightChannel); + // create the buffer and view to create the .WAV file + var buffer = new window.ArrayBuffer(44 + interleaved.length * 2); + var view = new window.DataView(buffer); + // write the WAV container, + // check spec at: https://web.archive.org/web/20171215131933/http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf + // RIFF chunk descriptor + writeUTFBytes(view, 0, 'RIFF'); + view.setUint32(4, 36 + interleaved.length * 2, true); + writeUTFBytes(view, 8, 'WAVE'); + // FMT sub-chunk + writeUTFBytes(view, 12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + // stereo (2 channels) + view.setUint16(22, 2, true); + view.setUint32(24, p5sound.audiocontext.sampleRate, true); + view.setUint32(28, p5sound.audiocontext.sampleRate * 4, true); + view.setUint16(32, 4, true); + view.setUint16(34, 16, true); + // data sub-chunk + writeUTFBytes(view, 36, 'data'); + view.setUint32(40, interleaved.length * 2, true); + // write the PCM samples + var lng = interleaved.length; + var index = 44; + var volume = 1; + for (var i = 0; i < lng; i++) { + view.setInt16(index, interleaved[i] * (32767 * volume), true); + index += 2; + } + return view; + } + // helper methods to save waves + function interleave(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + var result = new Float32Array(length); + var inputIndex = 0; + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + function writeUTFBytes(view, offset, string) { + var lng = string.length; + for (var i = 0; i < lng; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + } + return { + convertToWav: convertToWav, + midiToFreq: midiToFreq, + noteToFreq: noteToFreq + }; +}(master); +var errorHandler; +'use strict'; +errorHandler = function () { + /* + Helper function to generate an error + with a custom stack trace that points to the sketch + and removes other parts of the stack trace. + + @private + @class customError + @constructor + @param {String} name custom error name + @param {String} errorTrace custom error trace + @param {String} failedPath path to the file that failed to load + @property {String} name custom error name + @property {String} message custom error message + @property {String} stack trace the error back to a line in the user's sketch. + Note: this edits out stack trace within p5.js and p5.sound. + @property {String} originalStack unedited, original stack trace + @property {String} failedPath path to the file that failed to load + @return {Error} returns a custom Error object + */ + var CustomError = function (name, errorTrace, failedPath) { + var err = new Error(); + var tempStack, splitStack; + err.name = name; + err.originalStack = err.stack + errorTrace; + tempStack = err.stack + errorTrace; + err.failedPath = failedPath; + // only print the part of the stack trace that refers to the user code: + var splitStack = tempStack.split('\n'); + splitStack = splitStack.filter(function (ln) { + return !ln.match(/(p5.|native code|globalInit)/g); + }); + err.stack = splitStack.join('\n'); + return err; + }; + return CustomError; +}(); +var panner; +'use strict'; +panner = function () { + var p5sound = master; + var ac = p5sound.audiocontext; + // Stereo panner + // if there is a stereo panner node use it + if (typeof ac.createStereoPanner !== 'undefined') { + p5.Panner = function (input, output) { + this.stereoPanner = this.input = ac.createStereoPanner(); + input.connect(this.stereoPanner); + this.stereoPanner.connect(output); + }; + p5.Panner.prototype.pan = function (val, tFromNow) { + var time = tFromNow || 0; + var t = ac.currentTime + time; + this.stereoPanner.pan.linearRampToValueAtTime(val, t); + }; + //not implemented because stereopanner + //node does not require this and will automatically + //convert single channel or multichannel to stereo. + //tested with single and stereo, not with (>2) multichannel + p5.Panner.prototype.inputChannels = function () { + }; + p5.Panner.prototype.connect = function (obj) { + this.stereoPanner.connect(obj); + }; + p5.Panner.prototype.disconnect = function () { + if (this.stereoPanner) { + this.stereoPanner.disconnect(); + } + }; + } else { + // if there is no createStereoPanner object + // such as in safari 7.1.7 at the time of writing this + // use this method to create the effect + p5.Panner = function (input, output, numInputChannels) { + this.input = ac.createGain(); + input.connect(this.input); + this.left = ac.createGain(); + this.right = ac.createGain(); + this.left.channelInterpretation = 'discrete'; + this.right.channelInterpretation = 'discrete'; + // if input is stereo + if (numInputChannels > 1) { + this.splitter = ac.createChannelSplitter(2); + this.input.connect(this.splitter); + this.splitter.connect(this.left, 1); + this.splitter.connect(this.right, 0); + } else { + this.input.connect(this.left); + this.input.connect(this.right); + } + this.output = ac.createChannelMerger(2); + this.left.connect(this.output, 0, 1); + this.right.connect(this.output, 0, 0); + this.output.connect(output); + }; + // -1 is left, +1 is right + p5.Panner.prototype.pan = function (val, tFromNow) { + var time = tFromNow || 0; + var t = ac.currentTime + time; + var v = (val + 1) / 2; + var rightVal = Math.cos(v * Math.PI / 2); + var leftVal = Math.sin(v * Math.PI / 2); + this.left.gain.linearRampToValueAtTime(leftVal, t); + this.right.gain.linearRampToValueAtTime(rightVal, t); + }; + p5.Panner.prototype.inputChannels = function (numChannels) { + if (numChannels === 1) { + this.input.disconnect(); + this.input.connect(this.left); + this.input.connect(this.right); + } else if (numChannels === 2) { + if (typeof (this.splitter === 'undefined')) { + this.splitter = ac.createChannelSplitter(2); + } + this.input.disconnect(); + this.input.connect(this.splitter); + this.splitter.connect(this.left, 1); + this.splitter.connect(this.right, 0); + } + }; + p5.Panner.prototype.connect = function (obj) { + this.output.connect(obj); + }; + p5.Panner.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + } +}(master); +var soundfile; +'use strict'; +soundfile = function () { + var CustomError = errorHandler; + var p5sound = master; + var ac = p5sound.audiocontext; + var midiToFreq = helpers.midiToFreq; + var convertToWav = helpers.convertToWav; + /** + * <p>SoundFile object with a path to a file.</p> + * + * <p>The p5.SoundFile may not be available immediately because + * it loads the file information asynchronously.</p> + * + * <p>To do something with the sound as soon as it loads + * pass the name of a function as the second parameter.</p> + * + * <p>Only one file path is required. However, audio file formats + * (i.e. mp3, ogg, wav and m4a/aac) are not supported by all + * web browsers. If you want to ensure compatability, instead of a single + * file path, you may include an Array of filepaths, and the browser will + * choose a format that works.</p> + * + * @class p5.SoundFile + * @constructor + * @param {String|Array} path path to a sound file (String). Optionally, + * you may include multiple file formats in + * an array. Alternately, accepts an object + * from the HTML5 File API, or a p5.File. + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if file fails to + * load. This function will receive an error or + * XMLHttpRequest object with information + * about what went wrong. + * @param {Function} [whileLoadingCallback] Name of a function to call while file + * is loading. That function will + * receive progress of the request to + * load the sound file + * (between 0 and 1) as its first + * parameter. This progress + * does not account for the additional + * time needed to decode the audio data. + * + * @example + * <div><code> + * + * function preload() { + * soundFormats('mp3', 'ogg'); + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * mySound.setVolume(0.1); + * mySound.play(); + * } + * + * </code></div> + */ + p5.SoundFile = function (paths, onload, onerror, whileLoading) { + if (typeof paths !== 'undefined') { + if (typeof paths === 'string' || typeof paths[0] === 'string') { + var path = p5.prototype._checkFileFormats(paths); + this.url = path; + } else if (typeof paths === 'object') { + if (!(window.File && window.FileReader && window.FileList && window.Blob)) { + // The File API isn't supported in this browser + throw 'Unable to load file because the File API is not supported'; + } + } + // if type is a p5.File...get the actual file + if (paths.file) { + paths = paths.file; + } + this.file = paths; + } + // private _onended callback, set by the method: onended(callback) + this._onended = function () { + }; + this._looping = false; + this._playing = false; + this._paused = false; + this._pauseTime = 0; + // cues for scheduling events with addCue() removeCue() + this._cues = []; + this._cueIDCounter = 0; + // position of the most recently played sample + this._lastPos = 0; + this._counterNode = null; + this._scopeNode = null; + // array of sources so that they can all be stopped! + this.bufferSourceNodes = []; + // current source + this.bufferSourceNode = null; + this.buffer = null; + this.playbackRate = 1; + this.input = p5sound.audiocontext.createGain(); + this.output = p5sound.audiocontext.createGain(); + this.reversed = false; + // start and end of playback / loop + this.startTime = 0; + this.endTime = null; + this.pauseTime = 0; + // "restart" would stop playback before retriggering + this.mode = 'sustain'; + // time that playback was started, in millis + this.startMillis = null; + // stereo panning + this.panPosition = 0; + this.panner = new p5.Panner(this.output, p5sound.input, 2); + // it is possible to instantiate a soundfile with no path + if (this.url || this.file) { + this.load(onload, onerror); + } + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + if (typeof whileLoading === 'function') { + this._whileLoading = whileLoading; + } else { + this._whileLoading = function () { + }; + } + this._onAudioProcess = _onAudioProcess.bind(this); + this._clearOnEnd = _clearOnEnd.bind(this); + }; + // register preload handling of loadSound + p5.prototype.registerPreloadMethod('loadSound', p5.prototype); + /** + * loadSound() returns a new p5.SoundFile from a specified + * path. If called during preload(), the p5.SoundFile will be ready + * to play in time for setup() and draw(). If called outside of + * preload, the p5.SoundFile will not be ready immediately, so + * loadSound accepts a callback as the second parameter. Using a + * <a href="https://github.com/processing/p5.js/wiki/Local-server"> + * local server</a> is recommended when loading external files. + * + * @method loadSound + * @param {String|Array} path Path to the sound file, or an array with + * paths to soundfiles in multiple formats + * i.e. ['sound.ogg', 'sound.mp3']. + * Alternately, accepts an object: either + * from the HTML5 File API, or a p5.File. + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if there is + * an error loading the file. + * @param {Function} [whileLoading] Name of a function to call while file is loading. + * This function will receive the percentage loaded + * so far, from 0.0 to 1.0. + * @return {SoundFile} Returns a p5.SoundFile + * @example + * <div><code> + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * mySound.setVolume(0.1); + * mySound.play(); + * } + * </code></div> + */ + p5.prototype.loadSound = function (path, callback, onerror, whileLoading) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + window.alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + var self = this; + var s = new p5.SoundFile(path, function () { + if (typeof callback === 'function') { + callback.apply(self, arguments); + } + if (typeof self._decrementPreload === 'function') { + self._decrementPreload(); + } + }, onerror, whileLoading); + return s; + }; + /** + * This is a helper function that the p5.SoundFile calls to load + * itself. Accepts a callback (the name of another function) + * as an optional parameter. + * + * @private + * @param {Function} [successCallback] Name of a function to call once file loads + * @param {Function} [errorCallback] Name of a function to call if there is an error + */ + p5.SoundFile.prototype.load = function (callback, errorCallback) { + var self = this; + var errorTrace = new Error().stack; + if (this.url !== undefined && this.url !== '') { + var request = new XMLHttpRequest(); + request.addEventListener('progress', function (evt) { + self._updateProgress(evt); + }, false); + request.open('GET', this.url, true); + request.responseType = 'arraybuffer'; + request.onload = function () { + if (request.status === 200) { + // on sucess loading file: + if (!self.panner) + return; + ac.decodeAudioData(request.response, // success decoding buffer: + function (buff) { + if (!self.panner) + return; + self.buffer = buff; + self.panner.inputChannels(buff.numberOfChannels); + if (callback) { + callback(self); + } + }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 + function () { + if (!self.panner) + return; + var err = new CustomError('decodeAudioData', errorTrace, self.url); + var msg = 'AudioContext error at decodeAudioData for ' + self.url; + if (errorCallback) { + err.msg = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }); + } else { + if (!self.panner) + return; + var err = new CustomError('loadSound', errorTrace, self.url); + var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + } + }; + // if there is another error, aside from 404... + request.onerror = function () { + var err = new CustomError('loadSound', errorTrace, self.url); + var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }; + request.send(); + } else if (this.file !== undefined) { + var reader = new FileReader(); + reader.onload = function () { + if (!self.panner) + return; + ac.decodeAudioData(reader.result, function (buff) { + if (!self.panner) + return; + self.buffer = buff; + self.panner.inputChannels(buff.numberOfChannels); + if (callback) { + callback(self); + } + }); + }; + reader.onerror = function (e) { + if (!self.panner) + return; + if (onerror) { + onerror(e); + } + }; + reader.readAsArrayBuffer(this.file); + } + }; + // TO DO: use this method to create a loading bar that shows progress during file upload/decode. + p5.SoundFile.prototype._updateProgress = function (evt) { + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total * 0.99; + this._whileLoading(percentComplete, evt); + } else { + // Unable to compute progress information since the total size is unknown + this._whileLoading('size unknown'); + } + }; + /** + * Returns true if the sound file finished loading successfully. + * + * @method isLoaded + * @return {Boolean} + */ + p5.SoundFile.prototype.isLoaded = function () { + if (this.buffer) { + return true; + } else { + return false; + } + }; + /** + * Play the p5.SoundFile + * + * @method play + * @param {Number} [startTime] (optional) schedule playback to start (in seconds from now). + * @param {Number} [rate] (optional) playback rate + * @param {Number} [amp] (optional) amplitude (volume) + * of playback + * @param {Number} [cueStart] (optional) cue start time in seconds + * @param {Number} [duration] (optional) duration of playback in seconds + */ + p5.SoundFile.prototype.play = function (startTime, rate, amp, _cueStart, duration) { + if (!this.output) { + console.warn('SoundFile.play() called after dispose'); + return; + } + var self = this; + var now = p5sound.audiocontext.currentTime; + var cueStart, cueEnd; + var time = startTime || 0; + if (time < 0) { + time = 0; + } + time = time + now; + if (typeof rate !== 'undefined') { + this.rate(rate); + } + if (typeof amp !== 'undefined') { + this.setVolume(amp); + } + // TO DO: if already playing, create array of buffers for easy stop() + if (this.buffer) { + // reset the pause time (if it was paused) + this._pauseTime = 0; + // handle restart playmode + if (this.mode === 'restart' && this.buffer && this.bufferSourceNode) { + this.bufferSourceNode.stop(time); + this._counterNode.stop(time); + } + //dont create another instance if already playing + if (this.mode === 'untildone' && this.isPlaying()) { + return; + } + // make a new source and counter. They are automatically assigned playbackRate and buffer + this.bufferSourceNode = this._initSourceNode(); + // garbage collect counterNode and create a new one + delete this._counterNode; + this._counterNode = this._initCounterNode(); + if (_cueStart) { + if (_cueStart >= 0 && _cueStart < this.buffer.duration) { + // this.startTime = cueStart; + cueStart = _cueStart; + } else { + throw 'start time out of range'; + } + } else { + cueStart = 0; + } + if (duration) { + // if duration is greater than buffer.duration, just play entire file anyway rather than throw an error + duration = duration <= this.buffer.duration - cueStart ? duration : this.buffer.duration; + } + // if it was paused, play at the pause position + if (this._paused) { + this.bufferSourceNode.start(time, this.pauseTime, duration); + this._counterNode.start(time, this.pauseTime, duration); + } else { + this.bufferSourceNode.start(time, cueStart, duration); + this._counterNode.start(time, cueStart, duration); + } + this._playing = true; + this._paused = false; + // add source to sources array, which is used in stopAll() + this.bufferSourceNodes.push(this.bufferSourceNode); + this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1; + this.bufferSourceNode.addEventListener('ended', this._clearOnEnd); + } else { + throw 'not ready to play file, buffer has yet to load. Try preload()'; + } + // if looping, will restart at original time + this.bufferSourceNode.loop = this._looping; + this._counterNode.loop = this._looping; + if (this._looping === true) { + cueEnd = duration ? duration : cueStart - 1e-15; + this.bufferSourceNode.loopStart = cueStart; + this.bufferSourceNode.loopEnd = cueEnd; + this._counterNode.loopStart = cueStart; + this._counterNode.loopEnd = cueEnd; + } + }; + /** + * p5.SoundFile has two play modes: <code>restart</code> and + * <code>sustain</code>. Play Mode determines what happens to a + * p5.SoundFile if it is triggered while in the middle of playback. + * In sustain mode, playback will continue simultaneous to the + * new playback. In restart mode, play() will stop playback + * and start over. With untilDone, a sound will play only if it's + * not already playing. Sustain is the default mode. + * + * @method playMode + * @param {String} str 'restart' or 'sustain' or 'untilDone' + * @example + * <div><code> + * var mySound; + * function preload(){ + * mySound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * function mouseClicked() { + * mySound.playMode('sustain'); + * mySound.play(); + * } + * function keyPressed() { + * mySound.playMode('restart'); + * mySound.play(); + * } + * + * </code></div> + */ + p5.SoundFile.prototype.playMode = function (str) { + var s = str.toLowerCase(); + // if restart, stop all other sounds from playing + if (s === 'restart' && this.buffer && this.bufferSourceNode) { + for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { + var now = p5sound.audiocontext.currentTime; + this.bufferSourceNodes[i].stop(now); + } + } + // set play mode to effect future playback + if (s === 'restart' || s === 'sustain' || s === 'untildone') { + this.mode = s; + } else { + throw 'Invalid play mode. Must be either "restart" or "sustain"'; + } + }; + /** + * Pauses a file that is currently playing. If the file is not + * playing, then nothing will happen. + * + * After pausing, .play() will resume from the paused + * position. + * If p5.SoundFile had been set to loop before it was paused, + * it will continue to loop after it is unpaused with .play(). + * + * @method pause + * @param {Number} [startTime] (optional) schedule event to occur + * seconds from now + * @example + * <div><code> + * var soundFile; + * + * function preload() { + * soundFormats('ogg', 'mp3'); + * soundFile = loadSound('assets/Damscray_-_Dancing_Tiger_02.mp3'); + * } + * function setup() { + * background(0, 255, 0); + * soundFile.setVolume(0.1); + * soundFile.loop(); + * } + * function keyTyped() { + * if (key == 'p') { + * soundFile.pause(); + * background(255, 0, 0); + * } + * } + * + * function keyReleased() { + * if (key == 'p') { + * soundFile.play(); + * background(0, 255, 0); + * } + * } + * </code> + * </div> + */ + p5.SoundFile.prototype.pause = function (startTime) { + var now = p5sound.audiocontext.currentTime; + var time = startTime || 0; + var pTime = time + now; + if (this.isPlaying() && this.buffer && this.bufferSourceNode) { + this.pauseTime = this.currentTime(); + this.bufferSourceNode.stop(pTime); + this._counterNode.stop(pTime); + this._paused = true; + this._playing = false; + this._pauseTime = this.currentTime(); + } else { + this._pauseTime = 0; + } + }; + /** + * Loop the p5.SoundFile. Accepts optional parameters to set the + * playback rate, playback volume, loopStart, loopEnd. + * + * @method loop + * @param {Number} [startTime] (optional) schedule event to occur + * seconds from now + * @param {Number} [rate] (optional) playback rate + * @param {Number} [amp] (optional) playback volume + * @param {Number} [cueLoopStart] (optional) startTime in seconds + * @param {Number} [duration] (optional) loop duration in seconds + */ + p5.SoundFile.prototype.loop = function (startTime, rate, amp, loopStart, duration) { + this._looping = true; + this.play(startTime, rate, amp, loopStart, duration); + }; + /** + * Set a p5.SoundFile's looping flag to true or false. If the sound + * is currently playing, this change will take effect when it + * reaches the end of the current playback. + * + * @method setLoop + * @param {Boolean} Boolean set looping to true or false + */ + p5.SoundFile.prototype.setLoop = function (bool) { + if (bool === true) { + this._looping = true; + } else if (bool === false) { + this._looping = false; + } else { + throw 'Error: setLoop accepts either true or false'; + } + if (this.bufferSourceNode) { + this.bufferSourceNode.loop = this._looping; + this._counterNode.loop = this._looping; + } + }; + /** + * Returns 'true' if a p5.SoundFile is currently looping and playing, 'false' if not. + * + * @method isLooping + * @return {Boolean} + */ + p5.SoundFile.prototype.isLooping = function () { + if (!this.bufferSourceNode) { + return false; + } + if (this._looping === true && this.isPlaying() === true) { + return true; + } + return false; + }; + /** + * Returns true if a p5.SoundFile is playing, false if not (i.e. + * paused or stopped). + * + * @method isPlaying + * @return {Boolean} + */ + p5.SoundFile.prototype.isPlaying = function () { + return this._playing; + }; + /** + * Returns true if a p5.SoundFile is paused, false if not (i.e. + * playing or stopped). + * + * @method isPaused + * @return {Boolean} + */ + p5.SoundFile.prototype.isPaused = function () { + return this._paused; + }; + /** + * Stop soundfile playback. + * + * @method stop + * @param {Number} [startTime] (optional) schedule event to occur + * in seconds from now + */ + p5.SoundFile.prototype.stop = function (timeFromNow) { + var time = timeFromNow || 0; + if (this.mode === 'sustain' || this.mode === 'untildone') { + this.stopAll(time); + this._playing = false; + this.pauseTime = 0; + this._paused = false; + } else if (this.buffer && this.bufferSourceNode) { + var now = p5sound.audiocontext.currentTime; + var t = time || 0; + this.pauseTime = 0; + this.bufferSourceNode.stop(now + t); + this._counterNode.stop(now + t); + this._playing = false; + this._paused = false; + } + }; + /** + * Stop playback on all of this soundfile's sources. + * @private + */ + p5.SoundFile.prototype.stopAll = function (_time) { + var now = p5sound.audiocontext.currentTime; + var time = _time || 0; + if (this.buffer && this.bufferSourceNode) { + for (var i in this.bufferSourceNodes) { + const bufferSourceNode = this.bufferSourceNodes[i]; + if (!!bufferSourceNode) { + try { + bufferSourceNode.stop(now + time); + } catch (e) { + } + } + } + this._counterNode.stop(now + time); + this._onended(this); + } + }; + /** + * Multiply the output volume (amplitude) of a sound file + * between 0.0 (silence) and 1.0 (full volume). + * 1.0 is the maximum amplitude of a digital sound, so multiplying + * by greater than 1.0 may cause digital distortion. To + * fade, provide a <code>rampTime</code> parameter. For more + * complex fades, see the Envelope class. + * + * Alternately, you can pass in a signal source such as an + * oscillator to modulate the amplitude with an audio signal. + * + * @method setVolume + * @param {Number|Object} volume Volume (amplitude) between 0.0 + * and 1.0 or modulating signal/oscillator + * @param {Number} [rampTime] Fade for t seconds + * @param {Number} [timeFromNow] Schedule this event to happen at + * t seconds in the future + */ + p5.SoundFile.prototype.setVolume = function (vol, _rampTime, _tFromNow) { + if (typeof vol === 'number') { + var rampTime = _rampTime || 0; + var tFromNow = _tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now + tFromNow); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(this.output.gain); + } else { + // return the Gain Node + return this.output.gain; + } + }; + // same as setVolume, to match Processing Sound + p5.SoundFile.prototype.amp = p5.SoundFile.prototype.setVolume; + // these are the same thing + p5.SoundFile.prototype.fade = p5.SoundFile.prototype.setVolume; + p5.SoundFile.prototype.getVolume = function () { + return this.output.gain.value; + }; + /** + * Set the stereo panning of a p5.sound object to + * a floating point number between -1.0 (left) and 1.0 (right). + * Default is 0.0 (center). + * + * @method pan + * @param {Number} [panValue] Set the stereo panner + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @example + * <div><code> + * + * var ball = {}; + * var soundFile; + * + * function preload() { + * soundFormats('ogg', 'mp3'); + * soundFile = loadSound('assets/beatbox.mp3'); + * } + * + * function draw() { + * background(0); + * ball.x = constrain(mouseX, 0, width); + * ellipse(ball.x, height/2, 20, 20) + * } + * + * function mousePressed(){ + * // map the ball's x location to a panning degree + * // between -1.0 (left) and 1.0 (right) + * var panning = map(ball.x, 0., width,-1.0, 1.0); + * soundFile.pan(panning); + * soundFile.play(); + * } + * </div></code> + */ + p5.SoundFile.prototype.pan = function (pval, tFromNow) { + this.panPosition = pval; + this.panner.pan(pval, tFromNow); + }; + /** + * Returns the current stereo pan position (-1.0 to 1.0) + * + * @method getPan + * @return {Number} Returns the stereo pan setting of the Oscillator + * as a number between -1.0 (left) and 1.0 (right). + * 0.0 is center and default. + */ + p5.SoundFile.prototype.getPan = function () { + return this.panPosition; + }; + /** + * Set the playback rate of a sound file. Will change the speed and the pitch. + * Values less than zero will reverse the audio buffer. + * + * @method rate + * @param {Number} [playbackRate] Set the playback rate. 1.0 is normal, + * .5 is half-speed, 2.0 is twice as fast. + * Values less than zero play backwards. + * @example + * <div><code> + * var song; + * + * function preload() { + * song = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * song.loop(); + * } + * + * function draw() { + * background(200); + * + * // Set the rate to a range between 0.1 and 4 + * // Changing the rate also alters the pitch + * var speed = map(mouseY, 0.1, height, 0, 2); + * speed = constrain(speed, 0.01, 4); + * song.rate(speed); + * + * // Draw a circle to show what is going on + * stroke(0); + * fill(51, 100); + * ellipse(mouseX, 100, 48, 48); + * } + * + * </code> + * </div> + * + */ + p5.SoundFile.prototype.rate = function (playbackRate) { + var reverse = false; + if (typeof playbackRate === 'undefined') { + return this.playbackRate; + } + this.playbackRate = playbackRate; + if (playbackRate === 0) { + playbackRate = 1e-13; + } else if (playbackRate < 0 && !this.reversed) { + playbackRate = Math.abs(playbackRate); + reverse = true; + } else if (playbackRate > 0 && this.reversed) { + reverse = true; + } + if (this.bufferSourceNode) { + var now = p5sound.audiocontext.currentTime; + this.bufferSourceNode.playbackRate.cancelScheduledValues(now); + this.bufferSourceNode.playbackRate.linearRampToValueAtTime(Math.abs(playbackRate), now); + this._counterNode.playbackRate.cancelScheduledValues(now); + this._counterNode.playbackRate.linearRampToValueAtTime(Math.abs(playbackRate), now); + } + if (reverse) { + this.reverseBuffer(); + } + return this.playbackRate; + }; + // TO DO: document this + p5.SoundFile.prototype.setPitch = function (num) { + var newPlaybackRate = midiToFreq(num) / midiToFreq(60); + this.rate(newPlaybackRate); + }; + p5.SoundFile.prototype.getPlaybackRate = function () { + return this.playbackRate; + }; + /** + * Returns the duration of a sound file in seconds. + * + * @method duration + * @return {Number} The duration of the soundFile in seconds. + */ + p5.SoundFile.prototype.duration = function () { + // Return Duration + if (this.buffer) { + return this.buffer.duration; + } else { + return 0; + } + }; + /** + * Return the current position of the p5.SoundFile playhead, in seconds. + * Time is relative to the normal buffer direction, so if `reverseBuffer` + * has been called, currentTime will count backwards. + * + * @method currentTime + * @return {Number} currentTime of the soundFile in seconds. + */ + p5.SoundFile.prototype.currentTime = function () { + return this.reversed ? Math.abs(this._lastPos - this.buffer.length) / ac.sampleRate : this._lastPos / ac.sampleRate; + }; + /** + * Move the playhead of the song to a position, in seconds. Start timing + * and playback duration. If none are given, will reset the file to play + * entire duration from start to finish. + * + * @method jump + * @param {Number} cueTime cueTime of the soundFile in seconds. + * @param {Number} duration duration in seconds. + */ + p5.SoundFile.prototype.jump = function (cueTime, duration) { + if (cueTime < 0 || cueTime > this.buffer.duration) { + throw 'jump time out of range'; + } + if (duration > this.buffer.duration - cueTime) { + throw 'end time out of range'; + } + var cTime = cueTime || 0; + var dur = duration || undefined; + if (this.isPlaying()) { + this.stop(0); + } + this.play(0, this.playbackRate, this.output.gain.value, cTime, dur); + }; + /** + * Return the number of channels in a sound file. + * For example, Mono = 1, Stereo = 2. + * + * @method channels + * @return {Number} [channels] + */ + p5.SoundFile.prototype.channels = function () { + return this.buffer.numberOfChannels; + }; + /** + * Return the sample rate of the sound file. + * + * @method sampleRate + * @return {Number} [sampleRate] + */ + p5.SoundFile.prototype.sampleRate = function () { + return this.buffer.sampleRate; + }; + /** + * Return the number of samples in a sound file. + * Equal to sampleRate * duration. + * + * @method frames + * @return {Number} [sampleCount] + */ + p5.SoundFile.prototype.frames = function () { + return this.buffer.length; + }; + /** + * Returns an array of amplitude peaks in a p5.SoundFile that can be + * used to draw a static waveform. Scans through the p5.SoundFile's + * audio buffer to find the greatest amplitudes. Accepts one + * parameter, 'length', which determines size of the array. + * Larger arrays result in more precise waveform visualizations. + * + * Inspired by Wavesurfer.js. + * + * @method getPeaks + * @params {Number} [length] length is the size of the returned array. + * Larger length results in more precision. + * Defaults to 5*width of the browser window. + * @returns {Float32Array} Array of peaks. + */ + p5.SoundFile.prototype.getPeaks = function (length) { + if (this.buffer) { + // set length to window's width if no length is provided + if (!length) { + length = window.width * 5; + } + if (this.buffer) { + var buffer = this.buffer; + var sampleSize = buffer.length / length; + var sampleStep = ~~(sampleSize / 10) || 1; + var channels = buffer.numberOfChannels; + var peaks = new Float32Array(Math.round(length)); + for (var c = 0; c < channels; c++) { + var chan = buffer.getChannelData(c); + for (var i = 0; i < length; i++) { + var start = ~~(i * sampleSize); + var end = ~~(start + sampleSize); + var max = 0; + for (var j = start; j < end; j += sampleStep) { + var value = chan[j]; + if (value > max) { + max = value; + } else if (-value > max) { + max = value; + } + } + if (c === 0 || Math.abs(max) > peaks[i]) { + peaks[i] = max; + } + } + } + return peaks; + } + } else { + throw 'Cannot load peaks yet, buffer is not loaded'; + } + }; + /** + * Reverses the p5.SoundFile's buffer source. + * Playback must be handled separately (see example). + * + * @method reverseBuffer + * @example + * <div><code> + * var drum; + * + * function preload() { + * drum = loadSound('assets/drum.mp3'); + * } + * + * function setup() { + * drum.reverseBuffer(); + * drum.play(); + * } + * + * </code> + * </div> + */ + p5.SoundFile.prototype.reverseBuffer = function () { + if (this.buffer) { + var currentPos = this._lastPos / ac.sampleRate; + var curVol = this.getVolume(); + this.setVolume(0, 0.001); + const numChannels = this.buffer.numberOfChannels; + for (var i = 0; i < numChannels; i++) { + this.buffer.getChannelData(i).reverse(); + } + // set reversed flag + this.reversed = !this.reversed; + if (currentPos) { + this.jump(this.duration() - currentPos); + } + this.setVolume(curVol, 0.001); + } else { + throw 'SoundFile is not done loading'; + } + }; + /** + * Schedule an event to be called when the soundfile + * reaches the end of a buffer. If the soundfile is + * playing through once, this will be called when it + * ends. If it is looping, it will be called when + * stop is called. + * + * @method onended + * @param {Function} callback function to call when the + * soundfile has ended. + */ + p5.SoundFile.prototype.onended = function (callback) { + this._onended = callback; + return this; + }; + p5.SoundFile.prototype.add = function () { + }; + p5.SoundFile.prototype.dispose = function () { + var now = p5sound.audiocontext.currentTime; + // remove reference to soundfile + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.stop(now); + if (this.buffer && this.bufferSourceNode) { + for (var i = 0; i < this.bufferSourceNodes.length - 1; i++) { + if (this.bufferSourceNodes[i] !== null) { + this.bufferSourceNodes[i].disconnect(); + try { + this.bufferSourceNodes[i].stop(now); + } catch (e) { + console.warning('no buffer source node to dispose'); + } + this.bufferSourceNodes[i] = null; + } + } + if (this.isPlaying()) { + try { + this._counterNode.stop(now); + } catch (e) { + console.log(e); + } + this._counterNode = null; + } + } + if (this.output) { + this.output.disconnect(); + this.output = null; + } + if (this.panner) { + this.panner.disconnect(); + this.panner = null; + } + }; + /** + * Connects the output of a p5sound object to input of another + * p5.sound object. For example, you may connect a p5.SoundFile to an + * FFT or an Effect. If no parameter is given, it will connect to + * the master output. Most p5sound objects connect to the master + * output when they are created. + * + * @method connect + * @param {Object} [object] Audio object that accepts an input + */ + p5.SoundFile.prototype.connect = function (unit) { + if (!unit) { + this.panner.connect(p5sound.input); + } else { + if (unit.hasOwnProperty('input')) { + this.panner.connect(unit.input); + } else { + this.panner.connect(unit); + } + } + }; + /** + * Disconnects the output of this p5sound object. + * + * @method disconnect + */ + p5.SoundFile.prototype.disconnect = function () { + if (this.panner) { + this.panner.disconnect(); + } + }; + /** + */ + p5.SoundFile.prototype.getLevel = function () { + console.warn('p5.SoundFile.getLevel has been removed from the library. Use p5.Amplitude instead'); + }; + /** + * Reset the source for this SoundFile to a + * new path (URL). + * + * @method setPath + * @param {String} path path to audio file + * @param {Function} callback Callback + */ + p5.SoundFile.prototype.setPath = function (p, callback) { + var path = p5.prototype._checkFileFormats(p); + this.url = path; + this.load(callback); + }; + /** + * Replace the current Audio Buffer with a new Buffer. + * + * @method setBuffer + * @param {Array} buf Array of Float32 Array(s). 2 Float32 Arrays + * will create a stereo source. 1 will create + * a mono source. + */ + p5.SoundFile.prototype.setBuffer = function (buf) { + var numChannels = buf.length; + var size = buf[0].length; + var newBuffer = ac.createBuffer(numChannels, size, ac.sampleRate); + if (!(buf[0] instanceof Float32Array)) { + buf[0] = new Float32Array(buf[0]); + } + for (var channelNum = 0; channelNum < numChannels; channelNum++) { + var channel = newBuffer.getChannelData(channelNum); + channel.set(buf[channelNum]); + } + this.buffer = newBuffer; + // set numbers of channels on input to the panner + this.panner.inputChannels(numChannels); + }; + ////////////////////////////////////////////////// + // script processor node with an empty buffer to help + // keep a sample-accurate position in playback buffer. + // Inspired by Chinmay Pendharkar's technique for Sonoport --> http://bit.ly/1HwdCsV + // Copyright [2015] [Sonoport (Asia) Pte. Ltd.], + // Licensed under the Apache License http://apache.org/licenses/LICENSE-2.0 + //////////////////////////////////////////////////////////////////////////////////// + var _createCounterBuffer = function (buffer) { + const len = buffer.length; + const audioBuf = ac.createBuffer(1, buffer.length, ac.sampleRate); + const arrayBuffer = audioBuf.getChannelData(0); + for (var index = 0; index < len; index++) { + arrayBuffer[index] = index; + } + return audioBuf; + }; + // initialize counterNode, set its initial buffer and playbackRate + p5.SoundFile.prototype._initCounterNode = function () { + var self = this; + var now = ac.currentTime; + var cNode = ac.createBufferSource(); + // dispose of scope node if it already exists + if (self._scopeNode) { + self._scopeNode.disconnect(); + self._scopeNode.removeEventListener('audioprocess', self._onAudioProcess); + delete self._scopeNode; + } + self._scopeNode = ac.createScriptProcessor(256, 1, 1); + // create counter buffer of the same length as self.buffer + cNode.buffer = _createCounterBuffer(self.buffer); + cNode.playbackRate.setValueAtTime(self.playbackRate, now); + cNode.connect(self._scopeNode); + self._scopeNode.connect(p5.soundOut._silentNode); + self._scopeNode.addEventListener('audioprocess', self._onAudioProcess); + return cNode; + }; + // initialize sourceNode, set its initial buffer and playbackRate + p5.SoundFile.prototype._initSourceNode = function () { + var bufferSourceNode = ac.createBufferSource(); + bufferSourceNode.buffer = this.buffer; + bufferSourceNode.playbackRate.value = this.playbackRate; + bufferSourceNode.connect(this.output); + return bufferSourceNode; + }; + /** + * processPeaks returns an array of timestamps where it thinks there is a beat. + * + * This is an asynchronous function that processes the soundfile in an offline audio context, + * and sends the results to your callback function. + * + * The process involves running the soundfile through a lowpass filter, and finding all of the + * peaks above the initial threshold. If the total number of peaks are below the minimum number of peaks, + * it decreases the threshold and re-runs the analysis until either minPeaks or minThreshold are reached. + * + * @method processPeaks + * @param {Function} callback a function to call once this data is returned + * @param {Number} [initThreshold] initial threshold defaults to 0.9 + * @param {Number} [minThreshold] minimum threshold defaults to 0.22 + * @param {Number} [minPeaks] minimum number of peaks defaults to 200 + * @return {Array} Array of timestamped peaks + */ + p5.SoundFile.prototype.processPeaks = function (callback, _initThreshold, _minThreshold, _minPeaks) { + var bufLen = this.buffer.length; + var sampleRate = this.buffer.sampleRate; + var buffer = this.buffer; + var allPeaks = []; + var initialThreshold = _initThreshold || 0.9, threshold = initialThreshold, minThreshold = _minThreshold || 0.22, minPeaks = _minPeaks || 200; + // Create offline context + var offlineContext = new window.OfflineAudioContext(1, bufLen, sampleRate); + // create buffer source + var source = offlineContext.createBufferSource(); + source.buffer = buffer; + // Create filter. TO DO: allow custom setting of filter + var filter = offlineContext.createBiquadFilter(); + filter.type = 'lowpass'; + source.connect(filter); + filter.connect(offlineContext.destination); + // start playing at time:0 + source.start(0); + offlineContext.startRendering(); + // Render the song + // act on the result + offlineContext.oncomplete = function (e) { + if (!self.panner) + return; + var filteredBuffer = e.renderedBuffer; + var bufferData = filteredBuffer.getChannelData(0); + // step 1: + // create Peak instances, add them to array, with strength and sampleIndex + do { + allPeaks = getPeaksAtThreshold(bufferData, threshold); + threshold -= 0.005; + } while (Object.keys(allPeaks).length < minPeaks && threshold >= minThreshold); + // step 2: + // find intervals for each peak in the sampleIndex, add tempos array + var intervalCounts = countIntervalsBetweenNearbyPeaks(allPeaks); + // step 3: find top tempos + var groups = groupNeighborsByTempo(intervalCounts, filteredBuffer.sampleRate); + // sort top intervals + var topTempos = groups.sort(function (intA, intB) { + return intB.count - intA.count; + }).splice(0, 5); + // set this SoundFile's tempo to the top tempo ?? + this.tempo = topTempos[0].tempo; + // step 4: + // new array of peaks at top tempo within a bpmVariance + var bpmVariance = 5; + var tempoPeaks = getPeaksAtTopTempo(allPeaks, topTempos[0].tempo, filteredBuffer.sampleRate, bpmVariance); + callback(tempoPeaks); + }; + }; + // process peaks + var Peak = function (amp, i) { + this.sampleIndex = i; + this.amplitude = amp; + this.tempos = []; + this.intervals = []; + }; + // 1. for processPeaks() Function to identify peaks above a threshold + // returns an array of peak indexes as frames (samples) of the original soundfile + function getPeaksAtThreshold(data, threshold) { + var peaksObj = {}; + var length = data.length; + for (var i = 0; i < length; i++) { + if (data[i] > threshold) { + var amp = data[i]; + var peak = new Peak(amp, i); + peaksObj[i] = peak; + // Skip forward ~ 1/8s to get past this peak. + i += 6000; + } + i++; + } + return peaksObj; + } + // 2. for processPeaks() + function countIntervalsBetweenNearbyPeaks(peaksObj) { + var intervalCounts = []; + var peaksArray = Object.keys(peaksObj).sort(); + for (var index = 0; index < peaksArray.length; index++) { + // find intervals in comparison to nearby peaks + for (var i = 0; i < 10; i++) { + var startPeak = peaksObj[peaksArray[index]]; + var endPeak = peaksObj[peaksArray[index + i]]; + if (startPeak && endPeak) { + var startPos = startPeak.sampleIndex; + var endPos = endPeak.sampleIndex; + var interval = endPos - startPos; + // add a sample interval to the startPeak in the allPeaks array + if (interval > 0) { + startPeak.intervals.push(interval); + } + // tally the intervals and return interval counts + var foundInterval = intervalCounts.some(function (intervalCount) { + if (intervalCount.interval === interval) { + intervalCount.count++; + return intervalCount; + } + }); + // store with JSON like formatting + if (!foundInterval) { + intervalCounts.push({ + interval: interval, + count: 1 + }); + } + } + } + } + return intervalCounts; + } + // 3. for processPeaks --> find tempo + function groupNeighborsByTempo(intervalCounts, sampleRate) { + var tempoCounts = []; + intervalCounts.forEach(function (intervalCount) { + try { + // Convert an interval to tempo + var theoreticalTempo = Math.abs(60 / (intervalCount.interval / sampleRate)); + theoreticalTempo = mapTempo(theoreticalTempo); + var foundTempo = tempoCounts.some(function (tempoCount) { + if (tempoCount.tempo === theoreticalTempo) + return tempoCount.count += intervalCount.count; + }); + if (!foundTempo) { + if (isNaN(theoreticalTempo)) { + return; + } + tempoCounts.push({ + tempo: Math.round(theoreticalTempo), + count: intervalCount.count + }); + } + } catch (e) { + throw e; + } + }); + return tempoCounts; + } + // 4. for processPeaks - get peaks at top tempo + function getPeaksAtTopTempo(peaksObj, tempo, sampleRate, bpmVariance) { + var peaksAtTopTempo = []; + var peaksArray = Object.keys(peaksObj).sort(); + // TO DO: filter out peaks that have the tempo and return + for (var i = 0; i < peaksArray.length; i++) { + var key = peaksArray[i]; + var peak = peaksObj[key]; + for (var j = 0; j < peak.intervals.length; j++) { + var intervalBPM = Math.round(Math.abs(60 / (peak.intervals[j] / sampleRate))); + intervalBPM = mapTempo(intervalBPM); + if (Math.abs(intervalBPM - tempo) < bpmVariance) { + // convert sampleIndex to seconds + peaksAtTopTempo.push(peak.sampleIndex / sampleRate); + } + } + } + // filter out peaks that are very close to each other + peaksAtTopTempo = peaksAtTopTempo.filter(function (peakTime, index, arr) { + var dif = arr[index + 1] - peakTime; + if (dif > 0.01) { + return true; + } + }); + return peaksAtTopTempo; + } + // helper function for processPeaks + function mapTempo(theoreticalTempo) { + // these scenarios create infinite while loop + if (!isFinite(theoreticalTempo) || theoreticalTempo === 0) { + return; + } + // Adjust the tempo to fit within the 90-180 BPM range + while (theoreticalTempo < 90) + theoreticalTempo *= 2; + while (theoreticalTempo > 180 && theoreticalTempo > 90) + theoreticalTempo /= 2; + return theoreticalTempo; + } + /*** SCHEDULE EVENTS ***/ + // Cue inspired by JavaScript setTimeout, and the + // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org + var Cue = function (callback, time, id, val) { + this.callback = callback; + this.time = time; + this.id = id; + this.val = val; + }; + /** + * Schedule events to trigger every time a MediaElement + * (audio/video) reaches a playback cue point. + * + * Accepts a callback function, a time (in seconds) at which to trigger + * the callback, and an optional parameter for the callback. + * + * Time will be passed as the first parameter to the callback function, + * and param will be the second parameter. + * + * + * @method addCue + * @param {Number} time Time in seconds, relative to this media + * element's playback. For example, to trigger + * an event every time playback reaches two + * seconds, pass in the number 2. This will be + * passed as the first parameter to + * the callback function. + * @param {Function} callback Name of a function that will be + * called at the given time. The callback will + * receive time and (optionally) param as its + * two parameters. + * @param {Object} [value] An object to be passed as the + * second parameter to the + * callback function. + * @return {Number} id ID of this cue, + * useful for removeCue(id) + * @example + * <div><code> + * var mySound; + * function preload() { + * mySound = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * // schedule calls to changeText + * mySound.addCue(0.50, changeText, "hello" ); + * mySound.addCue(1.00, changeText, "p5" ); + * mySound.addCue(1.50, changeText, "what" ); + * mySound.addCue(2.00, changeText, "do" ); + * mySound.addCue(2.50, changeText, "you" ); + * mySound.addCue(3.00, changeText, "want" ); + * mySound.addCue(4.00, changeText, "to" ); + * mySound.addCue(5.00, changeText, "make" ); + * mySound.addCue(6.00, changeText, "?" ); + * } + * + * function changeText(val) { + * background(0); + * text(val, width/2, height/2); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * if (mySound.isPlaying() ) { + * mySound.stop(); + * } else { + * mySound.play(); + * } + * } + * } + * </code></div> + */ + p5.SoundFile.prototype.addCue = function (time, callback, val) { + var id = this._cueIDCounter++; + var cue = new Cue(callback, time, id, val); + this._cues.push(cue); + // if (!this.elt.ontimeupdate) { + // this.elt.ontimeupdate = this._onTimeUpdate.bind(this); + // } + return id; + }; + /** + * Remove a callback based on its ID. The ID is returned by the + * addCue method. + * + * @method removeCue + * @param {Number} id ID of the cue, as returned by addCue + */ + p5.SoundFile.prototype.removeCue = function (id) { + var cueLength = this._cues.length; + for (var i = 0; i < cueLength; i++) { + var cue = this._cues[i]; + if (cue.id === id) { + this._cues.splice(i, 1); + break; + } + } + if (this._cues.length === 0) { + } + }; + /** + * Remove all of the callbacks that had originally been scheduled + * via the addCue method. + * + * @method clearCues + */ + p5.SoundFile.prototype.clearCues = function () { + this._cues = []; + }; + // private method that checks for cues to be fired if events + // have been scheduled using addCue(callback, time). + p5.SoundFile.prototype._onTimeUpdate = function (position) { + var playbackTime = position / this.buffer.sampleRate; + var cueLength = this._cues.length; + for (var i = 0; i < cueLength; i++) { + var cue = this._cues[i]; + var callbackTime = cue.time; + var val = cue.val; + if (this._prevTime < callbackTime && callbackTime <= playbackTime) { + // pass the scheduled callbackTime as parameter to the callback + cue.callback(val); + } + } + this._prevTime = playbackTime; + }; + /** + * Save a p5.SoundFile as a .wav file. The browser will prompt the user + * to download the file to their device. To upload a file to a server, see + * <a href="/docs/reference/#/p5.SoundFile/getBlob">getBlob</a> + * + * @method save + * @param {String} [fileName] name of the resulting .wav file. + * @example + * <div><code> + * var inp, button, mySound; + * var fileName = 'cool'; + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * function setup() { + * btn = createButton('click to save file'); + * btn.position(0, 0); + * btn.mouseClicked(handleMouseClick); + * } + * + * function handleMouseClick() { + * mySound.save(fileName); + * } + * </code></div> + */ + p5.SoundFile.prototype.save = function (fileName) { + const dataView = convertToWav(this.buffer); + p5.prototype.saveSound([dataView], fileName, 'wav'); + }; + /** + * This method is useful for sending a SoundFile to a server. It returns the + * .wav-encoded audio data as a "<a target="_blank" title="Blob reference at + * MDN" href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>". + * A Blob is a file-like data object that can be uploaded to a server + * with an <a href="/docs/reference/#/p5/httpDo">http</a> request. We'll + * use the `httpDo` options object to send a POST request with some + * specific options: we encode the request as `multipart/form-data`, + * and attach the blob as one of the form values using `FormData`. + * + * + * @method getBlob + * @returns {Blob} A file-like data object + * @example + * <div><code> + * + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * noCanvas(); + * var soundBlob = mySound.getBlob(); + * + * // Now we can send the blob to a server... + * var serverUrl = 'https://jsonplaceholder.typicode.com/posts'; + * var httpRequestOptions = { + * method: 'POST', + * body: new FormData().append('soundBlob', soundBlob), + * headers: new Headers({ + * 'Content-Type': 'multipart/form-data' + * }) + * }; + * httpDo(serverUrl, httpRequestOptions); + * + * // We can also create an `ObjectURL` pointing to the Blob + * var blobUrl = URL.createObjectURL(soundBlob); + * + * // The `<Audio>` Element accepts Object URL's + * var htmlAudioElt = createAudio(blobUrl).showControls(); + * + * createDiv(); + * + * // The ObjectURL exists as long as this tab is open + * var input = createInput(blobUrl); + * input.attribute('readonly', true); + * input.mouseClicked(function() { input.elt.select() }); + * } + * + * </code></div> + */ + p5.SoundFile.prototype.getBlob = function () { + const dataView = convertToWav(this.buffer); + return new Blob([dataView], { type: 'audio/wav' }); + }; + // event handler to keep track of current position + function _onAudioProcess(processEvent) { + var inputBuffer = processEvent.inputBuffer.getChannelData(0); + this._lastPos = inputBuffer[inputBuffer.length - 1] || 0; + // do any callbacks that have been scheduled + this._onTimeUpdate(self._lastPos); + } + // event handler to remove references to the bufferSourceNode when it is done playing + function _clearOnEnd(e) { + const thisBufferSourceNode = e.target; + const soundFile = this; + // delete this.bufferSourceNode from the sources array when it is done playing: + thisBufferSourceNode._playing = false; + thisBufferSourceNode.removeEventListener('ended', soundFile._clearOnEnd); + // call the onended callback + soundFile._onended(soundFile); + soundFile.bufferSourceNodes.forEach(function (n, i) { + if (n._playing === false) { + soundFile.bufferSourceNodes.splice(i); + } + }); + if (soundFile.bufferSourceNodes.length === 0) { + soundFile._playing = false; + } + } +}(errorHandler, master, helpers, helpers); +var amplitude; +'use strict'; +amplitude = function () { + var p5sound = master; + /** + * Amplitude measures volume between 0.0 and 1.0. + * Listens to all p5sound by default, or use setInput() + * to listen to a specific sound source. Accepts an optional + * smoothing value, which defaults to 0. + * + * @class p5.Amplitude + * @constructor + * @param {Number} [smoothing] between 0.0 and .999 to smooth + * amplitude readings (defaults to 0) + * @example + * <div><code> + * var sound, amplitude, cnv; + * + * function preload(){ + * sound = loadSound('assets/beat.mp3'); + * } + * function setup() { + * cnv = createCanvas(100,100); + * amplitude = new p5.Amplitude(); + * + * // start / stop the sound when canvas is clicked + * cnv.mouseClicked(function() { + * if (sound.isPlaying() ){ + * sound.stop(); + * } else { + * sound.play(); + * } + * }); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * + * </code></div> + */ + p5.Amplitude = function (smoothing) { + // Set to 2048 for now. In future iterations, this should be inherited or parsed from p5sound's default + this.bufferSize = 2048; + // set audio context + this.audiocontext = p5sound.audiocontext; + this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1); + // for connections + this.input = this.processor; + this.output = this.audiocontext.createGain(); + // smoothing defaults to 0 + this.smoothing = smoothing || 0; + // the variables to return + this.volume = 0; + this.average = 0; + this.stereoVol = [ + 0, + 0 + ]; + this.stereoAvg = [ + 0, + 0 + ]; + this.stereoVolNorm = [ + 0, + 0 + ]; + this.volMax = 0.001; + this.normalize = false; + this.processor.onaudioprocess = this._audioProcess.bind(this); + this.processor.connect(this.output); + this.output.gain.value = 0; + // this may only be necessary because of a Chrome bug + this.output.connect(this.audiocontext.destination); + // connect to p5sound master output by default, unless set by input() + p5sound.meter.connect(this.processor); + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connects to the p5sound instance (master output) by default. + * Optionally, you can pass in a specific source (i.e. a soundfile). + * + * @method setInput + * @param {soundObject|undefined} [snd] set the sound source + * (optional, defaults to + * master output) + * @param {Number|undefined} [smoothing] a range between 0.0 and 1.0 + * to smooth amplitude readings + * @example + * <div><code> + * function preload(){ + * sound1 = loadSound('assets/beat.mp3'); + * sound2 = loadSound('assets/drum.mp3'); + * } + * function setup(){ + * amplitude = new p5.Amplitude(); + * sound1.play(); + * sound2.play(); + * amplitude.setInput(sound2); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * function mouseClicked(){ + * sound1.stop(); + * sound2.stop(); + * } + * </code></div> + */ + p5.Amplitude.prototype.setInput = function (source, smoothing) { + p5sound.meter.disconnect(); + if (smoothing) { + this.smoothing = smoothing; + } + // connect to the master out of p5s instance if no snd is provided + if (source == null) { + console.log('Amplitude input source is not ready! Connecting to master output instead'); + p5sound.meter.connect(this.processor); + } else if (source instanceof p5.Signal) { + source.output.connect(this.processor); + } else if (source) { + source.connect(this.processor); + this.processor.disconnect(); + this.processor.connect(this.output); + } else { + p5sound.meter.connect(this.processor); + } + }; + p5.Amplitude.prototype.connect = function (unit) { + if (unit) { + if (unit.hasOwnProperty('input')) { + this.output.connect(unit.input); + } else { + this.output.connect(unit); + } + } else { + this.output.connect(this.panner.connect(p5sound.input)); + } + }; + p5.Amplitude.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + // TO DO make this stereo / dependent on # of audio channels + p5.Amplitude.prototype._audioProcess = function (event) { + for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) { + var inputBuffer = event.inputBuffer.getChannelData(channel); + var bufLength = inputBuffer.length; + var total = 0; + var sum = 0; + var x; + for (var i = 0; i < bufLength; i++) { + x = inputBuffer[i]; + if (this.normalize) { + total += Math.max(Math.min(x / this.volMax, 1), -1); + sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1); + } else { + total += x; + sum += x * x; + } + } + var average = total / bufLength; + // ... then take the square root of the sum. + var rms = Math.sqrt(sum / bufLength); + this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing); + this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing); + this.volMax = Math.max(this.stereoVol[channel], this.volMax); + } + // add volume from all channels together + var self = this; + var volSum = this.stereoVol.reduce(function (previousValue, currentValue, index) { + self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1] / self.volMax, 1), 0); + self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index] / self.volMax, 1), 0); + return previousValue + currentValue; + }); + // volume is average of channels + this.volume = volSum / this.stereoVol.length; + // normalized value + this.volNorm = Math.max(Math.min(this.volume / this.volMax, 1), 0); + }; + /** + * Returns a single Amplitude reading at the moment it is called. + * For continuous readings, run in the draw loop. + * + * @method getLevel + * @param {Number} [channel] Optionally return only channel 0 (left) or 1 (right) + * @return {Number} Amplitude as a number between 0.0 and 1.0 + * @example + * <div><code> + * function preload(){ + * sound = loadSound('assets/beat.mp3'); + * } + * function setup() { + * amplitude = new p5.Amplitude(); + * sound.play(); + * } + * function draw() { + * background(0); + * fill(255); + * var level = amplitude.getLevel(); + * var size = map(level, 0, 1, 0, 200); + * ellipse(width/2, height/2, size, size); + * } + * function mouseClicked(){ + * sound.stop(); + * } + * </code></div> + */ + p5.Amplitude.prototype.getLevel = function (channel) { + if (typeof channel !== 'undefined') { + if (this.normalize) { + return this.stereoVolNorm[channel]; + } else { + return this.stereoVol[channel]; + } + } else if (this.normalize) { + return this.volNorm; + } else { + return this.volume; + } + }; + /** + * Determines whether the results of Amplitude.process() will be + * Normalized. To normalize, Amplitude finds the difference the + * loudest reading it has processed and the maximum amplitude of + * 1.0. Amplitude adds this difference to all values to produce + * results that will reliably map between 0.0 and 1.0. However, + * if a louder moment occurs, the amount that Normalize adds to + * all the values will change. Accepts an optional boolean parameter + * (true or false). Normalizing is off by default. + * + * @method toggleNormalize + * @param {boolean} [boolean] set normalize to true (1) or false (0) + */ + p5.Amplitude.prototype.toggleNormalize = function (bool) { + if (typeof bool === 'boolean') { + this.normalize = bool; + } else { + this.normalize = !this.normalize; + } + }; + /** + * Smooth Amplitude analysis by averaging with the last analysis + * frame. Off by default. + * + * @method smooth + * @param {Number} set smoothing from 0.0 <= 1 + */ + p5.Amplitude.prototype.smooth = function (s) { + if (s >= 0 && s < 1) { + this.smoothing = s; + } else { + console.log('Error: smoothing must be between 0 and 1'); + } + }; + p5.Amplitude.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.input) { + this.input.disconnect(); + delete this.input; + } + if (this.output) { + this.output.disconnect(); + delete this.output; + } + delete this.processor; + }; +}(master); +var fft; +'use strict'; +fft = function () { + var p5sound = master; + /** + * <p>FFT (Fast Fourier Transform) is an analysis algorithm that + * isolates individual + * <a href="https://en.wikipedia.org/wiki/Audio_frequency"> + * audio frequencies</a> within a waveform.</p> + * + * <p>Once instantiated, a p5.FFT object can return an array based on + * two types of analyses: <br> • <code>FFT.waveform()</code> computes + * amplitude values along the time domain. The array indices correspond + * to samples across a brief moment in time. Each value represents + * amplitude of the waveform at that sample of time.<br> + * • <code>FFT.analyze() </code> computes amplitude values along the + * frequency domain. The array indices correspond to frequencies (i.e. + * pitches), from the lowest to the highest that humans can hear. Each + * value represents amplitude at that slice of the frequency spectrum. + * Use with <code>getEnergy()</code> to measure amplitude at specific + * frequencies, or within a range of frequencies. </p> + * + * <p>FFT analyzes a very short snapshot of sound called a sample + * buffer. It returns an array of amplitude measurements, referred + * to as <code>bins</code>. The array is 1024 bins long by default. + * You can change the bin array length, but it must be a power of 2 + * between 16 and 1024 in order for the FFT algorithm to function + * correctly. The actual size of the FFT buffer is twice the + * number of bins, so given a standard sample rate, the buffer is + * 2048/44100 seconds long.</p> + * + * + * @class p5.FFT + * @constructor + * @param {Number} [smoothing] Smooth results of Freq Spectrum. + * 0.0 < smoothing < 1.0. + * Defaults to 0.8. + * @param {Number} [bins] Length of resulting array. + * Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @example + * <div><code> + * function preload(){ + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup(){ + * var cnv = createCanvas(100,100); + * cnv.mouseClicked(togglePlay); + * fft = new p5.FFT(); + * sound.amp(0.2); + * } + * + * function draw(){ + * background(0); + * + * var spectrum = fft.analyze(); + * noStroke(); + * fill(0,255,0); // spectrum is green + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width / spectrum.length, h ) + * } + * + * var waveform = fft.waveform(); + * noFill(); + * beginShape(); + * stroke(255,0,0); // waveform is red + * strokeWeight(1); + * for (var i = 0; i< waveform.length; i++){ + * var x = map(i, 0, waveform.length, 0, width); + * var y = map( waveform[i], -1, 1, 0, height); + * vertex(x,y); + * } + * endShape(); + * + * text('click to play/pause', 4, 10); + * } + * + * // fade sound if mouse is over canvas + * function togglePlay() { + * if (sound.isPlaying()) { + * sound.pause(); + * } else { + * sound.loop(); + * } + * } + * </code></div> + */ + p5.FFT = function (smoothing, bins) { + this.input = this.analyser = p5sound.audiocontext.createAnalyser(); + Object.defineProperties(this, { + bins: { + get: function () { + return this.analyser.fftSize / 2; + }, + set: function (b) { + this.analyser.fftSize = b * 2; + }, + configurable: true, + enumerable: true + }, + smoothing: { + get: function () { + return this.analyser.smoothingTimeConstant; + }, + set: function (s) { + this.analyser.smoothingTimeConstant = s; + }, + configurable: true, + enumerable: true + } + }); + // set default smoothing and bins + this.smooth(smoothing); + this.bins = bins || 1024; + // default connections to p5sound fftMeter + p5sound.fftMeter.connect(this.analyser); + this.freqDomain = new Uint8Array(this.analyser.frequencyBinCount); + this.timeDomain = new Uint8Array(this.analyser.frequencyBinCount); + // predefined frequency ranges, these will be tweakable + this.bass = [ + 20, + 140 + ]; + this.lowMid = [ + 140, + 400 + ]; + this.mid = [ + 400, + 2600 + ]; + this.highMid = [ + 2600, + 5200 + ]; + this.treble = [ + 5200, + 14000 + ]; + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Set the input source for the FFT analysis. If no source is + * provided, FFT will analyze all sound in the sketch. + * + * @method setInput + * @param {Object} [source] p5.sound object (or web audio API source node) + */ + p5.FFT.prototype.setInput = function (source) { + if (!source) { + p5sound.fftMeter.connect(this.analyser); + } else { + if (source.output) { + source.output.connect(this.analyser); + } else if (source.connect) { + source.connect(this.analyser); + } + p5sound.fftMeter.disconnect(); + } + }; + /** + * Returns an array of amplitude values (between -1.0 and +1.0) that represent + * a snapshot of amplitude readings in a single buffer. Length will be + * equal to bins (defaults to 1024). Can be used to draw the waveform + * of a sound. + * + * @method waveform + * @param {Number} [bins] Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @param {String} [precision] If any value is provided, will return results + * in a Float32 Array which is more precise + * than a regular array. + * @return {Array} Array Array of amplitude values (-1 to 1) + * over time. Array length = bins. + * + */ + p5.FFT.prototype.waveform = function () { + var bins, mode, normalArray; + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] === 'number') { + bins = arguments[i]; + this.analyser.fftSize = bins * 2; + } + if (typeof arguments[i] === 'string') { + mode = arguments[i]; + } + } + // getFloatFrequencyData doesnt work in Safari as of 5/2015 + if (mode && !p5.prototype._isSafari()) { + timeToFloat(this, this.timeDomain); + this.analyser.getFloatTimeDomainData(this.timeDomain); + return this.timeDomain; + } else { + timeToInt(this, this.timeDomain); + this.analyser.getByteTimeDomainData(this.timeDomain); + var normalArray = new Array(); + for (var j = 0; j < this.timeDomain.length; j++) { + var scaled = p5.prototype.map(this.timeDomain[j], 0, 255, -1, 1); + normalArray.push(scaled); + } + return normalArray; + } + }; + /** + * Returns an array of amplitude values (between 0 and 255) + * across the frequency spectrum. Length is equal to FFT bins + * (1024 by default). The array indices correspond to frequencies + * (i.e. pitches), from the lowest to the highest that humans can + * hear. Each value represents amplitude at that slice of the + * frequency spectrum. Must be called prior to using + * <code>getEnergy()</code>. + * + * @method analyze + * @param {Number} [bins] Must be a power of two between + * 16 and 1024. Defaults to 1024. + * @param {Number} [scale] If "dB," returns decibel + * float measurements between + * -140 and 0 (max). + * Otherwise returns integers from 0-255. + * @return {Array} spectrum Array of energy (amplitude/volume) + * values across the frequency spectrum. + * Lowest energy (silence) = 0, highest + * possible is 255. + * @example + * <div><code> + * var osc; + * var fft; + * + * function setup(){ + * createCanvas(100,100); + * osc = new p5.Oscillator(); + * osc.amp(0); + * osc.start(); + * fft = new p5.FFT(); + * } + * + * function draw(){ + * background(0); + * + * var freq = map(mouseX, 0, 800, 20, 15000); + * freq = constrain(freq, 1, 20000); + * osc.freq(freq); + * + * var spectrum = fft.analyze(); + * noStroke(); + * fill(0,255,0); // spectrum is green + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width / spectrum.length, h ); + * } + * + * stroke(255); + * text('Freq: ' + round(freq)+'Hz', 10, 10); + * + * isMouseOverCanvas(); + * } + * + * // only play sound when mouse is over canvas + * function isMouseOverCanvas() { + * var mX = mouseX, mY = mouseY; + * if (mX > 0 && mX < width && mY < height && mY > 0) { + * osc.amp(0.5, 0.2); + * } else { + * osc.amp(0, 0.2); + * } + * } + * </code></div> + * + * + */ + p5.FFT.prototype.analyze = function () { + var mode; + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] === 'number') { + this.bins = arguments[i]; + this.analyser.fftSize = this.bins * 2; + } + if (typeof arguments[i] === 'string') { + mode = arguments[i]; + } + } + if (mode && mode.toLowerCase() === 'db') { + freqToFloat(this); + this.analyser.getFloatFrequencyData(this.freqDomain); + return this.freqDomain; + } else { + freqToInt(this, this.freqDomain); + this.analyser.getByteFrequencyData(this.freqDomain); + var normalArray = Array.apply([], this.freqDomain); + normalArray.length === this.analyser.fftSize; + normalArray.constructor === Array; + return normalArray; + } + }; + /** + * Returns the amount of energy (volume) at a specific + * <a href="https://en.wikipedia.org/wiki/Audio_frequency" target="_blank"> + * frequency</a>, or the average amount of energy between two + * frequencies. Accepts Number(s) corresponding + * to frequency (in Hz), or a String corresponding to predefined + * frequency ranges ("bass", "lowMid", "mid", "highMid", "treble"). + * Returns a range between 0 (no energy/volume at that frequency) and + * 255 (maximum energy). + * <em>NOTE: analyze() must be called prior to getEnergy(). Analyze() + * tells the FFT to analyze frequency data, and getEnergy() uses + * the results determine the value at a specific frequency or + * range of frequencies.</em></p> + * + * @method getEnergy + * @param {Number|String} frequency1 Will return a value representing + * energy at this frequency. Alternately, + * the strings "bass", "lowMid" "mid", + * "highMid", and "treble" will return + * predefined frequency ranges. + * @param {Number} [frequency2] If a second frequency is given, + * will return average amount of + * energy that exists between the + * two frequencies. + * @return {Number} Energy Energy (volume/amplitude) from + * 0 and 255. + * + */ + p5.FFT.prototype.getEnergy = function (frequency1, frequency2) { + var nyquist = p5sound.audiocontext.sampleRate / 2; + if (frequency1 === 'bass') { + frequency1 = this.bass[0]; + frequency2 = this.bass[1]; + } else if (frequency1 === 'lowMid') { + frequency1 = this.lowMid[0]; + frequency2 = this.lowMid[1]; + } else if (frequency1 === 'mid') { + frequency1 = this.mid[0]; + frequency2 = this.mid[1]; + } else if (frequency1 === 'highMid') { + frequency1 = this.highMid[0]; + frequency2 = this.highMid[1]; + } else if (frequency1 === 'treble') { + frequency1 = this.treble[0]; + frequency2 = this.treble[1]; + } + if (typeof frequency1 !== 'number') { + throw 'invalid input for getEnergy()'; + } else if (!frequency2) { + // if only one parameter: + var index = Math.round(frequency1 / nyquist * this.freqDomain.length); + return this.freqDomain[index]; + } else if (frequency1 && frequency2) { + // if two parameters: + // if second is higher than first + if (frequency1 > frequency2) { + var swap = frequency2; + frequency2 = frequency1; + frequency1 = swap; + } + var lowIndex = Math.round(frequency1 / nyquist * this.freqDomain.length); + var highIndex = Math.round(frequency2 / nyquist * this.freqDomain.length); + var total = 0; + var numFrequencies = 0; + // add up all of the values for the frequencies + for (var i = lowIndex; i <= highIndex; i++) { + total += this.freqDomain[i]; + numFrequencies += 1; + } + // divide by total number of frequencies + var toReturn = total / numFrequencies; + return toReturn; + } else { + throw 'invalid input for getEnergy()'; + } + }; + // compatability with v.012, changed to getEnergy in v.0121. Will be deprecated... + p5.FFT.prototype.getFreq = function (freq1, freq2) { + console.log('getFreq() is deprecated. Please use getEnergy() instead.'); + var x = this.getEnergy(freq1, freq2); + return x; + }; + /** + * Returns the + * <a href="http://en.wikipedia.org/wiki/Spectral_centroid" target="_blank"> + * spectral centroid</a> of the input signal. + * <em>NOTE: analyze() must be called prior to getCentroid(). Analyze() + * tells the FFT to analyze frequency data, and getCentroid() uses + * the results determine the spectral centroid.</em></p> + * + * @method getCentroid + * @return {Number} Spectral Centroid Frequency Frequency of the spectral centroid in Hz. + * + * + * @example + * <div><code> + * + * + *function setup(){ + * cnv = createCanvas(100,100); + * sound = new p5.AudioIn(); + * sound.start(); + * fft = new p5.FFT(); + * sound.connect(fft); + *} + * + * + *function draw(){ + * + * var centroidplot = 0.0; + * var spectralCentroid = 0; + * + * + * background(0); + * stroke(0,255,0); + * var spectrum = fft.analyze(); + * fill(0,255,0); // spectrum is green + * + * //draw the spectrum + * for (var i = 0; i< spectrum.length; i++){ + * var x = map(log(i), 0, log(spectrum.length), 0, width); + * var h = map(spectrum[i], 0, 255, 0, height); + * var rectangle_width = (log(i+1)-log(i))*(width/log(spectrum.length)); + * rect(x, height, rectangle_width, -h ) + * } + + * var nyquist = 22050; + * + * // get the centroid + * spectralCentroid = fft.getCentroid(); + * + * // the mean_freq_index calculation is for the display. + * var mean_freq_index = spectralCentroid/(nyquist/spectrum.length); + * + * centroidplot = map(log(mean_freq_index), 0, log(spectrum.length), 0, width); + * + * + * stroke(255,0,0); // the line showing where the centroid is will be red + * + * rect(centroidplot, 0, width / spectrum.length, height) + * noStroke(); + * fill(255,255,255); // text is white + * text("centroid: ", 10, 20); + * text(round(spectralCentroid)+" Hz", 10, 40); + *} + * </code></div> + */ + p5.FFT.prototype.getCentroid = function () { + var nyquist = p5sound.audiocontext.sampleRate / 2; + var cumulative_sum = 0; + var centroid_normalization = 0; + for (var i = 0; i < this.freqDomain.length; i++) { + cumulative_sum += i * this.freqDomain[i]; + centroid_normalization += this.freqDomain[i]; + } + var mean_freq_index = 0; + if (centroid_normalization !== 0) { + mean_freq_index = cumulative_sum / centroid_normalization; + } + var spec_centroid_freq = mean_freq_index * (nyquist / this.freqDomain.length); + return spec_centroid_freq; + }; + /** + * Smooth FFT analysis by averaging with the last analysis frame. + * + * @method smooth + * @param {Number} smoothing 0.0 < smoothing < 1.0. + * Defaults to 0.8. + */ + p5.FFT.prototype.smooth = function (s) { + if (typeof s !== 'undefined') { + this.smoothing = s; + } + return this.smoothing; + }; + p5.FFT.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.analyser) { + this.analyser.disconnect(); + delete this.analyser; + } + }; + /** + * Returns an array of average amplitude values for a given number + * of frequency bands split equally. N defaults to 16. + * <em>NOTE: analyze() must be called prior to linAverages(). Analyze() + * tells the FFT to analyze frequency data, and linAverages() uses + * the results to group them into a smaller set of averages.</em></p> + * + * @method linAverages + * @param {Number} N Number of returned frequency groups + * @return {Array} linearAverages Array of average amplitude values for each group + */ + p5.FFT.prototype.linAverages = function (N) { + var N = N || 16; + // This prevents undefined, null or 0 values of N + var spectrum = this.freqDomain; + var spectrumLength = spectrum.length; + var spectrumStep = Math.floor(spectrumLength / N); + var linearAverages = new Array(N); + // Keep a second index for the current average group and place the values accordingly + // with only one loop in the spectrum data + var groupIndex = 0; + for (var specIndex = 0; specIndex < spectrumLength; specIndex++) { + linearAverages[groupIndex] = linearAverages[groupIndex] !== undefined ? (linearAverages[groupIndex] + spectrum[specIndex]) / 2 : spectrum[specIndex]; + // Increase the group index when the last element of the group is processed + if (specIndex % spectrumStep === spectrumStep - 1) { + groupIndex++; + } + } + return linearAverages; + }; + /** + * Returns an array of average amplitude values of the spectrum, for a given + * set of <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank"> + * Octave Bands</a> + * <em>NOTE: analyze() must be called prior to logAverages(). Analyze() + * tells the FFT to analyze frequency data, and logAverages() uses + * the results to group them into a smaller set of averages.</em></p> + * + * @method logAverages + * @param {Array} octaveBands Array of Octave Bands objects for grouping + * @return {Array} logAverages Array of average amplitude values for each group + */ + p5.FFT.prototype.logAverages = function (octaveBands) { + var nyquist = p5sound.audiocontext.sampleRate / 2; + var spectrum = this.freqDomain; + var spectrumLength = spectrum.length; + var logAverages = new Array(octaveBands.length); + // Keep a second index for the current average group and place the values accordingly + // With only one loop in the spectrum data + var octaveIndex = 0; + for (var specIndex = 0; specIndex < spectrumLength; specIndex++) { + var specIndexFrequency = Math.round(specIndex * nyquist / this.freqDomain.length); + // Increase the group index if the current frequency exceeds the limits of the band + if (specIndexFrequency > octaveBands[octaveIndex].hi) { + octaveIndex++; + } + logAverages[octaveIndex] = logAverages[octaveIndex] !== undefined ? (logAverages[octaveIndex] + spectrum[specIndex]) / 2 : spectrum[specIndex]; + } + return logAverages; + }; + /** + * Calculates and Returns the 1/N + * <a href="https://en.wikipedia.org/wiki/Octave_band" target="_blank">Octave Bands</a> + * N defaults to 3 and minimum central frequency to 15.625Hz. + * (1/3 Octave Bands ~= 31 Frequency Bands) + * Setting fCtr0 to a central value of a higher octave will ignore the lower bands + * and produce less frequency groups. + * + * @method getOctaveBands + * @param {Number} N Specifies the 1/N type of generated octave bands + * @param {Number} fCtr0 Minimum central frequency for the lowest band + * @return {Array} octaveBands Array of octave band objects with their bounds + */ + p5.FFT.prototype.getOctaveBands = function (N, fCtr0) { + var N = N || 3; + // Default to 1/3 Octave Bands + var fCtr0 = fCtr0 || 15.625; + // Minimum central frequency, defaults to 15.625Hz + var octaveBands = []; + var lastFrequencyBand = { + lo: fCtr0 / Math.pow(2, 1 / (2 * N)), + ctr: fCtr0, + hi: fCtr0 * Math.pow(2, 1 / (2 * N)) + }; + octaveBands.push(lastFrequencyBand); + var nyquist = p5sound.audiocontext.sampleRate / 2; + while (lastFrequencyBand.hi < nyquist) { + var newFrequencyBand = {}; + newFrequencyBand.lo = lastFrequencyBand.hi; + newFrequencyBand.ctr = lastFrequencyBand.ctr * Math.pow(2, 1 / N); + newFrequencyBand.hi = newFrequencyBand.ctr * Math.pow(2, 1 / (2 * N)); + octaveBands.push(newFrequencyBand); + lastFrequencyBand = newFrequencyBand; + } + return octaveBands; + }; + // helper methods to convert type from float (dB) to int (0-255) + var freqToFloat = function (fft) { + if (fft.freqDomain instanceof Float32Array === false) { + fft.freqDomain = new Float32Array(fft.analyser.frequencyBinCount); + } + }; + var freqToInt = function (fft) { + if (fft.freqDomain instanceof Uint8Array === false) { + fft.freqDomain = new Uint8Array(fft.analyser.frequencyBinCount); + } + }; + var timeToFloat = function (fft) { + if (fft.timeDomain instanceof Float32Array === false) { + fft.timeDomain = new Float32Array(fft.analyser.frequencyBinCount); + } + }; + var timeToInt = function (fft) { + if (fft.timeDomain instanceof Uint8Array === false) { + fft.timeDomain = new Uint8Array(fft.analyser.frequencyBinCount); + } + }; +}(master); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_SignalBase; +Tone_signal_SignalBase = function (Tone) { + 'use strict'; + Tone.SignalBase = function () { + }; + Tone.extend(Tone.SignalBase); + Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) { + if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor || Tone.TimelineSignal && Tone.TimelineSignal === node.constructor) { + node._param.cancelScheduledValues(0); + node._param.value = 0; + node.overridden = true; + } else if (node instanceof AudioParam) { + node.cancelScheduledValues(0); + node.value = 0; + } + Tone.prototype.connect.call(this, node, outputNumber, inputNumber); + return this; + }; + return Tone.SignalBase; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_WaveShaper; +Tone_signal_WaveShaper = function (Tone) { + 'use strict'; + Tone.WaveShaper = function (mapping, bufferLen) { + this._shaper = this.input = this.output = this.context.createWaveShaper(); + this._curve = null; + if (Array.isArray(mapping)) { + this.curve = mapping; + } else if (isFinite(mapping) || this.isUndef(mapping)) { + this._curve = new Float32Array(this.defaultArg(mapping, 1024)); + } else if (this.isFunction(mapping)) { + this._curve = new Float32Array(this.defaultArg(bufferLen, 1024)); + this.setMap(mapping); + } + }; + Tone.extend(Tone.WaveShaper, Tone.SignalBase); + Tone.WaveShaper.prototype.setMap = function (mapping) { + for (var i = 0, len = this._curve.length; i < len; i++) { + var normalized = i / (len - 1) * 2 - 1; + this._curve[i] = mapping(normalized, i); + } + this._shaper.curve = this._curve; + return this; + }; + Object.defineProperty(Tone.WaveShaper.prototype, 'curve', { + get: function () { + return this._shaper.curve; + }, + set: function (mapping) { + this._curve = new Float32Array(mapping); + this._shaper.curve = this._curve; + } + }); + Object.defineProperty(Tone.WaveShaper.prototype, 'oversample', { + get: function () { + return this._shaper.oversample; + }, + set: function (oversampling) { + if ([ + 'none', + '2x', + '4x' + ].indexOf(oversampling) !== -1) { + this._shaper.oversample = oversampling; + } else { + throw new RangeError('Tone.WaveShaper: oversampling must be either \'none\', \'2x\', or \'4x\''); + } + } + }); + Tone.WaveShaper.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._shaper.disconnect(); + this._shaper = null; + this._curve = null; + return this; + }; + return Tone.WaveShaper; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_type_TimeBase; +Tone_type_TimeBase = function (Tone) { + Tone.TimeBase = function (val, units) { + if (this instanceof Tone.TimeBase) { + this._expr = this._noOp; + if (val instanceof Tone.TimeBase) { + this.copy(val); + } else if (!this.isUndef(units) || this.isNumber(val)) { + units = this.defaultArg(units, this._defaultUnits); + var method = this._primaryExpressions[units].method; + this._expr = method.bind(this, val); + } else if (this.isString(val)) { + this.set(val); + } else if (this.isUndef(val)) { + this._expr = this._defaultExpr(); + } + } else { + return new Tone.TimeBase(val, units); + } + }; + Tone.extend(Tone.TimeBase); + Tone.TimeBase.prototype.set = function (exprString) { + this._expr = this._parseExprString(exprString); + return this; + }; + Tone.TimeBase.prototype.clone = function () { + var instance = new this.constructor(); + instance.copy(this); + return instance; + }; + Tone.TimeBase.prototype.copy = function (time) { + var val = time._expr(); + return this.set(val); + }; + Tone.TimeBase.prototype._primaryExpressions = { + 'n': { + regexp: /^(\d+)n/i, + method: function (value) { + value = parseInt(value); + if (value === 1) { + return this._beatsToUnits(this._timeSignature()); + } else { + return this._beatsToUnits(4 / value); + } + } + }, + 't': { + regexp: /^(\d+)t/i, + method: function (value) { + value = parseInt(value); + return this._beatsToUnits(8 / (parseInt(value) * 3)); + } + }, + 'm': { + regexp: /^(\d+)m/i, + method: function (value) { + return this._beatsToUnits(parseInt(value) * this._timeSignature()); + } + }, + 'i': { + regexp: /^(\d+)i/i, + method: function (value) { + return this._ticksToUnits(parseInt(value)); + } + }, + 'hz': { + regexp: /^(\d+(?:\.\d+)?)hz/i, + method: function (value) { + return this._frequencyToUnits(parseFloat(value)); + } + }, + 'tr': { + regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method: function (m, q, s) { + var total = 0; + if (m && m !== '0') { + total += this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== '0') { + total += this._beatsToUnits(parseFloat(q)); + } + if (s && s !== '0') { + total += this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }, + 's': { + regexp: /^(\d+(?:\.\d+)?s)/, + method: function (value) { + return this._secondsToUnits(parseFloat(value)); + } + }, + 'samples': { + regexp: /^(\d+)samples/, + method: function (value) { + return parseInt(value) / this.context.sampleRate; + } + }, + 'default': { + regexp: /^(\d+(?:\.\d+)?)/, + method: function (value) { + return this._primaryExpressions[this._defaultUnits].method.call(this, value); + } + } + }; + Tone.TimeBase.prototype._binaryExpressions = { + '+': { + regexp: /^\+/, + precedence: 2, + method: function (lh, rh) { + return lh() + rh(); + } + }, + '-': { + regexp: /^\-/, + precedence: 2, + method: function (lh, rh) { + return lh() - rh(); + } + }, + '*': { + regexp: /^\*/, + precedence: 1, + method: function (lh, rh) { + return lh() * rh(); + } + }, + '/': { + regexp: /^\//, + precedence: 1, + method: function (lh, rh) { + return lh() / rh(); + } + } + }; + Tone.TimeBase.prototype._unaryExpressions = { + 'neg': { + regexp: /^\-/, + method: function (lh) { + return -lh(); + } + } + }; + Tone.TimeBase.prototype._syntaxGlue = { + '(': { regexp: /^\(/ }, + ')': { regexp: /^\)/ } + }; + Tone.TimeBase.prototype._tokenize = function (expr) { + var position = -1; + var tokens = []; + while (expr.length > 0) { + expr = expr.trim(); + var token = getNextToken(expr, this); + tokens.push(token); + expr = expr.substr(token.value.length); + } + function getNextToken(expr, context) { + var expressions = [ + '_binaryExpressions', + '_unaryExpressions', + '_primaryExpressions', + '_syntaxGlue' + ]; + for (var i = 0; i < expressions.length; i++) { + var group = context[expressions[i]]; + for (var opName in group) { + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null) { + return { + method: op.method, + precedence: op.precedence, + regexp: op.regexp, + value: match[0] + }; + } + } + } + throw new SyntaxError('Tone.TimeBase: Unexpected token ' + expr); + } + return { + next: function () { + return tokens[++position]; + }, + peek: function () { + return tokens[position + 1]; + } + }; + }; + Tone.TimeBase.prototype._matchGroup = function (token, group, prec) { + var ret = false; + if (!this.isUndef(token)) { + for (var opName in group) { + var op = group[opName]; + if (op.regexp.test(token.value)) { + if (!this.isUndef(prec)) { + if (op.precedence === prec) { + return op; + } + } else { + return op; + } + } + } + } + return ret; + }; + Tone.TimeBase.prototype._parseBinary = function (lexer, precedence) { + if (this.isUndef(precedence)) { + precedence = 2; + } + var expr; + if (precedence < 0) { + expr = this._parseUnary(lexer); + } else { + expr = this._parseBinary(lexer, precedence - 1); + } + var token = lexer.peek(); + while (token && this._matchGroup(token, this._binaryExpressions, precedence)) { + token = lexer.next(); + expr = token.method.bind(this, expr, this._parseBinary(lexer, precedence - 1)); + token = lexer.peek(); + } + return expr; + }; + Tone.TimeBase.prototype._parseUnary = function (lexer) { + var token, expr; + token = lexer.peek(); + var op = this._matchGroup(token, this._unaryExpressions); + if (op) { + token = lexer.next(); + expr = this._parseUnary(lexer); + return op.method.bind(this, expr); + } + return this._parsePrimary(lexer); + }; + Tone.TimeBase.prototype._parsePrimary = function (lexer) { + var token, expr; + token = lexer.peek(); + if (this.isUndef(token)) { + throw new SyntaxError('Tone.TimeBase: Unexpected end of expression'); + } + if (this._matchGroup(token, this._primaryExpressions)) { + token = lexer.next(); + var matching = token.value.match(token.regexp); + return token.method.bind(this, matching[1], matching[2], matching[3]); + } + if (token && token.value === '(') { + lexer.next(); + expr = this._parseBinary(lexer); + token = lexer.next(); + if (!(token && token.value === ')')) { + throw new SyntaxError('Expected )'); + } + return expr; + } + throw new SyntaxError('Tone.TimeBase: Cannot process token ' + token.value); + }; + Tone.TimeBase.prototype._parseExprString = function (exprString) { + if (!this.isString(exprString)) { + exprString = exprString.toString(); + } + var lexer = this._tokenize(exprString); + var tree = this._parseBinary(lexer); + return tree; + }; + Tone.TimeBase.prototype._noOp = function () { + return 0; + }; + Tone.TimeBase.prototype._defaultExpr = function () { + return this._noOp; + }; + Tone.TimeBase.prototype._defaultUnits = 's'; + Tone.TimeBase.prototype._frequencyToUnits = function (freq) { + return 1 / freq; + }; + Tone.TimeBase.prototype._beatsToUnits = function (beats) { + return 60 / Tone.Transport.bpm.value * beats; + }; + Tone.TimeBase.prototype._secondsToUnits = function (seconds) { + return seconds; + }; + Tone.TimeBase.prototype._ticksToUnits = function (ticks) { + return ticks * (this._beatsToUnits(1) / Tone.Transport.PPQ); + }; + Tone.TimeBase.prototype._timeSignature = function () { + return Tone.Transport.timeSignature; + }; + Tone.TimeBase.prototype._pushExpr = function (val, name, units) { + if (!(val instanceof Tone.TimeBase)) { + val = new this.constructor(val, units); + } + this._expr = this._binaryExpressions[name].method.bind(this, this._expr, val._expr); + return this; + }; + Tone.TimeBase.prototype.add = function (val, units) { + return this._pushExpr(val, '+', units); + }; + Tone.TimeBase.prototype.sub = function (val, units) { + return this._pushExpr(val, '-', units); + }; + Tone.TimeBase.prototype.mult = function (val, units) { + return this._pushExpr(val, '*', units); + }; + Tone.TimeBase.prototype.div = function (val, units) { + return this._pushExpr(val, '/', units); + }; + Tone.TimeBase.prototype.valueOf = function () { + return this._expr(); + }; + Tone.TimeBase.prototype.dispose = function () { + this._expr = null; + }; + return Tone.TimeBase; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_type_Time; +Tone_type_Time = function (Tone) { + Tone.Time = function (val, units) { + if (this instanceof Tone.Time) { + this._plusNow = false; + Tone.TimeBase.call(this, val, units); + } else { + return new Tone.Time(val, units); + } + }; + Tone.extend(Tone.Time, Tone.TimeBase); + Tone.Time.prototype._unaryExpressions = Object.create(Tone.TimeBase.prototype._unaryExpressions); + Tone.Time.prototype._unaryExpressions.quantize = { + regexp: /^@/, + method: function (rh) { + return Tone.Transport.nextSubdivision(rh()); + } + }; + Tone.Time.prototype._unaryExpressions.now = { + regexp: /^\+/, + method: function (lh) { + this._plusNow = true; + return lh(); + } + }; + Tone.Time.prototype.quantize = function (subdiv, percent) { + percent = this.defaultArg(percent, 1); + this._expr = function (expr, subdivision, percent) { + expr = expr(); + subdivision = subdivision.toSeconds(); + var multiple = Math.round(expr / subdivision); + var ideal = multiple * subdivision; + var diff = ideal - expr; + return expr + diff * percent; + }.bind(this, this._expr, new this.constructor(subdiv), percent); + return this; + }; + Tone.Time.prototype.addNow = function () { + this._plusNow = true; + return this; + }; + Tone.Time.prototype._defaultExpr = function () { + this._plusNow = true; + return this._noOp; + }; + Tone.Time.prototype.copy = function (time) { + Tone.TimeBase.prototype.copy.call(this, time); + this._plusNow = time._plusNow; + return this; + }; + Tone.Time.prototype.toNotation = function () { + var time = this.toSeconds(); + var testNotations = [ + '1m', + '2n', + '4n', + '8n', + '16n', + '32n', + '64n', + '128n' + ]; + var retNotation = this._toNotationHelper(time, testNotations); + var testTripletNotations = [ + '1m', + '2n', + '2t', + '4n', + '4t', + '8n', + '8t', + '16n', + '16t', + '32n', + '32t', + '64n', + '64t', + '128n' + ]; + var retTripletNotation = this._toNotationHelper(time, testTripletNotations); + if (retTripletNotation.split('+').length < retNotation.split('+').length) { + return retTripletNotation; + } else { + return retNotation; + } + }; + Tone.Time.prototype._toNotationHelper = function (units, testNotations) { + var threshold = this._notationToUnits(testNotations[testNotations.length - 1]); + var retNotation = ''; + for (var i = 0; i < testNotations.length; i++) { + var notationTime = this._notationToUnits(testNotations[i]); + var multiple = units / notationTime; + var floatingPointError = 0.000001; + if (1 - multiple % 1 < floatingPointError) { + multiple += floatingPointError; + } + multiple = Math.floor(multiple); + if (multiple > 0) { + if (multiple === 1) { + retNotation += testNotations[i]; + } else { + retNotation += multiple.toString() + '*' + testNotations[i]; + } + units -= multiple * notationTime; + if (units < threshold) { + break; + } else { + retNotation += ' + '; + } + } + } + if (retNotation === '') { + retNotation = '0'; + } + return retNotation; + }; + Tone.Time.prototype._notationToUnits = function (notation) { + var primaryExprs = this._primaryExpressions; + var notationExprs = [ + primaryExprs.n, + primaryExprs.t, + primaryExprs.m + ]; + for (var i = 0; i < notationExprs.length; i++) { + var expr = notationExprs[i]; + var match = notation.match(expr.regexp); + if (match) { + return expr.method.call(this, match[1]); + } + } + }; + Tone.Time.prototype.toBarsBeatsSixteenths = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.toSeconds() / quarterTime; + var measures = Math.floor(quarters / this._timeSignature()); + var sixteenths = quarters % 1 * 4; + quarters = Math.floor(quarters) % this._timeSignature(); + sixteenths = sixteenths.toString(); + if (sixteenths.length > 3) { + sixteenths = parseFloat(sixteenths).toFixed(3); + } + var progress = [ + measures, + quarters, + sixteenths + ]; + return progress.join(':'); + }; + Tone.Time.prototype.toTicks = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.valueOf() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + Tone.Time.prototype.toSamples = function () { + return this.toSeconds() * this.context.sampleRate; + }; + Tone.Time.prototype.toFrequency = function () { + return 1 / this.toSeconds(); + }; + Tone.Time.prototype.toSeconds = function () { + return this.valueOf(); + }; + Tone.Time.prototype.toMilliseconds = function () { + return this.toSeconds() * 1000; + }; + Tone.Time.prototype.valueOf = function () { + var val = this._expr(); + return val + (this._plusNow ? this.now() : 0); + }; + return Tone.Time; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_type_Frequency; +Tone_type_Frequency = function (Tone) { + Tone.Frequency = function (val, units) { + if (this instanceof Tone.Frequency) { + Tone.TimeBase.call(this, val, units); + } else { + return new Tone.Frequency(val, units); + } + }; + Tone.extend(Tone.Frequency, Tone.TimeBase); + Tone.Frequency.prototype._primaryExpressions = Object.create(Tone.TimeBase.prototype._primaryExpressions); + Tone.Frequency.prototype._primaryExpressions.midi = { + regexp: /^(\d+(?:\.\d+)?midi)/, + method: function (value) { + return this.midiToFrequency(value); + } + }; + Tone.Frequency.prototype._primaryExpressions.note = { + regexp: /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i, + method: function (pitch, octave) { + var index = noteToScaleIndex[pitch.toLowerCase()]; + var noteNumber = index + (parseInt(octave) + 1) * 12; + return this.midiToFrequency(noteNumber); + } + }; + Tone.Frequency.prototype._primaryExpressions.tr = { + regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method: function (m, q, s) { + var total = 1; + if (m && m !== '0') { + total *= this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== '0') { + total *= this._beatsToUnits(parseFloat(q)); + } + if (s && s !== '0') { + total *= this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }; + Tone.Frequency.prototype.transpose = function (interval) { + this._expr = function (expr, interval) { + var val = expr(); + return val * this.intervalToFrequencyRatio(interval); + }.bind(this, this._expr, interval); + return this; + }; + Tone.Frequency.prototype.harmonize = function (intervals) { + this._expr = function (expr, intervals) { + var val = expr(); + var ret = []; + for (var i = 0; i < intervals.length; i++) { + ret[i] = val * this.intervalToFrequencyRatio(intervals[i]); + } + return ret; + }.bind(this, this._expr, intervals); + return this; + }; + Tone.Frequency.prototype.toMidi = function () { + return this.frequencyToMidi(this.valueOf()); + }; + Tone.Frequency.prototype.toNote = function () { + var freq = this.valueOf(); + var log = Math.log(freq / Tone.Frequency.A4) / Math.LN2; + var noteNumber = Math.round(12 * log) + 57; + var octave = Math.floor(noteNumber / 12); + if (octave < 0) { + noteNumber += -12 * octave; + } + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); + }; + Tone.Frequency.prototype.toSeconds = function () { + return 1 / this.valueOf(); + }; + Tone.Frequency.prototype.toFrequency = function () { + return this.valueOf(); + }; + Tone.Frequency.prototype.toTicks = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.valueOf() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + Tone.Frequency.prototype._frequencyToUnits = function (freq) { + return freq; + }; + Tone.Frequency.prototype._ticksToUnits = function (ticks) { + return 1 / (ticks * 60 / (Tone.Transport.bpm.value * Tone.Transport.PPQ)); + }; + Tone.Frequency.prototype._beatsToUnits = function (beats) { + return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats); + }; + Tone.Frequency.prototype._secondsToUnits = function (seconds) { + return 1 / seconds; + }; + Tone.Frequency.prototype._defaultUnits = 'hz'; + var noteToScaleIndex = { + 'cbb': -2, + 'cb': -1, + 'c': 0, + 'c#': 1, + 'cx': 2, + 'dbb': 0, + 'db': 1, + 'd': 2, + 'd#': 3, + 'dx': 4, + 'ebb': 2, + 'eb': 3, + 'e': 4, + 'e#': 5, + 'ex': 6, + 'fbb': 3, + 'fb': 4, + 'f': 5, + 'f#': 6, + 'fx': 7, + 'gbb': 5, + 'gb': 6, + 'g': 7, + 'g#': 8, + 'gx': 9, + 'abb': 7, + 'ab': 8, + 'a': 9, + 'a#': 10, + 'ax': 11, + 'bbb': 9, + 'bb': 10, + 'b': 11, + 'b#': 12, + 'bx': 13 + }; + var scaleIndexToNote = [ + 'C', + 'C#', + 'D', + 'D#', + 'E', + 'F', + 'F#', + 'G', + 'G#', + 'A', + 'A#', + 'B' + ]; + Tone.Frequency.A4 = 440; + Tone.Frequency.prototype.midiToFrequency = function (midi) { + return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12); + }; + Tone.Frequency.prototype.frequencyToMidi = function (frequency) { + return 69 + 12 * Math.log(frequency / Tone.Frequency.A4) / Math.LN2; + }; + return Tone.Frequency; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_type_TransportTime; +Tone_type_TransportTime = function (Tone) { + Tone.TransportTime = function (val, units) { + if (this instanceof Tone.TransportTime) { + Tone.Time.call(this, val, units); + } else { + return new Tone.TransportTime(val, units); + } + }; + Tone.extend(Tone.TransportTime, Tone.Time); + Tone.TransportTime.prototype._unaryExpressions = Object.create(Tone.Time.prototype._unaryExpressions); + Tone.TransportTime.prototype._unaryExpressions.quantize = { + regexp: /^@/, + method: function (rh) { + var subdivision = this._secondsToTicks(rh()); + var multiple = Math.ceil(Tone.Transport.ticks / subdivision); + return this._ticksToUnits(multiple * subdivision); + } + }; + Tone.TransportTime.prototype._secondsToTicks = function (seconds) { + var quarterTime = this._beatsToUnits(1); + var quarters = seconds / quarterTime; + return Math.round(quarters * Tone.Transport.PPQ); + }; + Tone.TransportTime.prototype.valueOf = function () { + var val = this._secondsToTicks(this._expr()); + return val + (this._plusNow ? Tone.Transport.ticks : 0); + }; + Tone.TransportTime.prototype.toTicks = function () { + return this.valueOf(); + }; + Tone.TransportTime.prototype.toSeconds = function () { + var val = this._expr(); + return val + (this._plusNow ? Tone.Transport.seconds : 0); + }; + Tone.TransportTime.prototype.toFrequency = function () { + return 1 / this.toSeconds(); + }; + return Tone.TransportTime; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_type_Type; +Tone_type_Type = function (Tone) { + Tone.Type = { + Default: 'number', + Time: 'time', + Frequency: 'frequency', + TransportTime: 'transportTime', + Ticks: 'ticks', + NormalRange: 'normalRange', + AudioRange: 'audioRange', + Decibels: 'db', + Interval: 'interval', + BPM: 'bpm', + Positive: 'positive', + Cents: 'cents', + Degrees: 'degrees', + MIDI: 'midi', + BarsBeatsSixteenths: 'barsBeatsSixteenths', + Samples: 'samples', + Hertz: 'hertz', + Note: 'note', + Milliseconds: 'milliseconds', + Seconds: 'seconds', + Notation: 'notation' + }; + Tone.prototype.toSeconds = function (time) { + if (this.isNumber(time)) { + return time; + } else if (this.isUndef(time)) { + return this.now(); + } else if (this.isString(time)) { + return new Tone.Time(time).toSeconds(); + } else if (time instanceof Tone.TimeBase) { + return time.toSeconds(); + } + }; + Tone.prototype.toFrequency = function (freq) { + if (this.isNumber(freq)) { + return freq; + } else if (this.isString(freq) || this.isUndef(freq)) { + return new Tone.Frequency(freq).valueOf(); + } else if (freq instanceof Tone.TimeBase) { + return freq.toFrequency(); + } + }; + Tone.prototype.toTicks = function (time) { + if (this.isNumber(time) || this.isString(time)) { + return new Tone.TransportTime(time).toTicks(); + } else if (this.isUndef(time)) { + return Tone.Transport.ticks; + } else if (time instanceof Tone.TimeBase) { + return time.toTicks(); + } + }; + return Tone; +}(Tone_core_Tone, Tone_type_Time, Tone_type_Frequency, Tone_type_TransportTime); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Param; +Tone_core_Param = function (Tone) { + 'use strict'; + Tone.Param = function () { + var options = this.optionsObject(arguments, [ + 'param', + 'units', + 'convert' + ], Tone.Param.defaults); + this._param = this.input = options.param; + this.units = options.units; + this.convert = options.convert; + this.overridden = false; + this._lfo = null; + if (this.isObject(options.lfo)) { + this.value = options.lfo; + } else if (!this.isUndef(options.value)) { + this.value = options.value; + } + }; + Tone.extend(Tone.Param); + Tone.Param.defaults = { + 'units': Tone.Type.Default, + 'convert': true, + 'param': undefined + }; + Object.defineProperty(Tone.Param.prototype, 'value', { + get: function () { + return this._toUnits(this._param.value); + }, + set: function (value) { + if (this.isObject(value)) { + if (this.isUndef(Tone.LFO)) { + throw new Error('Include \'Tone.LFO\' to use an LFO as a Param value.'); + } + if (this._lfo) { + this._lfo.dispose(); + } + this._lfo = new Tone.LFO(value).start(); + this._lfo.connect(this.input); + } else { + var convertedVal = this._fromUnits(value); + this._param.cancelScheduledValues(0); + this._param.value = convertedVal; + } + } + }); + Tone.Param.prototype._fromUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Time: + return this.toSeconds(val); + case Tone.Type.Frequency: + return this.toFrequency(val); + case Tone.Type.Decibels: + return this.dbToGain(val); + case Tone.Type.NormalRange: + return Math.min(Math.max(val, 0), 1); + case Tone.Type.AudioRange: + return Math.min(Math.max(val, -1), 1); + case Tone.Type.Positive: + return Math.max(val, 0); + default: + return val; + } + } else { + return val; + } + }; + Tone.Param.prototype._toUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; + } + }; + Tone.Param.prototype._minOutput = 0.00001; + Tone.Param.prototype.setValueAtTime = function (value, time) { + value = this._fromUnits(value); + time = this.toSeconds(time); + if (time <= this.now() + this.blockTime) { + this._param.value = value; + } else { + this._param.setValueAtTime(value, time); + } + return this; + }; + Tone.Param.prototype.setRampPoint = function (now) { + now = this.defaultArg(now, this.now()); + var currentVal = this._param.value; + if (currentVal === 0) { + currentVal = this._minOutput; + } + this._param.setValueAtTime(currentVal, now); + return this; + }; + Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + this._param.linearRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + Tone.Param.prototype.exponentialRampToValue = function (value, rampTime, startTime) { + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.exponentialRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); + return this; + }; + Tone.Param.prototype.linearRampToValue = function (value, rampTime, startTime) { + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.linearRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); + return this; + }; + Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); + return this; + }; + Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration) { + for (var i = 0; i < values.length; i++) { + values[i] = this._fromUnits(values[i]); + } + this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); + return this; + }; + Tone.Param.prototype.cancelScheduledValues = function (startTime) { + this._param.cancelScheduledValues(this.toSeconds(startTime)); + return this; + }; + Tone.Param.prototype.rampTo = function (value, rampTime, startTime) { + rampTime = this.defaultArg(rampTime, 0); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM || this.units === Tone.Type.Decibels) { + this.exponentialRampToValue(value, rampTime, startTime); + } else { + this.linearRampToValue(value, rampTime, startTime); + } + return this; + }; + Object.defineProperty(Tone.Param.prototype, 'lfo', { + get: function () { + return this._lfo; + } + }); + Tone.Param.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._param = null; + if (this._lfo) { + this._lfo.dispose(); + this._lfo = null; + } + return this; + }; + return Tone.Param; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Gain; +Tone_core_Gain = function (Tone) { + 'use strict'; + if (window.GainNode && !AudioContext.prototype.createGain) { + AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; + } + Tone.Gain = function () { + var options = this.optionsObject(arguments, [ + 'gain', + 'units' + ], Tone.Gain.defaults); + this.input = this.output = this._gainNode = this.context.createGain(); + this.gain = new Tone.Param({ + 'param': this._gainNode.gain, + 'units': options.units, + 'value': options.gain, + 'convert': options.convert + }); + this._readOnly('gain'); + }; + Tone.extend(Tone.Gain); + Tone.Gain.defaults = { + 'gain': 1, + 'convert': true + }; + Tone.Gain.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._gainNode.disconnect(); + this._gainNode = null; + this._writable('gain'); + this.gain.dispose(); + this.gain = null; + }; + Tone.prototype.createInsOuts = function (inputs, outputs) { + if (inputs === 1) { + this.input = new Tone.Gain(); + } else if (inputs > 1) { + this.input = new Array(inputs); + } + if (outputs === 1) { + this.output = new Tone.Gain(); + } else if (outputs > 1) { + this.output = new Array(inputs); + } + }; + return Tone.Gain; +}(Tone_core_Tone, Tone_core_Param); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Signal; +Tone_signal_Signal = function (Tone) { + 'use strict'; + Tone.Signal = function () { + var options = this.optionsObject(arguments, [ + 'value', + 'units' + ], Tone.Signal.defaults); + this.output = this._gain = this.context.createGain(); + options.param = this._gain.gain; + Tone.Param.call(this, options); + this.input = this._param = this._gain.gain; + this.context.getConstant(1).chain(this._gain); + }; + Tone.extend(Tone.Signal, Tone.Param); + Tone.Signal.defaults = { + 'value': 0, + 'units': Tone.Type.Default, + 'convert': true + }; + Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect; + Tone.Signal.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._param = null; + this._gain.disconnect(); + this._gain = null; + return this; + }; + return Tone.Signal; +}(Tone_core_Tone, Tone_signal_WaveShaper, Tone_type_Type, Tone_core_Param); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Add; +Tone_signal_Add = function (Tone) { + 'use strict'; + Tone.Add = function (value) { + this.createInsOuts(2, 0); + this._sum = this.input[0] = this.input[1] = this.output = new Tone.Gain(); + this._param = this.input[1] = new Tone.Signal(value); + this._param.connect(this._sum); + }; + Tone.extend(Tone.Add, Tone.Signal); + Tone.Add.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._sum.dispose(); + this._sum = null; + this._param.dispose(); + this._param = null; + return this; + }; + return Tone.Add; +}(Tone_core_Tone, Tone_signal_Signal); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Multiply; +Tone_signal_Multiply = function (Tone) { + 'use strict'; + Tone.Multiply = function (value) { + this.createInsOuts(2, 0); + this._mult = this.input[0] = this.output = new Tone.Gain(); + this._param = this.input[1] = this.output.gain; + this._param.value = this.defaultArg(value, 0); + }; + Tone.extend(Tone.Multiply, Tone.Signal); + Tone.Multiply.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._mult.dispose(); + this._mult = null; + this._param = null; + return this; + }; + return Tone.Multiply; +}(Tone_core_Tone, Tone_signal_Signal); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Scale; +Tone_signal_Scale = function (Tone) { + 'use strict'; + Tone.Scale = function (outputMin, outputMax) { + this._outputMin = this.defaultArg(outputMin, 0); + this._outputMax = this.defaultArg(outputMax, 1); + this._scale = this.input = new Tone.Multiply(1); + this._add = this.output = new Tone.Add(0); + this._scale.connect(this._add); + this._setRange(); + }; + Tone.extend(Tone.Scale, Tone.SignalBase); + Object.defineProperty(Tone.Scale.prototype, 'min', { + get: function () { + return this._outputMin; + }, + set: function (min) { + this._outputMin = min; + this._setRange(); + } + }); + Object.defineProperty(Tone.Scale.prototype, 'max', { + get: function () { + return this._outputMax; + }, + set: function (max) { + this._outputMax = max; + this._setRange(); + } + }); + Tone.Scale.prototype._setRange = function () { + this._add.value = this._outputMin; + this._scale.value = this._outputMax - this._outputMin; + }; + Tone.Scale.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._add.dispose(); + this._add = null; + this._scale.dispose(); + this._scale = null; + return this; + }; + return Tone.Scale; +}(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply); +var signal; +'use strict'; +signal = function () { + // Signal is built with the Tone.js signal by Yotam Mann + // https://github.com/TONEnoTONE/Tone.js/ + var Signal = Tone_signal_Signal; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + /** + * <p>p5.Signal is a constant audio-rate signal used by p5.Oscillator + * and p5.Envelope for modulation math.</p> + * + * <p>This is necessary because Web Audio is processed on a seprate clock. + * For example, the p5 draw loop runs about 60 times per second. But + * the audio clock must process samples 44100 times per second. If we + * want to add a value to each of those samples, we can't do it in the + * draw loop, but we can do it by adding a constant-rate audio signal.</p. + * + * <p>This class mostly functions behind the scenes in p5.sound, and returns + * a Tone.Signal from the Tone.js library by Yotam Mann. + * If you want to work directly with audio signals for modular + * synthesis, check out + * <a href='http://bit.ly/1oIoEng' target=_'blank'>tone.js.</a></p> + * + * @class p5.Signal + * @constructor + * @return {Tone.Signal} A Signal object from the Tone.js library + * @example + * <div><code> + * function setup() { + * carrier = new p5.Oscillator('sine'); + * carrier.amp(1); // set amplitude + * carrier.freq(220); // set frequency + * carrier.start(); // start oscillating + * + * modulator = new p5.Oscillator('sawtooth'); + * modulator.disconnect(); + * modulator.amp(1); + * modulator.freq(4); + * modulator.start(); + * + * // Modulator's default amplitude range is -1 to 1. + * // Multiply it by -200, so the range is -200 to 200 + * // then add 220 so the range is 20 to 420 + * carrier.freq( modulator.mult(-200).add(220) ); + * } + * </code></div> + */ + p5.Signal = function (value) { + var s = new Signal(value); + // p5sound.soundArray.push(s); + return s; + }; + /** + * Fade to value, for smooth transitions + * + * @method fade + * @param {Number} value Value to set this signal + * @param {Number} [secondsFromNow] Length of fade, in seconds from now + */ + Signal.prototype.fade = Signal.prototype.linearRampToValueAtTime; + Mult.prototype.fade = Signal.prototype.fade; + Add.prototype.fade = Signal.prototype.fade; + Scale.prototype.fade = Signal.prototype.fade; + /** + * Connect a p5.sound object or Web Audio node to this + * p5.Signal so that its amplitude values can be scaled. + * + * @method setInput + * @param {Object} input + */ + Signal.prototype.setInput = function (_input) { + _input.connect(this); + }; + Mult.prototype.setInput = Signal.prototype.setInput; + Add.prototype.setInput = Signal.prototype.setInput; + Scale.prototype.setInput = Signal.prototype.setInput; + // signals can add / mult / scale themselves + /** + * Add a constant value to this audio signal, + * and return the resulting audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalAdd. + * + * @method add + * @param {Number} number + * @return {p5.Signal} object + */ + Signal.prototype.add = function (num) { + var add = new Add(num); + // add.setInput(this); + this.connect(add); + return add; + }; + Mult.prototype.add = Signal.prototype.add; + Add.prototype.add = Signal.prototype.add; + Scale.prototype.add = Signal.prototype.add; + /** + * Multiply this signal by a constant value, + * and return the resulting audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalMult. + * + * @method mult + * @param {Number} number to multiply + * @return {p5.Signal} object + */ + Signal.prototype.mult = function (num) { + var mult = new Mult(num); + // mult.setInput(this); + this.connect(mult); + return mult; + }; + Mult.prototype.mult = Signal.prototype.mult; + Add.prototype.mult = Signal.prototype.mult; + Scale.prototype.mult = Signal.prototype.mult; + /** + * Scale this signal value to a given range, + * and return the result as an audio signal. Does + * not change the value of the original signal, + * instead it returns a new p5.SignalScale. + * + * @method scale + * @param {Number} number to multiply + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.Signal} object + */ + Signal.prototype.scale = function (inMin, inMax, outMin, outMax) { + var mapOutMin, mapOutMax; + if (arguments.length === 4) { + mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; + mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; + } else { + mapOutMin = arguments[0]; + mapOutMax = arguments[1]; + } + var scale = new Scale(mapOutMin, mapOutMax); + this.connect(scale); + return scale; + }; + Mult.prototype.scale = Signal.prototype.scale; + Add.prototype.scale = Signal.prototype.scale; + Scale.prototype.scale = Signal.prototype.scale; +}(Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale); +var oscillator; +'use strict'; +oscillator = function () { + var p5sound = master; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + /** + * <p>Creates a signal that oscillates between -1.0 and 1.0. + * By default, the oscillation takes the form of a sinusoidal + * shape ('sine'). Additional types include 'triangle', + * 'sawtooth' and 'square'. The frequency defaults to + * 440 oscillations per second (440Hz, equal to the pitch of an + * 'A' note).</p> + * + * <p>Set the type of oscillation with setType(), or by instantiating a + * specific oscillator: <a href="/reference/#/p5.SinOsc">p5.SinOsc</a>, <a + * href="/reference/#/p5.TriOsc">p5.TriOsc</a>, <a + * href="/reference/#/p5.SqrOsc">p5.SqrOsc</a>, or <a + * href="/reference/#/p5.SawOsc">p5.SawOsc</a>. + * </p> + * + * @class p5.Oscillator + * @constructor + * @param {Number} [freq] frequency defaults to 440Hz + * @param {String} [type] type of oscillator. Options: + * 'sine' (default), 'triangle', + * 'sawtooth', 'square' + * @example + * <div><code> + * var osc; + * var playing = false; + * + * function setup() { + * backgroundColor = color(255,0,255); + * textAlign(CENTER); + * + * osc = new p5.Oscillator(); + * osc.setType('sine'); + * osc.freq(240); + * osc.amp(0); + * osc.start(); + * } + * + * function draw() { + * background(backgroundColor) + * text('click to play', width/2, height/2); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY < height && mouseY > 0) { + * if (!playing) { + * // ramp amplitude to 0.5 over 0.05 seconds + * osc.amp(0.5, 0.05); + * playing = true; + * backgroundColor = color(0,255,255); + * } else { + * // ramp amplitude to 0 over 0.5 seconds + * osc.amp(0, 0.5); + * playing = false; + * backgroundColor = color(255,0,255); + * } + * } + * } + * </code> </div> + */ + p5.Oscillator = function (freq, type) { + if (typeof freq === 'string') { + var f = type; + type = freq; + freq = f; + } + if (typeof type === 'number') { + var f = type; + type = freq; + freq = f; + } + this.started = false; + // components + this.phaseAmount = undefined; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.f = freq || 440; + // frequency + this.oscillator.type = type || 'sine'; + this.oscillator.frequency.setValueAtTime(this.f, p5sound.audiocontext.currentTime); + // connections + this.output = p5sound.audiocontext.createGain(); + this._freqMods = []; + // modulators connected to this oscillator's frequency + // set default output gain to 0.5 + this.output.gain.value = 0.5; + this.output.gain.setValueAtTime(0.5, p5sound.audiocontext.currentTime); + this.oscillator.connect(this.output); + // stereo panning + this.panPosition = 0; + this.connection = p5sound.input; + // connect to p5sound by default + this.panner = new p5.Panner(this.output, this.connection, 1); + //array of math operation signal chaining + this.mathOps = [this.output]; + // add to the soundArray so we can dispose of the osc later + p5sound.soundArray.push(this); + }; + /** + * Start an oscillator. Accepts an optional parameter to + * determine how long (in seconds from now) until the + * oscillator starts. + * + * @method start + * @param {Number} [time] startTime in seconds from now. + * @param {Number} [frequency] frequency in Hz. + */ + p5.Oscillator.prototype.start = function (time, f) { + if (this.started) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + } + if (!this.started) { + var freq = f || this.f; + var type = this.oscillator.type; + // set old osc free to be garbage collected (memory) + if (this.oscillator) { + this.oscillator.disconnect(); + delete this.oscillator; + } + // var detune = this.oscillator.frequency.value; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.oscillator.frequency.value = Math.abs(freq); + this.oscillator.type = type; + // this.oscillator.detune.value = detune; + this.oscillator.connect(this.output); + time = time || 0; + this.oscillator.start(time + p5sound.audiocontext.currentTime); + this.freqNode = this.oscillator.frequency; + // if other oscillators are already connected to this osc's freq + for (var i in this._freqMods) { + if (typeof this._freqMods[i].connect !== 'undefined') { + this._freqMods[i].connect(this.oscillator.frequency); + } + } + this.started = true; + } + }; + /** + * Stop an oscillator. Accepts an optional parameter + * to determine how long (in seconds from now) until the + * oscillator stops. + * + * @method stop + * @param {Number} secondsFromNow Time, in seconds from now. + */ + p5.Oscillator.prototype.stop = function (time) { + if (this.started) { + var t = time || 0; + var now = p5sound.audiocontext.currentTime; + this.oscillator.stop(t + now); + this.started = false; + } + }; + /** + * Set the amplitude between 0 and 1.0. Or, pass in an object + * such as an oscillator to modulate amplitude with an audio signal. + * + * @method amp + * @param {Number|Object} vol between 0 and 1.0 + * or a modulating signal/oscillator + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {AudioParam} gain If no value is provided, + * returns the Web Audio API + * AudioParam that controls + * this oscillator's + * gain/amplitude/volume) + */ + p5.Oscillator.prototype.amp = function (vol, rampTime, tFromNow) { + var self = this; + if (typeof vol === 'number') { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + } else if (vol) { + vol.connect(self.output.gain); + } else { + // return the Gain Node + return this.output.gain; + } + }; + // these are now the same thing + p5.Oscillator.prototype.fade = p5.Oscillator.prototype.amp; + p5.Oscillator.prototype.getAmp = function () { + return this.output.gain.value; + }; + /** + * Set frequency of an oscillator to a value. Or, pass in an object + * such as an oscillator to modulate the frequency with an audio signal. + * + * @method freq + * @param {Number|Object} Frequency Frequency in Hz + * or modulating signal/oscillator + * @param {Number} [rampTime] Ramp time (in seconds) + * @param {Number} [timeFromNow] Schedule this event to happen + * at x seconds from now + * @return {AudioParam} Frequency If no value is provided, + * returns the Web Audio API + * AudioParam that controls + * this oscillator's frequency + * @example + * <div><code> + * var osc = new p5.Oscillator(300); + * osc.start(); + * osc.freq(40, 10); + * </code></div> + */ + p5.Oscillator.prototype.freq = function (val, rampTime, tFromNow) { + if (typeof val === 'number' && !isNaN(val)) { + this.f = val; + var now = p5sound.audiocontext.currentTime; + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var t = now + tFromNow + rampTime; + // var currentFreq = this.oscillator.frequency.value; + // this.oscillator.frequency.cancelScheduledValues(now); + if (rampTime === 0) { + this.oscillator.frequency.setValueAtTime(val, tFromNow + now); + } else { + if (val > 0) { + this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + } else { + this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now); + } + } + // reset phase if oscillator has a phase + if (this.phaseAmount) { + this.phase(this.phaseAmount); + } + } else if (val) { + if (val.output) { + val = val.output; + } + val.connect(this.oscillator.frequency); + // keep track of what is modulating this param + // so it can be re-connected if + this._freqMods.push(val); + } else { + // return the Frequency Node + return this.oscillator.frequency; + } + }; + p5.Oscillator.prototype.getFreq = function () { + return this.oscillator.frequency.value; + }; + /** + * Set type to 'sine', 'triangle', 'sawtooth' or 'square'. + * + * @method setType + * @param {String} type 'sine', 'triangle', 'sawtooth' or 'square'. + */ + p5.Oscillator.prototype.setType = function (type) { + this.oscillator.type = type; + }; + p5.Oscillator.prototype.getType = function () { + return this.oscillator.type; + }; + /** + * Connect to a p5.sound / Web Audio object. + * + * @method connect + * @param {Object} unit A p5.sound or Web Audio object + */ + p5.Oscillator.prototype.connect = function (unit) { + if (!unit) { + this.panner.connect(p5sound.input); + } else if (unit.hasOwnProperty('input')) { + this.panner.connect(unit.input); + this.connection = unit.input; + } else { + this.panner.connect(unit); + this.connection = unit; + } + }; + /** + * Disconnect all outputs + * + * @method disconnect + */ + p5.Oscillator.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + if (this.panner) { + this.panner.disconnect(); + if (this.output) { + this.output.connect(this.panner); + } + } + this.oscMods = []; + }; + /** + * Pan between Left (-1) and Right (1) + * + * @method pan + * @param {Number} panning Number between -1 and 1 + * @param {Number} timeFromNow schedule this event to happen + * seconds from now + */ + p5.Oscillator.prototype.pan = function (pval, tFromNow) { + this.panPosition = pval; + this.panner.pan(pval, tFromNow); + }; + p5.Oscillator.prototype.getPan = function () { + return this.panPosition; + }; + // get rid of the oscillator + p5.Oscillator.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.oscillator) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + this.disconnect(); + this.panner = null; + this.oscillator = null; + } + // if it is a Pulse + if (this.osc2) { + this.osc2.dispose(); + } + }; + /** + * Set the phase of an oscillator between 0.0 and 1.0. + * In this implementation, phase is a delay time + * based on the oscillator's current frequency. + * + * @method phase + * @param {Number} phase float between 0.0 and 1.0 + */ + p5.Oscillator.prototype.phase = function (p) { + var delayAmt = p5.prototype.map(p, 0, 1, 0, 1 / this.f); + var now = p5sound.audiocontext.currentTime; + this.phaseAmount = p; + if (!this.dNode) { + // create a delay node + this.dNode = p5sound.audiocontext.createDelay(); + // put the delay node in between output and panner + this.oscillator.disconnect(); + this.oscillator.connect(this.dNode); + this.dNode.connect(this.output); + } + // set delay time to match phase: + this.dNode.delayTime.setValueAtTime(delayAmt, now); + }; + // ========================== // + // SIGNAL MATH FOR MODULATION // + // ========================== // + // return sigChain(this, scale, thisChain, nextChain, Scale); + var sigChain = function (o, mathObj, thisChain, nextChain, type) { + var chainSource = o.oscillator; + // if this type of math already exists in the chain, replace it + for (var i in o.mathOps) { + if (o.mathOps[i] instanceof type) { + chainSource.disconnect(); + o.mathOps[i].dispose(); + thisChain = i; + // assume nextChain is output gain node unless... + if (thisChain < o.mathOps.length - 2) { + nextChain = o.mathOps[i + 1]; + } + } + } + if (thisChain === o.mathOps.length - 1) { + o.mathOps.push(nextChain); + } + // assume source is the oscillator unless i > 0 + if (i > 0) { + chainSource = o.mathOps[i - 1]; + } + chainSource.disconnect(); + chainSource.connect(mathObj); + mathObj.connect(nextChain); + o.mathOps[thisChain] = mathObj; + return o; + }; + /** + * Add a value to the p5.Oscillator's output amplitude, + * and return the oscillator. Calling this method again + * will override the initial add() with a new value. + * + * @method add + * @param {Number} number Constant number to add + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with scaled output + * + */ + p5.Oscillator.prototype.add = function (num) { + var add = new Add(num); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, add, thisChain, nextChain, Add); + }; + /** + * Multiply the p5.Oscillator's output amplitude + * by a fixed value (i.e. turn it up!). Calling this method + * again will override the initial mult() with a new value. + * + * @method mult + * @param {Number} number Constant number to multiply + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with multiplied output + */ + p5.Oscillator.prototype.mult = function (num) { + var mult = new Mult(num); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, mult, thisChain, nextChain, Mult); + }; + /** + * Scale this oscillator's amplitude values to a given + * range, and return the oscillator. Calling this method + * again will override the initial scale() with new values. + * + * @method scale + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.Oscillator} Oscillator Returns this oscillator + * with scaled output + */ + p5.Oscillator.prototype.scale = function (inMin, inMax, outMin, outMax) { + var mapOutMin, mapOutMax; + if (arguments.length === 4) { + mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; + mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; + } else { + mapOutMin = arguments[0]; + mapOutMax = arguments[1]; + } + var scale = new Scale(mapOutMin, mapOutMax); + var thisChain = this.mathOps.length - 1; + var nextChain = this.output; + return sigChain(this, scale, thisChain, nextChain, Scale); + }; + // ============================== // + // SinOsc, TriOsc, SqrOsc, SawOsc // + // ============================== // + /** + * Constructor: <code>new p5.SinOsc()</code>. + * This creates a Sine Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('sine') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('sine')</code>. + * See p5.Oscillator for methods. + * + * @class p5.SinOsc + * @constructor + * @extends p5.Oscillator + * @param {Number} [freq] Set the frequency + */ + p5.SinOsc = function (freq) { + p5.Oscillator.call(this, freq, 'sine'); + }; + p5.SinOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.TriOsc()</code>. + * This creates a Triangle Wave Oscillator and is + * equivalent to <code>new p5.Oscillator('triangle') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('triangle')</code>. + * See p5.Oscillator for methods. + * + * @class p5.TriOsc + * @constructor + * @extends p5.Oscillator + * @param {Number} [freq] Set the frequency + */ + p5.TriOsc = function (freq) { + p5.Oscillator.call(this, freq, 'triangle'); + }; + p5.TriOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.SawOsc()</code>. + * This creates a SawTooth Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('sawtooth') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('sawtooth')</code>. + * See p5.Oscillator for methods. + * + * @class p5.SawOsc + * @constructor + * @extends p5.Oscillator + * @param {Number} [freq] Set the frequency + */ + p5.SawOsc = function (freq) { + p5.Oscillator.call(this, freq, 'sawtooth'); + }; + p5.SawOsc.prototype = Object.create(p5.Oscillator.prototype); + /** + * Constructor: <code>new p5.SqrOsc()</code>. + * This creates a Square Wave Oscillator and is + * equivalent to <code> new p5.Oscillator('square') + * </code> or creating a p5.Oscillator and then calling + * its method <code>setType('square')</code>. + * See p5.Oscillator for methods. + * + * @class p5.SqrOsc + * @constructor + * @extends p5.Oscillator + * @param {Number} [freq] Set the frequency + */ + p5.SqrOsc = function (freq) { + p5.Oscillator.call(this, freq, 'square'); + }; + p5.SqrOsc.prototype = Object.create(p5.Oscillator.prototype); +}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Timeline; +Tone_core_Timeline = function (Tone) { + 'use strict'; + Tone.Timeline = function () { + var options = this.optionsObject(arguments, ['memory'], Tone.Timeline.defaults); + this._timeline = []; + this._toRemove = []; + this._iterating = false; + this.memory = options.memory; + }; + Tone.extend(Tone.Timeline); + Tone.Timeline.defaults = { 'memory': Infinity }; + Object.defineProperty(Tone.Timeline.prototype, 'length', { + get: function () { + return this._timeline.length; + } + }); + Tone.Timeline.prototype.add = function (event) { + if (this.isUndef(event.time)) { + throw new Error('Tone.Timeline: events must have a time attribute'); + } + if (this._timeline.length) { + var index = this._search(event.time); + this._timeline.splice(index + 1, 0, event); + } else { + this._timeline.push(event); + } + if (this.length > this.memory) { + var diff = this.length - this.memory; + this._timeline.splice(0, diff); + } + return this; + }; + Tone.Timeline.prototype.remove = function (event) { + if (this._iterating) { + this._toRemove.push(event); + } else { + var index = this._timeline.indexOf(event); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } + return this; + }; + Tone.Timeline.prototype.get = function (time) { + var index = this._search(time); + if (index !== -1) { + return this._timeline[index]; + } else { + return null; + } + }; + Tone.Timeline.prototype.peek = function () { + return this._timeline[0]; + }; + Tone.Timeline.prototype.shift = function () { + return this._timeline.shift(); + }; + Tone.Timeline.prototype.getAfter = function (time) { + var index = this._search(time); + if (index + 1 < this._timeline.length) { + return this._timeline[index + 1]; + } else { + return null; + } + }; + Tone.Timeline.prototype.getBefore = function (time) { + var len = this._timeline.length; + if (len > 0 && this._timeline[len - 1].time < time) { + return this._timeline[len - 1]; + } + var index = this._search(time); + if (index - 1 >= 0) { + return this._timeline[index - 1]; + } else { + return null; + } + }; + Tone.Timeline.prototype.cancel = function (after) { + if (this._timeline.length > 1) { + var index = this._search(after); + if (index >= 0) { + if (this._timeline[index].time === after) { + for (var i = index; i >= 0; i--) { + if (this._timeline[i].time === after) { + index = i; + } else { + break; + } + } + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = this._timeline.slice(0, index + 1); + } + } else { + this._timeline = []; + } + } else if (this._timeline.length === 1) { + if (this._timeline[0].time >= after) { + this._timeline = []; + } + } + return this; + }; + Tone.Timeline.prototype.cancelBefore = function (time) { + if (this._timeline.length) { + var index = this._search(time); + if (index >= 0) { + this._timeline = this._timeline.slice(index + 1); + } + } + return this; + }; + Tone.Timeline.prototype._search = function (time) { + var beginning = 0; + var len = this._timeline.length; + var end = len; + if (len > 0 && this._timeline[len - 1].time <= time) { + return len - 1; + } + while (beginning < end) { + var midPoint = Math.floor(beginning + (end - beginning) / 2); + var event = this._timeline[midPoint]; + var nextEvent = this._timeline[midPoint + 1]; + if (event.time === time) { + for (var i = midPoint; i < this._timeline.length; i++) { + var testEvent = this._timeline[i]; + if (testEvent.time === time) { + midPoint = i; + } + } + return midPoint; + } else if (event.time < time && nextEvent.time > time) { + return midPoint; + } else if (event.time > time) { + end = midPoint; + } else if (event.time < time) { + beginning = midPoint + 1; + } + } + return -1; + }; + Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) { + this._iterating = true; + lowerBound = this.defaultArg(lowerBound, 0); + upperBound = this.defaultArg(upperBound, this._timeline.length - 1); + for (var i = lowerBound; i <= upperBound; i++) { + callback(this._timeline[i]); + } + this._iterating = false; + if (this._toRemove.length > 0) { + for (var j = 0; j < this._toRemove.length; j++) { + var index = this._timeline.indexOf(this._toRemove[j]); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } + this._toRemove = []; + } + }; + Tone.Timeline.prototype.forEach = function (callback) { + this._iterate(callback); + return this; + }; + Tone.Timeline.prototype.forEachBefore = function (time, callback) { + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(callback, 0, upperBound); + } + return this; + }; + Tone.Timeline.prototype.forEachAfter = function (time, callback) { + var lowerBound = this._search(time); + this._iterate(callback, lowerBound + 1); + return this; + }; + Tone.Timeline.prototype.forEachFrom = function (time, callback) { + var lowerBound = this._search(time); + while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) { + lowerBound--; + } + this._iterate(callback, lowerBound + 1); + return this; + }; + Tone.Timeline.prototype.forEachAtTime = function (time, callback) { + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(function (event) { + if (event.time === time) { + callback(event); + } + }, 0, upperBound); + } + return this; + }; + Tone.Timeline.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._timeline = null; + this._toRemove = null; + }; + return Tone.Timeline; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_TimelineSignal; +Tone_signal_TimelineSignal = function (Tone) { + 'use strict'; + Tone.TimelineSignal = function () { + var options = this.optionsObject(arguments, [ + 'value', + 'units' + ], Tone.Signal.defaults); + this._events = new Tone.Timeline(10); + Tone.Signal.apply(this, options); + options.param = this._param; + Tone.Param.call(this, options); + this._initial = this._fromUnits(this._param.value); + }; + Tone.extend(Tone.TimelineSignal, Tone.Param); + Tone.TimelineSignal.Type = { + Linear: 'linear', + Exponential: 'exponential', + Target: 'target', + Curve: 'curve', + Set: 'set' + }; + Object.defineProperty(Tone.TimelineSignal.prototype, 'value', { + get: function () { + var now = this.now(); + var val = this.getValueAtTime(now); + return this._toUnits(val); + }, + set: function (value) { + var convertedVal = this._fromUnits(value); + this._initial = convertedVal; + this.cancelScheduledValues(); + this._param.value = convertedVal; + } + }); + Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) { + value = this._fromUnits(value); + startTime = this.toSeconds(startTime); + this._events.add({ + 'type': Tone.TimelineSignal.Type.Set, + 'value': value, + 'time': startTime + }); + this._param.setValueAtTime(value, startTime); + return this; + }; + Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + endTime = this.toSeconds(endTime); + this._events.add({ + 'type': Tone.TimelineSignal.Type.Linear, + 'value': value, + 'time': endTime + }); + this._param.linearRampToValueAtTime(value, endTime); + return this; + }; + Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + endTime = this.toSeconds(endTime); + var beforeEvent = this._searchBefore(endTime); + if (beforeEvent && beforeEvent.value === 0) { + this.setValueAtTime(this._minOutput, beforeEvent.time); + } + value = this._fromUnits(value); + var setValue = Math.max(value, this._minOutput); + this._events.add({ + 'type': Tone.TimelineSignal.Type.Exponential, + 'value': setValue, + 'time': endTime + }); + if (value < this._minOutput) { + this._param.exponentialRampToValueAtTime(this._minOutput, endTime - this.sampleTime); + this.setValueAtTime(0, endTime); + } else { + this._param.exponentialRampToValueAtTime(value, endTime); + } + return this; + }; + Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + startTime = this.toSeconds(startTime); + this._events.add({ + 'type': Tone.TimelineSignal.Type.Target, + 'value': value, + 'time': startTime, + 'constant': timeConstant + }); + this._param.setTargetAtTime(value, startTime, timeConstant); + return this; + }; + Tone.TimelineSignal.prototype.setValueCurveAtTime = function (values, startTime, duration, scaling) { + scaling = this.defaultArg(scaling, 1); + var floats = new Array(values.length); + for (var i = 0; i < floats.length; i++) { + floats[i] = this._fromUnits(values[i]) * scaling; + } + startTime = this.toSeconds(startTime); + duration = this.toSeconds(duration); + this._events.add({ + 'type': Tone.TimelineSignal.Type.Curve, + 'value': floats, + 'time': startTime, + 'duration': duration + }); + this._param.setValueAtTime(floats[0], startTime); + for (var j = 1; j < floats.length; j++) { + var segmentTime = startTime + j / (floats.length - 1) * duration; + this._param.linearRampToValueAtTime(floats[j], segmentTime); + } + return this; + }; + Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) { + after = this.toSeconds(after); + this._events.cancel(after); + this._param.cancelScheduledValues(after); + return this; + }; + Tone.TimelineSignal.prototype.setRampPoint = function (time) { + time = this.toSeconds(time); + var val = this._toUnits(this.getValueAtTime(time)); + var before = this._searchBefore(time); + if (before && before.time === time) { + this.cancelScheduledValues(time + this.sampleTime); + } else if (before && before.type === Tone.TimelineSignal.Type.Curve && before.time + before.duration > time) { + this.cancelScheduledValues(time); + this.linearRampToValueAtTime(val, time); + } else { + var after = this._searchAfter(time); + if (after) { + this.cancelScheduledValues(time); + if (after.type === Tone.TimelineSignal.Type.Linear) { + this.linearRampToValueAtTime(val, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + this.exponentialRampToValueAtTime(val, time); + } + } + this.setValueAtTime(val, time); + } + return this; + }; + Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.linearRampToValueAtTime(value, finish); + return this; + }; + Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.exponentialRampToValueAtTime(value, finish); + return this; + }; + Tone.TimelineSignal.prototype._searchBefore = function (time) { + return this._events.get(time); + }; + Tone.TimelineSignal.prototype._searchAfter = function (time) { + return this._events.getAfter(time); + }; + Tone.TimelineSignal.prototype.getValueAtTime = function (time) { + time = this.toSeconds(time); + var after = this._searchAfter(time); + var before = this._searchBefore(time); + var value = this._initial; + if (before === null) { + value = this._initial; + } else if (before.type === Tone.TimelineSignal.Type.Target) { + var previous = this._events.getBefore(before.time); + var previouVal; + if (previous === null) { + previouVal = this._initial; + } else { + previouVal = previous.value; + } + value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (before.type === Tone.TimelineSignal.Type.Curve) { + value = this._curveInterpolate(before.time, before.value, before.duration, time); + } else if (after === null) { + value = before.value; + } else if (after.type === Tone.TimelineSignal.Type.Linear) { + value = this._linearInterpolate(before.time, before.value, after.time, after.value, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time); + } else { + value = before.value; + } + return value; + }; + Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect; + Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { + v0 = Math.max(this._minOutput, v0); + return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); + }; + Tone.TimelineSignal.prototype._curveInterpolate = function (start, curve, duration, time) { + var len = curve.length; + if (time >= start + duration) { + return curve[len - 1]; + } else if (time <= start) { + return curve[0]; + } else { + var progress = (time - start) / duration; + var lowerIndex = Math.floor((len - 1) * progress); + var upperIndex = Math.ceil((len - 1) * progress); + var lowerVal = curve[lowerIndex]; + var upperVal = curve[upperIndex]; + if (upperIndex === lowerIndex) { + return lowerVal; + } else { + return this._linearInterpolate(lowerIndex, lowerVal, upperIndex, upperVal, progress * (len - 1)); + } + } + }; + Tone.TimelineSignal.prototype.dispose = function () { + Tone.Signal.prototype.dispose.call(this); + Tone.Param.prototype.dispose.call(this); + this._events.dispose(); + this._events = null; + }; + return Tone.TimelineSignal; +}(Tone_core_Tone, Tone_signal_Signal); +var envelope; +'use strict'; +envelope = function () { + var p5sound = master; + var Add = Tone_signal_Add; + var Mult = Tone_signal_Multiply; + var Scale = Tone_signal_Scale; + var TimelineSignal = Tone_signal_TimelineSignal; + /** + * <p>Envelopes are pre-defined amplitude distribution over time. + * Typically, envelopes are used to control the output volume + * of an object, a series of fades referred to as Attack, Decay, + * Sustain and Release ( + * <a href="https://upload.wikimedia.org/wikipedia/commons/e/ea/ADSR_parameter.svg">ADSR</a> + * ). Envelopes can also control other Web Audio Parameters—for example, a p5.Envelope can + * control an Oscillator's frequency like this: <code>osc.freq(env)</code>.</p> + * <p>Use <code><a href="#/p5.Envelope/setRange">setRange</a></code> to change the attack/release level. + * Use <code><a href="#/p5.Envelope/setADSR">setADSR</a></code> to change attackTime, decayTime, sustainPercent and releaseTime.</p> + * <p>Use the <code><a href="#/p5.Envelope/play">play</a></code> method to play the entire envelope, + * the <code><a href="#/p5.Envelope/ramp">ramp</a></code> method for a pingable trigger, + * or <code><a href="#/p5.Envelope/triggerAttack">triggerAttack</a></code>/ + * <code><a href="#/p5.Envelope/triggerRelease">triggerRelease</a></code> to trigger noteOn/noteOff.</p> + * + * @class p5.Envelope + * @constructor + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv() { + * env.play(); + * } + * </code></div> + */ + p5.Envelope = function (t1, l1, t2, l2, t3, l3) { + /** + * Time until envelope reaches attackLevel + * @property attackTime + */ + this.aTime = t1 || 0.1; + /** + * Level once attack is complete. + * @property attackLevel + */ + this.aLevel = l1 || 1; + /** + * Time until envelope reaches decayLevel. + * @property decayTime + */ + this.dTime = t2 || 0.5; + /** + * Level after decay. The envelope will sustain here until it is released. + * @property decayLevel + */ + this.dLevel = l2 || 0; + /** + * Duration of the release portion of the envelope. + * @property releaseTime + */ + this.rTime = t3 || 0; + /** + * Level at the end of the release. + * @property releaseLevel + */ + this.rLevel = l3 || 0; + this._rampHighPercentage = 0.98; + this._rampLowPercentage = 0.02; + this.output = p5sound.audiocontext.createGain(); + this.control = new TimelineSignal(); + this._init(); + // this makes sure the envelope starts at zero + this.control.connect(this.output); + // connect to the output + this.connection = null; + // store connection + //array of math operation signal chaining + this.mathOps = [this.control]; + //whether envelope should be linear or exponential curve + this.isExponential = false; + // oscillator or buffer source to clear on env complete + // to save resources if/when it is retriggered + this.sourceToClear = null; + // set to true if attack is set, then false on release + this.wasTriggered = false; + // add to the soundArray so we can dispose of the env later + p5sound.soundArray.push(this); + }; + // this init function just smooths the starting value to zero and gives a start point for the timeline + // - it was necessary to remove glitches at the beginning. + p5.Envelope.prototype._init = function () { + var now = p5sound.audiocontext.currentTime; + var t = now; + this.control.setTargetAtTime(0.00001, t, 0.001); + //also, compute the correct time constants + this._setRampAD(this.aTime, this.dTime); + }; + /** + * Reset the envelope with a series of time/value pairs. + * + * @method set + * @param {Number} attackTime Time (in seconds) before level + * reaches attackLevel + * @param {Number} attackLevel Typically an amplitude between + * 0.0 and 1.0 + * @param {Number} decayTime Time + * @param {Number} decayLevel Amplitude (In a standard ADSR envelope, + * decayLevel = sustainLevel) + * @param {Number} releaseTime Release Time (in seconds) + * @param {Number} releaseLevel Amplitude + * @example + * <div><code> + * var t1 = 0.1; // attack time in seconds + * var l1 = 0.7; // attack level 0.0 to 1.0 + * var t2 = 0.3; // decay time in seconds + * var l2 = 0.1; // decay level 0.0 to 1.0 + * var t3 = 0.2; // sustain time in seconds + * var l3 = 0.5; // sustain level 0.0 to 1.0 + * // release level defaults to zero + * + * var env; + * var triOsc; + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(t1, l1, t2, l2, t3, l3); + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); // give the env control of the triOsc's amp + * triOsc.start(); + * } + * + * // mouseClick triggers envelope if over canvas + * function mouseClicked() { + * // is mouse over canvas? + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * env.play(triOsc); + * } + * } + * </code></div> + * + */ + p5.Envelope.prototype.set = function (t1, l1, t2, l2, t3, l3) { + this.aTime = t1; + this.aLevel = l1; + this.dTime = t2 || 0; + this.dLevel = l2 || 0; + this.rTime = t3 || 0; + this.rLevel = l3 || 0; + // set time constants for ramp + this._setRampAD(t1, t2); + }; + /** + * Set values like a traditional + * <a href="https://en.wikipedia.org/wiki/Synthesizer#/media/File:ADSR_parameter.svg"> + * ADSR envelope + * </a>. + * + * @method setADSR + * @param {Number} attackTime Time (in seconds before envelope + * reaches Attack Level + * @param {Number} [decayTime] Time (in seconds) before envelope + * reaches Decay/Sustain Level + * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, + * where 1.0 = attackLevel, 0.0 = releaseLevel. + * The susRatio determines the decayLevel and the level at which the + * sustain portion of the envelope will sustain. + * For example, if attackLevel is 0.4, releaseLevel is 0, + * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is + * increased to 1.0 (using <code>setRange</code>), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv() { + * env.play(); + * } + * </code></div> + */ + p5.Envelope.prototype.setADSR = function (aTime, dTime, sPercent, rTime) { + this.aTime = aTime; + this.dTime = dTime || 0; + // lerp + this.sPercent = sPercent || 0; + this.dLevel = typeof sPercent !== 'undefined' ? sPercent * (this.aLevel - this.rLevel) + this.rLevel : 0; + this.rTime = rTime || 0; + // also set time constants for ramp + this._setRampAD(aTime, dTime); + }; + /** + * Set max (attackLevel) and min (releaseLevel) of envelope. + * + * @method setRange + * @param {Number} aLevel attack level (defaults to 1) + * @param {Number} rLevel release level (defaults to 0) + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv() { + * env.play(); + * } + * </code></div> + */ + p5.Envelope.prototype.setRange = function (aLevel, rLevel) { + this.aLevel = aLevel || 1; + this.rLevel = rLevel || 0; + }; + // private (undocumented) method called when ADSR is set to set time constants for ramp + // + // Set the <a href="https://en.wikipedia.org/wiki/RC_time_constant"> + // time constants</a> for simple exponential ramps. + // The larger the time constant value, the slower the + // transition will be. + // + // method _setRampAD + // param {Number} attackTimeConstant attack time constant + // param {Number} decayTimeConstant decay time constant + // + p5.Envelope.prototype._setRampAD = function (t1, t2) { + this._rampAttackTime = this.checkExpInput(t1); + this._rampDecayTime = this.checkExpInput(t2); + var TCDenominator = 1; + /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) + TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); + this._rampAttackTC = t1 / this.checkExpInput(TCDenominator); + TCDenominator = Math.log(1 / this._rampLowPercentage); + this._rampDecayTC = t2 / this.checkExpInput(TCDenominator); + }; + // private method + p5.Envelope.prototype.setRampPercentages = function (p1, p2) { + //set the percentages that the simple exponential ramps go to + this._rampHighPercentage = this.checkExpInput(p1); + this._rampLowPercentage = this.checkExpInput(p2); + var TCDenominator = 1; + //now re-compute the time constants based on those percentages + /// Aatish Bhatia's calculation for time constant for rise(to adjust 1/1-e calculation to any percentage) + TCDenominator = Math.log(1 / this.checkExpInput(1 - this._rampHighPercentage)); + this._rampAttackTC = this._rampAttackTime / this.checkExpInput(TCDenominator); + TCDenominator = Math.log(1 / this._rampLowPercentage); + this._rampDecayTC = this._rampDecayTime / this.checkExpInput(TCDenominator); + }; + /** + * Assign a parameter to be controlled by this envelope. + * If a p5.Sound object is given, then the p5.Envelope will control its + * output gain. If multiple inputs are provided, the env will + * control all of them. + * + * @method setInput + * @param {Object} [...inputs] A p5.sound object or + * Web Audio Param. + */ + p5.Envelope.prototype.setInput = function () { + for (var i = 0; i < arguments.length; i++) { + this.connect(arguments[i]); + } + }; + /** + * Set whether the envelope ramp is linear (default) or exponential. + * Exponential ramps can be useful because we perceive amplitude + * and frequency logarithmically. + * + * @method setExp + * @param {Boolean} isExp true is exponential, false is linear + */ + p5.Envelope.prototype.setExp = function (isExp) { + this.isExponential = isExp; + }; + //helper method to protect against zero values being sent to exponential functions + p5.Envelope.prototype.checkExpInput = function (value) { + if (value <= 0) { + value = 1e-8; + } + return value; + }; + /** + * Play tells the envelope to start acting on a given input. + * If the input is a p5.sound object (i.e. AudioIn, Oscillator, + * SoundFile), then Envelope will control its output volume. + * Envelopes can also be used to control any <a href=" + * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> + * Web Audio Audio Param.</a> + * + * @method play + * @param {Object} unit A p5.sound object or + * Web Audio Param. + * @param {Number} [startTime] time from now (in seconds) at which to play + * @param {Number} [sustainTime] time to sustain before releasing the envelope + * @example + * <div><code> + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var susPercent = 0.2; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(playEnv); + * } + * + * function playEnv() { + * // trigger env on triOsc, 0 seconds from now + * // After decay, sustain for 0.2 seconds before release + * env.play(triOsc, 0, 0.2); + * } + * </code></div> + */ + p5.Envelope.prototype.play = function (unit, secondsFromNow, susTime) { + var tFromNow = secondsFromNow || 0; + var susTime = susTime || 0; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + this.triggerAttack(unit, tFromNow); + this.triggerRelease(unit, tFromNow + this.aTime + this.dTime + susTime); + }; + /** + * Trigger the Attack, and Decay portion of the Envelope. + * Similar to holding down a key on a piano, but it will + * hold the sustain level until you let go. Input can be + * any p5.sound object, or a <a href=" + * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> + * Web Audio Param</a>. + * + * @method triggerAttack + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow time from now (in seconds) + * @example + * <div><code> + * + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.3; + * var susPercent = 0.4; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * background(200); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(envAttack); + * } + * + * function envAttack() { + * console.log('trigger attack'); + * env.triggerAttack(); + * + * background(0,255,0); + * text('attack!', width/2, height/2); + * } + * + * function mouseReleased() { + * env.triggerRelease(); + * + * background(200); + * text('click to play', width/2, height/2); + * } + * </code></div> + */ + p5.Envelope.prototype.triggerAttack = function (unit, secondsFromNow) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + this.lastAttack = t; + this.wasTriggered = true; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + // get and set value (with linear ramp) to anchor automation + var valToSet = this.control.getValueAtTime(t); + if (this.isExponential === true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); + } else { + this.control.linearRampToValueAtTime(valToSet, t); + } + // after each ramp completes, cancel scheduled values + // (so they can be overridden in case env has been re-triggered) + // then, set current value (with linearRamp to avoid click) + // then, schedule the next automation... + // attack + t += this.aTime; + if (this.isExponential === true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.aLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.aLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + // decay to decay level (if using ADSR, then decay level == sustain level) + t += this.dTime; + if (this.isExponential === true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.dLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.dLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + }; + /** + * Trigger the Release of the Envelope. This is similar to releasing + * the key on a piano and letting the sound fade according to the + * release level and release time. + * + * @method triggerRelease + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow time to trigger the release + * @example + * <div><code> + * + * var attackLevel = 1.0; + * var releaseLevel = 0; + * + * var attackTime = 0.001; + * var decayTime = 0.3; + * var susPercent = 0.4; + * var releaseTime = 0.5; + * + * var env, triOsc; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * background(200); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime, susPercent, releaseTime); + * env.setRange(attackLevel, releaseLevel); + * + * triOsc = new p5.Oscillator('triangle'); + * triOsc.amp(env); + * triOsc.start(); + * triOsc.freq(220); + * + * cnv.mousePressed(envAttack); + * } + * + * function envAttack() { + * console.log('trigger attack'); + * env.triggerAttack(); + * + * background(0,255,0); + * text('attack!', width/2, height/2); + * } + * + * function mouseReleased() { + * env.triggerRelease(); + * + * background(200); + * text('click to play', width/2, height/2); + * } + * </code></div> + */ + p5.Envelope.prototype.triggerRelease = function (unit, secondsFromNow) { + // only trigger a release if an attack was triggered + if (!this.wasTriggered) { + // this currently causes a bit of trouble: + // if a later release has been scheduled (via the play function) + // a new earlier release won't interrupt it, because + // this.wasTriggered has already been set to false. + // If we want new earlier releases to override, then we need to + // keep track of the last release time, and if the new release time is + // earlier, then use it. + return; + } + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + // get and set value (with linear or exponential ramp) to anchor automation + var valToSet = this.control.getValueAtTime(t); + if (this.isExponential === true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(valToSet), t); + } else { + this.control.linearRampToValueAtTime(valToSet, t); + } + // release + t += this.rTime; + if (this.isExponential === true) { + this.control.exponentialRampToValueAtTime(this.checkExpInput(this.rLevel), t); + valToSet = this.checkExpInput(this.control.getValueAtTime(t)); + this.control.cancelScheduledValues(t); + this.control.exponentialRampToValueAtTime(valToSet, t); + } else { + this.control.linearRampToValueAtTime(this.rLevel, t); + valToSet = this.control.getValueAtTime(t); + this.control.cancelScheduledValues(t); + this.control.linearRampToValueAtTime(valToSet, t); + } + this.wasTriggered = false; + }; + /** + * Exponentially ramp to a value using the first two + * values from <code><a href="#/p5.Envelope/setADSR">setADSR(attackTime, decayTime)</a></code> + * as <a href="https://en.wikipedia.org/wiki/RC_time_constant"> + * time constants</a> for simple exponential ramps. + * If the value is higher than current value, it uses attackTime, + * while a decrease uses decayTime. + * + * @method ramp + * @param {Object} unit p5.sound Object or Web Audio Param + * @param {Number} secondsFromNow When to trigger the ramp + * @param {Number} v Target value + * @param {Number} [v2] Second target value (optional) + * @example + * <div><code> + * var env, osc, amp, cnv; + * + * var attackTime = 0.001; + * var decayTime = 0.2; + * var attackLevel = 1; + * var decayLevel = 0; + * + * function setup() { + * cnv = createCanvas(100, 100); + * fill(0,255,0); + * noStroke(); + * + * env = new p5.Envelope(); + * env.setADSR(attackTime, decayTime); + * + * osc = new p5.Oscillator(); + * osc.amp(env); + * osc.start(); + * + * amp = new p5.Amplitude(); + * + * cnv.mousePressed(triggerRamp); + * } + * + * function triggerRamp() { + * env.ramp(osc, 0, attackLevel, decayLevel); + * } + * + * function draw() { + * background(20,20,20); + * text('click me', 10, 20); + * var h = map(amp.getLevel(), 0, 0.4, 0, height);; + * + * rect(0, height, width, -h); + * } + * </code></div> + */ + p5.Envelope.prototype.ramp = function (unit, secondsFromNow, v1, v2) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + var destination1 = this.checkExpInput(v1); + var destination2 = typeof v2 !== 'undefined' ? this.checkExpInput(v2) : undefined; + // connect env to unit if not already connected + if (unit) { + if (this.connection !== unit) { + this.connect(unit); + } + } + //get current value + var currentVal = this.checkExpInput(this.control.getValueAtTime(t)); + // this.control.cancelScheduledValues(t); + //if it's going up + if (destination1 > currentVal) { + this.control.setTargetAtTime(destination1, t, this._rampAttackTC); + t += this._rampAttackTime; + } else if (destination1 < currentVal) { + this.control.setTargetAtTime(destination1, t, this._rampDecayTC); + t += this._rampDecayTime; + } + // Now the second part of envelope begins + if (destination2 === undefined) + return; + //if it's going up + if (destination2 > destination1) { + this.control.setTargetAtTime(destination2, t, this._rampAttackTC); + } else if (destination2 < destination1) { + this.control.setTargetAtTime(destination2, t, this._rampDecayTC); + } + }; + p5.Envelope.prototype.connect = function (unit) { + this.connection = unit; + // assume we're talking about output gain + // unless given a different audio param + if (unit instanceof p5.Oscillator || unit instanceof p5.SoundFile || unit instanceof p5.AudioIn || unit instanceof p5.Reverb || unit instanceof p5.Noise || unit instanceof p5.Filter || unit instanceof p5.Delay) { + unit = unit.output.gain; + } + if (unit instanceof AudioParam) { + //set the initial value + unit.setValueAtTime(0, p5sound.audiocontext.currentTime); + } + if (unit instanceof p5.Signal) { + unit.setValue(0); + } + this.output.connect(unit); + }; + p5.Envelope.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + // Signal Math + /** + * Add a value to the p5.Oscillator's output amplitude, + * and return the oscillator. Calling this method + * again will override the initial add() with new values. + * + * @method add + * @param {Number} number Constant number to add + * @return {p5.Envelope} Envelope Returns this envelope + * with scaled output + */ + p5.Envelope.prototype.add = function (num) { + var add = new Add(num); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, add, thisChain, nextChain, Add); + }; + /** + * Multiply the p5.Envelope's output amplitude + * by a fixed value. Calling this method + * again will override the initial mult() with new values. + * + * @method mult + * @param {Number} number Constant number to multiply + * @return {p5.Envelope} Envelope Returns this envelope + * with scaled output + */ + p5.Envelope.prototype.mult = function (num) { + var mult = new Mult(num); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, mult, thisChain, nextChain, Mult); + }; + /** + * Scale this envelope's amplitude values to a given + * range, and return the envelope. Calling this method + * again will override the initial scale() with new values. + * + * @method scale + * @param {Number} inMin input range minumum + * @param {Number} inMax input range maximum + * @param {Number} outMin input range minumum + * @param {Number} outMax input range maximum + * @return {p5.Envelope} Envelope Returns this envelope + * with scaled output + */ + p5.Envelope.prototype.scale = function (inMin, inMax, outMin, outMax) { + var scale = new Scale(inMin, inMax, outMin, outMax); + var thisChain = this.mathOps.length; + var nextChain = this.output; + return p5.prototype._mathChain(this, scale, thisChain, nextChain, Scale); + }; + // get rid of the oscillator + p5.Envelope.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.disconnect(); + if (this.control) { + this.control.dispose(); + this.control = null; + } + for (var i = 1; i < this.mathOps.length; i++) { + this.mathOps[i].dispose(); + } + }; + // Different name for backwards compatibility, replicates p5.Envelope class + p5.Env = function (t1, l1, t2, l2, t3, l3) { + console.warn('WARNING: p5.Env is now deprecated and may be removed in future versions. ' + 'Please use the new p5.Envelope instead.'); + p5.Envelope.call(this, t1, l1, t2, l2, t3, l3); + }; + p5.Env.prototype = Object.create(p5.Envelope.prototype); +}(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_signal_TimelineSignal); +var pulse; +'use strict'; +pulse = function () { + var p5sound = master; + /** + * Creates a Pulse object, an oscillator that implements + * Pulse Width Modulation. + * The pulse is created with two oscillators. + * Accepts a parameter for frequency, and to set the + * width between the pulses. See <a href=" + * http://p5js.org/reference/#/p5.Oscillator"> + * <code>p5.Oscillator</code> for a full list of methods. + * + * @class p5.Pulse + * @extends p5.Oscillator + * @constructor + * @param {Number} [freq] Frequency in oscillations per second (Hz) + * @param {Number} [w] Width between the pulses (0 to 1.0, + * defaults to 0) + * @example + * <div><code> + * var pulse; + * function setup() { + * background(0); + * + * // Create and start the pulse wave oscillator + * pulse = new p5.Pulse(); + * pulse.amp(0.5); + * pulse.freq(220); + * pulse.start(); + * } + * + * function draw() { + * var w = map(mouseX, 0, width, 0, 1); + * w = constrain(w, 0, 1); + * pulse.width(w) + * } + * </code></div> + */ + p5.Pulse = function (freq, w) { + p5.Oscillator.call(this, freq, 'sawtooth'); + // width of PWM, should be betw 0 to 1.0 + this.w = w || 0; + // create a second oscillator with inverse frequency + this.osc2 = new p5.SawOsc(freq); + // create a delay node + this.dNode = p5sound.audiocontext.createDelay(); + // dc offset + this.dcOffset = createDCOffset(); + this.dcGain = p5sound.audiocontext.createGain(); + this.dcOffset.connect(this.dcGain); + this.dcGain.connect(this.output); + // set delay time based on PWM width + this.f = freq || 440; + var mW = this.w / this.oscillator.frequency.value; + this.dNode.delayTime.value = mW; + this.dcGain.gain.value = 1.7 * (0.5 - this.w); + // disconnect osc2 and connect it to delay, which is connected to output + this.osc2.disconnect(); + this.osc2.panner.disconnect(); + this.osc2.amp(-1); + // inverted amplitude + this.osc2.output.connect(this.dNode); + this.dNode.connect(this.output); + this.output.gain.value = 1; + this.output.connect(this.panner); + }; + p5.Pulse.prototype = Object.create(p5.Oscillator.prototype); + /** + * Set the width of a Pulse object (an oscillator that implements + * Pulse Width Modulation). + * + * @method width + * @param {Number} [width] Width between the pulses (0 to 1.0, + * defaults to 0) + */ + p5.Pulse.prototype.width = function (w) { + if (typeof w === 'number') { + if (w <= 1 && w >= 0) { + this.w = w; + // set delay time based on PWM width + // var mW = map(this.w, 0, 1.0, 0, 1/this.f); + var mW = this.w / this.oscillator.frequency.value; + this.dNode.delayTime.value = mW; + } + this.dcGain.gain.value = 1.7 * (0.5 - this.w); + } else { + w.connect(this.dNode.delayTime); + var sig = new p5.SignalAdd(-0.5); + sig.setInput(w); + sig = sig.mult(-1); + sig = sig.mult(1.7); + sig.connect(this.dcGain.gain); + } + }; + p5.Pulse.prototype.start = function (f, time) { + var now = p5sound.audiocontext.currentTime; + var t = time || 0; + if (!this.started) { + var freq = f || this.f; + var type = this.oscillator.type; + this.oscillator = p5sound.audiocontext.createOscillator(); + this.oscillator.frequency.setValueAtTime(freq, now); + this.oscillator.type = type; + this.oscillator.connect(this.output); + this.oscillator.start(t + now); + // set up osc2 + this.osc2.oscillator = p5sound.audiocontext.createOscillator(); + this.osc2.oscillator.frequency.setValueAtTime(freq, t + now); + this.osc2.oscillator.type = type; + this.osc2.oscillator.connect(this.osc2.output); + this.osc2.start(t + now); + this.freqNode = [ + this.oscillator.frequency, + this.osc2.oscillator.frequency + ]; + // start dcOffset, too + this.dcOffset = createDCOffset(); + this.dcOffset.connect(this.dcGain); + this.dcOffset.start(t + now); + // if LFO connections depend on these oscillators + if (this.mods !== undefined && this.mods.frequency !== undefined) { + this.mods.frequency.connect(this.freqNode[0]); + this.mods.frequency.connect(this.freqNode[1]); + } + this.started = true; + this.osc2.started = true; + } + }; + p5.Pulse.prototype.stop = function (time) { + if (this.started) { + var t = time || 0; + var now = p5sound.audiocontext.currentTime; + this.oscillator.stop(t + now); + if (this.osc2.oscillator) { + this.osc2.oscillator.stop(t + now); + } + this.dcOffset.stop(t + now); + this.started = false; + this.osc2.started = false; + } + }; + p5.Pulse.prototype.freq = function (val, rampTime, tFromNow) { + if (typeof val === 'number') { + this.f = val; + var now = p5sound.audiocontext.currentTime; + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var currentFreq = this.oscillator.frequency.value; + this.oscillator.frequency.cancelScheduledValues(now); + this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); + this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + this.osc2.oscillator.frequency.cancelScheduledValues(now); + this.osc2.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); + this.osc2.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); + if (this.freqMod) { + this.freqMod.output.disconnect(); + this.freqMod = null; + } + } else if (val.output) { + val.output.disconnect(); + val.output.connect(this.oscillator.frequency); + val.output.connect(this.osc2.oscillator.frequency); + this.freqMod = val; + } + }; + // inspiration: http://webaudiodemos.appspot.com/oscilloscope/ + function createDCOffset() { + var ac = p5sound.audiocontext; + var buffer = ac.createBuffer(1, 2048, ac.sampleRate); + var data = buffer.getChannelData(0); + for (var i = 0; i < 2048; i++) + data[i] = 1; + var bufferSource = ac.createBufferSource(); + bufferSource.buffer = buffer; + bufferSource.loop = true; + return bufferSource; + } +}(master, oscillator); +var noise; +'use strict'; +noise = function () { + var p5sound = master; + /** + * Noise is a type of oscillator that generates a buffer with random values. + * + * @class p5.Noise + * @extends p5.Oscillator + * @constructor + * @param {String} type Type of noise can be 'white' (default), + * 'brown' or 'pink'. + */ + p5.Noise = function (type) { + var assignType; + p5.Oscillator.call(this); + delete this.f; + delete this.freq; + delete this.oscillator; + if (type === 'brown') { + assignType = _brownNoise; + } else if (type === 'pink') { + assignType = _pinkNoise; + } else { + assignType = _whiteNoise; + } + this.buffer = assignType; + }; + p5.Noise.prototype = Object.create(p5.Oscillator.prototype); + // generate noise buffers + var _whiteNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var whiteBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = whiteBuffer.getChannelData(0); + for (var i = 0; i < bufferSize; i++) { + noiseData[i] = Math.random() * 2 - 1; + } + whiteBuffer.type = 'white'; + return whiteBuffer; + }(); + var _pinkNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var pinkBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = pinkBuffer.getChannelData(0); + var b0, b1, b2, b3, b4, b5, b6; + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; + for (var i = 0; i < bufferSize; i++) { + var white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + noiseData[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + noiseData[i] *= 0.11; + // (roughly) compensate for gain + b6 = white * 0.115926; + } + pinkBuffer.type = 'pink'; + return pinkBuffer; + }(); + var _brownNoise = function () { + var bufferSize = 2 * p5sound.audiocontext.sampleRate; + var brownBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); + var noiseData = brownBuffer.getChannelData(0); + var lastOut = 0; + for (var i = 0; i < bufferSize; i++) { + var white = Math.random() * 2 - 1; + noiseData[i] = (lastOut + 0.02 * white) / 1.02; + lastOut = noiseData[i]; + noiseData[i] *= 3.5; + } + brownBuffer.type = 'brown'; + return brownBuffer; + }(); + /** + * Set type of noise to 'white', 'pink' or 'brown'. + * White is the default. + * + * @method setType + * @param {String} [type] 'white', 'pink' or 'brown' + */ + p5.Noise.prototype.setType = function (type) { + switch (type) { + case 'white': + this.buffer = _whiteNoise; + break; + case 'pink': + this.buffer = _pinkNoise; + break; + case 'brown': + this.buffer = _brownNoise; + break; + default: + this.buffer = _whiteNoise; + } + if (this.started) { + var now = p5sound.audiocontext.currentTime; + this.stop(now); + this.start(now + 0.01); + } + }; + p5.Noise.prototype.getType = function () { + return this.buffer.type; + }; + p5.Noise.prototype.start = function () { + if (this.started) { + this.stop(); + } + this.noise = p5sound.audiocontext.createBufferSource(); + this.noise.buffer = this.buffer; + this.noise.loop = true; + this.noise.connect(this.output); + var now = p5sound.audiocontext.currentTime; + this.noise.start(now); + this.started = true; + }; + p5.Noise.prototype.stop = function () { + var now = p5sound.audiocontext.currentTime; + if (this.noise) { + this.noise.stop(now); + this.started = false; + } + }; + p5.Noise.prototype.dispose = function () { + var now = p5sound.audiocontext.currentTime; + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.noise) { + this.noise.disconnect(); + this.stop(now); + } + if (this.output) { + this.output.disconnect(); + } + if (this.panner) { + this.panner.disconnect(); + } + this.output = null; + this.panner = null; + this.buffer = null; + this.noise = null; + }; +}(master); +var audioin; +'use strict'; +audioin = function () { + var p5sound = master; + // an array of input sources + p5sound.inputSources = []; + /** + * <p>Get audio from an input, i.e. your computer's microphone.</p> + * + * <p>Turn the mic on/off with the start() and stop() methods. When the mic + * is on, its volume can be measured with getLevel or by connecting an + * FFT object.</p> + * + * <p>If you want to hear the AudioIn, use the .connect() method. + * AudioIn does not connect to p5.sound output by default to prevent + * feedback.</p> + * + * <p><em>Note: This uses the <a href="http://caniuse.com/stream">getUserMedia/ + * Stream</a> API, which is not supported by certain browsers. Access in Chrome browser + * is limited to localhost and https, but access over http may be limited.</em></p> + * + * @class p5.AudioIn + * @constructor + * @param {Function} [errorCallback] A function to call if there is an error + * accessing the AudioIn. For example, + * Safari and iOS devices do not + * currently allow microphone access. + * @example + * <div><code> + * var mic; + * function setup(){ + * mic = new p5.AudioIn() + * mic.start(); + * } + * function draw(){ + * background(0); + * micLevel = mic.getLevel(); + * ellipse(width/2, constrain(height-micLevel*height*5, 0, height), 10, 10); + * } + * </code></div> + */ + p5.AudioIn = function (errorCallback) { + // set up audio input + /** + * @property {GainNode} input + */ + this.input = p5sound.audiocontext.createGain(); + /** + * @property {GainNode} output + */ + this.output = p5sound.audiocontext.createGain(); + /** + * @property {MediaStream|null} stream + */ + this.stream = null; + /** + * @property {MediaStreamAudioSourceNode|null} mediaStream + */ + this.mediaStream = null; + /** + * @property {Number|null} currentSource + */ + this.currentSource = null; + /** + * Client must allow browser to access their microphone / audioin source. + * Default: false. Will become true when the client enables acces. + * + * @property {Boolean} enabled + */ + this.enabled = false; + /** + * Input amplitude, connect to it by default but not to master out + * + * @property {p5.Amplitude} amplitude + */ + this.amplitude = new p5.Amplitude(); + this.output.connect(this.amplitude.input); + if (!window.MediaStreamTrack || !window.navigator.mediaDevices || !window.navigator.mediaDevices.getUserMedia) { + errorCallback ? errorCallback() : window.alert('This browser does not support MediaStreamTrack and mediaDevices'); + } + // add to soundArray so we can dispose on close + p5sound.soundArray.push(this); + }; + /** + * Start processing audio input. This enables the use of other + * AudioIn methods like getLevel(). Note that by default, AudioIn + * is not connected to p5.sound's output. So you won't hear + * anything unless you use the connect() method.<br/> + * + * Certain browsers limit access to the user's microphone. For example, + * Chrome only allows access from localhost and over https. For this reason, + * you may want to include an errorCallback—a function that is called in case + * the browser won't provide mic access. + * + * @method start + * @param {Function} [successCallback] Name of a function to call on + * success. + * @param {Function} [errorCallback] Name of a function to call if + * there was an error. For example, + * some browsers do not support + * getUserMedia. + */ + p5.AudioIn.prototype.start = function (successCallback, errorCallback) { + var self = this; + if (this.stream) { + this.stop(); + } + // set the audio source + var audioSource = p5sound.inputSources[self.currentSource]; + var constraints = { + audio: { + sampleRate: p5sound.audiocontext.sampleRate, + echoCancellation: false + } + }; + // if developers determine which source to use + if (p5sound.inputSources[this.currentSource]) { + constraints.audio.deviceId = audioSource.deviceId; + } + window.navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + self.stream = stream; + self.enabled = true; + // Wrap a MediaStreamSourceNode around the live input + self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); + self.mediaStream.connect(self.output); + // only send to the Amplitude reader, so we can see it but not hear it. + self.amplitude.setInput(self.output); + if (successCallback) + successCallback(); + }).catch(function (err) { + if (errorCallback) + errorCallback(err); + else + console.error(err); + }); + }; + /** + * Turn the AudioIn off. If the AudioIn is stopped, it cannot getLevel(). + * If re-starting, the user may be prompted for permission access. + * + * @method stop + */ + p5.AudioIn.prototype.stop = function () { + if (this.stream) { + this.stream.getTracks().forEach(function (track) { + track.stop(); + }); + this.mediaStream.disconnect(); + delete this.mediaStream; + delete this.stream; + } + }; + /** + * Connect to an audio unit. If no parameter is provided, will + * connect to the master output (i.e. your speakers).<br/> + * + * @method connect + * @param {Object} [unit] An object that accepts audio input, + * such as an FFT + */ + p5.AudioIn.prototype.connect = function (unit) { + if (unit) { + if (unit.hasOwnProperty('input')) { + this.output.connect(unit.input); + } else if (unit.hasOwnProperty('analyser')) { + this.output.connect(unit.analyser); + } else { + this.output.connect(unit); + } + } else { + this.output.connect(p5sound.input); + } + }; + /** + * Disconnect the AudioIn from all audio units. For example, if + * connect() had been called, disconnect() will stop sending + * signal to your speakers.<br/> + * + * @method disconnect + */ + p5.AudioIn.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + // stay connected to amplitude even if not outputting to p5 + this.output.connect(this.amplitude.input); + } + }; + /** + * Read the Amplitude (volume level) of an AudioIn. The AudioIn + * class contains its own instance of the Amplitude class to help + * make it easy to get a microphone's volume level. Accepts an + * optional smoothing value (0.0 < 1.0). <em>NOTE: AudioIn must + * .start() before using .getLevel().</em><br/> + * + * @method getLevel + * @param {Number} [smoothing] Smoothing is 0.0 by default. + * Smooths values based on previous values. + * @return {Number} Volume level (between 0.0 and 1.0) + */ + p5.AudioIn.prototype.getLevel = function (smoothing) { + if (smoothing) { + this.amplitude.smoothing = smoothing; + } + return this.amplitude.getLevel(); + }; + /** + * Set amplitude (volume) of a mic input between 0 and 1.0. <br/> + * + * @method amp + * @param {Number} vol between 0 and 1.0 + * @param {Number} [time] ramp time (optional) + */ + p5.AudioIn.prototype.amp = function (vol, t) { + if (t) { + var rampTime = t || 0; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); + this.output.gain.setValueAtTime(currentVol, p5sound.audiocontext.currentTime); + this.output.gain.linearRampToValueAtTime(vol, rampTime + p5sound.audiocontext.currentTime); + } else { + this.output.gain.cancelScheduledValues(p5sound.audiocontext.currentTime); + this.output.gain.setValueAtTime(vol, p5sound.audiocontext.currentTime); + } + }; + /** + * Returns a list of available input sources. This is a wrapper + * for <a title="MediaDevices.enumerateDevices() - Web APIs | MDN" target="_blank" href= + * "https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices" + * > and it returns a Promise. + * + * @method getSources + * @param {Function} [successCallback] This callback function handles the sources when they + * have been enumerated. The callback function + * receives the deviceList array as its only argument + * @param {Function} [errorCallback] This optional callback receives the error + * message as its argument. + * @returns {Promise} Returns a Promise that can be used in place of the callbacks, similar + * to the enumerateDevices() method + * @example + * <div><code> + * var audiograb; + * + * function setup(){ + * //new audioIn + * audioGrab = new p5.AudioIn(); + * + * audioGrab.getSources(function(deviceList) { + * //print out the array of available sources + * console.log(deviceList); + * //set the source to the first item in the deviceList array + * audioGrab.setSource(0); + * }); + * } + * </code></div> + */ + p5.AudioIn.prototype.getSources = function (onSuccess, onError) { + return new Promise(function (resolve, reject) { + window.navigator.mediaDevices.enumerateDevices().then(function (devices) { + p5sound.inputSources = devices.filter(function (device) { + return device.kind === 'audioinput'; + }); + resolve(p5sound.inputSources); + if (onSuccess) { + onSuccess(p5sound.inputSources); + } + }).catch(function (error) { + reject(error); + if (onError) { + onError(error); + } else { + console.error('This browser does not support MediaStreamTrack.getSources()'); + } + }); + }); + }; + /** + * Set the input source. Accepts a number representing a + * position in the array returned by getSources(). + * This is only available in browsers that support + * <a title="MediaDevices.enumerateDevices() - Web APIs | MDN" target="_blank" href= + * "https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices" + * >navigator.mediaDevices.enumerateDevices()</a>.<br/> + * + * @method setSource + * @param {number} num position of input source in the array + */ + p5.AudioIn.prototype.setSource = function (num) { + if (p5sound.inputSources.length > 0 && num < p5sound.inputSources.length) { + // set the current source + this.currentSource = num; + console.log('set source to ', p5sound.inputSources[this.currentSource]); + } else { + console.log('unable to set input source'); + } + // restart stream if currently active + if (this.stream && this.stream.active) { + this.start(); + } + }; + // private method + p5.AudioIn.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.stop(); + if (this.output) { + this.output.disconnect(); + } + if (this.amplitude) { + this.amplitude.disconnect(); + } + delete this.amplitude; + delete this.output; + }; +}(master); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Negate; +Tone_signal_Negate = function (Tone) { + 'use strict'; + Tone.Negate = function () { + this._multiply = this.input = this.output = new Tone.Multiply(-1); + }; + Tone.extend(Tone.Negate, Tone.SignalBase); + Tone.Negate.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._multiply.dispose(); + this._multiply = null; + return this; + }; + return Tone.Negate; +}(Tone_core_Tone, Tone_signal_Multiply); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Subtract; +Tone_signal_Subtract = function (Tone) { + 'use strict'; + Tone.Subtract = function (value) { + this.createInsOuts(2, 0); + this._sum = this.input[0] = this.output = new Tone.Gain(); + this._neg = new Tone.Negate(); + this._param = this.input[1] = new Tone.Signal(value); + this._param.chain(this._neg, this._sum); + }; + Tone.extend(Tone.Subtract, Tone.Signal); + Tone.Subtract.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._neg.dispose(); + this._neg = null; + this._sum.disconnect(); + this._sum = null; + this._param.dispose(); + this._param = null; + return this; + }; + return Tone.Subtract; +}(Tone_core_Tone, Tone_signal_Add, Tone_signal_Negate, Tone_signal_Signal); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_GreaterThanZero; +Tone_signal_GreaterThanZero = function (Tone) { + 'use strict'; + Tone.GreaterThanZero = function () { + this._thresh = this.output = new Tone.WaveShaper(function (val) { + if (val <= 0) { + return 0; + } else { + return 1; + } + }, 127); + this._scale = this.input = new Tone.Multiply(10000); + this._scale.connect(this._thresh); + }; + Tone.extend(Tone.GreaterThanZero, Tone.SignalBase); + Tone.GreaterThanZero.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._scale.dispose(); + this._scale = null; + this._thresh.dispose(); + this._thresh = null; + return this; + }; + return Tone.GreaterThanZero; +}(Tone_core_Tone, Tone_signal_Signal, Tone_signal_Multiply); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_GreaterThan; +Tone_signal_GreaterThan = function (Tone) { + 'use strict'; + Tone.GreaterThan = function (value) { + this.createInsOuts(2, 0); + this._param = this.input[0] = new Tone.Subtract(value); + this.input[1] = this._param.input[1]; + this._gtz = this.output = new Tone.GreaterThanZero(); + this._param.connect(this._gtz); + }; + Tone.extend(Tone.GreaterThan, Tone.Signal); + Tone.GreaterThan.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._param.dispose(); + this._param = null; + this._gtz.dispose(); + this._gtz = null; + return this; + }; + return Tone.GreaterThan; +}(Tone_core_Tone, Tone_signal_GreaterThanZero, Tone_signal_Subtract); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Abs; +Tone_signal_Abs = function (Tone) { + 'use strict'; + Tone.Abs = function () { + this._abs = this.input = this.output = new Tone.WaveShaper(function (val) { + if (val === 0) { + return 0; + } else { + return Math.abs(val); + } + }, 127); + }; + Tone.extend(Tone.Abs, Tone.SignalBase); + Tone.Abs.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._abs.dispose(); + this._abs = null; + return this; + }; + return Tone.Abs; +}(Tone_core_Tone, Tone_signal_WaveShaper); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Modulo; +Tone_signal_Modulo = function (Tone) { + 'use strict'; + Tone.Modulo = function (modulus) { + this.createInsOuts(1, 0); + this._shaper = new Tone.WaveShaper(Math.pow(2, 16)); + this._multiply = new Tone.Multiply(); + this._subtract = this.output = new Tone.Subtract(); + this._modSignal = new Tone.Signal(modulus); + this.input.fan(this._shaper, this._subtract); + this._modSignal.connect(this._multiply, 0, 0); + this._shaper.connect(this._multiply, 0, 1); + this._multiply.connect(this._subtract, 0, 1); + this._setWaveShaper(modulus); + }; + Tone.extend(Tone.Modulo, Tone.SignalBase); + Tone.Modulo.prototype._setWaveShaper = function (mod) { + this._shaper.setMap(function (val) { + var multiple = Math.floor((val + 0.0001) / mod); + return multiple; + }); + }; + Object.defineProperty(Tone.Modulo.prototype, 'value', { + get: function () { + return this._modSignal.value; + }, + set: function (mod) { + this._modSignal.value = mod; + this._setWaveShaper(mod); + } + }); + Tone.Modulo.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; + this._multiply.dispose(); + this._multiply = null; + this._subtract.dispose(); + this._subtract = null; + this._modSignal.dispose(); + this._modSignal = null; + return this; + }; + return Tone.Modulo; +}(Tone_core_Tone, Tone_signal_WaveShaper, Tone_signal_Multiply); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Pow; +Tone_signal_Pow = function (Tone) { + 'use strict'; + Tone.Pow = function (exp) { + this._exp = this.defaultArg(exp, 1); + this._expScaler = this.input = this.output = new Tone.WaveShaper(this._expFunc(this._exp), 8192); + }; + Tone.extend(Tone.Pow, Tone.SignalBase); + Object.defineProperty(Tone.Pow.prototype, 'value', { + get: function () { + return this._exp; + }, + set: function (exp) { + this._exp = exp; + this._expScaler.setMap(this._expFunc(this._exp)); + } + }); + Tone.Pow.prototype._expFunc = function (exp) { + return function (val) { + return Math.pow(Math.abs(val), exp); + }; + }; + Tone.Pow.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._expScaler.dispose(); + this._expScaler = null; + return this; + }; + return Tone.Pow; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_AudioToGain; +Tone_signal_AudioToGain = function (Tone) { + 'use strict'; + Tone.AudioToGain = function () { + this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { + return (x + 1) / 2; + }); + }; + Tone.extend(Tone.AudioToGain, Tone.SignalBase); + Tone.AudioToGain.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._norm.dispose(); + this._norm = null; + return this; + }; + return Tone.AudioToGain; +}(Tone_core_Tone, Tone_signal_WaveShaper); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_Expr; +Tone_signal_Expr = function (Tone) { + 'use strict'; + Tone.Expr = function () { + var expr = this._replacements(Array.prototype.slice.call(arguments)); + var inputCount = this._parseInputs(expr); + this._nodes = []; + this.input = new Array(inputCount); + for (var i = 0; i < inputCount; i++) { + this.input[i] = this.context.createGain(); + } + var tree = this._parseTree(expr); + var result; + try { + result = this._eval(tree); + } catch (e) { + this._disposeNodes(); + throw new Error('Tone.Expr: Could evaluate expression: ' + expr); + } + this.output = result; + }; + Tone.extend(Tone.Expr, Tone.SignalBase); + function applyBinary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + self._eval(args[1]).connect(op, 0, 1); + return op; + } + function applyUnary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + return op; + } + function getNumber(arg) { + return arg ? parseFloat(arg) : undefined; + } + function literalNumber(arg) { + return arg && arg.args ? parseFloat(arg.args) : undefined; + } + Tone.Expr._Expressions = { + 'value': { + 'signal': { + regexp: /^\d+\.\d+|^\d+/, + method: function (arg) { + var sig = new Tone.Signal(getNumber(arg)); + return sig; + } + }, + 'input': { + regexp: /^\$\d/, + method: function (arg, self) { + return self.input[getNumber(arg.substr(1))]; + } + } + }, + 'glue': { + '(': { regexp: /^\(/ }, + ')': { regexp: /^\)/ }, + ',': { regexp: /^,/ } + }, + 'func': { + 'abs': { + regexp: /^abs/, + method: applyUnary.bind(this, Tone.Abs) + }, + 'mod': { + regexp: /^mod/, + method: function (args, self) { + var modulus = literalNumber(args[1]); + var op = new Tone.Modulo(modulus); + self._eval(args[0]).connect(op); + return op; + } + }, + 'pow': { + regexp: /^pow/, + method: function (args, self) { + var exp = literalNumber(args[1]); + var op = new Tone.Pow(exp); + self._eval(args[0]).connect(op); + return op; + } + }, + 'a2g': { + regexp: /^a2g/, + method: function (args, self) { + var op = new Tone.AudioToGain(); + self._eval(args[0]).connect(op); + return op; + } + } + }, + 'binary': { + '+': { + regexp: /^\+/, + precedence: 1, + method: applyBinary.bind(this, Tone.Add) + }, + '-': { + regexp: /^\-/, + precedence: 1, + method: function (args, self) { + if (args.length === 1) { + return applyUnary(Tone.Negate, args, self); + } else { + return applyBinary(Tone.Subtract, args, self); + } + } + }, + '*': { + regexp: /^\*/, + precedence: 0, + method: applyBinary.bind(this, Tone.Multiply) + } + }, + 'unary': { + '-': { + regexp: /^\-/, + method: applyUnary.bind(this, Tone.Negate) + }, + '!': { + regexp: /^\!/, + method: applyUnary.bind(this, Tone.NOT) + } + } + }; + Tone.Expr.prototype._parseInputs = function (expr) { + var inputArray = expr.match(/\$\d/g); + var inputMax = 0; + if (inputArray !== null) { + for (var i = 0; i < inputArray.length; i++) { + var inputNum = parseInt(inputArray[i].substr(1)) + 1; + inputMax = Math.max(inputMax, inputNum); + } + } + return inputMax; + }; + Tone.Expr.prototype._replacements = function (args) { + var expr = args.shift(); + for (var i = 0; i < args.length; i++) { + expr = expr.replace(/\%/i, args[i]); + } + return expr; + }; + Tone.Expr.prototype._tokenize = function (expr) { + var position = -1; + var tokens = []; + while (expr.length > 0) { + expr = expr.trim(); + var token = getNextToken(expr); + tokens.push(token); + expr = expr.substr(token.value.length); + } + function getNextToken(expr) { + for (var type in Tone.Expr._Expressions) { + var group = Tone.Expr._Expressions[type]; + for (var opName in group) { + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null) { + return { + type: type, + value: match[0], + method: op.method + }; + } + } + } + throw new SyntaxError('Tone.Expr: Unexpected token ' + expr); + } + return { + next: function () { + return tokens[++position]; + }, + peek: function () { + return tokens[position + 1]; + } + }; + }; + Tone.Expr.prototype._parseTree = function (expr) { + var lexer = this._tokenize(expr); + var isUndef = this.isUndef.bind(this); + function matchSyntax(token, syn) { + return !isUndef(token) && token.type === 'glue' && token.value === syn; + } + function matchGroup(token, groupName, prec) { + var ret = false; + var group = Tone.Expr._Expressions[groupName]; + if (!isUndef(token)) { + for (var opName in group) { + var op = group[opName]; + if (op.regexp.test(token.value)) { + if (!isUndef(prec)) { + if (op.precedence === prec) { + return true; + } + } else { + return true; + } + } + } + } + return ret; + } + function parseExpression(precedence) { + if (isUndef(precedence)) { + precedence = 5; + } + var expr; + if (precedence < 0) { + expr = parseUnary(); + } else { + expr = parseExpression(precedence - 1); + } + var token = lexer.peek(); + while (matchGroup(token, 'binary', precedence)) { + token = lexer.next(); + expr = { + operator: token.value, + method: token.method, + args: [ + expr, + parseExpression(precedence - 1) + ] + }; + token = lexer.peek(); + } + return expr; + } + function parseUnary() { + var token, expr; + token = lexer.peek(); + if (matchGroup(token, 'unary')) { + token = lexer.next(); + expr = parseUnary(); + return { + operator: token.value, + method: token.method, + args: [expr] + }; + } + return parsePrimary(); + } + function parsePrimary() { + var token, expr; + token = lexer.peek(); + if (isUndef(token)) { + throw new SyntaxError('Tone.Expr: Unexpected termination of expression'); + } + if (token.type === 'func') { + token = lexer.next(); + return parseFunctionCall(token); + } + if (token.type === 'value') { + token = lexer.next(); + return { + method: token.method, + args: token.value + }; + } + if (matchSyntax(token, '(')) { + lexer.next(); + expr = parseExpression(); + token = lexer.next(); + if (!matchSyntax(token, ')')) { + throw new SyntaxError('Expected )'); + } + return expr; + } + throw new SyntaxError('Tone.Expr: Parse error, cannot process token ' + token.value); + } + function parseFunctionCall(func) { + var token, args = []; + token = lexer.next(); + if (!matchSyntax(token, '(')) { + throw new SyntaxError('Tone.Expr: Expected ( in a function call "' + func.value + '"'); + } + token = lexer.peek(); + if (!matchSyntax(token, ')')) { + args = parseArgumentList(); + } + token = lexer.next(); + if (!matchSyntax(token, ')')) { + throw new SyntaxError('Tone.Expr: Expected ) in a function call "' + func.value + '"'); + } + return { + method: func.method, + args: args, + name: name + }; + } + function parseArgumentList() { + var token, expr, args = []; + while (true) { + expr = parseExpression(); + if (isUndef(expr)) { + break; + } + args.push(expr); + token = lexer.peek(); + if (!matchSyntax(token, ',')) { + break; + } + lexer.next(); + } + return args; + } + return parseExpression(); + }; + Tone.Expr.prototype._eval = function (tree) { + if (!this.isUndef(tree)) { + var node = tree.method(tree.args, this); + this._nodes.push(node); + return node; + } + }; + Tone.Expr.prototype._disposeNodes = function () { + for (var i = 0; i < this._nodes.length; i++) { + var node = this._nodes[i]; + if (this.isFunction(node.dispose)) { + node.dispose(); + } else if (this.isFunction(node.disconnect)) { + node.disconnect(); + } + node = null; + this._nodes[i] = null; + } + this._nodes = null; + }; + Tone.Expr.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._disposeNodes(); + }; + return Tone.Expr; +}(Tone_core_Tone, Tone_signal_Add, Tone_signal_Subtract, Tone_signal_Multiply, Tone_signal_GreaterThan, Tone_signal_GreaterThanZero, Tone_signal_Abs, Tone_signal_Negate, Tone_signal_Modulo, Tone_signal_Pow); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_signal_EqualPowerGain; +Tone_signal_EqualPowerGain = function (Tone) { + 'use strict'; + Tone.EqualPowerGain = function () { + this._eqPower = this.input = this.output = new Tone.WaveShaper(function (val) { + if (Math.abs(val) < 0.001) { + return 0; + } else { + return this.equalPowerScale(val); + } + }.bind(this), 4096); + }; + Tone.extend(Tone.EqualPowerGain, Tone.SignalBase); + Tone.EqualPowerGain.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._eqPower.dispose(); + this._eqPower = null; + return this; + }; + return Tone.EqualPowerGain; +}(Tone_core_Tone); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_component_CrossFade; +Tone_component_CrossFade = function (Tone) { + 'use strict'; + Tone.CrossFade = function (initialFade) { + this.createInsOuts(2, 1); + this.a = this.input[0] = new Tone.Gain(); + this.b = this.input[1] = new Tone.Gain(); + this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange); + this._equalPowerA = new Tone.EqualPowerGain(); + this._equalPowerB = new Tone.EqualPowerGain(); + this._invert = new Tone.Expr('1 - $0'); + this.a.connect(this.output); + this.b.connect(this.output); + this.fade.chain(this._equalPowerB, this.b.gain); + this.fade.chain(this._invert, this._equalPowerA, this.a.gain); + this._readOnly('fade'); + }; + Tone.extend(Tone.CrossFade); + Tone.CrossFade.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable('fade'); + this._equalPowerA.dispose(); + this._equalPowerA = null; + this._equalPowerB.dispose(); + this._equalPowerB = null; + this.fade.dispose(); + this.fade = null; + this._invert.dispose(); + this._invert = null; + this.a.dispose(); + this.a = null; + this.b.dispose(); + this.b = null; + return this; + }; + return Tone.CrossFade; +}(Tone_core_Tone, Tone_signal_Signal, Tone_signal_Expr, Tone_signal_EqualPowerGain); +var effect; +'use strict'; +effect = function () { + var p5sound = master; + var CrossFade = Tone_component_CrossFade; + /** + * Effect is a base class for audio effects in p5. <br> + * This module handles the nodes and methods that are + * common and useful for current and future effects. + * + * + * This class is extended by <a href="/reference/#/p5.Distortion">p5.Distortion</a>, + * <a href="/reference/#/p5.Compressor">p5.Compressor</a>, + * <a href="/reference/#/p5.Delay">p5.Delay</a>, + * <a href="/reference/#/p5.Filter">p5.Filter</a>, + * <a href="/reference/#/p5.Reverb">p5.Reverb</a>. + * + * @class p5.Effect + * @constructor + * + * @param {Object} [ac] Reference to the audio context of the p5 object + * @param {AudioNode} [input] Gain Node effect wrapper + * @param {AudioNode} [output] Gain Node effect wrapper + * @param {Object} [_drywet] Tone.JS CrossFade node (defaults to value: 1) + * @param {AudioNode} [wet] Effects that extend this class should connect + * to the wet signal to this gain node, so that dry and wet + * signals are mixed properly. + */ + p5.Effect = function () { + this.ac = p5sound.audiocontext; + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + /** + * The p5.Effect class is built + * using Tone.js CrossFade + * @private + */ + this._drywet = new CrossFade(1); + /** + * In classes that extend + * p5.Effect, connect effect nodes + * to the wet parameter + */ + this.wet = this.ac.createGain(); + this.input.connect(this._drywet.a); + this.wet.connect(this._drywet.b); + this._drywet.connect(this.output); + this.connect(); + //Add to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Set the output volume of the filter. + * + * @method amp + * @param {Number} [vol] amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts until rampTime + * @param {Number} [tFromNow] schedule this event to happen in tFromNow seconds + */ + p5.Effect.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); + }; + /** + * Link effects together in a chain + * Example usage: filter.chain(reverb, delay, panner); + * May be used with an open-ended number of arguments + * + * @method chain + * @param {Object} [arguments] Chain together multiple sound objects + */ + p5.Effect.prototype.chain = function () { + if (arguments.length > 0) { + this.connect(arguments[0]); + for (var i = 1; i < arguments.length; i += 1) { + arguments[i - 1].connect(arguments[i]); + } + } + return this; + }; + /** + * Adjust the dry/wet value. + * + * @method drywet + * @param {Number} [fade] The desired drywet value (0 - 1.0) + */ + p5.Effect.prototype.drywet = function (fade) { + if (typeof fade !== 'undefined') { + this._drywet.fade.value = fade; + } + return this._drywet.fade.value; + }; + /** + * Send output to a p5.js-sound, Web Audio Node, or use signal to + * control an AudioParam + * + * @method connect + * @param {Object} unit + */ + p5.Effect.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Effect.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + p5.Effect.prototype.dispose = function () { + // remove refernce form soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.input) { + this.input.disconnect(); + delete this.input; + } + if (this.output) { + this.output.disconnect(); + delete this.output; + } + if (this._drywet) { + this._drywet.disconnect(); + delete this._drywet; + } + if (this.wet) { + this.wet.disconnect(); + delete this.wet; + } + this.ac = undefined; + }; + return p5.Effect; +}(master, Tone_component_CrossFade); +var filter; +'use strict'; +filter = function () { + var p5sound = master; + var Effect = effect; + /** + * <p>A p5.Filter uses a Web Audio Biquad Filter to filter + * the frequency response of an input source. Subclasses + * include:</p> + * * <a href="/reference/#/p5.LowPass"><code>p5.LowPass</code></a>: + * Allows frequencies below the cutoff frequency to pass through, + * and attenuates frequencies above the cutoff.<br/> + * * <a href="/reference/#/p5.HighPass"><code>p5.HighPass</code></a>: + * The opposite of a lowpass filter. <br/> + * * <a href="/reference/#/p5.BandPass"><code>p5.BandPass</code></a>: + * Allows a range of frequencies to pass through and attenuates + * the frequencies below and above this frequency range.<br/> + * + * The <code>.res()</code> method controls either width of the + * bandpass, or resonance of the low/highpass cutoff frequency. + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * + * @class p5.Filter + * @extends p5.Effect + * @constructor + * @param {String} [type] 'lowpass' (default), 'highpass', 'bandpass' + * @example + * <div><code> + * var fft, noise, filter; + * + * function setup() { + * fill(255, 40, 255); + * + * filter = new p5.BandPass(); + * + * noise = new p5.Noise(); + * // disconnect unfiltered noise, + * // and connect to filter + * noise.disconnect(); + * noise.connect(filter); + * noise.start(); + * + * fft = new p5.FFT(); + * } + * + * function draw() { + * background(30); + * + * // set the BandPass frequency based on mouseX + * var freq = map(mouseX, 0, width, 20, 10000); + * filter.freq(freq); + * // give the filter a narrow band (lower res = wider bandpass) + * filter.res(50); + * + * // draw filtered spectrum + * var spectrum = fft.analyze(); + * noStroke(); + * for (var i = 0; i < spectrum.length; i++) { + * var x = map(i, 0, spectrum.length, 0, width); + * var h = -height + map(spectrum[i], 0, 255, height, 0); + * rect(x, height, width/spectrum.length, h); + * } + * + * isMouseOverCanvas(); + * } + * + * function isMouseOverCanvas() { + * var mX = mouseX, mY = mouseY; + * if (mX > 0 && mX < width && mY < height && mY > 0) { + * noise.amp(0.5, 0.2); + * } else { + * noise.amp(0, 0.2); + * } + * } + * </code></div> + */ + //constructor with inheritance + p5.Filter = function (type) { + Effect.call(this); + //add extend Effect by adding a Biquad Filter + /** + * The p5.Filter is built with a + * <a href="http://www.w3.org/TR/webaudio/#BiquadFilterNode"> + * Web Audio BiquadFilter Node</a>. + * + * @property {DelayNode} biquadFilter + */ + this.biquad = this.ac.createBiquadFilter(); + this.input.connect(this.biquad); + this.biquad.connect(this.wet); + if (type) { + this.setType(type); + } + //Properties useful for the toggle method. + this._on = true; + this._untoggledType = this.biquad.type; + }; + p5.Filter.prototype = Object.create(Effect.prototype); + /** + * Filter an audio signal according to a set + * of filter parameters. + * + * @method process + * @param {Object} Signal An object that outputs audio + * @param {Number} [freq] Frequency in Hz, from 10 to 22050 + * @param {Number} [res] Resonance/Width of the filter frequency + * from 0.001 to 1000 + */ + p5.Filter.prototype.process = function (src, freq, res, time) { + src.connect(this.input); + this.set(freq, res, time); + }; + /** + * Set the frequency and the resonance of the filter. + * + * @method set + * @param {Number} [freq] Frequency in Hz, from 10 to 22050 + * @param {Number} [res] Resonance (Q) from 0.001 to 1000 + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Filter.prototype.set = function (freq, res, time) { + if (freq) { + this.freq(freq, time); + } + if (res) { + this.res(res, time); + } + }; + /** + * Set the filter frequency, in Hz, from 10 to 22050 (the range of + * human hearing, although in reality most people hear in a narrower + * range). + * + * @method freq + * @param {Number} freq Filter Frequency + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {Number} value Returns the current frequency value + */ + p5.Filter.prototype.freq = function (freq, time) { + var t = time || 0; + if (freq <= 0) { + freq = 1; + } + if (typeof freq === 'number') { + this.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t); + } else if (freq) { + freq.connect(this.biquad.frequency); + } + return this.biquad.frequency.value; + }; + /** + * Controls either width of a bandpass frequency, + * or the resonance of a low/highpass cutoff frequency. + * + * @method res + * @param {Number} res Resonance/Width of filter freq + * from 0.001 to 1000 + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + * @return {Number} value Returns the current res value + */ + p5.Filter.prototype.res = function (res, time) { + var t = time || 0; + if (typeof res === 'number') { + this.biquad.Q.value = res; + this.biquad.Q.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.biquad.Q.linearRampToValueAtTime(res, this.ac.currentTime + 0.02 + t); + } else if (res) { + res.connect(this.biquad.Q); + } + return this.biquad.Q.value; + }; + /** + * Controls the gain attribute of a Biquad Filter. + * This is distinctly different from .amp() which is inherited from p5.Effect + * .amp() controls the volume via the output gain node + * p5.Filter.gain() controls the gain parameter of a Biquad Filter node. + * + * @method gain + * @param {Number} gain + * @return {Number} Returns the current or updated gain value + */ + p5.Filter.prototype.gain = function (gain, time) { + var t = time || 0; + if (typeof gain === 'number') { + this.biquad.gain.value = gain; + this.biquad.gain.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.biquad.gain.linearRampToValueAtTime(gain, this.ac.currentTime + 0.02 + t); + } else if (gain) { + gain.connect(this.biquad.gain); + } + return this.biquad.gain.value; + }; + /** + * Toggle function. Switches between the specified type and allpass + * + * @method toggle + * @return {boolean} [Toggle value] + */ + p5.Filter.prototype.toggle = function () { + this._on = !this._on; + if (this._on === true) { + this.biquad.type = this._untoggledType; + } else if (this._on === false) { + this.biquad.type = 'allpass'; + } + return this._on; + }; + /** + * Set the type of a p5.Filter. Possible types include: + * "lowpass" (default), "highpass", "bandpass", + * "lowshelf", "highshelf", "peaking", "notch", + * "allpass". + * + * @method setType + * @param {String} t + */ + p5.Filter.prototype.setType = function (t) { + this.biquad.type = t; + this._untoggledType = this.biquad.type; + }; + p5.Filter.prototype.dispose = function () { + // remove reference from soundArray + Effect.prototype.dispose.apply(this); + if (this.biquad) { + this.biquad.disconnect(); + delete this.biquad; + } + }; + /** + * Constructor: <code>new p5.LowPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('lowpass')</code>. + * See p5.Filter for methods. + * + * @class p5.LowPass + * @constructor + * @extends p5.Filter + */ + p5.LowPass = function () { + p5.Filter.call(this, 'lowpass'); + }; + p5.LowPass.prototype = Object.create(p5.Filter.prototype); + /** + * Constructor: <code>new p5.HighPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('highpass')</code>. + * See p5.Filter for methods. + * + * @class p5.HighPass + * @constructor + * @extends p5.Filter + */ + p5.HighPass = function () { + p5.Filter.call(this, 'highpass'); + }; + p5.HighPass.prototype = Object.create(p5.Filter.prototype); + /** + * Constructor: <code>new p5.BandPass()</code> Filter. + * This is the same as creating a p5.Filter and then calling + * its method <code>setType('bandpass')</code>. + * See p5.Filter for methods. + * + * @class p5.BandPass + * @constructor + * @extends p5.Filter + */ + p5.BandPass = function () { + p5.Filter.call(this, 'bandpass'); + }; + p5.BandPass.prototype = Object.create(p5.Filter.prototype); + return p5.Filter; +}(master, effect); +var src_eqFilter; +'use strict'; +src_eqFilter = function () { + var Filter = filter; + var p5sound = master; + /** + * EQFilter extends p5.Filter with constraints + * necessary for the p5.EQ + * + * @private + */ + var EQFilter = function (freq, res) { + Filter.call(this, 'peaking'); + this.disconnect(); + this.set(freq, res); + this.biquad.gain.value = 0; + delete this.input; + delete this.output; + delete this._drywet; + delete this.wet; + }; + EQFilter.prototype = Object.create(Filter.prototype); + EQFilter.prototype.amp = function () { + console.warn('`amp()` is not available for p5.EQ bands. Use `.gain()`'); + }; + EQFilter.prototype.drywet = function () { + console.warn('`drywet()` is not available for p5.EQ bands.'); + }; + EQFilter.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + if (this.biquad) { + this.biquad.connect(u.input ? u.input : u); + } else { + this.output.connect(u.input ? u.input : u); + } + }; + EQFilter.prototype.disconnect = function () { + if (this.biquad) { + this.biquad.disconnect(); + } + }; + EQFilter.prototype.dispose = function () { + // remove reference form soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this.disconnect(); + delete this.biquad; + }; + return EQFilter; +}(filter, master); +var eq; +'use strict'; +eq = function () { + var Effect = effect; + var EQFilter = src_eqFilter; + /** + * p5.EQ is an audio effect that performs the function of a multiband + * audio equalizer. Equalization is used to adjust the balance of + * frequency compoenents of an audio signal. This process is commonly used + * in sound production and recording to change the waveform before it reaches + * a sound output device. EQ can also be used as an audio effect to create + * interesting distortions by filtering out parts of the spectrum. p5.EQ is + * built using a chain of Web Audio Biquad Filter Nodes and can be + * instantiated with 3 or 8 bands. Bands can be added or removed from + * the EQ by directly modifying p5.EQ.bands (the array that stores filters). + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * + * @class p5.EQ + * @constructor + * @extends p5.Effect + * @param {Number} [_eqsize] Constructor will accept 3 or 8, defaults to 3 + * @return {Object} p5.EQ object + * + * @example + * <div><code> + * var eq; + * var band_names; + * var band_index; + * + * var soundFile, play; + * + * function preload() { + * soundFormats('mp3', 'ogg'); + * soundFile = loadSound('assets/beat'); + * } + * + * function setup() { + * eq = new p5.EQ(3); + * soundFile.disconnect(); + * eq.process(soundFile); + * + * band_names = ['lows','mids','highs']; + * band_index = 0; + * play = false; + * textAlign(CENTER); + * } + * + * function draw() { + * background(30); + * noStroke(); + * fill(255); + * text('click to kill',50,25); + * + * fill(255, 40, 255); + * textSize(26); + * text(band_names[band_index],50,55); + * + * fill(255); + * textSize(9); + * text('space = play/pause',50,80); + * } + * + * //If mouse is over canvas, cycle to the next band and kill the frequency + * function mouseClicked() { + * for (var i = 0; i < eq.bands.length; i++) { + * eq.bands[i].gain(0); + * } + * eq.bands[band_index].gain(-40); + * if (mouseX > 0 && mouseX < width && mouseY < height && mouseY > 0) { + * band_index === 2 ? band_index = 0 : band_index++; + * } + * } + * + * //use space bar to trigger play / pause + * function keyPressed() { + * if (key===' ') { + * play = !play + * play ? soundFile.loop() : soundFile.pause(); + * } + * } + * </code></div> + */ + p5.EQ = function (_eqsize) { + Effect.call(this); + //p5.EQ can be of size (3) or (8), defaults to 3 + _eqsize = _eqsize === 3 || _eqsize === 8 ? _eqsize : 3; + var factor; + _eqsize === 3 ? factor = Math.pow(2, 3) : factor = 2; + /** + * The p5.EQ is built with abstracted p5.Filter objects. + * To modify any bands, use methods of the <a + * href="/reference/#/p5.Filter" title="p5.Filter reference"> + * p5.Filter</a> API, especially `gain` and `freq`. + * Bands are stored in an array, with indices 0 - 3, or 0 - 7 + * @property {Array} bands + * + */ + this.bands = []; + var freq, res; + for (var i = 0; i < _eqsize; i++) { + if (i === _eqsize - 1) { + freq = 21000; + res = 0.01; + } else if (i === 0) { + freq = 100; + res = 0.1; + } else if (i === 1) { + freq = _eqsize === 3 ? 360 * factor : 360; + res = 1; + } else { + freq = this.bands[i - 1].freq() * factor; + res = 1; + } + this.bands[i] = this._newBand(freq, res); + if (i > 0) { + this.bands[i - 1].connect(this.bands[i].biquad); + } else { + this.input.connect(this.bands[i].biquad); + } + } + this.bands[_eqsize - 1].connect(this.output); + }; + p5.EQ.prototype = Object.create(Effect.prototype); + /** + * Process an input by connecting it to the EQ + * @method process + * @param {Object} src Audio source + */ + p5.EQ.prototype.process = function (src) { + src.connect(this.input); + }; + // /** + // * Set the frequency and gain of each band in the EQ. This method should be + // * called with 3 or 8 frequency and gain pairs, depending on the size of the EQ. + // * ex. eq.set(freq0, gain0, freq1, gain1, freq2, gain2); + // * + // * @method set + // * @param {Number} [freq0] Frequency value for band with index 0 + // * @param {Number} [gain0] Gain value for band with index 0 + // * @param {Number} [freq1] Frequency value for band with index 1 + // * @param {Number} [gain1] Gain value for band with index 1 + // * @param {Number} [freq2] Frequency value for band with index 2 + // * @param {Number} [gain2] Gain value for band with index 2 + // * @param {Number} [freq3] Frequency value for band with index 3 + // * @param {Number} [gain3] Gain value for band with index 3 + // * @param {Number} [freq4] Frequency value for band with index 4 + // * @param {Number} [gain4] Gain value for band with index 4 + // * @param {Number} [freq5] Frequency value for band with index 5 + // * @param {Number} [gain5] Gain value for band with index 5 + // * @param {Number} [freq6] Frequency value for band with index 6 + // * @param {Number} [gain6] Gain value for band with index 6 + // * @param {Number} [freq7] Frequency value for band with index 7 + // * @param {Number} [gain7] Gain value for band with index 7 + // */ + p5.EQ.prototype.set = function () { + if (arguments.length === this.bands.length * 2) { + for (var i = 0; i < arguments.length; i += 2) { + this.bands[i / 2].freq(arguments[i]); + this.bands[i / 2].gain(arguments[i + 1]); + } + } else { + console.error('Argument mismatch. .set() should be called with ' + this.bands.length * 2 + ' arguments. (one frequency and gain value pair for each band of the eq)'); + } + }; + /** + * Add a new band. Creates a p5.Filter and strips away everything but + * the raw biquad filter. This method returns an abstracted p5.Filter, + * which can be added to p5.EQ.bands, in order to create new EQ bands. + * @private + * @method _newBand + * @param {Number} freq + * @param {Number} res + * @return {Object} Abstracted Filter + */ + p5.EQ.prototype._newBand = function (freq, res) { + return new EQFilter(freq, res); + }; + p5.EQ.prototype.dispose = function () { + Effect.prototype.dispose.apply(this); + if (this.bands) { + while (this.bands.length > 0) { + delete this.bands.pop().dispose(); + } + delete this.bands; + } + }; + return p5.EQ; +}(effect, src_eqFilter); +var panner3d; +'use strict'; +panner3d = function () { + var p5sound = master; + var Effect = effect; + /** + * Panner3D is based on the <a title="Web Audio Panner docs" href= + * "https://developer.mozilla.org/en-US/docs/Web/API/PannerNode"> + * Web Audio Spatial Panner Node</a>. + * This panner is a spatial processing node that allows audio to be positioned + * and oriented in 3D space. + * + * The position is relative to an <a title="Web Audio Listener docs" href= + * "https://developer.mozilla.org/en-US/docs/Web/API/AudioListener"> + * Audio Context Listener</a>, which can be accessed + * by <code>p5.soundOut.audiocontext.listener</code> + * + * + * @class p5.Panner3D + * @constructor + */ + p5.Panner3D = function () { + Effect.call(this); + /** + * <a title="Web Audio Panner docs" href= + * "https://developer.mozilla.org/en-US/docs/Web/API/PannerNode"> + * Web Audio Spatial Panner Node</a> + * + * Properties include + * - <a title="w3 spec for Panning Model" + * href="https://www.w3.org/TR/webaudio/#idl-def-PanningModelType" + * >panningModel</a>: "equal power" or "HRTF" + * - <a title="w3 spec for Distance Model" + * href="https://www.w3.org/TR/webaudio/#idl-def-DistanceModelType" + * >distanceModel</a>: "linear", "inverse", or "exponential" + * + * @property {AudioNode} panner + * + */ + this.panner = this.ac.createPanner(); + this.panner.panningModel = 'HRTF'; + this.panner.distanceModel = 'linear'; + this.panner.connect(this.output); + this.input.connect(this.panner); + }; + p5.Panner3D.prototype = Object.create(Effect.prototype); + /** + * Connect an audio sorce + * + * @method process + * @param {Object} src Input source + */ + p5.Panner3D.prototype.process = function (src) { + src.connect(this.input); + }; + /** + * Set the X,Y,Z position of the Panner + * @method set + * @param {Number} xVal + * @param {Number} yVal + * @param {Number} zVal + * @param {Number} time + * @return {Array} Updated x, y, z values as an array + */ + p5.Panner3D.prototype.set = function (xVal, yVal, zVal, time) { + this.positionX(xVal, time); + this.positionY(yVal, time); + this.positionZ(zVal, time); + return [ + this.panner.positionX.value, + this.panner.positionY.value, + this.panner.positionZ.value + ]; + }; + /** + * Getter and setter methods for position coordinates + * @method positionX + * @return {Number} updated coordinate value + */ + /** + * Getter and setter methods for position coordinates + * @method positionY + * @return {Number} updated coordinate value + */ + /** + * Getter and setter methods for position coordinates + * @method positionZ + * @return {Number} updated coordinate value + */ + p5.Panner3D.prototype.positionX = function (xVal, time) { + var t = time || 0; + if (typeof xVal === 'number') { + this.panner.positionX.value = xVal; + this.panner.positionX.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.positionX.linearRampToValueAtTime(xVal, this.ac.currentTime + 0.02 + t); + } else if (xVal) { + xVal.connect(this.panner.positionX); + } + return this.panner.positionX.value; + }; + p5.Panner3D.prototype.positionY = function (yVal, time) { + var t = time || 0; + if (typeof yVal === 'number') { + this.panner.positionY.value = yVal; + this.panner.positionY.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.positionY.linearRampToValueAtTime(yVal, this.ac.currentTime + 0.02 + t); + } else if (yVal) { + yVal.connect(this.panner.positionY); + } + return this.panner.positionY.value; + }; + p5.Panner3D.prototype.positionZ = function (zVal, time) { + var t = time || 0; + if (typeof zVal === 'number') { + this.panner.positionZ.value = zVal; + this.panner.positionZ.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.positionZ.linearRampToValueAtTime(zVal, this.ac.currentTime + 0.02 + t); + } else if (zVal) { + zVal.connect(this.panner.positionZ); + } + return this.panner.positionZ.value; + }; + /** + * Set the X,Y,Z position of the Panner + * @method orient + * @param {Number} xVal + * @param {Number} yVal + * @param {Number} zVal + * @param {Number} time + * @return {Array} Updated x, y, z values as an array + */ + p5.Panner3D.prototype.orient = function (xVal, yVal, zVal, time) { + this.orientX(xVal, time); + this.orientY(yVal, time); + this.orientZ(zVal, time); + return [ + this.panner.orientationX.value, + this.panner.orientationY.value, + this.panner.orientationZ.value + ]; + }; + /** + * Getter and setter methods for orient coordinates + * @method orientX + * @return {Number} updated coordinate value + */ + /** + * Getter and setter methods for orient coordinates + * @method orientY + * @return {Number} updated coordinate value + */ + /** + * Getter and setter methods for orient coordinates + * @method orientZ + * @return {Number} updated coordinate value + */ + p5.Panner3D.prototype.orientX = function (xVal, time) { + var t = time || 0; + if (typeof xVal === 'number') { + this.panner.orientationX.value = xVal; + this.panner.orientationX.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.orientationX.linearRampToValueAtTime(xVal, this.ac.currentTime + 0.02 + t); + } else if (xVal) { + xVal.connect(this.panner.orientationX); + } + return this.panner.orientationX.value; + }; + p5.Panner3D.prototype.orientY = function (yVal, time) { + var t = time || 0; + if (typeof yVal === 'number') { + this.panner.orientationY.value = yVal; + this.panner.orientationY.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.orientationY.linearRampToValueAtTime(yVal, this.ac.currentTime + 0.02 + t); + } else if (yVal) { + yVal.connect(this.panner.orientationY); + } + return this.panner.orientationY.value; + }; + p5.Panner3D.prototype.orientZ = function (zVal, time) { + var t = time || 0; + if (typeof zVal === 'number') { + this.panner.orientationZ.value = zVal; + this.panner.orientationZ.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.panner.orientationZ.linearRampToValueAtTime(zVal, this.ac.currentTime + 0.02 + t); + } else if (zVal) { + zVal.connect(this.panner.orientationZ); + } + return this.panner.orientationZ.value; + }; + /** + * Set the rolloff factor and max distance + * @method setFalloff + * @param {Number} [maxDistance] + * @param {Number} [rolloffFactor] + */ + p5.Panner3D.prototype.setFalloff = function (maxDistance, rolloffFactor) { + this.maxDist(maxDistance); + this.rolloff(rolloffFactor); + }; + /** + * Maxium distance between the source and the listener + * @method maxDist + * @param {Number} maxDistance + * @return {Number} updated value + */ + p5.Panner3D.prototype.maxDist = function (maxDistance) { + if (typeof maxDistance === 'number') { + this.panner.maxDistance = maxDistance; + } + return this.panner.maxDistance; + }; + /** + * How quickly the volume is reduced as the source moves away from the listener + * @method rollof + * @param {Number} rolloffFactor + * @return {Number} updated value + */ + p5.Panner3D.prototype.rolloff = function (rolloffFactor) { + if (typeof rolloffFactor === 'number') { + this.panner.rolloffFactor = rolloffFactor; + } + return this.panner.rolloffFactor; + }; + p5.Panner3D.dispose = function () { + Effect.prototype.dispose.apply(this); + if (this.panner) { + this.panner.disconnect(); + delete this.panner; + } + }; + return p5.Panner3D; +}(master, effect); +var listener3d; +'use strict'; +listener3d = function () { + var p5sound = master; + var Effect = effect; + // /** + // * listener is a class that can construct both a Spatial Panner + // * and a Spatial Listener. The panner is based on the + // * Web Audio Spatial Panner Node + // * https://www.w3.org/TR/webaudio/#the-listenernode-interface + // * This panner is a spatial processing node that allows audio to be positioned + // * and oriented in 3D space. + // * + // * The Listener modifies the properties of the Audio Context Listener. + // * Both objects types use the same methods. The default is a spatial panner. + // * + // * <code>p5.Panner3D</code> - Constructs a Spatial Panner<br/> + // * <code>p5.Listener3D</code> - Constructs a Spatial Listener<br/> + // * + // * @class listener + // * @constructor + // * @return {Object} p5.Listener3D Object + // * + // * @param {Web Audio Node} listener Web Audio Spatial Panning Node + // * @param {AudioParam} listener.panningModel "equal power" or "HRTF" + // * @param {AudioParam} listener.distanceModel "linear", "inverse", or "exponential" + // * @param {String} [type] [Specify construction of a spatial panner or listener] + // */ + p5.Listener3D = function (type) { + this.ac = p5sound.audiocontext; + this.listener = this.ac.listener; + }; + // /** + // * Connect an audio sorce + // * @param {Object} src Input source + // */ + p5.Listener3D.prototype.process = function (src) { + src.connect(this.input); + }; + // /** + // * Set the X,Y,Z position of the Panner + // * @param {[Number]} xVal + // * @param {[Number]} yVal + // * @param {[Number]} zVal + // * @param {[Number]} time + // * @return {[Array]} [Updated x, y, z values as an array] + // */ + p5.Listener3D.prototype.position = function (xVal, yVal, zVal, time) { + this.positionX(xVal, time); + this.positionY(yVal, time); + this.positionZ(zVal, time); + return [ + this.listener.positionX.value, + this.listener.positionY.value, + this.listener.positionZ.value + ]; + }; + // /** + // * Getter and setter methods for position coordinates + // * @return {Number} [updated coordinate value] + // */ + p5.Listener3D.prototype.positionX = function (xVal, time) { + var t = time || 0; + if (typeof xVal === 'number') { + this.listener.positionX.value = xVal; + this.listener.positionX.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.positionX.linearRampToValueAtTime(xVal, this.ac.currentTime + 0.02 + t); + } else if (xVal) { + xVal.connect(this.listener.positionX); + } + return this.listener.positionX.value; + }; + p5.Listener3D.prototype.positionY = function (yVal, time) { + var t = time || 0; + if (typeof yVal === 'number') { + this.listener.positionY.value = yVal; + this.listener.positionY.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.positionY.linearRampToValueAtTime(yVal, this.ac.currentTime + 0.02 + t); + } else if (yVal) { + yVal.connect(this.listener.positionY); + } + return this.listener.positionY.value; + }; + p5.Listener3D.prototype.positionZ = function (zVal, time) { + var t = time || 0; + if (typeof zVal === 'number') { + this.listener.positionZ.value = zVal; + this.listener.positionZ.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.positionZ.linearRampToValueAtTime(zVal, this.ac.currentTime + 0.02 + t); + } else if (zVal) { + zVal.connect(this.listener.positionZ); + } + return this.listener.positionZ.value; + }; + // cannot define method when class definition is commented + // /** + // * Overrides the listener orient() method because Listener has slightly + // * different params. In human terms, Forward vectors are the direction the + // * nose is pointing. Up vectors are the direction of the top of the head. + // * + // * @method orient + // * @param {Number} xValF Forward vector X direction + // * @param {Number} yValF Forward vector Y direction + // * @param {Number} zValF Forward vector Z direction + // * @param {Number} xValU Up vector X direction + // * @param {Number} yValU Up vector Y direction + // * @param {Number} zValU Up vector Z direction + // * @param {Number} time + // * @return {Array} All orienation params + // */ + p5.Listener3D.prototype.orient = function (xValF, yValF, zValF, xValU, yValU, zValU, time) { + if (arguments.length === 3 || arguments.length === 4) { + time = arguments[3]; + this.orientForward(xValF, yValF, zValF, time); + } else if (arguments.length === 6 || arguments === 7) { + this.orientForward(xValF, yValF, zValF); + this.orientUp(xValU, yValU, zValU, time); + } + return [ + this.listener.forwardX.value, + this.listener.forwardY.value, + this.listener.forwardZ.value, + this.listener.upX.value, + this.listener.upY.value, + this.listener.upZ.value + ]; + }; + p5.Listener3D.prototype.orientForward = function (xValF, yValF, zValF, time) { + this.forwardX(xValF, time); + this.forwardY(yValF, time); + this.forwardZ(zValF, time); + return [ + this.listener.forwardX, + this.listener.forwardY, + this.listener.forwardZ + ]; + }; + p5.Listener3D.prototype.orientUp = function (xValU, yValU, zValU, time) { + this.upX(xValU, time); + this.upY(yValU, time); + this.upZ(zValU, time); + return [ + this.listener.upX, + this.listener.upY, + this.listener.upZ + ]; + }; + // /** + // * Getter and setter methods for orient coordinates + // * @return {Number} [updated coordinate value] + // */ + p5.Listener3D.prototype.forwardX = function (xVal, time) { + var t = time || 0; + if (typeof xVal === 'number') { + this.listener.forwardX.value = xVal; + this.listener.forwardX.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.forwardX.linearRampToValueAtTime(xVal, this.ac.currentTime + 0.02 + t); + } else if (xVal) { + xVal.connect(this.listener.forwardX); + } + return this.listener.forwardX.value; + }; + p5.Listener3D.prototype.forwardY = function (yVal, time) { + var t = time || 0; + if (typeof yVal === 'number') { + this.listener.forwardY.value = yVal; + this.listener.forwardY.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.forwardY.linearRampToValueAtTime(yVal, this.ac.currentTime + 0.02 + t); + } else if (yVal) { + yVal.connect(this.listener.forwardY); + } + return this.listener.forwardY.value; + }; + p5.Listener3D.prototype.forwardZ = function (zVal, time) { + var t = time || 0; + if (typeof zVal === 'number') { + this.listener.forwardZ.value = zVal; + this.listener.forwardZ.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.forwardZ.linearRampToValueAtTime(zVal, this.ac.currentTime + 0.02 + t); + } else if (zVal) { + zVal.connect(this.listener.forwardZ); + } + return this.listener.forwardZ.value; + }; + p5.Listener3D.prototype.upX = function (xVal, time) { + var t = time || 0; + if (typeof xVal === 'number') { + this.listener.upX.value = xVal; + this.listener.upX.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.upX.linearRampToValueAtTime(xVal, this.ac.currentTime + 0.02 + t); + } else if (xVal) { + xVal.connect(this.listener.upX); + } + return this.listener.upX.value; + }; + p5.Listener3D.prototype.upY = function (yVal, time) { + var t = time || 0; + if (typeof yVal === 'number') { + this.listener.upY.value = yVal; + this.listener.upY.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.upY.linearRampToValueAtTime(yVal, this.ac.currentTime + 0.02 + t); + } else if (yVal) { + yVal.connect(this.listener.upY); + } + return this.listener.upY.value; + }; + p5.Listener3D.prototype.upZ = function (zVal, time) { + var t = time || 0; + if (typeof zVal === 'number') { + this.listener.upZ.value = zVal; + this.listener.upZ.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.listener.upZ.linearRampToValueAtTime(zVal, this.ac.currentTime + 0.02 + t); + } else if (zVal) { + zVal.connect(this.listener.upZ); + } + return this.listener.upZ.value; + }; + return p5.Listener3D; +}(master, effect); +var delay; +'use strict'; +delay = function () { + var Filter = filter; + var Effect = effect; + /** + * Delay is an echo effect. It processes an existing sound source, + * and outputs a delayed version of that sound. The p5.Delay can + * produce different effects depending on the delayTime, feedback, + * filter, and type. In the example below, a feedback of 0.5 (the + * defaul value) will produce a looping delay that decreases in + * volume by 50% each repeat. A filter will cut out the high + * frequencies so that the delay does not sound as piercing as the + * original source. + * + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * @class p5.Delay + * @extends p5.Effect + * @constructor + * @example + * <div><code> + * var noise, env, delay; + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * + * noise = new p5.Noise('brown'); + * noise.amp(0); + * noise.start(); + * + * delay = new p5.Delay(); + * + * // delay.process() accepts 4 parameters: + * // source, delayTime, feedback, filter frequency + * // play with these numbers!! + * delay.process(noise, .12, .7, 2300); + * + * // play the noise with an envelope, + * // a series of fades ( time / value pairs ) + * env = new p5.Envelope(.01, 0.2, .2, .1); + * } + * + * // mouseClick triggers envelope + * function mouseClicked() { + * // is mouse over canvas? + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * env.play(noise); + * } + * } + * </code></div> + */ + p5.Delay = function () { + Effect.call(this); + this._split = this.ac.createChannelSplitter(2); + this._merge = this.ac.createChannelMerger(2); + this._leftGain = this.ac.createGain(); + this._rightGain = this.ac.createGain(); + /** + * The p5.Delay is built with two + * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> + * Web Audio Delay Nodes</a>, one for each stereo channel. + * + * @property {DelayNode} leftDelay + */ + this.leftDelay = this.ac.createDelay(); + /** + * The p5.Delay is built with two + * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> + * Web Audio Delay Nodes</a>, one for each stereo channel. + * + * @property {DelayNode} rightDelay + */ + this.rightDelay = this.ac.createDelay(); + this._leftFilter = new Filter(); + this._rightFilter = new Filter(); + this._leftFilter.disconnect(); + this._rightFilter.disconnect(); + this._leftFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); + this._rightFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); + this._leftFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); + this._rightFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); + // graph routing + this.input.connect(this._split); + this.leftDelay.connect(this._leftGain); + this.rightDelay.connect(this._rightGain); + this._leftGain.connect(this._leftFilter.input); + this._rightGain.connect(this._rightFilter.input); + this._merge.connect(this.wet); + this._leftFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); + this._rightFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); + // default routing + this.setType(0); + this._maxDelay = this.leftDelay.delayTime.maxValue; + // set initial feedback to 0.5 + this.feedback(0.5); + }; + p5.Delay.prototype = Object.create(Effect.prototype); + /** + * Add delay to an audio signal according to a set + * of delay parameters. + * + * @method process + * @param {Object} Signal An object that outputs audio + * @param {Number} [delayTime] Time (in seconds) of the delay/echo. + * Some browsers limit delayTime to + * 1 second. + * @param {Number} [feedback] sends the delay back through itself + * in a loop that decreases in volume + * each time. + * @param {Number} [lowPass] Cutoff frequency. Only frequencies + * below the lowPass will be part of the + * delay. + */ + p5.Delay.prototype.process = function (src, _delayTime, _feedback, _filter) { + var feedback = _feedback || 0; + var delayTime = _delayTime || 0; + if (feedback >= 1) { + throw new Error('Feedback value will force a positive feedback loop.'); + } + if (delayTime >= this._maxDelay) { + throw new Error('Delay Time exceeds maximum delay time of ' + this._maxDelay + ' second.'); + } + src.connect(this.input); + this.leftDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); + this.rightDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); + this._leftGain.gain.value = feedback; + this._rightGain.gain.value = feedback; + if (_filter) { + this._leftFilter.freq(_filter); + this._rightFilter.freq(_filter); + } + }; + /** + * Set the delay (echo) time, in seconds. Usually this value will be + * a floating point number between 0.0 and 1.0. + * + * @method delayTime + * @param {Number} delayTime Time (in seconds) of the delay + */ + p5.Delay.prototype.delayTime = function (t) { + // if t is an audio node... + if (typeof t !== 'number') { + t.connect(this.leftDelay.delayTime); + t.connect(this.rightDelay.delayTime); + } else { + this.leftDelay.delayTime.cancelScheduledValues(this.ac.currentTime); + this.rightDelay.delayTime.cancelScheduledValues(this.ac.currentTime); + this.leftDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); + this.rightDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); + } + }; + /** + * Feedback occurs when Delay sends its signal back through its input + * in a loop. The feedback amount determines how much signal to send each + * time through the loop. A feedback greater than 1.0 is not desirable because + * it will increase the overall output each time through the loop, + * creating an infinite feedback loop. The default value is 0.5 + * + * @method feedback + * @param {Number|Object} feedback 0.0 to 1.0, or an object such as an + * Oscillator that can be used to + * modulate this param + * @returns {Number} Feedback value + * + */ + p5.Delay.prototype.feedback = function (f) { + // if f is an audio node... + if (f && typeof f !== 'number') { + f.connect(this._leftGain.gain); + f.connect(this._rightGain.gain); + } else if (f >= 1) { + throw new Error('Feedback value will force a positive feedback loop.'); + } else if (typeof f === 'number') { + this._leftGain.gain.value = f; + this._rightGain.gain.value = f; + } + // return value of feedback + return this._leftGain.gain.value; + }; + /** + * Set a lowpass filter frequency for the delay. A lowpass filter + * will cut off any frequencies higher than the filter frequency. + * + * @method filter + * @param {Number|Object} cutoffFreq A lowpass filter will cut off any + * frequencies higher than the filter frequency. + * @param {Number|Object} res Resonance of the filter frequency + * cutoff, or an object (i.e. a p5.Oscillator) + * that can be used to modulate this parameter. + * High numbers (i.e. 15) will produce a resonance, + * low numbers (i.e. .2) will produce a slope. + */ + p5.Delay.prototype.filter = function (freq, q) { + this._leftFilter.set(freq, q); + this._rightFilter.set(freq, q); + }; + /** + * Choose a preset type of delay. 'pingPong' bounces the signal + * from the left to the right channel to produce a stereo effect. + * Any other parameter will revert to the default delay setting. + * + * @method setType + * @param {String|Number} type 'pingPong' (1) or 'default' (0) + */ + p5.Delay.prototype.setType = function (t) { + if (t === 1) { + t = 'pingPong'; + } + this._split.disconnect(); + this._leftFilter.disconnect(); + this._rightFilter.disconnect(); + this._split.connect(this.leftDelay, 0); + this._split.connect(this.rightDelay, 1); + switch (t) { + case 'pingPong': + this._rightFilter.setType(this._leftFilter.biquad.type); + this._leftFilter.output.connect(this._merge, 0, 0); + this._rightFilter.output.connect(this._merge, 0, 1); + this._leftFilter.output.connect(this.rightDelay); + this._rightFilter.output.connect(this.leftDelay); + break; + default: + this._leftFilter.output.connect(this._merge, 0, 0); + this._rightFilter.output.connect(this._merge, 0, 1); + this._leftFilter.output.connect(this.leftDelay); + this._rightFilter.output.connect(this.rightDelay); + } + }; + // DocBlocks for methods inherited from p5.Effect + /** + * Set the output level of the delay effect. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Delay.prototype.dispose = function () { + Effect.prototype.dispose.apply(this); + this._split.disconnect(); + this._leftFilter.dispose(); + this._rightFilter.dispose(); + this._merge.disconnect(); + this._leftGain.disconnect(); + this._rightGain.disconnect(); + this.leftDelay.disconnect(); + this.rightDelay.disconnect(); + this._split = undefined; + this._leftFilter = undefined; + this._rightFilter = undefined; + this._merge = undefined; + this._leftGain = undefined; + this._rightGain = undefined; + this.leftDelay = undefined; + this.rightDelay = undefined; + }; +}(filter, effect); +var reverb; +'use strict'; +reverb = function () { + var CustomError = errorHandler; + var Effect = effect; + /** + * Reverb adds depth to a sound through a large number of decaying + * echoes. It creates the perception that sound is occurring in a + * physical space. The p5.Reverb has paramters for Time (how long does the + * reverb last) and decayRate (how much the sound decays with each echo) + * that can be set with the .set() or .process() methods. The p5.Convolver + * extends p5.Reverb allowing you to recreate the sound of actual physical + * spaces through convolution. + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * + * @class p5.Reverb + * @extends p5.Effect + * @constructor + * @example + * <div><code> + * var soundFile, reverb; + * function preload() { + * soundFile = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * reverb = new p5.Reverb(); + * soundFile.disconnect(); // so we'll only hear reverb... + * + * // connect soundFile to reverb, process w/ + * // 3 second reverbTime, decayRate of 2% + * reverb.process(soundFile, 3, 2); + * soundFile.play(); + * } + * </code></div> + */ + p5.Reverb = function () { + Effect.call(this); + this._initConvolverNode(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + // default params + this._seconds = 3; + this._decay = 2; + this._reverse = false; + this._buildImpulse(); + }; + p5.Reverb.prototype = Object.create(Effect.prototype); + p5.Reverb.prototype._initConvolverNode = function () { + this.convolverNode = this.ac.createConvolver(); + this.input.connect(this.convolverNode); + this.convolverNode.connect(this.wet); + }; + p5.Reverb.prototype._teardownConvolverNode = function () { + if (this.convolverNode) { + this.convolverNode.disconnect(); + delete this.convolverNode; + } + }; + p5.Reverb.prototype._setBuffer = function (audioBuffer) { + this._teardownConvolverNode(); + this._initConvolverNode(); + this.convolverNode.buffer = audioBuffer; + }; + /** + * Connect a source to the reverb, and assign reverb parameters. + * + * @method process + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + * @param {Number} [seconds] Duration of the reverb, in seconds. + * Min: 0, Max: 10. Defaults to 3. + * @param {Number} [decayRate] Percentage of decay with each echo. + * Min: 0, Max: 100. Defaults to 2. + * @param {Boolean} [reverse] Play the reverb backwards or forwards. + */ + p5.Reverb.prototype.process = function (src, seconds, decayRate, reverse) { + src.connect(this.input); + var rebuild = false; + if (seconds) { + this._seconds = seconds; + rebuild = true; + } + if (decayRate) { + this._decay = decayRate; + } + if (reverse) { + this._reverse = reverse; + } + if (rebuild) { + this._buildImpulse(); + } + }; + /** + * Set the reverb settings. Similar to .process(), but without + * assigning a new input. + * + * @method set + * @param {Number} [seconds] Duration of the reverb, in seconds. + * Min: 0, Max: 10. Defaults to 3. + * @param {Number} [decayRate] Percentage of decay with each echo. + * Min: 0, Max: 100. Defaults to 2. + * @param {Boolean} [reverse] Play the reverb backwards or forwards. + */ + p5.Reverb.prototype.set = function (seconds, decayRate, reverse) { + var rebuild = false; + if (seconds) { + this._seconds = seconds; + rebuild = true; + } + if (decayRate) { + this._decay = decayRate; + } + if (reverse) { + this._reverse = reverse; + } + if (rebuild) { + this._buildImpulse(); + } + }; + // DocBlocks for methods inherited from p5.Effect + /** + * Set the output level of the reverb effect. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + /** + * Disconnect all output. + * + * @method disconnect + */ + /** + * Inspired by Simple Reverb by Jordan Santell + * https://github.com/web-audio-components/simple-reverb/blob/master/index.js + * + * Utility function for building an impulse response + * based on the module parameters. + * + * @private + */ + p5.Reverb.prototype._buildImpulse = function () { + var rate = this.ac.sampleRate; + var length = rate * this._seconds; + var decay = this._decay; + var impulse = this.ac.createBuffer(2, length, rate); + var impulseL = impulse.getChannelData(0); + var impulseR = impulse.getChannelData(1); + var n, i; + for (i = 0; i < length; i++) { + n = this._reverse ? length - i : i; + impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); + impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); + } + this._setBuffer(impulse); + }; + p5.Reverb.prototype.dispose = function () { + Effect.prototype.dispose.apply(this); + this._teardownConvolverNode(); + }; + // ======================================================================= + // *** p5.Convolver *** + // ======================================================================= + /** + * <p>p5.Convolver extends p5.Reverb. It can emulate the sound of real + * physical spaces through a process called <a href=" + * https://en.wikipedia.org/wiki/Convolution_reverb#Real_space_simulation"> + * convolution</a>.</p> + * + * <p>Convolution multiplies any audio input by an "impulse response" + * to simulate the dispersion of sound over time. The impulse response is + * generated from an audio file that you provide. One way to + * generate an impulse response is to pop a balloon in a reverberant space + * and record the echo. Convolution can also be used to experiment with + * sound.</p> + * + * <p>Use the method <code>createConvolution(path)</code> to instantiate a + * p5.Convolver with a path to your impulse response audio file.</p> + * + * @class p5.Convolver + * @extends p5.Effect + * @constructor + * @param {String} path path to a sound file + * @param {Function} [callback] function to call when loading succeeds + * @param {Function} [errorCallback] function to call if loading fails. + * This function will receive an error or + * XMLHttpRequest object with information + * about what went wrong. + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * // We have both MP3 and OGG versions of all sound assets + * soundFormats('ogg', 'mp3'); + * + * // Try replacing 'bx-spring' with other soundfiles like + * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' + * cVerb = createConvolver('assets/bx-spring.mp3'); + * + * // Try replacing 'Damscray_DancingTiger' with + * // 'beat', 'doorbell', lucky_dragons_-_power_melody' + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.Convolver = function (path, callback, errorCallback) { + p5.Reverb.call(this); + /** + * Internally, the p5.Convolver uses the a + * <a href="http://www.w3.org/TR/webaudio/#ConvolverNode"> + * Web Audio Convolver Node</a>. + * + * @property {ConvolverNode} convolverNode + */ + this._initConvolverNode(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + if (path) { + this.impulses = []; + this._loadBuffer(path, callback, errorCallback); + } else { + // parameters + this._seconds = 3; + this._decay = 2; + this._reverse = false; + this._buildImpulse(); + } + }; + p5.Convolver.prototype = Object.create(p5.Reverb.prototype); + p5.prototype.registerPreloadMethod('createConvolver', p5.prototype); + /** + * Create a p5.Convolver. Accepts a path to a soundfile + * that will be used to generate an impulse response. + * + * @method createConvolver + * @param {String} path path to a sound file + * @param {Function} [callback] function to call if loading is successful. + * The object will be passed in as the argument + * to the callback function. + * @param {Function} [errorCallback] function to call if loading is not successful. + * A custom error will be passed in as the argument + * to the callback function. + * @return {p5.Convolver} + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * // We have both MP3 and OGG versions of all sound assets + * soundFormats('ogg', 'mp3'); + * + * // Try replacing 'bx-spring' with other soundfiles like + * // 'concrete-tunnel' 'small-plate' 'drum' 'beatbox' + * cVerb = createConvolver('assets/bx-spring.mp3'); + * + * // Try replacing 'Damscray_DancingTiger' with + * // 'beat', 'doorbell', lucky_dragons_-_power_melody' + * sound = loadSound('assets/Damscray_DancingTiger.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.prototype.createConvolver = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + var self = this; + var cReverb = new p5.Convolver(path, function (buffer) { + if (typeof callback === 'function') { + callback(buffer); + } + if (typeof self._decrementPreload === 'function') { + self._decrementPreload(); + } + }, errorCallback); + cReverb.impulses = []; + return cReverb; + }; + /** + * Private method to load a buffer as an Impulse Response, + * assign it to the convolverNode, and add to the Array of .impulses. + * + * @param {String} path + * @param {Function} callback + * @param {Function} errorCallback + * @private + */ + p5.Convolver.prototype._loadBuffer = function (path, callback, errorCallback) { + var path = p5.prototype._checkFileFormats(path); + var self = this; + var errorTrace = new Error().stack; + var ac = p5.prototype.getAudioContext(); + var request = new XMLHttpRequest(); + request.open('GET', path, true); + request.responseType = 'arraybuffer'; + request.onload = function () { + if (request.status === 200) { + // on success loading file: + ac.decodeAudioData(request.response, function (buff) { + var buffer = {}; + var chunks = path.split('/'); + buffer.name = chunks[chunks.length - 1]; + buffer.audioBuffer = buff; + self.impulses.push(buffer); + self._setBuffer(buffer.audioBuffer); + if (callback) { + callback(buffer); + } + }, // error decoding buffer. "e" is undefined in Chrome 11/22/2015 + function () { + var err = new CustomError('decodeAudioData', errorTrace, self.url); + var msg = 'AudioContext error at decodeAudioData for ' + self.url; + if (errorCallback) { + err.msg = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }); + } else { + var err = new CustomError('loadConvolver', errorTrace, self.url); + var msg = 'Unable to load ' + self.url + '. The request status was: ' + request.status + ' (' + request.statusText + ')'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + } + }; + // if there is another error, aside from 404... + request.onerror = function () { + var err = new CustomError('loadConvolver', errorTrace, self.url); + var msg = 'There was no response from the server at ' + self.url + '. Check the url and internet connectivity.'; + if (errorCallback) { + err.message = msg; + errorCallback(err); + } else { + console.error(msg + '\n The error stack trace includes: \n' + err.stack); + } + }; + request.send(); + }; + p5.Convolver.prototype.set = null; + /** + * Connect a source to the reverb, and assign reverb parameters. + * + * @method process + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + * @example + * <div><code> + * var cVerb, sound; + * function preload() { + * soundFormats('ogg', 'mp3'); + * + * cVerb = createConvolver('assets/concrete-tunnel.mp3'); + * + * sound = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * // disconnect from master output... + * sound.disconnect(); + * + * // ...and process with (i.e. connect to) cVerb + * // so that we only hear the convolution + * cVerb.process(sound); + * + * sound.play(); + * } + * </code></div> + */ + p5.Convolver.prototype.process = function (src) { + src.connect(this.input); + }; + /** + * If you load multiple impulse files using the .addImpulse method, + * they will be stored as Objects in this Array. Toggle between them + * with the <code>toggleImpulse(id)</code> method. + * + * @property {Array} impulses + */ + p5.Convolver.prototype.impulses = []; + /** + * Load and assign a new Impulse Response to the p5.Convolver. + * The impulse is added to the <code>.impulses</code> array. Previous + * impulses can be accessed with the <code>.toggleImpulse(id)</code> + * method. + * + * @method addImpulse + * @param {String} path path to a sound file + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) + */ + p5.Convolver.prototype.addImpulse = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + this._loadBuffer(path, callback, errorCallback); + }; + /** + * Similar to .addImpulse, except that the <code>.impulses</code> + * Array is reset to save memory. A new <code>.impulses</code> + * array is created with this impulse as the only item. + * + * @method resetImpulse + * @param {String} path path to a sound file + * @param {Function} callback function (optional) + * @param {Function} errorCallback function (optional) + */ + p5.Convolver.prototype.resetImpulse = function (path, callback, errorCallback) { + // if loading locally without a server + if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined') { + alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); + } + this.impulses = []; + this._loadBuffer(path, callback, errorCallback); + }; + /** + * If you have used <code>.addImpulse()</code> to add multiple impulses + * to a p5.Convolver, then you can use this method to toggle between + * the items in the <code>.impulses</code> Array. Accepts a parameter + * to identify which impulse you wish to use, identified either by its + * original filename (String) or by its position in the <code>.impulses + * </code> Array (Number).<br/> + * You can access the objects in the .impulses Array directly. Each + * Object has two attributes: an <code>.audioBuffer</code> (type: + * Web Audio <a href=" + * http://webaudio.github.io/web-audio-api/#the-audiobuffer-interface"> + * AudioBuffer)</a> and a <code>.name</code>, a String that corresponds + * with the original filename. + * + * @method toggleImpulse + * @param {String|Number} id Identify the impulse by its original filename + * (String), or by its position in the + * <code>.impulses</code> Array (Number). + */ + p5.Convolver.prototype.toggleImpulse = function (id) { + if (typeof id === 'number' && id < this.impulses.length) { + this._setBuffer(this.impulses[id].audioBuffer); + } + if (typeof id === 'string') { + for (var i = 0; i < this.impulses.length; i++) { + if (this.impulses[i].name === id) { + this._setBuffer(this.impulses[i].audioBuffer); + break; + } + } + } + }; + p5.Convolver.prototype.dispose = function () { + p5.Reverb.prototype.dispose.apply(this); + // remove all the Impulse Response buffers + for (var i in this.impulses) { + if (this.impulses[i]) { + this.impulses[i] = null; + } + } + }; +}(errorHandler, effect); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_TimelineState; +Tone_core_TimelineState = function (Tone) { + 'use strict'; + Tone.TimelineState = function (initial) { + Tone.Timeline.call(this); + this._initial = initial; + }; + Tone.extend(Tone.TimelineState, Tone.Timeline); + Tone.TimelineState.prototype.getValueAtTime = function (time) { + var event = this.get(time); + if (event !== null) { + return event.state; + } else { + return this._initial; + } + }; + Tone.TimelineState.prototype.setStateAtTime = function (state, time) { + this.add({ + 'state': state, + 'time': time + }); + }; + return Tone.TimelineState; +}(Tone_core_Tone, Tone_core_Timeline); +/** Tone.js module by Yotam Mann, MIT License 2016 http://opensource.org/licenses/MIT **/ +var Tone_core_Clock; +Tone_core_Clock = function (Tone) { + 'use strict'; + Tone.Clock = function () { + Tone.Emitter.call(this); + var options = this.optionsObject(arguments, [ + 'callback', + 'frequency' + ], Tone.Clock.defaults); + this.callback = options.callback; + this._nextTick = 0; + this._lastState = Tone.State.Stopped; + this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency); + this._readOnly('frequency'); + this.ticks = 0; + this._state = new Tone.TimelineState(Tone.State.Stopped); + this._boundLoop = this._loop.bind(this); + this.context.on('tick', this._boundLoop); + }; + Tone.extend(Tone.Clock, Tone.Emitter); + Tone.Clock.defaults = { + 'callback': Tone.noOp, + 'frequency': 1, + 'lookAhead': 'auto' + }; + Object.defineProperty(Tone.Clock.prototype, 'state', { + get: function () { + return this._state.getValueAtTime(this.now()); + } + }); + Tone.Clock.prototype.start = function (time, offset) { + time = this.toSeconds(time); + if (this._state.getValueAtTime(time) !== Tone.State.Started) { + this._state.add({ + 'state': Tone.State.Started, + 'time': time, + 'offset': offset + }); + } + return this; + }; + Tone.Clock.prototype.stop = function (time) { + time = this.toSeconds(time); + this._state.cancel(time); + this._state.setStateAtTime(Tone.State.Stopped, time); + return this; + }; + Tone.Clock.prototype.pause = function (time) { + time = this.toSeconds(time); + if (this._state.getValueAtTime(time) === Tone.State.Started) { + this._state.setStateAtTime(Tone.State.Paused, time); + } + return this; + }; + Tone.Clock.prototype._loop = function () { + var now = this.now(); + var lookAhead = this.context.lookAhead; + var updateInterval = this.context.updateInterval; + var lagCompensation = this.context.lag * 2; + var loopInterval = now + lookAhead + updateInterval + lagCompensation; + while (loopInterval > this._nextTick && this._state) { + var currentState = this._state.getValueAtTime(this._nextTick); + if (currentState !== this._lastState) { + this._lastState = currentState; + var event = this._state.get(this._nextTick); + if (currentState === Tone.State.Started) { + this._nextTick = event.time; + if (!this.isUndef(event.offset)) { + this.ticks = event.offset; + } + this.emit('start', event.time, this.ticks); + } else if (currentState === Tone.State.Stopped) { + this.ticks = 0; + this.emit('stop', event.time); + } else if (currentState === Tone.State.Paused) { + this.emit('pause', event.time); + } + } + var tickTime = this._nextTick; + if (this.frequency) { + this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick); + if (currentState === Tone.State.Started) { + this.callback(tickTime); + this.ticks++; + } + } + } + }; + Tone.Clock.prototype.getStateAtTime = function (time) { + time = this.toSeconds(time); + return this._state.getValueAtTime(time); + }; + Tone.Clock.prototype.dispose = function () { + Tone.Emitter.prototype.dispose.call(this); + this.context.off('tick', this._boundLoop); + this._writable('frequency'); + this.frequency.dispose(); + this.frequency = null; + this._boundLoop = null; + this._nextTick = Infinity; + this.callback = null; + this._state.dispose(); + this._state = null; + }; + return Tone.Clock; +}(Tone_core_Tone, Tone_signal_TimelineSignal, Tone_core_TimelineState, Tone_core_Emitter); +var metro; +'use strict'; +metro = function () { + var p5sound = master; + // requires the Tone.js library's Clock (MIT license, Yotam Mann) + // https://github.com/TONEnoTONE/Tone.js/ + var Clock = Tone_core_Clock; + p5.Metro = function () { + this.clock = new Clock({ 'callback': this.ontick.bind(this) }); + this.syncedParts = []; + this.bpm = 120; + // gets overridden by p5.Part + this._init(); + this.prevTick = 0; + this.tatumTime = 0; + this.tickCallback = function () { + }; + }; + p5.Metro.prototype.ontick = function (tickTime) { + var elapsedTime = tickTime - this.prevTick; + var secondsFromNow = tickTime - p5sound.audiocontext.currentTime; + if (elapsedTime - this.tatumTime <= -0.02) { + return; + } else { + // console.log('ok', this.syncedParts[0].phrases[0].name); + this.prevTick = tickTime; + // for all of the active things on the metro: + var self = this; + this.syncedParts.forEach(function (thisPart) { + if (!thisPart.isPlaying) + return; + thisPart.incrementStep(secondsFromNow); + // each synced source keeps track of its own beat number + thisPart.phrases.forEach(function (thisPhrase) { + var phraseArray = thisPhrase.sequence; + var bNum = self.metroTicks % phraseArray.length; + if (phraseArray[bNum] !== 0 && (self.metroTicks < phraseArray.length || !thisPhrase.looping)) { + thisPhrase.callback(secondsFromNow, phraseArray[bNum]); + } + }); + }); + this.metroTicks += 1; + this.tickCallback(secondsFromNow); + } + }; + p5.Metro.prototype.setBPM = function (bpm, rampTime) { + var beatTime = 60 / (bpm * this.tatums); + var now = p5sound.audiocontext.currentTime; + this.tatumTime = beatTime; + var rampTime = rampTime || 0; + this.clock.frequency.setValueAtTime(this.clock.frequency.value, now); + this.clock.frequency.linearRampToValueAtTime(bpm, now + rampTime); + this.bpm = bpm; + }; + p5.Metro.prototype.getBPM = function () { + return this.clock.getRate() / this.tatums * 60; + }; + p5.Metro.prototype._init = function () { + this.metroTicks = 0; + }; + // clear existing synced parts, add only this one + p5.Metro.prototype.resetSync = function (part) { + this.syncedParts = [part]; + }; + // push a new synced part to the array + p5.Metro.prototype.pushSync = function (part) { + this.syncedParts.push(part); + }; + p5.Metro.prototype.start = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + this.clock.start(now + t); + this.setBPM(this.bpm); + }; + p5.Metro.prototype.stop = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + this.clock.stop(now + t); + }; + p5.Metro.prototype.beatLength = function (tatums) { + this.tatums = 1 / tatums / 4; + }; +}(master, Tone_core_Clock); +var looper; +'use strict'; +looper = function () { + var p5sound = master; + var BPM = 120; + /** + * Set the global tempo, in beats per minute, for all + * p5.Parts. This method will impact all active p5.Parts. + * + * @method setBPM + * @param {Number} BPM Beats Per Minute + * @param {Number} rampTime Seconds from now + */ + p5.prototype.setBPM = function (bpm, rampTime) { + BPM = bpm; + for (var i in p5sound.parts) { + if (p5sound.parts[i]) { + p5sound.parts[i].setBPM(bpm, rampTime); + } + } + }; + /** + * <p>A phrase is a pattern of musical events over time, i.e. + * a series of notes and rests.</p> + * + * <p>Phrases must be added to a p5.Part for playback, and + * each part can play multiple phrases at the same time. + * For example, one Phrase might be a kick drum, another + * could be a snare, and another could be the bassline.</p> + * + * <p>The first parameter is a name so that the phrase can be + * modified or deleted later. The callback is a a function that + * this phrase will call at every step—for example it might be + * called <code>playNote(value){}</code>. The array determines + * which value is passed into the callback at each step of the + * phrase. It can be numbers, an object with multiple numbers, + * or a zero (0) indicates a rest so the callback won't be called).</p> + * + * @class p5.Phrase + * @constructor + * @param {String} name Name so that you can access the Phrase. + * @param {Function} callback The name of a function that this phrase + * will call. Typically it will play a sound, + * and accept two parameters: a time at which + * to play the sound (in seconds from now), + * and a value from the sequence array. The + * time should be passed into the play() or + * start() method to ensure precision. + * @param {Array} sequence Array of values to pass into the callback + * at each step of the phrase. + * @example + * <div><code> + * var mySound, myPhrase, myPart; + * var pattern = [1,0,0,2,0,2,0,0]; + * var msg = 'click to play'; + * + * function preload() { + * mySound = loadSound('assets/beatbox.mp3'); + * } + * + * function setup() { + * noStroke(); + * fill(255); + * textAlign(CENTER); + * masterVolume(0.1); + * + * myPhrase = new p5.Phrase('bbox', makeSound, pattern); + * myPart = new p5.Part(); + * myPart.addPhrase(myPhrase); + * myPart.setBPM(60); + * } + * + * function draw() { + * background(0); + * text(msg, width/2, height/2); + * } + * + * function makeSound(time, playbackRate) { + * mySound.rate(playbackRate); + * mySound.play(time); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * myPart.start(); + * msg = 'playing pattern'; + * } + * } + * + * </code></div> + */ + p5.Phrase = function (name, callback, sequence) { + this.phraseStep = 0; + this.name = name; + this.callback = callback; + /** + * Array of values to pass into the callback + * at each step of the phrase. Depending on the callback + * function's requirements, these values may be numbers, + * strings, or an object with multiple parameters. + * Zero (0) indicates a rest. + * + * @property {Array} sequence + */ + this.sequence = sequence; + }; + /** + * <p>A p5.Part plays back one or more p5.Phrases. Instantiate a part + * with steps and tatums. By default, each step represents a 1/16th note.</p> + * + * <p>See p5.Phrase for more about musical timing.</p> + * + * @class p5.Part + * @constructor + * @param {Number} [steps] Steps in the part + * @param {Number} [tatums] Divisions of a beat, e.g. use 1/4, or 0.25 for a quater note (default is 1/16, a sixteenth note) + * @example + * <div><code> + * var box, drum, myPart; + * var boxPat = [1,0,0,2,0,2,0,0]; + * var drumPat = [0,1,1,0,2,0,1,0]; + * var msg = 'click to play'; + * + * function preload() { + * box = loadSound('assets/beatbox.mp3'); + * drum = loadSound('assets/drum.mp3'); + * } + * + * function setup() { + * noStroke(); + * fill(255); + * textAlign(CENTER); + * masterVolume(0.1); + * + * var boxPhrase = new p5.Phrase('box', playBox, boxPat); + * var drumPhrase = new p5.Phrase('drum', playDrum, drumPat); + * myPart = new p5.Part(); + * myPart.addPhrase(boxPhrase); + * myPart.addPhrase(drumPhrase); + * myPart.setBPM(60); + * masterVolume(0.1); + * } + * + * function draw() { + * background(0); + * text(msg, width/2, height/2); + * } + * + * function playBox(time, playbackRate) { + * box.rate(playbackRate); + * box.play(time); + * } + * + * function playDrum(time, playbackRate) { + * drum.rate(playbackRate); + * drum.play(time); + * } + * + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * myPart.start(); + * msg = 'playing part'; + * } + * } + * </code></div> + */ + p5.Part = function (steps, bLength) { + this.length = steps || 0; + // how many beats + this.partStep = 0; + this.phrases = []; + this.isPlaying = false; + this.noLoop(); + this.tatums = bLength || 0.0625; + // defaults to quarter note + this.metro = new p5.Metro(); + this.metro._init(); + this.metro.beatLength(this.tatums); + this.metro.setBPM(BPM); + p5sound.parts.push(this); + this.callback = function () { + }; + }; + /** + * Set the tempo of this part, in Beats Per Minute. + * + * @method setBPM + * @param {Number} BPM Beats Per Minute + * @param {Number} [rampTime] Seconds from now + */ + p5.Part.prototype.setBPM = function (tempo, rampTime) { + this.metro.setBPM(tempo, rampTime); + }; + /** + * Returns the tempo, in Beats Per Minute, of this part. + * + * @method getBPM + * @return {Number} + */ + p5.Part.prototype.getBPM = function () { + return this.metro.getBPM(); + }; + /** + * Start playback of this part. It will play + * through all of its phrases at a speed + * determined by setBPM. + * + * @method start + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.start = function (time) { + if (!this.isPlaying) { + this.isPlaying = true; + this.metro.resetSync(this); + var t = time || 0; + this.metro.start(t); + } + }; + /** + * Loop playback of this part. It will begin + * looping through all of its phrases at a speed + * determined by setBPM. + * + * @method loop + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.loop = function (time) { + this.looping = true; + // rest onended function + this.onended = function () { + this.partStep = 0; + }; + var t = time || 0; + this.start(t); + }; + /** + * Tell the part to stop looping. + * + * @method noLoop + */ + p5.Part.prototype.noLoop = function () { + this.looping = false; + // rest onended function + this.onended = function () { + this.stop(); + }; + }; + /** + * Stop the part and cue it to step 0. Playback will resume from the begining of the Part when it is played again. + * + * @method stop + * @param {Number} [time] seconds from now + */ + p5.Part.prototype.stop = function (time) { + this.partStep = 0; + this.pause(time); + }; + /** + * Pause the part. Playback will resume + * from the current step. + * + * @method pause + * @param {Number} time seconds from now + */ + p5.Part.prototype.pause = function (time) { + this.isPlaying = false; + var t = time || 0; + this.metro.stop(t); + }; + /** + * Add a p5.Phrase to this Part. + * + * @method addPhrase + * @param {p5.Phrase} phrase reference to a p5.Phrase + */ + p5.Part.prototype.addPhrase = function (name, callback, array) { + var p; + if (arguments.length === 3) { + p = new p5.Phrase(name, callback, array); + } else if (arguments[0] instanceof p5.Phrase) { + p = arguments[0]; + } else { + throw 'invalid input. addPhrase accepts name, callback, array or a p5.Phrase'; + } + this.phrases.push(p); + // reset the length if phrase is longer than part's existing length + if (p.sequence.length > this.length) { + this.length = p.sequence.length; + } + }; + /** + * Remove a phrase from this part, based on the name it was + * given when it was created. + * + * @method removePhrase + * @param {String} phraseName + */ + p5.Part.prototype.removePhrase = function (name) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + this.phrases.splice(i, 1); + } + } + }; + /** + * Get a phrase from this part, based on the name it was + * given when it was created. Now you can modify its array. + * + * @method getPhrase + * @param {String} phraseName + */ + p5.Part.prototype.getPhrase = function (name) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + return this.phrases[i]; + } + } + }; + /** + * Find all sequences with the specified name, and replace their patterns with the specified array. + * + * @method replaceSequence + * @param {String} phraseName + * @param {Array} sequence Array of values to pass into the callback + * at each step of the phrase. + */ + p5.Part.prototype.replaceSequence = function (name, array) { + for (var i in this.phrases) { + if (this.phrases[i].name === name) { + this.phrases[i].sequence = array; + } + } + }; + p5.Part.prototype.incrementStep = function (time) { + if (this.partStep < this.length - 1) { + this.callback(time); + this.partStep += 1; + } else { + if (!this.looping && this.partStep === this.length - 1) { + console.log('done'); + // this.callback(time); + this.onended(); + } + } + }; + /** + * Set the function that will be called at every step. This will clear the previous function. + * + * @method onStep + * @param {Function} callback The name of the callback + * you want to fire + * on every beat/tatum. + */ + p5.Part.prototype.onStep = function (callback) { + this.callback = callback; + }; + // =============== + // p5.Score + // =============== + /** + * A Score consists of a series of Parts. The parts will + * be played back in order. For example, you could have an + * A part, a B part, and a C part, and play them back in this order + * <code>new p5.Score(a, a, b, a, c)</code> + * + * @class p5.Score + * @constructor + * @param {p5.Part} [...parts] One or multiple parts, to be played in sequence. + */ + p5.Score = function () { + // for all of the arguments + this.parts = []; + this.currentPart = 0; + var thisScore = this; + for (var i in arguments) { + if (arguments[i] && this.parts[i]) { + this.parts[i] = arguments[i]; + this.parts[i].nextPart = this.parts[i + 1]; + this.parts[i].onended = function () { + thisScore.resetPart(i); + playNextPart(thisScore); + }; + } + } + this.looping = false; + }; + p5.Score.prototype.onended = function () { + if (this.looping) { + // this.resetParts(); + this.parts[0].start(); + } else { + this.parts[this.parts.length - 1].onended = function () { + this.stop(); + this.resetParts(); + }; + } + this.currentPart = 0; + }; + /** + * Start playback of the score. + * + * @method start + */ + p5.Score.prototype.start = function () { + this.parts[this.currentPart].start(); + this.scoreStep = 0; + }; + /** + * Stop playback of the score. + * + * @method stop + */ + p5.Score.prototype.stop = function () { + this.parts[this.currentPart].stop(); + this.currentPart = 0; + this.scoreStep = 0; + }; + /** + * Pause playback of the score. + * + * @method pause + */ + p5.Score.prototype.pause = function () { + this.parts[this.currentPart].stop(); + }; + /** + * Loop playback of the score. + * + * @method loop + */ + p5.Score.prototype.loop = function () { + this.looping = true; + this.start(); + }; + /** + * Stop looping playback of the score. If it + * is currently playing, this will go into effect + * after the current round of playback completes. + * + * @method noLoop + */ + p5.Score.prototype.noLoop = function () { + this.looping = false; + }; + p5.Score.prototype.resetParts = function () { + var self = this; + this.parts.forEach(function (part) { + self.resetParts[part]; + }); + }; + p5.Score.prototype.resetPart = function (i) { + this.parts[i].stop(); + this.parts[i].partStep = 0; + for (var p in this.parts[i].phrases) { + if (this.parts[i]) { + this.parts[i].phrases[p].phraseStep = 0; + } + } + }; + /** + * Set the tempo for all parts in the score + * + * @method setBPM + * @param {Number} BPM Beats Per Minute + * @param {Number} rampTime Seconds from now + */ + p5.Score.prototype.setBPM = function (bpm, rampTime) { + for (var i in this.parts) { + if (this.parts[i]) { + this.parts[i].setBPM(bpm, rampTime); + } + } + }; + function playNextPart(aScore) { + aScore.currentPart++; + if (aScore.currentPart >= aScore.parts.length) { + aScore.scoreStep = 0; + aScore.onended(); + } else { + aScore.scoreStep = 0; + aScore.parts[aScore.currentPart - 1].stop(); + aScore.parts[aScore.currentPart].start(); + } + } +}(master); +var soundloop; +'use strict'; +soundloop = function () { + var p5sound = master; + var Clock = Tone_core_Clock; + /** + * SoundLoop + * + * @class p5.SoundLoop + * @constructor + * + * @param {Function} callback this function will be called on each iteration of theloop + * @param {Number|String} [interval] amount of time or beats for each iteration of the loop + * defaults to 1 + * + * @example + * <div><code> + * var click; + * var looper1; + * + * function preload() { + * click = loadSound('assets/drum.mp3'); + * } + * + * function setup() { + * //the looper's callback is passed the timeFromNow + * //this value should be used as a reference point from + * //which to schedule sounds + * looper1 = new p5.SoundLoop(function(timeFromNow){ + * click.play(timeFromNow); + * background(255 * (looper1.iterations % 2)); + * }, 2); + * + * //stop after 10 iteratios; + * looper1.maxIterations = 10; + * //start the loop + * looper1.start(); + * } + * </code></div> + */ + p5.SoundLoop = function (callback, interval) { + this.callback = callback; + /** + * musicalTimeMode uses <a href = "https://github.com/Tonejs/Tone.js/wiki/Time">Tone.Time</a> convention + * true if string, false if number + * @property {Boolean} musicalTimeMode + */ + this.musicalTimeMode = typeof this._interval === 'number' ? false : true; + this._interval = interval || 1; + /** + * musicalTimeMode variables + * modify these only when the interval is specified in musicalTime format as a string + */ + this._timeSignature = 4; + this._bpm = 60; + this.isPlaying = false; + /** + * Set a limit to the number of loops to play. defaults to Infinity + * @property {Number} maxIterations + */ + this.maxIterations = Infinity; + var self = this; + this.clock = new Clock({ + 'callback': function (time) { + var timeFromNow = time - p5sound.audiocontext.currentTime; + /** + * Do not initiate the callback if timeFromNow is < 0 + * This ususually occurs for a few milliseconds when the page + * is not fully loaded + * + * The callback should only be called until maxIterations is reached + */ + if (timeFromNow > 0 && self.iterations <= self.maxIterations) { + self.callback(timeFromNow); + } + }, + 'frequency': this._calcFreq() + }); + }; + /** + * Start the loop + * @method start + * @param {Number} [timeFromNow] schedule a starting time + */ + p5.SoundLoop.prototype.start = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + if (!this.isPlaying) { + this.clock.start(now + t); + this.isPlaying = true; + } + }; + /** + * Stop the loop + * @method stop + * @param {Number} [timeFromNow] schedule a stopping time + */ + p5.SoundLoop.prototype.stop = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + if (this.isPlaying) { + this.clock.stop(now + t); + this.isPlaying = false; + } + }; + /** + * Pause the loop + * @method pause + * @param {Number} [timeFromNow] schedule a pausing time + */ + p5.SoundLoop.prototype.pause = function (timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + if (this.isPlaying) { + this.clock.pause(now + t); + this.isPlaying = false; + } + }; + /** + * Synchronize loops. Use this method to start two more more loops in synchronization + * or to start a loop in synchronization with a loop that is already playing + * This method will schedule the implicit loop in sync with the explicit master loop + * i.e. loopToStart.syncedStart(loopToSyncWith) + * + * @method syncedStart + * @param {Object} otherLoop a p5.SoundLoop to sync with + * @param {Number} [timeFromNow] Start the loops in sync after timeFromNow seconds + */ + p5.SoundLoop.prototype.syncedStart = function (otherLoop, timeFromNow) { + var t = timeFromNow || 0; + var now = p5sound.audiocontext.currentTime; + if (!otherLoop.isPlaying) { + otherLoop.clock.start(now + t); + otherLoop.isPlaying = true; + this.clock.start(now + t); + this.isPlaying = true; + } else if (otherLoop.isPlaying) { + var time = otherLoop.clock._nextTick - p5sound.audiocontext.currentTime; + this.clock.start(now + time); + this.isPlaying = true; + } + }; + /** + * Updates frequency value, reflected in next callback + * @private + * @method _update + */ + p5.SoundLoop.prototype._update = function () { + this.clock.frequency.value = this._calcFreq(); + }; + /** + * Calculate the frequency of the clock's callback based on bpm, interval, and timesignature + * @private + * @method _calcFreq + * @return {Number} new clock frequency value + */ + p5.SoundLoop.prototype._calcFreq = function () { + //Seconds mode, bpm / timesignature has no effect + if (typeof this._interval === 'number') { + this.musicalTimeMode = false; + return 1 / this._interval; + } else if (typeof this._interval === 'string') { + this.musicalTimeMode = true; + return this._bpm / 60 / this._convertNotation(this._interval) * (this._timeSignature / 4); + } + }; + /** + * Convert notation from musical time format to seconds + * Uses <a href = "https://github.com/Tonejs/Tone.js/wiki/Time">Tone.Time</a> convention + * @private + * @method _convertNotation + * @param {String} value value to be converted + * @return {Number} converted value in seconds + */ + p5.SoundLoop.prototype._convertNotation = function (value) { + var type = value.slice(-1); + value = Number(value.slice(0, -1)); + switch (type) { + case 'm': + return this._measure(value); + case 'n': + return this._note(value); + default: + console.warn('Specified interval is not formatted correctly. See Tone.js ' + 'timing reference for more info: https://github.com/Tonejs/Tone.js/wiki/Time'); + } + }; + /** + * Helper conversion methods of measure and note + * @private + * @method _measure + * @private + * @method _note + */ + p5.SoundLoop.prototype._measure = function (value) { + return value * this._timeSignature; + }; + p5.SoundLoop.prototype._note = function (value) { + return this._timeSignature / value; + }; + /** + * Getters and Setters, setting any paramter will result in a change in the clock's + * frequency, that will be reflected after the next callback + * beats per minute (defaults to 60) + * @property {Number} bpm + */ + Object.defineProperty(p5.SoundLoop.prototype, 'bpm', { + get: function () { + return this._bpm; + }, + set: function (bpm) { + if (!this.musicalTimeMode) { + console.warn('Changing the BPM in "seconds" mode has no effect. ' + 'BPM is only relevant in musicalTimeMode ' + 'when the interval is specified as a string ' + '("2n", "4n", "1m"...etc)'); + } + this._bpm = bpm; + this._update(); + } + }); + /** + * number of quarter notes in a measure (defaults to 4) + * @property {Number} timeSignature + */ + Object.defineProperty(p5.SoundLoop.prototype, 'timeSignature', { + get: function () { + return this._timeSignature; + }, + set: function (timeSig) { + if (!this.musicalTimeMode) { + console.warn('Changing the timeSignature in "seconds" mode has no effect. ' + 'BPM is only relevant in musicalTimeMode ' + 'when the interval is specified as a string ' + '("2n", "4n", "1m"...etc)'); + } + this._timeSignature = timeSig; + this._update(); + } + }); + /** + * length of the loops interval + * @property {Number|String} interval + */ + Object.defineProperty(p5.SoundLoop.prototype, 'interval', { + get: function () { + return this._interval; + }, + set: function (interval) { + this.musicalTimeMode = typeof interval === 'Number' ? false : true; + this._interval = interval; + this._update(); + } + }); + /** + * how many times the callback has been called so far + * @property {Number} iterations + * @readonly + */ + Object.defineProperty(p5.SoundLoop.prototype, 'iterations', { + get: function () { + return this.clock.ticks; + } + }); + return p5.SoundLoop; +}(master, Tone_core_Clock); +var compressor; +compressor = function () { + 'use strict'; + var p5sound = master; + var Effect = effect; + var CustomError = errorHandler; + /** + * Compressor is an audio effect class that performs dynamics compression + * on an audio input source. This is a very commonly used technique in music + * and sound production. Compression creates an overall louder, richer, + * and fuller sound by lowering the volume of louds and raising that of softs. + * Compression can be used to avoid clipping (sound distortion due to + * peaks in volume) and is especially useful when many sounds are played + * at once. Compression can be used on indivudal sound sources in addition + * to the master output. + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * + * @class p5.Compressor + * @constructor + * @extends p5.Effect + * + * + */ + p5.Compressor = function () { + Effect.call(this); + /** + * The p5.Compressor is built with a <a href="https://www.w3.org/TR/webaudio/#the-dynamicscompressornode-interface" + * target="_blank" title="W3 spec for Dynamics Compressor Node">Web Audio Dynamics Compressor Node + * </a> + * @property {AudioNode} compressor + */ + this.compressor = this.ac.createDynamicsCompressor(); + this.input.connect(this.compressor); + this.compressor.connect(this.wet); + }; + p5.Compressor.prototype = Object.create(Effect.prototype); + /** + * Performs the same function as .connect, but also accepts + * optional parameters to set compressor's audioParams + * @method process + * + * @param {Object} src Sound source to be connected + * + * @param {Number} [attack] The amount of time (in seconds) to reduce the gain by 10dB, + * default = .003, range 0 - 1 + * @param {Number} [knee] A decibel value representing the range above the + * threshold where the curve smoothly transitions to the "ratio" portion. + * default = 30, range 0 - 40 + * @param {Number} [ratio] The amount of dB change in input for a 1 dB change in output + * default = 12, range 1 - 20 + * @param {Number} [threshold] The decibel value above which the compression will start taking effect + * default = -24, range -100 - 0 + * @param {Number} [release] The amount of time (in seconds) to increase the gain by 10dB + * default = .25, range 0 - 1 + */ + p5.Compressor.prototype.process = function (src, attack, knee, ratio, threshold, release) { + src.connect(this.input); + this.set(attack, knee, ratio, threshold, release); + }; + /** + * Set the paramters of a compressor. + * @method set + * @param {Number} attack The amount of time (in seconds) to reduce the gain by 10dB, + * default = .003, range 0 - 1 + * @param {Number} knee A decibel value representing the range above the + * threshold where the curve smoothly transitions to the "ratio" portion. + * default = 30, range 0 - 40 + * @param {Number} ratio The amount of dB change in input for a 1 dB change in output + * default = 12, range 1 - 20 + * @param {Number} threshold The decibel value above which the compression will start taking effect + * default = -24, range -100 - 0 + * @param {Number} release The amount of time (in seconds) to increase the gain by 10dB + * default = .25, range 0 - 1 + */ + p5.Compressor.prototype.set = function (attack, knee, ratio, threshold, release) { + if (typeof attack !== 'undefined') { + this.attack(attack); + } + if (typeof knee !== 'undefined') { + this.knee(knee); + } + if (typeof ratio !== 'undefined') { + this.ratio(ratio); + } + if (typeof threshold !== 'undefined') { + this.threshold(threshold); + } + if (typeof release !== 'undefined') { + this.release(release); + } + }; + /** + * Get current attack or set value w/ time ramp + * + * + * @method attack + * @param {Number} [attack] Attack is the amount of time (in seconds) to reduce the gain by 10dB, + * default = .003, range 0 - 1 + * @param {Number} [time] Assign time value to schedule the change in value + */ + p5.Compressor.prototype.attack = function (attack, time) { + var t = time || 0; + if (typeof attack == 'number') { + this.compressor.attack.value = attack; + this.compressor.attack.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.compressor.attack.linearRampToValueAtTime(attack, this.ac.currentTime + 0.02 + t); + } else if (typeof attack !== 'undefined') { + attack.connect(this.compressor.attack); + } + return this.compressor.attack.value; + }; + /** + * Get current knee or set value w/ time ramp + * + * @method knee + * @param {Number} [knee] A decibel value representing the range above the + * threshold where the curve smoothly transitions to the "ratio" portion. + * default = 30, range 0 - 40 + * @param {Number} [time] Assign time value to schedule the change in value + */ + p5.Compressor.prototype.knee = function (knee, time) { + var t = time || 0; + if (typeof knee == 'number') { + this.compressor.knee.value = knee; + this.compressor.knee.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.compressor.knee.linearRampToValueAtTime(knee, this.ac.currentTime + 0.02 + t); + } else if (typeof knee !== 'undefined') { + knee.connect(this.compressor.knee); + } + return this.compressor.knee.value; + }; + /** + * Get current ratio or set value w/ time ramp + * @method ratio + * + * @param {Number} [ratio] The amount of dB change in input for a 1 dB change in output + * default = 12, range 1 - 20 + * @param {Number} [time] Assign time value to schedule the change in value + */ + p5.Compressor.prototype.ratio = function (ratio, time) { + var t = time || 0; + if (typeof ratio == 'number') { + this.compressor.ratio.value = ratio; + this.compressor.ratio.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.compressor.ratio.linearRampToValueAtTime(ratio, this.ac.currentTime + 0.02 + t); + } else if (typeof ratio !== 'undefined') { + ratio.connect(this.compressor.ratio); + } + return this.compressor.ratio.value; + }; + /** + * Get current threshold or set value w/ time ramp + * @method threshold + * + * @param {Number} threshold The decibel value above which the compression will start taking effect + * default = -24, range -100 - 0 + * @param {Number} [time] Assign time value to schedule the change in value + */ + p5.Compressor.prototype.threshold = function (threshold, time) { + var t = time || 0; + if (typeof threshold == 'number') { + this.compressor.threshold.value = threshold; + this.compressor.threshold.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.compressor.threshold.linearRampToValueAtTime(threshold, this.ac.currentTime + 0.02 + t); + } else if (typeof threshold !== 'undefined') { + threshold.connect(this.compressor.threshold); + } + return this.compressor.threshold.value; + }; + /** + * Get current release or set value w/ time ramp + * @method release + * + * @param {Number} release The amount of time (in seconds) to increase the gain by 10dB + * default = .25, range 0 - 1 + * + * @param {Number} [time] Assign time value to schedule the change in value + */ + p5.Compressor.prototype.release = function (release, time) { + var t = time || 0; + if (typeof release == 'number') { + this.compressor.release.value = release; + this.compressor.release.cancelScheduledValues(this.ac.currentTime + 0.01 + t); + this.compressor.release.linearRampToValueAtTime(release, this.ac.currentTime + 0.02 + t); + } else if (typeof number !== 'undefined') { + release.connect(this.compressor.release); + } + return this.compressor.release.value; + }; + /** + * Return the current reduction value + * + * @method reduction + * @return {Number} Value of the amount of gain reduction that is applied to the signal + */ + p5.Compressor.prototype.reduction = function () { + return this.compressor.reduction.value; + }; + p5.Compressor.prototype.dispose = function () { + Effect.prototype.dispose.apply(this); + if (this.compressor) { + this.compressor.disconnect(); + delete this.compressor; + } + }; + return p5.Compressor; +}(master, effect, errorHandler); +var soundRecorder; +'use strict'; +soundRecorder = function () { + // inspiration: recorder.js, Tone.js & typedarray.org + var p5sound = master; + var convertToWav = helpers.convertToWav; + var ac = p5sound.audiocontext; + /** + * <p>Record sounds for playback and/or to save as a .wav file. + * The p5.SoundRecorder records all sound output from your sketch, + * or can be assigned a specific source with setInput().</p> + * <p>The record() method accepts a p5.SoundFile as a parameter. + * When playback is stopped (either after the given amount of time, + * or with the stop() method), the p5.SoundRecorder will send its + * recording to that p5.SoundFile for playback.</p> + * + * @class p5.SoundRecorder + * @constructor + * @example + * <div><code> + * var mic, recorder, soundFile; + * var state = 0; + * + * function setup() { + * background(200); + * // create an audio in + * mic = new p5.AudioIn(); + * + * // prompts user to enable their browser mic + * mic.start(); + * + * // create a sound recorder + * recorder = new p5.SoundRecorder(); + * + * // connect the mic to the recorder + * recorder.setInput(mic); + * + * // this sound file will be used to + * // playback & save the recording + * soundFile = new p5.SoundFile(); + * + * text('keyPress to record', 20, 20); + * } + * + * function keyPressed() { + * // make sure user enabled the mic + * if (state === 0 && mic.enabled) { + * + * // record to our p5.SoundFile + * recorder.record(soundFile); + * + * background(255,0,0); + * text('Recording!', 20, 20); + * state++; + * } + * else if (state === 1) { + * background(0,255,0); + * + * // stop recorder and + * // send result to soundFile + * recorder.stop(); + * + * text('Stopped', 20, 20); + * state++; + * } + * + * else if (state === 2) { + * soundFile.play(); // play the result! + * save(soundFile, 'mySound.wav'); + * state++; + * } + * } + * </div></code> + */ + p5.SoundRecorder = function () { + this.input = ac.createGain(); + this.output = ac.createGain(); + this.recording = false; + this.bufferSize = 1024; + this._channels = 2; + // stereo (default) + this._clear(); + // initialize variables + this._jsNode = ac.createScriptProcessor(this.bufferSize, this._channels, 2); + this._jsNode.onaudioprocess = this._audioprocess.bind(this); + /** + * callback invoked when the recording is over + * @private + * @type Function(Float32Array) + */ + this._callback = function () { + }; + // connections + this._jsNode.connect(p5.soundOut._silentNode); + this.setInput(); + // add this p5.SoundFile to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connect a specific device to the p5.SoundRecorder. + * If no parameter is given, p5.SoundRecorer will record + * all audible p5.sound from your sketch. + * + * @method setInput + * @param {Object} [unit] p5.sound object or a web audio unit + * that outputs sound + */ + p5.SoundRecorder.prototype.setInput = function (unit) { + this.input.disconnect(); + this.input = null; + this.input = ac.createGain(); + this.input.connect(this._jsNode); + this.input.connect(this.output); + if (unit) { + unit.connect(this.input); + } else { + p5.soundOut.output.connect(this.input); + } + }; + /** + * Start recording. To access the recording, provide + * a p5.SoundFile as the first parameter. The p5.SoundRecorder + * will send its recording to that p5.SoundFile for playback once + * recording is complete. Optional parameters include duration + * (in seconds) of the recording, and a callback function that + * will be called once the complete recording has been + * transfered to the p5.SoundFile. + * + * @method record + * @param {p5.SoundFile} soundFile p5.SoundFile + * @param {Number} [duration] Time (in seconds) + * @param {Function} [callback] The name of a function that will be + * called once the recording completes + */ + p5.SoundRecorder.prototype.record = function (sFile, duration, callback) { + this.recording = true; + if (duration) { + this.sampleLimit = Math.round(duration * ac.sampleRate); + } + if (sFile && callback) { + this._callback = function () { + this.buffer = this._getBuffer(); + sFile.setBuffer(this.buffer); + callback(); + }; + } else if (sFile) { + this._callback = function () { + this.buffer = this._getBuffer(); + sFile.setBuffer(this.buffer); + }; + } + }; + /** + * Stop the recording. Once the recording is stopped, + * the results will be sent to the p5.SoundFile that + * was given on .record(), and if a callback function + * was provided on record, that function will be called. + * + * @method stop + */ + p5.SoundRecorder.prototype.stop = function () { + this.recording = false; + this._callback(); + this._clear(); + }; + p5.SoundRecorder.prototype._clear = function () { + this._leftBuffers = []; + this._rightBuffers = []; + this.recordedSamples = 0; + this.sampleLimit = null; + }; + /** + * internal method called on audio process + * + * @private + * @param {AudioProcessorEvent} event + */ + p5.SoundRecorder.prototype._audioprocess = function (event) { + if (this.recording === false) { + return; + } else if (this.recording === true) { + // if we are past the duration, then stop... else: + if (this.sampleLimit && this.recordedSamples >= this.sampleLimit) { + this.stop(); + } else { + // get channel data + var left = event.inputBuffer.getChannelData(0); + var right = event.inputBuffer.getChannelData(1); + // clone the samples + this._leftBuffers.push(new Float32Array(left)); + this._rightBuffers.push(new Float32Array(right)); + this.recordedSamples += this.bufferSize; + } + } + }; + p5.SoundRecorder.prototype._getBuffer = function () { + var buffers = []; + buffers.push(this._mergeBuffers(this._leftBuffers)); + buffers.push(this._mergeBuffers(this._rightBuffers)); + return buffers; + }; + p5.SoundRecorder.prototype._mergeBuffers = function (channelBuffer) { + var result = new Float32Array(this.recordedSamples); + var offset = 0; + var lng = channelBuffer.length; + for (var i = 0; i < lng; i++) { + var buffer = channelBuffer[i]; + result.set(buffer, offset); + offset += buffer.length; + } + return result; + }; + p5.SoundRecorder.prototype.dispose = function () { + this._clear(); + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + this._callback = function () { + }; + if (this.input) { + this.input.disconnect(); + } + this.input = null; + this._jsNode = null; + }; + /** + * Save a p5.SoundFile as a .wav file. The browser will prompt the user + * to download the file to their device. + * For uploading audio to a server, use + * <a href="/docs/reference/#/p5.SoundFile/saveBlob">`p5.SoundFile.saveBlob`</a>. + * + * @for p5 + * @method saveSound + * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save + * @param {String} fileName name of the resulting .wav file. + */ + // add to p5.prototype as this is used by the p5 `save()` method. + p5.prototype.saveSound = function (soundFile, fileName) { + const dataView = convertToWav(soundFile.buffer); + p5.prototype.writeFile([dataView], fileName, 'wav'); + }; +}(master, helpers); +var peakdetect; +'use strict'; +peakdetect = function () { + /** + * <p>PeakDetect works in conjunction with p5.FFT to + * look for onsets in some or all of the frequency spectrum. + * </p> + * <p> + * To use p5.PeakDetect, call <code>update</code> in the draw loop + * and pass in a p5.FFT object. + * </p> + * <p> + * You can listen for a specific part of the frequency spectrum by + * setting the range between <code>freq1</code> and <code>freq2</code>. + * </p> + * + * <p><code>threshold</code> is the threshold for detecting a peak, + * scaled between 0 and 1. It is logarithmic, so 0.1 is half as loud + * as 1.0.</p> + * + * <p> + * The update method is meant to be run in the draw loop, and + * <b>frames</b> determines how many loops must pass before + * another peak can be detected. + * For example, if the frameRate() = 60, you could detect the beat of a + * 120 beat-per-minute song with this equation: + * <code> framesPerPeak = 60 / (estimatedBPM / 60 );</code> + * </p> + * + * <p> + * Based on example contribtued by @b2renger, and a simple beat detection + * explanation by <a + * href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/" + * target="_blank">Felix Turner</a>. + * </p> + * + * @class p5.PeakDetect + * @constructor + * @param {Number} [freq1] lowFrequency - defaults to 20Hz + * @param {Number} [freq2] highFrequency - defaults to 20000 Hz + * @param {Number} [threshold] Threshold for detecting a beat between 0 and 1 + * scaled logarithmically where 0.1 is 1/2 the loudness + * of 1.0. Defaults to 0.35. + * @param {Number} [framesPerPeak] Defaults to 20. + * @example + * <div><code> + * + * var cnv, soundFile, fft, peakDetect; + * var ellipseWidth = 10; + * + * function preload() { + * soundFile = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * background(0); + * noStroke(); + * fill(255); + * textAlign(CENTER); + * + * // p5.PeakDetect requires a p5.FFT + * fft = new p5.FFT(); + * peakDetect = new p5.PeakDetect(); + * } + * + * function draw() { + * background(0); + * text('click to play/pause', width/2, height/2); + * + * // peakDetect accepts an fft post-analysis + * fft.analyze(); + * peakDetect.update(fft); + * + * if ( peakDetect.isDetected ) { + * ellipseWidth = 50; + * } else { + * ellipseWidth *= 0.95; + * } + * + * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); + * } + * + * // toggle play/stop when canvas is clicked + * function mouseClicked() { + * if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { + * if (soundFile.isPlaying() ) { + * soundFile.stop(); + * } else { + * soundFile.play(); + * } + * } + * } + * </code></div> + */ + p5.PeakDetect = function (freq1, freq2, threshold, _framesPerPeak) { + // framesPerPeak determines how often to look for a beat. + // If a beat is provided, try to look for a beat based on bpm + this.framesPerPeak = _framesPerPeak || 20; + this.framesSinceLastPeak = 0; + this.decayRate = 0.95; + this.threshold = threshold || 0.35; + this.cutoff = 0; + // how much to increase the cutoff + // TO DO: document this / figure out how to make it accessible + this.cutoffMult = 1.5; + this.energy = 0; + this.penergy = 0; + // TO DO: document this property / figure out how to make it accessible + this.currentValue = 0; + /** + * isDetected is set to true when a peak is detected. + * + * @attribute isDetected {Boolean} + * @default false + */ + this.isDetected = false; + this.f1 = freq1 || 40; + this.f2 = freq2 || 20000; + // function to call when a peak is detected + this._onPeak = function () { + }; + }; + /** + * The update method is run in the draw loop. + * + * Accepts an FFT object. You must call .analyze() + * on the FFT object prior to updating the peakDetect + * because it relies on a completed FFT analysis. + * + * @method update + * @param {p5.FFT} fftObject A p5.FFT object + */ + p5.PeakDetect.prototype.update = function (fftObject) { + var nrg = this.energy = fftObject.getEnergy(this.f1, this.f2) / 255; + if (nrg > this.cutoff && nrg > this.threshold && nrg - this.penergy > 0) { + // trigger callback + this._onPeak(); + this.isDetected = true; + // debounce + this.cutoff = nrg * this.cutoffMult; + this.framesSinceLastPeak = 0; + } else { + this.isDetected = false; + if (this.framesSinceLastPeak <= this.framesPerPeak) { + this.framesSinceLastPeak++; + } else { + this.cutoff *= this.decayRate; + this.cutoff = Math.max(this.cutoff, this.threshold); + } + } + this.currentValue = nrg; + this.penergy = nrg; + }; + /** + * onPeak accepts two arguments: a function to call when + * a peak is detected. The value of the peak, + * between 0.0 and 1.0, is passed to the callback. + * + * @method onPeak + * @param {Function} callback Name of a function that will + * be called when a peak is + * detected. + * @param {Object} [val] Optional value to pass + * into the function when + * a peak is detected. + * @example + * <div><code> + * var cnv, soundFile, fft, peakDetect; + * var ellipseWidth = 0; + * + * function preload() { + * soundFile = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * cnv = createCanvas(100,100); + * textAlign(CENTER); + * + * fft = new p5.FFT(); + * peakDetect = new p5.PeakDetect(); + * + * setupSound(); + * + * // when a beat is detected, call triggerBeat() + * peakDetect.onPeak(triggerBeat); + * } + * + * function draw() { + * background(0); + * fill(255); + * text('click to play', width/2, height/2); + * + * fft.analyze(); + * peakDetect.update(fft); + * + * ellipseWidth *= 0.95; + * ellipse(width/2, height/2, ellipseWidth, ellipseWidth); + * } + * + * // this function is called by peakDetect.onPeak + * function triggerBeat() { + * ellipseWidth = 50; + * } + * + * // mouseclick starts/stops sound + * function setupSound() { + * cnv.mouseClicked( function() { + * if (soundFile.isPlaying() ) { + * soundFile.stop(); + * } else { + * soundFile.play(); + * } + * }); + * } + * </code></div> + */ + p5.PeakDetect.prototype.onPeak = function (callback, val) { + var self = this; + self._onPeak = function () { + callback(self.energy, val); + }; + }; +}(); +var gain; +'use strict'; +gain = function () { + var p5sound = master; + /** + * A gain node is usefull to set the relative volume of sound. + * It's typically used to build mixers. + * + * @class p5.Gain + * @constructor + * @example + * <div><code> + * + * // load two soundfile and crossfade beetween them + * var sound1,sound2; + * var gain1, gain2, gain3; + * + * function preload(){ + * soundFormats('ogg', 'mp3'); + * sound1 = loadSound('assets/Damscray_-_Dancing_Tiger_01'); + * sound2 = loadSound('assets/beat.mp3'); + * } + * + * function setup() { + * createCanvas(400,200); + * + * // create a 'master' gain to which we will connect both soundfiles + * gain3 = new p5.Gain(); + * gain3.connect(); + * + * // setup first sound for playing + * sound1.rate(1); + * sound1.loop(); + * sound1.disconnect(); // diconnect from p5 output + * + * gain1 = new p5.Gain(); // setup a gain node + * gain1.setInput(sound1); // connect the first sound to its input + * gain1.connect(gain3); // connect its output to the 'master' + * + * sound2.rate(1); + * sound2.disconnect(); + * sound2.loop(); + * + * gain2 = new p5.Gain(); + * gain2.setInput(sound2); + * gain2.connect(gain3); + * + * } + * + * function draw(){ + * background(180); + * + * // calculate the horizontal distance beetween the mouse and the right of the screen + * var d = dist(mouseX,0,width,0); + * + * // map the horizontal position of the mouse to values useable for volume control of sound1 + * var vol1 = map(mouseX,0,width,0,1); + * var vol2 = 1-vol1; // when sound1 is loud, sound2 is quiet and vice versa + * + * gain1.amp(vol1,0.5,0); + * gain2.amp(vol2,0.5,0); + * + * // map the vertical position of the mouse to values useable for 'master volume control' + * var vol3 = map(mouseY,0,height,0,1); + * gain3.amp(vol3,0.5,0); + * } + *</code></div> + * + */ + p5.Gain = function () { + this.ac = p5sound.audiocontext; + this.input = this.ac.createGain(); + this.output = this.ac.createGain(); + // otherwise, Safari distorts + this.input.gain.value = 0.5; + this.input.connect(this.output); + // add to the soundArray + p5sound.soundArray.push(this); + }; + /** + * Connect a source to the gain node. + * + * @method setInput + * @param {Object} src p5.sound / Web Audio object with a sound + * output. + */ + p5.Gain.prototype.setInput = function (src) { + src.connect(this.input); + }; + /** + * Send output to a p5.sound or web audio object + * + * @method connect + * @param {Object} unit + */ + p5.Gain.prototype.connect = function (unit) { + var u = unit || p5.soundOut.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all output. + * + * @method disconnect + */ + p5.Gain.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + /** + * Set the output level of the gain node. + * + * @method amp + * @param {Number} volume amplitude between 0 and 1.0 + * @param {Number} [rampTime] create a fade that lasts rampTime + * @param {Number} [timeFromNow] schedule this event to happen + * seconds from now + */ + p5.Gain.prototype.amp = function (vol, rampTime, tFromNow) { + var rampTime = rampTime || 0; + var tFromNow = tFromNow || 0; + var now = p5sound.audiocontext.currentTime; + var currentVol = this.output.gain.value; + this.output.gain.cancelScheduledValues(now); + this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); + this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); + }; + p5.Gain.prototype.dispose = function () { + // remove reference from soundArray + var index = p5sound.soundArray.indexOf(this); + p5sound.soundArray.splice(index, 1); + if (this.output) { + this.output.disconnect(); + delete this.output; + } + if (this.input) { + this.input.disconnect(); + delete this.input; + } + }; +}(master); +var audioVoice; +'use strict'; +audioVoice = function () { + var p5sound = master; + /** + * Base class for monophonic synthesizers. Any extensions of this class + * should follow the API and implement the methods below in order to + * remain compatible with p5.PolySynth(); + * + * @class p5.AudioVoice + * @constructor + */ + p5.AudioVoice = function () { + this.ac = p5sound.audiocontext; + this.output = this.ac.createGain(); + this.connect(); + p5sound.soundArray.push(this); + }; + p5.AudioVoice.prototype.play = function (note, velocity, secondsFromNow, sustime) { + }; + p5.AudioVoice.prototype.triggerAttack = function (note, velocity, secondsFromNow) { + }; + p5.AudioVoice.prototype.triggerRelease = function (secondsFromNow) { + }; + p5.AudioVoice.prototype.amp = function (vol, rampTime) { + }; + /** + * Connect to p5 objects or Web Audio Nodes + * @method connect + * @param {Object} unit + */ + p5.AudioVoice.prototype.connect = function (unit) { + var u = unit || p5sound.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect from soundOut + * @method disconnect + */ + p5.AudioVoice.prototype.disconnect = function () { + this.output.disconnect(); + }; + p5.AudioVoice.prototype.dispose = function () { + if (this.output) { + this.output.disconnect(); + delete this.output; + } + }; + return p5.AudioVoice; +}(master); +var monosynth; +'use strict'; +monosynth = function () { + var p5sound = master; + var AudioVoice = audioVoice; + var noteToFreq = helpers.noteToFreq; + var DEFAULT_SUSTAIN = 0.15; + /** + * A MonoSynth is used as a single voice for sound synthesis. + * This is a class to be used in conjunction with the PolySynth + * class. Custom synthetisers should be built inheriting from + * this class. + * + * @class p5.MonoSynth + * @constructor + * @example + * <div><code> + * var monoSynth; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * cnv.mousePressed(playSynth); + * + * monoSynth = new p5.MonoSynth(); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * } + * + * function playSynth() { + * // time from now (in seconds) + * var time = 0; + * // note duration (in seconds) + * var dur = 0.25; + * // velocity (volume, from 0 to 1) + * var v = 0.2; + * + * monoSynth.play("G3", v, time, dur); + * monoSynth.play("C4", v, time += dur, dur); + * + * background(random(255), random(255), 255); + * text('click to play', width/2, height/2); + * } + * </code></div> + **/ + p5.MonoSynth = function () { + AudioVoice.call(this); + this.oscillator = new p5.Oscillator(); + this.env = new p5.Envelope(); + this.env.setRange(1, 0); + this.env.setExp(true); + //set params + this.setADSR(0.02, 0.25, 0.05, 0.35); + // oscillator --> env --> this.output (gain) --> p5.soundOut + this.oscillator.disconnect(); + this.oscillator.connect(this.output); + this.env.disconnect(); + this.env.setInput(this.output.gain); + // reset oscillator gain to 1.0 + this.oscillator.output.gain.value = 1; + this.oscillator.start(); + this.connect(); + p5sound.soundArray.push(this); + }; + p5.MonoSynth.prototype = Object.create(p5.AudioVoice.prototype); + /** + * Play tells the MonoSynth to start playing a note. This method schedules + * the calling of .triggerAttack and .triggerRelease. + * + * @method play + * @param {String | Number} note the note you want to play, specified as a + * frequency in Hertz (Number) or as a midi + * value in Note/Octave format ("C4", "Eb3"...etc") + * See <a href = "https://github.com/Tonejs/Tone.js/wiki/Instruments"> + * Tone</a>. Defaults to 440 hz. + * @param {Number} [velocity] velocity of the note to play (ranging from 0 to 1) + * @param {Number} [secondsFromNow] time from now (in seconds) at which to play + * @param {Number} [sustainTime] time to sustain before releasing the envelope + * @example + * <div><code> + * var monoSynth; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * cnv.mousePressed(playSynth); + * + * monoSynth = new p5.MonoSynth(); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * } + * + * function playSynth() { + * // time from now (in seconds) + * var time = 0; + * // note duration (in seconds) + * var dur = 1/6; + * // note velocity (volume, from 0 to 1) + * var v = random(); + * + * monoSynth.play("Fb3", v, 0, dur); + * monoSynth.play("Gb3", v, time += dur, dur); + * + * background(random(255), random(255), 255); + * text('click to play', width/2, height/2); + * } + * </code></div> + * + */ + p5.MonoSynth.prototype.play = function (note, velocity, secondsFromNow, susTime) { + this.triggerAttack(note, velocity, ~~secondsFromNow); + this.triggerRelease(~~secondsFromNow + (susTime || DEFAULT_SUSTAIN)); + }; + /** + * Trigger the Attack, and Decay portion of the Envelope. + * Similar to holding down a key on a piano, but it will + * hold the sustain level until you let go. + * + * @param {String | Number} note the note you want to play, specified as a + * frequency in Hertz (Number) or as a midi + * value in Note/Octave format ("C4", "Eb3"...etc") + * See <a href = "https://github.com/Tonejs/Tone.js/wiki/Instruments"> + * Tone</a>. Defaults to 440 hz + * @param {Number} [velocity] velocity of the note to play (ranging from 0 to 1) + * @param {Number} [secondsFromNow] time from now (in seconds) at which to play + * @method triggerAttack + * @example + * <div><code> + * var monoSynth = new p5.MonoSynth(); + * + * function mousePressed() { + * monoSynth.triggerAttack("E3"); + * } + * + * function mouseReleased() { + * monoSynth.triggerRelease(); + * } + * </code></div> + */ + p5.MonoSynth.prototype.triggerAttack = function (note, velocity, secondsFromNow) { + var secondsFromNow = ~~secondsFromNow; + var freq = noteToFreq(note); + var vel = velocity || 0.1; + this.oscillator.freq(freq, 0, secondsFromNow); + this.env.ramp(this.output.gain, secondsFromNow, vel); + }; + /** + * Trigger the release of the Envelope. This is similar to releasing + * the key on a piano and letting the sound fade according to the + * release level and release time. + * + * @param {Number} secondsFromNow time to trigger the release + * @method triggerRelease + * @example + * <div><code> + * var monoSynth = new p5.MonoSynth(); + * + * function mousePressed() { + * monoSynth.triggerAttack("E3"); + * } + * + * function mouseReleased() { + * monoSynth.triggerRelease(); + * } + * </code></div> + */ + p5.MonoSynth.prototype.triggerRelease = function (secondsFromNow) { + var secondsFromNow = secondsFromNow || 0; + this.env.ramp(this.output.gain, secondsFromNow, 0); + }; + /** + * Set values like a traditional + * <a href="https://en.wikipedia.org/wiki/Synthesizer#/media/File:ADSR_parameter.svg"> + * ADSR envelope + * </a>. + * + * @method setADSR + * @param {Number} attackTime Time (in seconds before envelope + * reaches Attack Level + * @param {Number} [decayTime] Time (in seconds) before envelope + * reaches Decay/Sustain Level + * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, + * where 1.0 = attackLevel, 0.0 = releaseLevel. + * The susRatio determines the decayLevel and the level at which the + * sustain portion of the envelope will sustain. + * For example, if attackLevel is 0.4, releaseLevel is 0, + * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is + * increased to 1.0 (using <code>setRange</code>), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + */ + p5.MonoSynth.prototype.setADSR = function (attack, decay, sustain, release) { + this.env.setADSR(attack, decay, sustain, release); + }; + /** + * Getters and Setters + * @property {Number} attack + */ + /** + * @property {Number} decay + */ + /** + * @property {Number} sustain + */ + /** + * @property {Number} release + */ + Object.defineProperties(p5.MonoSynth.prototype, { + 'attack': { + get: function () { + return this.env.aTime; + }, + set: function (attack) { + this.env.setADSR(attack, this.env.dTime, this.env.sPercent, this.env.rTime); + } + }, + 'decay': { + get: function () { + return this.env.dTime; + }, + set: function (decay) { + this.env.setADSR(this.env.aTime, decay, this.env.sPercent, this.env.rTime); + } + }, + 'sustain': { + get: function () { + return this.env.sPercent; + }, + set: function (sustain) { + this.env.setADSR(this.env.aTime, this.env.dTime, sustain, this.env.rTime); + } + }, + 'release': { + get: function () { + return this.env.rTime; + }, + set: function (release) { + this.env.setADSR(this.env.aTime, this.env.dTime, this.env.sPercent, release); + } + } + }); + /** + * MonoSynth amp + * @method amp + * @param {Number} vol desired volume + * @param {Number} [rampTime] Time to reach new volume + * @return {Number} new volume value + */ + p5.MonoSynth.prototype.amp = function (vol, rampTime) { + var t = rampTime || 0; + if (typeof vol !== 'undefined') { + this.oscillator.amp(vol, t); + } + return this.oscillator.amp().value; + }; + /** + * Connect to a p5.sound / Web Audio object. + * + * @method connect + * @param {Object} unit A p5.sound or Web Audio object + */ + p5.MonoSynth.prototype.connect = function (unit) { + var u = unit || p5sound.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all outputs + * + * @method disconnect + */ + p5.MonoSynth.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + /** + * Get rid of the MonoSynth and free up its resources / memory. + * + * @method dispose + */ + p5.MonoSynth.prototype.dispose = function () { + AudioVoice.prototype.dispose.apply(this); + if (this.env) { + this.env.dispose(); + } + if (this.oscillator) { + this.oscillator.dispose(); + } + }; +}(master, audioVoice, helpers); +var polysynth; +'use strict'; +polysynth = function () { + var p5sound = master; + var TimelineSignal = Tone_signal_TimelineSignal; + var noteToFreq = helpers.noteToFreq; + /** + * An AudioVoice is used as a single voice for sound synthesis. + * The PolySynth class holds an array of AudioVoice, and deals + * with voices allocations, with setting notes to be played, and + * parameters to be set. + * + * @class p5.PolySynth + * @constructor + * + * @param {Number} [synthVoice] A monophonic synth voice inheriting + * the AudioVoice class. Defaults to p5.MonoSynth + * @param {Number} [maxVoices] Number of voices, defaults to 8; + * @example + * <div><code> + * var polySynth; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * cnv.mousePressed(playSynth); + * + * polySynth = new p5.PolySynth(); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * } + * + * function playSynth() { + * // note duration (in seconds) + * var dur = 1.5; + * + * // time from now (in seconds) + * var time = 0; + * + * // velocity (volume, from 0 to 1) + * var vel = 0.1; + * + * // notes can overlap with each other + * polySynth.play("G2", vel, 0, dur); + * polySynth.play("C3", vel, time += 1/3, dur); + * polySynth.play("G3", vel, time += 1/3, dur); + * + * background(random(255), random(255), 255); + * text('click to play', width/2, height/2); + * } + * </code></div> + **/ + p5.PolySynth = function (audioVoice, maxVoices) { + //audiovoices will contain maxVoices many monophonic synths + this.audiovoices = []; + /** + * An object that holds information about which notes have been played and + * which notes are currently being played. New notes are added as keys + * on the fly. While a note has been attacked, but not released, the value of the + * key is the audiovoice which is generating that note. When notes are released, + * the value of the key becomes undefined. + * @property notes + */ + this.notes = {}; + //indices of the most recently used, and least recently used audiovoice + this._newest = 0; + this._oldest = 0; + /** + * A PolySynth must have at least 1 voice, defaults to 8 + * @property polyvalue + */ + this.maxVoices = maxVoices || 8; + /** + * Monosynth that generates the sound for each note that is triggered. The + * p5.PolySynth defaults to using the p5.MonoSynth as its voice. + * @property AudioVoice + */ + this.AudioVoice = audioVoice === undefined ? p5.MonoSynth : audioVoice; + /** + * This value must only change as a note is attacked or released. Due to delay + * and sustain times, Tone.TimelineSignal is required to schedule the change in value. + * @private + * @property {Tone.TimelineSignal} _voicesInUse + */ + this._voicesInUse = new TimelineSignal(0); + this.output = p5sound.audiocontext.createGain(); + this.connect(); + //Construct the appropriate number of audiovoices + this._allocateVoices(); + p5sound.soundArray.push(this); + }; + /** + * Construct the appropriate number of audiovoices + * @private + * @method _allocateVoices + */ + p5.PolySynth.prototype._allocateVoices = function () { + for (var i = 0; i < this.maxVoices; i++) { + this.audiovoices.push(new this.AudioVoice()); + this.audiovoices[i].disconnect(); + this.audiovoices[i].connect(this.output); + } + }; + /** + * Play a note by triggering noteAttack and noteRelease with sustain time + * + * @method play + * @param {Number} [note] midi note to play (ranging from 0 to 127 - 60 being a middle C) + * @param {Number} [velocity] velocity of the note to play (ranging from 0 to 1) + * @param {Number} [secondsFromNow] time from now (in seconds) at which to play + * @param {Number} [sustainTime] time to sustain before releasing the envelope + * @example + * <div><code> + * var polySynth; + * + * function setup() { + * var cnv = createCanvas(100, 100); + * cnv.mousePressed(playSynth); + * + * polySynth = new p5.PolySynth(); + * + * textAlign(CENTER); + * text('click to play', width/2, height/2); + * } + * + * function playSynth() { + * // note duration (in seconds) + * var dur = 0.1; + * + * // time from now (in seconds) + * var time = 0; + * + * // velocity (volume, from 0 to 1) + * var vel = 0.1; + * + * polySynth.play("G2", vel, 0, dur); + * polySynth.play("C3", vel, 0, dur); + * polySynth.play("G3", vel, 0, dur); + * + * background(random(255), random(255), 255); + * text('click to play', width/2, height/2); + * } + * </code></div> + */ + p5.PolySynth.prototype.play = function (note, velocity, secondsFromNow, susTime) { + var susTime = susTime || 1; + this.noteAttack(note, velocity, secondsFromNow); + this.noteRelease(note, secondsFromNow + susTime); + }; + /** + * noteADSR sets the envelope for a specific note that has just been triggered. + * Using this method modifies the envelope of whichever audiovoice is being used + * to play the desired note. The envelope should be reset before noteRelease is called + * in order to prevent the modified envelope from being used on other notes. + * + * @method noteADSR + * @param {Number} [note] Midi note on which ADSR should be set. + * @param {Number} [attackTime] Time (in seconds before envelope + * reaches Attack Level + * @param {Number} [decayTime] Time (in seconds) before envelope + * reaches Decay/Sustain Level + * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, + * where 1.0 = attackLevel, 0.0 = releaseLevel. + * The susRatio determines the decayLevel and the level at which the + * sustain portion of the envelope will sustain. + * For example, if attackLevel is 0.4, releaseLevel is 0, + * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is + * increased to 1.0 (using <code>setRange</code>), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + **/ + p5.PolySynth.prototype.noteADSR = function (note, a, d, s, r, timeFromNow) { + var now = p5sound.audiocontext.currentTime; + var timeFromNow = timeFromNow || 0; + var t = now + timeFromNow; + this.audiovoices[this.notes[note].getValueAtTime(t)].setADSR(a, d, s, r); + }; + /** + * Set the PolySynths global envelope. This method modifies the envelopes of each + * monosynth so that all notes are played with this envelope. + * + * @method setADSR + * @param {Number} [attackTime] Time (in seconds before envelope + * reaches Attack Level + * @param {Number} [decayTime] Time (in seconds) before envelope + * reaches Decay/Sustain Level + * @param {Number} [susRatio] Ratio between attackLevel and releaseLevel, on a scale from 0 to 1, + * where 1.0 = attackLevel, 0.0 = releaseLevel. + * The susRatio determines the decayLevel and the level at which the + * sustain portion of the envelope will sustain. + * For example, if attackLevel is 0.4, releaseLevel is 0, + * and susAmt is 0.5, the decayLevel would be 0.2. If attackLevel is + * increased to 1.0 (using <code>setRange</code>), + * then decayLevel would increase proportionally, to become 0.5. + * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) + **/ + p5.PolySynth.prototype.setADSR = function (a, d, s, r) { + this.audiovoices.forEach(function (voice) { + voice.setADSR(a, d, s, r); + }); + }; + /** + * Trigger the Attack, and Decay portion of a MonoSynth. + * Similar to holding down a key on a piano, but it will + * hold the sustain level until you let go. + * + * @method noteAttack + * @param {Number} [note] midi note on which attack should be triggered. + * @param {Number} [velocity] velocity of the note to play (ranging from 0 to 1)/ + * @param {Number} [secondsFromNow] time from now (in seconds) + * @example + * <div><code> + * var polySynth = new p5.PolySynth(); + * var pitches = ["G", "D", "G", "C"]; + * var octaves = [2, 3, 4]; + * + * function mousePressed() { + * // play a chord: multiple notes at the same time + * for (var i = 0; i < 4; i++) { + * var note = random(pitches) + random(octaves); + * polySynth.noteAttack(note, 0.1); + * } + * } + * + * function mouseReleased() { + * // release all voices + * polySynth.noteRelease(); + * } + * </code></div> + */ + p5.PolySynth.prototype.noteAttack = function (_note, _velocity, secondsFromNow) { + //this value goes to the audiovoices which handle their own scheduling + var secondsFromNow = ~~secondsFromNow; + //this value is used by this._voicesInUse + var acTime = p5sound.audiocontext.currentTime + secondsFromNow; + //Convert note to frequency if necessary. This is because entries into this.notes + //should be based on frequency for the sake of consistency. + var note = noteToFreq(_note); + var velocity = _velocity || 0.1; + var currentVoice; + //Release the note if it is already playing + if (this.notes[note] && this.notes[note].getValueAtTime(acTime) !== null) { + this.noteRelease(note, 0); + } + //Check to see how many voices are in use at the time the note will start + if (this._voicesInUse.getValueAtTime(acTime) < this.maxVoices) { + currentVoice = Math.max(~~this._voicesInUse.getValueAtTime(acTime), 0); + } else { + currentVoice = this._oldest; + var oldestNote = p5.prototype.freqToMidi(this.audiovoices[this._oldest].oscillator.freq().value); + this.noteRelease(oldestNote); + this._oldest = (this._oldest + 1) % (this.maxVoices - 1); + } + //Overrite the entry in the notes object. A note (frequency value) + //corresponds to the index of the audiovoice that is playing it + this.notes[note] = new TimelineSignal(); + this.notes[note].setValueAtTime(currentVoice, acTime); + //Find the scheduled change in this._voicesInUse that will be previous to this new note + //Add 1 and schedule this value at time 't', when this note will start playing + var previousVal = this._voicesInUse._searchBefore(acTime) === null ? 0 : this._voicesInUse._searchBefore(acTime).value; + this._voicesInUse.setValueAtTime(previousVal + 1, acTime); + //Then update all scheduled values that follow to increase by 1 + this._updateAfter(acTime, 1); + this._newest = currentVoice; + //The audiovoice handles the actual scheduling of the note + if (typeof velocity === 'number') { + var maxRange = 1 / this._voicesInUse.getValueAtTime(acTime) * 2; + velocity = velocity > maxRange ? maxRange : velocity; + } + this.audiovoices[currentVoice].triggerAttack(note, velocity, secondsFromNow); + }; + /** + * Private method to ensure accurate values of this._voicesInUse + * Any time a new value is scheduled, it is necessary to increment all subsequent + * scheduledValues after attack, and decrement all subsequent + * scheduledValues after release + * + * @private + * @param {[type]} time [description] + * @param {[type]} value [description] + * @return {[type]} [description] + */ + p5.PolySynth.prototype._updateAfter = function (time, value) { + if (this._voicesInUse._searchAfter(time) === null) { + return; + } else { + this._voicesInUse._searchAfter(time).value += value; + var nextTime = this._voicesInUse._searchAfter(time).time; + this._updateAfter(nextTime, value); + } + }; + /** + * Trigger the Release of an AudioVoice note. This is similar to releasing + * the key on a piano and letting the sound fade according to the + * release level and release time. + * + * @method noteRelease + * @param {Number} [note] midi note on which attack should be triggered. + * If no value is provided, all notes will be released. + * @param {Number} [secondsFromNow] time to trigger the release + * @example + * <div><code> + * var pitches = ["G", "D", "G", "C"]; + * var octaves = [2, 3, 4]; + * var polySynth = new p5.PolySynth(); + * + * function mousePressed() { + * // play a chord: multiple notes at the same time + * for (var i = 0; i < 4; i++) { + * var note = random(pitches) + random(octaves); + * polySynth.noteAttack(note, 0.1); + * } + * } + * + * function mouseReleased() { + * // release all voices + * polySynth.noteRelease(); + * } + * </code></div> + * + */ + p5.PolySynth.prototype.noteRelease = function (_note, secondsFromNow) { + var now = p5sound.audiocontext.currentTime; + var tFromNow = secondsFromNow || 0; + var t = now + tFromNow; + // if a note value is not provided, release all voices + if (!_note) { + this.audiovoices.forEach(function (voice) { + voice.triggerRelease(tFromNow); + }); + this._voicesInUse.setValueAtTime(0, t); + for (var n in this.notes) { + this.notes[n].dispose(); + delete this.notes[n]; + } + return; + } + //Make sure note is in frequency inorder to query the this.notes object + var note = noteToFreq(_note); + if (!this.notes[note] || this.notes[note].getValueAtTime(t) === null) { + console.warn('Cannot release a note that is not already playing'); + } else { + //Find the scheduled change in this._voicesInUse that will be previous to this new note + //subtract 1 and schedule this value at time 't', when this note will stop playing + var previousVal = Math.max(~~this._voicesInUse.getValueAtTime(t).value, 1); + this._voicesInUse.setValueAtTime(previousVal - 1, t); + //Then update all scheduled values that follow to decrease by 1 but never go below 0 + if (previousVal > 0) { + this._updateAfter(t, -1); + } + this.audiovoices[this.notes[note].getValueAtTime(t)].triggerRelease(tFromNow); + this.notes[note].dispose(); + delete this.notes[note]; + this._newest = this._newest === 0 ? 0 : (this._newest - 1) % (this.maxVoices - 1); + } + }; + /** + * Connect to a p5.sound / Web Audio object. + * + * @method connect + * @param {Object} unit A p5.sound or Web Audio object + */ + p5.PolySynth.prototype.connect = function (unit) { + var u = unit || p5sound.input; + this.output.connect(u.input ? u.input : u); + }; + /** + * Disconnect all outputs + * + * @method disconnect + */ + p5.PolySynth.prototype.disconnect = function () { + if (this.output) { + this.output.disconnect(); + } + }; + /** + * Get rid of the MonoSynth and free up its resources / memory. + * + * @method dispose + */ + p5.PolySynth.prototype.dispose = function () { + this.audiovoices.forEach(function (voice) { + voice.dispose(); + }); + if (this.output) { + this.output.disconnect(); + delete this.output; + } + }; +}(master, Tone_signal_TimelineSignal, helpers); +var distortion; +'use strict'; +distortion = function () { + var Effect = effect; + /* + * Adapted from [Kevin Ennis on StackOverflow](http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion) + */ + function makeDistortionCurve(amount) { + var k = typeof amount === 'number' ? amount : 50; + var numSamples = 44100; + var curve = new Float32Array(numSamples); + var deg = Math.PI / 180; + var i = 0; + var x; + for (; i < numSamples; ++i) { + x = i * 2 / numSamples - 1; + curve[i] = (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x)); + } + return curve; + } + /** + * A Distortion effect created with a Waveshaper Node, + * with an approach adapted from + * [Kevin Ennis](http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion) + * + * This class extends <a href = "/reference/#/p5.Effect">p5.Effect</a>. + * Methods <a href = "/reference/#/p5.Effect/amp">amp()</a>, <a href = "/reference/#/p5.Effect/chain">chain()</a>, + * <a href = "/reference/#/p5.Effect/drywet">drywet()</a>, <a href = "/reference/#/p5.Effect/connect">connect()</a>, and + * <a href = "/reference/#/p5.Effect/disconnect">disconnect()</a> are available. + * + * @class p5.Distortion + * @extends p5.Effect + * @constructor + * @param {Number} [amount=0.25] Unbounded distortion amount. + * Normal values range from 0-1. + * @param {String} [oversample='none'] 'none', '2x', or '4x'. + * + */ + p5.Distortion = function (amount, oversample) { + Effect.call(this); + if (typeof amount === 'undefined') { + amount = 0.25; + } + if (typeof amount !== 'number') { + throw new Error('amount must be a number'); + } + if (typeof oversample === 'undefined') { + oversample = '2x'; + } + if (typeof oversample !== 'string') { + throw new Error('oversample must be a String'); + } + var curveAmount = p5.prototype.map(amount, 0, 1, 0, 2000); + /** + * The p5.Distortion is built with a + * <a href="http://www.w3.org/TR/webaudio/#WaveShaperNode"> + * Web Audio WaveShaper Node</a>. + * + * @property {AudioNode} WaveShaperNode + */ + this.waveShaperNode = this.ac.createWaveShaper(); + this.amount = curveAmount; + this.waveShaperNode.curve = makeDistortionCurve(curveAmount); + this.waveShaperNode.oversample = oversample; + this.input.connect(this.waveShaperNode); + this.waveShaperNode.connect(this.wet); + }; + p5.Distortion.prototype = Object.create(Effect.prototype); + /** + * Process a sound source, optionally specify amount and oversample values. + * + * @method process + * @param {Number} [amount=0.25] Unbounded distortion amount. + * Normal values range from 0-1. + * @param {String} [oversample='none'] 'none', '2x', or '4x'. + */ + p5.Distortion.prototype.process = function (src, amount, oversample) { + src.connect(this.input); + this.set(amount, oversample); + }; + /** + * Set the amount and oversample of the waveshaper distortion. + * + * @method set + * @param {Number} [amount=0.25] Unbounded distortion amount. + * Normal values range from 0-1. + * @param {String} [oversample='none'] 'none', '2x', or '4x'. + */ + p5.Distortion.prototype.set = function (amount, oversample) { + if (amount) { + var curveAmount = p5.prototype.map(amount, 0, 1, 0, 2000); + this.amount = curveAmount; + this.waveShaperNode.curve = makeDistortionCurve(curveAmount); + } + if (oversample) { + this.waveShaperNode.oversample = oversample; + } + }; + /** + * Return the distortion amount, typically between 0-1. + * + * @method getAmount + * @return {Number} Unbounded distortion amount. + * Normal values range from 0-1. + */ + p5.Distortion.prototype.getAmount = function () { + return this.amount; + }; + /** + * Return the oversampling. + * + * @method getOversample + * + * @return {String} Oversample can either be 'none', '2x', or '4x'. + */ + p5.Distortion.prototype.getOversample = function () { + return this.waveShaperNode.oversample; + }; + p5.Distortion.prototype.dispose = function () { + Effect.prototype.dispose.apply(this); + if (this.waveShaperNode) { + this.waveShaperNode.disconnect(); + this.waveShaperNode = null; + } + }; +}(effect); +var src_app; +'use strict'; +src_app = function () { + var p5SOUND = master; + return p5SOUND; +}(shims, audiocontext, master, helpers, errorHandler, panner, soundfile, amplitude, fft, signal, oscillator, envelope, pulse, noise, audioin, filter, eq, panner3d, listener3d, delay, reverb, metro, looper, soundloop, compressor, soundRecorder, peakdetect, gain, monosynth, polysynth, distortion, audioVoice, monosynth, polysynth); +})); \ No newline at end of file -- GitLab