From d5578db76f6e33a9f9d090a1d0551870e0040f7c Mon Sep 17 00:00:00 2001 From: lgtst Date: Wed, 7 Sep 2022 22:46:15 +0300 Subject: [PATCH 1/3] first approximation for setValue() functionality; --- src/sound/AudioClipPlayer.js | 14 ++++++++++++++ src/sound/Sound.js | 22 ++++++++++++++++++++++ src/sound/TonePlayer.js | 14 ++++++++++++++ src/sound/TrackPlayer.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/src/sound/AudioClipPlayer.js b/src/sound/AudioClipPlayer.js index 10dd99b8..fb9c6db0 100644 --- a/src/sound/AudioClipPlayer.js +++ b/src/sound/AudioClipPlayer.js @@ -129,6 +129,20 @@ export class AudioClipPlayer extends SoundPlayer // TODO } + /** + * Set the audio clip. + * + * @param {Object} options.audioClip - the module:sound.AudioClip. + */ + setAudioClip (audioClip) + { + if (audioClip instanceof AudioClip) + { + this.stop(); + this._audioClip = audioClip; + } + } + /** * Start playing the sound. * diff --git a/src/sound/Sound.js b/src/sound/Sound.js index cf4ef454..d4b71c85 100644 --- a/src/sound/Sound.js +++ b/src/sound/Sound.js @@ -174,6 +174,28 @@ export class Sound extends PsychObject }; } + /** + * Set value for a particular player + * + * @param {number|string} [value = "C"] the sound value (see above for a full description) + */ + setValue(value = "C", log = false) + { + this._setAttribute("value", value, log); + if (this._player instanceof TonePlayer) + { + this._player.setNote(value); + } + else if (this._player instanceof TrackPlayer) + { + this._player.setTrack(value); + } + else if (this._player instanceof AudioClipPlayer) + { + this._player.setAudioClip(value); + } + } + /** * Set the number of loops. * diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index 488016a2..141987f3 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -172,6 +172,20 @@ export class TonePlayer extends SoundPlayer } } + /** + * Set the note for tone. + * + * @param {string|number} - note (if string) or frequency (if number) + */ + setNote (note = "C4") + { + this._note = note; + if (this._synth !== undefined) + { + this._synth.setNote(note); + } + } + /** * Start playing the sound. * diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index 7451ae6f..c66f2e47 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -8,6 +8,7 @@ */ import { SoundPlayer } from "./SoundPlayer.js"; +import { Howl } from "howler"; /** *

This class handles the playback of sound tracks.

