PHP Classes

File: p5/addons/p5.sound.js

Recommend this page to a friend!
  Classes of Ghali Ahmed  >  PHP Math Progressions  >  p5/addons/p5.sound.js  >  Download  
File: p5/addons/p5.sound.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP Math Progressions
Calculate math progressions of several types
Author: By
Last change: Update of p5/addons/p5.sound.js
Date: 29 days ago
Size: 205,248 bytes
 

Contents

Class file image Download
/*! p5.sound.js v0.1.7 2015-01-30 */ /** * p5.sound extends p5 with <a href="http://caniuse.com/audio-api" * target="_blank">Web Audio</a> functionality including audio input, * playback, analysis and synthesis. * <br/><br/> * <a href="#/p5.SoundFile"><b>p5.SoundFile</b></a>: Load and play sound files.<br/> * <a href="#/p5.Amplitude"><b>p5.Amplitude</b></a>: Get the current volume of a sound.<br/> * <a href="#/p5.AudioIn"><b>p5.AudioIn</b></a>: Get sound from an input source, typically * a computer microphone.<br/> * <a href="#/p5.FFT"><b>p5.FFT</b></a>: Analyze the frequency of sound. Returns * results from the frequency spectrum or time domain (waveform).<br/> * <a href="#/p5.Oscillator"><b>p5.Oscillator</b></a>: Generate Sine, * Triangle, Square and Sawtooth waveforms. Base class of * <a href="#/p5.Noise">p5.Noise</a> and <a href="#/p5.Pulse">p5.Pulse</a>. * <br/> * <a href="#/p5.Env"><b>p5.Env</b></a>: An Envelope is a series * of fades over time. Often used to control an object's * output gain level as an "ADSR Envelope" (Attack, Decay, * Sustain, Release). Can also modulate other parameters.<br/> * <a href="#/p5.Delay"><b>p5.Delay</b></a>: A delay effect with * parameters for feedback, delayTime, and lowpass filter.<br/> * <a href="#/p5.Filter"><b>p5.Filter</b></a>: Filter the frequency range of a * sound. * <br/> * <a href="#/p5.Reverb"><b>p5.Reverb</b></a>: Add reverb to a sound by specifying * duration and decay. <br/> * <b><a href="#/p5.Convolver">p5.Convolver</a>:</b> Extends * <a href="#/p5.Reverb">p5.Reverb</a> to simulate the sound of real * physical spaces through convolution.<br/> * <b><a href="#/p5.SoundRecorder">p5.SoundRecorder</a></b>: Record sound for playback * / save the .wav file. * <b><a href="#/p5.Phrase">p5.Phrase</a></b>, <b><a href="#/p5.Part">p5.Part</a></b> and * <b><a href="#/p5.Score">p5.Score</a></b>: Compose musical sequences. * <br/><br/> * p5.sound is on <a href="https://github.com/therewasaguy/p5.sound/">GitHub</a>. * Download the latest version * <a href="https://github.com/therewasaguy/p5.sound/blob/master/lib/p5.sound.js">here</a>. * * @module p5.sound * @submodule p5.sound * @for p5.sound * @main */ /** * p5.sound developed by Jason Sigal for the Processing Foundation, Google Summer of Code 2014. The MIT License (MIT). * * http://github.com/therewasaguy/p5.sound * * Some of the many audio libraries & resources that inspire p5.sound: * - TONE.js (c) Yotam Mann, 2014. Licensed under The MIT License (MIT). https://github.com/TONEnoTONE/Tone.js * - buzz.js (c) Jay Salvat, 2013. Licensed under The MIT License (MIT). http://buzz.jaysalvat.com/ * - Boris Smus Web Audio API book, 2013. Licensed under the Apache License http://www.apache.org/licenses/LICENSE-2.0 * - wavesurfer.js https://github.com/katspaugh/wavesurfer.js * - Web Audio Components by Jordan Santell https://github.com/web-audio-components * - Wilm Thoben's Sound library for Processing https://github.com/processing/processing/tree/master/java/libraries/sound * * Web Audio API: http://w3.org/TR/webaudio/ */ var sndcore; sndcore = function () { 'use strict'; /** * Web Audio SHIMS and helper functions to ensure compatability across browsers */ // If window.AudioContext is unimplemented, it will alias to window.webkitAudioContext. window.AudioContext = window.AudioContext || window.webkitAudioContext; // Create the Audio Context var audiocontext = new window.AudioContext(); /** * <p>Returns the Audio Context for this sketch. Useful for users * who would like to dig deeper into the <a target='_blank' href= * 'http://webaudio.github.io/web-audio-api/'>Web Audio API * </a>.</p> * * @method getAudioContext * @return {Object} AudioContext for this sketch */ p5.prototype.getAudioContext = function () { return audiocontext; }; // Polyfills & SHIMS (inspired by tone.js and the AudioContext MonkeyPatch https://github.com/cwilso/AudioContext-MonkeyPatch/ (c) 2013 Chris Wilson, Licensed under the Apache License) // if (typeof audiocontext.createGain !== 'function') { window.audioContext.createGain = window.audioContext.createGainNode; } if (typeof audiocontext.createDelay !== 'function') { window.audioContext.createDelay = window.audioContext.createDelayNode; } if (typeof window.AudioBufferSourceNode.prototype.start !== 'function') { window.AudioBufferSourceNode.prototype.start = window.AudioBufferSourceNode.prototype.noteGrainOn; } if (typeof window.AudioBufferSourceNode.prototype.stop !== 'function') { window.AudioBufferSourceNode.prototype.stop = window.AudioBufferSourceNode.prototype.noteOff; } if (typeof window.OscillatorNode.prototype.start !== 'function') { window.OscillatorNode.prototype.start = window.OscillatorNode.prototype.noteOn; } if (typeof window.OscillatorNode.prototype.stop !== 'function') { window.OscillatorNode.prototype.stop = window.OscillatorNode.prototype.noteOff; } if (!window.AudioContext.prototype.hasOwnProperty('createScriptProcessor')) { window.AudioContext.prototype.createScriptProcessor = window.AudioContext.prototype.createJavaScriptNode; } // Polyfill for AudioIn, also handled by p5.dom createCapture navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; /** * Determine which filetypes are supported (inspired by buzz.js) * The audio element (el) will only be used to test browser support for various audio formats */ var el = document.createElement('audio'); p5.prototype.isSupported = function () { return !!el.canPlayType; }; var isOGGSupported = function () { return !!el.canPlayType && el.canPlayType('audio/ogg; codecs="vorbis"'); }; var isMP3Supported = function () { return !!el.canPlayType && el.canPlayType('audio/mpeg;'); }; var isWAVSupported = function () { return !!el.canPlayType && el.canPlayType('audio/wav; codecs="1"'); }; var isAACSupported = function () { return !!el.canPlayType && (el.canPlayType('audio/x-m4a;') || el.canPlayType('audio/aac;')); }; var isAIFSupported = function () { return !!el.canPlayType && el.canPlayType('audio/x-aiff;'); }; p5.prototype.isFileSupported = function (extension) { switch (extension.toLowerCase()) { case 'mp3': return isMP3Supported(); case 'wav': return isWAVSupported(); case 'ogg': return isOGGSupported(); case 'aac', 'm4a', 'mp4': return isAACSupported(); case 'aif', 'aiff': return isAIFSupported(); default: return false; } }; // if it is iOS, we have to have a user interaction to start Web Audio // http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false; if (iOS) { window.addEventListener('touchstart', function () { // create empty buffer var buffer = audiocontext.createBuffer(1, 1, 22050); var source = audiocontext.createBufferSource(); source.buffer = buffer; // connect to output (your speakers) source.connect(audiocontext.destination); // play the file source.start(0); }, false); } }(); var master; master = function () { 'use strict'; /** * Master contains AudioContext and the master sound output. */ var Master = function () { var audiocontext = p5.prototype.getAudioContext(); this.input = audiocontext.createGain(); this.output = audiocontext.createGain(); //put a hard limiter on the output this.limiter = audiocontext.createDynamicsCompressor(); this.limiter.threshold.value = 0; this.limiter.ratio.value = 100; this.audiocontext = audiocontext; this.output.disconnect(this.audiocontext.destination); // an array of input sources this.inputSources = []; // connect input to limiter this.input.connect(this.limiter); // connect limiter to output this.limiter.connect(this.output); // meter is just for measuring global Amplitude this.meter = audiocontext.createGain(); this.output.connect(this.meter); // 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(); /** * p5.soundOut is the p5.sound master output. It sends output to * the destination of this window's web audio context. It contains * Web Audio API nodes including a dyanmicsCompressor (<code>.limiter</code>), * and Gain Nodes for <code>.input</code> and <code>.output</code>. * * @property p5.soundOut * @type {Object} */ p5.soundOut = p5sound; /** * a silent connection to the DesinationNode * which will ensure that anything connected to it * will not be garbage collected * * @private */ p5.soundOut._silentNode = p5sound.audiocontext.createGain(); p5.soundOut._silentNode.gain.value = 0; p5.soundOut._silentNode.connect(p5sound.audiocontext.destination); return p5sound; }(sndcore); var helpers; helpers = function () { 'use strict'; var p5sound = master; /** * <p>Set the master amplitude (volume) for sound in this sketch.</p> * * <p>Note that values greater than 1.0 may lead to digital distortion.</p> * * <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> * * @method masterVolume * @param {Number} volume Master amplitude (volume) for sound in * this sketch. Should be between 0.0 * (silence) and 1.0. Values greater than * 1.0 may lead to digital distortion. * @example * <div><code> * masterVolume(.5); * </code></div> * */ p5.prototype.masterVolume = function (vol) { p5sound.output.gain.value = vol; }; /** * 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; }; p5.prototype.getMasterVolume = function () { return p5sound.output.gain.value; }; /** * Returns the closest MIDI note value for * a given frequency. * * @param {Number} frequency A freqeuncy, for example, the "A" * above Middle C is 440Hz * @return {Number} MIDI note value */ p5.prototype.freqToMidi = function (f) { var mathlog2 = Math.log(f / 440) / Math.log(2); var m = Math.round(12 * mathlog2) + 57; return m; }; /** * Returns the frequency value of a MIDI note value. * General MIDI treats notes as integers where middle C * is 60, C# is 61, D is 62 etc. Useful for generating * musical frequencies with oscillators. * * @method midiToFreq * @param {Number} midiNote The number of a MIDI note * @return {Number} Frequency value of the given MIDI note * @example * <div><code> * var notes = [60, 64, 67, 72]; * var i = 0; * * function setup() { * osc = new p5.Oscillator('Triangle'); * osc.start(); * frameRate(1); * } * * function draw() { * var freq = midiToFreq(notes[i]); * osc.freq(freq); * i++; * if (i >= notes.length){ * i = 0; * } * } * </code></div> */ p5.prototype.midiToFreq = function (m) { return 440 * Math.pow(2, (m - 69) / 12); }; /** * List the SoundFile formats that you will include. LoadSound * will search your directory for these extensions, and will pick * a format that is compatable with the client's web browser. * <a href="http://media.io/">Here</a> is a free online file * converter. * * @method soundFormats * @param {String|Strings} formats i.e. 'mp3', 'wav', 'ogg' * @example * <div><code> * function preload() { * // set the global sound formats * soundFormats('mp3', 'ogg'); * * // load either beatbox.mp3, or .ogg, depending on browser * mySound = loadSound('../sounds/beatbox.mp3'); * } * * function setup() { * mySound.play(); * } * </code></div> */ p5.prototype.soundFormats = function () { // reset extensions array p5sound.extensions = []; // add extensions for (var i = 0; i < arguments.length; i++) { arguments[i] = arguments[i].toLowerCase(); if ([ 'mp3', 'wav', 'ogg', 'm4a', 'aac' ].indexOf(arguments[i]) > -1) { p5sound.extensions.push(arguments[i]); } else { throw arguments[i] + ' is not a valid sound format!'; } } }; p5.prototype.disposeSound = function () { for (var i = 0; i < p5sound.soundArray.length; i++) { p5sound.soundArray[i].dispose(); } }; // register removeSound to dispose of p5sound SoundFiles, Convolvers, // Oscillators etc when sketch ends p5.prototype.registerMethod('remove', p5.prototype.disposeSound); p5.prototype._checkFileFormats = function (paths) { var path; // if path is a single string, check to see if extension is provided if (typeof paths === 'string') { path = paths; // see if extension is provided var extTest = path.split('.').pop(); // if an extension is provided... if ([ 'mp3', 'wav', 'ogg', 'm4a', 'aac' ].indexOf(extTest) > -1) { var supported = p5.prototype.isFileSupported(extTest); if (supported) { path = path; } else { var pathSplit = path.split('.'); var pathCore = pathSplit[pathSplit.length - 1]; for (var i = 0; i < p5sound.extensions.length; i++) { var extension = p5sound.extensions[i]; var supported = p5.prototype.isFileSupported(extension); if (supported) { pathCore = ''; if (pathSplit.length === 2) { pathCore += pathSplit[0]; } for (var i = 1; i <= pathSplit.length - 2; i++) { var p = pathSplit[i]; pathCore += '.' + p; } path = pathCore += '.'; path = path += extension; break; } } } } else { for (var i = 0; i < p5sound.extensions.length; i++) { var extension = p5sound.extensions[i]; var supported = p5.prototype.isFileSupported(extension); if (supported) { path = path + '.' + extension; break; } } } } else if (typeof paths === 'object') { for (var i = 0; i < paths.length; i++) { var extension = paths[i].split('.').pop(); var supported = p5.prototype.isFileSupported(extension); if (supported) { // console.log('.'+extension + ' is ' + supported + // ' supported by your browser.'); path = paths[i]; break; } } } return path; }; /** * Used by Osc and Env to chain signal math */ p5.prototype._mathChain = function (o, math, thisChain, nextChain, type) { // if this type of math already exists in the chain, replace it for (var i in o.mathOps) { if (o.mathOps[i] instanceof type) { o.mathOps[i].dispose(); thisChain = i; if (thisChain < o.mathOps.length - 1) { nextChain = o.mathOps[i + 1]; } } } o.mathOps[thisChain - 1].disconnect(); o.mathOps[thisChain - 1].connect(math); math.connect(nextChain); o.mathOps[thisChain] = math; return o; }; }(master); var panner; panner = function () { 'use strict'; var p5sound = master; var ac = p5sound.audiocontext; // Stereo panner 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 leftVal = Math.cos(v * Math.PI / 2); var rightVal = Math.sin(v * Math.PI / 2); this.left.gain.linearRampToValueAtTime(leftVal, t); this.right.gain.linearRampToValueAtTime(rightVal, t); }; p5.Panner.prototype.inputChannels = function (numChannels) { if (numChannels === 1) { this.input.disconnect(); this.input.connect(this.left); this.input.connect(this.right); } else if (numChannels === 2) { if (typeof (this.splitter === 'undefined')) { this.splitter = ac.createChannelSplitter(2); } this.input.disconnect(); this.input.connect(this.splitter); this.splitter.connect(this.left, 1); this.splitter.connect(this.right, 0); } }; p5.Panner.prototype.connect = function (obj) { this.output.connect(obj); }; p5.Panner.prototype.disconnect = function (obj) { this.output.disconnect(); }; // 3D panner p5.Panner3D = function (input, output) { var panner3D = ac.createPanner(); panner3D.panningModel = 'HRTF'; panner3D.distanceModel = 'linear'; panner3D.setPosition(0, 0, 0); input.connect(panner3D); panner3D.connect(output); panner3D.pan = function (xVal, yVal, zVal) { panner3D.setPosition(xVal, yVal, zVal); }; return panner3D; }; }(master); var soundfile; soundfile = function () { 'use strict'; var p5sound = master; /** * <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. * @param {Function} [callback] Name of a function to call once file loads * @return {Object} p5.SoundFile Object * @example * <div><code> * function preload() { * mySound = loadSound('assets/drum.mp3'); * } * * function setup() { * mySound.play(); * } * * </code></div> */ p5.SoundFile = function (paths, onload, whileLoading) { var path = p5.prototype._checkFileFormats(paths); // player variables this.url = path; // array of sources so that they can all be stopped! this.sources = []; // current source this.source = null; this.buffer = null; this.playbackRate = 1; this.gain = 1; this.input = p5sound.audiocontext.createGain(); this.output = p5sound.audiocontext.createGain(); this.reversed = false; // start and end of playback / loop this.startTime = 0; this.endTime = null; // playing - defaults to false this.playing = false; // paused - defaults to true this.paused = null; // "restart" would stop playback before retriggering this.mode = 'sustain'; // time that playback was started, in millis this.startMillis = null; this.amplitude = new p5.Amplitude(); this.output.connect(this.amplitude.input); // stereo panning this.panPosition = 0; this.panner = new p5.Panner(this.output, p5sound.input, 2); // it is possible to instantiate a soundfile with no path if (this.url) { this.load(onload); } // add this p5.SoundFile to the soundArray p5sound.soundArray.push(this); if (typeof whileLoading === 'function') { this.whileLoading = whileLoading; } else { this.whileLoading = function () { }; } }; // register preload handling of loadSound p5.prototype.registerPreloadMethod('loadSound'); /** * 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/lmccart/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'] * @param {Function} [callback] Name of a function to call once file loads * @param {Function} [callback] Name of a function to call while file is loading. * This function will receive a percentage from 0.0 * to 1.0. * @return {SoundFile} Returns a p5.SoundFile * @example * <div><code> * function preload() { * mySound = loadSound('assets/drum.mp3'); * } * * function setup() { * mySound.loop(); * } * </code></div> */ p5.prototype.loadSound = function (path, callback, whileLoading) { // if loading locally without a server if (window.location.origin.indexOf('file://') > -1) { alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); } var s = new p5.SoundFile(path, callback, whileLoading); 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} [callback] Name of a function to call once file loads */ p5.SoundFile.prototype.load = function (callback) { var sf = this; var request = new XMLHttpRequest(); request.addEventListener('progress', function (evt) { sf._updateProgress(evt); }, false); request.open('GET', this.url, true); request.responseType = 'arraybuffer'; // decode asyncrohonously var self = this; request.onload = function () { var ac = p5.prototype.getAudioContext(); ac.decodeAudioData(request.response, function (buff) { self.buffer = buff; self.panner.inputChannels(buff.numberOfChannels); if (callback) { callback(self); } }); }; request.send(); }; p5.SoundFile.prototype._updateProgress = function (evt) { if (evt.lengthComputable) { var percentComplete = Math.log(evt.loaded / evt.total * 9.9); this.whileLoading(percentComplete); } else { console.log('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} [cueEnd] (optional) cue end time in seconds */ p5.SoundFile.prototype.play = function (time, rate, amp, startTime, endTime) { var now = p5sound.audiocontext.currentTime; var time = time || 0; if (time < 0) { time = 0; } // var tFromNow = time + now; // TO DO: if already playing, create array of buffers for easy stop() if (this.buffer) { // handle restart playmode if (this.mode === 'restart' && this.buffer && this.source) { var now = p5sound.audiocontext.currentTime; this.source.stop(time); } if (startTime) { if (startTime >= 0 && startTime < this.buffer.duration) { this.startTime = startTime; } else { throw 'start time out of range'; } } if (endTime) { if (endTime >= 0 && endTime <= this.buffer.duration) { this.endTime = endTime; } else { throw 'end time out of range'; } } else { this.endTime = this.buffer.duration; } // make a new source this.source = p5sound.audiocontext.createBufferSource(); this.source.buffer = this.buffer; this.source.loop = this.looping; if (this.source.loop === true) { this.source.loopStart = this.startTime; this.source.loopEnd = this.endTime; } this.source.onended = function () { }; // firefox method of controlling gain without resetting volume if (!this.source.gain) { this.source.gain = p5sound.audiocontext.createGain(); this.source.connect(this.source.gain); // set local amp if provided, otherwise 1 var a = amp || 1; this.source.gain.gain.setValueAtTime(a, p5sound.audiocontext.currentTime); this.source.gain.connect(this.output); } else { this.source.gain.value = amp || 1; this.source.connect(this.output); } this.source.playbackRate.cancelScheduledValues(now); rate = rate || Math.abs(this.playbackRate); this.source.playbackRate.setValueAtTime(rate, now); if (this.paused) { this.wasUnpaused = true; } // play the sound if (this.paused && this.wasUnpaused) { this.source.start(time, this.pauseTime, this.endTime); } else { this.wasUnpaused = false; this.pauseTime = 0; this.source.start(time, this.startTime, this.endTime); } this.startSeconds = time + now; this.playing = true; this.paused = false; // add the source to sources array this.sources.push(this.source); } else { throw 'not ready to play file, buffer has yet to load. Try preload()'; } }; /** * p5.SoundFile has two play modes: <code>restart</code> and * <code>sustain</code>. Play Mode determines what happens to a * p5.SoundFile if it is triggered while in the middle of playback. * In sustain mode, playback will continue simultaneous to the * new playback. In restart mode, play() will stop playback * and start over. Sustain is the default mode. * * @method playMode * @param {String} str 'restart' or 'sustain' * @example * <div><code> * function setup(){ * mySound = loadSound('assets/Damscray_DancingTiger.mp3'); * } * function mouseClicked() { * mySound.playMode('sustain'); * mySound.play(); * } * function keyPressed() { * mySound.playMode('restart'); * mySound.play(); * } * * </code></div> */ p5.SoundFile.prototype.playMode = function (str) { var s = str.toLowerCase(); // if restart, stop all other sounds from playing if (s === 'restart' && this.buffer && this.source) { for (var i = 0; i < this.sources.length - 1; i++) { var now = p5sound.audiocontext.currentTime; this.sources[i].stop(now); } } // set play mode to effect future playback if (s === 'restart' || s === 'sustain') { this.mode = s; } else { throw 'Invalid play mode. Must be either "restart" or "sustain"'; } }; /** * Pauses a file that is currently playing. If the file is not * playing, then nothing will happen. * * After pausing, .play() will resume from the paused * position. * If p5.SoundFile had been set to loop before it was paused, * it will continue to loop after it is unpaused with .play(). * * @method pause * @param {Number} [startTime] (optional) schedule event to occur * seconds from now * @example * <div><code> * var soundFile; * * function preload() { * soundFormats('ogg', 'mp3'); * soundFile = loadSound('../_files/Damscray_-_Dancing_Tiger_02'); * } * function setup() { * background(0, 255, 0); * soundFile.loop(); * } * function keyTyped() { * if (key == 'p') { * soundFile.pause(); * background(255, 0, 0); * } * } * * function keyReleased() { * if (key == 'p') { * soundFile.play(); * background(0, 255, 0); * } */ p5.SoundFile.prototype.pause = function (time) { var now = p5sound.audiocontext.currentTime; var time = time || 0; var pTime = time + now; var keepLoop = this.looping; if (this.isPlaying() && this.buffer && this.source) { this.pauseTime = this.currentTime(); this.source.stop(pTime); this.paused = true; this.wasUnpaused = false; this.playing = false; } }; /** * 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} [cueLoopEnd] (optional) endTime in seconds */ p5.SoundFile.prototype.loop = function (rate, amp, loopStart, loopEnd) { this.looping = true; this.play(rate, amp, loopStart, loopEnd); }; /** * Set a p5.SoundFile's looping flag to true or false. If the sound * is currently playing, this change will take effect when it * reaches the end of the current playback. * * @param {Boolean} Boolean set looping to true or false */ p5.SoundFile.prototype.setLoop = function (bool) { if (bool === true) { this.looping = true; } else if (bool === false) { this.looping = false; } else { throw 'Error: setLoop accepts either true or false'; } if (this.source) { this.source.loop = this.looping; } }; /** * Returns 'true' if a p5.SoundFile is looping, 'false' if not. * * @return {Boolean} */ p5.SoundFile.prototype.isLooping = function () { if (!this.source) { 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 () { if (this.playing !== null) { return this.playing; } else { return false; } }; /** * 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 () { if (!this.paused) { return false; } 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 (time) { if (this.mode == 'sustain') { this.stopAll(); this.playing = false; this.pauseTime = 0; this.wasUnpaused = false; this.paused = false; } else if (this.buffer && this.source) { var now = p5sound.audiocontext.currentTime; var t = time || 0; this.source.stop(now + t); this.playing = false; this.pauseTime = 0; this.wasUnpaused = false; this.paused = false; } }; /** * Stop playback on all of this soundfile's sources. * @private */ p5.SoundFile.prototype.stopAll = function () { if (this.buffer && this.source) { for (var i = 0; i < this.sources.length; i++) { if (this.sources[i] !== null) { var now = p5sound.audiocontext.currentTime; this.sources[i].stop(now); } } } }; /** * Multiply the output volume (amplitude) of a sound file * between 0.0 (silence) and 1.0 (full volume). * 1.0 is the maximum amplitude of a digital sound, so multiplying * by greater than 1.0 may cause digital distortion. To * fade, provide a <code>rampTime</code> parameter. For more * complex fades, see the Env class. * * Alternately, you can pass in a signal source such as an * oscillator to modulate the amplitude with an audio signal. * * @method setVolume * @param {Number|Object} volume Volume (amplitude) between 0.0 * and 1.0 or modulating signal/oscillator * @param {Number} [rampTime] Fade for t seconds * @param {Number} [timeFromNow] Schedule this event to happen at * t seconds in the future */ p5.SoundFile.prototype.setVolume = function (vol, rampTime, tFromNow) { if (typeof vol === 'number') { var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var now = p5sound.audiocontext.currentTime; var currentVol = this.output.gain.value; this.output.gain.cancelScheduledValues(now + tFromNow); this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); } else if (vol) { vol.connect(this.output.gain); } else { // return the Gain Node return this.output.gain; } }; // same as setVolume, to match Processing Sound p5.SoundFile.prototype.amp = p5.SoundFile.prototype.setVolume; // these are the same thing p5.SoundFile.prototype.fade = p5.SoundFile.prototype.setVolume; p5.SoundFile.prototype.getVolume = function () { return this.output.gain.value; }; /** * Set the stereo panning of a p5.sound object to * a floating point number between -1.0 (left) and 1.0 (right). * Default is 0.0 (center). * * @method pan * @param {Number} [panValue] Set the stereo panner * @param {Number} timeFromNow schedule this event to happen * seconds from now * @example * <div><code> * * var ball = {}; * var soundFile; * * function setup() { * soundFormats('ogg', 'mp3'); * soundFile = loadSound('assets/beatbox.mp3'); * } * * function draw() { * background(0); * ball.x = constrain(mouseX, 0, width); * ellipse(ball.x, height/2, 20, 20) * } * * function mousePressed(){ * // map the ball's x location to a panning degree * // between -1.0 (left) and 1.0 (right) * var panning = map(ball.x, 0., width,-1.0, 1.0); * soundFile.pan(panning); * soundFile.play(); * } * </div></code> */ p5.SoundFile.prototype.pan = function (pval, tFromNow) { this.panPosition = pval; this.panner.pan(pval, tFromNow); }; /** * Returns the current stereo pan position (-1.0 to 1.0) * * @return {Number} Returns the stereo pan setting of the Oscillator * as a number between -1.0 (left) and 1.0 (right). * 0.0 is center and default. */ p5.SoundFile.prototype.getPan = function () { return this.panPosition; }; /** * Set the playback rate of a sound file. Will change the speed and the pitch. * Values less than zero will reverse the audio buffer. * * @method rate * @param {Number} [playbackRate] Set the playback rate. 1.0 is normal, * .5 is half-speed, 2.0 is twice as fast. * Must be greater than zero. * @example * <div><code> * var song; * * function preload() { * song = loadSound('assets/Damscray_DancingTiger.mp3'); * } * * function setup() { * song.loop(); * } * * function draw() { * background(200); * * // Set the rate to a range between 0.1 and 4 * // Changing the rate also alters the pitch * var speed = map(mouseY, 0.1, height, 0, 2); * speed = constrain(speed, 0.01, 4); * song.rate(speed); * * // Draw a circle to show what is going on * stroke(0); * fill(51, 100); * ellipse(mouseX, 100, 48, 48); * } * * </code> * </div> * */ p5.SoundFile.prototype.rate = function (playbackRate) { if (this.playbackRate === playbackRate && this.source) { if (this.source.playbackRate.value === playbackRate) { return; } } this.playbackRate = playbackRate; var rate = playbackRate; if (this.playbackRate === 0 && this.playing) { this.pause(); } if (this.playbackRate < 0 && !this.reversed) { var cPos = this.currentTime(); var cRate = this.source.playbackRate.value; // this.pause(); this.reverseBuffer(); rate = Math.abs(playbackRate); var newPos = (cPos - this.duration()) / rate; this.pauseTime = newPos; } else if (this.playbackRate > 0 && this.reversed) { this.reverseBuffer(); } if (this.source) { var now = p5sound.audiocontext.currentTime; this.source.playbackRate.cancelScheduledValues(now); this.source.playbackRate.linearRampToValueAtTime(Math.abs(rate), now); } }; p5.SoundFile.prototype.getPlaybackRate = function () { return this.playbackRate; }; /** * Returns the duration of a sound file in seconds. * * @method duration * @return {Number} The duration of the soundFile in seconds. */ p5.SoundFile.prototype.duration = function () { // Return Duration if (this.buffer) { return this.buffer.duration; } else { return 0; } }; /** * Return the current position of the p5.SoundFile playhead, in seconds. * Note that if you change the playbackRate while the p5.SoundFile is * playing, the results may not be accurate. * * @method currentTime * @return {Number} currentTime of the soundFile in seconds. */ p5.SoundFile.prototype.currentTime = function () { // TO DO --> make reverse() flip these values appropriately ? var howLong; if (this.isPlaying()) { var timeSinceStart = p5sound.audiocontext.currentTime - this.startSeconds + this.startTime + this.pauseTime; howLong = timeSinceStart * this.playbackRate % (this.duration() * this.playbackRate); // howLong = ( (p5sound.audiocontext.currentTime - this.startSeconds + this.startTime) * this.source.playbackRate.value ) % this.duration(); console.log('1'); return howLong; } else if (this.paused) { return this.pauseTime; } else { return this.startTime; } }; /** * Move the playhead of the song to a position, in seconds. Start * and Stop time. If none are given, will reset the file to play * entire duration from start to finish. * * @method jump * @param {Number} cueTime cueTime of the soundFile in seconds. * @param {Number} endTime endTime of the soundFile in seconds. */ p5.SoundFile.prototype.jump = function (cueTime, endTime) { if (cueTime < 0 || cueTime > this.buffer.duration) { throw 'jump time out of range'; } if (endTime < cueTime || endTime > this.buffer.duration) { throw 'end time out of range'; } this.startTime = cueTime || 0; if (endTime) { this.endTime = endTime; } else { this.endTime = this.buffer.duration; } // this.endTime = endTime || this.buffer.duration; if (this.isPlaying()) { var now = p5sound.audiocontext.currentTime; this.stop(now); this.play(cueTime, this.endTime); } }; /** * 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 || max > peaks[i]) { peaks[i] = max; } } } return peaks; } } else { throw 'Cannot load peaks yet, buffer is not loaded'; } }; /** * Reverses the p5.SoundFile's buffer source. * Playback must be handled separately (see example). * * @method reverseBuffer * @example * <div><code> * var drum; * * function preload() { * drum = loadSound('assets/drum.mp3'); * } * * function setup() { * drum.reverseBuffer(); * drum.play(); * } * * </code> * </div> */ p5.SoundFile.prototype.reverseBuffer = function () { var curVol = this.getVolume(); this.setVolume(0, 0.01, 0); this.pause(); if (this.buffer) { Array.prototype.reverse.call(this.buffer.getChannelData(0)); Array.prototype.reverse.call(this.buffer.getChannelData(1)); // set reversed flag this.reversed = !this.reversed; } else { throw 'SoundFile is not done loading'; } this.setVolume(curVol, 0.01, 0.0101); this.play(); }; // private function for onended behavior p5.SoundFile.prototype._onEnded = function (s) { s.onended = function (s) { var now = p5sound.audiocontext.currentTime; s.stop(now); }; }; p5.SoundFile.prototype.add = function () { }; p5.SoundFile.prototype.dispose = function () { if (this.buffer && this.source) { for (var i = 0; i < this.sources.length - 1; i++) { if (this.sources[i] !== null) { // this.sources[i].disconnect(); var now = p5sound.audiocontext.currentTime; this.sources[i].stop(now); this.sources[i] = null; } } } if (this.output) { this.output.disconnect(); this.output = null; } if (this.panner) { this.panner.disconnect(); this.panner = null; } }; /** * Connects the output of a p5sound object to input of another * p5.sound object. For example, you may connect a p5.SoundFile to an * FFT or an Effect. If no parameter is given, it will connect to * the master output. Most p5sound objects connect to the master * output when they are created. * * @method connect * @param {Object} [object] Audio object that accepts an input */ p5.SoundFile.prototype.connect = function (unit) { if (!unit) { this.panner.connect(p5sound.input); } else { if (unit.hasOwnProperty('input')) { this.panner.connect(unit.input); } else { this.panner.connect(unit); } } }; /** * Disconnects the output of this p5sound object. * * @method disconnect */ p5.SoundFile.prototype.disconnect = function (unit) { this.panner.disconnect(unit); }; /** * Read the Amplitude (volume level) of a p5.SoundFile. The * p5.SoundFile class contains its own instance of the Amplitude * class to help make it easy to get a SoundFile's volume level. * Accepts an optional smoothing value (0.0 < 1.0). * * @method getLevel * @param {Number} [smoothing] Smoothing is 0.0 by default. * Smooths values based on previous values. * @return {Number} Volume level (between 0.0 and 1.0) */ p5.SoundFile.prototype.getLevel = function (smoothing) { if (smoothing) { this.amplitude.smoothing = smoothing; } return this.amplitude.getLevel(); }; /** * Reset the source for this SoundFile to a * new path (URL). * * @method setPath * @param {String} path path to audio file * @param {Function} callback Callback */ p5.SoundFile.prototype.setPath = function (p, callback) { var path = p5.prototype._checkFileFormats(p); this.url = path; this.load(callback); }; /** * Replace the current Audio Buffer with a new Buffer. * * @param {Array} buf Array of Float32 Array(s). 2 Float32 Arrays * will create a stereo source. 1 will create * a mono source. */ p5.SoundFile.prototype.setBuffer = function (buf) { var ac = p5sound.audiocontext; var newBuffer = ac.createBuffer(2, buf[0].length, ac.sampleRate); var numChannels = 0; for (var channelNum = 0; channelNum < buf.length; channelNum++) { var channel = newBuffer.getChannelData(channelNum); channel.set(buf[channelNum]); numChannels++; } this.buffer = newBuffer; // set numbers of channels on input to the panner this.panner.inputChannels(numChannels); }; }(sndcore, master); var amplitude; amplitude = function () { 'use strict'; var p5sound = master; /** * Amplitude measures volume between 0.0 and 1.0. * Listens to all p5sound by default, or use setInput() * to listen to a specific sound source. Accepts an optional * smoothing value, which defaults to 0. * * @class p5.Amplitude * @constructor * @param {Number} [smoothing] between 0.0 and .999 to smooth * amplitude readings (defaults to 0) * @return {Object} Amplitude Object * @example * <div><code> * var sound, amplitude; * * function preload(){ * sound = loadSound('assets/beat.mp3'); * } * function setup() { * amplitude = new p5.Amplitude(); * sound.loop(); * } * 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 = 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); // 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.volMax = 0.001; this.normalize = false; this.processor.onaudioprocess = this.volumeAudioProcess.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); }; /** * 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.loop(); * sound2.loop(); * 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(this.processor); if (smoothing) { this.smoothing = smoothing; } // connect to the master out of p5s instance if no snd is provided if (source == null) { console.log('Amplitude input source is not ready! Connecting to master output instead'); p5sound.meter.connect(this.processor); } else if (source instanceof p5.Signal) { source.output.connect(this.processor); } else if (source) { source.connect(this.processor); this.processor.disconnect(); this.processor.connect(this.output); } else { p5sound.meter.connect(this.processor); } }; p5.Amplitude.prototype.connect = function (unit) { if (unit) { if (unit.hasOwnProperty('input')) { this.output.connect(unit.input); } else { this.output.connect(unit); } } else { this.output.connect(this.panner.connect(p5sound.input)); } }; p5.Amplitude.prototype.disconnect = function (unit) { this.output.disconnect(); }; // Should this be a private function? // TO DO make this stereo / dependent on # of audio channels p5.Amplitude.prototype.volumeAudioProcess = function (event) { // return result var inputBuffer = event.inputBuffer.getChannelData(0); 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.avgVol = Math.max(average, this.volume*this.smoothing); this.volume = Math.max(rms, this.volume * this.smoothing); this.volMax = Math.max(this.volume, this.volMax); // normalized values 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 * @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.loop(); * } * 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 () { 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'); } }; }(master); var fft; fft = function () { 'use strict'; var p5sound = master; /** * <p>FFT (Fast Fourier Transform) is an analysis algorithm that * isolates individual * <a href="https://en.wikipedia.org/wiki/Audio_frequency"> * audio frequencies</a> within a waveform.</p> * * <p>Once instantiated, a p5.FFT object can return an array based on * two types of analyses: <br> • <code>FFT.waveform()</code> computes * amplitude values along the time domain. The array indices correspond * to samples across a brief moment in time. Each value represents * amplitude of the waveform at that sample of time.<br> * • <code>FFT.analyze() </code> computes amplitude values along the * frequency domain. The array indices correspond to frequencies (i.e. * pitches), from the lowest to the highest that humans can hear. Each * value represents amplitude at that slice of the frequency spectrum. * Use with <code>getEnergy()</code> to measure amplitude at specific * frequencies, or within a range of frequencies. </p> * * <p>FFT analyzes a very short snapshot of sound called a sample * buffer. It returns an array of amplitude measurements, referred * to as <code>bins</code>. The array is 1024 bins long by default. * You can change the bin array length, but it must be a power of 2 * between 16 and 1024 in order for the FFT algorithm to function * correctly. The actual size of the FFT buffer is twice the * number of bins, so given a standard sample rate, the buffer is * 2048/44100 seconds long.</p> * * * @class p5.FFT * @constructor * @param {Number} [smoothing] Smooth results of Freq Spectrum. * 0.0 < smoothing < 1.0. * Defaults to 0.8. * @param {Number} [bins] Length of resulting array. * Must be a power of two between * 16 and 1024. Defaults to 1024. * @return {Object} FFT Object * @example * <div><code> * function preload(){ * sound = loadSound('assets/Damscray_DancingTiger.mp3'); * } * * function setup(){ * createCanvas(100,100); * sound.loop(); * fft = new p5.FFT(); * } * * 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], 0, 255, 0, height); * vertex(x,y); * } * endShape(); * } * * function mouseClicked(){ * sound.stop(); * } * </code></div> */ p5.FFT = function (smoothing, bins) { var SMOOTHING = smoothing || 0.8; if (smoothing === 0) { SMOOTHING = smoothing; } var FFT_SIZE = bins * 2 || 2048; this.analyser = p5sound.audiocontext.createAnalyser(); // default connections to p5sound master p5sound.output.connect(this.analyser); this.analyser.smoothingTimeConstant = SMOOTHING; this.analyser.fftSize = FFT_SIZE; this.freqDomain = new Uint8Array(this.analyser.frequencyBinCount); this.timeDomain = new Uint8Array(this.analyser.frequencyBinCount); // predefined frequency ranages, these will be tweakable this.bass = [ 20, 140 ]; this.lowMid = [ 140, 400 ]; this.mid = [ 400, 2600 ]; this.highMid = [ 2600, 5200 ]; this.treble = [ 5200, 14000 ]; }; /** * 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) * @param {Number} [bins] Must be a power of two between 16 and 1024 */ p5.FFT.prototype.setInput = function (source, bins) { if (bins) { this.analyser.fftSize = bins * 2; } if (source.output) { source.output.connect(this.analyser); } else { source.connect(this.analyser); } }; /** * Returns an array of amplitude values (between 0-255) 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. * @return {Array} Array Array of amplitude values (0-255) * over time. Array length = bins. * */ p5.FFT.prototype.waveform = function (bins) { if (bins) { this.analyser.fftSize = bins * 2; } this.analyser.getByteTimeDomainData(this.timeDomain); var normalArray = Array.apply([], this.timeDomain); normalArray.length === this.analyser.fftSize; normalArray.constructor === Array; 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. * @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.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); * } * </code></div> * * */ p5.FFT.prototype.analyze = function (bins) { if (bins) { this.analyser.fftSize = bins * 2; } this.analyser.getByteFrequencyData(this.freqDomain); var normalArray = Array.apply([], this.freqDomain); normalArray.length === this.analyser.fftSize; normalArray.constructor === Array; return normalArray; }; /** * Returns the amount of energy (volume) at a specific * <a href="en.wikipedia.org/wiki/Audio_frequency" target="_blank"> * frequency</a>, or the average amount of energy between two * frequencies. Accepts Number(s) corresponding * to frequency (in Hz), or a String corresponding to predefined * frequency ranges ("bass", "lowMid", "mid", "highMid", "treble"). * Returns a range between 0 (no energy/volume at that frequency) and * 255 (maximum energy). * <em>NOTE: analyze() must be called prior to getEnergy(). Analyze() * tells the FFT to analyze frequency data, and getEnergy() uses * the results determine the value at a specific frequency or * range of frequencies.</em></p> * * @method getEnergy * @param {Number|String} frequency1 Will return a value representing * energy at this frequency. Alternately, * the strings "bass", "lowMid" "mid", * "highMid", and "treble" will return * predefined frequency ranges. * @param {Number} [frequency2] If a second frequency is given, * will return average amount of * energy that exists between the * two frequencies. * @return {Number} Energy Energy (volume/amplitude) from * 0 and 255. * */ p5.FFT.prototype.getEnergy = function (frequency1, frequency2) { var nyquist = p5sound.audiocontext.sampleRate / 2; if (frequency1 === 'bass') { frequency1 = this.bass[0]; frequency2 = this.bass[1]; } else if (frequency1 === 'lowMid') { frequency1 = this.lowMid[0]; frequency2 = this.lowMid[1]; } else if (frequency1 === 'mid') { frequency1 = this.mid[0]; frequency2 = this.mid[1]; } else if (frequency1 === 'highMid') { frequency1 = this.highMid[0]; frequency2 = this.highMid[1]; } else if (frequency1 === 'treble') { frequency1 = this.treble[0]; frequency2 = this.treble[1]; } if (typeof frequency1 !== 'number') { throw 'invalid input for getEnergy()'; } else if (!frequency2) { var index = Math.round(frequency1 / nyquist * this.freqDomain.length); return this.freqDomain[index]; } else if (frequency1 && frequency2) { // if second is higher than first if (frequency1 > frequency2) { var swap = frequency2; frequency2 = frequency1; frequency1 = swap; } var lowIndex = Math.round(frequency1 / nyquist * this.freqDomain.length); var highIndex = Math.round(frequency2 / nyquist * this.freqDomain.length); var total = 0; var numFrequencies = 0; // add up all of the values for the frequencies for (var i = lowIndex; i <= highIndex; i++) { total += this.freqDomain[i]; numFrequencies += 1; } // divide by total number of frequencies var toReturn = total / numFrequencies; return toReturn; } else { throw 'invalid input for getEnergy()'; } }; // compatability with v.012, changed to getEnergy in v.0121. Will be deprecated... p5.FFT.prototype.getFreq = function (freq1, freq2) { console.log('getFreq() is deprecated. Please use getEnergy() instead.'); var x = this.getEnergy(freq1, freq2); return x; }; /** * 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) { this.analyser.smoothingTimeConstant = s; }; }(master); /** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ var Tone_core_Tone; Tone_core_Tone = function () { 'use strict'; function isUndef(val) { return val === void 0; } var audioContext; if (isUndef(window.AudioContext)) { window.AudioContext = window.webkitAudioContext; } if (isUndef(window.OfflineAudioContext)) { window.OfflineAudioContext = window.webkitOfflineAudioContext; } if (!isUndef(AudioContext)) { audioContext = new AudioContext(); } else { throw new Error('Web Audio is not supported in this browser'); } if (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.createPeriodicWave !== 'function') { AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; } if (typeof AudioBufferSourceNode.prototype.start !== 'function') { AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn; } if (typeof AudioBufferSourceNode.prototype.stop !== 'function') { AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff; } if (typeof OscillatorNode.prototype.start !== 'function') { OscillatorNode.prototype.start = OscillatorNode.prototype.noteOn; } if (typeof OscillatorNode.prototype.stop !== 'function') { OscillatorNode.prototype.stop = OscillatorNode.prototype.noteOff; } if (typeof OscillatorNode.prototype.setPeriodicWave !== 'function') { OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; } AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; AudioNode.prototype.connect = function (B, outNum, inNum) { if (B.input) { if (Array.isArray(B.input)) { if (isUndef(inNum)) { inNum = 0; } this.connect(B.input[inNum]); } else { this.connect(B.input, outNum, inNum); } } else { try { if (B instanceof AudioNode) { this._nativeConnect(B, outNum, inNum); } else { this._nativeConnect(B, outNum); } } catch (e) { throw new Error('error connecting to node: ' + B); } } }; var Tone = function (inputs, outputs) { if (isUndef(inputs) || inputs === 1) { this.input = this.context.createGain(); } else if (inputs > 1) { this.input = new Array(inputs); } if (isUndef(outputs) || outputs === 1) { this.output = this.context.createGain(); } else if (outputs > 1) { this.output = new Array(inputs); } }; Tone.context = audioContext; Tone.prototype.context = Tone.context; Tone.prototype.bufferSize = 2048; Tone.prototype.bufferTime = Tone.prototype.bufferSize / Tone.context.sampleRate; 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); } }; Tone.prototype.disconnect = function (outputNum) { if (Array.isArray(this.output)) { outputNum = this.defaultArg(outputNum, 0); this.output[outputNum].disconnect(); } else { this.output.disconnect(); } }; 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; } } }; Tone.prototype.connectParallel = function () { var connectFrom = arguments[0]; if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { var connectTo = arguments[i]; connectFrom.connect(connectTo); } } }; 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; } } }; Tone.prototype.fan = function () { if (arguments.length > 0) { for (var i = 1; i < arguments.length; i++) { this.connect(arguments[i]); } } }; AudioNode.prototype.chain = Tone.prototype.chain; AudioNode.prototype.fan = Tone.prototype.fan; Tone.prototype.defaultArg = function (given, fallback) { if (typeof given === 'object' && typeof fallback === 'object') { var ret = {}; for (var givenProp in given) { ret[givenProp] = this.defaultArg(given[givenProp], given[givenProp]); } for (var prop in fallback) { ret[prop] = this.defaultArg(given[prop], fallback[prop]); } return ret; } else { return isUndef(given) ? fallback : given; } }; Tone.prototype.optionsObject = function (values, keys, defaults) { var options = {}; if (values.length === 1 && typeof values[0] === 'object') { options = values[0]; } else { for (var i = 0; i < keys.length; i++) { options[keys[i]] = values[i]; } } if (!this.isUndef(defaults)) { return this.defaultArg(options, defaults); } else { return options; } }; Tone.prototype.isUndef = isUndef; Tone.prototype.equalPowerScale = function (percent) { var piFactor = 0.5 * Math.PI; return Math.sin(percent * piFactor); }; Tone.prototype.logScale = function (gain) { return Math.max(this.normalize(this.gainToDb(gain), -100, 0), 0); }; Tone.prototype.expScale = function (gain) { return this.dbToGain(this.interpolate(gain, -100, 0)); }; 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.interpolate = function (input, outputMin, outputMax) { return input * (outputMax - outputMin) + outputMin; }; Tone.prototype.normalize = function (input, inputMin, inputMax) { if (inputMin > inputMax) { var tmp = inputMax; inputMax = inputMin; inputMin = tmp; } else if (inputMin == inputMax) { return 0; } return (input - inputMin) / (inputMax - inputMin); }; Tone.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; } }; var _silentNode = null; Tone.prototype.noGC = function () { this.output.connect(_silentNode); }; AudioNode.prototype.noGC = function () { this.connect(_silentNode); }; Tone.prototype.now = function () { return this.context.currentTime; }; Tone.prototype.samplesToSeconds = function (samples) { return samples / this.context.sampleRate; }; Tone.prototype.toSamples = function (time) { var seconds = this.toSeconds(time); return Math.round(seconds * this.context.sampleRate); }; Tone.prototype.toSeconds = function (time, now) { now = this.defaultArg(now, this.now()); if (typeof time === 'number') { return time; } else if (typeof time === 'string') { var plusTime = 0; if (time.charAt(0) === '+') { time = time.slice(1); plusTime = now; } return parseFloat(time) + plusTime; } else { return now; } }; Tone.prototype.frequencyToSeconds = function (freq) { return 1 / parseFloat(freq); }; Tone.prototype.secondsToFrequency = function (seconds) { return 1 / seconds; }; var newContextCallbacks = []; Tone._initAudioContext = function (callback) { callback(Tone.context); newContextCallbacks.push(callback); }; Tone.setContext = function (ctx) { Tone.prototype.context = ctx; Tone.context = ctx; for (var i = 0; i < newContextCallbacks.length; i++) { newContextCallbacks[i](ctx); } }; Tone.extend = function (child, parent) { if (isUndef(parent)) { parent = Tone; } function TempConstructor() { } TempConstructor.prototype = parent.prototype; child.prototype = new TempConstructor(); child.prototype.constructor = child; }; Tone.startMobile = function () { var osc = Tone.context.createOscillator(); var silent = Tone.context.createGain(); silent.gain.value = 0; osc.connect(silent); silent.connect(Tone.context.destination); var now = Tone.context.currentTime; osc.start(now); osc.stop(now + 1); }; Tone._initAudioContext(function (audioContext) { Tone.prototype.bufferTime = Tone.prototype.bufferSize / audioContext.sampleRate; _silentNode = audioContext.createGain(); _silentNode.gain.value = 0; _silentNode.connect(audioContext.destination); }); console.log('%c * Tone.js r3 * ', 'background: #000; color: #fff'); return Tone; }(); /** Tone.js module by Yotam Mann, MIT License 2014 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 (node instanceof Tone.Signal) { node.setValue(0); } else if (node instanceof AudioParam) { node.value = 0; } Tone.prototype.connect.call(this, node, outputNumber, inputNumber); }; Tone.SignalBase.prototype.dispose = function () { Tone.prototype.dispose.call(this); }; return Tone.SignalBase; }(Tone_core_Tone); /** Tone.js module by Yotam Mann, MIT License 2014 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.setCurve(mapping); } else if (isFinite(mapping) || this.isUndef(mapping)) { this._curve = new Float32Array(this.defaultArg(mapping, 1024)); } else if (typeof mapping === 'function') { this._curve = new Float32Array(this.defaultArg(bufferLen, 1024)); this.setMap(mapping); } }; Tone.extend(Tone.WaveShaper, Tone.SignalBase); Tone.WaveShaper.prototype.setMap = function (mapping) { for (var i = 0, len = this._curve.length; i < len; i++) { var normalized = i / len * 2 - 1; var normOffOne = i / (len - 1) * 2 - 1; this._curve[i] = mapping(normalized, i, normOffOne); } this._shaper.curve = this._curve; }; Tone.WaveShaper.prototype.setCurve = function (mapping) { if (this._isSafari()) { var first = mapping[0]; mapping.unshift(first); } this._curve = new Float32Array(mapping); this._shaper.curve = this._curve; }; Tone.WaveShaper.prototype.setOversample = function (oversampling) { this._shaper.oversample = oversampling; }; Tone.WaveShaper.prototype._isSafari = function () { var ua = navigator.userAgent.toLowerCase(); return ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1; }; Tone.WaveShaper.prototype.dispose = function () { Tone.prototype.dispose.call(this); this._shaper.disconnect(); this._shaper = null; this._curve = null; }; return Tone.WaveShaper; }(Tone_core_Tone); /** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ var Tone_signal_Signal; Tone_signal_Signal = function (Tone) { 'use strict'; Tone.Signal = function (value) { this._scalar = this.context.createGain(); this.input = this.output = this.context.createGain(); this._syncRatio = 1; this.value = this.defaultArg(value, 0); Tone.Signal._constant.chain(this._scalar, this.output); }; Tone.extend(Tone.Signal, Tone.SignalBase); Tone.Signal.prototype.getValue = function () { return this._scalar.gain.value; }; Tone.Signal.prototype.setValue = function (value) { if (this._syncRatio === 0) { value = 0; } else { value *= this._syncRatio; } this._scalar.gain.value = value; }; Tone.Signal.prototype.setValueAtTime = function (value, time) { value *= this._syncRatio; this._scalar.gain.setValueAtTime(value, this.toSeconds(time)); }; Tone.Signal.prototype.setCurrentValueNow = function (now) { now = this.defaultArg(now, this.now()); var currentVal = this.getValue(); this.cancelScheduledValues(now); this._scalar.gain.setValueAtTime(currentVal, now); return currentVal; }; Tone.Signal.prototype.linearRampToValueAtTime = function (value, endTime) { value *= this._syncRatio; this._scalar.gain.linearRampToValueAtTime(value, this.toSeconds(endTime)); }; Tone.Signal.prototype.exponentialRampToValueAtTime = function (value, endTime) { value *= this._syncRatio; try { this._scalar.gain.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); } catch (e) { this._scalar.gain.linearRampToValueAtTime(value, this.toSeconds(endTime)); } }; Tone.Signal.prototype.exponentialRampToValueNow = function (value, endTime) { var now = this.now(); this.setCurrentValueNow(now); if (endTime.toString().charAt(0) === '+') { endTime = endTime.substr(1); } this.exponentialRampToValueAtTime(value, now + this.toSeconds(endTime)); }; Tone.Signal.prototype.linearRampToValueNow = function (value, endTime) { var now = this.now(); this.setCurrentValueNow(now); value *= this._syncRatio; if (endTime.toString().charAt(0) === '+') { endTime = endTime.substr(1); } this._scalar.gain.linearRampToValueAtTime(value, now + this.toSeconds(endTime)); }; Tone.Signal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { value *= this._syncRatio; this._scalar.gain.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); }; Tone.Signal.prototype.setValueCurveAtTime = function (values, startTime, duration) { for (var i = 0; i < values.length; i++) { values[i] *= this._syncRatio; } this._scalar.gain.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); }; Tone.Signal.prototype.cancelScheduledValues = function (startTime) { this._scalar.gain.cancelScheduledValues(this.toSeconds(startTime)); }; Tone.Signal.prototype.sync = function (signal, ratio) { if (ratio) { this._syncRatio = ratio; } else { if (signal.getValue() !== 0) { this._syncRatio = this.getValue() / signal.getValue(); } else { this._syncRatio = 0; } } this._scalar.disconnect(); this._scalar = this.context.createGain(); this.connectSeries(signal, this._scalar, this.output); this._scalar.gain.value = this._syncRatio; }; Tone.Signal.prototype.unsync = function () { var currentGain = this.getValue(); this._scalar.disconnect(); this._scalar = this.context.createGain(); this._scalar.gain.value = currentGain / this._syncRatio; this._syncRatio = 1; Tone.Signal._constant.chain(this._scalar, this.output); }; Tone.Signal.prototype.dispose = function () { Tone.prototype.dispose.call(this); this._scalar.disconnect(); this._scalar = null; }; Object.defineProperty(Tone.Signal.prototype, 'value', { get: function () { return this.getValue(); }, set: function (val) { this.setValue(val); } }); Tone.Signal._generator = null; Tone.Signal._constant = null; Tone._initAudioContext(function (audioContext) { Tone.Signal._generator = audioContext.createOscillator(); Tone.Signal._constant = new Tone.WaveShaper([ 1, 1 ]); Tone.Signal._generator.connect(Tone.Signal._constant); Tone.Signal._generator.start(0); Tone.Signal._generator.noGC(); }); return Tone.Signal; }(Tone_core_Tone); /** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ var Tone_signal_Add; Tone_signal_Add = function (Tone) { 'use strict'; Tone.Add = function (value) { Tone.call(this, 2, 0); this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain(); this._value = null; if (isFinite(value)) { this._value = new Tone.Signal(value); this._value.connect(this._sum); } }; Tone.extend(Tone.Add, Tone.SignalBase); Tone.Add.prototype.setValue = function (value) { if (this._value !== null) { this._value.setValue(value); } else { throw new Error('cannot switch from signal to number'); } }; Tone.Add.prototype.dispose = function () { Tone.prototype.dispose.call(this); this._sum = null; if (this._value) { this._value.dispose(); this._value = null; } }; return Tone.Add; }(Tone_core_Tone); /** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ var Tone_signal_Multiply; Tone_signal_Multiply = function (Tone) { 'use strict'; Tone.Multiply = function (value) { Tone.call(this, 2, 0); this._mult = this.input[0] = this.output = this.context.createGain(); this._factor = this.input[1] = this.output.gain; this._factor.value = this.defaultArg(value, 0); }; Tone.extend(Tone.Multiply, Tone.SignalBase); Tone.Multiply.prototype.setValue = function (value) { this._factor.value = value; }; Tone.Multiply.prototype.dispose = function () { Tone.prototype.dispose.call(this); this._mult = null; this._factor = null; }; return Tone.Multiply; }(Tone_core_Tone); /** Tone.js module by Yotam Mann, MIT License 2014 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); Tone.Scale.prototype.setMin = function (min) { this._outputMin = min; this._setRange(); }; Tone.Scale.prototype.setMax = function (max) { this._outputMax = max; this._setRange(); }; Tone.Scale.prototype._setRange = function () { this._add.setValue(this._outputMin); this._scale.setValue(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 Tone.Scale; }(Tone_core_Tone, Tone_signal_Add, Tone_signal_Multiply); var signal; signal = function () { 'use strict'; // Signal is built with the Tone.js signal by Yotam Mann // https://github.com/TONEnoTONE/Tone.js/ var Signal = Tone_signal_Signal; var Add = Tone_signal_Add; var Mult = Tone_signal_Multiply; var Scale = Tone_signal_Scale; var Tone = Tone_core_Tone; var p5sound = master; Tone.setContext(p5sound.audiocontext); /** * <p>p5.Signal is a constant audio-rate signal used by p5.Oscillator * and p5.Envelope for modulation math.</p> * * <p>This is necessary because Web Audio is processed on a seprate clock. * For example, the p5 draw loop runs about 60 times per second. But * the audio clock must process samples 44100 times per second. If we * want to add a value to each of those samples, we can't do it in the * draw loop, but we can do it by adding a constant-rate audio signal.</p. * * <p>This class mostly functions behind the scenes in p5.sound, and returns * a Tone.Signal from the Tone.js library by Yotam Mann. * If you want to work directly with audio signals for modular * synthesis, check out * <a href='http://bit.ly/1oIoEng' target=_'blank'>tone.js.</a></p> * * @class p5.Signal * @constructor * @return {Tone.Signal} A Signal object from the Tone.js library * @example * <div><code> * function setup() { * carrier = new p5.Oscillator('sine'); * carrier.amp(1); // set amplitude * carrier.freq(220); // set frequency * carrier.start(); // start oscillating * * modulator = new p5.Oscillator('sawtooth'); * modulator.disconnect(); * modulator.amp(1); * modulator.freq(4); * modulator.start(); * * // Modulator's default amplitude range is -1 to 1. * // Multiply it by -200, so the range is -200 to 200 * // then add 220 so the range is 20 to 420 * carrier.freq( modulator.mult(-200).add(220) ); * } * </code></div> */ p5.Signal = function (value) { var s = new Signal(value); // p5sound.soundArray.push(s); return s; }; /** * Fade to value, for smooth transitions * * @method fade * @param {Number} value Value to set this signal * @param {[Number]} secondsFromNow Length of fade, in seconds from now */ Signal.prototype.fade = Signal.prototype.linearRampToValueAtTime; Mult.prototype.fade = Signal.prototype.fade; Add.prototype.fade = Signal.prototype.fade; Scale.prototype.fade = Signal.prototype.fade; /** * Connect a p5.sound object or Web Audio node to this * p5.Signal so that its amplitude values can be scaled. * * @param {Object} input */ Signal.prototype.setInput = function (_input) { _input.connect(this); }; Mult.prototype.setInput = Signal.prototype.setInput; Add.prototype.setInput = Signal.prototype.setInput; Scale.prototype.setInput = Signal.prototype.setInput; // signals can add / mult / scale themselves /** * Add a constant value to this audio signal, * and return the resulting audio signal. Does * not change the value of the original signal, * instead it returns a new p5.SignalAdd. * * @method add * @param {Number} number * @return {p5.SignalAdd} object */ Signal.prototype.add = function (num) { var add = new Add(num); // add.setInput(this); this.connect(add); return add; }; Mult.prototype.add = Signal.prototype.add; Add.prototype.add = Signal.prototype.add; Scale.prototype.add = Signal.prototype.add; /** * Multiply this signal by a constant value, * and return the resulting audio signal. Does * not change the value of the original signal, * instead it returns a new p5.SignalMult. * * @method mult * @param {Number} number to multiply * @return {Tone.Multiply} object */ Signal.prototype.mult = function (num) { var mult = new Mult(num); // mult.setInput(this); this.connect(mult); return mult; }; Mult.prototype.mult = Signal.prototype.mult; Add.prototype.mult = Signal.prototype.mult; Scale.prototype.mult = Signal.prototype.mult; /** * Scale this signal value to a given range, * and return the result as an audio signal. Does * not change the value of the original signal, * instead it returns a new p5.SignalScale. * * @method scale * @param {Number} number to multiply * @param {Number} inMin input range minumum * @param {Number} inMax input range maximum * @param {Number} outMin input range minumum * @param {Number} outMax input range maximum * @return {p5.SignalScale} object */ Signal.prototype.scale = function (inMin, inMax, outMin, outMax) { var mapOutMin, mapOutMax; if (arguments.length === 4) { mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; } else { mapOutMin = arguments[0]; mapOutMax = arguments[1]; } var scale = new Scale(mapOutMin, mapOutMax); this.connect(scale); return scale; }; Mult.prototype.scale = Signal.prototype.scale; Add.prototype.scale = Signal.prototype.scale; Scale.prototype.scale = Signal.prototype.scale; }(Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone, master); var oscillator; oscillator = function () { 'use strict'; var p5sound = master; var Signal = Tone_signal_Signal; var Add = Tone_signal_Add; var Mult = Tone_signal_Multiply; var Scale = Tone_signal_Scale; /** * <p>Creates a signal that oscillates between -1.0 and 1.0. * By default, the oscillation takes the form of a sinusoidal * shape ('sine'). Additional types include 'triangle', * 'sawtooth' and 'square'. The frequency defaults to * 440 oscillations per second (440Hz, equal to the pitch of an * 'A' note).</p> * * <p>Set the type of oscillation with setType(), or by creating a * specific oscillator.</p> For example: * <code>new p5.SinOsc(freq)</code> * <code>new p5.TriOsc(freq)</code> * <code>new p5.SqrOsc(freq)</code> * <code>new p5.SawOsc(freq)</code>. * </p> * * @class p5.Oscillator * @constructor * @param {Number} [freq] frequency defaults to 440Hz * @param {String} [type] type of oscillator. Options: * 'sine' (default), 'triangle', * 'sawtooth', 'square' * @return {Object} Oscillator object */ 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.oscillator = p5sound.audiocontext.createOscillator(); this.f = freq || 440; // frequency this.oscillator.frequency.setValueAtTime(this.f, p5sound.audiocontext.currentTime); this.oscillator.type = type || 'sine'; var o = this.oscillator; // connections this.input = p5sound.audiocontext.createGain(); this.output = p5sound.audiocontext.createGain(); this._freqMods = []; // modulators connected to this oscillator's frequency // set default output gain to 0.5 this.output.gain.value = 0.5; this.output.gain.setValueAtTime(0.5, p5sound.audiocontext.currentTime); this.oscillator.connect(this.output); // stereo panning this.panPosition = 0; this.connection = p5sound.input; // connect to p5sound by default this.panner = new p5.Panner(this.output, this.connection, 1); //array of math operation signal chaining this.mathOps = [this.output]; // add to the soundArray so we can dispose of the osc later p5sound.soundArray.push(this); }; /** * Start an oscillator. Accepts an optional parameter to * determine how long (in seconds from now) until the * oscillator starts. * * @method start * @param {Number} [time] startTime in seconds from now. * @param {Number} [frequency] frequency in Hz. */ p5.Oscillator.prototype.start = function (time, f) { if (this.started) { var now = p5sound.audiocontext.currentTime; this.stop(now); } if (!this.started) { var freq = f || this.f; var type = this.oscillator.type; // var detune = this.oscillator.frequency.value; this.oscillator = p5sound.audiocontext.createOscillator(); this.oscillator.frequency.exponentialRampToValueAtTime(Math.abs(freq), p5sound.audiocontext.currentTime); this.oscillator.type = type; // this.oscillator.detune.value = detune; this.oscillator.connect(this.output); time = time || 0; this.oscillator.start(time + p5sound.audiocontext.currentTime); this.freqNode = this.oscillator.frequency; // if other oscillators are already connected to this osc's freq for (var i in this._freqMods) { if (typeof this._freqMods[i].connect !== 'undefined') { this._freqMods[i].connect(this.oscillator.frequency); } } this.started = true; } }; /** * Stop an oscillator. Accepts an optional parameter * to determine how long (in seconds from now) until the * oscillator stops. * * @method stop * @param {Number} secondsFromNow Time, in seconds from now. */ p5.Oscillator.prototype.stop = function (time) { if (this.started) { var t = time || 0; var now = p5sound.audiocontext.currentTime; this.oscillator.stop(t + now); this.started = false; } }; /** * Set the amplitude between 0 and 1.0. Or, pass in an object * such as an oscillator to modulate amplitude with an audio signal. * * @method amp * @param {Number|Object} vol between 0 and 1.0 * or a modulating signal/oscillator * @param {Number} [rampTime] create a fade that lasts rampTime * @param {Number} [timeFromNow] schedule this event to happen * seconds from now * @return {AudioParam} gain If no value is provided, * returns the Web Audio API * AudioParam that controls * this oscillator's * gain/amplitude/volume) */ p5.Oscillator.prototype.amp = function (vol, rampTime, tFromNow) { var self = this; if (typeof vol === 'number') { var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var now = p5sound.audiocontext.currentTime; var currentVol = this.output.gain.value; this.output.gain.cancelScheduledValues(now); this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime); } else if (vol) { console.log(vol); vol.connect(self.output.gain); } else { // return the Gain Node return this.output.gain; } }; // these are now the same thing p5.Oscillator.prototype.fade = p5.Oscillator.prototype.amp; p5.Oscillator.prototype.getAmp = function () { return this.output.gain.value; }; /** * Set frequency of an oscillator to a value. Or, pass in an object * such as an oscillator to modulate the frequency with an audio signal. * * @method freq * @param {Number|Object} Frequency Frequency in Hz * or modulating signal/oscillator * @param {Number} [rampTime] Ramp time (in seconds) * @param {Number} [timeFromNow] Schedule this event to happen * at x seconds from now * @return {AudioParam} Frequency If no value is provided, * returns the Web Audio API * AudioParam that controls * this oscillator's frequency * @example * <div><code> * var osc = new p5.Oscillator(300); * osc.start(); * osc.freq(40, 10); * </code></div> */ p5.Oscillator.prototype.freq = function (val, rampTime, tFromNow) { if (typeof val === 'number' && !isNaN(val)) { this.f = val; var now = p5sound.audiocontext.currentTime; var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var currentFreq = this.oscillator.frequency.value; this.oscillator.frequency.cancelScheduledValues(now); this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); if (val > 0) { this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); } else { this.oscillator.frequency.linearRampToValueAtTime(val, tFromNow + rampTime + now); } } else if (val) { if (val.output) { val = val.output; } val.connect(this.oscillator.frequency); // keep track of what is modulating this param // so it can be re-connected if this._freqMods.push(val); } else { // return the Frequency Node return this.oscillator.frequency; } }; p5.Oscillator.prototype.getFreq = function () { return this.oscillator.frequency.value; }; /** * Set type to 'sine', 'triangle', 'sawtooth' or 'square'. * * @method setType * @param {String} type 'sine', 'triangle', 'sawtooth' or 'square'. */ p5.Oscillator.prototype.setType = function (type) { this.oscillator.type = type; }; p5.Oscillator.prototype.getType = function () { return this.oscillator.type; }; /** * Connect to a p5.sound / Web Audio object. * * @method connect * @param {Object} unit A p5.sound or Web Audio object */ p5.Oscillator.prototype.connect = function (unit) { if (!unit) { this.panner.connect(p5sound.input); } else if (unit.hasOwnProperty('input')) { this.panner.connect(unit.input); this.connection = unit.input; } else { this.panner.connect(unit); this.connection = unit; } }; /** * Disconnect all outputs * * @method disconnect */ p5.Oscillator.prototype.disconnect = function (unit) { this.output.disconnect(); this.panner.disconnect(); this.output.connect(this.panner); this.oscMods = []; }; /** * Pan between Left (-1) and Right (1) * * @method pan * @param {Number} panning Number between -1 and 1 * @param {Number} timeFromNow schedule this event to happen * seconds from now */ p5.Oscillator.prototype.pan = function (pval, tFromNow) { this.panPosition = pval; this.panner.pan(pval, tFromNow); }; p5.Oscillator.prototype.getPan = function () { return this.panPosition; }; // get rid of the oscillator p5.Oscillator.prototype.dispose = function () { if (this.oscillator) { var now = p5sound.audiocontext.currentTime; this.stop(now); this.disconnect(); this.oscillator.disconnect(); this.panner = null; this.oscillator = null; } // if it is a Pulse if (this.osc2) { this.osc2.dispose(); } }; /** * Set the phase of an oscillator between 0.0 and 1.0 * * @method phase * @param {Number} phase float between 0.0 and 1.0 */ p5.Oscillator.prototype.phase = function (p) { if (!this.dNode) { // create a delay node this.dNode = p5sound.audiocontext.createDelay(); // put the delay node in between output and panner this.output.disconnect(); this.output.connect(this.dNode); this.dNode.connect(this.panner); } // set delay time based on PWM width var now = p5sound.audiocontext.currentTime; this.dNode.delayTime.linearRampToValueAtTime(p5.prototype.map(p, 0, 1, 0, 1 / this.oscillator.frequency.value), now); }; // ========================== // // SIGNAL MATH FOR MODULATION // // ========================== // // return sigChain(this, scale, thisChain, nextChain, Scale); var sigChain = function (o, mathObj, thisChain, nextChain, type) { var chainSource = o.oscillator; // if this type of math already exists in the chain, replace it for (var i in o.mathOps) { if (o.mathOps[i] instanceof type) { chainSource.disconnect(); o.mathOps[i].dispose(); thisChain = i; // assume nextChain is output gain node unless... if (thisChain < o.mathOps.length - 2) { nextChain = o.mathOps[i + 1]; } } } if (thisChain == o.mathOps.length - 1) { o.mathOps.push(nextChain); } // assume source is the oscillator unless i > 0 if (i > 0) { chainSource = o.mathOps[i - 1]; } chainSource.disconnect(); chainSource.connect(mathObj); mathObj.connect(nextChain); o.mathOps[thisChain] = mathObj; return o; }; /** * Add a value to the p5.Oscillator's output amplitude, * and return the oscillator. Calling this method again * will override the initial add() with a new value. * * @method add * @param {Number} number Constant number to add * @return {p5.Oscillator} Oscillator Returns this oscillator * with scaled output * */ p5.Oscillator.prototype.add = function (num) { var add = new Add(num); var thisChain = this.mathOps.length - 1; var nextChain = this.output; return sigChain(this, add, thisChain, nextChain, Add); }; /** * Multiply the p5.Oscillator's output amplitude * by a fixed value (i.e. turn it up!). Calling this method * again will override the initial mult() with a new value. * * @method mult * @param {Number} number Constant number to multiply * @return {p5.Oscillator} Oscillator Returns this oscillator * with multiplied output */ p5.Oscillator.prototype.mult = function (num) { var mult = new Mult(num); var thisChain = this.mathOps.length - 1; var nextChain = this.output; return sigChain(this, mult, thisChain, nextChain, Mult); }; /** * Scale this oscillator's amplitude values to a given * range, and return the oscillator. Calling this method * again will override the initial scale() with new values. * * @method scale * @param {Number} inMin input range minumum * @param {Number} inMax input range maximum * @param {Number} outMin input range minumum * @param {Number} outMax input range maximum * @return {p5.Oscillator} Oscillator Returns this oscillator * with scaled output */ p5.Oscillator.prototype.scale = function (inMin, inMax, outMin, outMax) { var mapOutMin, mapOutMax; if (arguments.length === 4) { mapOutMin = p5.prototype.map(outMin, inMin, inMax, 0, 1) - 0.5; mapOutMax = p5.prototype.map(outMax, inMin, inMax, 0, 1) - 0.5; } else { mapOutMin = arguments[0]; mapOutMax = arguments[1]; } var scale = new Scale(mapOutMin, mapOutMax); var thisChain = this.mathOps.length - 1; var nextChain = this.output; return sigChain(this, scale, thisChain, nextChain, Scale); }; // ============================== // // SinOsc, TriOsc, SqrOsc, SawOsc // // ============================== // /** * Constructor: <code>new p5.SinOsc()</code>. * This creates a Sine Wave Oscillator and is * equivalent to <code> new p5.Oscillator('sine') * </code> or creating a p5.Oscillator and then calling * its method <code>setType('sine')</code>. * See p5.Oscillator for methods. * * @method p5.SinOsc * @param {[Number]} freq Set the frequency */ p5.SinOsc = function (freq) { p5.Oscillator.call(this, freq, 'sine'); }; p5.SinOsc.prototype = Object.create(p5.Oscillator.prototype); /** * Constructor: <code>new p5.TriOsc()</code>. * This creates a Triangle Wave Oscillator and is * equivalent to <code>new p5.Oscillator('triangle') * </code> or creating a p5.Oscillator and then calling * its method <code>setType('triangle')</code>. * See p5.Oscillator for methods. * * @method p5.TriOsc * @param {[Number]} freq Set the frequency */ p5.TriOsc = function (freq) { p5.Oscillator.call(this, freq, 'triangle'); }; p5.TriOsc.prototype = Object.create(p5.Oscillator.prototype); /** * Constructor: <code>new p5.SawOsc()</code>. * This creates a SawTooth Wave Oscillator and is * equivalent to <code> new p5.Oscillator('sawtooth') * </code> or creating a p5.Oscillator and then calling * its method <code>setType('sawtooth')</code>. * See p5.Oscillator for methods. * * @method p5.SawOsc * @param {[Number]} freq Set the frequency */ p5.SawOsc = function (freq) { p5.Oscillator.call(this, freq, 'sawtooth'); }; p5.SawOsc.prototype = Object.create(p5.Oscillator.prototype); /** * Constructor: <code>new p5.SqrOsc()</code>. * This creates a Square Wave Oscillator and is * equivalent to <code> new p5.Oscillator('square') * </code> or creating a p5.Oscillator and then calling * its method <code>setType('square')</code>. * See p5.Oscillator for methods. * * @method p5.SawOsc * @param {[Number]} freq Set the frequency */ p5.SqrOsc = function (freq) { p5.Oscillator.call(this, freq, 'square'); }; p5.SqrOsc.prototype = Object.create(p5.Oscillator.prototype); }(master, Tone_signal_Signal, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale); var env; env = function () { 'use strict'; var p5sound = master; var Add = Tone_signal_Add; var Mult = Tone_signal_Multiply; var Scale = Tone_signal_Scale; var Tone = Tone_core_Tone; Tone.setContext(p5sound.audiocontext); // oscillator or buffer source to clear on env complete // to save resources if/when it is retriggered var sourceToClear = null; /** * <p>Envelopes are pre-defined amplitude distribution over time. * The p5.Env accepts up to four time/level pairs, where time * determines how long of a ramp before value reaches level. * Typically, envelopes are used to control the output volume * of an object, a series of fades referred to as Attack, Decay, * Sustain and Release (ADSR). But p5.Env can control any * Web Audio Param, for example it can be passed to an Oscillator * frequency like osc.freq(env) </p> * * @class p5.Env * @constructor * @param {Number} aTime Time (in seconds) before level * reaches attackLevel * @param {Number} aLevel Typically an amplitude between * 0.0 and 1.0 * @param {Number} dTime Time * @param {Number} [dLevel] Amplitude (In a standard ADSR envelope, * decayLevel = sustainLevel) * @param {Number} [sTime] Time (in seconds) * @param {Number} [sLevel] Amplitude 0.0 to 1.0 * @param {Number} [rTime] Time (in seconds) * @param {Number} [rLevel] Amplitude 0.0 to 1.0 * @example * <div><code> * var aT = 0.1; // attack time in seconds * var aL = 0.7; // attack level 0.0 to 1.0 * var dT = 0.3; // decay time in seconds * var dL = 0.1; // decay level 0.0 to 1.0 * var sT = 0.2; // sustain time in seconds * var sL = dL; // sustain level 0.0 to 1.0 * var rT = 0.5; // release time in seconds * // release level defaults to zero * * var env; * var triOsc; * * function setup() { * env = new p5.Env(aT, aL, dT, dL, sT, sL, rT); * triOsc = new p5.Oscillator('triangle'); * triOsc.amp(env); // give the env control of the triOsc's amp * triOsc.start(); * env.play(); * } * </code></div> */ p5.Env = function (t1, l1, t2, l2, t3, l3, t4, l4) { /** * @property attackTime */ this.aTime = t1; /** * @property attackLevel */ this.aLevel = l1; /** * @property decayTime */ this.dTime = t2 || 0; /** * @property decayLevel */ this.dLevel = l2 || 0; /** * @property sustainTime */ this.sTime = t3 || 0; /** * @property sustainLevel */ this.sLevel = l3 || 0; /** * @property releaseTime */ this.rTime = t4 || 0; /** * @property releaseLevel */ this.rLevel = l4 || 0; this.output = p5sound.audiocontext.createGain(); this.control = new p5.Signal(); this.control.connect(this.output); this.timeoutID = null; // store clearThing timeouts this.connection = null; // store connection //array of math operation signal chaining this.mathOps = [this.control]; // add to the soundArray so we can dispose of the env later p5sound.soundArray.push(this); }; /** * Reset the envelope with a series of time/value pairs. * * @method set * @param {Number} aTime Time (in seconds) before level * reaches attackLevel * @param {Number} aLevel Typically an amplitude between * 0.0 and 1.0 * @param {Number} dTime Time * @param {Number} [dLevel] Amplitude (In a standard ADSR envelope, * decayLevel = sustainLevel) * @param {Number} [sTime] Time (in seconds) * @param {Number} [sLevel] Amplitude 0.0 to 1.0 * @param {Number} [rTime] Time (in seconds) * @param {Number} [rLevel] Amplitude 0.0 to 1.0 */ p5.Env.prototype.set = function (t1, l1, t2, l2, t3, l3, t4, l4) { this.aTime = t1; this.aLevel = l1; this.dTime = t2 || 0; this.dLevel = l2 || 0; this.sTime = t3 || 0; this.sLevel = l3 || 0; this.rTime = t4 || 0; this.rLevel = l4 || 0; }; /** * * @param {Object} input A p5.sound object or * Web Audio Param */ p5.Env.prototype.setInput = function (unit) { this.connect(unit); }; p5.Env.prototype.ctrl = function (unit) { this.connect(unit); }; /** * Play tells the envelope to start acting on a given input. * If the input is a p5.sound object (i.e. AudioIn, Oscillator, * SoundFile), then Env will control its output volume. * Envelopes can also be used to control any <a href=" * http://docs.webplatform.org/wiki/apis/webaudio/AudioParam"> * Web Audio Audio Param.</a> * * @method play * @param {Object} unit A p5.sound object or * Web Audio Param. * @param {Number} secondsFromNow time from now (in seconds) */ p5.Env.prototype.play = function (unit, secondsFromNow) { var now = p5sound.audiocontext.currentTime; var tFromNow = secondsFromNow || 0; var t = now + tFromNow + 0.0001; if (typeof this.timeoutID === 'number') { window.clearTimeout(this.timeoutID); } if (unit) { if (this.connection !== unit) { this.connect(unit); } } this.control.cancelScheduledValues(t - 0.0001); this.control.linearRampToValueAtTime(0, t - 0.00005); // attack this.control.linearRampToValueAtTime(this.aLevel, t + this.aTime); // decay to decay level this.control.linearRampToValueAtTime(this.dLevel, t + this.aTime + this.dTime); // hold sustain level this.control.linearRampToValueAtTime(this.sLevel, t + this.aTime + this.dTime + this.sTime); // release this.control.linearRampToValueAtTime(this.rLevel, t + this.aTime + this.dTime + this.sTime + this.rTime); var clearTime = t + this.aTime + this.dTime + this.sTime + this.rTime; }; /** * Trigger the Attack, Decay, and Sustain 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) */ p5.Env.prototype.triggerAttack = function (unit, secondsFromNow) { var now = p5sound.audiocontext.currentTime; var tFromNow = secondsFromNow || 0; var t = now + tFromNow + 0.0001; this.lastAttack = t; if (typeof this.timeoutID === 'number') { window.clearTimeout(this.timeoutID); } var currentVal = this.control.getValue(); // not working on Firefox, always returns 0 this.control.cancelScheduledValues(t - 0.0001); this.control.linearRampToValueAtTime(currentVal, t - 0.00005); if (unit) { if (this.connection !== unit) { this.connect(unit); } } this.control.linearRampToValueAtTime(this.aLevel, t + this.aTime); // attack this.control.linearRampToValueAtTime(this.aLevel, t + this.aTime); // decay to sustain level this.control.linearRampToValueAtTime(this.dLevel, t + this.aTime + this.dTime); this.control.linearRampToValueAtTime(this.sLevel, t + this.aTime + this.dTime + this.sTime); }; /** * 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 */ p5.Env.prototype.triggerRelease = function (unit, secondsFromNow) { var now = p5sound.audiocontext.currentTime; var tFromNow = secondsFromNow || 0; var t = now + tFromNow + 0.00001; var relTime; if (unit) { if (this.connection !== unit) { this.connect(unit); } } this.control.cancelScheduledValues(t - 0.00001); // ideally would get & set currentValue here, // but this.control._scalar.gain.value not working in firefox // release based on how much time has passed since this.lastAttack if (now - this.lastAttack < this.aTime) { var a = this.aTime - (t - this.lastAttack); this.control.linearRampToValueAtTime(this.aLevel, t + a); this.control.linearRampToValueAtTime(this.dLevel, t + a + this.dTime); this.control.linearRampToValueAtTime(this.sLevel, t + a + this.dTime + this.sTime); this.control.linearRampToValueAtTime(this.rLevel, t + a + this.dTime + this.sTime + this.rTime); relTime = t + this.dTime + this.sTime + this.rTime; } else if (now - this.lastAttack < this.aTime + this.dTime) { var d = this.aTime + this.dTime - (now - this.lastAttack); this.control.linearRampToValueAtTime(this.dLevel, t + d); // this.control.linearRampToValueAtTime(this.sLevel, t + d + this.sTime); this.control.linearRampToValueAtTime(this.sLevel, t + d + 0.01); this.control.linearRampToValueAtTime(this.rLevel, t + d + 0.01 + this.rTime); relTime = t + this.sTime + this.rTime; } else if (now - this.lastAttack < this.aTime + this.dTime + this.sTime) { var s = this.aTime + this.dTime + this.sTime - (now - this.lastAttack); this.control.linearRampToValueAtTime(this.sLevel, t + s); this.control.linearRampToValueAtTime(this.rLevel, t + s + this.rTime); relTime = t + this.rTime; } else { this.control.linearRampToValueAtTime(this.sLevel, t); this.control.linearRampToValueAtTime(this.rLevel, t + this.rTime); relTime = t + this.dTime + this.sTime + this.rTime; } // clear osc / sources var clearTime = t + this.aTime + this.dTime + this.sTime + this.rTime; // * 1000; if (this.connection && this.connection.hasOwnProperty('oscillator')) { sourceToClear = this.connection.oscillator; sourceToClear.stop(clearTime + 0.01); } else if (this.connect && this.connection.hasOwnProperty('source')) { sourceToClear = this.connection.source; sourceToClear.stop(clearTime + 0.01); } }; p5.Env.prototype.connect = function (unit) { this.disconnect(); this.connection = unit; // assume we're talking about output gain // unless given a different audio param if (unit instanceof p5.Oscillator || unit instanceof p5.SoundFile || unit instanceof p5.AudioIn || unit instanceof p5.Reverb || unit instanceof p5.Noise || unit instanceof p5.Filter || unit instanceof p5.Delay) { unit = unit.output.gain; } if (unit instanceof AudioParam) { //set the initial value unit.setValueAtTime(0, p5sound.audiocontext.currentTime); } if (unit instanceof p5.Signal) { unit.setValue(0); } this.output.connect(unit); }; p5.Env.prototype.disconnect = function (unit) { this.output.disconnect(); }; // Signal Math /** * Add a value to the p5.Oscillator's output amplitude, * and return the oscillator. Calling this method * again will override the initial add() with new values. * * @method add * @param {Number} number Constant number to add * @return {p5.Env} Envelope Returns this envelope * with scaled output */ p5.Env.prototype.add = function (num) { var add = new Add(num); var thisChain = this.mathOps.length; var nextChain = this.output; return p5.prototype._mathChain(this, add, thisChain, nextChain, Add); }; /** * Multiply the p5.Env's output amplitude * by a fixed value. Calling this method * again will override the initial mult() with new values. * * @method mult * @param {Number} number Constant number to multiply * @return {p5.Env} Envelope Returns this envelope * with scaled output */ p5.Env.prototype.mult = function (num) { var mult = new Mult(num); var thisChain = this.mathOps.length; var nextChain = this.output; return p5.prototype._mathChain(this, mult, thisChain, nextChain, Mult); }; /** * Scale this envelope's amplitude values to a given * range, and return the envelope. Calling this method * again will override the initial scale() with new values. * * @method scale * @param {Number} inMin input range minumum * @param {Number} inMax input range maximum * @param {Number} outMin input range minumum * @param {Number} outMax input range maximum * @return {p5.Env} Envelope Returns this envelope * with scaled output */ p5.Env.prototype.scale = function (inMin, inMax, outMin, outMax) { var scale = new Scale(inMin, inMax, outMin, outMax); var thisChain = this.mathOps.length; var nextChain = this.output; return p5.prototype._mathChain(this, scale, thisChain, nextChain, Scale); }; // get rid of the oscillator p5.Env.prototype.dispose = function () { var now = p5sound.audiocontext.currentTime; this.disconnect(); this.control.dispose(); this.control = null; for (var i = 1; i < this.mathOps.length; i++) { mathOps[i].dispose(); } }; }(master, Tone_signal_Add, Tone_signal_Multiply, Tone_signal_Scale, Tone_core_Tone); var pulse; pulse = function () { 'use strict'; var p5sound = master; /** * Creates a Pulse object, an oscillator that implements * Pulse Width Modulation. * The pulse is created with two oscillators. * Accepts a parameter for frequency, and to set the * width between the pulses. See <a href=" * http://p5js.org/reference/#/p5.Oscillator"> * <code>p5.Oscillator</code> for a full list of methods. * * @class p5.Pulse * @constructor * @param {Number} [freq] Frequency in oscillations per second (Hz) * @param {Number} [w] Width between the pulses (0 to 1.0, * defaults to 0) * @example * <div><code> * var pulse; * function setup() { * background(0); * * // Create and start the pulse wave oscillator * pulse = new p5.Pulse(); * pulse.amp(0.5); * pulse.freq(220); * pulse.start(); * } * * function draw() { * var w = map(mouseX, 0, width, 0, 1); * w = constrain(w, 0, 1); * pulse.width(w) * } * </code></div> */ p5.Pulse = function (freq, w) { p5.Oscillator.call(this, freq, 'sawtooth'); // width of PWM, should be betw 0 to 1.0 this.w = w || 0; // create a second oscillator with inverse frequency this.osc2 = new p5.SawOsc(freq); // create a delay node this.dNode = p5sound.audiocontext.createDelay(); // dc offset this.dcOffset = createDCOffset(); this.dcGain = p5sound.audiocontext.createGain(); this.dcOffset.connect(this.dcGain); this.dcGain.connect(this.output); // set delay time based on PWM width this.f = freq || 440; var mW = this.w / this.oscillator.frequency.value; this.dNode.delayTime.value = mW; this.dcGain.gain.value = 1.7 * (0.5 - this.w); // disconnect osc2 and connect it to delay, which is connected to output this.osc2.disconnect(); this.osc2.output.gain.minValue = -10; this.osc2.output.gain.maxValue = 10; this.osc2.panner.disconnect(); this.osc2.amp(-1); // inverted amplitude this.osc2.output.connect(this.dNode); this.dNode.connect(this.output); this.output.gain.value = 1; this.output.connect(this.panner); }; p5.Pulse.prototype = Object.create(p5.Oscillator.prototype); /** * Set the width of a Pulse object (an oscillator that implements * Pulse Width Modulation). * * @method width * @param {Number} [width] Width between the pulses (0 to 1.0, * defaults to 0) */ p5.Pulse.prototype.width = function (w) { if (typeof w === 'number') { if (w <= 1 && w >= 0) { this.w = w; // set delay time based on PWM width // var mW = map(this.w, 0, 1.0, 0, 1/this.f); var mW = this.w / this.oscillator.frequency.value; this.dNode.delayTime.value = mW; } this.dcGain.gain.value = 1.7 * (0.5 - this.w); } else { w.connect(this.dNode.delayTime); var sig = new p5.SignalAdd(-0.5); sig.setInput(w); sig = sig.mult(-1); sig = sig.mult(1.7); sig.connect(this.dcGain.gain); } }; p5.Pulse.prototype.start = function (f, time) { var now = p5sound.audiocontext.currentTime; var t = time || 0; if (!this.started) { var freq = f || this.f; var type = this.oscillator.type; this.oscillator = p5sound.audiocontext.createOscillator(); this.oscillator.frequency.setValueAtTime(freq, now); this.oscillator.type = type; this.oscillator.connect(this.output); this.oscillator.start(t + now); // set up osc2 this.osc2.oscillator = p5sound.audiocontext.createOscillator(); this.osc2.oscillator.frequency.setValueAtTime(freq, t + now); this.osc2.oscillator.type = type; this.osc2.oscillator.connect(this.osc2.output); this.osc2.start(t + now); this.freqNode = [ this.oscillator.frequency, this.osc2.oscillator.frequency ]; // start dcOffset, too this.dcOffset = createDCOffset(); this.dcOffset.connect(this.dcGain); this.dcOffset.start(t + now); // if LFO connections depend on these oscillators if (this.mods !== undefined && this.mods.frequency !== undefined) { this.mods.frequency.connect(this.freqNode[0]); this.mods.frequency.connect(this.freqNode[1]); } this.started = true; this.osc2.started = true; } }; p5.Pulse.prototype.stop = function (time) { if (this.started) { var t = time || 0; var now = p5sound.audiocontext.currentTime; this.oscillator.stop(t + now); this.osc2.oscillator.stop(t + now); this.dcOffset.stop(t + now); this.started = false; this.osc2.started = false; } }; p5.Pulse.prototype.freq = function (val, rampTime, tFromNow) { if (typeof val === 'number') { this.f = val; var now = p5sound.audiocontext.currentTime; var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var currentFreq = this.oscillator.frequency.value; this.oscillator.frequency.cancelScheduledValues(now); this.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); this.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); this.osc2.oscillator.frequency.cancelScheduledValues(now); this.osc2.oscillator.frequency.setValueAtTime(currentFreq, now + tFromNow); this.osc2.oscillator.frequency.exponentialRampToValueAtTime(val, tFromNow + rampTime + now); if (this.freqMod) { this.freqMod.output.disconnect(); this.freqMod = null; } } else if (val.output) { val.output.disconnect(); val.output.connect(this.oscillator.frequency); val.output.connect(this.osc2.oscillator.frequency); this.freqMod = val; } }; // inspiration: http://webaudiodemos.appspot.com/oscilloscope/ function createDCOffset() { var ac = p5sound.audiocontext; var buffer = ac.createBuffer(1, 2048, ac.sampleRate); var data = buffer.getChannelData(0); for (var i = 0; i < 2048; i++) data[i] = 1; var bufferSource = ac.createBufferSource(); bufferSource.buffer = buffer; bufferSource.loop = true; return bufferSource; } }(master, oscillator); var noise; noise = function () { 'use strict'; var p5sound = master; /** * Noise is a type of oscillator that generates a buffer with random values. * * @class p5.Noise * @constructor * @param {String} type Type of noise can be 'white' (default), * 'brown' or 'pink'. * @return {Object} Noise Object */ p5.Noise = function (type) { p5.Oscillator.call(this); delete this.f; delete this.freq; delete this.oscillator; this.buffer = _whiteNoise; }; p5.Noise.prototype = Object.create(p5.Oscillator.prototype); // generate noise buffers var _whiteNoise = function () { var bufferSize = 2 * p5sound.audiocontext.sampleRate; var whiteBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); var noiseData = whiteBuffer.getChannelData(0); for (var i = 0; i < bufferSize; i++) { noiseData[i] = Math.random() * 2 - 1; } whiteBuffer.type = 'white'; return whiteBuffer; }(); var _pinkNoise = function () { var bufferSize = 2 * p5sound.audiocontext.sampleRate; var pinkBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); var noiseData = pinkBuffer.getChannelData(0); var b0, b1, b2, b3, b4, b5, b6; b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; for (var i = 0; i < bufferSize; i++) { var white = Math.random() * 2 - 1; b0 = 0.99886 * b0 + white * 0.0555179; b1 = 0.99332 * b1 + white * 0.0750759; b2 = 0.969 * b2 + white * 0.153852; b3 = 0.8665 * b3 + white * 0.3104856; b4 = 0.55 * b4 + white * 0.5329522; b5 = -0.7616 * b5 - white * 0.016898; noiseData[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; noiseData[i] *= 0.11; // (roughly) compensate for gain b6 = white * 0.115926; } pinkBuffer.type = 'pink'; return pinkBuffer; }(); var _brownNoise = function () { var bufferSize = 2 * p5sound.audiocontext.sampleRate; var brownBuffer = p5sound.audiocontext.createBuffer(1, bufferSize, p5sound.audiocontext.sampleRate); var noiseData = brownBuffer.getChannelData(0); var lastOut = 0; for (var i = 0; i < bufferSize; i++) { var white = Math.random() * 2 - 1; noiseData[i] = (lastOut + 0.02 * white) / 1.02; lastOut = noiseData[i]; noiseData[i] *= 3.5; } brownBuffer.type = 'brown'; return brownBuffer; }(); /** * Set type of noise to 'white', 'pink' or 'brown'. * White is the default. * * @method setType * @param {String} [type] 'white', 'pink' or 'brown' */ p5.Noise.prototype.setType = function (type) { switch (type) { case 'white': this.buffer = _whiteNoise; break; case 'pink': this.buffer = _pinkNoise; break; case 'brown': this.buffer = _brownNoise; break; default: this.buffer = _whiteNoise; } if (this.started) { var now = p5sound.audiocontext.currentTime; this.stop(now); this.start(now + 0.01); } }; p5.Noise.prototype.getType = function () { return this.buffer.type; }; /** * Start the noise * * @method start */ p5.Noise.prototype.start = function () { if (this.started) { this.stop(); } this.noise = p5sound.audiocontext.createBufferSource(); this.noise.buffer = this.buffer; this.noise.loop = true; this.noise.connect(this.output); var now = p5sound.audiocontext.currentTime; this.noise.start(now); this.started = true; }; /** * Stop the noise. * * @method stop */ p5.Noise.prototype.stop = function () { var now = p5sound.audiocontext.currentTime; if (this.noise) { this.noise.stop(now); this.started = false; } }; /** * Pan the noise. * * @method pan * @param {Number} panning Number between -1 (left) * and 1 (right) * @param {Number} timeFromNow schedule this event to happen * seconds from now */ /** * Set the amplitude of the noise between 0 and 1.0. Or, * modulate amplitude with an audio signal such as an oscillator. * * @param {Number|Object} volume amplitude between 0 and 1.0 * or modulating signal/oscillator * @param {Number} [rampTime] create a fade that lasts rampTime * @param {Number} [timeFromNow] schedule this event to happen * seconds from now */ /** * Send output to a p5.sound or web audio object * * @method connect * @param {Object} unit */ /** * Disconnect all output. * * @method disconnect */ p5.Noise.prototype.dispose = function () { var now = p5sound.audiocontext.currentTime; if (this.noise) { this.noise.disconnect(); this.stop(now); } if (this.output) { this.output.disconnect(); } if (this.panner) { this.panner.disconnect(); } this.output = null; this.panner = null; this.buffer = null; this.noise = null; }; }(master); var audioin; audioin = function () { 'use strict'; var p5sound = master; /** * <p>Get audio from an input, i.e. your computer's microphone.</p> * * <p>Turn the mic on/off with the start() and stop() methods. When the mic * is on, its volume can be measured with getLevel or by connecting an * FFT object.</p> * * <p>If you want to hear the AudioIn, use the .connect() method. * AudioIn does not connect to p5.sound output by default to prevent * feedback.</p> * * <p><em>Note: This uses the <a href="http://caniuse.com/stream">getUserMedia/ * Stream</a> API, which is not supported by certain browsers.</em></p> * * @class p5.AudioIn * @constructor * @return {Object} AudioIn * @example * <div><code> * var mic; * function setup(){ * mic = new p5.AudioIn() * mic.start(); * } * function draw(){ * background(0); * micLevel = mic.getLevel(); * ellipse(width/2, constrain(height-micLevel*height*5, 0, height), 10, 10); * } * </code></div> */ p5.AudioIn = function () { // set up audio input this.input = p5sound.audiocontext.createGain(); this.output = p5sound.audiocontext.createGain(); this.stream = null; this.mediaStream = null; this.currentSource = 0; /** * Client must allow browser to access their microphone / audioin source. * Default: false. Will become true when the client enables acces. * * @property {Boolean} enabled */ this.enabled = false; // create an amplitude, connect to it by default but not to master out this.amplitude = new p5.Amplitude(); this.output.connect(this.amplitude.input); // Some browsers let developer determine their input sources if (typeof window.MediaStreamTrack === 'undefined') { window.alert('This browser does not support MediaStreamTrack'); } else if (typeof window.MediaStreamTrack.getSources !== 'undefined') { // Chrome supports getSources to list inputs. Dev picks default window.MediaStreamTrack.getSources(this._gotSources); } else { } // add to soundArray so we can dispose on close p5sound.soundArray.push(this); }; /** * Start processing audio input. This enables the use of other * AudioIn methods like getLevel(). Note that by default, AudioIn * is not connected to p5.sound's output. So you won't hear * anything unless you use the connect() method.<br/> * * @method start */ p5.AudioIn.prototype.start = function () { var self = this; // if _gotSources() i.e. developers determine which source to use if (p5sound.inputSources[self.currentSource]) { // set the audio source var audioSource = p5sound.inputSources[self.currentSource].id; var constraints = { audio: { optional: [{ sourceId: audioSource }] } }; navigator.getUserMedia(constraints, this._onStream = function (stream) { 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); }, this._onStreamError = function (stream) { console.error(stream); }); } else { // if Firefox where users select their source via browser // if (typeof MediaStreamTrack.getSources === 'undefined') { // Only get the audio stream. window.navigator.getUserMedia({ 'audio': true }, this._onStream = function (stream) { self.stream = stream; self.enabled = true; // Wrap a MediaStreamSourceNode around the live input self.mediaStream = p5sound.audiocontext.createMediaStreamSource(stream); self.mediaStream.connect(self.output); // only send to the Amplitude reader, so we can see it but not hear it. self.amplitude.setInput(self.output); }, this._onStreamError = function (stream) { console.error(stream); }); } }; /** * Turn the AudioIn off. If the AudioIn is stopped, it cannot getLevel().<br/> * * @method stop */ p5.AudioIn.prototype.stop = function () { if (this.stream) { this.stream.stop(); } }; /** * Connect to an audio unit. If no parameter is provided, will * connect to the master output (i.e. your speakers).<br/> * * @method connect * @param {Object} [unit] An object that accepts audio input, * such as an FFT */ p5.AudioIn.prototype.connect = function (unit) { if (unit) { if (unit.hasOwnProperty('input')) { this.output.connect(unit.input); } else if (unit.hasOwnProperty('analyser')) { this.output.connect(unit.analyser); } else { this.output.connect(unit); } } else { this.output.connect(p5sound.input); } }; /** * Disconnect the AudioIn from all audio units. For example, if * connect() had been called, disconnect() will stop sending * signal to your speakers.<br/> * * @method disconnect */ p5.AudioIn.prototype.disconnect = function (unit) { this.output.disconnect(unit); // stay connected to amplitude even if not outputting to p5 this.output.connect(this.amplitude.input); }; /** * Read the Amplitude (volume level) of an AudioIn. The AudioIn * class contains its own instance of the Amplitude class to help * make it easy to get a microphone's volume level. Accepts an * optional smoothing value (0.0 < 1.0). <em>NOTE: AudioIn must * .start() before using .getLevel().</em><br/> * * @method getLevel * @param {Number} [smoothing] Smoothing is 0.0 by default. * Smooths values based on previous values. * @return {Number} Volume level (between 0.0 and 1.0) */ p5.AudioIn.prototype.getLevel = function (smoothing) { if (smoothing) { this.amplitude.smoothing = smoothing; } return this.amplitude.getLevel(); }; /** * Add input sources to the list of available sources. * * @private */ p5.AudioIn.prototype._gotSources = function (sourceInfos) { for (var i = 0; i !== sourceInfos.length; i++) { var sourceInfo = sourceInfos[i]; if (sourceInfo.kind === 'audio') { // add the inputs to inputSources p5sound.inputSources.push(sourceInfo); } } }; /** * 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. Some browsers * give the client the option to set their own media source. * Others allow JavaScript to determine which source, * and for this we have listSources() and setSource().<br/> * * @method listSources * @return {Array} */ p5.AudioIn.prototype.listSources = function () { console.log('input sources: '); console.log(p5sound.inputSources); if (p5sound.inputSources.length > 0) { return p5sound.inputSources; } else { return 'This browser does not support MediaStreamTrack.getSources()'; } }; /** * Set the input source. Accepts a number representing a * position in the array returned by listSources(). * This is only available in browsers that support * MediaStreamTrack.getSources(). Instead, some browsers * give users the option to set their own media source.<br/> * * @method setSource * @param {number} num position of input source in the array */ p5.AudioIn.prototype.setSource = function (num) { // TO DO - set input by string or # (array position) var self = this; if (p5sound.inputSources.length > 0 && num < p5sound.inputSources.length) { // set the current source self.currentSource = num; console.log('set source to ' + p5sound.inputSources[self.currentSource].id); } else { console.log('unable to set input source'); } }; // private method p5.AudioIn.prototype.dispose = function () { this.stop(); if (this.output) { this.output.disconnect(); } if (this.amplitude) { this.amplitude.disconnect(); } this.amplitude = null; this.output = null; }; }(master); var filter; filter = function () { 'use strict'; var p5sound = master; /** * A p5.Filter uses a Web Audio Biquad Filter to filter * the frequency response of an input source. Inheriting * classes include:<br/> * * <code>p5.LowPass</code> - allows frequencies below * the cutoff frequency to pass through, and attenuates * frequencies above the cutoff.<br/> * * <code>p5.HighPass</code> - the opposite of a lowpass * filter. <br/> * * <code>p5.BandPass</code> - allows a range of * frequencies to pass through and attenuates the frequencies * below and above this frequency range.<br/> * * The <code>.res()</code> method controls either width of the * bandpass, or resonance of the low/highpass cutoff frequency. * * @class p5.Filter * @constructor * @param {[String]} type 'lowpass' (default), 'highpass', 'bandpass' * @return {Object} p5.Filter * @example * <div><code> * var fft, noise, filter; * * function setup() { * fill(255, 40, 255); * * filter = new p5.BandPass(); * * noise = new p5.Noise(); * // disconnect unfiltered noise, * // and connect to filter * noise.disconnect(); * noise.connect(filter); * noise.start(); * * fft = new p5.FFT(); * } * * function draw() { * background(30); * * // set the BandPass frequency based on mouseX * var freq = map(mouseX, 0, width, 20, 10000); * filter.freq(freq); * // give the filter a narrow band (lower res = wider bandpass) * filter.res(50); * * // draw filtered spectrum * var spectrum = fft.analyze(); * noStroke(); * for (var i = 0; i < spectrum.length; i++) { * var x = map(i, 0, spectrum.length, 0, width); * var h = -height + map(spectrum[i], 0, 255, height, 0); * rect(x, height, width/spectrum.length, h); * } * } * </code></div> */ p5.Filter = function (type) { this.ac = p5sound.audiocontext; this.input = this.ac.createGain(); this.output = this.ac.createGain(); /** * The p5.Filter is built with a * <a href="http://www.w3.org/TR/webaudio/#BiquadFilterNode"> * Web Audio BiquadFilter Node</a>. * * @property biquadFilter * @type {Object} Web Audio Delay Node */ this.biquad = this.ac.createBiquadFilter(); this.input.connect(this.biquad); this.biquad.connect(this.output); this.connect(); if (type) { this.setType(type); } }; /** * Filter an audio signal according to a set * of filter parameters. * * @method process * @param {Object} Signal An object that outputs audio * @param {[Number]} freq Frequency in Hz, from 10 to 22050 * @param {[Number]} res Resonance/Width of the filter frequency * from 0.001 to 1000 */ p5.Filter.prototype.process = function (src, freq, res) { src.connect(this.input); this.set(freq, res); }; /** * Set the frequency and the resonance of the filter. * * @method set * @param {Number} freq Frequency in Hz, from 10 to 22050 * @param {Number} res Resonance (Q) from 0.001 to 1000 * @param {Number} [timeFromNow] schedule this event to happen * seconds from now */ p5.Filter.prototype.set = function (freq, res, time) { if (freq) { this.freq(freq, time); } if (res) { this.res(res, time); } }; /** * Set the filter frequency, in Hz, from 10 to 22050 (the range of * human hearing, although in reality most people hear in a narrower * range). * * @method freq * @param {Number} freq Filter Frequency * @param {Number} [timeFromNow] schedule this event to happen * seconds from now * @return {Number} value Returns the current frequency value */ p5.Filter.prototype.freq = function (freq, time) { var self = this; var t = time || 0; if (freq <= 0) { freq = 1; } if (typeof freq === 'number') { self.biquad.frequency.value = freq; self.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t); self.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t); } else if (freq) { freq.connect(this.biquad.frequency); } return self.biquad.frequency.value; }; /** * Controls either width of a bandpass frequency, * or the resonance of a low/highpass cutoff frequency. * * @method res * @param {Number} res Resonance/Width of filter freq * from 0.001 to 1000 * @param {Number} [timeFromNow] schedule this event to happen * seconds from now * @return {Number} value Returns the current res value */ p5.Filter.prototype.res = function (res, time) { var self = this; var t = time || 0; if (typeof res == 'number') { self.biquad.Q.value = res; self.biquad.Q.cancelScheduledValues(self.ac.currentTime + 0.01 + t); self.biquad.Q.linearRampToValueAtTime(res, self.ac.currentTime + 0.02 + t); } else if (res) { freq.connect(this.biquad.Q); } return self.biquad.Q.value; }; /** * Set the type of a p5.Filter. Possible types include: * "lowpass" (default), "highpass", "bandpass", * "lowshelf", "highshelf", "peaking", "notch", * "allpass". * * @method setType * @param {String} */ p5.Filter.prototype.setType = function (t) { this.biquad.type = t; }; /** * Set the output level of the filter. * * @method amp * @param {Number} volume amplitude between 0 and 1.0 * @param {Number} [rampTime] create a fade that lasts rampTime * @param {Number} [timeFromNow] schedule this event to happen * seconds from now */ p5.Filter.prototype.amp = function (vol, rampTime, tFromNow) { var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var now = p5sound.audiocontext.currentTime; var currentVol = this.output.gain.value; this.output.gain.cancelScheduledValues(now); this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); }; /** * Send output to a p5.sound or web audio object * * @method connect * @param {Object} unit */ p5.Filter.prototype.connect = function (unit) { var u = unit || p5.soundOut.input; this.output.connect(u); }; /** * Disconnect all output. * * @method disconnect */ p5.Filter.prototype.disconnect = function () { this.output.disconnect(); }; /** * Constructor: <code>new p5.LowPass()</code> Filter. * This is the same as creating a p5.Filter and then calling * its method <code>setType('lowpass')</code>. * See p5.Filter for methods. * * @method p5.LowPass */ p5.LowPass = function () { p5.Filter.call(this, 'lowpass'); }; p5.LowPass.prototype = Object.create(p5.Filter.prototype); /** * Constructor: <code>new p5.HighPass()</code> Filter. * This is the same as creating a p5.Filter and then calling * its method <code>setType('highpass')</code>. * See p5.Filter for methods. * * @method p5.HighPass */ p5.HighPass = function () { p5.Filter.call(this, 'highpass'); }; p5.HighPass.prototype = Object.create(p5.Filter.prototype); /** * Constructor: <code>new p5.BandPass()</code> Filter. * This is the same as creating a p5.Filter and then calling * its method <code>setType('bandpass')</code>. * See p5.Filter for methods. * * @method p5.BandPass */ p5.BandPass = function () { p5.Filter.call(this, 'bandpass'); }; p5.BandPass.prototype = Object.create(p5.Filter.prototype); }(master); var delay; delay = function () { 'use strict'; var p5sound = master; var Filter = filter; /** * Delay is an echo effect. It processes an existing sound source, * and outputs a delayed version of that sound. The p5.Delay can * produce different effects depending on the delayTime, feedback, * filter, and type. In the example below, a feedback of 0.5 will * produce a looping delay that decreases in volume by * 50% each repeat. A filter will cut out the high frequencies so * that the delay does not sound as piercing as the original source. * * @class p5.Delay * @constructor * @return {Object} Returns a p5.Delay object * @example * <div><code> * var noise, env, delay; * * function setup() { * noise = new p5.Noise('brown'); * noise.start(); * * delay = new p5.Delay(); * * // delay.process() accepts 4 parameters: * // source, delayTime, feedback, filter frequency * // play with these numbers!! * delay.process(noise, .12, .7, 2300); * * // play the noise with an envelope, * // a series of fades ( time / value pairs ) * env = new p5.Env(.01, 1, .2, .1); * env.play(noise); * } * </code></div> */ p5.Delay = function () { this.ac = p5sound.audiocontext; this.input = this.ac.createGain(); this.output = this.ac.createGain(); this._split = this.ac.createChannelSplitter(2); this._merge = this.ac.createChannelMerger(2); this._leftGain = this.ac.createGain(); this._rightGain = this.ac.createGain(); /** * The p5.Delay is built with two * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> * Web Audio Delay Nodes</a>, one for each stereo channel. * * @property leftDelay * @type {Object} Web Audio Delay Node */ this.leftDelay = this.ac.createDelay(); /** * The p5.Delay is built with two * <a href="http://www.w3.org/TR/webaudio/#DelayNode"> * Web Audio Delay Nodes</a>, one for each stereo channel. * * @property rightDelay * @type {Object} Web Audio Delay Node */ this.rightDelay = this.ac.createDelay(); this._leftFilter = new p5.Filter(); this._rightFilter = new p5.Filter(); this._leftFilter.disconnect(); this._rightFilter.disconnect(); /** * Internal filter. Set to lowPass by default, but can be accessed directly. * See p5.Filter for methods. Or use the p5.Delay.filter() method to change * frequency and q. * * @property lowPass * @type {p5.Filter} */ this.lowPass = this._leftFilter; this._leftFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); this._rightFilter.biquad.frequency.setValueAtTime(1200, this.ac.currentTime); this._leftFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); this._rightFilter.biquad.Q.setValueAtTime(0.3, this.ac.currentTime); // graph routing this.input.connect(this._split); this.leftDelay.connect(this._leftGain); this.rightDelay.connect(this._rightGain); this._leftGain.connect(this._leftFilter.input); this._rightGain.connect(this._rightFilter.input); this._merge.connect(this.output); this.output.connect(p5.soundOut.input); this._leftFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); this._rightFilter.biquad.gain.setValueAtTime(1, this.ac.currentTime); // default routing this.setType(0); this._maxDelay = this.leftDelay.delayTime.maxValue; }; /** * Add delay to an audio signal according to a set * of delay parameters. * * @method process * @param {Object} Signal An object that outputs audio * @param {Number} [delayTime] Time (in seconds) of the delay/echo. * Some browsers limit delayTime to * 1 second. * @param {Number} [feedback] sends the delay back through itself * in a loop that decreases in volume * each time. * @param {Number} [lowPass] Cutoff frequency. Only frequencies * below the lowPass will be part of the * delay. */ p5.Delay.prototype.process = function (src, _delayTime, _feedback, _filter) { var feedback = _feedback || 0; var delayTime = _delayTime || 0; if (feedback >= 1) { throw new Error('Feedback value will force a positive feedback loop.'); } if (delayTime >= this._maxDelay) { throw new Error('Delay Time exceeds maximum delay time of ' + this._maxDelay + ' second.'); } src.connect(this.input); this.leftDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); this.rightDelay.delayTime.setValueAtTime(delayTime, this.ac.currentTime); this._leftGain.gain.setValueAtTime(feedback, this.ac.currentTime); this._rightGain.gain.setValueAtTime(feedback, this.ac.currentTime); if (_filter) { this._leftFilter.freq(_filter); this._rightFilter.freq(_filter); } }; /** * Set the delay (echo) time, in seconds. Usually this value will be * a floating point number between 0.0 and 1.0. * * @method delayTime * @param {Number} delayTime Time (in seconds) of the delay */ p5.Delay.prototype.delayTime = function (t) { // if t is an audio node... if (typeof t !== 'number') { t.connect(this.leftDelay.delayTime); t.connect(this.rightDelay.delayTime); } else { this.leftDelay.delayTime.cancelScheduledValues(this.ac.currentTime); this.rightDelay.delayTime.cancelScheduledValues(this.ac.currentTime); this.leftDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); this.rightDelay.delayTime.linearRampToValueAtTime(t, this.ac.currentTime); } }; /** * Feedback occurs when Delay sends its signal back through its input * in a loop. The feedback amount determines how much signal to send each * time through the loop. A feedback greater than 1.0 is not desirable because * it will increase the overall output each time through the loop, * creating an infinite feedback loop. * * @method feedback * @param {Number|Object} feedback 0.0 to 1.0, or an object such as an * Oscillator that can be used to * modulate this param */ p5.Delay.prototype.feedback = function (f) { // if f is an audio node... if (typeof f !== 'number') { f.connect(this._leftGain.gain); f.connect(this._rightGain.gain); } else if (f >= 1) { throw new Error('Feedback value will force a positive feedback loop.'); } else { this._leftGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); this._rightGain.gain.exponentialRampToValueAtTime(f, this.ac.currentTime); } }; /** * Set a lowpass filter frequency for the delay. A lowpass filter * will cut off any frequencies higher than the filter frequency. * * @method filter * @param {Number|Object} cutoffFreq A lowpass filter will cut off any * frequencies higher than the filter frequency. * @param {Number|Object} res Resonance of the filter frequency * cutoff, or an object (i.e. a p5.Oscillator) * that can be used to modulate this parameter. * High numbers (i.e. 15) will produce a resonance, * low numbers (i.e. .2) will produce a slope. */ p5.Delay.prototype.filter = function (freq, q) { this._leftFilter.set(freq, q); this._rightFilter.set(freq, q); }; /** * Choose a preset type of delay. 'pingPong' bounces the signal * from the left to the right channel to produce a stereo effect. * Any other parameter will revert to the default delay setting. * * @method setType * @param {String|Number} type 'pingPong' (1) or 'default' (0) */ p5.Delay.prototype.setType = function (t) { if (t === 1) { t = 'pingPong'; } this._split.disconnect(); this._leftFilter.disconnect(); this._rightFilter.disconnect(); this._split.connect(this.leftDelay, 0); this._split.connect(this.rightDelay, 1); switch (t) { case 'pingPong': this._rightFilter.setType(this._leftFilter.biquad.type); this._leftFilter.output.connect(this._merge, 0, 0); this._rightFilter.output.connect(this._merge, 0, 1); this._leftFilter.output.connect(this.rightDelay); this._rightFilter.output.connect(this.leftDelay); break; default: this._leftFilter.output.connect(this._merge, 0, 0); this._leftFilter.output.connect(this._merge, 0, 1); this._leftFilter.output.connect(this.leftDelay); this._leftFilter.output.connect(this.rightDelay); } }; /** * Set the output level of the delay effect. * * @method amp * @param {Number} volume amplitude between 0 and 1.0 * @param {Number} [rampTime] create a fade that lasts rampTime * @param {Number} [timeFromNow] schedule this event to happen * seconds from now */ p5.Delay.prototype.amp = function (vol, rampTime, tFromNow) { var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var now = p5sound.audiocontext.currentTime; var currentVol = this.output.gain.value; this.output.gain.cancelScheduledValues(now); this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); }; /** * Send output to a p5.sound or web audio object * * @method connect * @param {Object} unit */ p5.Delay.prototype.connect = function (unit) { var u = unit || p5.soundOut.input; this.output.connect(u); }; /** * Disconnect all output. * * @method disconnect */ p5.Delay.prototype.disconnect = function () { this.output.disconnect(); }; }(master, filter); var reverb; reverb = function () { 'use strict'; var p5sound = master; /** * Reverb adds depth to a sound through a large number of decaying * echoes. It creates the perception that sound is occurring in a * physical space. The p5.Reverb has paramters for Time (how long does the * reverb last) and decayRate (how much the sound decays with each echo) * that can be set with the .set() or .process() methods. The p5.Convolver * extends p5.Reverb allowing you to recreate the sound of actual physical * spaces through convolution. * * @class p5.Reverb * @constructor * @example * <div><code> * var soundFile, reverb; * function preload() { * soundFile = loadSound('assets/Damscray_DancingTiger.mp3'); * } * * function setup() { * reverb = new p5.Reverb(); * soundFile.disconnect(); // so we'll only hear reverb... * * // connect soundFile to reverb, process w/ * // 3 second reverbTime, decayRate of 2% * reverb.process(soundFile, 3, 2); * soundFile.play(); * } * </code></div> */ p5.Reverb = function () { this.ac = p5sound.audiocontext; this.convolverNode = this.ac.createConvolver(); this.input = this.ac.createGain(); this.output = this.ac.createGain(); // otherwise, Safari distorts this.input.gain.value = 0.5; this.input.connect(this.convolverNode); this.convolverNode.connect(this.output); // default params this._seconds = 3; this._decay = 2; this._reverse = false; this._buildImpulse(); this.connect(); p5sound.soundArray.push(this); }; /** * Connect a source to the reverb, and assign reverb parameters. * * @method process * @param {Object} src p5.sound / Web Audio object with a sound * output. * @param {[Number]} seconds Duration of the reverb, in seconds. * Min: 0, Max: 10. Defaults to 3. * @param {[Number]} decayRate Percentage of decay with each echo. * Min: 0, Max: 100. Defaults to 2. * @param {[Boolean]} reverse Play the reverb backwards or forwards. */ p5.Reverb.prototype.process = function (src, seconds, decayRate, reverse) { src.connect(this.input); var rebuild = false; if (seconds) { this._seconds = seconds; rebuild = true; } if (decayRate) { this._decay = decayRate; } if (reverse) { this._reverse = reverse; } if (rebuild) { this._buildImpulse(); } }; /** * Set the reverb settings. Similar to .process(), but without * assigning a new input. * * @method set * @param {[Number]} seconds Duration of the reverb, in seconds. * Min: 0, Max: 10. Defaults to 3. * @param {[Number]} decayRate Percentage of decay with each echo. * Min: 0, Max: 100. Defaults to 2. * @param {[Boolean]} reverse Play the reverb backwards or forwards. */ p5.Reverb.prototype.set = function (seconds, decayRate, reverse) { var rebuild = false; if (seconds) { this._seconds = seconds; rebuild = true; } if (decayRate) { this._decay = decayRate; } if (reverse) { this._reverse = reverse; } if (rebuild) { this._buildImpulse(); } }; /** * Set the output level of the delay effect. * * @method amp * @param {Number} volume amplitude between 0 and 1.0 * @param {Number} [rampTime] create a fade that lasts rampTime * @param {Number} [timeFromNow] schedule this event to happen * seconds from now */ p5.Reverb.prototype.amp = function (vol, rampTime, tFromNow) { var rampTime = rampTime || 0; var tFromNow = tFromNow || 0; var now = p5sound.audiocontext.currentTime; var currentVol = this.output.gain.value; this.output.gain.cancelScheduledValues(now); this.output.gain.linearRampToValueAtTime(currentVol, now + tFromNow + 0.001); this.output.gain.linearRampToValueAtTime(vol, now + tFromNow + rampTime + 0.001); }; /** * Send output to a p5.sound or web audio object * * @method connect * @param {Object} unit */ p5.Reverb.prototype.connect = function (unit) { var u = unit || p5.soundOut.input; this.output.connect(u.input ? u.input : u); }; /** * Disconnect all output. * * @method disconnect */ p5.Reverb.prototype.disconnect = function () { this.output.disconnect(); }; /** * Inspired by Simple Reverb by Jordan Santell * https://github.com/web-audio-components/simple-reverb/blob/master/index.js * * Utility function for building an impulse response * based on the module parameters. * * @private */ p5.Reverb.prototype._buildImpulse = function () { var rate = this.ac.sampleRate; var length = rate * this._seconds; var decay = this._decay; var impulse = this.ac.createBuffer(2, length, rate); var impulseL = impulse.getChannelData(0); var impulseR = impulse.getChannelData(1); var n, i; for (i = 0; i < length; i++) { n = this.reverse ? length - i : i; impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay); } this.convolverNode.buffer = impulse; }; p5.Reverb.prototype.dispose = function () { this.convolverNode.buffer = null; this.convolverNode = null; if (typeof this.output !== 'undefined') { this.output.disconnect(); this.output = null; } if (typeof this.panner !== 'undefined') { this.panner.disconnect(); this.panner = null; } }; // ======================================================================= // *** p5.Convolver *** // ======================================================================= /** * <p>p5.Convolver extends p5.Reverb. It can emulate the sound of real * physical spaces through a process called <a href=" * https://en.wikipedia.org/wiki/Convolution_reverb#Real_space_simulation"> * convolution</a>.</p> * * <p>Convolution multiplies any audio input by an "impulse response" * to simulate the dispersion of sound over time. The impulse response is * generated from an audio file that you provide. One way to * generate an impulse response is to pop a balloon in a reverberant space * and record the echo. Convolution can also be used to experiment with * sound.</p> * * <p>Use the method <code>createConvolution(path)</code> to instantiate a * p5.Convolver with a path to your impulse response audio file.</p> * * @class p5.Convolver * @constructor * @param {String} path path to a sound file * @param {[Function]} callback function (optional) * @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) { this.ac = p5sound.audiocontext; /** * Internally, the p5.Convolver uses the a * <a href="http://www.w3.org/TR/webaudio/#ConvolverNode"> * Web Audio Convolver Node</a>. * * @property convolverNode * @type {Object} Web Audio Convolver Node */ this.convolverNode = this.ac.createConvolver(); this.input = this.ac.createGain(); this.output = this.ac.createGain(); // otherwise, Safari distorts this.input.gain.value = 0.5; this.input.connect(this.convolverNode); this.convolverNode.connect(this.output); if (path) { this.impulses = []; this._loadBuffer(path, callback); } else { // parameters this._seconds = 3; this._decay = 2; this._reverse = false; this._buildImpulse(); } this.connect(); p5sound.soundArray.push(this); }; p5.Convolver.prototype = Object.create(p5.Reverb.prototype); p5.prototype.registerPreloadMethod('createConvolver'); /** * 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 (optional) * @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) { // if loading locally without a server if (window.location.origin.indexOf('file://') > -1) { alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); } var cReverb = new p5.Convolver(path, callback); 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 * @private */ p5.Convolver.prototype._loadBuffer = function (path, callback) { path = p5.prototype._checkFileFormats(path); var request = new XMLHttpRequest(); request.open('GET', path, true); request.responseType = 'arraybuffer'; // decode asyncrohonously var self = this; request.onload = function () { var ac = p5.prototype.getAudioContext(); ac.decodeAudioData(request.response, function (buff) { var buffer = {}; var chunks = path.split('/'); buffer.name = chunks[chunks.length - 1]; buffer.audioBuffer = buff; self.impulses.push(buffer); self.convolverNode.buffer = buffer.audioBuffer; if (callback) { callback(buffer); } }); }; request.send(); }; p5.Convolver.prototype.set = null; /** * Connect a source to the reverb, and assign reverb parameters. * * @method process * @param {Object} src p5.sound / Web Audio object with a sound * output. * @example * <div><code> * var cVerb, sound; * function preload() { * soundFormats('ogg', 'mp3'); * * cVerb = createConvolver('assets/concrete-tunnel.mp3'); * * sound = loadSound('assets/beat.mp3'); * } * * function setup() { * // disconnect from master output... * sound.disconnect(); * * // ...and process with (i.e. connect to) cVerb * // so that we only hear the convolution * cVerb.process(sound); * * sound.play(); * } * </code></div> */ p5.Convolver.prototype.process = function (src) { src.connect(this.input); }; /** * If you load multiple impulse files using the .addImpulse method, * they will be stored as Objects in this Array. Toggle between them * with the <code>toggleImpulse(id)</code> method. * * @property impulses * @type {Array} Array of Web Audio Buffers */ p5.Convolver.prototype.impulses = []; /** * Load and assign a new Impulse Response to the p5.Convolver. * The impulse is added to the <code>.impulses</code> array. Previous * impulses can be accessed with the <code>.toggleImpulse(id)</code> * method. * * @method addImpulse * @param {String} path path to a sound file * @param {[Function]} callback function (optional) */ p5.Convolver.prototype.addImpulse = function (path, callback) { // if loading locally without a server if (window.location.origin.indexOf('file://') > -1) { alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); } this._loadBuffer(path, callback); }; /** * 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) */ p5.Convolver.prototype.resetImpulse = function (path, callback) { // if loading locally without a server if (window.location.origin.indexOf('file://') > -1) { alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); } this.impulses = []; this._loadBuffer(path, callback); }; /** * If you have used <code>.addImpulse()</code> to add multiple impulses * to a p5.Convolver, then you can use this method to toggle between * the items in the <code>.impulses</code> Array. Accepts a parameter * to identify which impulse you wish to use, identified either by its * original filename (String) or by its position in the <code>.impulses * </code> Array (Number).<br/> * You can access the objects in the .impulses Array directly. Each * Object has two attributes: an <code>.audioBuffer</code> (type: * Web Audio <a href=" * http://webaudio.github.io/web-audio-api/#the-audiobuffer-interface"> * AudioBuffer)</a> and a <code>.name</code>, a String that corresponds * with the original filename. * * @method toggleImpulse * @param {String|Number} id Identify the impulse by its original filename * (String), or by its position in the * <code>.impulses</code> Array (Number). */ p5.Convolver.prototype.toggleImpulse = function (id) { if (typeof id === 'number' && id < this.impulses.length) { this.convolverNode.buffer = this.impulses[id].audioBuffer; } if (typeof id === 'string') { for (var i = 0; i < this.impulses.length; i++) { if (this.impulses[i].name === id) { this.convolverNode.buffer = this.impulses[i].audioBuffer; break; } } } }; p5.Convolver.prototype.dispose = function () { // remove all the Impulse Response buffers for (var i in this.impulses) { this.impulses[i] = null; } this.convolverNode.disconnect(); this.concolverNode = null; if (typeof this.output !== 'undefined') { this.output.disconnect(); this.output = null; } if (typeof this.panner !== 'undefined') { this.panner.disconnect(); this.panner = null; } }; }(master, sndcore); /** Tone.js module by Yotam Mann, MIT License 2014 http://opensource.org/licenses/MIT **/ var Tone_core_Clock; Tone_core_Clock = function (Tone) { 'use strict'; Tone.Clock = function (rate, callback) { this._oscillator = null; this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); this._jsNode.onaudioprocess = this._processBuffer.bind(this); this._controlSignal = new Tone.Signal(1); this._upTick = false; this.tick = this.defaultArg(callback, function () { }); this._jsNode.noGC(); this.setRate(rate); }; Tone.extend(Tone.Clock); Tone.Clock.prototype.setRate = function (rate, rampTime) { var freqVal = this.secondsToFrequency(this.toSeconds(rate)); if (!rampTime) { this._controlSignal.cancelScheduledValues(0); this._controlSignal.setValue(freqVal); } else { this._controlSignal.exponentialRampToValueNow(freqVal, rampTime); } }; Tone.Clock.prototype.getRate = function () { return this._controlSignal.getValue(); }; Tone.Clock.prototype.start = function (time) { this._oscillator = this.context.createOscillator(); this._oscillator.type = 'square'; this._oscillator.connect(this._jsNode); this._controlSignal.connect(this._oscillator.frequency); this._upTick = false; var startTime = this.toSeconds(time); this._oscillator.start(startTime); this._oscillator.onended = function () { }; }; Tone.Clock.prototype.stop = function (time, onend) { var stopTime = this.toSeconds(time); this._oscillator.onended = onend; this._oscillator.stop(stopTime); }; Tone.Clock.prototype._processBuffer = function (event) { var now = this.defaultArg(event.playbackTime, this.now()); var bufferSize = this._jsNode.bufferSize; var incomingBuffer = event.inputBuffer.getChannelData(0); var upTick = this._upTick; var self = this; for (var i = 0; i < bufferSize; i++) { var sample = incomingBuffer[i]; if (sample > 0 && !upTick) { upTick = true; setTimeout(function () { var tickTime = now + self.samplesToSeconds(i + bufferSize * 2); return function () { self.tick(tickTime); }; }(), 0); } else if (sample < 0 && upTick) { upTick = false; } } this._upTick = upTick; }; Tone.Clock.prototype.dispose = function () { this._jsNode.disconnect(); this._controlSignal.dispose(); if (this._oscillator) { this._oscillator.onended(); this._oscillator.disconnect(); } this._jsNode.onaudioprocess = function () { }; this._jsNode = null; this._controlSignal = null; this._oscillator = null; }; return Tone.Clock; }(Tone_core_Tone); var metro; metro = function () { 'use strict'; var p5sound = master; // requires the Tone.js library's Clock (MIT license, Yotam Mann) // https://github.com/TONEnoTONE/Tone.js/ var Clock = Tone_core_Clock; var ac = p5sound.audiocontext; // var upTick = false; p5.Metro = function () { this.clock = new Clock(ac.sampleRate, this.ontick.bind(this)); this.syncedParts = []; this.bpm = 120; // gets overridden by p5.Part this._init(); this.tickCallback = function () { }; }; var prevTick = 0; var tatumTime = 0; p5.Metro.prototype.ontick = function (tickTime) { var elapsedTime = tickTime - prevTick; var secondsFromNow = tickTime - p5sound.audiocontext.currentTime; if (elapsedTime - tatumTime <= -0.02) { return; } else { prevTick = tickTime; // for all of the active things on the metro: for (var i in this.syncedParts) { var thisPart = this.syncedParts[i]; thisPart.incrementStep(secondsFromNow); // each synced source keeps track of its own beat number for (var j in thisPart.phrases) { var thisPhrase = thisPart.phrases[j]; var phraseArray = thisPhrase.sequence; var bNum = this.metroTicks % phraseArray.length; if (phraseArray[bNum] !== 0 && (this.metroTicks < phraseArray.length || !thisPhrase.looping)) { thisPhrase.callback(phraseArray[bNum], secondsFromNow); } } } this.metroTicks += 1; this.tickCallback(secondsFromNow); } }; p5.Metro.prototype.setBPM = function (bpm, rampTime) { var beatTime = 60 / (bpm * this.tatums); tatumTime = beatTime; var ramp = rampTime || 0; this.clock.setRate(beatTime, rampTime + p5sound.audiocontext.currentTime); this.bpm = bpm; }; p5.Metro.prototype.getBPM = function (tempo) { return this.clock.getRate() / this.tatums * 60; }; p5.Metro.prototype._init = function () { this.metroTicks = 0; }; // clear existing synced parts, add only this one p5.Metro.prototype.resetSync = function (part) { this.syncedParts = [part]; }; // push a new synced part to the array p5.Metro.prototype.pushSync = function (part) { this.syncedParts.push(part); }; p5.Metro.prototype.start = function (time) { var t = time || 0; this.clock.start(t); this.setBPM(this.bpm); }; p5.Metro.prototype.stop = function (time) { var t = time || 0; if (this.clock._oscillator) { this.clock.stop(t); } }; p5.Metro.prototype.beatLength = function (tatums) { this.tatums = 1 / tatums / 4; }; }(master, Tone_core_Clock); var looper; looper = function () { 'use strict'; var p5sound = master; var bpm = 120; /** * Set the global tempo, in beats per minute, for all * p5.Parts. This method will impact all active p5.Parts. * * @param {Number} BPM Beats Per Minute * @param {Number} rampTime Seconds from now */ p5.prototype.setBPM = function (BPM, rampTime) { bpm = BPM; for (var i in p5sound.parts) { p5sound.parts[i].setBPM(bpm, rampTime); } }; /** * <p>A phrase is a pattern of musical events over time, i.e. * a series of notes and rests.</p> * * <p>Phrases must be added to a p5.Part for playback, and * each part can play multiple phrases at the same time. * For example, one Phrase might be a kick drum, another * could be a snare, and another could be the bassline.</p> * * <p>The first parameter is a name so that the phrase can be * modified or deleted later. The callback is a a function that * this phrase will call at every step—for example it might be * called <code>playNote(value){}</code>. The array determines * which value is passed into the callback at each step of the * phrase. It can be numbers, an object with multiple numbers, * or a zero (0) indicates a rest so the callback won't be called).</p> * * @class p5.Phrase * @constructor * @param {String} name Name so that you can access the Phrase. * @param {Function} callback The name of a function that this phrase * will call. Typically it will play a sound, * and accept two parameters: a value from the * sequence array, followed by a time at which * to play the sound. * @param {Array} sequence Array of values to pass into the callback * at each step of the phrase. * @example * <div><code> * var mySound; * var pattern = [1,0,0,2,0,2,0,0]; * * function preload() { * mySound = loadSound('assets/beatbox.mp3'); * } * * function setup() { * var myPhrase = new p5.Phrase('bbox', makeSound, pattern); * var myPart = new p5.Part(); * myPart.addPhrase(myPhrase); * myPart.setBPM(60); * myPart.start(); * } * * function makeSound(time, playbackRate) { * mySound.rate(playbackRate); * mySound.play(time); * } * </code></div> */ p5.Phrase = function (name, callback, sequence) { this.phraseStep = 0; this.name = name; this.callback = callback; /** * Array of values to pass into the callback * at each step of the phrase. Depending on the callback * function's requirements, these values may be numbers, * strings, or an object with multiple parameters. * Zero (0) indicates a rest. * * @property sequence * @type {Array} */ this.sequence = sequence; }; /** * A p5.Part plays back one or more p5.Phrases. Instantiate a part * with steps and tatums. By default, each step represents 1/16th note. * * @class p5.Part * @constructor * @param {Number} [steps] Steps in the part * @param {Number} [tatums] Divisions of a beat (default is 1/16, a quarter note) * @example * <div><code> * var box, drum; * var boxPat = [1,0,0,2,0,2,0,0]; * var drumPat = [0,1,1,0,2,0,1,0]; * * function preload() { * box = loadSound('assets/beatbox.mp3'); * drum = loadSound('assets/drum.mp3'); * } * * function setup() { * var boxPhrase = new p5.Phrase('box', playBox, boxPat); * var drumPhrase = new p5.Phrase('drum', playDrum, drumPat); * var myPart = new p5.Part(); * myPart.addPhrase(boxPhrase); * myPart.addPhrase(drumPhrase); * myPart.setBPM(60); * myPart.start(); * } * * function playBox(playbackRate, time) { * box.rate(playbackRate); * box.play(time); * } * * function playDrum(playbackRate, time) { * drum.rate(playbackRate); * drum.play(time); * } * </code></div> */ p5.Part = function (steps, bLength) { this.length = steps || 0; // how many beats this.partStep = 0; this.phrases = []; this.looping = false; this.isPlaying = false; // what does this looper do when it gets to the last step? this.onended = function () { this.stop(); }; this.tatums = bLength || 0.0625; // defaults to quarter note this.metro = new p5.Metro(); this.metro._init(); this.metro.beatLength(this.tatums); this.metro.setBPM(bpm); p5sound.parts.push(this); this.callback = function () { }; }; /** * Set the tempo of this part, in Beats Per Minute. * * @method setBPM * @param {Number} BPM Beats Per Minute * @param {Number} [rampTime] Seconds from now */ p5.Part.prototype.setBPM = function (tempo, rampTime) { this.metro.setBPM(tempo, rampTime); }; /** * Returns the Beats Per Minute of this currently part. * * @method getBPM * @return {Number} */ p5.Part.prototype.getBPM = function () { return this.metro.getBPM(); }; /** * Start playback of this part. It will play * through all of its phrases at a speed * determined by setBPM. * * @method start * @param {Number} [time] seconds from now */ p5.Part.prototype.start = function (time) { if (!this.isPlaying) { this.isPlaying = true; this.metro.resetSync(this); var t = time || 0; this.metro.start(t); } }; /** * Loop playback of this part. It will begin * looping through all of its phrases at a speed * determined by setBPM. * * @method loop * @param {Number} [time] seconds from now */ p5.Part.prototype.loop = function (time) { this.looping = true; // rest onended function this.onended = function () { this.partStep = 0; }; var t = time || 0; this.start(t); }; /** * Tell the part to stop looping. * * @method noLoop */ p5.Part.prototype.noLoop = function () { this.looping = false; // rest onended function this.onended = function () { this.stop(); }; }; /** * Stop the part and cue it to step 0. * * @method stop * @param {Number} [time] seconds from now */ p5.Part.prototype.stop = function (time) { this.partStep = 0; this.pause(time); }; /** * Pause the part. Playback will resume * from the current step. * * @method pause * @param {Number} time seconds from now */ p5.Part.prototype.pause = function (time) { this.isPlaying = false; var t = time || 0; this.metro.stop(t); }; /** * Add a p5.Phrase to this Part. * * @method addPhrase * @param {p5.Phrase} phrase reference to a p5.Phrase */ p5.Part.prototype.addPhrase = function (name, callback, array) { var p; if (arguments.length === 3) { p = new p5.Phrase(name, callback, array); } else if (arguments[0] instanceof p5.Phrase) { p = arguments[0]; } else { throw 'invalid input. addPhrase accepts name, callback, array or a p5.Phrase'; } this.phrases.push(p); // reset the length if phrase is longer than part's existing length if (p.sequence.length > this.length) { this.length = p.sequence.length; } }; /** * Remove a phrase from this part, based on the name it was * given when it was created. * * @method removePhrase * @param {String} phraseName */ p5.Part.prototype.removePhrase = function (name) { for (var i in this.phrases) { if (this.phrases[i].name === name) { this.phrases.split(i, 1); } } }; /** * Get a phrase from this part, based on the name it was * given when it was created. Now you can modify its array. * * @method getPhrase * @param {String} phraseName */ p5.Part.prototype.getPhrase = function (name) { for (var i in this.phrases) { if (this.phrases[i].name === name) { return this.phrases[i]; } } }; /** * Get a phrase from this part, based on the name it was * given when it was created. Now you can modify its array. * * @method replaceSequence * @param {String} phraseName * @param {Array} sequence Array of values to pass into the callback * at each step of the phrase. */ p5.Part.prototype.replaceSequence = function (name, array) { for (var i in this.phrases) { if (this.phrases[i].name === name) { this.phrases[i].sequence = array; } } }; p5.Part.prototype.incrementStep = function (time) { if (this.partStep < this.length - 1) { this.callback(time); this.partStep += 1; } else { if (this.looping) { this.callback(time); } this.onended(); this.partStep = 0; } }; /** * Fire a callback function at every step. * * @method onStep * @param {Function} callback The name of the callback * you want to fire * on every beat/tatum. */ p5.Part.prototype.onStep = function (callback) { this.callback = callback; }; // =============== // p5.Score // =============== /** * A Score consists of a series of Parts. The parts will * be played back in order. For example, you could have an * A part, a B part, and a C part, and play them back in this order * <code>new p5.Score(a, a, b, a, c)</code> * * @class p5.Score * @constructor * @param {p5.Part} part(s) Parts to add to the score. * @example * <div><code> * var box, drum; * var boxPat = [1,0,0,2,0,2,0,0]; * var drumPat = [0,1,1,0,2,0,1,0]; * var osc, env; * * function preload() { * box = loadSound('assets/beatbox.mp3'); * drum = loadSound('assets/drum.mp3'); * } * * function setup() { * var myPart = new p5.Part(); * myPart.addPhrase('box', playBox, boxPat); * myPart.addPhrase('drum', playDrum, drumPat); * myPart.setBPM(60); * myPart.start(); * * osc = new p5.Oscillator(); * env = new p5.Env(0.01, 1, 0.2, 0); * } * * function playBox(playbackRate, time) { * box.rate(playbackRate); * box.play(time); * } * * function playDrum(playbackRate, time) { * drum.rate(playbackRate) * drum.play(time); * } * </code></div> */ p5.Score = function () { // for all of the arguments this.parts = []; this.currentPart = 0; var thisScore = this; for (var i in arguments) { this.parts[i] = arguments[i]; this.parts[i].nextPart = this.parts[i + 1]; this.parts[i].onended = function () { thisScore.resetPart(i); playNextPart(thisScore); }; } this.looping = false; }; p5.Score.prototype.onended = function () { if (this.looping) { // this.resetParts(); this.parts[0].start(); } else { this.parts[this.parts.length - 1].onended = function () { this.stop(); this.resetParts(); }; } this.currentPart = 0; }; /** * Start playback of the score. * * @method start */ p5.Score.prototype.start = function () { this.parts[this.currentPart].start(); this.scoreStep = 0; }; /** * Stop playback of the score. * * @method stop */ p5.Score.prototype.stop = function () { this.parts[this.currentPart].stop(); this.currentPart = 0; this.scoreStep = 0; }; /** * Pause playback of the score. * * @method pause */ p5.Score.prototype.pause = function () { this.parts[this.currentPart].stop(); }; /** * Loop playback of the score. * * @method loop */ p5.Score.prototype.loop = function () { this.looping = true; this.start(); }; /** * Stop looping playback of the score. If it * is currently playing, this will go into effect * after the current round of playback completes. * * @method noLoop */ p5.Score.prototype.noLoop = function () { this.looping = false; }; p5.Score.prototype.resetParts = function () { for (var i in this.parts) { this.resetPart(i); } }; p5.Score.prototype.resetPart = function (i) { this.parts[i].stop(); this.parts[i].partStep = 0; for (var p in this.parts[i].phrases) { this.parts[i].phrases[p].phraseStep = 0; } }; /** * Set the tempo for all parts in the score * * @param {Number} BPM Beats Per Minute * @param {Number} rampTime Seconds from now */ p5.Score.prototype.setBPM = function (bpm, rampTime) { for (var i in this.parts) { this.parts[i].setBPM(bpm, rampTime); } }; function playNextPart(aScore) { aScore.currentPart++; if (aScore.currentPart >= aScore.parts.length) { aScore.scoreStep = 0; aScore.onended(); } else { aScore.scoreStep = 0; aScore.parts[aScore.currentPart - 1].stop(); aScore.parts[aScore.currentPart].start(); } } }(master); var soundRecorder; soundRecorder = function () { 'use strict'; var p5sound = master; var ac = p5sound.audiocontext; /** * <p>Record sounds for playback and/or to save as a .wav file. * The p5.SoundRecorder records all sound output from your sketch, * or can be assigned a specific source with setInput().</p> * <p>The record() method accepts a p5.SoundFile as a parameter. * When playback is stopped (either after the given amount of time, * or with the stop() method), the p5.SoundRecorder will send its * recording to that p5.SoundFile for playback.</p> * * @class p5.SoundRecorder * @constructor * @example * <div><code> * var mic, recorder, soundFile; * var state = 0; * * function setup() { * background(200); * // create an audio in * mic = new p5.AudioIn(); * * // prompts user to enable their browser mic * mic.start(); * * // create a sound recorder * recorder = new p5.SoundRecorder(); * * // connect the mic to the recorder * recorder.setInput(mic); * * // this sound file will be used to * // playback & save the recording * soundFile = new p5.SoundFile(); * * text('keyPress to record', 20, 20); * } * * function keyPressed() { * // make sure user enabled the mic * if (state === 0 && mic.enabled) { * * // record to our p5.SoundFile * recorder.record(soundFile); * * background(255,0,0); * text('Recording!', 20, 20); * state++; * } * else if (state === 1) { * background(0,255,0); * * // stop recorder and * // send result to soundFile * recorder.stop(); * * text('Stopped', 20, 20); * state++; * } * * else if (state === 2) { * soundFile.play(); // play the result! * save(soundFile, 'mySound.wav'); * state++; * } * } * </div></code> */ p5.SoundRecorder = function () { this.input = ac.createGain(); this.output = ac.createGain(); this.recording = false; this.bufferSize = 1024; this._channels = 2; // stereo (default) this._clear(); // initialize variables this._jsNode = ac.createScriptProcessor(this.bufferSize, this._channels, 2); this._jsNode.onaudioprocess = this._audioprocess.bind(this); /** * callback invoked when the recording is over * @private * @type {function(Float32Array)} */ this._callback = function () { }; // connections this._jsNode.connect(p5.soundOut._silentNode); this.setInput(); // add this p5.SoundFile to the soundArray p5sound.soundArray.push(this); }; /** * Connect a specific device to the p5.SoundRecorder. * If no parameter is given, p5.SoundRecorer will record * all audible p5.sound from your sketch. * * @method setInput * @param {Object} [unit] p5.sound object or a web audio unit * that outputs sound */ p5.SoundRecorder.prototype.setInput = function (unit) { this.input.disconnect(); this.input = null; this.input = ac.createGain(); this.input.connect(this._jsNode); this.input.connect(this.output); if (unit) { unit.connect(this.input); } else { p5.soundOut.output.connect(this.input); } }; /** * Start recording. To access the recording, provide * a p5.SoundFile as the first parameter. The p5.SoundRecorder * will send its recording to that p5.SoundFile for playback once * recording is complete. Optional parameters include duration * (in seconds) of the recording, and a callback function that * will be called once the complete recording has been * transfered to the p5.SoundFile. * * @method record * @param {p5.SoundFile} soundFile p5.SoundFile * @param {Number} [duration] Time (in seconds) * @param {Function} [callback] The name of a function that will be * called once the recording completes */ p5.SoundRecorder.prototype.record = function (sFile, duration, callback) { this.recording = true; if (duration) { this.sampleLimit = Math.round(duration * ac.sampleRate); } if (sFile && callback) { this._callback = function () { this.buffer = this._getBuffer(); sFile.setBuffer(this.buffer); callback(); }; } else if (sFile) { this._callback = function () { this.buffer = this._getBuffer(); sFile.setBuffer(this.buffer); }; } }; /** * Stop the recording. Once the recording is stopped, * the results will be sent to the p5.SoundFile that * was given on .record(), and if a callback function * was provided on record, that function will be called. * * @method stop */ p5.SoundRecorder.prototype.stop = function () { this.recording = false; this._callback(); this._clear(); }; p5.SoundRecorder.prototype._clear = function () { this._leftBuffers = []; this._rightBuffers = []; this.recordedSamples = 0; this.sampleLimit = null; }; /** * internal method called on audio process * * @private * @param {AudioProcessorEvent} event */ p5.SoundRecorder.prototype._audioprocess = function (event) { if (this.recording === false) { return; } else if (this.recording === true) { // if we are past the duration, then stop... else: if (this.sampleLimit && this.recordedSamples >= this.sampleLimit) { this.stop(); } else { // get channel data var left = event.inputBuffer.getChannelData(0); var right = event.inputBuffer.getChannelData(1); // clone the samples this._leftBuffers.push(new Float32Array(left)); this._rightBuffers.push(new Float32Array(right)); this.recordedSamples += this.bufferSize; } } }; p5.SoundRecorder.prototype._getBuffer = function () { var buffers = []; buffers.push(this._mergeBuffers(this._leftBuffers)); buffers.push(this._mergeBuffers(this._rightBuffers)); return buffers; }; p5.SoundRecorder.prototype._mergeBuffers = function (channelBuffer) { var result = new Float32Array(this.recordedSamples); var offset = 0; var lng = channelBuffer.length; for (var i = 0; i < lng; i++) { var buffer = channelBuffer[i]; result.set(buffer, offset); offset += buffer.length; } return result; }; p5.SoundRecorder.prototype.dispose = function () { this._clear(); this._callback = function () { }; if (this.input) { this.input.disconnect(); } this.input = null; this._jsNode = null; }; /** * Save a p5.SoundFile as a .wav audio file. * * @method saveSound * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save * @param {String} name name of the resulting .wav file. */ p5.prototype.saveSound = function (soundFile, name) { var leftChannel = soundFile.buffer.getChannelData(0); var rightChannel = soundFile.buffer.getChannelData(1); var interleaved = interleave(leftChannel, rightChannel); // create the buffer and view to create the .WAV file var buffer = new ArrayBuffer(44 + interleaved.length * 2); var view = new DataView(buffer); // write the WAV container, // check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ // RIFF chunk descriptor writeUTFBytes(view, 0, 'RIFF'); view.setUint32(4, 44 + interleaved.length * 2, true); writeUTFBytes(view, 8, 'WAVE'); // FMT sub-chunk writeUTFBytes(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); // stereo (2 channels) view.setUint16(22, 2, true); view.setUint32(24, 44100, true); view.setUint32(28, 44100 * 4, true); view.setUint16(32, 4, true); view.setUint16(34, 16, true); // data sub-chunk writeUTFBytes(view, 36, 'data'); view.setUint32(40, interleaved.length * 2, true); // write the PCM samples var lng = interleaved.length; var index = 44; var volume = 1; for (var i = 0; i < lng; i++) { view.setInt16(index, interleaved[i] * (32767 * volume), true); index += 2; } p5.prototype.writeFile([view], name, 'wav'); }; // helper methods to save waves function interleave(leftChannel, rightChannel) { var length = leftChannel.length + rightChannel.length; var result = new Float32Array(length); var inputIndex = 0; for (var index = 0; index < length;) { result[index++] = leftChannel[inputIndex]; result[index++] = rightChannel[inputIndex]; inputIndex++; } return result; } function writeUTFBytes(view, offset, string) { var lng = string.length; for (var i = 0; i < lng; i++) { view.setUint8(offset + i, string.charCodeAt(i)); } } }(sndcore, master); var src_app; src_app = function () { 'use strict'; var p5SOUND = sndcore; return p5SOUND; }(sndcore, master, helpers, panner, soundfile, amplitude, fft, signal, oscillator, env, pulse, noise, audioin, filter, delay, reverb, metro, looper, soundRecorder);