diff --git a/src/audioin.js b/src/audioin.js index 07c63630..7200f5fc 100644 --- a/src/audioin.js +++ b/src/audioin.js @@ -50,40 +50,47 @@ p5sound.inputSources = []; */ class AudioIn { constructor(errorCallback) { - // set up audio input /** + * Set up audio input * @property {GainNode} input */ this.input = p5sound.audiocontext.createGain(); /** + * Send audio as an output, i.e. your computer's speaker. * @property {GainNode} output */ this.output = p5sound.audiocontext.createGain(); - /** + * Used to store the MediaStream object that is returned from the getUserMedia() API, + * which allows access to the user's microphone. The stream is used to create a MediaStreamAudioSourceNode, + * which is used as the audio source for the input and output gain nodes. + * The stream is also used to check if the browser supports the MediaStreamTrack and mediaDevices API, + * and if not, an errorCallback function is called or an alert is displayed. * @property {MediaStream|null} stream */ this.stream = null; /** + * Used to access the "audio input" from the user's microphone. + * It creates a MediaStream object that can be used to start and stop the mic and measure its volume using the getLevel() method or by connecting it to an FFT object. + * MediaStream object can also be use to check if the browser supports MediaStreamTrack and mediaDevices and to add the AudioIn object to the soundArray for disposal on close. * @property {MediaStreamAudioSourceNode|null} mediaStream */ this.mediaStream = null; /** + * Used to store the "current source of audio input", such as the user's microphone. + * Initially set to "null" and can be updated as the user selects different audio sources. + * Also used in conjunction with the "input" and "mediaStream" properties to control audio input. * @property {Number|null} currentSource */ this.currentSource = null; - /** * Client must allow browser to access their microphone / audioin source. * Default: false. Will become true when the client enables access. - * * @property {Boolean} enabled */ this.enabled = false; - /** * Input amplitude, connect to it by default but not to master out - * * @property {p5.Amplitude} amplitude */ this.amplitude = new Amplitude(); diff --git a/src/effect.js b/src/effect.js index fee75547..fd17e01c 100644 --- a/src/effect.js +++ b/src/effect.js @@ -13,6 +13,7 @@ import CrossFade from 'Tone/component/CrossFade.js'; * p5.Filter, * p5.Reverb, * p5.EQ, + * p5.Panner. * p5.Panner3D. * * @class p5.Effect diff --git a/src/monosynth.js b/src/monosynth.js index d0f2750f..a2e7b8d6 100644 --- a/src/monosynth.js +++ b/src/monosynth.js @@ -77,14 +77,21 @@ class MonoSynth extends AudioVoice { * @for p5.MonoSynth */ /** + * Allows user to set the decay time of the envelope (ADSR) of the MonoSynth class. + * It is a getter and setter that can be used to retrieve or change the decay time. + * Used in conjunction with the attack, sustain, and release fields/functions to set the full envelope of the synthesizer. * @property {Number} decay * @for p5.MonoSynth */ /** + * Allows the user to retrieve and adjust the sustain level of the envelope, + * which controls the level at which the sound is sustained during the sustain phase of the envelope. + * The default sustain level is set to 0.15. * @property {Number} sustain * @for p5.MonoSynth */ /** + * Allows the user to access and change the release time of the envelope. * @property {Number} release * @for p5.MonoSynth */ diff --git a/src/oscillator.js b/src/oscillator.js index 9bd17a40..79d72c55 100644 --- a/src/oscillator.js +++ b/src/oscillator.js @@ -133,10 +133,14 @@ class Oscillator { this.oscillator.connect(this.output); // stereo panning - this.panPosition = 0.0; this.connection = p5sound.input; // connect to p5sound by default - this.panner = new Panner(this.output, this.connection, 1); + if (typeof p5sound.audiocontext.createStereoPanner !== 'undefined') { + this.panner = new Panner(); + this.output.connect(this.panner); + } else { + this.panner = new Panner(this.output, this.connection, 1); + } //array of math operation signal chaining this.mathOps = [this.output]; @@ -415,21 +419,20 @@ class Oscillator { * seconds from now */ pan(pval, tFromNow) { - this.panPosition = pval; this.panner.pan(pval, tFromNow); } /** - * Returns the current value of panPosition , between Left (-1) and Right (1) + * Returns the current value of pan position , between Left (-1) and Right (1) * * @method getPan * @for p5.Oscillator * - * @returns {number} panPosition of oscillator , between Left (-1) and Right (1) + * @returns {number} pan position of oscillator , between Left (-1) and Right (1) */ getPan() { - return this.panPosition; + return this.panner.getPan(); } // get rid of the oscillator @@ -442,6 +445,7 @@ class Oscillator { var now = p5sound.audiocontext.currentTime; this.stop(now); this.disconnect(); + this.panner.dispose(); this.panner = null; this.oscillator = null; } diff --git a/src/panner.js b/src/panner.js index e946f788..e234d23d 100644 --- a/src/panner.js +++ b/src/panner.js @@ -1,38 +1,85 @@ +import Effect from './effect.js'; + import p5sound from './main'; var ac = p5sound.audiocontext; var panner; // Stereo panner // if there is a stereo panner node use it if (typeof ac.createStereoPanner !== 'undefined') { - class Panner { - constructor(input, output) { - this.stereoPanner = this.input = ac.createStereoPanner(); - input.connect(this.stereoPanner); - this.stereoPanner.connect(output); + /** + * The Panner class allows you to control the stereo + * panning of a sound source. It uses the [StereoPannerNode](https://developer.mozilla.org/en-US/docs/Web/API/StereoPannerNode), + * which allows you to adjust the balance between the left and right channels of a sound source. + * + * This class extends p5.Effect. + * Methods amp(), chain(), + * drywet(), connect(), and + * disconnect() are available. + * + * @class p5.Panner + * @extends p5.Effect + */ + class Panner extends Effect { + constructor() { + super(); + this.stereoPanner = this.ac.createStereoPanner(); + + this.input.connect(this.stereoPanner); + this.stereoPanner.connect(this.wet); } + /** + * Set the stereo pan position, a value of -1 means the sound will be fully panned + * to the left, a value of 0 means the sound will be centered, and a value of 1 means + * the sound will be fully panned to the right. + * @method pan + * @for p5.Panner + * @param {Number} value A value between -1 and 1 that sets the pan position. + * + * @param {Number} [time] time in seconds that it will take for the panning to change to the specified value. + */ pan(val, tFromNow) { - var time = tFromNow || 0; - var t = ac.currentTime + time; - - this.stereoPanner.pan.linearRampToValueAtTime(val, t); + if (typeof val === 'number') { + let time = tFromNow || 0; + this.stereoPanner.pan.linearRampToValueAtTime( + val, + this.ac.currentTime + time + ); + } else if (typeof val !== 'undefined') { + val.connect(this.stereoPanner.pan); + } } - //not implemented because stereopanner - //node does not require this and will automatically - //convert single channel or multichannel to stereo. - //tested with single and stereo, not with (>2) multichannel - inputChannels() {} - - connect(obj) { - this.stereoPanner.connect(obj); + /** + * Return the current panning value. + * + * @method getPan + * @for p5.Panner + * @return {Number} current panning value, number between -1 (left) and 1 (right). + */ + getPan() { + return this.stereoPanner.pan.value; } - disconnect() { + /** + * Get rid of the Panner and free up its resources / memory. + * + * @method dispose + * @for p5.Panner + */ + dispose() { + super.dispose(); if (this.stereoPanner) { this.stereoPanner.disconnect(); + delete this.stereoPanner; } } + + //not implemented because stereopanner + //node does not require this and will automatically + //convert single channel or multichannel to stereo. + //tested with single and stereo, not with (>2) multichannel + inputChannels() {} } panner = Panner; @@ -45,6 +92,7 @@ if (typeof ac.createStereoPanner !== 'undefined') { this.input = ac.createGain(); input.connect(this.input); + this.panValue = 0; this.left = ac.createGain(); this.right = ac.createGain(); this.left.channelInterpretation = 'discrete'; @@ -70,6 +118,7 @@ if (typeof ac.createStereoPanner !== 'undefined') { // -1 is left, +1 is right pan(val, tFromNow) { + this.panValue = val; var time = tFromNow || 0; var t = ac.currentTime + time; var v = (val + 1) / 2; @@ -79,6 +128,10 @@ if (typeof ac.createStereoPanner !== 'undefined') { this.right.gain.linearRampToValueAtTime(rightVal, t); } + getPan() { + return this.panValue; + } + inputChannels(numChannels) { if (numChannels === 1) { this.input.disconnect(); @@ -104,6 +157,29 @@ if (typeof ac.createStereoPanner !== 'undefined') { this.output.disconnect(); } } + + dispose() { + if (this.input) { + this.input.disconnect(); + delete this.input; + } + if (this.output) { + this.output.disconnect(); + delete this.output; + } + if (this.left) { + this.left.disconnect(); + delete this.left; + } + if (this.right) { + this.right.disconnect(); + delete this.right; + } + if (this.splitter) { + this.splitter.disconnect(); + delete this.splitter; + } + } } panner = Panner; } diff --git a/src/peakDetect.js b/src/peakDetect.js index 02db7f61..744eda1e 100644 --- a/src/peakDetect.js +++ b/src/peakDetect.js @@ -111,10 +111,11 @@ class PeakDetect { this.currentValue = 0; /** - * isDetected is set to true when a peak is detected. - * + * It returns a boolean indicating whether a peak in the audio frequency spectrum has been detected or not. * @attribute isDetected {Boolean} * @default false + * @property {Number} isDetected + * @for p5.PeakDetect */ this.isDetected = false; diff --git a/src/soundfile.js b/src/soundfile.js index 11e2517c..602c5471 100644 --- a/src/soundfile.js +++ b/src/soundfile.js @@ -182,8 +182,12 @@ class SoundFile { this.startMillis = null; // stereo panning - this.panPosition = 0.0; - this.panner = new Panner(this.output, p5sound.input, 2); + if (typeof p5sound.audiocontext.createStereoPanner !== 'undefined') { + this.panner = new Panner(); + this.output.connect(this.panner); + } else { + this.panner = new Panner(this.output, p5sound.input, 2); + } // it is possible to instantiate a soundfile with no path if (this.url || this.file) { @@ -795,7 +799,6 @@ class SoundFile { * */ pan(pval, tFromNow) { - this.panPosition = pval; this.panner.pan(pval, tFromNow); } @@ -809,7 +812,7 @@ class SoundFile { * 0.0 is center and default. */ getPan() { - return this.panPosition; + return this.panner.getPan(); } /** @@ -1264,7 +1267,7 @@ class SoundFile { this.output = null; } if (this.panner) { - this.panner.disconnect(); + this.panner.dispose(); this.panner = null; } } diff --git a/test/tests/p5.Oscillator.js b/test/tests/p5.Oscillator.js index d5ce2f01..40f80fc5 100644 --- a/test/tests/p5.Oscillator.js +++ b/test/tests/p5.Oscillator.js @@ -214,11 +214,8 @@ describe('p5.Oscillator', function () { it('can be panned without any delay', function (done) { let osc = new p5.Oscillator(); let panner = osc.panner; - expect(osc.panPosition).to.equal(0); //default value expect(osc.getPan()).to.equal(0); osc.pan(-0.3); - expect(osc.panPosition).to.equal(-0.3); - expect(osc.getPan()).to.equal(-0.3); if (typeof p5.soundOut.audiocontext.createStereoPanner !== 'undefined') { setTimeout(() => { expect(panner.stereoPanner.pan.value).to.be.approximately(-0.3, 0.01); @@ -236,8 +233,6 @@ describe('p5.Oscillator', function () { let osc = new p5.Oscillator(); osc.pan(0.7, 0.1); let panner = osc.panner; - expect(osc.panPosition).to.equal(0.7); - expect(osc.getPan()).to.equal(0.7); if (typeof p5.soundOut.audiocontext.createStereoPanner !== 'undefined') { setTimeout(() => { expect(panner.stereoPanner.pan.value).to.not.be.approximately( @@ -250,7 +245,7 @@ describe('p5.Oscillator', function () { 0.01 ); done(); - }, 50); + }, 60); }, 50); } else { setTimeout(() => { @@ -266,7 +261,7 @@ describe('p5.Oscillator', function () { expect(panner.left.gain.value).to.be.approximately(0.972, 0.001); expect(panner.right.gain.value).to.be.approximately(0.233, 0.001); done(); - }, 100); + }, 60); }, 50); } }); diff --git a/test/tests/p5.Panner.js b/test/tests/p5.Panner.js index 2fe639df..d41f83fe 100644 --- a/test/tests/p5.Panner.js +++ b/test/tests/p5.Panner.js @@ -6,22 +6,28 @@ describe('p5.Panner', function () { input = ac.createGain(); }); it('can be created', function () { - new p5.Panner(input, output); + new p5.Panner(); }); it('can be connected and disconnected', function () { - let panner = new p5.Panner(input, output); + let panner = new p5.Panner(); panner.connect(input); panner.disconnect(); }); - it('can be panned without a delay', function () { - let panner = new p5.Panner(input, output); + it('can be panned without a delay', function (done) { + let panner = new p5.Panner(); panner.pan(0.4); - panner.pan(0.3, 0); - //TODO: to check the value of left gain/ right gain (or) the stereoPanner.pan + setTimeout(() => { + expect(panner.getPan()).to.be.approximately(0.4, 0.01); + done(); + }, 25); }); - it('can be panned with a delay', function () { + it('can be panned with a delay', function (done) { let panner = new p5.Panner(input, output); - panner.pan(0.4, 10); + panner.pan(-0.7, 0.1); + setTimeout(() => { + expect(panner.getPan()).to.be.approximately(-0.7, 0.01); + done(); + }, 125); }); it('can take inputChannels as 1 or 2', function () { let panner = new p5.Panner(input, output); diff --git a/test/tests/p5.SoundFile.js b/test/tests/p5.SoundFile.js index da745162..ba872b9a 100644 --- a/test/tests/p5.SoundFile.js +++ b/test/tests/p5.SoundFile.js @@ -30,7 +30,7 @@ describe('p5.SoundFile', function () { expect(sf.pauseTime).to.equal(0); expect(sf.mode).to.equal('sustain'); expect(sf.startMillis).to.be.null; - expect(sf.panPosition).to.equal(0); + expect(sf.getPan()).to.equal(0); expect(sf.panner).to.have.property('stereoPanner'); expect(p5.soundOut.soundArray).to.include(sf); @@ -507,13 +507,13 @@ describe('p5.SoundFile', function () { let sf = new p5.SoundFile(); expect(sf.getPan()).to.equal(0); sf.pan(0.32); - expect(sf.panPosition).to.equal(0.32); - expect(sf.getPan()).to.equal(0.32); + setTimeout(() => { + expect(sf.getPan()).to.equal(0.32); + }, 5); //with delay let sf2 = new p5.SoundFile(); sf2.pan(-0.89, 0.1); setTimeout(() => { - expect(sf2.panPosition).to.equal(-0.89); expect(sf2.getPan()).to.equal(-0.89); }, 100); });