@@ -142,6 +143,36 @@ export class TrackPlayer extends SoundPlayer } } + /** + * Set new track to play. + * + * @param {number} loops - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped. + */ + setTrack (track) + { + let newHowl = undefined; + + if (typeof track === "string") + { + newHowl = sound.psychoJS.serverManager.getResource(sound.value); + } + else if (track instanceof Howl) + { + newHowl = track; + } + + if (newHowl !== undefined) + { + this._howl.once("fade", (id) => + { + this._howl.stop(id); + this._howl.off("end"); + this._howl = newHowl; + }); + this._howl.fade(this._howl.volume(), 0, 17, this._id); + } + } + /** * Start playing the sound. * From 07e02dd19cba87de11a6ca1938ba56adaee3043e Mon Sep 17 00:00:00 2001 From: lgtst Date: Wed, 7 Sep 2022 22:56:43 +0300 Subject: [PATCH 2/3] proper setTrack description; --- src/sound/TrackPlayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index c66f2e47..b14466da 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -146,7 +146,7 @@ export class TrackPlayer extends SoundPlayer /** * Set new track to play. * - * @param {number} loops - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped. + * @param {Object|string} track - a track resource name or Howl object (see {@link https://howlerjs.com/}) */ setTrack (track) { From f4067a774fff76cd78a4f1778ab6e1a7ab4f8731 Mon Sep 17 00:00:00 2001 From: lgtst Date: Fri, 9 Sep 2022 00:58:23 +0300 Subject: [PATCH 3/3] a more complete coverage of other types of players, separated checkValueSupport functionality; --- src/sound/AudioClipPlayer.js | 26 ++++++++++++++--- src/sound/Sound.js | 27 +++++++++++++---- src/sound/TonePlayer.js | 56 +++++++++++++++++++++--------------- src/sound/TrackPlayer.js | 29 +++++++++++++++---- 4 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/sound/AudioClipPlayer.js b/src/sound/AudioClipPlayer.js index fb9c6db0..f59f393e 100644 --- a/src/sound/AudioClipPlayer.js +++ b/src/sound/AudioClipPlayer.js @@ -50,6 +50,22 @@ export class AudioClipPlayer extends SoundPlayer this._currentLoopIndex = -1; } + /** + * Determine whether this player can play the given sound. + * + * @param {string} value - the sound object, which should be an AudioClip + * @return {boolean} whether or not the value is supported + */ + static checkValueSupport (value) + { + if (value instanceof AudioClip) + { + return true; + } + + return false; + } + /** * Determine whether this player can play the given sound. * @@ -58,10 +74,10 @@ export class AudioClipPlayer extends SoundPlayer */ static accept(sound) { - if (sound.value instanceof AudioClip) + if (AudioClipPlayer.checkValueSupport(sound.value)) { // build the player: - const player = new AudioClipPlayer({ + return new AudioClipPlayer({ psychoJS: sound.psychoJS, audioClip: sound.value, startTime: sound.startTime, @@ -70,7 +86,6 @@ export class AudioClipPlayer extends SoundPlayer loops: sound.loops, volume: sound.volume, }); - return player; } // AudioClipPlayer is not an appropriate player for the given sound: @@ -138,7 +153,10 @@ export class AudioClipPlayer extends SoundPlayer { if (audioClip instanceof AudioClip) { - this.stop(); + if (this._audioClip !== undefined) + { + this.stop(); + } this._audioClip = audioClip; } } diff --git a/src/sound/Sound.js b/src/sound/Sound.js index d4b71c85..7d56c5c5 100644 --- a/src/sound/Sound.js +++ b/src/sound/Sound.js @@ -182,17 +182,32 @@ export class Sound extends PsychObject setValue(value = "C", log = false) { this._setAttribute("value", value, log); - if (this._player instanceof TonePlayer) + + if (this._player === undefined) { - this._player.setNote(value); + return; } - else if (this._player instanceof TrackPlayer) + + // Analyse the value and change the type of player if needed. + const valSupported = this._player.constructor.checkValueSupport(value); + if (valSupported) { - this._player.setTrack(value); + if (this._player instanceof TonePlayer) + { + this._player.setNote(value); + } + else if (this._player instanceof TrackPlayer) + { + this._player.setTrack(value); + } + else if (this._player instanceof AudioClipPlayer) + { + this._player.setAudioClip(value); + } } - else if (this._player instanceof AudioClipPlayer) + else { - this._player.setAudioClip(value); + this._getPlayer(); } } diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index 141987f3..1ab75751 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -63,28 +63,19 @@ export class TonePlayer extends SoundPlayer /** * Determine whether this player can play the given sound. * - *

Note: if TonePlayer accepts the sound but Tone.js is not available, e.g. if the browser is IE11, - * we throw an exception.

- * - * @param {module:sound.Sound} sound - the sound - * @return {Object|undefined} an instance of TonePlayer that can play the given sound or undefined otherwise + * @param {string|number} value - potential note of the tone. + * @return {boolean} whether or not this value can be used by TonePlayer. */ - static accept(sound) + static checkValueSupport (value) { // if the sound's value is an integer, we interpret it as a frequency: - if (isNumeric(sound.value)) + if (isNumeric(value)) { - return new TonePlayer({ - psychoJS: sound.psychoJS, - note: sound.value, - duration_s: sound.secs, - volume: sound.volume, - loops: sound.loops, - }); + return true; } // if the sound's value is a string, we check whether it is a note: - if (typeof sound.value === "string") + if (typeof value === "string") { // mapping between the PsychoPY notes and the standard ones: let psychopyToToneMap = new Map(); @@ -96,19 +87,38 @@ export class TonePlayer extends SoundPlayer } // check whether the sound's value is a recognised note: - const note = psychopyToToneMap.get(sound.value); + const note = psychopyToToneMap.get(value); if (typeof note !== "undefined") { - return new TonePlayer({ - psychoJS: sound.psychoJS, - note: note + sound.octave, - duration_s: sound.secs, - volume: sound.volume, - loops: sound.loops, - }); + return true; } } + return false; + } + + /** + * Determine whether this player can play the given sound and if so return an instance of the player. + * + *

Note: if TonePlayer accepts the sound but Tone.js is not available, e.g. if the browser is IE11, + * we throw an exception.

+ * + * @param {module:sound.Sound} sound - the sound + * @return {Object|undefined} an instance of TonePlayer that can play the given sound or undefined otherwise + */ + static accept(sound) + { + if (TonePlayer.checkValueSupport(sound.value)) + { + return new TonePlayer({ + psychoJS: sound.psychoJS, + note: sound.value, + duration_s: sound.secs, + volume: sound.volume, + loops: sound.loops, + }); + } + // TonePlayer is not an appropriate player for the given sound: return undefined; } diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index b14466da..300382a1 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -52,6 +52,22 @@ export class TrackPlayer extends SoundPlayer this._currentLoopIndex = -1; } + /** + * Determine whether this player can play the given sound. + * + * @param {string} value - the sound, which should be the name of an audio resource file + * @return {boolean} whether or not value is supported + */ + static checkValueSupport (value) + { + if (typeof value === "string") + { + return true; + } + + return false; + } + /** * Determine whether this player can play the given sound. * @@ -61,14 +77,16 @@ export class TrackPlayer extends SoundPlayer */ static accept(sound) { + let howl = undefined; + // if the sound's value is a string, we check whether it is the name of a resource: - if (typeof sound.value === "string") + if (TrackPlayer.checkValueSupport(sound.value)) { - const howl = sound.psychoJS.serverManager.getResource(sound.value); - if (typeof howl !== "undefined") + howl = sound.psychoJS.serverManager.getResource(sound.value); + if (howl !== undefined) { // build the player: - const player = new TrackPlayer({ + return new TrackPlayer({ psychoJS: sound.psychoJS, howl: howl, startTime: sound.startTime, @@ -77,7 +95,6 @@ export class TrackPlayer extends SoundPlayer loops: sound.loops, volume: sound.volume, }); - return player; } } @@ -154,7 +171,7 @@ export class TrackPlayer extends SoundPlayer if (typeof track === "string") { - newHowl = sound.psychoJS.serverManager.getResource(sound.value); + newHowl = this.psychoJS.serverManager.getResource(track); } else if (track instanceof Howl) {