Last active
August 29, 2015 14:26
-
-
Save karneaud/78738ee6644b24a71ee6 to your computer and use it in GitHub Desktop.
Alternative fallback for p5.sound when there is no Web Audio capability
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
p5.SoundFileAlt = function(paths, onload, whileLoading) { | |
if (typeof paths !== 'undefined') { | |
if (typeof paths == 'string' || typeof paths[0] == 'string'){ | |
var path = p5.prototype._checkFileFormats(paths); | |
this.url = path; | |
} | |
else if((typeof paths) == 'object'){ | |
if (!(window.File && window.FileReader && window.FileList && window.Blob)) { | |
// The File API isn't supported in this browser | |
throw('Unable to load file because the File API is not supported'); | |
} | |
} | |
// if type is a p5.File...get the actual file | |
if (paths.file) { | |
paths = paths.file; | |
} | |
this.file = paths; | |
} | |
this._audio = null; | |
this._looping = false; | |
this._playing = false; | |
this._paused = false; | |
this._pauseTime = 0; | |
// position of the most recently played sample | |
this._lastPos = 0; | |
this.buffer = null; | |
this.playbackRate = 1; | |
this.reversed = false; | |
// start and end of playback / loop | |
this.startTime = 0; | |
this.endTime = null; | |
this.pauseTime = 0; | |
// time that playback was started, in millis | |
this.startMillis = null; | |
// use the url | |
if (this.url) { | |
this._audio = createAudio(this.url, onload, function(){ | |
console.log(arguments,'error'); | |
throw "error loading file"; | |
}); | |
} | |
if (typeof(whileLoading) === 'function') { | |
this.whileLoading = whileLoading; | |
} else { | |
this.whileLoading = function() {}; | |
} | |
} | |
// TO DO: use this method to create a loading bar that shows progress during file upload/decode. | |
p5.SoundFileAlt.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'); | |
// Unable to compute progress information since the total size is unknown | |
} | |
}; | |
p5.SoundFileAlt.prototype.isLoaded = function() { | |
}; | |
Branch: master p5.js-sound/src/soundfile.js | |
@therewasaguytherewasaguy on Jun 1 fft db mode | |
2 contributors @therewasaguy @johnpasquarello | |
RawBlameHistory 1560 lines (1372 sloc) 48.58 kB | |
define(function (require) { | |
'use strict'; | |
require('sndcore'); | |
var p5sound = require('master'); | |
var ac = p5sound.audiocontext; | |
/** | |
* <p>SoundFile object with a path to a file.</p> | |
* | |
* <p>The p5.SoundFile may not be available immediately because | |
* it loads the file information asynchronously.</p> | |
* | |
* <p>To do something with the sound as soon as it loads | |
* pass the name of a function as the second parameter.</p> | |
* | |
* <p>Only one file path is required. However, audio file formats | |
* (i.e. mp3, ogg, wav and m4a/aac) are not supported by all | |
* web browsers. If you want to ensure compatability, instead of a single | |
* file path, you may include an Array of filepaths, and the browser will | |
* choose a format that works.</p> | |
* | |
* @class p5.SoundFile | |
* @constructor | |
* @param {String/Array} path path to a sound file (String). Optionally, | |
* you may include multiple file formats in | |
* an array. Alternately, accepts an object | |
* from the HTML5 File API, or a p5.File. | |
* @param {Function} [callback] Name of a function to call once file loads | |
* @return {Object} p5.SoundFile Object | |
* @example | |
* <div><code> | |
* | |
* function preload() { | |
* mySound = loadSound('assets/doorbell.mp3'); | |
* } | |
* | |
* function setup() { | |
* mySound.setVolume(0.1); | |
* mySound.play(); | |
* } | |
* | |
* </code></div> | |
*/ | |
p5.SoundFile = function(paths, onload, whileLoading) { | |
if (typeof paths !== 'undefined') { | |
if (typeof paths == 'string' || typeof paths[0] == 'string'){ | |
var path = p5.prototype._checkFileFormats(paths); | |
this.url = path; | |
} | |
else if((typeof paths) == 'object'){ | |
if (!(window.File && window.FileReader && window.FileList && window.Blob)) { | |
// The File API isn't supported in this browser | |
throw('Unable to load file because the File API is not supported'); | |
} | |
} | |
// if type is a p5.File...get the actual file | |
if (paths.file) { | |
paths = paths.file; | |
} | |
this.file = paths; | |
} | |
this._looping = false; | |
this._playing = false; | |
this._paused = false; | |
this._pauseTime = 0; | |
// cues for scheduling events with addCue() removeCue() | |
this._cues = []; | |
// position of the most recently played sample | |
this._lastPos = 0; | |
this._counterNode; | |
this._scopeNode; | |
// array of sources so that they can all be stopped! | |
this.bufferSourceNodes = []; | |
// current source | |
this.bufferSourceNode = 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; | |
this.pauseTime = 0; | |
// "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.0; | |
this.panner = new p5.Panner(this.output, p5sound.input, 2); | |
// it is possible to instantiate a soundfile with no path | |
if (this.url || this.file) { | |
this.load(onload); | |
} | |
// 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/processing/p5.js/wiki/Local-server"> | |
* local server</a> is recommended when loading external files. | |
* | |
* @method loadSound | |
* @param {String/Array} path Path to the sound file, or an array with | |
* paths to soundfiles in multiple formats | |
* i.e. ['sound.ogg', 'sound.mp3']. | |
* Alternately, accepts an object: either | |
* from the HTML5 File API, or a p5.File. | |
* @param {Function} [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/doorbell.mp3'); | |
* } | |
* | |
* function setup() { | |
* mySound.setVolume(0.1); | |
* mySound.play(); | |
* } | |
* </code></div> | |
*/ | |
p5.prototype.loadSound = function(path, callback, whileLoading){ | |
// if loading locally without a server | |
if (window.location.origin.indexOf('file://') > -1 && window.cordova === 'undefined' ) { | |
alert('This sketch may require a server to load external files. Please see http://bit.ly/1qcInwS'); | |
} | |
var s = new p5.SoundFile(path, callback, whileLoading); | |
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){ | |
if(this.url != undefined && this.url != ""){ | |
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() { | |
ac.decodeAudioData(request.response, function(buff) { | |
self.buffer = buff; | |
self.panner.inputChannels(buff.numberOfChannels); | |
if (callback) { | |
callback(self); | |
} | |
}); | |
}; | |
request.send(); | |
} | |
else if(this.file != undefined){ | |
var reader = new FileReader(); | |
var self = this; | |
reader.onload = function() { | |
ac.decodeAudioData(reader.result, function(buff) { | |
self.buffer = buff; | |
self.panner.inputChannels(buff.numberOfChannels); | |
if (callback) { | |
callback(self); | |
} | |
}); | |
}; | |
reader.readAsArrayBuffer(this.file); | |
} | |
}; | |
// TO DO: use this method to create a loading bar that shows progress during file upload/decode. | |
p5.SoundFile.prototype._updateProgress = function(evt) { | |
if (evt.lengthComputable) { | |
var percentComplete = Math.log(evt.loaded / evt.total * 9.9); | |
this.whileLoading(percentComplete); | |
// ... | |
} else { | |
console.log('size unknown'); | |
// Unable to compute progress information since the total size is unknown | |
} | |
}; | |
/** | |
* Returns true if the sound file finished loading successfully. | |
* | |
* @method isLoaded | |
* @return {Boolean} | |
*/ | |
p5.SoundFile.prototype.isLoaded = function() { | |
if (this.buffer) { | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
/** | |
* Play the p5.SoundFile | |
* | |
* @method play | |
* @param {Number} [startTime] (optional) schedule playback to start (in seconds from now). | |
* @param {Number} [rate] (optional) playback rate | |
* @param {Number} [amp] (optional) amplitude (volume) | |
* of playback | |
* @param {Number} [cueStart] (optional) cue start time in seconds | |
* @param {Number} [duration] (optional) duration of playback in seconds | |
*/ | |
p5.SoundFile.prototype.play = function(time, rate, amp, _cueStart, duration) { | |
var self = this; | |
var now = p5sound.audiocontext.currentTime; | |
var cueStart, cueEnd; | |
var time = time || 0; | |
if (time < 0) { | |
time = 0; | |
} | |
time = time + now; | |
// TO DO: if already playing, create array of buffers for easy stop() | |
if (this.buffer) { | |
// reset the pause time (if it was paused) | |
this._pauseTime = 0; | |
// handle restart playmode | |
if (this.mode === 'restart' && this.buffer && this.bufferSourceNode) { | |
var now = p5sound.audiocontext.currentTime; | |
this.bufferSourceNode.stop(time); | |
this._counterNode.stop(time); | |
} | |
// make a new source and counter. They are automatically assigned playbackRate and buffer | |
this.bufferSourceNode = this._initSourceNode(); | |
this._counterNode = this._initCounterNode(); | |
if (_cueStart) { | |
if (_cueStart >=0 && _cueStart < this.buffer.duration){ | |
// this.startTime = cueStart; | |
cueStart = _cueStart; | |
} else { throw 'start time out of range'; } | |
} else { | |
cueStart = 0; | |
} | |
if (duration) { | |
// if duration is greater than buffer.duration, just play entire file anyway rather than throw an error | |
duration = duration <= this.buffer.duration - cueStart ? duration : this.buffer.duration; | |
} else { | |
duration = this.buffer.duration - cueStart; | |
} | |
// TO DO: Fix this. It broke in Safari | |
// | |
// method of controlling gain for individual bufferSourceNodes, without resetting overall soundfile volume | |
// if (typeof(this.bufferSourceNode.gain === 'undefined' ) ) { | |
// this.bufferSourceNode.gain = p5sound.audiocontext.createGain(); | |
// } | |
// this.bufferSourceNode.connect(this.bufferSourceNode.gain); | |
// set local amp if provided, otherwise 1 | |
var a = amp || 1; | |
// this.bufferSourceNode.gain.gain.setValueAtTime(a, p5sound.audiocontext.currentTime); | |
// this.bufferSourceNode.gain.connect(this.output); | |
this.bufferSourceNode.connect(this.output); | |
this.output.gain.value = a; | |
// not necessary with _initBufferSource ? | |
// this.bufferSourceNode.playbackRate.cancelScheduledValues(now); | |
rate = rate || Math.abs(this.playbackRate); | |
this.bufferSourceNode.playbackRate.setValueAtTime(rate, now); | |
// if it was paused, play at the pause position | |
if (this._paused){ | |
this.bufferSourceNode.start(time, this.pauseTime, duration); | |
this._counterNode.start(time, this.pauseTime, duration); | |
} | |
else { | |
this.bufferSourceNode.start(time, cueStart, duration); | |
this._counterNode.start(time, cueStart, duration); | |
} | |
this._playing = true; | |
this._paused = false; | |
// add source to sources array, which is used in stopAll() | |
this.bufferSourceNodes.push(this.bufferSourceNode); | |
this.bufferSourceNode._arrayIndex = this.bufferSourceNodes.length - 1; | |
// delete this.bufferSourceNode from the sources array when it is done playing: | |
this.bufferSourceNode.onended = function(e) { | |
var theNode = this; | |
// if (self.bufferSourceNodes.length === 1) { | |
this._playing = false; | |
// } | |
setTimeout( function(){ | |
self.bufferSourceNodes.splice(theNode._arrayIndex, 1); | |
if (self.bufferSourceNodes.length === 0) { | |
self._playing = false; | |
} | |
}, 1); | |
} | |
} | |
// If soundFile hasn't loaded the buffer yet, throw an error | |
else { | |
throw 'not ready to play file, buffer has yet to load. Try preload()'; | |
} | |
// if looping, will restart at original time | |
this.bufferSourceNode.loop = this._looping; | |
this._counterNode.loop = this._looping; | |
if (this._looping === true){ | |
var cueEnd = cueStart + duration; | |
this.bufferSourceNode.loopStart = cueStart; | |
this.bufferSourceNode.loopEnd = cueEnd; | |
this._counterNode.loopStart = cueStart; | |
this._counterNode.loopEnd = cueEnd; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment