Skip to content

Instantly share code, notes, and snippets.

@karneaud
Last active August 29, 2015 14:26
Show Gist options
  • Save karneaud/78738ee6644b24a71ee6 to your computer and use it in GitHub Desktop.
Save karneaud/78738ee6644b24a71ee6 to your computer and use it in GitHub Desktop.
Alternative fallback for p5.sound when there is no Web Audio capability
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