Skip to content

Commit 7aa26e7

Browse files
authored
Merge pull request #3443 from processing/p5.sound-0.3.10
p5.sound 0.3.10 adds userStartAudio
2 parents ae25005 + db189f7 commit 7aa26e7

File tree

2 files changed

+174
-45
lines changed

2 files changed

+174
-45
lines changed

lib/addons/p5.sound.js

Lines changed: 168 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*! p5.sound.js v0.3.9 2018-09-10 */
1+
/*! p5.sound.js v0.3.10 2019-01-10 */
22
/**
33
* p5.sound extends p5 with <a href="http://caniuse.com/audio-api"
44
* target="_blank">Web Audio</a> functionality including audio input,
@@ -254,9 +254,111 @@ shims = function () {
254254
}
255255
};
256256
}();
257+
var StartAudioContext;
258+
(function (root, factory) {
259+
if (true) {
260+
StartAudioContext = function () {
261+
return factory();
262+
}();
263+
} else if (typeof module === 'object' && module.exports) {
264+
module.exports = factory();
265+
} else {
266+
root.StartAudioContext = factory();
267+
}
268+
}(this, function () {
269+
var TapListener = function (element, context) {
270+
this._dragged = false;
271+
this._element = element;
272+
this._bindedMove = this._moved.bind(this);
273+
this._bindedEnd = this._ended.bind(this, context);
274+
};
275+
TapListener.prototype._moved = function (e) {
276+
this._dragged = true;
277+
};
278+
TapListener.prototype._ended = function (context) {
279+
if (!this._dragged) {
280+
startContext(context);
281+
}
282+
this._dragged = false;
283+
};
284+
TapListener.prototype.dispose = function () {
285+
this._element.removeEventListener('touchstart', this._bindedEnd);
286+
this._element.removeEventListener('touchmove', this._bindedMove);
287+
this._element.removeEventListener('touchend', this._bindedEnd);
288+
this._element.removeEventListener('mouseup', this._bindedEnd);
289+
this._bindedMove = null;
290+
this._bindedEnd = null;
291+
this._element = null;
292+
};
293+
function startContext(context) {
294+
var buffer = context.createBuffer(1, 1, context.sampleRate);
295+
var source = context.createBufferSource();
296+
source.buffer = buffer;
297+
source.connect(context.destination);
298+
source.start(0);
299+
if (context.resume) {
300+
context.resume();
301+
}
302+
}
303+
function isStarted(context) {
304+
return context.state === 'running';
305+
}
306+
function onStarted(context, callback) {
307+
function checkLoop() {
308+
if (isStarted(context)) {
309+
callback();
310+
} else {
311+
requestAnimationFrame(checkLoop);
312+
if (context.resume) {
313+
context.resume();
314+
}
315+
}
316+
}
317+
if (isStarted(context)) {
318+
callback();
319+
} else {
320+
checkLoop();
321+
}
322+
}
323+
function bindTapListener(element, tapListeners, context) {
324+
if (Array.isArray(element) || NodeList && element instanceof NodeList) {
325+
for (var i = 0; i < element.length; i++) {
326+
bindTapListener(element[i], tapListeners, context);
327+
}
328+
} else if (typeof element === 'string') {
329+
bindTapListener(document.querySelectorAll(element), tapListeners, context);
330+
} else if (element.jquery && typeof element.toArray === 'function') {
331+
bindTapListener(element.toArray(), tapListeners, context);
332+
} else if (Element && element instanceof Element) {
333+
var tap = new TapListener(element, context);
334+
tapListeners.push(tap);
335+
}
336+
}
337+
function StartAudioContext(context, elements, callback) {
338+
var promise = new Promise(function (success) {
339+
onStarted(context, success);
340+
});
341+
var tapListeners = [];
342+
if (!elements) {
343+
elements = document.body;
344+
}
345+
bindTapListener(elements, tapListeners, context);
346+
promise.then(function () {
347+
for (var i = 0; i < tapListeners.length; i++) {
348+
tapListeners[i].dispose();
349+
}
350+
tapListeners = null;
351+
if (callback) {
352+
callback();
353+
}
354+
});
355+
return promise;
356+
}
357+
return StartAudioContext;
358+
}));
257359
var audiocontext;
258360
'use strict';
259-
audiocontext = function () {
361+
audiocontext = function (StartAudioContext) {
260362
// Create the Audio Context
261363
var audiocontext = new window.AudioContext();
262364
/**
@@ -296,32 +398,60 @@ audiocontext = function () {
296398
p5.prototype.getAudioContext = function () {
297399
return audiocontext;
298400
};
299-
// if it is iOS, we have to have a user interaction to start Web Audio
300-
// http://paulbakaus.com/tutorials/html5/web-audio-on-ios/
301-
var iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false;
302-
if (iOS) {
303-
var iosStarted = false;
304-
var startIOS = function () {
305-
if (iosStarted)
306-
return;
307-
// create empty buffer
308-
var buffer = audiocontext.createBuffer(1, 1, 22050);
309-
var source = audiocontext.createBufferSource();
310-
source.buffer = buffer;
311-
// connect to output (your speakers)
312-
source.connect(audiocontext.destination);
313-
// play the file
314-
source.start(0);
315-
console.log('start ios!');
316-
if (audiocontext.state === 'running') {
317-
iosStarted = true;
318-
}
319-
};
320-
document.addEventListener('touchend', startIOS, false);
321-
document.addEventListener('touchstart', startIOS, false);
322-
}
401+
/**
402+
* <p>It is a good practice to give users control over starting audio playback.
403+
* This practice is enforced by Google Chrome's autoplay policy as of r70
404+
* (<a href="https://goo.gl/7K7WLu">info</a>), iOS Safari, and other browsers.
405+
* </p>
406+
*
407+
* <p>
408+
* userStartAudio() starts the <a href="https://developer.mozilla.org/en-US/docs/Web/API/AudioContext"
409+
* target="_blank" title="Audio Context @ MDN">Audio Context</a> on a user gesture. It utilizes
410+
* the <a href="https://github.com/tambien/StartAudioContext">StartAudioContext</a> library by
411+
* Yotam Mann (MIT Licence, 2016). Read more at https://github.com/tambien/StartAudioContext.
412+
* </p>
413+
*
414+
* <p>Starting the audio context on a user gesture can be as simple as <code>userStartAudio()</code>.
415+
* Optional parameters let you decide on a specific element that will start the audio context,
416+
* and/or call a function once the audio context is started.</p>
417+
* @param {Element|Array} [element(s)] This argument can be an Element,
418+
* Selector String, NodeList, p5.Element,
419+
* jQuery Element, or an Array of any of those.
420+
* @param {Function} [callback] Callback to invoke when the AudioContext has started
421+
* @return {Promise} Returns a Promise which is resolved when
422+
* the AudioContext state is 'running'
423+
* @method userStartAudio
424+
* @example
425+
* <div><code>
426+
* function setup() {
427+
* var myDiv = createDiv('click to start audio');
428+
* myDiv.position(0, 0);
429+
*
430+
* var mySynth = new p5.MonoSynth();
431+
*
432+
* // This won't play until the context has started
433+
* mySynth.play('A6');
434+
*
435+
* // Start the audio context on a click/touch event
436+
* userStartAudio().then(function() {
437+
* myDiv.remove();
438+
* });
439+
* }
440+
* </code></div>
441+
*/
442+
p5.prototype.userStartAudio = function (elements, callback) {
443+
var elt = elements;
444+
if (elements instanceof p5.Element) {
445+
elt = elements.elt;
446+
} else if (elements instanceof Array && elements[0] instanceof p5.Element) {
447+
elt = elements.map(function (e) {
448+
return e.elt;
449+
});
450+
}
451+
return StartAudioContext(audiocontext, elt, callback);
452+
};
323453
return audiocontext;
324-
}();
454+
}(StartAudioContext);
325455
var master;
326456
'use strict';
327457
master = function () {
@@ -6326,13 +6456,13 @@ envelope = function () {
63266456
*
63276457
* env = new p5.Envelope(t1, l1, t2, l2, t3, l3);
63286458
* triOsc = new p5.Oscillator('triangle');
6329-
* triOsc.amp(env); // give the envelope control of the oscillator's amplitude
6459+
* triOsc.amp(env); // give the env control of the triOsc's amp
63306460
* triOsc.start();
63316461
* }
63326462
*
63336463
* // mouseClick triggers envelope if over canvas
63346464
* function mouseClicked() {
6335-
* // is the mouse over the canvas?
6465+
* // is mouse over canvas?
63366466
* if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
63376467
* env.play(triOsc);
63386468
* }
@@ -8505,7 +8635,6 @@ filter = function () {
85058635
freq = 1;
85068636
}
85078637
if (typeof freq === 'number') {
8508-
this.biquad.frequency.value = freq;
85098638
this.biquad.frequency.cancelScheduledValues(this.ac.currentTime + 0.01 + t);
85108639
this.biquad.frequency.exponentialRampToValueAtTime(freq, this.ac.currentTime + 0.02 + t);
85118640
} else if (freq) {
@@ -10425,14 +10554,14 @@ looper = function () {
1042510554
};
1042610555
/**
1042710556
* <p>A p5.Part plays back one or more p5.Phrases. Instantiate a part
10428-
* with steps and tatums. By default, each step represents 1/16th note.</p>
10557+
* with steps and tatums. By default, each step represents a 1/16th note.</p>
1042910558
*
1043010559
* <p>See p5.Phrase for more about musical timing.</p>
1043110560
*
1043210561
* @class p5.Part
1043310562
* @constructor
1043410563
* @param {Number} [steps] Steps in the part
10435-
* @param {Number} [tatums] Divisions of a beat (default is 1/16, a quarter note)
10564+
* @param {Number} [tatums] Divisions of a beat, e.g. use 1/4, or 0.25 for a quater note (default is 1/16, a sixteenth note)
1043610565
* @example
1043710566
* <div><code>
1043810567
* var box, drum, myPart;
@@ -10511,7 +10640,7 @@ looper = function () {
1051110640
this.metro.setBPM(tempo, rampTime);
1051210641
};
1051310642
/**
10514-
* Returns the Beats Per Minute of this currently part.
10643+
* Returns the tempo, in Beats Per Minute, of this part.
1051510644
*
1051610645
* @method getBPM
1051710646
* @return {Number}
@@ -10565,7 +10694,7 @@ looper = function () {
1056510694
};
1056610695
};
1056710696
/**
10568-
* Stop the part and cue it to step 0.
10697+
* Stop the part and cue it to step 0. Playback will resume from the begining of the Part when it is played again.
1056910698
*
1057010699
* @method stop
1057110700
* @param {Number} [time] seconds from now
@@ -10636,8 +10765,7 @@ looper = function () {
1063610765
}
1063710766
};
1063810767
/**
10639-
* Get a phrase from this part, based on the name it was
10640-
* given when it was created. Now you can modify its array.
10768+
* Find all sequences with the specified name, and replace their patterns with the specified array.
1064110769
*
1064210770
* @method replaceSequence
1064310771
* @param {String} phraseName
@@ -10664,7 +10792,7 @@ looper = function () {
1066410792
}
1066510793
};
1066610794
/**
10667-
* Fire a callback function at every step.
10795+
* Set the function that will be called at every step. This will clear the previous function.
1066810796
*
1066910797
* @method onStep
1067010798
* @param {Function} callback The name of the callback
@@ -11546,7 +11674,8 @@ peakdetect = function () {
1154611674
*
1154711675
* <p>
1154811676
* Based on example contribtued by @b2renger, and a simple beat detection
11549-
* explanation by <a href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/"
11677+
* explanation by <a
11678+
* href="http://www.airtightinteractive.com/2013/10/making-audio-reactive-visuals/"
1155011679
* target="_blank">Felix Turner</a>.
1155111680
* </p>
1155211681
*
@@ -12741,4 +12870,4 @@ src_app = function () {
1274112870
var p5SOUND = master;
1274212871
return p5SOUND;
1274312872
}(shims, audiocontext, master, helpers, errorHandler, panner, soundfile, amplitude, fft, signal, oscillator, envelope, pulse, noise, audioin, filter, eq, panner3d, listener3d, delay, reverb, metro, looper, soundloop, compressor, soundRecorder, peakdetect, gain, monosynth, polysynth, distortion, audioVoice, monosynth, polysynth);
12744-
}));
12873+
}));

0 commit comments

Comments
 (0)