/*! 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);
|