diff --git a/README.md b/README.md index 697de497..acddc00d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PsychoJS is a JavaScript library that makes it possible to run neuroscience, psychology, and psychophysics experiments in a browser. It is the online counterpart of the [PsychoPy](http://www.psychopy.org/) Python library. -You can create online experiments from the [PsychoPy Builder](http://www.psychopy.org/builder/builder.html), you can find and adapt existing experiments on [pavlovia.org](https://www.pavlovia.org), or create them from scratch: the PsychoJS API is available [here](https://psychopy.github.io/psychojs/). +You can create online experiments from the [PsychoPy Builder](http://www.psychopy.org/builder/builder.html), you can find and adapt existing experiments on [pavlovia.org](https://www.pavlovia.org), or create them from scratch. PsychoJS is an open-source project. You can contribute by submitting pull requests to the [PsychoJS GitHub repository](https://github.com/psychopy/psychojs), and discuss issues and current and future features on the [Online category of the PsychoPy Forum](https://discourse.psychopy.org/c/online). @@ -55,6 +55,7 @@ Alain Pitiot - [@apitiot](https://github.com/apitiot) The PsychoJS library was initially written by [Ilixa](http://www.ilixa.com) with support from the [Wellcome Trust](https://wellcome.ac.uk). It is now a collaborative effort, supported by the [Chan Zuckerberg Initiative](https://chanzuckerberg.com/) (2020-2021) and [Open Science Tools](https://opensciencetools.org/) (2020-): - Alain Pitiot - [@apitiot](https://github.com/apitiot) +- Nikita Agafonov - [@lightest](https://github.com/lightest) - Sotiri Bakagiannis - [@thewhodidthis](https://github.com/thewhodidthis) - Jonathan Peirce - [@peircej](https://github.com/peircej) - Thomas Pronk - [@tpronk](https://github.com/tpronk) diff --git a/docs/AudioClip.html b/docs/AudioClip.html new file mode 100644 index 00000000..ab8a8972 --- /dev/null +++ b/docs/AudioClip.html @@ -0,0 +1,2132 @@ + + + + + + AudioClip - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

AudioClip

+ + + + + + + +
+ +
+ +

+ sound + + AudioClip +

+ +

AudioClip encapsulates an audio recording.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new AudioClip(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

name + + +String + + + + + + <optional>
+ + + + + +
+ + 'audioclip' + +

the name used when logging messages

format + + +string + + + + + + + + + + + +

the format for the audio file

sampleRateHz + + +number + + + + + + + + + + + +

the sampling rate

data + + +Blob + + + + + + + + + + + +

the audio data, in the given format, at the given sampling rate

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Engine :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
GOOGLE + + +Symbol + + + +

Google Cloud Speech-to-Text.

+ + + + + + +
+

Recognition engines.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Status :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
CREATED + + +Symbol + + + +
DECODING + + +Symbol + + + +
READY + + +Symbol + + + +
+ + + + + + +
+

AudioClip status.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(protected) _base64ArrayBuffer(arrayBuffer) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert an array buffer to a base64 string.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
arrayBuffer + +

the input buffer

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the base64 encoded input buffer

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(protected) _decodeAudio()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Decode the formatted audio data (e.g. webm) into a 32bit float PCM audio buffer.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _GoogleTranscribe(transcriptionKey, languageCode) → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Transcribe the audio clip using the Google Cloud Speech-To-Text Engine.

+

ref: https://cloud.google.com/speech-to-text/docs/reference/rest/v1/speech/recognize

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
transcriptionKey + + +String + + + +

the secret key to the Google service

languageCode + + +String + + + +

the BCP-47 language code for the recognition, e.g. 'en-GB'

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

a promise resolving to the transcript and associated +transcription confidence

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

download()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Offer the audio clip to the participant as a sound file to download.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) getDuration() → {Promise.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the duration of the audio clip, in seconds.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the duration of the audio clip

+
+ + + +
+
+ Type +
+
+ +Promise.<number> + + +
+
+ + + + + + + + + + +

setVolume(volume)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the volume of the playback.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
volume + + +number + + + +

the volume of the playback (must be between 0.0 and 1.0)

+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) startPlayback()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the audio clip.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) stopPlayback(fadeDurationopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop playing the audio clip.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fadeDuration + + +number + + + + + + <optional>
+ + + + + +
+ + 17 + +

how long the fading out should last, in ms

+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) transcribe(options) → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Transcribe the audio clip.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
engine + + +Symbol + + + +

the speech-to-text engine

languageCode + + +String + + + +

the BCP-47 language code for the recognition, +e.g. 'en-GB'

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

a promise resolving to the transcript and associated +transcription confidence

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

upload()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Upload the audio clip to the pavlovia server.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/AudioClipPlayer.html b/docs/AudioClipPlayer.html new file mode 100644 index 00000000..efe21592 --- /dev/null +++ b/docs/AudioClipPlayer.html @@ -0,0 +1,1672 @@ + + + + + + AudioClipPlayer - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

AudioClipPlayer

+ + + + + + + +
+ +
+ +

+ sound + + AudioClipPlayer +

+ +

This class handles the playback of an audio clip, e.g. a microphone recording.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new AudioClipPlayer(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

audioClip + + +Object + + + + + + + + + + + +

the module:sound.AudioClip

startTime + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

start of playback (in seconds)

stopTime + + +number + + + + + + <optional>
+ + + + + +
+ + -1 + +

end of playback (in seconds)

stereo + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to play the sound or track in stereo

volume + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

volume of the sound (must be between 0 and 1.0)

loops + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

how many times to repeat the track or tone after it has played *

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(static) accept(sound) → {Object|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether this player can play the given sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
sound + + +module:sound.Sound + + + +

the sound object, which should be an AudioClip

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

an instance of AudioClipPlayer if sound is an AudioClip or undefined otherwise

+
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + +

getDuration() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the duration of the AudioClip, in seconds.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the duration of the clip, in seconds

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

play(loops, fadeDurationopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
loops + + +number + + + + + + + + + + + +

how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.

fadeDuration + + +number + + + + + + <optional>
+ + + + + +
+ + 17 + +

how long should the fading in last in ms

+ + + + + + + + + + + + + + + + + + + + + + + + +

setDuration(duration_s)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the duration of the audio clip.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
duration_s + + +number + + + +

the duration of the clip in seconds

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLoops(loops)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the number of loops.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loops + + +number + + + +

how many times to repeat the clip after it has played once. If loops == -1, the clip will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

setVolume(volume, muteopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the volume of the playback.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
volume + + +number + + + + + + + + + + + +

the volume of the playback (must be between 0.0 and 1.0)

mute + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to mute the playback

+ + + + + + + + + + + + + + + + + + + + + + + + +

stop(fadeDurationopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop playing the sound immediately.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fadeDuration + + +number + + + + + + <optional>
+ + + + + +
+ + 17 + +

how long the fading out should last, in ms

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/BuilderKeyResponse.html b/docs/BuilderKeyResponse.html new file mode 100644 index 00000000..e6f633ad --- /dev/null +++ b/docs/BuilderKeyResponse.html @@ -0,0 +1,306 @@ + + + + + + BuilderKeyResponse - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

BuilderKeyResponse

+ + + + + + + +
+ +
+ +

+ BuilderKeyResponse +

+ +

Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard)

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new BuilderKeyResponse(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

Classes

+ +
+
BuilderKeyResponse
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ButtonStim.html b/docs/ButtonStim.html new file mode 100644 index 00000000..8f5b513b --- /dev/null +++ b/docs/ButtonStim.html @@ -0,0 +1,4100 @@ + + + + + + ButtonStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

ButtonStim

+ + + + + + + +
+ +
+ +

+ visual + + ButtonStim +

+ +

ButtonStim visual stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new ButtonStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
win + + +module:core.Window + + + + + + + + + + + +

the associated Window

name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

text + + +string + + + + + + <optional>
+ + + + + +
+ + "" + +

the text to be rendered

font + + +string + + + + + + <optional>
+ + + + + +
+ + "Arial" + +

the font family

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the text

anchor + + +string + + + + + + <optional>
+ + + + + +
+ + "center" + +

horizontal alignment

units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the text size and position

color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color("white") + +

the background color

fillColor + + +Color + + + + + + <optional>
+ + + + + +
+ + Color("darkgrey") + +

the fill color

borderColor + + +Color + + + + + + <optional>
+ + + + + +
+ + Color("white") + +

the border color

borderWidth + + +Color + + + + + + <optional>
+ + + + + +
+ + 0 + +

the border width

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

letterHeight + + +number + + + + + + <optional>
+ + + + + +
+ +

the height of the text

bold + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the text is bold

italic + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the text is italic

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + +

Members

+ + + +

isClicked

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Is this button currently being clicked on?

+
+ + + + + + + + + + +

numClicks

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

How many times has this button been clicked on?

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _addEventListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add event listeners to text-box object. Method is called internally upon object construction.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + +
Inherited From:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getAnchor() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert the anchor attribute into numerical values.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the anchor, as an array of numbers in [0,1]
  • +
+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _getDefaultLetterHeight() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the default letter height given the stimulus' units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the letter height corresponding to this stimulus' units.
  • +
+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected) _getTextInputOptions()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the TextInput options applied to the PIXI.TextInput.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • take size into account
  • +
+
+ +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

clear()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clears the current text value.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getText() → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

For accessing the underlying input value.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the current text value of the underlying input element.
  • +
+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

reset()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clears the current text value or sets it back to match the placeholder.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setAlignment(alignment, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the alignment attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
alignment + + +boolean + + + + + + + + + + + + center + +

alignment of the text

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setAnchor(anchor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the anchor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
anchor + + +boolean + + + + + + + + + + + + center + +

anchor of the textbox

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setBorderColor(borderColor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the borderColor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
borderColor + + +Color + + + + + + + + + + + +

border color of the text box

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setColor(color, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the color attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
color + + +boolean + + + + + + + + + + + +

color of the text

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFillColor(fillColor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the fillColor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fillColor + + +boolean + + + + + + + + + + + +

fill color of the text box

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFitToContent(fitToContent, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the fitToContent attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fitToContent + + +boolean + + + + + + + + + + + +

whether or not to autoresize textbox to fit to text content

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFont(font, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the font for textbox.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
font + + +string + + + + + + + + + + + + Arial + +

the font family

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLanguageStyle(languageStyle, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the languageStyle attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
languageStyle + + +String + + + + + + + + + + + + LTR + +

text direction in textbox, accepts values ["LTR", "RTL", "Arabic"]

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLetterHeight(fontSizeopt, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set letterHeight (font size) for textbox.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fontSize + + +string + + + + + + <optional>
+ + + + + +
+ + <default value> + +

the size of the font

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setSize(size, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the size attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
size + + +boolean + + + + + + + + + + + +

whether or not to wrap the text at the given width

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setText(text)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

For tweaking the underlying input value.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
text + + +string + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Clock.html b/docs/Clock.html new file mode 100644 index 00000000..fd73b74a --- /dev/null +++ b/docs/Clock.html @@ -0,0 +1,752 @@ + + + + + + Clock - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Clock

+ + + + + + + +
+ +
+ +

+ util + + Clock +

+ +

Clock is a MonotonicClock that also offers the possibility of being reset.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Clock()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

add(deltaTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add more time to the clock's 'start' time (t0).

+

Note: by adding time to t0, the current time is pushed forward (it becomes +smaller). As a consequence, getTime() may return a negative number.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
deltaTime + + +number + + + + + + <optional>
+ + + + + +

the time to be added to the clock's start time (t0)

+ + + + + + + + + + + + + + + + + + + + + + + + +

getLastResetTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current offset being applied to the high resolution timebase used by this Clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the offset (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

getTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current time on this clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the current time (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

reset(newTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the time on the clock.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
newTime + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the new time on the clock.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Color.html b/docs/Color.html new file mode 100644 index 00000000..59c3370a --- /dev/null +++ b/docs/Color.html @@ -0,0 +1,6506 @@ + + + + + + Color - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Color

+ + + + + + + +
+ +
+ +

+ util + + Color +

+ +

This class handles multiple color spaces, and offers various +static methods for converting colors from one space to another.

+

The constructor accepts the following color representations: +

    +
  • a named color, e.g. 'aliceblue' (the colorspace must be RGB)
  • +
  • an hexadecimal string representation, e.g. '#FF0000' (the colorspace must be RGB)
  • +
  • an hexadecimal number representation, e.g. 0xFF0000 (the colorspace must be RGB)
  • +
  • a triplet of numbers, e.g. [-1, 0, 1], [0, 128, 255] (the numbers must be within the range determined by the colorspace)
  • +
+

+

Note: internally, colors are represented as a [r,g,b] triplet with r,g,b in [0,1].

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Color(objopt, colorspaceopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • implement HSV, DKL, and LMS colorspaces
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
obj + + +string +| + +number +| + +Array.<number> +| + +undefined + + + + + + <optional>
+ + + + + +
+ + 'black' + +

an object representing a color

colorspace + + +module:util.Color#COLOR_SPACE +| + +undefined + + + + + + <optional>
+ + + + + +
+ + Color.COLOR_SPACE.RGB + +

the colorspace of that color

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) COLOR_SPACE :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
RGB + + +Symbol + + + +

RGB colorspace: [r,g,b] with r,g,b in [-1, 1]

RGB255 + + +Symbol + + + +

RGB255 colorspace: [r,g,b] with r,g,b in [0, 255]

+ + + + + + +
+

Color spaces.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) NAMED_COLORS :string

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
aliceblue + + +string + + + +
antiquewhite + + +string + + + +
aqua + + +string + + + +
aquamarine + + +string + + + +
azure + + +string + + + +
beige + + +string + + + +
bisque + + +string + + + +
black + + +string + + + +
blanchedalmond + + +string + + + +
blue + + +string + + + +
blueviolet + + +string + + + +
brown + + +string + + + +
burlywood + + +string + + + +
cadetblue + + +string + + + +
chartreuse + + +string + + + +
chocolate + + +string + + + +
coral + + +string + + + +
cornflowerblue + + +string + + + +
cornsilk + + +string + + + +
crimson + + +string + + + +
cyan + + +string + + + +
darkblue + + +string + + + +
darkcyan + + +string + + + +
darkgoldenrod + + +string + + + +
darkgray + + +string + + + +
darkgrey + + +string + + + +
darkgreen + + +string + + + +
darkkhaki + + +string + + + +
darkmagenta + + +string + + + +
darkolivegreen + + +string + + + +
darkorange + + +string + + + +
darkorchid + + +string + + + +
darkred + + +string + + + +
darksalmon + + +string + + + +
darkseagreen + + +string + + + +
darkslateblue + + +string + + + +
darkslategray + + +string + + + +
darkslategrey + + +string + + + +
darkturquoise + + +string + + + +
darkviolet + + +string + + + +
deeppink + + +string + + + +
deepskyblue + + +string + + + +
dimgray + + +string + + + +
dimgrey + + +string + + + +
dodgerblue + + +string + + + +
firebrick + + +string + + + +
floralwhite + + +string + + + +
forestgreen + + +string + + + +
fuchsia + + +string + + + +
gainsboro + + +string + + + +
ghostwhite + + +string + + + +
gold + + +string + + + +
goldenrod + + +string + + + +
gray + + +string + + + +
grey + + +string + + + +
green + + +string + + + +
greenyellow + + +string + + + +
honeydew + + +string + + + +
hotpink + + +string + + + +
indianred + + +string + + + +
indigo + + +string + + + +
ivory + + +string + + + +
khaki + + +string + + + +
lavender + + +string + + + +
lavenderblush + + +string + + + +
lawngreen + + +string + + + +
lemonchiffon + + +string + + + +
lightblue + + +string + + + +
lightcoral + + +string + + + +
lightcyan + + +string + + + +
lightgoldenrodyellow + + +string + + + +
lightgray + + +string + + + +
lightgrey + + +string + + + +
lightgreen + + +string + + + +
lightpink + + +string + + + +
lightsalmon + + +string + + + +
lightseagreen + + +string + + + +
lightskyblue + + +string + + + +
lightslategray + + +string + + + +
lightslategrey + + +string + + + +
lightsteelblue + + +string + + + +
lightyellow + + +string + + + +
lime + + +string + + + +
limegreen + + +string + + + +
linen + + +string + + + +
magenta + + +string + + + +
maroon + + +string + + + +
mediumaquamarine + + +string + + + +
mediumblue + + +string + + + +
mediumorchid + + +string + + + +
mediumpurple + + +string + + + +
mediumseagreen + + +string + + + +
mediumslateblue + + +string + + + +
mediumspringgreen + + +string + + + +
mediumturquoise + + +string + + + +
mediumvioletred + + +string + + + +
midnightblue + + +string + + + +
mintcream + + +string + + + +
mistyrose + + +string + + + +
moccasin + + +string + + + +
navajowhite + + +string + + + +
navy + + +string + + + +
oldlace + + +string + + + +
olive + + +string + + + +
olivedrab + + +string + + + +
orange + + +string + + + +
orangered + + +string + + + +
orchid + + +string + + + +
palegoldenrod + + +string + + + +
palegreen + + +string + + + +
paleturquoise + + +string + + + +
palevioletred + + +string + + + +
papayawhip + + +string + + + +
peachpuff + + +string + + + +
peru + + +string + + + +
pink + + +string + + + +
plum + + +string + + + +
powderblue + + +string + + + +
purple + + +string + + + +
red + + +string + + + +
rosybrown + + +string + + + +
royalblue + + +string + + + +
saddlebrown + + +string + + + +
salmon + + +string + + + +
sandybrown + + +string + + + +
seagreen + + +string + + + +
seashell + + +string + + + +
sienna + + +string + + + +
silver + + +string + + + +
skyblue + + +string + + + +
slateblue + + +string + + + +
slategray + + +string + + + +
slategrey + + +string + + + +
snow + + +string + + + +
springgreen + + +string + + + +
steelblue + + +string + + + +
tan + + +string + + + +
teal + + +string + + + +
thistle + + +string + + + +
tomato + + +string + + + +
turquoise + + +string + + + +
violet + + +string + + + +
wheat + + +string + + + +
white + + +string + + + +
whitesmoke + + +string + + + +
yellow + + +string + + + +
yellowgreen + + +string + + + +
+ + + + + + +
+

Named colors.

+
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + + + + + + +

hex

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the hexadecimal color code equivalent of this Color.

+
+ + + + + + + + + + +

int

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer code equivalent of this Color.

+
+ + + + + + + + + + +

rgb

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0,1] RGB triplet equivalent of this Color.

+
+ + + + + + + + + + +

rgb255

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0,255] RGB triplet equivalent of this Color.

+
+ + + + + + + + + + +

rgbFull

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [-1,1] RGB triplet equivalent of this Color.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected, static) _checkTypeAndRange(arg, rangeopt) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Check that the argument is an array of numbers of size 3, and, potentially, that its elements fall within the range.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
arg + + +any + + + + + + + + + +

the argument

range + + +Array.<number> + + + + + + <optional>
+ + + + + +

the lower and higher bounds of the range

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether the argument is an array of numbers of size 3, and, potentially, whether its elements fall within the range (if range is not undefined)

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

(protected, static) _intToRgb(hex) → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0, 1] based RGB triplet equivalent of the integer color code.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
hex + + +number + + + +

the integer color code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the [0, 1] RGB equivalent

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected, static) _intToRgb255(hex) → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0, 255] based RGB triplet equivalent of the integer color code.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
hex + + +number + + + +

the integer color code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the [0, 255] RGB equivalent

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected, static) _rgb255ToHex(rgb255) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the hexadecimal color code equivalent of the [0, 255] RGB triplet.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb255 + + +Array.<number> + + + +

the [0, 255] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the hexadecimal color code equivalent

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(protected, static) _rgb255ToInt(rgb255) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer equivalent of the [0, 255] RGB triplet.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb255 + + +Array.<number> + + + +

the [0, 255] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the integer equivalent

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected, static) _rgbToHex(rgb) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the hexadecimal color code equivalent of the [0, 1] RGB triplet.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb + + +Array.<number> + + + +

the [0, 1] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the hexadecimal color code equivalent

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(protected, static) _rgbToInt(rgb) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer equivalent of the [0, 1] RGB triplet.

+

Note: this is the fast, unsafe version which does not check for argument sanity

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb + + +Array.<number> + + + +

the [0, 1] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the integer equivalent

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(static) hexToRgb(hex) → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0,1] RGB triplet equivalent of the hexadecimal color code.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
hex + + +string + + + +

the hexadecimal color code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the [0,1] RGB triplet equivalent

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(static) hexToRgb255(hex) → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the [0,255] RGB triplet equivalent of the hexadecimal color code.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
hex + + +string + + + +

the hexadecimal color code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the [0,255] RGB triplet equivalent

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(static) rgb255ToHex(rgb255) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the hexadecimal color code equivalent of the [0, 255] RGB triplet.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb255 + + +Array.<number> + + + +

the [0, 255] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the hexadecimal color code equivalent

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(static) rgb255ToInt(rgb255) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer equivalent of the [0, 255] RGB triplet.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb255 + + +Array.<number> + + + +

the [0, 255] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the integer equivalent

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(static) rgbToHex(rgb) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the hexadecimal color code equivalent of the [0, 1] RGB triplet.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb + + +Array.<number> + + + +

the [0, 1] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the hexadecimal color code equivalent

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(static) rgbToInt(rgb) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer equivalent of the [0, 1] RGB triplet.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rgb + + +Array.<number> + + + +

the [0, 1] RGB triplet

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the integer equivalent

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

toString() → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

String representation of the color, i.e. the hexadecimal representation.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the representation.

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/CountdownTimer.html b/docs/CountdownTimer.html new file mode 100644 index 00000000..c9ec6ef1 --- /dev/null +++ b/docs/CountdownTimer.html @@ -0,0 +1,824 @@ + + + + + + CountdownTimer - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

CountdownTimer

+ + + + + + + +
+ +
+ +

+ util + + CountdownTimer +

+ +

CountdownTimer is a clock counts down from the time of last reset. + + +

+ +
+ +
+ + + + +

Constructor

+ + +

new CountdownTimer(startTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
startTime + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the start time of the countdown

+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

add(deltaTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add more time to the clock's 'start' time (t0).

+

Note: by adding time to t0, you push the current time forward (make it +smaller). As a consequence, getTime() may return a negative number.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
deltaTime + + +number + + + + + + <optional>
+ + + + + +

the time to be added to the clock's start time (t0)

+ + + + + + + + + + + + + + + + + + + + + + + + +

getLastResetTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current offset being applied to the high resolution timebase used by this Clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the offset (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

getTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the time currently left on the countdown.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the time left on the countdown (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

reset(newTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the time on the countdown.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
newTime + + +number + + + + + + <optional>
+ + + + + +

if newTime is undefined, the countdown time is reset to zero, otherwise we set it +to newTime

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/EventEmitter.html b/docs/EventEmitter.html new file mode 100644 index 00000000..c7cca150 --- /dev/null +++ b/docs/EventEmitter.html @@ -0,0 +1,895 @@ + + + + + + EventEmitter - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

EventEmitter

+ + + + + + + +
+ +
+ +

+ util + + EventEmitter +

+ +

EventEmitter implements the classic observer/observable pattern.

+

Note: this is heavily inspired by http://www.datchley.name/es6-eventemitter/

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new EventEmitter()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
Example
+ +
let observable = new EventEmitter();
+let uuid1 = observable.on('change', data => { console.log(data); });
+observable.emit("change", { a: 1 });
+observable.off("change", uuid1);
+observable.emit("change", { a: 1 });
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

emit(name, data) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Emit an event with a given name and associated data.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +String + + + +

the name of the event

data + + +object + + + +

the data of the event

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

true if at least one listener has been registered for that event, and false otherwise

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

off(name, listener)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Remove the listener with the given uuid associated to the given event name.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +String + + + +

the name of the event

listener + + +module:util.EventEmitter~Listener + + + +

a listener called upon emission of the event

+ + + + + + + + + + + + + + + + + + + + + + + + +

on(name, listener)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Register a new listener for events with the given name emitted by this instance.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +String + + + +

the name of the event

listener + + +module:util.EventEmitter~Listener + + + +

a listener called upon emission of the event

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)

+
+ + + + + + + + + + + + +

once(name, listener)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Register a new listener for the given event name, and remove it as soon as the event has been emitted.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +String + + + +

the name of the event

listener + + +module:util.EventEmitter~Listener + + + +

a listener called upon emission of the event

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)

+
+ + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/EventManager.html b/docs/EventManager.html new file mode 100644 index 00000000..216a0e10 --- /dev/null +++ b/docs/EventManager.html @@ -0,0 +1,2386 @@ + + + + + + EventManager - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

EventManager

+ + + + + + + +
+ +
+ +

+ core + + EventManager +

+ +

This manager handles all participant interactions with the experiment, i.e. keyboard, mouse and touch events.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new EventManager(psychoJS, psychoJS)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +Object + + + +
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static, readonly) _keycodeMap :Object.<number, String>

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map provides support for browsers that have not yet +adopted the W3C KeyboardEvent.code standard for detecting key presses. +It maps the deprecated KeyboardEvent.keycode values to the W3C UI event codes.

+

Unfortunately, it is not very fine-grained: for instance, there is no difference between Alt Left and Alt +Right, or between Enter and Numpad Enter. Use at your own risk (or upgrade your browser...).

+
+ + + +
Type:
+
    +
  • + +Object.<number, String> + + +
  • +
+ + + + + + + + +

(protected, static, readonly) _pygletMap :Object.<String, String>

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates pyglet key names to the corresponding W3C KeyboardEvent codes values.

+

More information can be found here

+
+ + + +
Type:
+
    +
  • + +Object.<String, String> + + +
  • +
+ + + + + + + + +

(protected, static, readonly) _reversePygletMap :Object.<String, String>

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names. +

+ + + +
Type:
+
    +
  • + +Object.<String, String> + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(static) keycode2w3c(keycode) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert a keycode to a W3C UI Event code.

+

This is for legacy browsers.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
keycode + + +number + + + +

the keycode

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

corresponding W3C UI Event code

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(static) pyglet2w3c(pygletKeyList) → {Array.string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values.

+

This allows key lists that work in the builder environment to work in psychoJS web experiments.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pygletKeyList + + +Array.string + + + +

the array of pyglet key names

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the w3c keyList

+
+ + + +
+
+ Type +
+
+ +Array.string + + +
+
+ + + + + + + + + + +

(static) w3c2pyglet(code) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert a W3C Key Code into a pyglet key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
code + + +string + + + +

W3C Key Code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

corresponding pyglet key

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(protected) _addKeyListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add key listeners to the document.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

addMouseListeners(renderer)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add various mouse listeners to the Pixi renderer of the Window.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
renderer + + +PIXI.Renderer + + + +

The Pixi renderer

+ + + + + + + + + + + + + + + + + + + + + + + + +

clearEvents()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • handle the attribs argument
  • +
+
+ +
+ + + + + +
+

Clear all events from the event buffer.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

clearKeys()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clear all keys from the key buffer.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getKeys(options) → {Array.<string>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the list of keys pressed by the participant.

+

Note: The w3c key-event viewer can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
keyList + + +Array.<string> + + + + + + <optional>
+ + + + + +
+ + null + +

keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely.

timeStamped + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time).

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the list of keys that were pressed.

+
+ + + +
+
+ Type +
+
+ +Array.<string> + + +
+
+ + + + + + + + + + +

getMouseInfo() → {EventManager.MouseInfo}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the mouse info.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the mouse info.

+
+ + + +
+
+ Type +
+
+ +EventManager.MouseInfo + + +
+
+ + + + + + + + + + +

resetMoveClock()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • not implemented
  • +
+
+ +
+ + + + + +
+

Reset the move clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

startMoveClock()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • not implemented
  • +
+
+ +
+ + + + + +
+

Start the move clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

stopMoveClock()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • not implemented
  • +
+
+ +
+ + + + + +
+

Stop the move clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Type Definitions

+ + + +

ButtonInfo

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pressed + + +Array.number + + + +

the status of each mouse button [left, center, right]: 1 for pressed, 0 for released

clocks + + +Array.Clock + + + +

the clocks associated to the mouse buttons, reset whenever the button is pressed

times + + +Array.number + + + +

the time elapsed since the last rest of the associated clock

+ + + + + + + + + + + + + + + +

MouseInfo

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pos + + +Array.number + + + +

the position of the mouse [x, y]

wheelRel + + +Array.number + + + +

the relative position of the wheel [x, y]

buttons + + +EventManager.ButtonInfo + + + +

the mouse button info

moveClock + + +Clock + + + +

the clock that is reset whenever the mouse moves

+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ExperimentHandler.html b/docs/ExperimentHandler.html new file mode 100644 index 00000000..0e77cd70 --- /dev/null +++ b/docs/ExperimentHandler.html @@ -0,0 +1,2010 @@ + + + + + + ExperimentHandler - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

ExperimentHandler

+ + + + + + + +
+ +
+ +

+ data + + ExperimentHandler +

+ +

An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful +for generating a single data file from an experiment with many different loops (e.g. interleaved +staircases or loops within loops.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new ExperimentHandler(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

name + + +string + + + +

name of the experiment

extraInfo + + +Object + + + +

additional information, such as session name, participant name, etc.

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Environment :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
SERVER + + +Symbol + + + +
LOCAL + + +Symbol + + + +
+ + + + + + +
+

Experiment environment.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) SaveFormat :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
CSV + + +Symbol + + + +

Results are saved to a .csv file

DATABASE + + +Symbol + + + +

Results are saved to a database

+ + + + + + +
+

Experiment result format

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

_thisEntry

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Legacy experiment getters.

+
+ + + + + + + + + + +

experimentEnded

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Getter for experimentEnded.

+
+ + + + + + + + + + +

experimentEnded

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for experimentEnded.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected, static) _getLoopAttributes(loop)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the attribute names and values for the current trial of a given loop.

+

Only info relating to the trial execution are returned.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loop + + +Object + + + +

the loop

+ + + + + + + + + + + + + + + + + + + + + + + + +

addData(key, value)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add the key/value pair.

+

Multiple key/value pairs can be added to any given entry of the data file. There are +considered part of the same entry until a call to nextEntry is made.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Object + + + +

the key

value + + +Object + + + +

the value

+ + + + + + + + + + + + + + + + + + + + + + + + +

addLoop(loop)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add a loop.

+

The loop might be a TrialHandler, for instance.

+

Data from this loop will be included in the resulting data files.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loop + + +Object + + + +

the loop, e.g. an instance of TrialHandler or StairHandler

+ + + + + + + + + + + + + + + + + + + + + + + + +

isEntryEmpty() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • This really should be renamed: IsCurrentEntryNotEmpty
  • +
+
+ +
+ + + + + +
+

Whether or not the current entry (i.e. trial data) is empty.

+

Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the current entry is empty

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

nextEntry(snapshots)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Inform this ExperimentHandler that the current trial has ended. Further calls to addData +will be associated with the next trial.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
snapshots + + +Object +| + +Array.<Object> +| + +undefined + + + +

array of loop snapshots

+ + + + + + + + + + + + + + + + + + + + + + + + +

removeLoop(loop)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Remove the given loop from the list of unfinished loops, e.g. when it has completed.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loop + + +Object + + + +

the loop, e.g. an instance of TrialHandler or StairHandler

+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) save(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Save the results of the experiment.

+
    +
  • For an experiment running locally, the results are offered for immediate download.
  • +
  • For an experiment running on the server, the results are uploaded to the server.
  • +
+

+

+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
attributes + + +Array.<Object> + + + + + + <optional>
+ + + + + +
+ +

the attributes to be saved

sync + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to communicate with the server in a synchronous manner

tag + + +string + + + + + + <optional>
+ + + + + +
+ + '' + +

an optional tag to add to the filename to which the data is saved (for CSV and XLSX saving options)

clear + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to clear all experiment results immediately after they are saved (this is useful when saving data in separate chunks, throughout an experiment)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/FaceDetector.html b/docs/FaceDetector.html new file mode 100644 index 00000000..415f795a --- /dev/null +++ b/docs/FaceDetector.html @@ -0,0 +1,1744 @@ + + + + + + FaceDetector - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

FaceDetector

+ + + + + + + +
+ +
+ +

+ visual + + FaceDetector +

+ +

This manager handles the detecting of faces in video streams. FaceDetector relies on the +Face-API library developed by +Vincent Muehler.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new FaceDetector(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from the detector

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

input + + +string +| + +HTMLVideoElement +| + +module:visual.Camera + + + + + + + + + + + +

the name of a +movie resource or of a HTMLVideoElement or of a Camera component

faceApiUrl + + +string + + + + + + <optional>
+ + + + + +
+ + 'face-api.js' + +

the Url of the face-api library

modelDir + + +string + + + + + + <optional>
+ + + + + +
+ + 'models' + +

the directory where to find the face detection models

units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the stimulus (e.g. for size, position, vertices)

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the stimulus

units + + +string + + + + + + <optional>
+ + + + + +
+ + 'norm' + +

the units of the stimulus vertices, size and position

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

size + + +number + + + + + + <optional>
+ + + + + +
+ +

the size of the rendered image (the size of the image will be used if size is not specified)

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async, protected) _initFaceApi()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Init the Face-API library.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the visual representation of the detected faces, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

isReady() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Query whether or not the face detector is ready to detect.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the face detector is ready to detect

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

setInput(input, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the video attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
input + + +string +| + +HTMLVideoElement +| + +module:visual.Camera + + + + + + + + + + + +

the name of a +movie resource or a HTMLVideoElement or a Camera component

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

start(period, detectionCallback, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start detecting faces.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
period + + +number + + + + + + + + + + + +

the detection period, in ms (e.g. 100 ms for 10Hz)

detectionCallback + + + + + + + + + +

the callback triggered when detection results are available

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

stop(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop detecting faces.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Form.html b/docs/Form.html new file mode 100644 index 00000000..08963fa2 --- /dev/null +++ b/docs/Form.html @@ -0,0 +1,4669 @@ + + + + + + Form - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Form

+ + + + + + + +
+ +
+ +

+ visual + + Form +

+ +

Form stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Form(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the slider

size + + +Array.<number> + + + + + + + + + + + +

the size of the slider, e.g. [1, 0.1] for an horizontal slider

units + + +string + + + + + + <optional>
+ + + + + +
+ + 'height' + +

the units of the Slider position, and font size

color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('LightGray') + +

the color of the slider

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast of the slider

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity of the slider

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth (i.e. the z order), note that the text, radio buttons and slider elements are at depth - 1

items + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [] + +

the array of labels

itemPadding + + +number + + + + + + <optional>
+ + + + + +
+ + 0.05 + +

the granularity

font + + +string + + + + + + <optional>
+ + + + + +
+ + 'Arial' + +

the text font

fontFamily + + +string + + + + + + <optional>
+ + + + + +
+ + 'Helvetica' + +

the text font

bold + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the font of the labels is bold

italic + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the font of the labels is italic

fontSize + + +number + + + + + + <optional>
+ + + + + +
+ +

the font size of the labels (in form units), the default fontSize +depends on the Form units: 14 for 'pix', 0.03 otherwise

clipMask + + +PIXI.Graphics + + + + + + <optional>
+ + + + + +
+ + null + +

the clip mask

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every +frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static, readonly) _defaultItems

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Default form item.

+
+ + + + + + + + + + +

(static, readonly) Layout :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
HORIZONTAL + + +Symbol + + + +
VERTICAL + + +Symbol + + + +
+ + + + + + +
+

Form item layout.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Types :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
HEADING + + +Symbol + + + +
DESCRIPTION + + +Symbol + + + +
RATING + + +Symbol + + + +
SLIDER + + +Symbol + + + +
FREE_TEXT + + +Symbol + + + +
CHOICE + + +Symbol + + + +
RADIO + + +Symbol + + + +
+ + + + + + +
+

Form item types.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getBoundingBox_px() → {PIXI.Rectangle}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the bounding box in pixel coordinates

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the bounding box, in pixel coordinates

+
+ + + +
+
+ Type +
+
+ +PIXI.Rectangle + + +
+
+ + + + + + + + + + +

(protected) _importItems()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Import the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _onChange(withPixiopt, withBoundingBoxopt) → {function}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Generate a callback that prepares updates to the stimulus. +This is typically called in the constructor of a stimulus, when attributes are added +with _addAttribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
withPixi + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the PIXI representation must +also be updated

withBoundingBox + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to immediately estimate +the bounding box

+ + + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +function + + +
+
+ + + + + + + + + + +

(protected) _processItems()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Import and process the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _sanitizeItems()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Sanitize the form items: check that the keys are valid, and fill in default values.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupStimuli()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the stimuli, and the scrollbar.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateDecorations()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the form decorations (bounding box, lines between items, etc.)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the form visual representation, if necessary.

+

This estimate which stimuli are visible, and updates the decorations.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateVisibleStimuli()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the visible stimuli.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

addDataToExp(experiment, formatopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add the form data to the given experiment.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
experiment + + +module:data.ExperimentHandler + + + + + + + + + + + +

the experiment into which to insert the form data

format + + +string + + + + + + <optional>
+ + + + + +
+ + 'rows' + +

whether to insert the data as rows or as columns

+ + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside the bounding box of the stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +string + + + +

the units

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the object is inside the bounding box of the stimulus

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

draw()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Overridden draw that also calls the draw method of all form elements.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

formComplete() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Check if the form is complete.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • whether there are any remaining incomplete responses.
  • +
+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

getData() → {object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Collate the questions and responses into a single dataset.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the dataset with all questions and responses.
  • +
+
+ + + +
+
+ Type +
+
+ +object + + +
+
+ + + + + + + + + + +

hide()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Overridden hide that also calls the hide method of all form elements.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

refresh()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Force a refresh of the stimulus.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

release(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Release the PIXI representation, if there is one.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

reset()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the form.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setAutoDraw(autoDraw, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the autoDraw attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
autoDraw + + +boolean + + + + + + + + + + + +

the new value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setDepth(depth, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the depth attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
depth + + +Array.<number> + + + + + + + + + + + + 0 + +

order in which stimuli is rendered, kind of css's z-index with a negative sign.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setOri(ori, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the orientation attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
ori + + +number + + + + + + + + + + + +

the orientation in degree with 0 as the vertical position, positive values rotate clockwise.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setPos(pos, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the position attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
pos + + +Array.<number> + + + + + + + + + + + +

position of the center of the stimulus, in stimulus units

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setSize(size, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the size attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
size + + +undefined +| + +null +| + +number +| + +Array.<number> + + + + + + + + + + + +

the stimulus size

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/GUI.html b/docs/GUI.html new file mode 100644 index 00000000..c1386d90 --- /dev/null +++ b/docs/GUI.html @@ -0,0 +1,2417 @@ + + + + + + GUI - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

GUI

+ + + + + + + +
+ +
+ +

+ core + + GUI +

+ +

GUI manages the various pop-up dialog boxes that guide the participant, throughout the +lifecycle of the experiment, e.g. at the start while the resources are downloading, or at the +end when the data is uploading to the server

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new GUI(psychoJS)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

DEFAULT_SETTINGS :Object

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Default settings for GUI.

+
+ + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(protected, static) _onKeyChange(gui, event)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback triggered upon change event (for required keys).

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
gui + + +module:core.GUI + + + +

this GUI

event + + +Event + + + +

the key's event

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _onCancelExperiment()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback triggered when the participant presses the Cancel button

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _onResourceEvents(signal)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback triggered upon a resource event from the Server Manager.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
signal + + +Object.<string, (string|Symbol)> + + + +

the ServerManager's signal

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _onStartExperiment()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback triggered when the participant presses the OK button

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setProgressMessage(message)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the progress message.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +string + + + +

the message

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateDialog(changeOKButtonFocusopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the dialog box.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
changeOKButtonFocus + + + + <optional>
+ + + + + +
+ + false + +

whether to change the focus to the OK button

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateProgressBar()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the progress bar.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _userFriendlyError(errorCode) → {Object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the user-friendly html message associated to a pavlovia.or server error code.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
errorCode + + +number + + + +

the pavlovia.org server error code

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

a user-friendly error message

+
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + +

closeDialog()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Close the previously opened dialog box, if there is one.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

dialog(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Show a message to the participant in a dialog box.

+

This function can be used to display both warning and error messages.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
message + + +string + + + + + + + + + + + +

the message to be displayed

error + + +Object.<string, *> + + + + + + + + + + + +

an exception

warning + + +string + + + + + + + + + + + +

a warning message

showOK + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

specifies whether to show the OK button

onOK + + +GUI.onOK + + + + + + <optional>
+ + + + + +
+ +

function called when the participant presses the OK button

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

DlgFromDict(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Create a dialog box that (a) enables the participant to set some +experimental values (e.g. the session name), (b) shows progress of resource +download, and (c) enables the participant to cancel the experiment.

+

Setting experiment values

+

DlgFromDict displays an input field for all values in the dictionary. +It is possible to specify default values e.g.:

+let expName = 'stroop';
+let expInfo = {'participant':'', 'session':'01'};
+psychoJS.schedule(psychoJS.gui.DlgFromDict({dictionary: expInfo, title: expName}));
+

If the participant cancels (by pressing Cancel or by closing the dialog box), then +the dictionary remains unchanged.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
logoUrl + + +String + + + + + + <optional>
+ + + + + +
+ +

Url of the experiment logo

text + + +String + + + + + + <optional>
+ + + + + +
+ +

information text

dictionary + + +Object + + + + + + + + + + + +

associative array of values for the participant to set

title + + +String + + + + + + + + + + + +

name of the project

requireParticipantClick + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether the participant must click on the OK +button, when it becomes enabled, to move on with the experiment

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

finishDialog(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Create a dialog box with a progress bar, to inform the participant of +the last stages of the experiment: upload of results, of log, and closing +of session.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
text + + +String + + + + + + <optional>
+ + + + + +

information text

+ +
+ + + + + + + + + + + + + + + + + + + + + + + +

Type Definitions

+ + + + + + +

onOK()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/GratingStim.html b/docs/GratingStim.html new file mode 100644 index 00000000..032ef7bd --- /dev/null +++ b/docs/GratingStim.html @@ -0,0 +1,3195 @@ + + + + + + GratingStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

GratingStim

+ + + + + + + +
+ +
+ +

+ visual + + GratingStim +

+ +

Grating Stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new GratingStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +Window + + + + + + + + + + + +

the associated Window

tex + + +String +| + +HTMLImageElement + + + + + + <optional>
+ + + + + +
+ + "sin" + +

the name of the predefined grating texture or image resource or the HTMLImageElement corresponding to the texture

mask + + +String +| + +HTMLImageElement + + + + + + <optional>
+ + + + + +
+ +

the name of the mask resource or HTMLImageElement corresponding to the mask

units + + +String + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the stimulus (e.g. for size, position, vertices)

sf + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

spatial frequency of the function used in grating stimulus

phase + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

phase of the function used in grating stimulus, multiples of period of that function

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the stimulus

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

size + + +number + + + + + + <optional>
+ + + + + +
+ +

the size of the rendered image (DEFAULT_STIM_SIZE_PX will be used if size is not specified)

color + + +Color + + + + + + <optional>
+ + + + + +
+ + "white" + +

Foreground color of the stimulus. Can be String like "red" or "#ff0000" or Number like 0xff0000.

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

Set the opacity of the stimulus. Determines how visible the stimulus is relative to background.

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

Set the contrast of the stimulus, i.e. scales how far the stimulus deviates from the middle grey. Ranges [-1, 1].

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth (i.e. the z order)

interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

Whether to interpolate (linearly) the texture in the stimulus. Currently supports only image based gratings.

blendmode + + +String + + + + + + <optional>
+ + + + + +
+ + "avg" + +

blend mode of the stimulus, determines how the stimulus is blended with the background. Supported values: "avg", "add", "mul", "screen".

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getDisplaySize() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the size of the display image, which is either that of the GratingStim or that of the image +it contains.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the size of the displayed image

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _getPixiMeshFromPredefinedShaders(shaderName, uniforms) → {Pixi.Mesh}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Generate PIXI.Mesh object based on provided shader function name and uniforms.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
shaderName + + +String + + + +

name of the shader. Must be one of the SHADERS

uniforms + + +Object + + + +

a set of uniforms to supply to the shader. Mixed together with default uniform values.

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

Pixi.Mesh object that represents shader and later added to the scene.

+
+ + + +
+
+ Type +
+
+ +Pixi.Mesh + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setBlendmode(blendMode, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set blend mode of the grating stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
blendMode + + +String + + + + + + + + + + + + avg + +

blend mode, can be one of the following: ["avg", "add", "mul", "screen"].

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setColor(colorVal, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set foreground color value for the grating stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
colorVal + + +Color + + + + + + + + + + + + white + +

color value, can be String like "red" or "#ff0000" or Number like 0xff0000.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setColorSpace(colorSpaceVal, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set color space value for the grating stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
colorSpaceVal + + +String + + + + + + + + + + + + RGB + +

color space value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setInterpolate(interpolate, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Whether to interpolate (linearly) the texture in the stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
interpolate + + +boolean + + + + + + + + + + + + false + +

interpolate or not.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setMask(mask, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the mask attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
mask + + +HTMLImageElement +| + +string + + + + + + + + + + + +

the name of the mask resource or HTMLImageElement corresponding to the mask

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setOpacity(opacityopt, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determines how visible the stimulus is relative to background.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1 + +

opacity - The value should be a single float ranging 1.0 (opaque) to 0.0 (transparent).

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setPhase(phase, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set phase value for the function.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
phase + + +number + + + + + + + + + + + +

phase value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setSF(sf, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set spatial frequency value for the function.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
sf + + +number + + + + + + + + + + + +

spatial frequency value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setTex(tex, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the tex attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
tex + + +HTMLImageElement +| + +string + + + + + + + + + + + +

the name of built in shader function or name of the image resource or HTMLImageElement corresponding to the image

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ImageStim.html b/docs/ImageStim.html new file mode 100644 index 00000000..43fe403d --- /dev/null +++ b/docs/ImageStim.html @@ -0,0 +1,1899 @@ + + + + + + ImageStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

ImageStim

+ + + + + + + +
+ +
+ +

+ visual + + ImageStim +

+ +

Image Stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new ImageStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +Window + + + + + + + + + + + +

the associated Window

image + + +string +| + +HTMLImageElement + + + + + + + + + + + +

the name of the image resource or the HTMLImageElement corresponding to the image

mask + + +string +| + +HTMLImageElement + + + + + + + + + + + +

the name of the mask resource or HTMLImageElement corresponding to the mask

units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the stimulus (e.g. for size, position, vertices)

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the stimulus

units + + +string + + + + + + <optional>
+ + + + + +
+ + 'norm' + +

the units of the stimulus vertices, size and position

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

size + + +number + + + + + + <optional>
+ + + + + +
+ +

the size of the rendered image (the size of the image will be used if size is not specified)

color + + +Color + + + + + + <optional>
+ + + + + +
+ + 'white' + +

the background color

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth (i.e. the z order)

texRes + + +number + + + + + + <optional>
+ + + + + +
+ + 128 + +

the resolution of the text

interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the image is interpolated

flipHoriz + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip horizontally

flipVert + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip vertically

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getDisplaySize() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the size of the display image, which is either that of the ImageStim or that of the image +it contains.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the size of the displayed image

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setImage(image, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the image attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
image + + +HTMLImageElement +| + +string + + + + + + + + + + + +

the name of the image resource or HTMLImageElement corresponding to the image

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setInterpolate(interpolate, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Whether to interpolate (linearly) the texture in the stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
interpolate + + +boolean + + + + + + + + + + + + false + +

interpolate or not.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setMask(mask, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the mask attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
mask + + +HTMLImageElement +| + +string + + + + + + + + + + + +

the name of the mask resource or HTMLImageElement corresponding to the mask

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/KeyPress.html b/docs/KeyPress.html new file mode 100644 index 00000000..6f2ad691 --- /dev/null +++ b/docs/KeyPress.html @@ -0,0 +1,301 @@ + + + + + + KeyPress - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

KeyPress

+ + + + + + + +
+ +
+ +

+ core + + KeyPress +

+ +

<pKeyPress holds information about a key that has been pressed, such as the duration of the press.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new KeyPress(code, tDown, name)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
code + + +string + + + +

W3C Key Code

tDown + + +number + + + +

time of key press (keydown event) relative to the global Monotonic Clock

name + + +string +| + +undefined + + + +

pyglet key name

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Keyboard.html b/docs/Keyboard.html new file mode 100644 index 00000000..37c873d2 --- /dev/null +++ b/docs/Keyboard.html @@ -0,0 +1,1793 @@ + + + + + + Keyboard - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Keyboard

+ + + + + + + +
+ +
+ +

+ core + + Keyboard +

+ +

This manager handles all keyboard events. It is a substitute for the keyboard component of EventManager.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Keyboard(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

bufferSize + + +number + + + + + + <optional>
+ + + + + +
+ + 10000 + +

the maximum size of the circular keyboard event buffer

waitForStart + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to wait for a call to module:core.Keyboard#start +before recording keyboard events

clock + + +Clock + + + + + + <optional>
+ + + + + +
+ +

an optional clock

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) KeyStatus :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
KEY_DOWN + + +Symbol + + + +
KEY_UP + + +Symbol + + + +
+ + + + + + +
+

Keyboard KeyStatus.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(static) includes(keypressList, keyName) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Test whether a list of KeyPress's contains one with a particular name.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
keypressList + + +Array.<module:core.KeyPress> + + + +

list of KeyPress's

keyName + + +string + + + +

pyglet key name, e.g. 'escape', 'left'

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not a KeyPress with the given pyglet key name is present in the list

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

(protected) _addKeyListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add key listeners to the document.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

clearEvents()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clear all events and resets the circular buffers.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getEvents() → {Array.<Keyboard.KeyEvent>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the list of those keyboard events still in the buffer, i.e. those that have not been +previously cleared by calls to getKeys with clear = true.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the list of events still in the buffer

+
+ + + +
+
+ Type +
+
+ +Array.<Keyboard.KeyEvent> + + +
+
+ + + + + + + + + + +

getKeys(options) → {Array.<KeyPress>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the list of keys pressed or pushed by the participant.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
keyList + + +Array.<string> + + + + + + <optional>
+ + + + + +
+ + [] + +

the list of keys to consider. If keyList is empty, we consider all keys. +Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy.

waitRelease + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to include those keys pressed but not released. If +waitRelease = false, key presses without a corresponding key release will have an undefined duration.

clear + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to keep in the buffer the key presses or pushes for a subsequent call to getKeys. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the list of keys that were pressed (keydown followed by keyup) or pushed +(keydown with no subsequent keyup at the time getKeys is called).

+
+ + + +
+
+ Type +
+
+ +Array.<KeyPress> + + +
+
+ + + + + + + + + + +

start()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start recording keyboard events.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

stop()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop recording keyboard events.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Type Definitions

+ + + +

KeyEvent

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
W3C + + +string + + + +

key code

W3C + + +string + + + +

key

pyglet + + +string + + + +

key

key + + +module:core.Keyboard#KeyStatus + + + +

status

timestamp + + +number + + + +

(in seconds)

+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Logger.html b/docs/Logger.html new file mode 100644 index 00000000..3c1126cc --- /dev/null +++ b/docs/Logger.html @@ -0,0 +1,2177 @@ + + + + + + Logger - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Logger

+ + + + + + + +
+ +
+ +

+ core + + Logger +

+ +

This class handles a variety of loggers, e.g. a browser console one (mostly for debugging), +a remote one, etc.

+

Note: we use log4javascript for the console logger, and our own for the server logger.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Logger(psychoJS, threshold)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

threshold + + +* + + + +

the logging threshold, e.g. log4javascript.Level.ERROR

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static, readonly) _ServerLevelValue :number

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
CRITICAL + + +number + + + +
ERROR + + +number + + + +
WARNING + + +number + + + +
DATA + + +number + + + +
EXP + + +number + + + +
INFO + + +number + + + +
DEBUG + + +number + + + +
NOTSET + + +number + + + +
+ + + + + + +
+

Server logging level values.

+

We use those values to determine whether a log is to be sent to the server or not.

+
+ + + +
Type:
+
    +
  • + +number + + +
  • +
+ + + + + + + + +

(static, readonly) ServerLevel :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
CRITICAL + + +Symbol + + + +
ERROR + + +Symbol + + + +
WARNING + + +Symbol + + + +
DATA + + +Symbol + + + +
EXP + + +Symbol + + + +
INFO + + +Symbol + + + +
DEBUG + + +Symbol + + + +
NOTSET + + +Symbol + + + +
+ + + + + + +
+

Server logging level.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(protected) _customConsoleLayout() → {*}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Create a custom console layout.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the custom layout

+
+ + + +
+
+ Type +
+
+ +* + + +
+
+ + + + + + + + + + +

(protected) _getValue(level) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the integer value associated with a logging level.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
level + + +module:core.Logger.ServerLevel + + + +

the logging level

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the value associated with the logging level, or 30 is the logging level is unknown.
  • +
+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected) _throttle(time) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Check whether or not a log messages must be throttled.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
time + + +number + + + +

the time of the latest log message

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not to log the message

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

data(msg, timeopt, objopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Log a server message at the DATA level.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
msg + + +string + + + + + + + + + +

the message to be logged.

time + + +number + + + + + + <optional>
+ + + + + +

the logging time

obj + + +object + + + + + + <optional>
+ + + + + +

the associated object (e.g. a Trial)

+ + + + + + + + + + + + + + + + + + + + + + + + +

exp(msg, timeopt, objopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Log a server message at the EXP level.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
msg + + +string + + + + + + + + + +

the message to be logged.

time + + +number + + + + + + <optional>
+ + + + + +

the logging time

obj + + +object + + + + + + <optional>
+ + + + + +

the associated object (e.g. a Trial)

+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) flush()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Flush all server logs to the server.

+

Note: the logs are compressed using Pako's zlib algorithm. +See https://github.com/nodeca/pako for details.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

log(msg, level, timeopt, objopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Log a server message.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
msg + + +string + + + + + + + + + +

the message to be logged.

level + + +module:core.Logger.ServerLevel + + + + + + + + + +

logging level

time + + +number + + + + + + <optional>
+ + + + + +

the logging time

obj + + +object + + + + + + <optional>
+ + + + + +

the associated object (e.g. a Trial)

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLevel(serverLevel)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Change the logging level.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
serverLevel + + +module:core.Logger.ServerLevel + + + +

the new logging level

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Microphone.html b/docs/Microphone.html new file mode 100644 index 00000000..9fa12a37 --- /dev/null +++ b/docs/Microphone.html @@ -0,0 +1,1946 @@ + + + + + + Microphone - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Microphone

+ + + + + + + +
+ +
+ +

+ sound + + Microphone +

+ +

This manager handles the recording of audio signal.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Microphone(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
win + + +module:core.Window + + + + + + + + + + + +

the associated Window

name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

format + + +string + + + + + + <optional>
+ + + + + +
+ + 'audio/webm;codecs=opus' + +

the format for the audio file

sampleRateHz + + +number + + + + + + <optional>
+ + + + + +
+ + 48000 + +

the audio sampling rate, in Hz

clock + + +Clock + + + + + + <optional>
+ + + + + +
+ +

an optional clock

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _onChange()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback for changes to the recording settings.

+

Changes to the settings require the recording to stop and be re-started.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async, protected) _prepareRecording()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Prepare the recording.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

download(filename)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Offer the audio recording to the participant as a sound file to download.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
filename + + +string + + + + + + audio.webm + +

the filename

+ + + + + + + + + + + + + + + + + + + + + + + + +

flush() → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Submit a request to flush the recording.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the data has actually been made available

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

(async) getRecording(tag, flushopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current audio recording as an AudioClip in the given format.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
tag + + +string + + + + + + + + + + + +

an optional tag for the audio clip

flush + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to first flush the recording

+ + + + + + + + + + + + + + + + + + + + + + + + +

pause() → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Submit a request to pause the recording.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the recording actually paused

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

resume(options) → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Submit a request to resume the recording.

+

resume has no effect if the recording was not previously paused.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
clear + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to empty the audio buffer before +resuming the recording

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the recording actually resumed

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

start() → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Submit a request to start the recording.

+

Note that it typically takes 50ms-200ms for the recording to actually starts once +a request to start has been submitted.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the recording actually started

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

stop(options) → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Submit a request to stop the recording.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
filename + + +string + + + + + + <optional>
+ + + + + +

the name of the file to which the audio recording will be +saved

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the recording actually stopped, and the recorded +data was made available

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

(async) upload(tag)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Upload the audio recording to the pavlovia server.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
tag + + +string + + + +

an optional tag for the audio file

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/MinimalStim.html b/docs/MinimalStim.html new file mode 100644 index 00000000..be8dc293 --- /dev/null +++ b/docs/MinimalStim.html @@ -0,0 +1,1213 @@ + + + + + + MinimalStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

MinimalStim

+ + + + + + + +
+ +
+ +

+ core + + MinimalStim +

+ +

MinimalStim is the base class for all stimuli.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new MinimalStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + win.autoLog + +

whether to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(abstract, protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+

Note: this is an abstract function, which should not be called.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) contains(object, units)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside this stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +String + + + +

the stimulus units

+ + + + + + + + + + + + + + + + + + + + + + + + +

draw()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Draw this stimulus on the next frame draw.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

hide()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Hide this stimulus on the next frame draw.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

release(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Release the PIXI representation, if there is one.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setAutoDraw(autoDraw, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the autoDraw attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
autoDraw + + +boolean + + + + + + + + + + + +

the new value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/MonotonicClock.html b/docs/MonotonicClock.html new file mode 100644 index 00000000..51ae4ed9 --- /dev/null +++ b/docs/MonotonicClock.html @@ -0,0 +1,987 @@ + + + + + + MonotonicClock - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

MonotonicClock

+ + + + + + + +
+ +
+ +

+ util + + MonotonicClock +

+ +

MonotonicClock offers a convenient way to keep track of time during experiments. An experiment can have as many independent clocks as needed, e.g. one to time responses, another one to keep track of stimuli, etc.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new MonotonicClock(startTimeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
startTime + + +number + + + + + + <optional>
+ + + + + +
+ + <time elapsed since the reference point, i.e. the time when the module was loaded> + +

the clock's start time (in ms)

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static) _referenceTime :number

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

The clock's referenceTime is the time when the module was loaded (in seconds).

+
+ + + +
Type:
+
    +
  • + +number + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(static) getDate(locales, options) → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current timestamp with language-sensitive formatting rules applied.

+

Note: This is just a convenience wrapper around `Intl.DateTimeFormat()`.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
locales + + +string +| + +array.string + + + + + + en-CA + +

A string with a BCP 47 language tag, or an array of such strings.

options + + +object + + + + + +

An object with detailed date and time styling information.

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

The current timestamp in the chosen format.

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(static) getDateStr() → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the clock's current time in the default format filtering out file name unsafe characters.

+

Note: This is mostly used as an appendix to the name of the keys save to the server.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

A string representing the current time formatted as YYYY-MM-DD_HH[h]mm.ss.sss

+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

(static) getReferenceTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the time elapsed since the reference point.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the time elapsed since the reference point (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

getLastResetTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current offset being applied to the high resolution timebase used by this Clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the offset (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

getTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current time on this clock.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the current time (in seconds)

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Mouse.html b/docs/Mouse.html new file mode 100644 index 00000000..dcba2bc8 --- /dev/null +++ b/docs/Mouse.html @@ -0,0 +1,1757 @@ + + + + + + Mouse - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Mouse

+ + + + + + + +
+ +
+ +

+ core + + Mouse +

+ +

This manager handles the interactions between the experiment's stimuli and the mouse.

+

Note: the unit of Mouse is that of its associated Window.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Mouse(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • visible is not handled at the moment (mouse is always visible)
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +Window + + + + + + + + + + + +

the associated Window

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

clickReset(buttonsopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the clocks associated to the given mouse buttons.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
buttons + + +Array.number + + + + + + <optional>
+ + + + + +
+ + [0,1,2] + +

the buttons to reset (0: left, 1: center, 2: right)

+ + + + + + + + + + + + + + + + + + + + + + + + +

getPos() → {Array.number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current position of the mouse in mouse/Window units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the position of the mouse in mouse/Window units

+
+ + + +
+
+ Type +
+
+ +Array.number + + +
+
+ + + + + + + + + + +

getPressed(getTimeopt) → {Array.number|Array.<Array.number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to clickReset and the pressing or releasing of the buttons.

+

Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
getTime + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to also return timestamps

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps.

+
+ + + +
+
+ Type +
+
+ +Array.number +| + +Array.<Array.number> + + +
+
+ + + + + + + + + + +

getRel() → {Array.number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the position of the mouse relative to that at the last call to getRel +or getPos, in mouse/Window units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the relation position of the mouse in mouse/Window units.

+
+ + + +
+
+ Type +
+
+ +Array.number + + +
+
+ + + + + + + + + + +

getWheelRel() → {Array.number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the travel of the mouse scroll wheel since the last call to getWheelRel.

+

Note: Even though this method returns a [x, y] array, for most wheels/systems y is the only +value that varies.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the mouse scroll wheel travel

+
+ + + +
+
+ Type +
+
+ +Array.number + + +
+
+ + + + + + + + + + +

isPressedIn(shape, buttonsopt, optionsopt) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Helper method for checking whether a stimulus has had any button presses within bounds.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
shape + + +object +| + +module:visual.VisualStim + + + + + + + + + +

A type of visual stimulus or object having a contains() method.

buttons + + +object +| + +number + + + + + + <optional>
+ + + + + +

The target button index potentially tucked inside an object.

options + + +object + + + + + + <optional>
+ + + + + +
+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
shape + + +object +| + +module:visual.VisualStim + + + + + + <optional>
+ + + + + +
buttons + + +number + + + + + + <optional>
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

Whether button pressed is contained within stimulus.

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

mouseMoved(distanceopt, resetopt) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether the mouse has moved beyond a certain distance.

+

distance +

    +
  • mouseMoved() or mouseMoved(undefined, false): determine whether the mouse has moved at all since the last +call to getPos
  • +
  • mouseMoved(distance: number, false): determine whether the mouse has travelled further than distance, in terms of line of sight
  • +
  • mouseMoved(distance: [number,number], false): determine whether the mouse has travelled horizontally or vertically further then the given horizontal and vertical distances
  • +

+

reset +

    +
  • mouseMoved(distance, true): reset the mouse move clock, return false
  • +
  • mouseMoved(distance, 'here'): return false
  • +
  • mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance
  • +

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
distance + + +undefined +| + +number +| + +Array.number + + + + + + <optional>
+ + + + + +
+ +

the distance to which the mouse movement is compared (see above for a full description)

reset + + +boolean +| + +String +| + +Array.number + + + + + + <optional>
+ + + + + +
+ + false + +

see above for a full description

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

see above for a full description

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

mouseMoveTime() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the amount of time elapsed since the last mouse movement.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the time elapsed since the last mouse movement

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/MovieStim.html b/docs/MovieStim.html new file mode 100644 index 00000000..7fa0abc3 --- /dev/null +++ b/docs/MovieStim.html @@ -0,0 +1,2363 @@ + + + + + + MovieStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

MovieStim

+ + + + + + + +
+ +
+ +

+ visual + + MovieStim +

+ +

Movie Stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new MovieStim(options, movie)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • autoPlay does not work for the moment.
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
options + + +Object + + + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +String + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + +

the associated Window

+ +
movie + + +string +| + +HTMLVideoElement +| + +module:visual.Camera + + + + + + + + + + + +

the name of a +movie resource or of a HTMLVideoElement or of a Camera component

options.units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the stimulus (e.g. for size, position, vertices)

options.pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the stimulus

options.units + + +string + + + + + + <optional>
+ + + + + +
+ + 'norm' + +

the units of the stimulus vertices, size and position

options.ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

options.size + + +number + + + + + + <optional>
+ + + + + +
+ +

the size of the rendered image (the size of the image will be used if size is not specified)

options.color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('white') + +

the background color

options.opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

options.contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

options.interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the image is interpolated

options.flipHoriz + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip horizontally

options.flipVert + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip vertically

options.loop + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to loop the movie

options.volume + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the volume of the audio track (must be between 0.0 and 1.0)

options.noAudio + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to play the audio

options.autoPlay + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to autoplay the video

options.autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

options.autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getDisplaySize() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the size of the display image, which is either that of the ImageStim or that of the image +it contains.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the size of the displayed image

+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

pause(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Pause the movie.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

play(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the movie.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

reset(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

seek(timePoint, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Jump to a specific timepoint

+

Note: seek is experimental and does not work on all browsers at the moment.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
timePoint + + +number + + + + + + + + + + + +

the timepoint to which to jump (in second)

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setMovie(movie, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the movie attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
movie + + +string +| + +HTMLVideoElement +| + +module:visual.Camera + + + + + + + + + + + +

the name of a +movie resource or of a HTMLVideoElement or of a Camera component

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

stop(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop the movie and reset to 0s.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/MultiStairHandler.html b/docs/MultiStairHandler.html new file mode 100644 index 00000000..7e3b581a --- /dev/null +++ b/docs/MultiStairHandler.html @@ -0,0 +1,1410 @@ + + + + + + MultiStairHandler - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

MultiStairHandler

+ + + + + + + +
+ +
+ +

+ data + + MultiStairHandler +

+ +

A handler dealing with multiple staircases, simultaneously.

+

Note that, at the moment, using the MultiStairHandler requires the jsQuest.js +library to be loaded as a resource, at the start of the experiment.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new MultiStairHandler(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + +

the handler options

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

varName + + +string + + + + + + + + + + + +

the name of the variable / intensity / contrast +/ threshold manipulated by the staircases

stairType + + +MultiStairHandler.StaircaseType + + + + + + <optional>
+ + + + + +
+ + "simple" + +

the +handler type

conditions + + +Array.<Object> +| + +String + + + + + + <optional>
+ + + + + +
+ + [undefined] + +

if it is a string, +we treat it as the name of a conditions resource

method + + +module:data.TrialHandler.Method + + + + + + + + + + + +

the trial method

nTrials + + +number + + + + + + <optional>
+ + + + + +
+ + 50 + +

maximum number of trials

randomSeed + + +number + + + + + + + + + + + +

seed for the random number generator

name + + +string + + + + + + + + + + + +

name of the handler

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • TrialHandler
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) StaircaseStatus :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
RUNNING + + +Symbol + + + +

The staircase is currently running.

FINISHED + + +Symbol + + + +

The staircase is now finished.

+ + + + + + +
+

Staircase status.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) StaircaseType :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
SIMPLE + + +Symbol + + + +

Simple staircase handler.

QUEST + + +Symbol + + + +

QUEST handler.

+ + + + + + +
+

MultiStairHandler staircase type.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

currentStaircase

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current staircase.

+
+ + + + + + + + + + +

intensity

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current intensity.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _nextTrial()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Move onto the next trial.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _prepareStaircases()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the staircases, according to the conditions.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _validateConditions()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Validate the conditions.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

addResponse()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add a response to the current staircase.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Polygon.html b/docs/Polygon.html new file mode 100644 index 00000000..8f6b4e40 --- /dev/null +++ b/docs/Polygon.html @@ -0,0 +1,2125 @@ + + + + + + Polygon - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Polygon

+ + + + + + + +
+ +
+ +

+ visual + + Polygon +

+ +

Polygonal visual stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Polygon(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Polygonal visual stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +Window + + + + + + + + + + + +

the associated Window

lineWidth + + +number + + + + + + <optional>
+ + + + + +
+ + 1.5 + +

the line width

lineColor + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('white') + +

the line color

fillColor + + +Color + + + + + + + + + + + +

the fill color

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

edges + + +number + + + + + + <optional>
+ + + + + +
+ + 3 + +

the number of edges of the polygon

radius + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + +

the radius of the polygon

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position

size + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the size

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

units + + +string + + + + + + + + + + + +

the units of the stimulus vertices, size and position

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth

interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the shape is interpolated

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + +
Inherited From:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getPixiPolygon() → {Object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the PIXI polygon (in pixel units) corresponding to the vertices.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the PIXI polygon corresponding to this stimulus vertices.

+
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + +

(protected) _getVertices_px() → {Array.<Array.<number>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the vertices in pixel units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the vertices (in pixel units)

+
+ + + +
+
+ Type +
+
+ +Array.<Array.<number>> + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + +
Inherited From:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside the bounding box of the ShapeStim.

+

This is overridden in order to provide a finer inclusion test.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +string + + + +

the units

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the object is inside the bounding box of the ShapeStim

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

setEdges(edges, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the edges attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
edges + + +number + + + + + + + + + + + +

the number of edges

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setRadius(radius, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the radius attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
radius + + +number + + + + + + + + + + + +

the polygon radius

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setVertices(vertices, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the vertices attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
vertices + + +Array.<Array.<number>> + + + + + + + + + + + +

the vertices

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/QuestHandler.html b/docs/QuestHandler.html new file mode 100644 index 00000000..5d843f75 --- /dev/null +++ b/docs/QuestHandler.html @@ -0,0 +1,2262 @@ + + + + + + QuestHandler - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

QuestHandler

+ + + + + + + +
+ +
+ +

+ data + + QuestHandler +

+ +

A Trial Handler that implements the Quest algorithm for quick measurement of + psychophysical thresholds. QuestHandler relies on the jsQuest library, a port of Prof Dennis Pelli's QUEST algorithm by Daiichiro Kuroki.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new QuestHandler(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + +

the handler options

+
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

varName + + +string + + + + + + + + + + + +

the name of the variable / intensity / contrast / threshold manipulated by QUEST

startVal + + +number + + + + + + + + + + + +

initial guess for the threshold

startValSd + + +number + + + + + + + + + + + +

standard deviation of the initial guess

minVal + + +number + + + + + + + + + + + +

minimum value for the threshold

maxVal + + +number + + + + + + + + + + + +

maximum value for the threshold

pThreshold + + +number + + + + + + <optional>
+ + + + + +
+ + 0.82 + +

threshold criterion expressed as probability of getting a correct response

nTrials + + +number + + + + + + + + + + + +

maximum number of trials

stopInterval + + +number + + + + + + + + + + + +

minimum [5%, 95%] confidence interval required for the loop to stop

method + + +QuestHandler.Method + + + + + + + + + + + +

the QUEST method

beta + + +number + + + + + + <optional>
+ + + + + +
+ + 3.5 + +

steepness of the QUEST psychometric function

delta + + +number + + + + + + <optional>
+ + + + + +
+ + 0.01 + +

fraction of trials with blind responses

gamma + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + +

fraction of trails that would generate a correct response when the threshold is infinitely small

grain + + +number + + + + + + <optional>
+ + + + + +
+ + 0.01 + +

quantization of the internal table

name + + +string + + + + + + + + + + + +

name of the handler

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • TrialHandler
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Method :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
QUANTILE + + +Symbol + + + +

Quantile threshold estimate.

MEAN + + +Symbol + + + +

Mean threshold estimate.

MODE + + +Symbol + + + +

Mode threshold estimate.

+ + + + + + +
+

QuestHandler method

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

intensity

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current value of the variable / contrast / threshold.

+

This is the getter associated to getQuestValue.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateQuestValue()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the next value of the QUEST variable, based on the current value +and on the selected QUEST method.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupJsQuest()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the JS Quest object.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

addResponse()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add a response and update the PDF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

confInterval()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get an estimate of the 5%-95% confidence interval (CI).

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getQuestValue() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current value of the variable / contrast / threshold.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the current QUEST value for the variable / contrast / threshold

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

mean() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the mean of the Quest posterior PDF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the mean

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

mode() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the mode of the Quest posterior PDF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the mode

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

quantile() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the standard deviation of the Quest posterior PDF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the quantile

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

sd() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the standard deviation of the Quest posterior PDF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the standard deviation

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

setMethod(method, log)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the method attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
method + + +mixed + + + +

the method value, PsychoPy-style values ("mean", "median", +"quantile") are converted to their respective QuestHandler.Method values

log + + +boolean + + + +

whether or not to log the change of seed

+ + + + + + + + + + + + + + + + + + + + + + + + +

simulate()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Simulate a response.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Rect.html b/docs/Rect.html new file mode 100644 index 00000000..b71fe10e --- /dev/null +++ b/docs/Rect.html @@ -0,0 +1,2214 @@ + + + + + + Rect - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Rect

+ + + + + + + +
+ +
+ +

+ visual + + Rect +

+ +

Rectangular visual stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Rect(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

lineWidth + + +number + + + + + + <optional>
+ + + + + +
+ + 1.5 + +

the line width

lineColor + + +Color + + + + + + <optional>
+ + + + + +
+ + 'white' + +

the line color

fillColor + + +Color + + + + + + <optional>
+ + + + + +
+ +

the fill color

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

width + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + +

the width of the rectangle

height + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + +

the height of the rectangle

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position

size + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the size

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

units + + +string + + + + + + <optional>
+ + + + + +
+ + "height" + +

the units of the stimulus vertices, size and position

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth

interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the shape is interpolated

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + +
Inherited From:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getPixiPolygon() → {Object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the PIXI polygon (in pixel units) corresponding to the vertices.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the PIXI polygon corresponding to this stimulus vertices.

+
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + +

(protected) _getVertices_px() → {Array.<Array.<number>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the vertices in pixel units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the vertices (in pixel units)

+
+ + + +
+
+ Type +
+
+ +Array.<Array.<number>> + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateVertices()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the vertices.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + +
Inherited From:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside the bounding box of the ShapeStim.

+

This is overridden in order to provide a finer inclusion test.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +string + + + +

the units

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the object is inside the bounding box of the ShapeStim

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

setHeight(height, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the height attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
height + + +number + + + + + + + + + + + +

the rectangle height

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setVertices(vertices, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the vertices attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
vertices + + +Array.<Array.<number>> + + + + + + + + + + + +

the vertices

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setWidth(width, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the width attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
width + + +number + + + + + + + + + + + +

the rectangle width

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Scheduler.html b/docs/Scheduler.html new file mode 100644 index 00000000..6efc906b --- /dev/null +++ b/docs/Scheduler.html @@ -0,0 +1,1478 @@ + + + + + + Scheduler - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Scheduler

+ + + + + + + +
+ +
+ +

+ util + + Scheduler +

+ +

A scheduler helps run the main loop by managing scheduled functions, +called tasks, after each frame is displayed.

+

+Tasks are either another Scheduler, or a +JavaScript functions returning one of the following codes: +

    +
  • Scheduler.Event.NEXT: Move onto the next task *without* rendering the scene first.
  • +
  • Scheduler.Event.FLIP_REPEAT: Render the scene and repeat the task.
  • +
  • Scheduler.Event.FLIP_NEXT: Render the scene and move onto the next task.
  • +
  • Scheduler.Event.QUIT: Quit the scheduler.
  • +
+

+

It is possible to create sub-schedulers, e.g. to handle loops. +Sub-schedulers are added to a parent scheduler as a normal +task would be by calling scheduler.add(subScheduler).

+

Conditional branching is also available: +scheduler.addConditionalBranches

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Scheduler(psychoJS)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +module:core.PsychoJS + + + +

the PsychoJS instance

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Event :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
NEXT + + +Symbol + + + +

Move onto the next task without rendering the scene first.

FLIP_REPEAT + + +Symbol + + + +

Render the scene and repeat the task.

FLIP_NEXT + + +Symbol + + + +

Render the scene and move onto the next task.

QUIT + + +Symbol + + + +

Quit the scheduler.

+ + + + + + +
+

Events.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Status :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
RUNNING + + +Symbol + + + +

The Scheduler is running.

STOPPED + + +Symbol + + + +

The Scheduler is stopped.

+ + + + + + +
+

Status.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

status

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the status of the scheduler.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

add(task, …args)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Schedule a new task.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
task + + +Scheduler~Task +| + +Scheduler + + + + + + + + + +

the task to be scheduled

args + + +* + + + + + + + + + + <repeatable>
+ +

arguments for that task

+ + + + + + + + + + + + + + + + + + + + + + + + +

addConditional(condition, thenScheduler, elseScheduler)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Schedule a series of task or another, based on a condition.

+

Note: the tasks are sub-schedulers.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
condition + + +Scheduler~Condition + + + +

the condition

thenScheduler + + +Scheduler + + + +

the Scheduler to be run if the condition is satisfied

elseScheduler + + +Scheduler + + + +

the Scheduler to be run if the condition is not satisfied

+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) start()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start this scheduler.

+

Note: tasks are run after each animation frame.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

stop()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop this scheduler.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Type Definitions

+ + + + + + +

Condition() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Condition evaluated when the task is run.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

Task(argsopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Task to be run by the scheduler.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
args + + +* + + + + + + <optional>
+ + + + + +

optional arguments

+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ServerManager.html b/docs/ServerManager.html new file mode 100644 index 00000000..68ff35ba --- /dev/null +++ b/docs/ServerManager.html @@ -0,0 +1,4671 @@ + + + + + + ServerManager - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

ServerManager

+ + + + + + + +
+ +
+ +

+ core + + ServerManager +

+ +

This manager handles all communications between the experiment running in the participant's browser and the pavlovia.org server, in an asynchronous manner.

+

It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results, logs, and audio recordings.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new ServerManager(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Event :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
RESOURCE + + +Symbol + + + +

Event type: resource event

DOWNLOADING_RESOURCES + + +Symbol + + + +

Event: resources have started to download

DOWNLOADING_RESOURCE + + +Symbol + + + +

Event: a specific resource download has started

RESOURCE_DOWNLOADED + + +Symbol + + + +

Event: a specific resource has been downloaded

DOWNLOAD_COMPLETED + + +Symbol + + + +

Event: resources have all downloaded

STATUS + + +Symbol + + + +

Event type: status event

+ + + + + + +
+

Server event

+

A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) ResourceStatus :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
ERROR + + +Symbol + + + +

There was an error during downloading, or the resource is in an unknown state.

REGISTERED + + +Symbol + + + +

The resource has been registered.

DOWNLOADING + + +Symbol + + + +

The resource is currently downloading.

DOWNLOADED + + +Symbol + + + +

The resource has been downloaded.

+ + + + + + +
+

Resource status

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Status :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
READY + + +Symbol + + + +

The manager is ready.

BUSY + + +Symbol + + + +

The manager is busy, e.g. it is downloaded resources.

ERROR + + +Symbol + + + +

The manager has encountered an error, e.g. it was unable to download a resource.

+ + + + + + +
+

Server status

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(readonly) ALL_RESOURCES :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Used to indicate to the ServerManager that all resources must be registered (and +subsequently downloaded)

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(async, protected) _downloadResources(resources)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Download the specified resources.

+

Note: we use the preloadjs library.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
resources + + +Set + + + +

a set of names of previously registered resources

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _listResources()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

List the resources available to the experiment.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _queryServerAPI(method, path, data, contentTypeopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Query the pavlovia server API.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
method + + + + + + + + + +

the HTTP method, i.e. GET, PUT, POST, or DELETE

path + + + + + + + + + +

the resource path, without the server address

data + + + + + + + + + +

the data to be sent

contentType + + +string + + + + + + <optional>
+ + + + + +
+ + "JSON" + +

the content type, either JSON or FORM

+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupPreloadQueue()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the preload.js queue, and the associated callbacks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) closeSession(isCompletedopt, syncopt) → {Promise.<ServerManager.CloseSessionPromise>|void}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Close the session for this experiment on the remote PsychoJS manager.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
isCompleted + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the experiment was completed

sync + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to communicate with the server in a synchronous manner

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.CloseSessionPromise> +| + +void + + +
+
+ + + + + + + + + + +

getConfiguration(configURL) → {Promise.<ServerManager.GetConfigurationPromise>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Read the configuration file for the experiment.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
configURL + + +string + + + +

the URL of the configuration file

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.GetConfigurationPromise> + + +
+
+ + + + + + + + + + +

getResource(name, errorIfNotDownloadedopt) → {Object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a resource.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +string + + + + + + + + + + + +

name of the requested resource

errorIfNotDownloaded + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to throw an exception if the +resource status is not DOWNLOADED

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if no resource with that name has previously been registered

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

value of the resource, or undefined if the resource has been registered +but not downloaded yet.

+
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + +

getResourceStatus(names) → {module:core.ServerManager.ResourceStatus}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the status of a single resource or the reduced status of an array of resources.

+

If an array of resources is given, getResourceStatus returns a single, reduced status +that is the status furthest away from DOWNLOADED, with the status ordered as follow: +ERROR (furthest from DOWNLOADED), REGISTERED, DOWNLOADING, and DOWNLOADED

+

For example, given three resources: +

    +
  • if at least one of the resource status is ERROR, the reduced status is ERROR
  • +
  • if at least one of the resource status is DOWNLOADING, the reduced status is DOWNLOADING
  • +
  • if the status of all three resources is REGISTERED, the reduced status is REGISTERED
  • +
  • if the status of all three resources is DOWNLOADED, the reduced status is DOWNLOADED
  • +
+

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
names + + +string +| + +Array.<string> + + + +

names of the resources whose statuses are requested

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

if at least one of the names is not that of a previously +registered resource

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

status of the resource if there is only one, or reduced status otherwise

+
+ + + +
+
+ Type +
+
+ +module:core.ServerManager.ResourceStatus + + +
+
+ + + + + + + + + + +

openSession() → {Promise.<ServerManager.OpenSessionPromise>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Open a session for this experiment on the remote PsychoJS manager.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.OpenSessionPromise> + + +
+
+ + + + + + + + + + +

(async) prepareResources(resourcesopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Prepare resources for the experiment: register them with the server manager and possibly +start downloading them right away.

+
    +
  • For an experiment running locally: the root directory for the specified resources is that of index.html + unless they are prepended with a protocol, such as http:// or https://.
  • +
  • For an experiment running on the server: if no resources are specified, all files in the resources directory + of the experiment are downloaded, otherwise we only download the specified resources. All resources are assumed + local to index.html unless they are prepended with a protocol.
  • +
  • If resources is null, then we do not download any resources
  • +
+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
resources + + +String +| + +Array.<({name: string, path: string, download: boolean}|String|Symbol)> + + + + + + <optional>
+ + + + + +
+ + [] + +

the list of resources or a single resource

+ + + + + + + + + + + + + + + + + + + + + + + + +

resetStatus() → {ServerManager.Status.READY}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the resource manager status to ServerManager.Status.READY.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the new status

+
+ + + +
+
+ Type +
+
+ +ServerManager.Status.READY + + +
+
+ + + + + + + + + + +

setStatus()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the resource manager status.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) uploadAudioVideo(@param) → {Promise.<ServerManager.UploadDataPromise>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Synchronously or asynchronously upload audio data to the pavlovia server.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
@param + + +Object + + + + + + + + + + + +

options

options.mediaBlob + + +Blob + + + + + + + + + + + +

the audio or video blob to be uploaded

options.tag + + +string + + + + + + + + + + + +

additional tag

options.waitForCompletion + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to wait for completion +before returning

options.showDialog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server

options.dialogMsg + + +string + + + + + + <optional>
+ + + + + +
+ + "Please wait a few moments while the data is uploading to the server" + +

default message informing the participant to wait for the data to be uploaded to the server

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.UploadDataPromise> + + +
+
+ + + + + + + + + + +

uploadData(key, value, syncopt) → {Promise.<ServerManager.UploadDataPromise>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Asynchronously upload experiment data to the pavlovia server.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
key + + +string + + + + + + + + + + + +

the data key (e.g. the name of .csv file)

value + + +string + + + + + + + + + + + +

the data value (e.g. a string containing the .csv header and records)

sync + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to communicate with the server in a synchronous manner

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.UploadDataPromise> + + +
+
+ + + + + + + + + + +

uploadLog(logs, compressedopt) → {Promise.<ServerManager.UploadDataPromise>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Asynchronously upload experiment logs to the pavlovia server.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
logs + + +string + + + + + + + + + + + +

the base64 encoded, compressed, formatted logs

compressed + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the logs are compressed

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response

+
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.UploadDataPromise> + + +
+
+ + + + + + + + + + +

waitForResources(resourcesopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Block the experiment until the specified resources have been downloaded.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
resources + + +Array.<{name: string, path: string}> + + + + + + <optional>
+ + + + + +
+ + [] + +

the list of resources

+ + + + + + + + + + + + + + + + + + + + + + + +

Type Definitions

+ + + +

CloseSessionPromise

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
origin + + +string + + + + + + + +

the calling method

context + + +string + + + + + + + +

the context

error + + +Object.<string, *> + + + + + + <optional>
+ + + +

an error message if we could not close the session (e.g. if it has not previously been opened)

+ + + + + + + + + + + + + + + +

GetConfigurationPromise

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
origin + + +string + + + + + + + +

the calling method

context + + +string + + + + + + + +

the context

config + + +Object.<string, *> + + + + + + <optional>
+ + + +

the configuration

error + + +Object.<string, *> + + + + + + <optional>
+ + + +

an error message if we could not read the configuration file

+ + + + + + + + + + + + + + + +

OpenSessionPromise

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
origin + + +string + + + + + + + +

the calling method

context + + +string + + + + + + + +

the context

token + + +string + + + + + + <optional>
+ + + +

the session token

error + + +Object.<string, *> + + + + + + <optional>
+ + + +

an error message if we could not open the session

+ + + + + + + + + + + + + + + +

UploadDataPromise

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
origin + + +string + + + + + + + +

the calling method

context + + +string + + + + + + + +

the context

error + + +Object.<string, *> + + + + + + <optional>
+ + + +

an error message if we could not upload the data

+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/ShapeStim.html b/docs/ShapeStim.html new file mode 100644 index 00000000..be13260f --- /dev/null +++ b/docs/ShapeStim.html @@ -0,0 +1,1780 @@ + + + + + + ShapeStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

ShapeStim

+ + + + + + + +
+ +
+ +

+ visual + + ShapeStim +

+ +

This class provides the basic functionality of shape stimuli.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new ShapeStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

lineWidth + + +number + + + + + + + + + + + +

the line width

lineColor + + +Color + + + + + + <optional>
+ + + + + +
+ + 'white' + +

the line color

fillColor + + +Color + + + + + + + + + + + +

the fill color

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

vertices + + +Array.<Array.<number>> + + + + + + <optional>
+ + + + + +
+ + [[-0.5, 0], [0, 0.5], [0.5, 0]] + +

the shape vertices

closeShape + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the shape is closed

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the shape

size + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the size

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

units + + +string + + + + + + + + + + + +

the units of the stimulus vertices, size and position

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth

interpolate + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the shape is interpolated

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) KnownShapes

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Known shapes.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getPixiPolygon() → {Object}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the PIXI polygon (in pixel units) corresponding to the vertices.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the PIXI polygon corresponding to this stimulus vertices.

+
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + +

(protected) _getVertices_px() → {Array.<Array.<number>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the vertices in pixel units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the vertices (in pixel units)

+
+ + + +
+
+ Type +
+
+ +Array.<Array.<number>> + + +
+
+ + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside the bounding box of the ShapeStim.

+

This is overridden in order to provide a finer inclusion test.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +string + + + +

the units

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the object is inside the bounding box of the ShapeStim

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

setVertices(vertices, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the vertices attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
vertices + + +Array.<Array.<number>> + + + + + + + + + + + +

the vertices

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Shelf.html b/docs/Shelf.html new file mode 100644 index 00000000..43857c58 --- /dev/null +++ b/docs/Shelf.html @@ -0,0 +1,7014 @@ + + + + + + Shelf - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Shelf

+ + + + + + + +
+ +
+ +

+ data + + Shelf +

+ +

Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the +server, and can be accessed and manipulated in a concurrent fashion.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Shelf(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Status :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
READY + + +Symbol + + + +

The shelf is ready.

BUSY + + +Symbol + + + +

The shelf is busy, e.g. storing or retrieving values.

ERROR + + +Symbol + + + +

The shelf has encountered an error.

+ + + + + + +
+

Shelf status

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Type :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
INTEGER + + +Symbol + + + +
TEXT + + +Symbol + + + +
DICTIONARY + + +Symbol + + + +
BOOLEAN + + +Symbol + + + +
LIST + + +Symbol + + + +
+ + + + + + +
+

Shelf record types.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

_checkAvailability(methodNameopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Check whether it is possible to run a given shelf command.

+

Since all Shelf methods call _checkAvailability, we also use it as a means to throttle those calls.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
methodName + + +string + + + + + + <optional>
+ + + + + +
+ + "" + +

name of the method requiring a check

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if it is not possible to run the given shelf command

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + + + + + + + + + +

_checkKey(key)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Check the validity of the key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +object + + + +

key whose validity is to be checked

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if the key is invalid

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + + + + + + + + + +

(async) _getValue(key, type, optionsopt) → {Promise.<any>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value associated with the given key.

+

This is a generic method, typically called from the Shelf helper methods, e.g. getBinaryValue.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
key + + +Array.<string> + + + + + + + + + +

key as an array of key components

type + + +Shelf.Type + + + + + + + + + +

the type of the record associated with the given key

options + + +Object + + + + + + <optional>
+ + + + + +

the options, e.g. the default value returned if no record with the +given key exists on the shelf

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is a record associated with the given key but it is not of +the given type

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value

+
+ + + +
+
+ Type +
+
+ +Promise.<any> + + +
+
+ + + + + + + + + + +

(async) _updateValue(key, type, update) → {Promise.<any>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the value associated with the given key.

+

This is a generic method, typically called from the Shelf helper methods, e.g. setBinaryValue.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

type + + +Shelf.Type + + + +

the type of the record associated with the given key

update + + +* + + + +

the desired update

+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record associated with the given key or if there is one +but it is not of the given type

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the updated value

+
+ + + +
+
+ Type +
+
+ +Promise.<any> + + +
+
+ + + + + + + + + + +

addIntegerValue(options) → {Promise.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add a delta to the value of a record of type INTEGER associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

delta + + +number + + + +

the delta, positive or negative, to add to the value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if delta is not an integer, or if there is no record with the given +key, or if there is a record but it is locked or it is not of type INTEGER

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<number> + + +
+
+ + + + + + + + + + +

appendListValue(options) → {Promise.<Array.<*>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Append an element, or a list of elements, to the value of a record of type LIST associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

elements + + +* + + + +

the element or list of elements to be appended

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<*>> + + +
+
+ + + + + + + + + + +

clearListValue(options) → {Promise.<Array.<*>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Empty the value of a record of type LIST associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new, empty value, i.e. []

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<*>> + + +
+
+ + + + + + + + + + +

(async) counterBalanceSelect(options) → {Promise.<{string, boolean}>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the name of a group, using a counterbalanced design.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

groups + + +Array.<string> + + + +

the names of the groups

groupSizes + + +Array.<number> + + + +

the size of the groups

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

an object with the name of the selected group and whether all groups +have been depleted

+
+ + + +
+
+ Type +
+
+ +Promise.<{string, boolean}> + + +
+
+ + + + + + + + + + +

flipBooleanValue(options) → {Promise.<boolean>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Flip the value of a record of type BOOLEAN associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or +if there is a record but it is not of type BOOLEAN

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new, flipped, value

+
+ + + +
+
+ Type +
+
+ +Promise.<boolean> + + +
+
+ + + + + + + + + + +

getBooleanValue(options) → {Promise.<boolean>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a record of type BOOLEAN associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

defaultValue + + +boolean + + + +

the default value returned if no record with the given key exists +on the shelf

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is a record associated with the given key +but it is not of type BOOLEAN

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value associated with the key

+
+ + + +
+
+ Type +
+
+ +Promise.<boolean> + + +
+
+ + + + + + + + + + +

(async) getDictionaryFieldNames(options) → {Promise.<Array.<string>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the names of the fields in the dictionary record associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type DICTIONARY

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the list of field names

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<string>> + + +
+
+ + + + + + + + + + +

(async) getDictionaryFieldValue(options) → {Promise.<*>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a given field in the dictionary record associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

fieldName + + +string + + + +

the name of the field

defaultValue + + +boolean + + + +

the default value returned if no record with the given key exists on +the shelf, or if is a record of type DICTIONARY with the given key but it has no such field

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, +or if there is a record but it is locked or it is not of type DICTIONARY

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value of that field

+
+ + + +
+
+ Type +
+
+ +Promise.<*> + + +
+
+ + + + + + + + + + +

getDictionaryValue(options) → {Promise.<Object.<string, *>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a record of type DICTIONARY associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

defaultValue + + +Object.<string, *> + + + +

the default value returned if no record with the given key +exists on the shelf

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, +or if there is a record but it is locked or it is not of type DICTIONARY

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value associated with the key

+
+ + + +
+
+ Type +
+
+ +Promise.<Object.<string, *>> + + +
+
+ + + + + + + + + + +

getIntegerValue(options) → {Promise.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a record of type INTEGER associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

defaultValue + + +number + + + +

the default value returned if no record with the given key +exists on the shelf

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, +or if there is a record but it is locked or it is not of type BOOLEAN

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value associated with the key

+
+ + + +
+
+ Type +
+
+ +Promise.<number> + + +
+
+ + + + + + + + + + +

getListValue(options) → {Promise.<Array.<*>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a record of type LIST associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

defaultValue + + +Array.<*> + + + +

the default value returned if no record with the given key exists on +the shelf

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value associated with the key

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<*>> + + +
+
+ + + + + + + + + + +

getTextValue(options) → {Promise.<string>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the value of a record of type TEXT associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

defaultValue + + +string + + + +

the default value returned if no record with the given key exists on +the shelf

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is a record associated with the given key but it is +not of type TEXT

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the value associated with the key

+
+ + + +
+
+ Type +
+
+ +Promise.<string> + + +
+
+ + + + + + + + + + +

incrementComponent(key, increment, callback) → {function}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Schedulable component that will block the experiment until the counter associated with the given key +has been incremented by the given amount.

+
+ + + + + + + + + +
Example
+ +
const flowScheduler = new Scheduler(psychoJS);
+var experimentCounter = '<>';
+flowScheduler.add(psychoJS.shelf.incrementComponent(['counter'], 1, (value) => experimentCounter = value));
+ + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
key + + + +
increment + + + + 1 + +
callback + + + +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

a component that can be scheduled

+
+ + + +
+
+ Type +
+
+ +function + + +
+
+ + + + + + + + + + +

popListValue(options) → {Promise.<*>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Pop an element, at the given index, from the value of a record of type LIST associated +with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
key + + +Array.<string> + + + + + + + + + + + +

key as an array of key components

index + + +number + + + + + + <optional>
+ + + + + +
+ + -1 + +

the index of the element to be popped

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the popped element

+
+ + + +
+
+ Type +
+
+ +Promise.<*> + + +
+
+ + + + + + + + + + +

setBooleanValue(options) → {Promise.<boolean>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the value of a record of type BOOLEAN associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

value + + +boolean + + + +

the new value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if value is not a boolean, or if there is no record with the given +key, or if there is a record but it is locked or it is not of type BOOLEAN

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<boolean> + + +
+
+ + + + + + + + + + +

(async) setDictionaryFieldValue(options) → {Promise.<Object.<string, *>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set a field in the dictionary record associated to the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

fieldName + + +string + + + +

the name of the field

fieldValue + + +* + + + +

the value of the field

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, +or if there is a record but it is locked or it is not of type DICTIONARY

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the updated dictionary

+
+ + + +
+
+ Type +
+
+ +Promise.<Object.<string, *>> + + +
+
+ + + + + + + + + + +

setDictionaryValue(options) → {Promise.<Object.<string, *>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the value of a record of type DICTIONARY associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

value + + +Object.<string, *> + + + +

the new value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if value is not an object, or or if there is no record +with the given key, or if there is a record but it is locked or it is not of type DICTIONARY

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<Object.<string, *>> + + +
+
+ + + + + + + + + + +

setIntegerValue(options) → {Promise.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the value of a record of type INTEGER associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

value + + +number + + + +

the new value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if value is not an integer, or or if there is no record +with the given key, or if there is a record but it is locked or it is not of type INTEGER

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<number> + + +
+
+ + + + + + + + + + +

setListValue(options) → {Promise.<Array.<*>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the value of a record of type LIST associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

value + + +Array.<*> + + + +

the new value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if value is not an array or if there is no record with the given key, +or if there is a record but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<*>> + + +
+
+ + + + + + + + + + +

setTextValue(options) → {Promise.<string>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the value of a record of type TEXT associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

value + + +string + + + +

the new value

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if value is not a string, or if there is a record associated +with the given key but it is not of type TEXT

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new value

+
+ + + +
+
+ Type +
+
+ +Promise.<string> + + +
+
+ + + + + + + + + + +

shuffleListValue(options) → {Promise.<Array.<*>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Shuffle the elements of the value of a record of type LIST associated with the given key.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Array.<string> + + + +

key as an array of key components

+ +
+ + + + + + + + + + + + + + +
Throws:
+ + + +
+
+
+

exception if there is no record with the given key, or if there is a record +but it is locked or it is not of type LIST

+
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+

the new, shuffled value

+
+ + + +
+
+ Type +
+
+ +Promise.<Array.<*>> + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Slider.html b/docs/Slider.html new file mode 100644 index 00000000..1cbf4cdb --- /dev/null +++ b/docs/Slider.html @@ -0,0 +1,7169 @@ + + + + + + Slider - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Slider

+ + + + + + + +
+ +
+ +

+ visual + + Slider +

+ +

Slider stimulus.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Slider(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
+ + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • check that parameters are valid, e.g. ticks are an array of numbers, etc.
  • + +
  • readOnly
  • + +
  • complete setters, for instance setTicks should change this._isCategorical
  • + +
  • flesh out the skin approach
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the slider

size + + +Array.<number> + + + + + + + + + + + +

the size of the slider, e.g. [1, 0.1] for an horizontal slider

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

units + + +string + + + + + + <optional>
+ + + + + +
+ + 'height' + +

the units of the Slider position, and font size

color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('LightGray') + +

the color of the slider

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast of the slider

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity of the slider

style + + +string + + + + + + <optional>
+ + + + + +
+ + [Slider.Style.RATING] + +

the slider style

ticks + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [1,2,3,4,5] + +

the array of ticks

labels + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [] + +

the array of labels

granularity + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the granularity

flip + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip the position of the marker, ticks, +and labels with respect to the central bar

readOnly + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the slider is read only

font + + +string + + + + + + <optional>
+ + + + + +
+ + 'Arial' + +

the text font

bold + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the font of the labels is bold

italic + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the font of the labels is italic

fontSize + + +number + + + + + + <optional>
+ + + + + +
+ +

the font size of the labels (in pixels), the default fontSize depends on the +Slider's units: 14 for 'pix', 0.03 otherwise

compact + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the slider is compact, i.e. whether all graphical +elements (e.g. labels) fit within its size

clipMask + + +PIXI.Graphics + + + + + + + + + + + +

the clip mask

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every +frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

dependentStims + + +Array.<core.MinimalStim> + + + + + + <optional>
+ + + + + +
+ + [] + +

the list of dependent stimuli, +which must be updated when this Slider is updated, e.g. a Form.

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Shape :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
DISC + + +Symbol + + + +
TRIANGLE + + +Symbol + + + +
LINE + + +Symbol + + + +
BOX + + +Symbol + + + +
+ + + + + + +
+

Shape of the marker and of the ticks.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

(static, readonly) Skin :any

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
MARKER_SIZE + + +any + + + +
STANDARD + + +any + + + +
WHITE_ON_BLACK + + +any + + + +
+ + + + + + +
+

Skin.

+
+ + + +
Type:
+
    +
  • + +any + + +
  • +
+ + + + + + + + +

(static, readonly) Style :Symbol

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
RATING + + +Symbol + + + +
TRIANGLE_MARKER + + +Symbol + + + +
SLIDER + + +Symbol + + + +
WHITE_ON_BLACK + + +Symbol + + + +
LABELS_45 + + +Symbol + + + +
RADIO + + +Symbol + + + +
+ + + + + + +
+

Styles.

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

borderColor

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Let borderColor alias lineColor to parallel PsychoPy

+
+ + + + + + + + + + +

fillColor

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Let fillColor alias markerColor to parallel PsychoPy

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _addEventListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add event listeners.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getBoundingBox_px() → {PIXI.Rectangle}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the bounding box in pixel coordinates

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the bounding box, in pixel coordinates

+
+ + + +
+
+ Type +
+
+ +PIXI.Rectangle + + +
+
+ + + + + + + + + + +

(protected) _getPosition_px() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the position of the slider, taking the compactness into account.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the position of the slider, in pixels
  • +
+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _getTextStyle()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the PIXI Text Style applied to the PIXI.Text labels.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _granularise(rating) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Calculate the rating once granularity has been taken into account.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rating + + +number + + + +

the input rating

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the new rating with granularity applied

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected) _handlePointerDown()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Handle pointerdown event.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _handlePointerMove()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Handle pointermove event.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _handlePointerUp()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Handle pointerup event.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _isHorizontal() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether the slider is horizontal.

+

The slider is horizontal is its x-axis size is larger than its y-axis size.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the slider is horizontal

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

(protected) _onChange(withPixiopt, withBoundingBoxopt) → {function}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Generate a callback that prepares updates to the stimulus. +This is typically called in the constructor of a stimulus, when attributes are added +with _addAttribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
withPixi + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the PIXI representation must +also be updated

withBoundingBox + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to immediately estimate +the bounding box

+ + + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +function + + +
+
+ + + + + + + + + + +

(protected) _posToRating(pos_px) → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert a [x,y] position, in pixel units, relative to the slider, into a rating.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pos_px + + +Array.<number> + + + +

the [x,y] position, in pixel units, relative to the slider.

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the corresponding rating.

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected) _ratingToPos(ratings) → {Array.<Array.<number>>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert an array of ratings into an array of [x,y] positions (in Slider units, with 0 at the center of the Slider)

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
ratings + + +Array.<number> + + + +

the array of ratings

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the positions corresponding to the ratings (in Slider units, +with 0 at the center of the Slider)

+
+ + + +
+
+ Type +
+
+ +Array.<Array.<number>> + + +
+
+ + + + + + + + + + +

(protected) _removeEventListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Remove event listeners.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _sanitizeAttributes()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Sanitize the slider attributes: check for attribute conflicts, missing values, etc.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupBar()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the central bar.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupLabels()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the labels.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupMarker()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the marker, and the associated mouse events.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupSlider()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the PIXI components of the slider (bar, ticks, labels, marker, etc.).

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupStyle()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Apply a particular style to the slider.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupTicks()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup the ticks.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether an object is inside the bounding box of the stimulus.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + +

the object

units + + +string + + + +

the units

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the object is inside the bounding box of the stimulus

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

draw()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Draw this stimulus on the next frame draw.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getRating() → {number|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the current value of the rating.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the rating or undefined if there is none

+
+ + + +
+
+ Type +
+
+ +number +| + +undefined + + +
+
+ + + + + + + + + + +

getRT() → {number|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the response time of the most recent change to the rating.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the response time (in second) or undefined if there is none

+
+ + + +
+
+ Type +
+
+ +number +| + +undefined + + +
+
+ + + + + + + + + + +

hide()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Hide this stimulus on the next frame draw.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

isMarkerDragging() → {boolean}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Query whether or not the marker is currently being dragged.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

whether or not the marker is being dragged

+
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + +

recordRating(rating, responseTimeopt, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the current rating.

+

Setting the rating does also change the visible position of the marker.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
rating + + +number + + + + + + + + + + + +

the rating

responseTime + + +number + + + + + + <optional>
+ + + + + +
+ +

the reaction time

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

refresh()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Force a refresh of the stimulus.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

release(logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Release the PIXI representation, if there is one.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

reset()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Reset the slider.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setAutoDraw(autoDraw, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the autoDraw attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
autoDraw + + +boolean + + + + + + + + + + + +

the new value

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setDepth(depth, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the depth attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
depth + + +Array.<number> + + + + + + + + + + + + 0 + +

order in which stimuli is rendered, kind of css's z-index with a negative sign.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setMarkerPos(displayedRating, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the markerPos attribute.

+

Setting markerPos changes the visible position of the marker to the specified rating +but does not change the actual rating returned by the slider.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
displayedRating + + +number + + + + + + + + + + + +

the displayed rating

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setOri(ori, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the orientation attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
ori + + +number + + + + + + + + + + + + 0 + +

the orientation in degree with 0 as the vertical position, positive values rotate clockwise.

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setPos(pos, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the position attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
pos + + +Array.<number> + + + + + + + + + + + +

position of the center of the stimulus, in stimulus units

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setRating(rating, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the rating attribute.

+

Setting the rating does not change the visible position of the marker.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
rating + + +number + + + + + + + + + + + +

the rating

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setReadOnly(readOnlyopt, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the readOnly attribute.

+

Read-only sliders are half-opaque and do not provide responses.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
readOnly + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the slider is read-only

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setSize(size, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the size attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
size + + +undefined +| + +null +| + +number +| + +Array.<number> + + + + + + + + + + + +

the stimulus size

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/SoundPlayer.html b/docs/SoundPlayer.html new file mode 100644 index 00000000..d39de67f --- /dev/null +++ b/docs/SoundPlayer.html @@ -0,0 +1,1079 @@ + + + + + + SoundPlayer - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

SoundPlayer

+ + + + + + + +
+ +
+ +

+ sound + + SoundPlayer +

+ +

SoundPlayer is an interface for the sound players, who are responsible for actually playing the sounds, i.e. the tracks or the tones.

+ + +
+ +
+ +
+ + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(abstract, static) accept(sound) → {Object|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether this player can play the given sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
sound + + +module:sound.Sound + + + +

the sound

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

an instance of the SoundPlayer that can play the sound, or undefined if none could be found

+
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + +

(abstract) getDuration()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the duration of the sound, in seconds.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) play(loopsopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
loops + + +number + + + + + + <optional>
+ + + + + +

how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) setDuration()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the duration of the sound, in seconds.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) setLoops(loops)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the number of loops.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loops + + +number + + + +

how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) setVolume(volume, muteopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the volume of the tone.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
volume + + +Integer + + + + + + + + + + + +

the volume of the tone

mute + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to mute the tone

+ + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) stop()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop playing the sound immediately.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/SpeechRecognition.html b/docs/SpeechRecognition.html new file mode 100644 index 00000000..41c3baf1 --- /dev/null +++ b/docs/SpeechRecognition.html @@ -0,0 +1,1436 @@ + + + + + + SpeechRecognition - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

SpeechRecognition

+ + + + + + + +
+ +
+ +

+ sound + + SpeechRecognition +

+ +

This manager handles the live transcription of speech into text.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new SpeechRecognition(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • deal with alternatives, interim results, and recognition errors
  • +
+
+ +
+ + + + + +
+

This manager handles the live transcription of speech into text.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

name + + +String + + + + + + + + + + + +

the name used when logging messages

bufferSize + + +number + + + + + + <optional>
+ + + + + +
+ + 10000 + +

the maximum size of the circular transcript buffer

continuous + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + true + +

whether to continuously recognise

lang + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + 'en-US' + +

the spoken language

interimResults + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + false + +

whether to make interim results available

maxAlternatives + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + 1 + +

the maximum number of recognition alternatives

tokens + + +Array.<String> + + + + + + <optional>
+ + + + + +
+ + [] + +

the tokens to be recognised. This is experimental technology, not available in all browser.

clock + + +Clock + + + + + + <optional>
+ + + + + +
+ +

an optional clock

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _onChange()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Callback for changes to the recognition settings.

+

Changes to the recognition settings require the speech recognition process +to be stopped and be re-started.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _prepareRecognition()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Prepare the speech recognition process.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

clearTranscripts()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clear all transcripts and resets the circular buffers.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getTranscripts(options) → {Array.<Transcript>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the list of transcripts still in the buffer, i.e. those that have not been +previously cleared by calls to getTranscripts with clear = true.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
transcriptList + + +Array.<string> + + + + + + <optional>
+ + + + + +
+ + [] + +

the list of transcripts texts to consider. If transcriptList is empty, we consider all transcripts.

clear + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to keep in the buffer the transcripts for a subsequent call to getTranscripts. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList

+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the list of transcripts still in the buffer

+
+ + + +
+
+ Type +
+
+ +Array.<Transcript> + + +
+
+ + + + + + + + + + +

start() → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start the speech recognition process.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the process actually starts

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + +

stop() → {Promise}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop the speech recognition process.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

promise fulfilled when the process actually stops

+
+ + + +
+
+ Type +
+
+ +Promise + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TextBox.html b/docs/TextBox.html new file mode 100644 index 00000000..f52597d1 --- /dev/null +++ b/docs/TextBox.html @@ -0,0 +1,4353 @@ + + + + + + TextBox - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

TextBox

+ + + + + + + +
+ +
+ +

+ visual + + TextBox +

+ + +
+ +
+ +
+ + + + + +

new TextBox(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

text + + +string + + + + + + <optional>
+ + + + + +
+ + "" + +

the text to be rendered

font + + +string + + + + + + <optional>
+ + + + + +
+ + "Arial" + +

the font family

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the text

color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('white') + +

color of the text

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth (i.e. the z order)

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the text size and position

ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + +

the orientation (in degrees)

letterHeight + + +number + + + + + + <optional>
+ + + + + +
+ + <default value> + +

the height of the text

bold + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the text is bold

italic + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the text is italic

anchor + + +string + + + + + + <optional>
+ + + + + +
+ + 'left' + +

horizontal alignment

multiline + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not a multiline element is used

autofocus + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not the first input should receive focus by default

flipHoriz + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip the text horizontally

flipVert + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip the text vertically

fillColor + + +Color + + + + + + <optional>
+ + + + + +
+ +

fill color of the text-box

languageStyle + + +String + + + + + + <optional>
+ + + + + +
+ + "LTR" + +

sets the direction property of the text inputs. Possible values ["LTR", "RTL", "Arabic"]. "Arabic" is added for consistency with PsychoPy

borderColor + + +Color + + + + + + <optional>
+ + + + + +
+ +

border color of the text-box

clipMask + + +PIXI.Graphics + + + + + + <optional>
+ + + + + +
+ + null + +

the clip mask

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

fitToContent + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to resize itself automaitcally to fit to the text content

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static, readonly) _defaultLetterHeightMap

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates units to default letter height.

+
+ + + + + + + + + + +

(protected, static, readonly) _defaultSizeMap

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates units to default sizes.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _addEventListeners()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add event listeners to text-box object. Method is called internally upon object construction.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getAnchor() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert the anchor attribute into numerical values.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the anchor, as an array of numbers in [0,1]
  • +
+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _getDefaultLetterHeight() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the default letter height given the stimulus' units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the letter height corresponding to this stimulus' units.
  • +
+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

(protected) _getTextInputOptions()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the TextInput options applied to the PIXI.TextInput.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • take size into account
  • +
+
+ +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

clear()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clears the current text value.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getText() → {string}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

For accessing the underlying input value.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the current text value of the underlying input element.
  • +
+
+ + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + +

reset()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Clears the current text value or sets it back to match the placeholder.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setAlignment(alignment, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the alignment attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
alignment + + +boolean + + + + + + + + + + + + center + +

alignment of the text

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setAnchor(anchor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the anchor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
anchor + + +boolean + + + + + + + + + + + + center + +

anchor of the textbox

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setBorderColor(borderColor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the borderColor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
borderColor + + +Color + + + + + + + + + + + +

border color of the text box

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setColor(color, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the color attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
color + + +boolean + + + + + + + + + + + +

color of the text

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFillColor(fillColor, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the fillColor attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fillColor + + +boolean + + + + + + + + + + + +

fill color of the text box

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFitToContent(fitToContent, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the fitToContent attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fitToContent + + +boolean + + + + + + + + + + + +

whether or not to autoresize textbox to fit to text content

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setFont(font, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the font for textbox.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
font + + +string + + + + + + + + + + + + Arial + +

the font family

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLanguageStyle(languageStyle, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the languageStyle attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
languageStyle + + +String + + + + + + + + + + + + LTR + +

text direction in textbox, accepts values ["LTR", "RTL", "Arabic"]

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLetterHeight(fontSizeopt, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set letterHeight (font size) for textbox.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fontSize + + +string + + + + + + <optional>
+ + + + + +
+ + <default value> + +

the size of the font

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setSize(size, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the size attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
size + + +boolean + + + + + + + + + + + +

whether or not to wrap the text at the given width

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ + + + + + + + + + + + + + + + + + + + + + + + +

setText(text)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

For tweaking the underlying input value.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
text + + +string + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TextStim.html b/docs/TextStim.html new file mode 100644 index 00000000..215fc66e --- /dev/null +++ b/docs/TextStim.html @@ -0,0 +1,2296 @@ + + + + + + TextStim - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

TextStim

+ + + + + + + +
+ +
+ +

+ visual + + TextStim +

+ +

TextStim handles text stimuli.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new TextStim(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • vertical alignment, and orientation are currently NOT implemented
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + +

the name used when logging messages from this stimulus

win + + +module:core.Window + + + + + + + + + + + +

the associated Window

text + + +string + + + + + + <optional>
+ + + + + +
+ + "Hello World" + +

the text to be rendered

font + + +string + + + + + + <optional>
+ + + + + +
+ + "Arial" + +

the font family

pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + +

the position of the center of the text

color + + +Color + + + + + + <optional>
+ + + + + +
+ + 'white' + +

the background color

opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the opacity

depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

the depth (i.e. the z order)

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

the contrast

units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + +

the units of the text size and position

ori + + +number + + + + + + + + + + + +

the orientation (in degrees)

height + + +number + + + + + + <optional>
+ + + + + +
+ + 0.1 + +

the height of the text

bold + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the text is bold

italic + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the text is italic

alignHoriz + + +string + + + + + + <optional>
+ + + + + +
+ + 'center' + +

horizontal alignment

alignVert + + +string + + + + + + <optional>
+ + + + + +
+ + 'center' + +

vertical alignment

wrapWidth + + +boolean + + + + + + + + + + + +

whether or not to wrap the text horizontally

flipHoriz + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip the text horizontally

flipVert + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to flip the text vertically

clipMask + + +PIXI.Graphics + + + + + + <optional>
+ + + + + +
+ + null + +

the clip mask

autoDraw + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not the stimulus should be automatically drawn on every frame flip

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • VisualStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(protected, static, readonly) _defaultLetterHeightMap

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates units to default letter height.

+
+ + + + + + + + + + +

(protected, static, readonly) _defaultWrapWidthMap

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This map associates units to default wrap width.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(protected) _estimateBoundingBox()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the bounding box.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getAnchor() → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Convert the alignment attributes into an anchor.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the anchor
  • +
+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

(protected) _getDefaultLetterHeight()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the default letter height given the stimulus' units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getDefaultWrapWidth()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the default wrap width given the stimulus' units.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _getTextStyle()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the PIXI Text Style applied to the PIXI.Text

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update the stimulus, if necessary.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) getBoundingBox(tightopt) → {Array.<number>}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the bounding gox.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
tight + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to fit as closely as possible to the text

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+
    +
  • the bounding box, in the units of this TextStim
  • +
+
+ + + +
+
+ Type +
+
+ +Array.<number> + + +
+
+ + + + + + + + + + +

getTextMetrics()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the metrics estimated for the text and style.

+

Note: getTextMetrics does not require the PIXI representation of the stimulus +to be instantiated, unlike getSize().

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

setColor(color, logopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the color attribute.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
color + + +undefined +| + +null +| + +number + + + + + + + + + + + +

the color

log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether of not to log

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TonePlayer.html b/docs/TonePlayer.html new file mode 100644 index 00000000..6a14583b --- /dev/null +++ b/docs/TonePlayer.html @@ -0,0 +1,1657 @@ + + + + + + TonePlayer - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

TonePlayer

+ + + + + + + +
+ +
+ +

+ sound + + TonePlayer +

+ +

This class handles the playing of tones.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new TonePlayer(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

This class handles the playing of tones.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

duration_s + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + +

duration of the tone (in seconds). If duration_s == -1, the sound will play indefinitely.

note + + +string +| + +number + + + + + + <optional>
+ + + + + +
+ + 'C4' + +

note (if string) or frequency (if number)

volume + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

volume of the tone (must be between 0 and 1.0)

loops + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped.

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + +

Members

+ + + +

(static) SoundLibrary :Object

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
Type:
+
    +
  • + +Object + + +
  • +
+ + + + + + + + + + +

Methods

+ + + + + + +

(static) accept(sound) → {Object|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

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.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
sound + + +module:sound.Sound + + + +

the sound

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

an instance of TonePlayer that can play the given sound or undefined otherwise

+
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + +

(protected) _initSoundLibrary()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Initialise the sound library.

+

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

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getDuration() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the duration of the sound.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the duration of the sound, in seconds

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

play(loopsopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
loops + + +boolean + + + + + + <optional>
+ + + + + +

how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

setDuration(duration_s)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the duration of the tone.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
duration_s + + +number + + + +

the duration of the tone (in seconds) If duration_s == -1, the sound will play indefinitely.

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLoops(loops)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the number of loops.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loops + + +number + + + +

how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

setVolume(volume, muteopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the volume of the tone.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
volume + + +Integer + + + + + + + + + + + +

the volume of the tone

mute + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to mute the tone

+ + + + + + + + + + + + + + + + + + + + + + + + +

stop()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop playing the sound immediately.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TrackPlayer.html b/docs/TrackPlayer.html new file mode 100644 index 00000000..3088bf47 --- /dev/null +++ b/docs/TrackPlayer.html @@ -0,0 +1,1682 @@ + + + + + + TrackPlayer - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

TrackPlayer

+ + + + + + + +
+ +
+ +

+ sound + + TrackPlayer +

+ +

This class handles the playback of sound tracks.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new TrackPlayer(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • stopTime is currently not implemented (tracks will play from startTime to finish)
  • + +
  • stereo is currently not implemented
  • +
+
+ +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

howl + + +Object + + + + + + + + + + + +

the sound object (see https://howlerjs.com/)

startTime + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

start of playback (in seconds)

stopTime + + +number + + + + + + <optional>
+ + + + + +
+ + -1 + +

end of playback (in seconds)

stereo + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to play the sound or track in stereo

volume + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + +

volume of the sound (must be between 0 and 1.0)

loops + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + +

how many times to repeat the track or tone after it has played

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + + + + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(static) accept(sound) → {Object|undefined}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Determine whether this player can play the given sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
sound + + +module:sound.Sound + + + +

the sound, which should be the name of an audio resource +file

+ + + + + + + + + + + + + + + + +
Returns:
+ + +
+

an instance of TrackPlayer that can play the given track or undefined otherwise

+
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + +

getDuration() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Get the duration of the sound, in seconds.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

the duration of the track, in seconds

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

play(loops, fadeDurationopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Start playing the sound.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
loops + + +number + + + + + + + + + + + +

how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.

fadeDuration + + +number + + + + + + <optional>
+ + + + + +
+ + 17 + +

how long should the fading in last in ms

+ + + + + + + + + + + + + + + + + + + + + + + + +

setDuration(duration_s)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the duration of the track.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
duration_s + + +number + + + +

the duration of the track in seconds

+ + + + + + + + + + + + + + + + + + + + + + + + +

setLoops(loops)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the number of loops.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loops + + +number + + + +

how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.

+ + + + + + + + + + + + + + + + + + + + + + + + +

setVolume(volume, muteopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the volume of the tone.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
volume + + +Integer + + + + + + + + + + + +

the volume of the track (must be between 0 and 1.0)

mute + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to mute the track

+ + + + + + + + + + + + + + + + + + + + + + + + +

stop(fadeDurationopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + +
Overrides:
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Stop playing the sound immediately.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fadeDuration + + +number + + + + + + <optional>
+ + + + + +
+ + 17 + +

how long should the fading out last in ms

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Transcript.html b/docs/Transcript.html new file mode 100644 index 00000000..c97a1b63 --- /dev/null +++ b/docs/Transcript.html @@ -0,0 +1,323 @@ + + + + + + Transcript - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Transcript

+ + + + + + + +
+ +
+ +

+ Transcript +

+ +

Transcript.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Transcript(transcriber, text, confidence)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Object holding a transcription result.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefaultDescription
transcriber + + +SpeechRecognition + + + + + +

the transcriber

text + + +string + + + + + +

the transcript

confidence + + +number + + + + + + 0 + +

confidence in the transcript

+ + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +

Classes

+ +
+
Transcript
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Window.html b/docs/Window.html new file mode 100644 index 00000000..3cedd7b8 --- /dev/null +++ b/docs/Window.html @@ -0,0 +1,2327 @@ + + + + + + Window - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Window

+ + + + + + + +
+ +
+ +

+ core + + Window +

+ +

Window displays the various stimuli of the experiment.

+

It sets up a PIXI renderer, which we use to render the experiment stimuli.

+ + +
+ +
+ +
+ + + + +

Constructor

+ + +

new Window(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + +

the PsychoJS instance

name + + +string + + + + + + <optional>
+ + + + + +
+ +

the name of the window

fullscr + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to go fullscreen

color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('black') + +

the background color of the window

gamma + + +number + + + + + + <optional>
+ + + + + +
+ + 1 + +

sets the divisor for gamma correction. In other words gamma correction is calculated as pow(rgb, 1/gamma)

contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1 + +

sets the contrast value

units + + +string + + + + + + <optional>
+ + + + + +
+ + 'pix' + +

the units of the window

waitBlanking + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + +

whether or not to wait for all rendering operations to be done +before flipping

autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + +

whether or not to log

+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + +

Extends

+ + + + +
    +
  • PsychObject
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + +

(protected, static) _resizePixiRenderer(pjsWindow, event)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Adjust the size of the renderer and the position of the root container +in response to a change in the browser's size.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pjsWindow + + +module:core.Window + + + +

the PsychoJS Window

event + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _fullRefresh()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Force an update of all stimuli in this window's drawlist.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _refresh()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Recompute this window's draw list and _container children for the next animation frame.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _setupPixi()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setup PIXI.

+

A new renderer is created and a container is added to it. The renderer's touch and mouse events +are handled by the EventManager.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _updateIfNeeded()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Update this window, if need be.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(protected) _writeLogOnFlip()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Send all logged messages to the Logger.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

addPixiObject()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add PIXI.DisplayObject to the container displayed on the scene (window)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

adjustScreenSize()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Take the browser full screen if possible.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

callOnFlip(flipCallback, …flipCallbackArgs)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Add a callback function that will run after the next screen flip, i.e. immediately after the next rendering of the +Window.

+

This is typically used to reset a timer or clock.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
flipCallback + + +module:core.Window~OnFlipCallback + + + + + + + + + +

callback function.

flipCallbackArgs + + +* + + + + + + + + + + <repeatable>
+ +

arguments for the callback function.

+ + + + + + + + + + + + + + + + + + + + + + + + +

close()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Close the window.

+

Note: this actually only removes the canvas used to render the experiment stimuli.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

closeFullScreen()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Take the browser back from full screen if needed.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

getActualFrameRate() → {number}

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Estimate the frame rate.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Returns:
+ + +
+

rAF based delta time based approximation, 60.0 by default

+
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + +

logOnFlip(options, levelopt, objopt)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Log a message.

+

Note: the message will be time-stamped at the next call to requestAnimationFrame.

+
+ + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
options + + +Object + + + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
msg + + +String + + + +

the message to be logged

+ +
level + + +module:util.Logger.ServerLevel + + + + + + <optional>
+ + + + + +
+ + module:util.Logger.ServerLevel.EXP + +

the log level

obj + + +Object + + + + + + <optional>
+ + + + + +
+ +

the object associated with the message

+ + + + + + + + + + + + + + + + + + + + + + + + +

removePixiObject()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Remove PIXI.DisplayObject from the container displayed on the scene (window)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

render()

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Render the stimuli onto the canvas.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/core_EventManager.js.html b/docs/core_EventManager.js.html index 4d915f91..a176dfc2 100644 --- a/docs/core_EventManager.js.html +++ b/docs/core_EventManager.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/EventManager.js - - - + core/EventManager.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + -

Source: core/EventManager.js

+ + +
+ +

core/EventManager.js

+ @@ -30,27 +54,24 @@

Source: core/EventManager.js

* Manager handling the keyboard and mouse/touch events. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {MonotonicClock, Clock} from '../util/Clock'; -import {PsychoJS} from './PsychoJS'; - +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychoJS } from "./PsychoJS.js"; /** - * @class * <p>This manager handles all participant interactions with the experiment, i.e. keyboard, mouse and touch events.</p> - * - * @name module:core.EventManager - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance */ export class EventManager { - + /** + * @memberof module:core + * @param {Object} psychoJS + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { this._psychoJS = psychoJS; @@ -75,31 +96,27 @@

Source: core/EventManager.js

pressed: [0, 0, 0], clocks: [new Clock(), new Clock(), new Clock()], // time elapsed from last reset of the button.Clocks: - times: [0.0, 0.0, 0.0] + times: [0.0, 0.0, 0.0], }, // clock reset when mouse is moved: - moveClock: new Clock() + moveClock: new Clock(), }; } - /** * Get the list of keys pressed by the participant. * * <p>Note: The w3c [key-event viewer]{@link https://w3c.github.io/uievents/tools/key-event-viewer.html} can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.</p> * - * @name module:core.EventManager#getKeys - * @function - * @public * @param {Object} options * @param {string[]} [options.keyList= null] - keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely. * @param {boolean} [options.timeStamped= false] - If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time). * @return {string[]} the list of keys that were pressed. */ getKeys({ - keyList = null, - timeStamped = false - } = {}) + keyList = null, + timeStamped = false, + } = {}) { if (keyList != null) { @@ -151,7 +168,6 @@

Source: core/EventManager.js

return keys; } - /** * @typedef EventManager.ButtonInfo * @property {Array.number} pressed - the status of each mouse button [left, center, right]: 1 for pressed, 0 for released @@ -169,9 +185,6 @@

Source: core/EventManager.js

/** * Get the mouse info. * - * @name module:core.EventManager#getMouseInfo - * @function - * @public * @return {EventManager.MouseInfo} the mouse info. */ getMouseInfo() @@ -179,14 +192,9 @@

Source: core/EventManager.js

return this._mouseInfo; } - /** * Clear all events from the event buffer. * - * @name module:core.EventManager#clearEvents - * @function - * @public - * * @todo handle the attribs argument */ clearEvents(attribs) @@ -194,68 +202,44 @@

Source: core/EventManager.js

this.clearKeys(); } - /** * Clear all keys from the key buffer. - * - * @name module:core.EventManager#clearKeys - * @function - * @public */ clearKeys() { this._keyBuffer = []; } - /** * Start the move clock. * - * @name module:core.EventManager#startMoveClock - * @function - * @public - * * @todo not implemented */ startMoveClock() { } - /** * Stop the move clock. * - * @name module:core.EventManager#stopMoveClock - * @function - * @public - * * @todo not implemented */ stopMoveClock() { } - /** * Reset the move clock. * - * @name module:core.EventManager#resetMoveClock - * @function - * @public - * * @todo not implemented */ resetMoveClock() { } - /** * Add various mouse listeners to the Pixi renderer of the {@link Window}. * - * @name module:core.EventManager#addMouseListeners - * @function - * @public * @param {PIXI.Renderer} renderer - The Pixi renderer */ addMouseListeners(renderer) @@ -274,7 +258,6 @@

Source: core/EventManager.js

this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("touchstart", (event) => { event.preventDefault(); @@ -289,7 +272,6 @@

Source: core/EventManager.js

this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("pointerup", (event) => { event.preventDefault(); @@ -298,9 +280,20 @@

Source: core/EventManager.js

self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime(); self._mouseInfo.pos = [event.offsetX, event.offsetY]; - this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); + this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button up, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); + renderer.view.addEventListener("pointerout", (event) => + { + event.preventDefault(); + + // if the pointer leaves the canvas: cancel all buttons + self._mouseInfo.buttons.pressed = [0, 0, 0]; + self._mouseInfo.buttons.times = [0.0, 0.0, 0.0]; + self._mouseInfo.pos = [event.offsetX, event.offsetY]; + + this._psychoJS.experimentLogger.data("Mouse: out, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); + }, false); renderer.view.addEventListener("touchend", (event) => { @@ -313,10 +306,9 @@

Source: core/EventManager.js

const touches = event.changedTouches; self._mouseInfo.pos = [touches[0].pageX, touches[0].pageY]; - this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); + this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button up, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - renderer.view.addEventListener("pointermove", (event) => { event.preventDefault(); @@ -325,7 +317,6 @@

Source: core/EventManager.js

self._mouseInfo.pos = [event.offsetX, event.offsetY]; }, false); - renderer.view.addEventListener("touchmove", (event) => { event.preventDefault(); @@ -337,25 +328,20 @@

Source: core/EventManager.js

self._mouseInfo.pos = [touches[0].pageX, touches[0].pageY]; }, false); - // (*) wheel - renderer.view.addEventListener("wheel", event => + renderer.view.addEventListener("wheel", (event) => { self._mouseInfo.wheelRel[0] += event.deltaX; self._mouseInfo.wheelRel[1] += event.deltaY; this._psychoJS.experimentLogger.data("Mouse: wheel shift=(" + event.deltaX + "," + event.deltaY + "), pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")"); }, false); - } - /** * Add key listeners to the document. * - * @name module:core.EventManager#_addKeyListeners - * @function - * @private + * @protected */ _addKeyListeners() { @@ -364,14 +350,14 @@

Source: core/EventManager.js

// add a keydown listener // note: IE11 is not happy with document.addEventListener window.addEventListener("keydown", (event) => -// document.addEventListener("keydown", (event) => + // document.addEventListener("keydown", (event) => { const timestamp = MonotonicClock.getReferenceTime(); let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } @@ -380,24 +366,19 @@

Source: core/EventManager.js

code, key: event.key, keyCode: event.keyCode, - timestamp + timestamp, }); - self._psychoJS.logger.trace('keydown: ', event.key); - self._psychoJS.experimentLogger.data('Keydown: ' + event.key); + self._psychoJS.logger.trace("keydown: ", event.key); + self._psychoJS.experimentLogger.data("Keydown: " + event.key); event.stopPropagation(); }); - } - /** * Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values. * <p>This allows key lists that work in the builder environment to work in psychoJS web experiments.</p> * - * @name module:core.EventManager#pyglet2w3c - * @function - * @public * @param {Array.string} pygletKeyList - the array of pyglet key names * @return {Array.string} the w3c keyList */ @@ -406,7 +387,7 @@

Source: core/EventManager.js

let w3cKeyList = []; for (let i = 0; i < pygletKeyList.length; i++) { - if (typeof EventManager._pygletMap[pygletKeyList[i]] === 'undefined') + if (typeof EventManager._pygletMap[pygletKeyList[i]] === "undefined") { w3cKeyList.push(pygletKeyList[i]); } @@ -419,14 +400,9 @@

Source: core/EventManager.js

return w3cKeyList; } - /** * Convert a W3C Key Code into a pyglet key. * - * @name module:core.EventManager#w3c2pyglet - * @function - * @public - * @static * @param {string} code - W3C Key Code * @returns {string} corresponding pyglet key */ @@ -438,19 +414,14 @@

Source: core/EventManager.js

} else { - return 'N/A'; + return "N/A"; } } - /** * Convert a keycode to a W3C UI Event code. * <p>This is for legacy browsers.</p> * - * @name module:core.EventManager#keycode2w3c - * @function - * @public - * @static * @param {number} keycode - the keycode * @returns {string} corresponding W3C UI Event code */ @@ -460,7 +431,6 @@

Source: core/EventManager.js

} } - /** * <p>This map provides support for browsers that have not yet * adopted the W3C KeyboardEvent.code standard for detecting key presses. @@ -469,9 +439,8 @@

Source: core/EventManager.js

* <p>Unfortunately, it is not very fine-grained: for instance, there is no difference between Alt Left and Alt * Right, or between Enter and Numpad Enter. Use at your own risk (or upgrade your browser...).</p> * - * @name module:core.EventManager#_keycodeMap * @readonly - * @private + * @protected * @type {Object.<number,String>} */ EventManager._keycodeMap = { @@ -550,17 +519,15 @@

Source: core/EventManager.js

39: "ArrowRight", 40: "ArrowDown", 27: "Escape", - 32: "Space" + 32: "Space", }; - /** * This map associates pyglet key names to the corresponding W3C KeyboardEvent codes values. * <p>More information can be found [here]{@link https://www.w3.org/TR/uievents-code}</p> * - * @name module:core.EventManager#_pygletMap * @readonly - * @private + * @protected * @type {Object.<String,String>} */ EventManager._pygletMap = { @@ -653,26 +620,21 @@

Source: core/EventManager.js

"num_multiply": "NumpadMultiply", "num_divide": "NumpadDivide", "num_equal": "NumpadEqual", - "num_numlock": "NumpadNumlock" + "num_numlock": "NumpadNumlock", }; - /** * <p>This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names. * - * @name module:core.EventManager#_reversePygletMap * @readonly - * @private + * @protected * @type {Object.<String,String>} */ EventManager._reversePygletMap = {}; - /** * Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard) * - * @name module:core.BuilderKeyResponse - * @class * @param {Object} options * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance */ @@ -684,8 +646,8 @@

Source: core/EventManager.js

this.status = PsychoJS.Status.NOT_STARTED; this.keys = []; // the key(s) pressed - this.corr = 0; // was the resp correct this trial? (0=no, 1=yes) - this.rt = []; // response time(s) + this.corr = 0; // was the resp correct this trial? (0=no, 1=yes) + this.rt = []; // response time(s) this.clock = new Clock(); // we'll use this to measure the rt } } @@ -696,19 +658,23 @@

Source: core/EventManager.js

+ +
- -
- - + + + + + + + + diff --git a/docs/core_GUI.js.html b/docs/core_GUI.js.html index 3a3d2b17..98e5325f 100644 --- a/docs/core_GUI.js.html +++ b/docs/core_GUI.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/GUI.js - - - + core/GUI.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/GUI.js

+ + + + +
+ +

core/GUI.js

+ @@ -31,36 +55,49 @@

Source: core/GUI.js

* * @author Alain Pitiot * @author Sijia Zhao - fine-grained resource loading - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2021.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import * as Tone from 'tone'; -import {PsychoJS} from './PsychoJS'; -import {ServerManager} from './ServerManager'; -import {Scheduler} from '../util/Scheduler'; -import {Clock} from '../util/Clock'; -import {ExperimentHandler} from '../data/ExperimentHandler'; -import * as util from '../util/Util'; - +import * as Tone from "tone"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { Clock } from "../util/Clock.js"; +import { Scheduler } from "../util/Scheduler.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; +import { ServerManager } from "./ServerManager.js"; +import A11yDialog from "a11y-dialog"; /** - * @class - * Graphic User Interface - * - * @name module:core.GUI - * @class - * @param {module:core.PsychoJS} psychoJS the PsychoJS instance + * <p>GUI manages the various pop-up dialog boxes that guide the participant, throughout the + * lifecycle of the experiment, e.g. at the start while the resources are downloading, or at the + * end when the data is uploading to the server</p> */ export class GUI { + /** + * Default settings for GUI. + * + * @type {Object} + */ + static DEFAULT_SETTINGS = { + DlgFromDict: { + // The dialog box shows an OK button. The button becomes enable when all registered resources + // have been downloaded. Participants must click on the OK button to move on with the experiment. + requireParticipantClick: true + } + }; get dialogComponent() { return this._dialogComponent; } + /** + * @memberof module:core + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { this._psychoJS = psychoJS; @@ -70,11 +107,8 @@

Source: core/GUI.js

{ this._onResourceEvents(signal); }); - - this._dialogScalingFactor = 0; } - /** * <p>Create a dialog box that (a) enables the participant to set some * experimental values (e.g. the session name), (b) shows progress of resource @@ -89,38 +123,39 @@

Source: core/GUI.js

* <p>If the participant cancels (by pressing Cancel or by closing the dialog box), then * the dictionary remains unchanged.</p> * - * @name module:core.GUI#DlgFromDict - * @function - * @public * @param {Object} options * @param {String} [options.logoUrl] - Url of the experiment logo * @param {String} [options.text] - information text * @param {Object} options.dictionary - associative array of values for the participant to set * @param {String} options.title - name of the project + * @param {boolean} [options.requireParticipantClick=true] - whether the participant must click on the OK + * button, when it becomes enabled, to move on with the experiment */ DlgFromDict({ - logoUrl, - text, - dictionary, - title - }) + logoUrl, + text, + dictionary, + title, + requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick + }) { // get info from URL: const infoFromUrl = util.getUrlParameters(); - this._progressMsg = '&nbsp;'; this._progressBarMax = 0; this._allResourcesDownloaded = false; this._requiredKeys = []; this._setRequiredKeys = new Map(); + this._progressMessage = "&nbsp;"; + this._requireParticipantClick = requireParticipantClick; + this._dictionary = dictionary; - - // prepare PsychoJS component: + // prepare a PsychoJS component: this._dialogComponent = {}; this._dialogComponent.status = PsychoJS.Status.NOT_STARTED; const dialogClock = new Clock(); - const self = this; + const self = this; return () => { const t = dialogClock.getTime(); @@ -132,219 +167,141 @@

Source: core/GUI.js

// if the experiment is licensed, and running on the license rather than on credit, // we use the license logo: - if (self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - typeof self._psychoJS.config.experiment.license !== 'undefined' && - self._psychoJS.config.experiment.runMode === 'LICENSE' && - typeof self._psychoJS.config.experiment.license.institutionLogo !== 'undefined') + if (self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && typeof self._psychoJS.config.experiment.license !== "undefined" + && self._psychoJS.config.experiment.runMode === "LICENSE" + && typeof self._psychoJS.config.experiment.license.institutionLogo !== "undefined") { logoUrl = self._psychoJS.config.experiment.license.institutionLogo; } - // prepare jquery UI dialog box: - let htmlCode = - '<div id="expDialog" title="' + title + '">'; + // prepare the markup for the a11y-dialog: + let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>"; + markup += "<div class='dialog-overlay'></div>"; + // markup += "<div class='dialog-overlay' data-a11y-dialog-hide></div>"; + markup += "<div class='dialog-content'>"; - // uncomment for older version of the library: - // htmlCode += '<p style="font-size: 0.8em; padding: 0.5em; margin-bottom: 0.5em; color: #FFAA00; border: 1px solid #FFAA00;">&#9888; This experiment uses a deprecated version of the PsychoJS library. Consider updating to a newer version (e.g. by updating PsychoPy and re-exporting the experiment).</p>'+ + // alert title and close button: + markup += `<div id='experiment-dialog-title' class='dialog-title'><p>${title}</p><button id='dialogClose' class='dialog-close' data-a11y-dialog-hide aria-label='Cancel Experiment'>&times;</button></div>`; - // logo: - if (typeof logoUrl === 'string') + // logo, if need be: + if (typeof logoUrl === "string") { - htmlCode += '<img id="dialog-logo" class="logo" alt="logo" src="' + logoUrl + '">'; + markup += '<img id="dialog-logo" class="logo" alt="logo" src="' + logoUrl + '">'; } - // information text: - if (typeof text === 'string' && text.length > 0) + // add a combobox or text areas for each entry in the dictionary: + Object.keys(dictionary).forEach((key, keyIdx) => { - htmlCode += '<p>' + text + '</p>'; - } + const value = dictionary[key]; + const keyId = "form-input-" + keyIdx; + // only create an input if the key is not in the URL: + let inUrl = false; + const cleanedDictKey = key.trim().toLowerCase(); + infoFromUrl.forEach((urlValue, urlKey) => + { + const cleanedUrlKey = urlKey.trim().toLowerCase(); + if (cleanedUrlKey === cleanedDictKey) + { + inUrl = true; + // break; + } + }); - // add a combobox or text areas for each entry in the dictionary: - - // These may include Symbols as opposed to when using a for...in loop, - // but only strings are allowed in PsychoPy - Object.keys(dictionary).forEach((key, keyIdx) => + if (!inUrl) { - const value = dictionary[key]; - const keyId = 'form-input-' + keyIdx; + markup += `<label for='${keyId}'> ${key} </label>`; - // only create an input if the key is not in the URL: - let inUrl = false; - const cleanedDictKey = key.trim().toLowerCase(); - infoFromUrl.forEach((urlValue, urlKey) => - { - const cleanedUrlKey = urlKey.trim().toLowerCase(); - if (cleanedUrlKey === cleanedDictKey) - { - inUrl = true; - // break; - } - }); - - if (!inUrl) + // if the field is required: + if (key.slice(-1) === "*") { - htmlCode += '<label for="' + keyId + '">' + key + '</label>'; + self._requiredKeys.push(keyId); + } - // if the field is required: - if (key.slice(-1) === '*') + // if value is an array, we create a select drop-down menu: + if (Array.isArray(value)) + { + markup += `<select name='${key}' id='${keyId}' class='text'>`; + + // if the field is required, we add an empty option and select it: + if (key.slice(-1) === "*") { - self._requiredKeys.push(keyId); + markup += "<option disabled selected>...</option>"; } - // if value is an array, we create a select drop-down menu: - if (Array.isArray(value)) + for (const option of value) { - htmlCode += '<select name="' + key + '" id="' + keyId + '" class="text ui-widget-content' + - ' ui-corner-all">'; - - // if the field is required, we add an empty option and select it: - if (key.slice(-1) === '*') - { - htmlCode += '<option disabled selected>...</option>'; - } - - for (const option of value) - { - htmlCode += '<option>' + option + '</option>'; - } - - htmlCode += '</select>'; - jQuery('#' + keyId).selectmenu({classes: {}}); + markup += `<option> ${option} </option>`; } + markup += "</select>"; + } // otherwise we use a single string input: - else /*if (typeof value === 'string')*/ - { - htmlCode += '<input type="text" name="' + key + '" id="' + keyId; - htmlCode += '" value="' + value + '" class="text ui-widget-content ui-corner-all">'; - } + //if (typeof value === 'string') + else + { + markup += `<input type='text' name='${key}' id='${keyId}' value='${value}' class='text'>`; } } - ); - - htmlCode += '<p class="validateTips">Fields marked with an asterisk (*) are required.</p>'; - - // add a progress bar: - htmlCode += '<hr><div id="progressMsg" class="progress">' + self._progressMsg + '</div>'; - htmlCode += '<div id="progressbar"></div></div>'; - + }); - // replace root by the html code: - const dialogElement = document.getElementById('root'); - dialogElement.innerHTML = htmlCode; + if (self._requiredKeys.length > 0) + { + markup += "<p class='validateTips'>Fields marked with an asterisk (*) are required.</p>"; + } + // progress bar: + markup += `<hr><div id='progressMsg' class='progress-msg'>${self._progressMessage}</div>`; + markup += "<div class='progress-container'><div id='progressBar' class='progress-bar'></div></div>"; - // when the logo is loaded, we call _onDialogOpen again to reset the dimensions and position of - // the dialog box: - if (typeof logoUrl === 'string') + // buttons: + markup += "<hr>"; + markup += "<button id='dialogCancel' class='dialog-button' aria-label='Cancel Experiment'>Cancel</button>"; + if (self._requireParticipantClick) { - jQuery("#dialog-logo").on('load', () => - { - self._onDialogOpen('#expDialog')(); - }); + markup += "<button id='dialogOK' class='dialog-button disabled' aria-label='Start Experiment'>Ok</button>"; } + markup += "</div></div>"; - // setup change event handlers for all required keys: - this._requiredKeys.forEach((keyId) => - { - const input = document.getElementById(keyId); - if (input) - { - input.oninput = (event) => GUI._onKeyChange(self, event); - } - } - ); + // replace root by the markup code: + const dialogElement = document.getElementById("root"); + dialogElement.innerHTML = markup; // init and open the dialog box: - self._dialogComponent.button = 'Cancel'; - self._estimateDialogScalingFactor(); - const dialogSize = self._getDialogSize(); - jQuery("#expDialog").dialog({ - width: dialogSize[0], - maxHeight: dialogSize[1], - - autoOpen: true, - modal: true, - closeOnEscape: false, - resizable: false, - draggable: false, - - buttons: [ - { - id: "buttonCancel", - text: "Cancel", - click: function () - { - self._dialogComponent.button = 'Cancel'; - jQuery("#expDialog").dialog('close'); - } - }, - { - id: "buttonOk", - text: "Ok", - click: function () - { + const dialogDiv = document.getElementById("experiment-dialog"); + self._dialog = new A11yDialog(dialogDiv); + self._dialog.show(); + + // button callbacks: + self._dialogComponent.button = "Cancel"; + self._cancelButton = document.getElementById("dialogCancel"); + self._cancelButton.onclick = self._onCancelExperiment.bind(self); + if (self._requireParticipantClick) + { + self._okButton = document.getElementById("dialogOK"); + self._okButton.onclick = self._onStartExperiment.bind(self); + } + self._closeButton = document.getElementById("dialogClose"); + self._closeButton.onclick = self._onCancelExperiment.bind(self); - // update dictionary: - Object.keys(dictionary).forEach((key, keyIdx) => - { - const input = document.getElementById('form-input-' + keyIdx); - if (input) - { - dictionary[key] = input.value; - } - } - ); - - - self._dialogComponent.button = 'OK'; - jQuery("#expDialog").dialog('close'); - - // Tackle browser demands on having user action initiate audio context - Tone.start(); - - // switch to full screen if requested: - self._psychoJS.window.adjustScreenSize(); - - // Clear events (and keypresses) accumulated during the dialog - self._psychoJS.eventManager.clearEvents(); - } - } - ], + // update the OK button status: + self._updateDialog(); - // open the dialog in the middle of the screen: - open: self._onDialogOpen('#expDialog'), + self._progressMsg = document.getElementById("progressMsg"); + self._progressBar = document.getElementById("progressBar"); + self._updateProgressBar(); - // close is called by both buttons and when the user clicks on the cross: - close: function () + // setup change event handlers for all required keys: + this._requiredKeys.forEach((keyId) => + { + const input = document.getElementById(keyId); + if (input) { - //jQuery.unblockUI(); - jQuery(this).dialog('destroy').remove(); - self._dialogComponent.status = PsychoJS.Status.FINISHED; + input.oninput = (event) => GUI._onKeyChange(self, event); } - - }) - // change colour of title bar - .prev(".ui-dialog-titlebar").css("background", "green"); - - - // update the OK button status: - self._updateOkButtonStatus(); - - - // when the browser window is resize, we redimension and reposition the dialog: - self._dialogResize('#expDialog'); - - - // block UI until user has pressed dialog button: - // note: block UI does not allow for text to be entered in the dialog form boxes, alas! - //jQuery.blockUI({ message: "", baseZ: 1}); - - // show dialog box: - jQuery("#progressbar").progressbar({value: self._progressBarCurrentValue}); - jQuery("#progressbar").progressbar("option", "max", self._progressBarMax); + }); } if (self._dialogComponent.status === PsychoJS.Status.FINISHED) @@ -358,7 +315,6 @@

Source: core/GUI.js

}; } - /** * @callback GUI.onOK */ @@ -367,9 +323,6 @@

Source: core/GUI.js

* * <p>This function can be used to display both warning and error messages.</p> * - * @name module:core.GUI#dialog - * @function - * @public * @param {Object} options * @param {string} options.message - the message to be displayed * @param {Object.<string, *>} options.error - an exception @@ -378,55 +331,45 @@

Source: core/GUI.js

* @param {GUI.onOK} [options.onOK] - function called when the participant presses the OK button */ dialog({ - message, - warning, - error, - showOK = true, - onOK - } = {}) + message, + warning, + error, + showOK = true, + onOK + } = {}) { - // close the previously opened dialog box, if there is one: - const expDialog = jQuery("#expDialog"); - if (expDialog.length) - { - expDialog.dialog("destroy").remove(); - } - const msgDialog = jQuery("#msgDialog"); - if (msgDialog.length) - { - msgDialog.dialog("destroy").remove(); - } + this.closeDialog(); - let htmlCode; - let titleColour; + // prepare the markup for the a11y-dialog: + let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>"; + markup += "<div class='dialog-overlay'></div>"; + markup += "<div class='dialog-content'>"; // we are displaying an error: - if (typeof error !== 'undefined') + if (typeof error !== "undefined") { this._psychoJS.logger.fatal(util.toString(error)); // deal with null error: if (!error) { - error = 'Unspecified JavaScript error'; + error = "Unspecified JavaScript error"; } - let errorCode = null; - // go through the error stack and look for errorCode if there is one: - let stackCode = '<ul>'; + let errorCode = null; + let stackCode = "<ul>"; while (true) { - - if (typeof error === 'object' && 'errorCode' in error) + if (typeof error === "object" && "errorCode" in error) { errorCode = error.errorCode; } - if (typeof error === 'object' && 'context' in error) + if (typeof error === "object" && "context" in error) { - stackCode += '<li>' + error.context + '</li>'; + stackCode += "<li>" + error.context + "</li>"; error = error.error; } else @@ -437,209 +380,247 @@

Source: core/GUI.js

error = error.substring(1, 1000); } - stackCode += '<li><b>' + error + '</b></li>'; + stackCode += "<li><b>" + error + "</b></li>"; break; } } - stackCode += '</ul>'; + stackCode += "</ul>"; // if we found an errorCode, we replace the stack-based message by a more user-friendly one: if (errorCode) { const error = this._userFriendlyError(errorCode); - htmlCode = error.htmlCode; - titleColour = error.titleColour; + markup += `<div id='experiment-dialog-title' class='dialog-title ${error.class}'><p>${error.title}</p></div>`; + markup += `<p>${error.text}</p>`; } else { - htmlCode = '<div id="msgDialog" title="Error">'; - htmlCode += '<p class="validateTips">Unfortunately we encountered the following error:</p>'; - htmlCode += stackCode; - htmlCode += '<p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>'; - htmlCode += '</div>'; - - titleColour = 'red'; + markup += `<div id='experiment-dialog-title' class='dialog-title dialog-error'><p>Error</p></div>`; + markup += `<p>Unfortunately we encountered the following error:</p>`; + markup += stackCode; + markup += "<p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>"; } } - // we are displaying a message: - else if (typeof message !== 'undefined') + // we are displaying a warning: + else if (typeof warning !== "undefined") { - htmlCode = '<div id="msgDialog" title="Message">' + - '<p class="validateTips">' + message + '</p>' + - '</div>'; - titleColour = 'green'; + markup += `<div id='experiment-dialog-title' class='dialog-title dialog-warning'><p>Warning</p></div>`; + markup += `<p>${warning}</p>`; } - // we are displaying a warning: - else if (typeof warning !== 'undefined') + // we are displaying a message: + else if (typeof message !== "undefined") { - htmlCode = '<div id="msgDialog" title="Warning">' + - '<p class="validateTips">' + warning + '</p>' + - '</div>'; - titleColour = 'orange'; + markup += `<div id='experiment-dialog-title' class='dialog-title'><p>Message</p></div>`; + markup += `<p>${message}</p>`; } + if (showOK) + { + markup += "<hr><button id='dialogOK' class='dialog-button' aria-label='Close dialog'>Ok</button>"; + } + markup += "</div></div>"; - // replace root by the html code: - const dialogElement = document.getElementById('root'); - dialogElement.innerHTML = htmlCode; + // replace root by the markup code: + const dialogElement = document.getElementById("root"); + dialogElement.innerHTML = markup; // init and open the dialog box: - this._estimateDialogScalingFactor(); - const dialogSize = this._getDialogSize(); - const self = this; - jQuery("#msgDialog").dialog({ - dialogClass: 'no-close', - - width: dialogSize[0], - maxHeight: dialogSize[1], - - autoOpen: true, - modal: true, - closeOnEscape: false, - resizable: false, - draggable: false, - - buttons: (!showOK) ? [] : [{ - id: "buttonOk", - text: "Ok", - click: function () - { - jQuery(this).dialog("destroy").remove(); + const dialogDiv = document.getElementById("experiment-dialog"); + this._dialog = new A11yDialog(dialogDiv); + this._dialog.show(); - // execute callback function: - if (typeof onOK !== 'undefined') - { - onOK(); - } + // button callbacks: + if (showOK) + { + this._okButton = document.getElementById("dialogOK"); + this._okButton.onclick = () => + { + this.closeDialog(); + + // execute callback function: + if (typeof onOK !== "undefined") + { + onOK(); } - }], + }; + } + } + + /** + * <p>Create a dialog box with a progress bar, to inform the participant of + * the last stages of the experiment: upload of results, of log, and closing + * of session.</p> + * + * @param {Object} options + * @param {String} [options.text] - information text + */ + finishDialog({ text = "", nbSteps = 0 }) + { + this.closeDialog(); - // open the dialog in the middle of the screen: - open: self._onDialogOpen('#msgDialog'), + // prepare the markup for the a11y-dialog: + let markup = "<div class='dialog-container' id='experiment-dialog' aria-hidden='true' role='alertdialog'>"; + markup += "<div class='dialog-overlay'></div>"; + markup += "<div class='dialog-content'>"; + markup += `<div id='experiment-dialog-title' class='dialog-title dialog-warning'><p>Warning</p></div>`; + markup += `<p>${text}</p>`; - }) - // change colour of title bar - .prev(".ui-dialog-titlebar").css("background", titleColour); + // progress bar: + markup += `<hr><div id='progressMsg' class='progress-msg'>&nbsp;</div>`; + markup += "<div class='progress-container'><div id='progressBar' class='progress-bar'></div></div>"; + markup += "</div></div>"; - // when the browser window is resize, we redimension and reposition the dialog: - self._dialogResize('#msgDialog'); + // replace root by the markup code: + const dialogElement = document.getElementById("root"); + dialogElement.innerHTML = markup; + + // init and open the dialog box: + const dialogDiv = document.getElementById("experiment-dialog"); + this._dialog = new A11yDialog(dialogDiv); + this._dialog.show(); + + this._progressMsg = document.getElementById("progressMsg"); + this._progressBar = document.getElementById("progressBar"); + + this._progressMessage = "&nbsp;"; + this._progressBarCurrentValue = 0; + this._progressBarMax = nbSteps; + this._updateProgressBar(); } + finishDialogNextStep(text) + { + this._setProgressMessage(text); + ++ this._progressBarCurrentValue; + this._updateProgressBar(); + } /** - * Callback triggered when the jQuery UI dialog box is open. + * Close the previously opened dialog box, if there is one. + */ + closeDialog() + { + if (this._dialog) + { + this._dialog.hide(); + } + } + + /** + * Set the progress message. * - * @name module:core.GUI#_onDialogOpen - * @function - * @param {String} dialogId - the dialog ID - * @returns {Function} function setting the dimension and position of the dialog box - * @private + * @protected + * @param {string} message the message */ - _onDialogOpen(dialogId) + _setProgressMessage(message) { - const self = this; + this._progressMessage = message; + if (typeof this._progressMsg !== "undefined") + { + this._progressMsg.innerText = message; + } + } - return () => + /** + * Update the progress bar. + * + * @protected + */ + _updateProgressBar() + { + if (typeof this._progressBar !== "undefined") { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // note: jQuery(dialogId) is the dialog-content, jQuery(dialogId).parent() is the actual widget - const parent = jQuery(dialogId).parent(); - parent.css({ - position: 'absolute', - left: Math.max(0, (windowSize[0] - parent.outerWidth()) / 2.0), - top: Math.max(0, (windowSize[1] - parent.outerHeight()) / 2.0) - }); - - // record width and height difference between dialog content and dialog: - self._contentDelta = [ - parent.css('width').slice(0, -2) - jQuery(dialogId).css('width').slice(0, -2), - parent.css('height').slice(0, -2) - jQuery(dialogId).css('height').slice(0, -2)]; - }; + this._progressBar.style.width = `${Math.round(this._progressBarCurrentValue * 100.0 / this._progressBarMax)}%`; + } } + /** + * Callback triggered when the participant presses the Cancel button + * + * @protected + */ + _onCancelExperiment() + { + this._dialogComponent.button = "Cancel"; + + this._dialog.hide(); + this._dialog = null; + this._dialogComponent.status = PsychoJS.Status.FINISHED; + } /** - * Ensure that the browser window's resize events redimension and reposition the dialog UI. + * Callback triggered when the participant presses the OK button * - * @name module:core.GUI#_dialogResize - * @function - * @param {String} dialogId - the dialog ID - * @private + * @protected */ - _dialogResize(dialogId) + _onStartExperiment() { - const self = this; + this._dialogComponent.button = "OK"; - jQuery(window).resize(function () + // update the dictionary: + Object.keys(this._dictionary).forEach((key, keyIdx) => { - const parent = jQuery(dialogId).parent(); - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // size (we need to redimension both the dialog and the dialog content): - const dialogSize = self._getDialogSize(); - parent.css({ - width: dialogSize[0], - maxHeight: dialogSize[1] - }); - - const isDifferent = self._estimateDialogScalingFactor(); - if (!isDifferent) + const input = document.getElementById("form-input-" + keyIdx); + if (input) { - jQuery(dialogId).css({ - width: dialogSize[0] - self._contentDelta[0], - maxHeight: dialogSize[1] - self._contentDelta[1] - }); + this._dictionary[key] = input.value; } - - // position: - parent.css({ - position: 'absolute', - left: Math.max(0, (windowSize[0] - parent.outerWidth()) / 2.0), - top: Math.max(0, (windowSize[1] - parent.outerHeight()) / 2.0), - }); }); - } + // Start Tone here, since a user action is required to initiate the audio context: + Tone.start(); + + // switch to full screen if requested: + this._psychoJS.window.adjustScreenSize(); + + // clear all events (and keypresses) accumulated until now: + this._psychoJS.eventManager.clearEvents(); + + this._dialog.hide(); + this._dialog = null; + this._dialogComponent.status = PsychoJS.Status.FINISHED; + } + /** - * Listener for resource event from the [Server Manager]{@link ServerManager}. + * Callback triggered upon a resource event from the [Server Manager]{@link module:core.ServerManager}. * - * @name module:core.GUI#_onResourceEvents - * @function - * @private - * @param {Object.<string, string|Symbol>} signal the signal + * @protected + * @param {Object.<string, string|Symbol>} signal - the ServerManager's signal */ _onResourceEvents(signal) { - this._psychoJS.logger.debug('signal: ' + util.toString(signal)); + this._psychoJS.logger.debug("signal: " + util.toString(signal)); // the download of the specified resources has started: if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCES) { // for each resource, we have a 'downloading resource' and a 'resource downloaded' message: this._progressBarMax = signal.count * 2; - jQuery("#progressbar").progressbar("option", "max", this._progressBarMax); - this._progressBarCurrentValue = 0; + this._updateProgressBar(); } - // all the resources have been downloaded: show the ok button else if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED) { this._allResourcesDownloaded = true; - jQuery("#progressMsg").text('all resources downloaded.'); - this._updateOkButtonStatus(); - } + this._progressBarMax = 100; + this._progressBarCurrentValue = 100; + this._updateProgressBar(); + this._setProgressMessage("all resources downloaded."); + this._updateDialog(); + } // update progress bar: - else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE - || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) + else if ( + signal.message === ServerManager.Event.DOWNLOADING_RESOURCE + || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED + ) { - if (typeof this._progressBarCurrentValue === 'undefined') + if (typeof this._progressBarCurrentValue === "undefined") { this._progressBarCurrentValue = 0; } @@ -647,129 +628,82 @@

Source: core/GUI.js

if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - jQuery("#progressMsg").text('downloaded ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + this._setProgressMessage(`downloaded ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`); } else { - jQuery("#progressMsg").text('downloading ' + (this._progressBarCurrentValue / 2) + ' / ' + (this._progressBarMax / 2)); + this._setProgressMessage(`downloading ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`); } - // $("#progressMsg").text(signal.resource + ': downloaded.'); - jQuery("#progressbar").progressbar("option", "value", this._progressBarCurrentValue); - } - // unknown message: we just display it - else - { - jQuery("#progressMsg").text(signal.message); - } - } - - - /** - * Update the status of the OK button. - * - * @name module:core.GUI#_updateOkButtonStatus - * @param [changeFocus = false] - whether or not to change the focus to the OK button - * @function - * @private - */ - _updateOkButtonStatus(changeFocus = true) - { - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || (this._allResourcesDownloaded && this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length)) - { - if (changeFocus) - { - jQuery("#buttonOk").button("option", "disabled", false).focus(); - } - else - { - jQuery("#buttonOk").button("option", "disabled", false); - } + this._updateProgressBar(); } + // unknown message: we just display it else { - jQuery("#buttonOk").button("option", "disabled", true); + this._progressMsg.innerHTML = signal.message; } - - // strangely, changing the disabled option sometimes fails to update the ui, - // so we need to hide it and show it again: - jQuery("#buttonOk").hide(0, () => - { - jQuery("#buttonOk").show(); - }); } - /** - * Estimate the scaling factor for the dialog popup windows. + * Update the dialog box. * - * @name module:core.GUI#_estimateDialogScalingFactor - * @function - * @private - * @returns {boolean} whether or not the scaling factor is different from the previously estimated one + * @protected + * @param [changeOKButtonFocus = false] - whether to change the focus to the OK button */ - _estimateDialogScalingFactor() + _updateDialog(changeOKButtonFocus = true) { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - - // desktop: - let dialogScalingFactor = 1.0; + const allRequirementsFulfilled = this._allResourcesDownloaded + && (this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length); - // mobile or tablet: - if (windowSize[0] < 1080) + // if the participant is required to click on the OK button: + if (this._requireParticipantClick) { - // landscape: - if (windowSize[0] > windowSize[1]) + if (typeof this._okButton !== "undefined") { - dialogScalingFactor = 1.5; - }// portrait: - else - { - dialogScalingFactor = 2.0; + // locally the OK button is always enabled, otherwise only if all requirements have been fulfilled: + if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || allRequirementsFulfilled) + { + if (changeOKButtonFocus) + { + this._okButton.classList = ["dialog-button"]; + this._okButton.focus(); + } + else + { + this._okButton.classList = ["dialog-button"]; + } + } + else + { + this._okButton.classList = ["dialog-button", "disabled"]; + } } - } - - const isDifferent = (dialogScalingFactor !== this._dialogScalingFactor); - this._dialogScalingFactor = dialogScalingFactor; - return isDifferent; - } + return; + } - /** - * Get the size of the dialog. - * - * @name module:core.GUI#_getDialogSize - * @private - * @returns {number[]} the size of the popup dialog window - */ - _getDialogSize() - { - const windowSize = [jQuery(window).width(), jQuery(window).height()]; - this._estimateDialogScalingFactor(); - - return [ - Math.min(GUI.dialogMaxSize[0], (windowSize[0] - GUI.dialogMargin[0]) / this._dialogScalingFactor), - Math.min(GUI.dialogMaxSize[1], (windowSize[1] - GUI.dialogMargin[1]) / this._dialogScalingFactor)]; + // if all requirements are fulfilled and the participant is not required to click on the OK button, + // then we close the dialog box and move on with the experiment: + if (allRequirementsFulfilled) + { + this._onStartExperiment(); + } } - /** - * Listener for change event for required keys. + * Callback triggered upon change event (for required keys). * - * @name module:core.GUI#_onKeyChange - * @function - * @static - * @private + * @protected * @param {module:core.GUI} gui - this GUI - * @param {Event} event - event + * @param {Event} event - the key's event */ static _onKeyChange(gui, event) { const element = event.target; const value = element.value; - if (typeof value !== 'undefined' && value.length > 0) + if (typeof value !== "undefined" && value.length > 0) { gui._setRequiredKeys.set(event.target, true); } @@ -778,16 +712,15 @@

Source: core/GUI.js

gui._setRequiredKeys.delete(event.target); } - gui._updateOkButtonStatus(false); + gui._updateDialog(false); } - /** - * Get a more user-friendly html message. + * Get the user-friendly html message associated to a pavlovia.or server error code. * + * @protected * @param {number} errorCode - the pavlovia.org server error code - * @private - * @return {{htmlCode: string, titleColour: string}} a user-friendly error message + * @return {{class: string, title: string, text: string}} a user-friendly error message */ _userFriendlyError(errorCode) { @@ -796,111 +729,101 @@

Source: core/GUI.js

// INTERNAL_ERROR case 1: return { - htmlCode: '<div id="msgDialog" title="Error"><p>Oops we encountered an internal server error.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>', - titleColour: 'red' + class: "dialog-error", + title: "Error", + text: "<p>Oops we encountered an <strong>internal server error</strong>.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>" }; // MONGODB_ERROR case 2: return { - htmlCode: '<div id="msgDialog" title="Error"><p>Oops we encountered a database error.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>', - titleColour: 'red' + class: "dialog-error", + title: "Error", + text: "<p>Oops we encountered a <strong>database error</strong>.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>" }; // STATUS_NONE case 20: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any status and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any status and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p>` }; // STATUS_INACTIVE case 21: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently inactive and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently inactive and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.</p>` }; // STATUS_DELETED case 22: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been deleted and cannot be run.</p><p>If you are the experiment designer, either go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been deleted and cannot be run.</p><p>If you are the experiment designer, either go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.</p>` }; // STATUS_ARCHIVED case 23: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been archived and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> has been archived and cannot be run.</p><p>If you are the experiment designer, go to your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a> and change the experiment status to either PILOTING or RUNNING.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.</p>` }; // PILOTING_NO_TOKEN case 30: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently in PILOTING mode but the pilot token is missing from the URL.</p><p>If you are the experiment designer, you can pilot it by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is currently in PILOTING mode but the pilot token is missing from the URL.</p><p>If you are the experiment designer, you can pilot it by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p>` }; // PILOTING_INVALID_TOKEN case 31: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> cannot be run because the pilot token in the URL is invalid, possibly because it has expired.</p><p>If you are the experiment designer, you can generate a new token by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> cannot be run because the pilot token in the URL is invalid, possibly because it has expired.</p><p>If you are the experiment designer, you can generate a new token by pressing the pilot button on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.</p>` }; // LICENSE_EXPIRED case 50: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that has expired. </p><p>If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>, where you will also be able to change its running mode to CREDIT.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that has expired. </p><p>If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>, where you will also be able to change its running mode to CREDIT.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.</p>` }; // LICENSE_APPROVAL_NEEDED case 51: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that requires one or more documents to be approved before the experiment can be run. </p><p>If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> is covered by a license that requires one or more documents to be approved before the experiment can be run. </p><p>If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.</p>` }; // CREDIT_NOT_ENOUGH case 60: return { - htmlCode: `<div id="msgDialog" title="Warning"><p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any assigned credit left and cannot be run.</p><p>If you are the experiment designer, you can assign more credits to it on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.</p></div>`, - titleColour: 'orange' + class: "dialog-warning", + title: "Warning", + text: `<p><strong>${this._psychoJS.config.experiment.fullpath}</strong> does not have any assigned credit left and cannot be run.</p><p>If you are the experiment designer, you can assign more credits to it on your <a href="https://pavlovia.org/${this._psychoJS.config.experiment.fullpath}">experiment page</a>.</p><p>Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.</p>` }; default: return { - htmlCode: `<div id="msgDialog" title="Error"><p>Unfortunately we encountered an unspecified error (error code: ${errorCode}.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p></div>`, - titleColour: 'red' + class: "dialog-error", + title: "Error", + text: `<p>Unfortunately we encountered an unspecified error (error code: ${errorCode}.</p><p>Try to run the experiment again. If the error persists, contact the experiment designer.</p>` }; } } - } - -/** - * Maximal dimensions of the dialog window. - * - * @name module:core.GUI#dialogMaxSize - * @enum {Symbol} - * @readonly - * @public - */ -GUI.dialogMaxSize = [500, 600]; - - -/** - * Dialog window margins. - * - * @name module:core.GUI#dialogMargin - * @enum {Symbol} - * @readonly - * @public - */ -GUI.dialogMargin = [50, 50]; @@ -908,19 +831,23 @@

Source: core/GUI.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_Keyboard.js.html b/docs/core_Keyboard.js.html index 373e51f7..96d7d46c 100644 --- a/docs/core_Keyboard.js.html +++ b/docs/core_Keyboard.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/Keyboard.js - - - + core/Keyboard.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/Keyboard.js

+ + + + +
+ +

core/Keyboard.js

+ @@ -30,32 +54,32 @@

Source: core/Keyboard.js

* Manager handling the keyboard events. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {Clock, MonotonicClock} from "../util/Clock"; -import {PsychObject} from "../util/PsychObject"; -import {PsychoJS} from "./PsychoJS"; -import {EventManager} from "./EventManager"; - +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { EventManager } from "./EventManager.js"; +import { PsychoJS } from "./PsychoJS.js"; /** - * @name module:core.KeyPress - * @class - * - * @param {string} code - W3C Key Code - * @param {number} tDown - time of key press (keydown event) relative to the global Monotonic Clock - * @param {string | undefined} name - pyglet key name + * <pKeyPress holds information about a key that has been pressed, such as the duration of the press.</p> */ export class KeyPress { + /** + * @memberof module:core + * @param {string} code - W3C Key Code + * @param {number} tDown - time of key press (keydown event) relative to the global Monotonic Clock + * @param {string | undefined} name - pyglet key name + */ constructor(code, tDown, name) { this.code = code; this.tDown = tDown; - this.name = (typeof name !== 'undefined') ? name : EventManager.w3c2pyglet(code); + this.name = (typeof name !== "undefined") ? name : EventManager.w3c2pyglet(code); // duration of the keypress (time between keydown and keyup events) or undefined if there was no keyup this.duration = undefined; @@ -65,83 +89,68 @@

Source: core/Keyboard.js

} } - /** * <p>This manager handles all keyboard events. It is a substitute for the keyboard component of EventManager. </p> * - * @name module:core.Keyboard - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {number} [options.bufferSize= 10000] - the maximum size of the circular keyboard event buffer - * @param {boolean} [options.waitForStart= false] - whether or not to wait for a call to module:core.Keyboard#start - * before recording keyboard events - * @param {Clock} [options.clock= undefined] - an optional clock - * @param {boolean} [options.autoLog= false] - whether or not to log + * @extends PsychObject */ export class Keyboard extends PsychObject { - + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {number} [options.bufferSize= 10000] - the maximum size of the circular keyboard event buffer + * @param {boolean} [options.waitForStart= false] - whether or not to wait for a call to module:core.Keyboard#start + * before recording keyboard events + * @param {Clock} [options.clock= undefined] - an optional clock + * @param {boolean} [options.autoLog= false] - whether or not to log + */ constructor({ - psychoJS, - bufferSize = 10000, - waitForStart = false, - clock, - autoLog = false, - } = {}) + psychoJS, + bufferSize = 10000, + waitForStart = false, + clock, + autoLog = false, + } = {}) { - super(psychoJS); - if (typeof clock === 'undefined') + if (typeof clock === "undefined") { clock = new Clock(); - } //this._psychoJS.monotonicClock; + } // this._psychoJS.monotonicClock; - this._addAttribute('bufferSize', bufferSize); - this._addAttribute('waitForStart', waitForStart); - this._addAttribute('clock', clock); - this._addAttribute('autoLog', autoLog); + this._addAttribute("bufferSize", bufferSize); + this._addAttribute("waitForStart", waitForStart); + this._addAttribute("clock", clock); + this._addAttribute("autoLog", autoLog); // start recording key events if need be: - this._addAttribute('status', (waitForStart) ? PsychoJS.Status.NOT_STARTED : PsychoJS.Status.STARTED); + this._addAttribute("status", (waitForStart) ? PsychoJS.Status.NOT_STARTED : PsychoJS.Status.STARTED); // setup circular buffer: this.clearEvents(); // add key listeners: this._addKeyListeners(); - } - /** * Start recording keyboard events. - * - * @name module:core.Keyboard#start - * @function - * @public - * */ start() { this._status = PsychoJS.Status.STARTED; } - /** * Stop recording keyboard events. - * - * @name module:core.Keyboard#stop - * @function - * @public - * */ stop() { this._status = PsychoJS.Status.STOPPED; } - /** * @typedef Keyboard.KeyEvent * @@ -155,9 +164,6 @@

Source: core/Keyboard.js

* Get the list of those keyboard events still in the buffer, i.e. those that have not been * previously cleared by calls to getKeys with clear = true. * - * @name module:core.Keyboard#getEvents - * @function - * @public * @return {Keyboard.KeyEvent[]} the list of events still in the buffer */ getEvents() @@ -167,7 +173,6 @@

Source: core/Keyboard.js

return []; } - // iterate over the buffer, from start to end, and discard the null event: let filteredEvents = []; const bufferWrap = (this._bufferLength === this._bufferSize); @@ -185,13 +190,9 @@

Source: core/Keyboard.js

return filteredEvents; } - /** * Get the list of keys pressed or pushed by the participant. * - * @name module:core.Keyboard#getKeys - * @function - * @public * @param {Object} options * @param {string[]} [options.keyList= []]] - the list of keys to consider. If keyList is empty, we consider all keys. * Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy. @@ -202,12 +203,11 @@

Source: core/Keyboard.js

* (keydown with no subsequent keyup at the time getKeys is called). */ getKeys({ - keyList = [], - waitRelease = true, - clear = true - } = {}) + keyList = [], + waitRelease = true, + clear = true, + } = {}) { - // if nothing in the buffer, return immediately: if (this._bufferLength === 0) { @@ -231,7 +231,7 @@

Source: core/Keyboard.js

{ // look for a corresponding, preceding keydown event: const precedingKeydownIndex = keyEvent.keydownIndex; - if (typeof precedingKeydownIndex !== 'undefined') + if (typeof precedingKeydownIndex !== "undefined") { const precedingKeydownEvent = this._circularBuffer[precedingKeydownIndex]; if (precedingKeydownEvent) @@ -278,13 +278,10 @@

Source: core/Keyboard.js

{ this._circularBuffer[i] = null; } - } } - } while (i !== this._bufferIndex); - // if waitRelease = false, we iterate again over the map of unmatched keydown events: if (!waitRelease) { @@ -331,23 +328,17 @@

Source: core/Keyboard.js

} while (i !== this._bufferIndex);*/ } - // if clear = true and the keyList is empty, we clear all the events: if (clear && keyList.length === 0) { this.clearEvents(); } - return keyPresses; } - /** * Clear all events and resets the circular buffers. - * - * @name module:core.Keyboard#clearEvents - * @function */ clearEvents() { @@ -361,13 +352,9 @@

Source: core/Keyboard.js

this._unmatchedKeydownMap = new Map(); } - /** * Test whether a list of KeyPress's contains one with a particular name. * - * @name module:core.Keyboard#includes - * @function - * @static * @param {module:core.KeyPress[]} keypressList - list of KeyPress's * @param {string } keyName - pyglet key name, e.g. 'escape', 'left' * @return {boolean} whether or not a KeyPress with the given pyglet key name is present in the list @@ -380,26 +367,22 @@

Source: core/Keyboard.js

} const value = keypressList.find((keypress) => keypress.name === keyName); - return (typeof value !== 'undefined'); + return (typeof value !== "undefined"); } - /** * Add key listeners to the document. * - * @name module:core.Keyboard#_addKeyListeners - * @function - * @private + * @protected */ _addKeyListeners() { this._previousKeydownKey = undefined; const self = this; - // add a keydown listener: window.addEventListener("keydown", (event) => - // document.addEventListener("keydown", (event) => + // document.addEventListener("keydown", (event) => { // only consider non-repeat events, i.e. only the first keydown event associated with a participant // holding a key down: @@ -426,14 +409,13 @@

Source: core/Keyboard.js

let code = event.code; // take care of legacy Microsoft browsers (IE11 and pre-Chromium Edge): - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } let pigletKey = EventManager.w3c2pyglet(code); - self._bufferIndex = (self._bufferIndex + 1) % self._bufferSize; self._bufferLength = Math.min(self._bufferLength + 1, self._bufferSize); self._circularBuffer[self._bufferIndex] = { @@ -441,20 +423,19 @@

Source: core/Keyboard.js

key: event.key, pigletKey, status: Keyboard.KeyStatus.KEY_DOWN, - timestamp + timestamp, }; self._unmatchedKeydownMap.set(event.code, self._bufferIndex); - self._psychoJS.logger.trace('keydown: ', event.key); + self._psychoJS.logger.trace("keydown: ", event.key); event.stopPropagation(); }); - // add a keyup listener: window.addEventListener("keyup", (event) => - // document.addEventListener("keyup", (event) => + // document.addEventListener("keyup", (event) => { const timestamp = MonotonicClock.getReferenceTime(); // timestamp in seconds @@ -468,7 +449,7 @@

Source: core/Keyboard.js

let code = event.code; // take care of legacy Microsoft Edge: - if (typeof code === 'undefined') + if (typeof code === "undefined") { code = EventManager.keycode2w3c(event.keyCode); } @@ -482,39 +463,35 @@

Source: core/Keyboard.js

key: event.key, pigletKey, status: Keyboard.KeyStatus.KEY_UP, - timestamp + timestamp, }; // get the corresponding keydown event // note: if more keys are down than there are slots in the circular buffer, there might // not be a corresponding keydown event const correspondingKeydownIndex = self._unmatchedKeydownMap.get(event.code); - if (typeof correspondingKeydownIndex !== 'undefined') + if (typeof correspondingKeydownIndex !== "undefined") { self._circularBuffer[self._bufferIndex].keydownIndex = correspondingKeydownIndex; self._unmatchedKeydownMap.delete(event.code); } - self._psychoJS.logger.trace('keyup: ', event.key); + self._psychoJS.logger.trace("keyup: ", event.key); event.stopPropagation(); }); - } } - /** * Keyboard KeyStatus. * - * @name module:core.Keyboard#KeyStatus * @enum {Symbol} * @readonly - * @public */ Keyboard.KeyStatus = { - KEY_DOWN: Symbol.for('KEY_DOWN'), - KEY_UP: Symbol.for('KEY_UP') + KEY_DOWN: Symbol.for("KEY_DOWN"), + KEY_UP: Symbol.for("KEY_UP"), }; @@ -523,19 +500,23 @@

Source: core/Keyboard.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_Logger.js.html b/docs/core_Logger.js.html index 1d7ec8a4..92567213 100644 --- a/docs/core_Logger.js.html +++ b/docs/core_Logger.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/Logger.js - - - + core/Logger.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + + + -

Source: core/Logger.js

+
+ +

core/Logger.js

+ @@ -30,37 +54,36 @@

Source: core/Logger.js

* Logger * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import log4javascript from 'log4javascript'; -import pako from 'pako'; -import * as util from '../util/Util'; -import {MonotonicClock} from '../util/Clock'; -import {ExperimentHandler} from '../data/ExperimentHandler'; +import log4javascript from "log4javascript"; +import pako from "pako"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { MonotonicClock } from "../util/Clock.js"; +import * as util from "../util/Util.js"; /** * <p>This class handles a variety of loggers, e.g. a browser console one (mostly for debugging), * a remote one, etc.</p> * * <p>Note: we use log4javascript for the console logger, and our own for the server logger.</p> - * - * @name module:core.Logger - * @class - * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR */ export class Logger { - + /** + * @memberof module:core + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR + */ constructor(psychoJS, threshold) { this._psychoJS = psychoJS; // browser console logger: - this.consoleLogger = log4javascript.getLogger('psychojs'); + this.consoleLogger = log4javascript.getLogger("psychojs"); const appender = new log4javascript.BrowserConsoleAppender(); appender.setLayout(this._customConsoleLayout()); @@ -69,7 +92,6 @@

Source: core/Logger.js

this.consoleLogger.addAppender(appender); this.consoleLogger.setLevel(threshold); - // server logger: this._serverLogs = []; this._serverLevel = Logger.ServerLevel.WARNING; @@ -93,17 +115,13 @@

Source: core/Logger.js

// throttling message index: index: 0, // whether or not the designer has already been warned: - designerWasWarned: false + designerWasWarned: false, }; } - - /** * Change the logging level. * - * @name module:core.Logger#setLevel - * @public * @param {module:core.Logger.ServerLevel} serverLevel - the new logging level */ setLevel(serverLevel) @@ -112,13 +130,9 @@

Source: core/Logger.js

this._serverLevelValue = this._getValue(this._serverLevel); } - - /** * Log a server message at the EXP level. * - * @name module:core.Logger#exp - * @public * @param {string} msg - the message to be logged. * @param {number} [time] - the logging time * @param {object} [obj] - the associated object (e.g. a Trial) @@ -128,13 +142,9 @@

Source: core/Logger.js

this.log(msg, Logger.ServerLevel.EXP, time, obj); } - - /** * Log a server message at the DATA level. * - * @name module:core.Logger#data - * @public * @param {string} msg - the message to be logged. * @param {number} [time] - the logging time * @param {object} [obj] - the associated object (e.g. a Trial) @@ -144,13 +154,9 @@

Source: core/Logger.js

this.log(msg, Logger.ServerLevel.DATA, time, obj); } - - /** * Log a server message. * - * @name module:core.Logger#log - * @public * @param {string} msg - the message to be logged. * @param {module:core.Logger.ServerLevel} level - logging level * @param {number} [time] - the logging time @@ -165,7 +171,7 @@

Source: core/Logger.js

return; } - if (typeof time === 'undefined') + if (typeof time === "undefined") { time = MonotonicClock.getReferenceTime(); } @@ -182,18 +188,14 @@

Source: core/Logger.js

msg, level, time, - obj: util.toString(obj) + obj: util.toString(obj), }); } - - /** * Check whether or not a log messages must be throttled. * - * @name module:core.Logger#_throttle * @protected - * * @param {number} time - the time of the latest log message * @return {boolean} whether or not to log the message */ @@ -209,24 +211,26 @@

Source: core/Logger.js

// warn the designer if we are not already throttling: if (!this._throttling.isThrottling) { - const msg = `<p>[time= ${time.toFixed(3)}] More than ${this._throttling.threshold} messages were logged in the past ${this._throttling.window}s.</p>` + - `<p>We are now throttling: only 1 in ${this._throttling.factor} messages will be logged.</p>` + - `<p>You may want to change your experiment's logging level. Please see <a href="https://www.psychopy.org/api/logging.html">psychopy.org/api/logging.html</a> for details.</p>`; + const msg = `<p>[time= ${time.toFixed(3)}] More than ${this._throttling.threshold} messages were logged in the past ${this._throttling.window}s.</p>` + + `<p>We are now throttling: only 1 in ${this._throttling.factor} messages will be logged.</p>` + + `<p>You may want to change your experiment's logging level. Please see <a href="https://www.psychopy.org/api/logging.html">psychopy.org/api/logging.html</a> for details.</p>`; // console warning: this._psychoJS.logger.warn(msg); // in PILOTING mode and locally, we also warn the experimenter with a dialog box, // but only once: - if (!this._throttling.designerWasWarned && - (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || - this._psychoJS.config.experiment.status === 'PILOTING')) + if ( + !this._throttling.designerWasWarned + && (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL + || this._psychoJS.config.experiment.status === "PILOTING") + ) { this._throttling.designerWasWarned = true; this._psychoJS.gui.dialog({ warning: msg, - showOK: true + showOK: true, }); } @@ -235,7 +239,7 @@

Source: core/Logger.js

this._throttling.index = 0; } - ++ this._throttling.index; + ++this._throttling.index; if (this._throttling.index < this._throttling.factor) { // no logging @@ -248,8 +252,10 @@

Source: core/Logger.js

} else { - if (this._throttling.isThrottling && - (time - this._throttling.startOfThrottling) > this._throttling.minimumDuration) + if ( + this._throttling.isThrottling + && (time - this._throttling.startOfThrottling) > this._throttling.minimumDuration + ) { this._psychoJS.logger.info(`[time= ${time.toFixed(3)}] Log messages are not throttled any longer.`); this._throttling.isThrottling = false; @@ -260,53 +266,50 @@

Source: core/Logger.js

return false; } - - /** * Flush all server logs to the server. * * <p>Note: the logs are compressed using Pako's zlib algorithm. * See https://github.com/nodeca/pako for details.</p> - * - * @name module:core.Logger#flush - * @public */ async flush() { const response = { - origin: 'Logger.flush', - context: 'when flushing participant\'s logs for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "Logger.flush", + context: "when flushing participant's logs for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.info('[PsychoJS] Flush server logs.'); + this._psychoJS.logger.info("[PsychoJS] Flush server logs."); // prepare the formatted logs: - let formattedLogs = ''; + let formattedLogs = ""; for (const log of this._serverLogs) { - let formattedLog = util.toString(log.time) + - '\t' + Symbol.keyFor(log.level) + - '\t' + log.msg; - if (log.obj !== 'undefined') + let formattedLog = util.toString(log.time) + + "\t" + Symbol.keyFor(log.level) + + "\t" + log.msg; + if (log.obj !== "undefined") { - formattedLog += '\t' + log.obj; + formattedLog += "\t" + log.obj; } - formattedLog += '\n'; + formattedLog += "\n"; formattedLogs += formattedLog; } // send logs to the server or display them in the console: - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { // if the pako compression library is present, we compress the logs: - if (typeof pako !== 'undefined') + if (typeof pako !== "undefined") { try { - const utf16DeflatedLogs = pako.deflate(formattedLogs, {to: 'string'}); + const utf16DeflatedLogs = pako.deflate(formattedLogs, { to: "string" }); // const utf16DeflatedLogs = pako.deflate(unescape(encodeURIComponent(formattedLogs)), {to: 'string'}); const base64DeflatedLogs = btoa(utf16DeflatedLogs); @@ -314,29 +317,26 @@

Source: core/Logger.js

} catch (error) { - console.error('log compression error:', error); - throw Object.assign(response, {error: error}); + console.error("log compression error:", error); + throw Object.assign(response, { error: error }); } } - else // the pako compression library is not present, we do not compress the logs: + else { return await this._psychoJS.serverManager.uploadLog(formattedLogs, false); } } else { - this._psychoJS.logger.debug('\n' + formattedLogs); + this._psychoJS.logger.debug("\n" + formattedLogs); } } - - /** * Create a custom console layout. * - * @name module:core.Logger#_customConsoleLayout - * @private + * @protected * @return {*} the custom layout */ _customConsoleLayout() @@ -344,59 +344,59 @@

Source: core/Logger.js

const detectedBrowser = util.detectBrowser(); const customLayout = new log4javascript.PatternLayout("%p %d{HH:mm:ss.SSS} %f{1} | %m"); - customLayout.setCustomField('location', function (layout, loggingReference) + customLayout.setCustomField("location", function(layout, loggingReference) { // we throw a fake exception to retrieve the stack trace try { // (0)(); - throw Error('fake exception'); + throw Error("fake exception"); } catch (e) { - const stackEntries = e.stack.replace(/^.*?\n/, '').replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anon}(').split("\n"); + const stackEntries = e.stack.replace(/^.*?\n/, "").replace(/(?:\n@:0)?\s+$/m, "").replace(/^\(/gm, "{anon}(").split("\n"); let relevantEntry; - if (detectedBrowser === 'Firefox') + if (detectedBrowser === "Firefox") { // look for entry immediately after those of log4javascript: for (let entry of stackEntries) { - if (entry.indexOf('log4javascript.min.js') <= 0) + if (entry.indexOf("log4javascript.min.js") <= 0) { relevantEntry = entry; break; } } - const buf = relevantEntry.split(':'); + const buf = relevantEntry.split(":"); const line = buf[buf.length - 2]; - const file = buf[buf.length - 3].split('/').pop(); - const method = relevantEntry.split('@')[0]; + const file = buf[buf.length - 3].split("/").pop(); + const method = relevantEntry.split("@")[0]; - return method + ' ' + file + ':' + line; + return method + " " + file + ":" + line; } - else if (detectedBrowser === 'Safari') + else if (detectedBrowser === "Safari") { - return 'unknown'; + return "unknown"; } - else if (detectedBrowser === 'Chrome') + else if (detectedBrowser === "Chrome") { relevantEntry = stackEntries.pop(); - let buf = relevantEntry.split(' '); + let buf = relevantEntry.split(" "); let fileLine = buf.pop(); const method = buf.pop(); - buf = fileLine.split(':'); + buf = fileLine.split(":"); buf.pop(); const line = buf.pop(); - const file = buf.pop().split('/').pop(); + const file = buf.pop().split("/").pop(); - return method + ' ' + file + ':' + line; + return method + " " + file + ":" + line; } else { - return 'unknown'; + return "unknown"; } } }); @@ -404,12 +404,9 @@

Source: core/Logger.js

return customLayout; } - - /** * Get the integer value associated with a logging level. * - * @name module:core.Logger#_getValue * @protected * @param {module:core.Logger.ServerLevel} level - the logging level * @return {number} - the value associated with the logging level, or 30 is the logging level is unknown. @@ -421,49 +418,43 @@

Source: core/Logger.js

} } - - /** * Server logging level. * - * @name module:core.Logger#ServerLevel * @enum {Symbol} * @readonly - * @public * * @note These are similar to PsychoPy's logging levels, as defined in logging.py */ Logger.ServerLevel = { - CRITICAL: Symbol.for('CRITICAL'), - ERROR: Symbol.for('ERROR'), - WARNING: Symbol.for('WARNING'), - DATA: Symbol.for('DATA'), - EXP: Symbol.for('EXP'), - INFO: Symbol.for('INFO'), - DEBUG: Symbol.for('DEBUG'), - NOTSET: Symbol.for('NOTSET') + CRITICAL: Symbol.for("CRITICAL"), + ERROR: Symbol.for("ERROR"), + WARNING: Symbol.for("WARNING"), + DATA: Symbol.for("DATA"), + EXP: Symbol.for("EXP"), + INFO: Symbol.for("INFO"), + DEBUG: Symbol.for("DEBUG"), + NOTSET: Symbol.for("NOTSET"), }; - /** * Server logging level values. * * <p>We use those values to determine whether a log is to be sent to the server or not.</p> * - * @name module:core.Logger#_ServerLevelValue * @enum {number} * @readonly * @protected */ Logger._ServerLevelValue = { - 'CRITICAL': 50, - 'ERROR': 40, - 'WARNING': 30, - 'DATA': 25, - 'EXP': 22, - 'INFO': 20, - 'DEBUG': 10, - 'NOTSET': 0 + "CRITICAL": 50, + "ERROR": 40, + "WARNING": 30, + "DATA": 25, + "EXP": 22, + "INFO": 20, + "DEBUG": 10, + "NOTSET": 0, }; @@ -472,19 +463,23 @@

Source: core/Logger.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_MinimalStim.js.html b/docs/core_MinimalStim.js.html index 27a18327..6544b119 100644 --- a/docs/core_MinimalStim.js.html +++ b/docs/core_MinimalStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/MinimalStim.js - - - + core/MinimalStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/MinimalStim.js

+ + + + +
+ +

core/MinimalStim.js

+ @@ -30,33 +54,31 @@

Source: core/MinimalStim.js

* Base class for all stimuli. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.0 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {PsychObject} from '../util/PsychObject'; -import {PsychoJS} from './PsychoJS'; -import * as util from '../util/Util'; - - +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; /** * <p>MinimalStim is the base class for all stimuli.</p> * - * @name module:core.MinimalStim - * @class * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= win.autoLog] - whether or not to log */ export class MinimalStim extends PsychObject { - constructor({name, win, autoDraw, autoLog} = {}) + /** + * @memberof module:core + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= win.autoLog] - whether to log + */ + constructor({ name, win, autoDraw, autoLog } = {}) { super(win._psychoJS, name); @@ -64,46 +86,40 @@

Source: core/MinimalStim.js

this._pixi = undefined; this._addAttribute( - 'win', + "win", win, - undefined + undefined, ); this._addAttribute( - 'autoDraw', + "autoDraw", autoDraw, - false + false, ); this._addAttribute( - 'autoLog', + "autoLog", autoLog, - (typeof win !== 'undefined' && win !== null) ? win.autoLog : false + (typeof win !== "undefined" && win !== null) ? win.autoLog : false, ); this._needUpdate = false; this.status = PsychoJS.Status.NOT_STARTED; } - - /** * Setter for the autoDraw attribute. * - * @name module:core.MinimalStim#setAutoDraw - * @function - * @public * @param {boolean} autoDraw - the new value - * @param {boolean} [log= false] - whether or not to log + * @param {boolean} [log= false] - whether to log */ setAutoDraw(autoDraw, log = false) { - this._setAttribute('autoDraw', autoDraw, log); + this._setAttribute("autoDraw", autoDraw, log); // autoDraw = true: add the stimulus to the draw list if it's not there already if (this._autoDraw) { this.draw(); } - // autoDraw = false: remove the stimulus from the draw list (and from the root container if it's already there) else { @@ -111,14 +127,8 @@

Source: core/MinimalStim.js

} } - - /** * Draw this stimulus on the next frame draw. - * - * @name module:core.MinimalStim#draw - * @function - * @public */ draw() { @@ -131,13 +141,13 @@

Source: core/MinimalStim.js

{ // update the stimulus if need be before we add its PIXI representation to the window container: this._updateIfNeeded(); - if (typeof this._pixi === 'undefined') + if (typeof this._pixi === "undefined") { - this.psychoJS.logger.warn('the Pixi.js representation of this stimulus is undefined.'); + this.psychoJS.logger.warn("the Pixi.js representation of this stimulus is undefined."); } else { - this.win._rootContainer.addChild(this._pixi); + this._win.addPixiObject(this._pixi); this.win._drawList.push(this); } } @@ -145,11 +155,11 @@

Source: core/MinimalStim.js

{ // the stimulus is already in the list, if it needs to be updated, we remove it // from the window container, update it, then put it back: - if (this._needUpdate && typeof this._pixi !== 'undefined') + if (this._needUpdate && typeof this._pixi !== "undefined") { - this.win._rootContainer.removeChild(this._pixi); + this._win.removePixiObject(this._pixi); this._updateIfNeeded(); - this.win._rootContainer.addChild(this._pixi); + this._win.addPixiObject(this._pixi); } } } @@ -157,14 +167,8 @@

Source: core/MinimalStim.js

this.status = PsychoJS.Status.STARTED; } - - /** * Hide this stimulus on the next frame draw. - * - * @name module:core.MinimalStim#hide - * @function - * @public */ hide() { @@ -176,77 +180,62 @@

Source: core/MinimalStim.js

this._win._drawList.splice(index, 1); // if the stimulus has a pixi representation, remove it from the root container: - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { - this._win._rootContainer.removeChild(this._pixi); + this._win.removePixiObject(this._pixi); } } this.status = PsychoJS.Status.STOPPED; } } - - /** * Determine whether an object is inside this stimulus. * - * @name module:core.MinimalStim#contains - * @function * @abstract - * @public * @param {Object} object - the object * @param {String} units - the stimulus units */ contains(object, units) { throw { - origin: 'MinimalStim.contains', + origin: "MinimalStim.contains", context: `when determining whether stimulus: ${this._name} contains object: ${util.toString(object)}`, - error: 'this method is abstract and should not be called.' + error: "this method is abstract and should not be called.", }; } - - /** * Release the PIXI representation, if there is one. * - * @name module:core.MinimalStim#release - * @function - * @public - * - * @param {boolean} [log= false] - whether or not to log + * @param {boolean} [log= false] - whether to log */ release(log = false) { - this._setAttribute('autoDraw', false, log); + this._setAttribute("autoDraw", false, log); this.status = PsychoJS.Status.STOPPED; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); this._pixi = undefined; } } - - /** * Update the stimulus, if necessary. * * Note: this is an abstract function, which should not be called. * - * @name module:core.MinimalStim#_updateIfNeeded - * @function * @abstract - * @private + * @protected */ _updateIfNeeded() { throw { - origin: 'MinimalStim._updateIfNeeded', - context: 'when updating stimulus: ' + this._name, - error: 'this method is abstract and should not be called.' + origin: "MinimalStim._updateIfNeeded", + context: "when updating stimulus: " + this._name, + error: "this method is abstract and should not be called.", }; } } @@ -257,19 +246,23 @@

Source: core/MinimalStim.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_Mouse.js.html b/docs/core_Mouse.js.html index 2fd6be22..3b9cabd3 100644 --- a/docs/core_Mouse.js.html +++ b/docs/core_Mouse.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/Mouse.js - - - + core/Mouse.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/Mouse.js

+ + + + +
+ +

core/Mouse.js

+ @@ -31,38 +55,35 @@

Source: core/Mouse.js

* * @author Alain Pitiot * @author Sotiri Bakagiannis - isPressedIn - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from './PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; - +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { PsychoJS } from "./PsychoJS.js"; /** * <p>This manager handles the interactions between the experiment's stimuli and the mouse.</p> * <p>Note: the unit of Mouse is that of its associated Window.</p> * - * @name module:core.Mouse - * @class - * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {Window} options.win - the associated Window - * @param {boolean} [options.autoLog= true] - whether or not to log - * * @todo visible is not handled at the moment (mouse is always visible) */ export class Mouse extends PsychObject { - + /** + * @memberof module:core + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {Window} options.win - the associated Window + * @param {boolean} [options.autoLog= true] - whether or not to log + */ constructor({ - name, - win, - autoLog = true - } = {}) + name, + win, + autoLog = true, + } = {}) { super(win._psychoJS, name); @@ -73,21 +94,17 @@

Source: core/Mouse.js

const units = win.units; const visible = 1; - this._addAttribute('win', win); - this._addAttribute('units', units); - this._addAttribute('visible', visible); - this._addAttribute('autoLog', autoLog); + this._addAttribute("win", win); + this._addAttribute("units", units); + this._addAttribute("visible", visible); + this._addAttribute("autoLog", autoLog); this.status = PsychoJS.Status.NOT_STARTED; } - /** * Get the current position of the mouse in mouse/Window units. * - * @name module:core.Mouse#getPos - * @function - * @public * @return {Array.number} the position of the mouse in mouse/Window units */ getPos() @@ -101,24 +118,20 @@

Source: core/Mouse.js

pos_px[1] = this.win.size[1] / 2 - pos_px[1]; // convert to window units: - this._lastPos = util.to_win(pos_px, 'pix', this._win); + this._lastPos = util.to_win(pos_px, "pix", this._win); return this._lastPos; } - /** * Get the position of the mouse relative to that at the last call to getRel * or getPos, in mouse/Window units. * - * @name module:core.Mouse#getRel - * @function - * @public * @return {Array.number} the relation position of the mouse in mouse/Window units. */ getRel() { - if (typeof this._lastPos === 'undefined') + if (typeof this._lastPos === "undefined") { return this.getPos(); } @@ -131,16 +144,12 @@

Source: core/Mouse.js

} } - /** * Get the travel of the mouse scroll wheel since the last call to getWheelRel. * * <p>Note: Even though this method returns a [x, y] array, for most wheels/systems y is the only * value that varies.</p> * - * @name module:core.Mouse#getWheelRel - * @function - * @public * @return {Array.number} the mouse scroll wheel travel */ getWheelRel() @@ -149,21 +158,17 @@

Source: core/Mouse.js

const wheelRel_px = mouseInfo.wheelRel.slice(); // convert to window units: - const wheelRel = util.to_win(wheelRel_px, 'pix', this._win); + const wheelRel = util.to_win(wheelRel_px, "pix", this._win); mouseInfo.wheelRel = [0, 0]; return wheelRel; } - /** * Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to [clickReset]{@link module:core.Mouse#clickReset} and the pressing or releasing of the buttons. * * <p>Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.</p> * - * @name module:core.Mouse#getPressed - * @function - * @public * @param {boolean} [getTime= false] whether or not to also return timestamps * @return {Array.number | Array.<Array.number>} either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps. */ @@ -181,13 +186,9 @@

Source: core/Mouse.js

} } - /** * Helper method for checking whether a stimulus has had any button presses within bounds. * - * @name module:core.Mouse#isPressedIn - * @function - * @public * @param {object|module:visual.VisualStim} shape A type of visual stimulus or object having a `contains()` method. * @param {object|number} [buttons] The target button index potentially tucked inside an object. * @param {object} [options] @@ -198,14 +199,14 @@

Source: core/Mouse.js

isPressedIn(...args) { // Look for options given in object literal form, cut out falsy inputs - const [{ shape: shapeMaybe, buttons: buttonsMaybe } = {}] = args.filter(v => !!v); + const [{ shape: shapeMaybe, buttons: buttonsMaybe } = {}] = args.filter((v) => !!v); // Helper to check if some object features a certain key - const hasKey = key => object => !!(object && object[key]); + const hasKey = (key) => (object) => !!(object && object[key]); // Shapes are expected to be instances of stimuli, or at // the very least objects featuring a `contains()` method - const isShape = hasKey('contains'); + const isShape = hasKey("contains"); // Go through arguments array looking for a shape if options object offers none const shapeFound = isShape(shapeMaybe) ? shapeMaybe : args.find(isShape); @@ -215,23 +216,23 @@

Source: core/Mouse.js

// Buttons values may be extracted from an object // featuring the `buttons` key, or found as integers // in the arguments array - const hasButtons = hasKey('buttons'); + const hasButtons = hasKey("buttons"); const { isInteger } = Number; // Prioritize buttons value given as part of an options object, // then look for the first occurrence in the arguments array of either // an integer or an extra object with a `buttons` key - const buttonsFound = isInteger(buttonsMaybe) ? buttonsMaybe : args.find(o => hasButtons(o) || isInteger(o)); + const buttonsFound = isInteger(buttonsMaybe) ? buttonsMaybe : args.find((o) => hasButtons(o) || isInteger(o)); // Worst case scenario `wanted` ends up being an empty object const { buttons: wanted = buttonsFound || buttonsMaybe } = buttonsFound || {}; // Will throw if stimulus is falsy or non-object like - if (typeof shape.contains === 'function') + if (typeof shape.contains === "function") { const mouseInfo = this.psychoJS.eventManager.getMouseInfo(); const { pressed } = mouseInfo.buttons; // If no specific button wanted, any pressed will do - const hasButtonPressed = isInteger(wanted) ? pressed[wanted] > 0 : pressed.some(v => v > 0); + const hasButtonPressed = isInteger(wanted) ? pressed[wanted] > 0 : pressed.some((v) => v > 0); return hasButtonPressed && shape.contains(this); } @@ -239,7 +240,6 @@

Source: core/Mouse.js

return false; } - /** * Determine whether the mouse has moved beyond a certain distance. * @@ -258,9 +258,6 @@

Source: core/Mouse.js

* <li>mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance</li> * </ul></p> * - * @name module:core.Mouse#mouseMoved - * @function - * @public * @param {undefined|number|Array.number} [distance] - the distance to which the mouse movement is compared (see above for a full description) * @param {boolean|String|Array.number} [reset= false] - see above for a full description * @return {boolean} see above for a full description @@ -268,24 +265,26 @@

Source: core/Mouse.js

mouseMoved(distance, reset = false) { // make sure that _lastPos is defined: - if (typeof this._lastPos === 'undefined') + if (typeof this._lastPos === "undefined") { this.getPos(); } this._prevPos = this._lastPos.slice(); this.getPos(); - if (typeof reset === 'boolean' && reset == false) + if (typeof reset === "boolean" && reset == false) { - if (typeof distance === 'undefined') + if (typeof distance === "undefined") { return (this._prevPos[0] != this._lastPos[0]) || (this._prevPos[1] != this._lastPos[1]); } else { - if (typeof distance === 'number') + if (typeof distance === "number") { - this._movedistance = Math.sqrt((this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1])); + this._movedistance = Math.sqrt( + (this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1]), + ); return (this._movedistance > distance); } if (this._prevPos[0] + distance[0] - this._lastPos[0] > 0.0) @@ -299,21 +298,18 @@

Source: core/Mouse.js

return false; } } - - else if (typeof reset === 'boolean' && reset == true) + else if (typeof reset === "boolean" && reset == true) { // reset the moveClock: this.psychoJS.eventManager.getMouseInfo().moveClock.reset(); return false; } - - else if (reset === 'here') + else if (reset === "here") { // set to wherever we are this._prevPos = this._lastPos.clone(); return false; } - else if (reset instanceof Array) { // an (x,y) array @@ -322,42 +318,40 @@

Source: core/Mouse.js

if (!distance) { return false; - }// just resetting prevPos, not checking distance + } + // just resetting prevPos, not checking distance else { // checking distance of current pos to newly reset prevposition - if (typeof distance === 'number') + if (typeof distance === "number") { - this._movedistance = Math.sqrt((this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1])); + this._movedistance = Math.sqrt( + (this._prevPos[0] - this._lastPos[0]) * (this._prevPos[0] - this._lastPos[0]) + (this._prevPos[1] - this._lastPos[1]) * (this._prevPos[1] - this._lastPos[1]), + ); return (this._movedistance > distance); } if (Math.abs(this._lastPos[0] - this._prevPos[0]) > distance[0]) { return true; - } // moved on X-axis + } // moved on X-axis if (Math.abs(this._lastPos[1] - this._prevPos[1]) > distance[1]) { return true; - } // moved on Y-axis + } // moved on Y-axis return false; } } - else { return false; } } - /** * Get the amount of time elapsed since the last mouse movement. * - * @name module:core.Mouse#mouseMoveTime - * @function - * @public * @return {number} the time elapsed since the last mouse movement */ mouseMoveTime() @@ -365,13 +359,9 @@

Source: core/Mouse.js

return this.psychoJS.eventManager.getMouseInfo().moveClock.getTime(); } - /** * Reset the clocks associated to the given mouse buttons. * - * @name module:core.Mouse#clickReset - * @function - * @public * @param {Array.number} [buttons= [0,1,2]] the buttons to reset (0: left, 1: center, 2: right) */ clickReset(buttons = [0, 1, 2]) @@ -383,10 +373,7 @@

Source: core/Mouse.js

mouseInfo.buttons.times[b] = 0.0; } } - - } - @@ -394,19 +381,23 @@

Source: core/Mouse.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_PsychoJS.js.html b/docs/core_PsychoJS.js.html index f371059a..413033cc 100644 --- a/docs/core_PsychoJS.js.html +++ b/docs/core_PsychoJS.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/PsychoJS.js - - - + core/PsychoJS.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/PsychoJS.js

+ + + + +
+ +

core/PsychoJS.js

+ @@ -31,38 +55,29 @@

Source: core/PsychoJS.js

* Main component of the PsychoJS library. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import log4javascript from 'log4javascript'; -import {Scheduler} from '../util/Scheduler'; -import {ServerManager} from './ServerManager'; -import {ExperimentHandler} from '../data/ExperimentHandler'; -import {EventManager} from './EventManager'; -import {Window} from './Window'; -import {GUI} from './GUI'; -import {MonotonicClock} from '../util/Clock'; -import {Logger} from './Logger'; -import * as util from '../util/Util'; -// import {Shelf} from "../data/Shelf"; - +import log4javascript from "log4javascript"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { MonotonicClock } from "../util/Clock.js"; +import { Scheduler } from "../util/Scheduler.js"; +import * as util from "../util/Util.js"; +import { EventManager } from "./EventManager.js"; +import { GUI } from "./GUI.js"; +import { Logger } from "./Logger.js"; +import { ServerManager } from "./ServerManager.js"; +import { Window } from "./Window.js"; +import {Shelf} from "../data/Shelf"; /** - * <p>PsychoJS manages the lifecycle of an experiment. It initialises the PsychoJS library and its various components (e.g. the {@link ServerManager}, the {@link EventManager}), and is used by the experiment to schedule the various tasks.</p> - * - * @class - * @param {Object} options - * @param {boolean} [options.debug= true] whether or not to log debug information in the browser console - * @param {boolean} [options.collectIP= false] whether or not to collect the IP information of the participant + * <p>PsychoJS initialises the library and its various components (e.g. the [ServerManager]{@link module:core.ServerManager}, the [EventManager]{@link module:core.EventManager}), and manages + * the lifecycle of an experiment.</p> */ export class PsychoJS { - - /** - * Properties - */ get status() { return this._status; @@ -139,22 +154,22 @@

Source: core/PsychoJS.js

return this._browser; } - // get shelf() - // { - // return this._shelf; - // } - + get shelf() + { + return this._shelf; + } /** - * @constructor - * @public + * @param {Object} options + * @param {boolean} [options.debug= true] whether to log debug information in the browser console + * @param {boolean} [options.collectIP= false] whether to collect the IP information of the participant */ constructor({ - debug = true, - collectIP = false, - hosts = [], - topLevelStatus = true - } = {}) + debug = true, + collectIP = false, + hosts = [], + topLevelStatus = true, + } = {}) { // logging: this._logger = new Logger(this, (debug) ? log4javascript.Level.DEBUG : log4javascript.Level.INFO); @@ -162,7 +177,7 @@

Source: core/PsychoJS.js

// detect the browser: this._browser = util.detectBrowser(); - this.logger.info('[PsychoJS] Detected browser:', this._browser); + this.logger.info("[PsychoJS] Detected browser:", this._browser); // core clock: this._monotonicClock = new MonotonicClock(); @@ -170,12 +185,12 @@

Source: core/PsychoJS.js

// managers: this._eventManager = new EventManager(this); this._serverManager = new ServerManager({ - psychoJS: this + psychoJS: this, }); - // to be loading `configURL` files in `_configure` calls from - const hostsEvidently = new Set([...hosts, 'https://pavlovia.org/run/', 'https://run.pavlovia.org/']); - this._hosts = Array.from(hostsEvidently); + // add the pavlovia server to the list of hosts: + const hostsWithPavlovia = new Set([...hosts, "https://pavlovia.org/run/", "https://run.pavlovia.org/"]); + this._hosts = Array.from(hostsWithPavlovia); // GUI: this._gui = new GUI(this); @@ -189,8 +204,8 @@

Source: core/PsychoJS.js

// Window: this._window = undefined; - // // Shelf: - // this._shelf = new Shelf(this); + // Shelf: + this._shelf = new Shelf({psychoJS: this}); // redirection URLs: this._cancellationUrl = undefined; @@ -206,14 +221,14 @@

Source: core/PsychoJS.js

this._makeStatusTopLevel(); } - this.logger.info('[PsychoJS] Initialised.'); - this.logger.info('[PsychoJS] @version 2021.2.0'); + this.logger.info("[PsychoJS] Initialised."); + this.logger.info("[PsychoJS] @version 2022.2.1"); - // Hide #root::after - jQuery('#root').addClass('is-ready'); + // hide the initialisation message: + const root = document.getElementById("root"); + root.classList.add("is-ready"); } - /** * Get the experiment's environment. * @@ -221,14 +236,13 @@

Source: core/PsychoJS.js

*/ getEnvironment() { - if (typeof this._config === 'undefined') + if (typeof this._config === "undefined") { return undefined; } return this._config.environment; } - /** * Open a PsychoJS Window. * @@ -244,26 +258,25 @@

Source: core/PsychoJS.js

* @param {boolean} [options.waitBlanking] whether or not to wait for all rendering operations to be done * before flipping * @throws {Object.<string, *>} exception if a window has already been opened - * - * @public */ openWindow({ - name, - fullscr, - color, - units, - waitBlanking, - autoLog - } = {}) + name, + fullscr, + color, + gamma, + units, + waitBlanking, + autoLog, + } = {}) { - this.logger.info('[PsychoJS] Open Window.'); + this.logger.info("[PsychoJS] Open Window."); - if (typeof this._window !== 'undefined') + if (typeof this._window !== "undefined") { throw { - origin: 'PsychoJS.openWindow', - context: 'when opening a Window', - error: 'A Window has already been opened.' + origin: "PsychoJS.openWindow", + context: "when opening a Window", + error: "A Window has already been opened.", }; } @@ -272,13 +285,13 @@

Source: core/PsychoJS.js

name, fullscr, color, + gamma, units, waitBlanking, - autoLog + autoLog, }); } - /** * Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment. * @@ -291,22 +304,19 @@

Source: core/PsychoJS.js

this._cancellationUrl = cancellationUrl; } - /** * Schedule a task. * - * @param task - the task to be scheduled - * @param args - arguments for that task - * @public + * @param {module:util.Scheduler~Task} task - the task to be scheduled + * @param {*} args - arguments for that task */ schedule(task, args) { - this.logger.debug('schedule task: ', task.toString().substring(0, 50), '...'); + this.logger.debug("schedule task: ", task.toString().substring(0, 50), "..."); this._scheduler.add(task, args); } - /** * @callback PsychoJS.condition * @return {boolean} true if the thenScheduler is to be run, false if the elseScheduler is to be run @@ -315,18 +325,16 @@

Source: core/PsychoJS.js

* Schedule a series of task based on a condition. * * @param {PsychoJS.condition} condition - * @param {Scheduler} thenScheduler scheduler to run if the condition is true - * @param {Scheduler} elseScheduler scheduler to run if the condition is false - * @public + * @param {Scheduler} thenScheduler - scheduler to run if the condition is true + * @param {Scheduler} elseScheduler - scheduler to run if the condition is false */ scheduleCondition(condition, thenScheduler, elseScheduler) { - this.logger.debug('schedule condition: ', condition.toString().substring(0, 50), '...'); + this.logger.debug("schedule condition: ", condition.toString().substring(0, 50), "..."); this._scheduler.addConditional(condition, thenScheduler, elseScheduler); } - /** * Start the experiment. * @@ -346,14 +354,12 @@

Source: core/PsychoJS.js

* @param {string} [options.expName=UNKNOWN] - the name of the experiment * @param {Object.<string, *>} [options.expInfo] - additional information about the experiment * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @async - * @public */ - async start({configURL = 'config.json', expName = 'UNKNOWN', expInfo = {}, resources = []} = {}) + async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [], dataFileName } = {}) { this.logger.debug(); - const response = {origin: 'PsychoJS.start', context: 'when starting the experiment'}; + const response = { origin: "PsychoJS.start", context: "when starting the experiment" }; try { @@ -368,24 +374,25 @@

Source: core/PsychoJS.js

else { this._IP = { - IP: 'X', - hostname: 'X', - city: 'X', - region: 'X', - country: 'X', - location: 'X' + IP: "X", + hostname: "X", + city: "X", + region: "X", + country: "X", + location: "X", }; } // setup the experiment handler: this._experiment = new ExperimentHandler({ psychoJS: this, - extraInfo: expInfo + extraInfo: expInfo, + dataFileName }); // setup the logger: - //my.logger.console.setLevel(psychoJS.logging.WARNING); - //my.logger.server.set({'level':psychoJS.logging.WARNING, 'experimentInfo': my.expInfo}); + // my.logger.console.setLevel(psychoJS.logging.WARNING); + // my.logger.server.set({'level':psychoJS.logging.WARNING, 'experimentInfo': my.expInfo}); // if the experiment is running on the server: if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) @@ -400,54 +407,49 @@

Source: core/PsychoJS.js

event.preventDefault(); // Chrome requires returnValue to be set: - event.returnValue = ''; + event.returnValue = ""; }; - window.addEventListener('beforeunload', this.beforeunloadCallback); - + window.addEventListener("beforeunload", this.beforeunloadCallback); // when the user closes the tab or browser, we attempt to close the session, // optionally save the results, and release the WebGL context // note: we communicate with the server using the Beacon API const self = this; - window.addEventListener('unload', (event) => + window.addEventListener("unload", (event) => { - if (self._config.session.status === 'OPEN') + if (self._config.session.status === "OPEN") { // save the incomplete results if need be: if (self._config.experiment.saveIncompleteResults) { - self._experiment.save({sync: true}); + self._experiment.save({ sync: true }); } // close the session: self._serverManager.closeSession(false, true); } - if (typeof self._window !== 'undefined') + if (typeof self._window !== "undefined") { self._window.close(); } }); - } - // start the asynchronous download of resources: - await this._serverManager.prepareResources(resources); + this._serverManager.prepareResources(resources); // start the experiment: - this.logger.info('[PsychoJS] Start Experiment.'); + this.logger.info("[PsychoJS] Start Experiment."); await this._scheduler.start(); } catch (error) { // this._gui.dialog({ error: { ...response, error } }); - this._gui.dialog({error: Object.assign(response, {error})}); + this._gui.dialog({ error: Object.assign(response, { error }) }); } } - - /** * Block the experiment until the specified resources have been downloaded. * @@ -462,13 +464,12 @@

Source: core/PsychoJS.js

* local to index.html unless they are prepended with a protocol.</li> * * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @public */ waitForResources(resources = []) { const response = { - origin: 'PsychoJS.waitForResources', - context: 'while waiting for resources to be downloaded' + origin: "PsychoJS.waitForResources", + context: "while waiting for resources to be downloaded", }; try @@ -478,36 +479,30 @@

Source: core/PsychoJS.js

catch (error) { // this._gui.dialog({ error: { ...response, error } }); - this._gui.dialog({error: Object.assign(response, {error})}); + this._gui.dialog({ error: Object.assign(response, { error }) }); } } - - /** - * Make the attributes of the given object those of PsychoJS and those of - * the top level variable (e.g. window) as well. + * Make the attributes of the given object those of window, such that they become global. * - * @param {Object.<string, *>} obj the object whose attributes we will mirror - * @public + * @param {Object.<string, *>} obj the object whose attributes are to become global */ importAttributes(obj) { - this.logger.debug('import attributes from: ', util.toString(obj)); + this.logger.debug("import attributes from: ", util.toString(obj)); - if (typeof obj === 'undefined') + if (typeof obj === "undefined") { return; } for (const attribute in obj) { - // this[attribute] = obj[attribute]; window[attribute] = obj[attribute]; } } - /** * Close everything and exit nicely at the end of the experiment, * potentially redirecting to one of the URLs previously specified by setRedirectUrls. @@ -517,16 +512,15 @@

Source: core/PsychoJS.js

* * @param {Object} options * @param {string} [options.message] - optional message to be displayed in a dialog box before quitting - * @param {boolean} [options.isCompleted = false] - whether or not the participant has completed the experiment - * @async - * @public + * @param {boolean} [options.isCompleted = false] - whether the participant has completed the experiment */ - async quit({message, isCompleted = false} = {}) + async quit({ message, isCompleted = false } = {}) { - this.logger.info('[PsychoJS] Quit.'); + this.logger.info("[PsychoJS] Quit."); this._experiment.experimentEnded = true; this._status = PsychoJS.Status.FINISHED; + const isServerEnv = this.getEnvironment() === ExperimentHandler.Environment.SERVER; try { @@ -534,34 +528,38 @@

Source: core/PsychoJS.js

this._scheduler.stop(); // remove the beforeunload listener: - if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) + if (isServerEnv) { - window.removeEventListener('beforeunload', this.beforeunloadCallback); + window.removeEventListener("beforeunload", this.beforeunloadCallback); } // save the results and the logs of the experiment: - this.gui.dialog({ - warning: 'Closing the session. Please wait a few moments.', - showOK: false + this.gui.finishDialog({ + text: "Terminating the experiment. Please wait a few moments...", + nbSteps: 2 + ((isServerEnv) ? 1 : 0) }); + if (isCompleted || this._config.experiment.saveIncompleteResults) { - if (!this._serverMsg.has('__noOutput')) + if (!this._serverMsg.has("__noOutput")) { + this.gui.finishDialogNextStep("saving results"); await this._experiment.save(); + this.gui.finishDialogNextStep("saving logs"); await this._logger.flush(); } } // close the session: - if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) + if (isServerEnv) { + this.gui.finishDialogNextStep("closing the session"); await this._serverManager.closeSession(isCompleted); } // thank participant for waiting and either quit or redirect: - let text = 'Thank you for your patience.<br/><br/>'; - text += (typeof message !== 'undefined') ? message : 'Goodbye!'; + let text = "Thank you for your patience.<br/><br/>"; + text += (typeof message !== "undefined") ? message : "Goodbye!"; const self = this; this._gui.dialog({ message: text, @@ -580,30 +578,28 @@

Source: core/PsychoJS.js

this._window.closeFullScreen(); // redirect if redirection URLs have been provided: - if (isCompleted && typeof self._completionUrl !== 'undefined') + if (isCompleted && typeof self._completionUrl !== "undefined") { window.location = self._completionUrl; } - else if (!isCompleted && typeof self._cancellationUrl !== 'undefined') + else if (!isCompleted && typeof self._cancellationUrl !== "undefined") { window.location = self._cancellationUrl; } - } + }, }); } catch (error) { console.error(error); - this._gui.dialog({error}); + this._gui.dialog({ error }); } } - /** * Configure PsychoJS for the running experiment. * - * @async * @protected * @param {string} configURL - the URL of the configuration file * @param {string} name - the name of the experiment @@ -611,68 +607,67 @@

Source: core/PsychoJS.js

async _configure(configURL, name) { const response = { - origin: 'PsychoJS.configure', - context: 'when configuring PsychoJS for the experiment' + origin: "PsychoJS.configure", + context: "when configuring PsychoJS for the experiment", }; try { this.status = PsychoJS.Status.CONFIGURING; - // if the experiment is running from the pavlovia.org server, we read the configuration file: + // if the experiment is running from an approved hosts, e.e pavlovia.org, + // we read the configuration file: const experimentUrl = window.location.href; - // go through each url in allow list const isHost = this._hosts.some(url => experimentUrl.indexOf(url) === 0); if (isHost) { const serverResponse = await this._serverManager.getConfiguration(configURL); this._config = serverResponse.config; - // legacy experiments had a psychoJsManager block instead of a pavlovia block, - // and the URL pointed to https://pavlovia.org/server - if ('psychoJsManager' in this._config) + // update the configuration for legacy experiments, which had a psychoJsManager + // block instead of a pavlovia block, with URL pointing to https://pavlovia.org/server + if ("psychoJsManager" in this._config) { delete this._config.psychoJsManager; this._config.pavlovia = { - URL: 'https://pavlovia.org' + URL: "https://pavlovia.org", }; } // tests for the presence of essential blocks in the configuration: - if (!('experiment' in this._config)) + if (!("experiment" in this._config)) { - throw 'missing experiment block in configuration'; + throw "missing experiment block in configuration"; } - if (!('name' in this._config.experiment)) + if (!("name" in this._config.experiment)) { - throw 'missing name in experiment block in configuration'; + throw "missing name in experiment block in configuration"; } - if (!('fullpath' in this._config.experiment)) + if (!("fullpath" in this._config.experiment)) { - throw 'missing fullpath in experiment block in configuration'; + throw "missing fullpath in experiment block in configuration"; } - if (!('pavlovia' in this._config)) + if (!("pavlovia" in this._config)) { - throw 'missing pavlovia block in configuration'; + throw "missing pavlovia block in configuration"; } - if (!('URL' in this._config.pavlovia)) + if (!("URL" in this._config.pavlovia)) { - throw 'missing URL in pavlovia block in configuration'; + throw "missing URL in pavlovia block in configuration"; } - if (!('gitlab' in this._config)) + if (!("gitlab" in this._config)) { - throw 'missing gitlab block in configuration'; + throw "missing gitlab block in configuration"; } - if (!('projectId' in this._config.gitlab)) + if (!("projectId" in this._config.gitlab)) { - throw 'missing projectId in gitlab block in configuration'; + throw "missing projectId in gitlab block in configuration"; } this._config.environment = ExperimentHandler.Environment.SERVER; - } - else // otherwise we create an ad-hoc configuration: + else { this._config = { environment: ExperimentHandler.Environment.LOCAL, @@ -680,8 +675,8 @@

Source: core/PsychoJS.js

name, saveFormat: ExperimentHandler.SaveFormat.CSV, saveIncompleteResults: true, - keys: [] - } + keys: [], + }, }; } @@ -689,24 +684,22 @@

Source: core/PsychoJS.js

this._serverMsg = new Map(); util.getUrlParameters().forEach((value, key) => { - if (key.indexOf('__') === 0) + if (key.indexOf("__") === 0) { this._serverMsg.set(key, value); } }); - this.status = PsychoJS.Status.CONFIGURED; - this.logger.debug('configuration:', util.toString(this._config)); + this.logger.debug("configuration:", util.toString(this._config)); } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the IP information of the participant, asynchronously. * @@ -716,78 +709,94 @@

Source: core/PsychoJS.js

async _getParticipantIPInfo() { const response = { - origin: 'PsychoJS._getParticipantIPInfo', - context: 'when getting the IP information of the participant' + origin: "PsychoJS._getParticipantIPInfo", + context: "when getting the IP information of the participant", }; - this.logger.debug('getting the IP information of the participant'); + this.logger.debug("getting the IP information of the participant"); this._IP = {}; try { - const geoResponse = await jQuery.get('http://www.geoplugin.net/json.gp'); - const geoData = JSON.parse(geoResponse); + const url = "http://www.geoplugin.net/json.gp"; + const response = await fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + if (response.status !== 200) + { + throw `unable to obtain the IP of the participant: ${response.statusText}`; + } + const geoData = await response.json(); + this._IP = { IP: geoData.geoplugin_request, country: geoData.geoplugin_countryName, latitude: geoData.geoplugin_latitude, - longitude: geoData.geoplugin_longitude + longitude: geoData.geoplugin_longitude, }; - this.logger.debug('IP information of the participant: ' + util.toString(this._IP)); + this.logger.debug("IP information of the participant: " + util.toString(this._IP)); } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Capture all errors and display them in a pop-up error box. - * * @protected */ _captureErrors() { - this.logger.debug('capturing all errors and showing them in a pop up window'); + this.logger.debug("capturing all errors and showing them in a pop up window"); const self = this; - window.onerror = function (message, source, lineno, colno, error) + window.onerror = function(message, source, lineno, colno, error) { console.error(error); - document.body.setAttribute('data-error', JSON.stringify({ - message: message, - source: source, - lineno: lineno, - colno: colno, - error: error - })); - - self._gui.dialog({"error": error}); - + document.body.setAttribute( + "data-error", + JSON.stringify({ + message: message, + source: source, + lineno: lineno, + colno: colno, + error: error, + }), + ); + + self._gui.dialog({ "error": error }); + return true; }; - window.onunhandledrejection = function (error) + window.onunhandledrejection = function(error) { console.error(error?.reason); - if (error?.reason?.stack === undefined) { + if (error?.reason?.stack === undefined) + { // No stack? Error thrown by PsychoJS; stringify whole error - document.body.setAttribute('data-error', JSON.stringify(error?.reason)); - } else { + document.body.setAttribute("data-error", JSON.stringify(error?.reason)); + } + else + { // Yes stack? Error thrown by JS; stringify stack - document.body.setAttribute('data-error', JSON.stringify(error?.reason?.stack)); + document.body.setAttribute("data-error", JSON.stringify(error?.reason?.stack)); } - self._gui.dialog({error: error?.reason}); + self._gui.dialog({ error: error?.reason }); return true; }; } - /** * Make the various Status top level, in order to accommodate PsychoPy's Code Components. - * @private + * @protected */ _makeStatusTopLevel() { @@ -796,33 +805,29 @@

Source: core/PsychoJS.js

window[status] = PsychoJS.Status[status]; } } - } - /** * PsychoJS status. * * @enum {Symbol} * @readonly - * @public * * @note PsychoPy is currently moving away from STOPPED and replacing STOPPED by FINISHED. * For backward compatibility reasons, we are keeping * STOPPED in PsychoJS, but the Symbol is the same as that of FINISHED. */ PsychoJS.Status = { - NOT_CONFIGURED: Symbol.for('NOT_CONFIGURED'), - CONFIGURING: Symbol.for('CONFIGURING'), - CONFIGURED: Symbol.for('CONFIGURED'), - NOT_STARTED: Symbol.for('NOT_STARTED'), - STARTED: Symbol.for('STARTED'), - PAUSED: Symbol.for('PAUSED'), - FINISHED: Symbol.for('FINISHED'), - STOPPED: Symbol.for('FINISHED'), //Symbol.for('STOPPED') - ERROR: Symbol.for('ERROR') + NOT_CONFIGURED: Symbol.for("NOT_CONFIGURED"), + CONFIGURING: Symbol.for("CONFIGURING"), + CONFIGURED: Symbol.for("CONFIGURED"), + NOT_STARTED: Symbol.for("NOT_STARTED"), + STARTED: Symbol.for("STARTED"), + PAUSED: Symbol.for("PAUSED"), + FINISHED: Symbol.for("FINISHED"), + STOPPED: Symbol.for("FINISHED"), // Symbol.for('STOPPED') + ERROR: Symbol.for("ERROR"), }; - @@ -830,19 +835,23 @@

Source: core/PsychoJS.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_ServerManager.js.html b/docs/core_ServerManager.js.html index 2d323309..6681b714 100644 --- a/docs/core_ServerManager.js.html +++ b/docs/core_ServerManager.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/ServerManager.js - - - + core/ServerManager.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + + + -

Source: core/ServerManager.js

+
+ +

core/ServerManager.js

+ @@ -30,29 +54,24 @@

Source: core/ServerManager.js

* Manager responsible for the communication between the experiment running in the participant's browser and the pavlovia.org server. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import { Howl } from 'howler'; -import {PsychoJS} from './PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; -import {ExperimentHandler} from "../data/ExperimentHandler"; -import {MonotonicClock} from "../util/Clock"; - +import { Howl } from "howler"; +import { ExperimentHandler } from "../data/ExperimentHandler.js"; +import { Clock, MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; +import { Scheduler } from "../util/Scheduler.js"; +import { PsychoJS } from "./PsychoJS.js"; /** * <p>This manager handles all communications between the experiment running in the participant's browser and the [pavlovia.org]{@link http://pavlovia.org} server, <em>in an asynchronous manner</em>.</p> * <p>It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results, logs, and audio recordings.</p> * - * @name module:core.ServerManager - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class ServerManager extends PsychObject { @@ -60,17 +79,22 @@

Source: core/ServerManager.js

* Used to indicate to the ServerManager that all resources must be registered (and * subsequently downloaded) * - * @type {symbol} + * @type {Symbol} * @readonly * @public */ - static ALL_RESOURCES = Symbol.for('ALL_RESOURCES'); - + static ALL_RESOURCES = Symbol.for("ALL_RESOURCES"); + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {boolean} [options.autoLog= false] - whether or not to log + */ constructor({ - psychoJS, - autoLog = false - } = {}) + psychoJS, + autoLog = false, + } = {}) { super(psychoJS); @@ -79,11 +103,13 @@

Source: core/ServerManager.js

// resources is a map of <name: string, { path: string, status: ResourceStatus, data: any }> this._resources = new Map(); + this._nbLoadedResources = 0; + this._setupPreloadQueue(); - this._addAttribute('autoLog', autoLog); - this._addAttribute('status', ServerManager.Status.READY); - } + this._addAttribute("autoLog", autoLog); + this._addAttribute("status", ServerManager.Status.READY); + } /** * @typedef ServerManager.GetConfigurationPromise @@ -95,43 +121,53 @@

Source: core/ServerManager.js

/** * Read the configuration file for the experiment. * - * @name module:core.ServerManager#getConfiguration - * @function - * @public * @param {string} configURL - the URL of the configuration file - * * @returns {Promise<ServerManager.GetConfigurationPromise>} the response */ getConfiguration(configURL) { const response = { - origin: 'ServerManager.getConfiguration', - context: 'when reading the configuration file: ' + configURL + origin: "ServerManager.getConfiguration", + context: "when reading the configuration file: " + configURL, }; - this._psychoJS.logger.debug('reading the configuration file: ' + configURL); + this._psychoJS.logger.debug("reading the configuration file: " + configURL); const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - jQuery.get(configURL, 'json') - .done((config, textStatus) => + try + { + const getResponse = await fetch(configURL, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + if (getResponse.status === 404) { - // resolve({ ...response, config }); - resolve(Object.assign(response, {config})); - }) - .fail((jqXHR, textStatus, errorThrown) => + throw "the configuration file could not be found"; + } + else if (getResponse.status !== 200) { - self.setStatus(ServerManager.Status.ERROR); + throw `unable to read the configuration file: status= ${getResponse.status}`; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + // the configuration file should be valid json: + const config = await getResponse.json(); + resolve(Object.assign(response, { config })); + } + catch (error) + { + self.setStatus(ServerManager.Status.ERROR); + console.error("error:", error); - reject(Object.assign(response, {error: errorMsg})); - }); + reject(Object.assign(response, { error })); + } }); } - /** * @typedef ServerManager.OpenSessionPromise * @property {string} origin the calling method @@ -142,87 +178,88 @@

Source: core/ServerManager.js

/** * Open a session for this experiment on the remote PsychoJS manager. * - * @name module:core.ServerManager#openSession - * @function - * @public * @returns {Promise<ServerManager.OpenSessionPromise>} the response */ openSession() { const response = { - origin: 'ServerManager.openSession', - context: 'when opening a session for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.openSession", + context: "when opening a session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - - this._psychoJS.logger.debug('opening a session for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug("opening a session for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); - // prepare POST query: + // prepare a POST query: let data = {}; - if (this._psychoJS._serverMsg.has('__pilotToken')) + if (this._psychoJS._serverMsg.has("__pilotToken")) { - data.pilotToken = this._psychoJS._serverMsg.get('__pilotToken'); + data.pilotToken = this._psychoJS._serverMsg.get("__pilotToken"); } - // query pavlovia server: + // query the server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL + '/api/v2/experiments/' + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + '/sessions'; - jQuery.post(url, data, null, 'json') - .done((data, textStatus) => - { - if (!('token' in data)) - { - self.setStatus(ServerManager.Status.ERROR); - reject(Object.assign(response, {error: 'unexpected answer from server: no token'})); - // reject({...response, error: 'unexpected answer from server: no token'}); - } - if (!('experiment' in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({...response, error: 'unexpected answer from server: no experiment'}); - reject(Object.assign(response, {error: 'unexpected answer from server: no experiment'})); - } + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions`, + data, + "FORM" + ); - self._psychoJS.config.session = { - token: data.token, - status: 'OPEN' - }; - self._psychoJS.config.experiment.status = data.experiment.status2; - self._psychoJS.config.experiment.saveFormat = Symbol.for(data.experiment.saveFormat); - self._psychoJS.config.experiment.saveIncompleteResults = data.experiment.saveIncompleteResults; - self._psychoJS.config.experiment.license = data.experiment.license; - self._psychoJS.config.experiment.runMode = data.experiment.runMode; - - // secret keys for various services, e.g. Google Speech API - if ('keys' in data.experiment) - { - self._psychoJS.config.experiment.keys = data.experiment.keys; - } - else - { - self._psychoJS.config.experiment.keys = []; - } + const openSessionResponse = await postResponse.json(); - self.setStatus(ServerManager.Status.READY); - // resolve({ ...response, token: data.token, status: data.status }); - resolve(Object.assign(response, {token: data.token, status: data.status})); - }) - .fail((jqXHR, textStatus, errorThrown) => + if (postResponse.status !== 200) + { + throw ('error' in openSessionResponse) ? openSessionResponse.error : openSessionResponse; + } + if (!("token" in openSessionResponse)) { self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from the server: no token"; + } + if (!("experiment" in openSessionResponse)) + { + self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no experiment"; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + self._psychoJS.config.session = { + token: openSessionResponse.token, + status: "OPEN", + }; + const experiment = openSessionResponse.experiment; + self._psychoJS.config.experiment.status = experiment.status2; + self._psychoJS.config.experiment.saveFormat = Symbol.for(experiment.saveFormat); + self._psychoJS.config.experiment.saveIncompleteResults = experiment.saveIncompleteResults; + self._psychoJS.config.experiment.license = experiment.license; + self._psychoJS.config.experiment.runMode = experiment.runMode; + + // secret keys for various services, e.g. Google Speech API + if ("keys" in experiment) + { + self._psychoJS.config.experiment.keys = experiment.keys; + } + else + { + self._psychoJS.config.experiment.keys = []; + } - reject(Object.assign(response, {error: errorMsg})); - }); + self.setStatus(ServerManager.Status.READY); + resolve({...response, token: openSessionResponse.token, status: openSessionResponse.status }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } - /** * @typedef ServerManager.CloseSessionPromise * @property {string} origin the calling method @@ -232,9 +269,6 @@

Source: core/ServerManager.js

/** * Close the session for this experiment on the remote PsychoJS manager. * - * @name module:core.ServerManager#closeSession - * @function - * @public * @param {boolean} [isCompleted= false] - whether or not the experiment was completed * @param {boolean} [sync= false] - whether or not to communicate with the server in a synchronous manner * @returns {Promise<ServerManager.CloseSessionPromise> | void} the response @@ -242,80 +276,64 @@

Source: core/ServerManager.js

async closeSession(isCompleted = false, sync = false) { const response = { - origin: 'ServerManager.closeSession', - context: 'when closing the session for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.closeSession", + context: "when closing the session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - - this._psychoJS.logger.debug('closing the session for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("closing the session for experiment: " + this._psychoJS.config.experiment.name); this.setStatus(ServerManager.Status.BUSY); - // prepare DELETE query: - const url = this._psychoJS.config.pavlovia.URL + '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + '/sessions/' + this._psychoJS.config.session.token; - - // synchronous query the pavlovia server: + // synchronously query the pavlovia server: if (sync) { - /* This is now deprecated in most browsers. - const request = new XMLHttpRequest(); - request.open("DELETE", url, false); - request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - request.send(JSON.stringify(data)); - */ - /* This does not work in Chrome before of a CORS bug - await fetch(url, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json;charset=UTF-8' }, - body: JSON.stringify(data), - // keepalive makes it possible for the request to outlive the page (e.g. when the participant closes the tab) - keepalive: true - }); - */ + const url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + + "/sessions/" + this._psychoJS.config.session.token + "/delete"; const formData = new FormData(); - formData.append('isCompleted', isCompleted); - navigator.sendBeacon(url + '/delete', formData); - this._psychoJS.config.session.status = 'CLOSED'; + formData.append("isCompleted", isCompleted); + + navigator.sendBeacon(url, formData); + this._psychoJS.config.session.status = "CLOSED"; } // asynchronously query the pavlovia server: else { const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - jQuery.ajax({ - url, - type: 'delete', - data: {isCompleted}, - dataType: 'json' - }) - .done((data, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - self._psychoJS.config.session.status = 'CLOSED'; + try + { + const deleteResponse = await this._queryServerAPI( + "DELETE", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}`, + { isCompleted }, + "FORM" + ); - // resolve({ ...response, data }); - resolve(Object.assign(response, {data})); - }) - .fail((jqXHR, textStatus, errorThrown) => - { - self.setStatus(ServerManager.Status.ERROR); + const closeSessionResponse = await deleteResponse.json(); - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + if (deleteResponse.status !== 200) + { + throw ('error' in closeSessionResponse) ? closeSessionResponse.error : closeSessionResponse; + } - reject(Object.assign(response, {error: errorMsg})); - }); + self.setStatus(ServerManager.Status.READY); + self._psychoJS.config.session.status = "CLOSED"; + resolve({ ...response, ...closeSessionResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } } - /** * Get the value of a resource. * - * @name module:core.ServerManager#getResource - * @function - * @public * @param {string} name - name of the requested resource * @param {boolean} [errorIfNotDownloaded = false] whether or not to throw an exception if the * resource status is not DOWNLOADED @@ -326,83 +344,114 @@

Source: core/ServerManager.js

getResource(name, errorIfNotDownloaded = false) { const response = { - origin: 'ServerManager.getResource', - context: 'when getting the value of resource: ' + name + origin: "ServerManager.getResource", + context: "when getting the value of resource: " + name, }; const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { // throw { ...response, error: 'unknown resource' }; - throw Object.assign(response, {error: 'unknown resource'}); + throw Object.assign(response, { error: "unknown resource" }); } if (errorIfNotDownloaded && pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) { throw Object.assign(response, { - error: name + ' is not available for use (yet), its current status is: ' + - util.toString(pathStatusData.status) + error: name + " is not available for use (yet), its current status is: " + + util.toString(pathStatusData.status), }); } return pathStatusData.data; } - /** - * Get the status of a resource. + * Get the status of a single resource or the reduced status of an array of resources. * - * @name module:core.ServerManager#getResourceStatus - * @function - * @public - * @param {string} name of the requested resource - * @return {core.ServerManager.ResourceStatus} status of the resource - * @throws {Object.<string, *>} exception if no resource with that name has previously been registered + * <p>If an array of resources is given, getResourceStatus returns a single, reduced status + * that is the status furthest away from DOWNLOADED, with the status ordered as follow: + * ERROR (furthest from DOWNLOADED), REGISTERED, DOWNLOADING, and DOWNLOADED</p> + * <p>For example, given three resources: + * <ul> + * <li>if at least one of the resource status is ERROR, the reduced status is ERROR</li> + * <li>if at least one of the resource status is DOWNLOADING, the reduced status is DOWNLOADING</li> + * <li>if the status of all three resources is REGISTERED, the reduced status is REGISTERED</li> + * <li>if the status of all three resources is DOWNLOADED, the reduced status is DOWNLOADED</li> + * </ul> + * </p> + * + * @param {string | string[]} names names of the resources whose statuses are requested + * @return {module:core.ServerManager.ResourceStatus} status of the resource if there is only one, or reduced status otherwise + * @throws {Object.<string, *>} if at least one of the names is not that of a previously + * registered resource */ - getResourceStatus(name) + getResourceStatus(names) { const response = { - origin: 'ServerManager.getResourceStatus', - context: 'when getting the status of resource: ' + name + origin: "ServerManager.getResourceStatus", + context: `when getting the status of resource(s): ${JSON.stringify(names)}`, }; - const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + // sanity checks: + if (typeof names === 'string') { - // throw { ...response, error: 'unknown resource' }; - throw Object.assign(response, {error: 'unknown resource'}); + names = [names]; } + if (!Array.isArray(names)) + { + throw Object.assign(response, { error: "names should be either a string or an array of strings" }); + } + const statusOrder = new Map([ + [Symbol.keyFor(ServerManager.ResourceStatus.ERROR), 0], + [Symbol.keyFor(ServerManager.ResourceStatus.REGISTERED), 1], + [Symbol.keyFor(ServerManager.ResourceStatus.DOWNLOADING), 2], + [Symbol.keyFor(ServerManager.ResourceStatus.DOWNLOADED), 3] + ]); + let reducedStatus = ServerManager.ResourceStatus.DOWNLOADED; + for (const name of names) + { + const pathStatusData = this._resources.get(name); - return pathStatusData.status; - } + if (typeof pathStatusData === "undefined") + { + // throw { ...response, error: 'unknown resource' }; + throw Object.assign(response, { + error: `unable to find a previously registered resource with name: ${name}` + }); + } + // update the reduced status according to the order given by statusOrder: + if (statusOrder.get(Symbol.keyFor(pathStatusData.status)) < + statusOrder.get(Symbol.keyFor(reducedStatus))) + { + reducedStatus = pathStatusData.status; + } + } + + return reducedStatus; + } /** * Set the resource manager status. - * - * @name module:core.ServerManager#setStatus - * @function - * @public */ setStatus(status) { const response = { - origin: 'ServerManager.setStatus', - context: 'when changing the status of the server manager to: ' + util.toString(status) + origin: "ServerManager.setStatus", + context: "when changing the status of the server manager to: " + util.toString(status), }; // check status: - const statusKey = (typeof status === 'symbol') ? Symbol.keyFor(status) : null; + const statusKey = (typeof status === "symbol") ? Symbol.keyFor(status) : null; if (!statusKey) - // throw { ...response, error: 'status must be a symbol' }; - { - throw Object.assign(response, {error: 'status must be a symbol'}); + { // throw { ...response, error: 'status must be a symbol' }; + throw Object.assign(response, { error: "status must be a symbol" }); } if (!ServerManager.Status.hasOwnProperty(statusKey)) - // throw { ...response, error: 'unknown status' }; - { - throw Object.assign(response, {error: 'unknown status'}); + { // throw { ...response, error: 'unknown status' }; + throw Object.assign(response, { error: "unknown status" }); } this._status = status; @@ -413,13 +462,9 @@

Source: core/ServerManager.js

return this._status; } - /** * Reset the resource manager status to ServerManager.Status.READY. * - * @name module:core.ServerManager#resetStatus - * @function - * @public * @return {ServerManager.Status.READY} the new status */ resetStatus() @@ -427,7 +472,6 @@

Source: core/ServerManager.js

return this.setStatus(ServerManager.Status.READY); } - /** * Prepare resources for the experiment: register them with the server manager and possibly * start downloading them right away. @@ -441,19 +485,16 @@

Source: core/ServerManager.js

* <li>If resources is null, then we do not download any resources</li> * </ul> * - * @name module:core.ServerManager#prepareResources - * @param {Array.<{name: string, path: string, download: boolean} | Symbol>} [resources=[]] - the list of resources - * @function - * @public + * @param {String | Array.<{name: string, path: string, download: boolean} | String | Symbol>} [resources=[]] - the list of resources or a single resource */ async prepareResources(resources = []) { const response = { - origin: 'ServerManager.prepareResources', - context: 'when preparing resources for experiment: ' + this._psychoJS.config.experiment.name + origin: "ServerManager.prepareResources", + context: "when preparing resources for experiment: " + this._psychoJS.config.experiment.name, }; - this._psychoJS.logger.debug('preparing resources for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("preparing resources for experiment: " + this._psychoJS.config.experiment.name); try { @@ -462,19 +503,24 @@

Source: core/ServerManager.js

// register the resources: if (resources !== null) { + if (typeof resources === "string") + { + resources = [resources]; + } if (!Array.isArray(resources)) { - throw "resources should be an array of objects"; + throw "resources should be either (a) a string or (b) an array of string or objects"; } // whether all resources have been requested: - const allResources = (resources.length === 1 && resources[0] === ServerManager.ALL_RESOURCES); + const allResources = (resources.length === 1 && + resources[0] === ServerManager.ALL_RESOURCES); // if the experiment is hosted on the pavlovia.org server and // resources is [ServerManager.ALL_RESOURCES], then we register all the resources // in the "resources" sub-directory - if (this._psychoJS.config.environment === ExperimentHandler.Environment.SERVER - && allResources) + if (this._psychoJS.config.environment === ExperimentHandler.Environment.SERVER && + allResources) { // list the resources from the resources directory of the experiment on the server: const serverResponse = await this._listResources(); @@ -485,53 +531,66 @@

Source: core/ServerManager.js

{ if (!this._resources.has(name)) { - const path = serverResponse.resourceDirectory + '/' + name; + const path = serverResponse.resourceDirectory + "/" + name; this._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); - this._psychoJS.logger.debug('registered resource:', name, path); + this._psychoJS.logger.debug(`registered resource: name= ${name}, path= ${path}`); resourcesToDownload.add(name); } } } - // if the experiment is hosted locally (localhost) or if specific resources were given // then we register those specific resources, if they have not been registered already else { // we cannot ask for all resources to be registered locally, since we cannot list // them: - if (this._psychoJS.config.environment === ExperimentHandler.Environment.LOCAL - && allResources) + if (this._psychoJS.config.environment === ExperimentHandler.Environment.LOCAL && + allResources) { throw "resources must be manually specified when the experiment is running locally: ALL_RESOURCES cannot be used"; } - for (let {name, path, download} of resources) + // convert those resources that are only a string to an object with name and path: + for (let r = 0; r < resources.length; ++r) + { + const resource = resources[r]; + if (typeof resource === "string") + { + resources[r] = { + name: resource, + path: resource, + download: true + } + } + } + + for (let { name, path, download } of resources) { if (!this._resources.has(name)) { // to deal with potential CORS issues, we use the pavlovia.org proxy for resources // not hosted on pavlovia.org: - if ((path.toLowerCase().indexOf('www.') === 0 || - path.toLowerCase().indexOf('http:') === 0 || - path.toLowerCase().indexOf('https:') === 0) && - (path.indexOf('pavlovia.org') === -1)) + if ( (path.toLowerCase().indexOf("www.") === 0 || + path.toLowerCase().indexOf("http:") === 0 || + path.toLowerCase().indexOf("https:") === 0) && + (path.indexOf("pavlovia.org") === -1) ) { - path = 'https://pavlovia.org/api/v2/proxy/' + path; + path = "https://pavlovia.org/api/v2/proxy/" + path; } this._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); - this._psychoJS.logger.debug('registered resource:', name, path); + this._psychoJS.logger.debug(`registered resource: name= ${name}, path= ${path}`); // download resources by default: - if (typeof download === 'undefined' || download) + if (typeof download === "undefined" || download) { resourcesToDownload.add(name); } @@ -540,25 +599,45 @@

Source: core/ServerManager.js

} } - // download those registered resources for which download = true: - /*await*/ this._downloadResources(resourcesToDownload); + // download those registered resources for which download = true + // note: we return a Promise that will be resolved when all the resources are downloaded + if (resourcesToDownload.size === 0) + { + this.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); + + return Promise.resolve(); + } + else + { + return new Promise((resolve, reject) => + { + const uuid = this.on(ServerManager.Event.RESOURCE, (signal) => + { + if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED) + { + this.off(ServerManager.Event.RESOURCE, uuid); + resolve(); + } + }); + + this._downloadResources(resourcesToDownload); + }); + } } catch (error) { - console.log('error', error); - throw Object.assign(response, {error}); + console.error("error", error); + throw Object.assign(response, { error }); // throw { ...response, error: error }; } } - /** * Block the experiment until the specified resources have been downloaded. * - * @name module:core.ServerManager#waitForResources * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @function - * @public */ waitForResources(resources = []) { @@ -566,11 +645,11 @@

Source: core/ServerManager.js

this._waitForDownloadComponent = { status: PsychoJS.Status.NOT_STARTED, clock: new Clock(), - resources: new Set() + resources: new Set(), }; const self = this; - return () => + return async () => { const t = self._waitForDownloadComponent.clock.getTime(); @@ -583,75 +662,77 @@

Source: core/ServerManager.js

// if resources is an empty array, we consider all registered resources: if (resources.length === 0) { - for (const [name, {status, path, data}] of this._resources) + for (const [name, { status, path, data }] of this._resources) { - resources.append({ name, path }); + resources.push({ name, path }); } } - // only download those resources not already downloaded or downloading: + // only download those resources not already downloaded and not downloading: const resourcesToDownload = new Set(); - for (let {name, path} of resources) + for (let { name, path } of resources) { // to deal with potential CORS issues, we use the pavlovia.org proxy for resources // not hosted on pavlovia.org: - if ( (path.toLowerCase().indexOf('www.') === 0 || - path.toLowerCase().indexOf('http:') === 0 || - path.toLowerCase().indexOf('https:') === 0) && - (path.indexOf('pavlovia.org') === -1) ) + if ( + (path.toLowerCase().indexOf("www.") === 0 + || path.toLowerCase().indexOf("http:") === 0 + || path.toLowerCase().indexOf("https:") === 0) + && (path.indexOf("pavlovia.org") === -1) + ) { - path = 'https://devlovia.org/api/v2/proxy/' + path; + path = "https://devlovia.org/api/v2/proxy/" + path; } const pathStatusData = this._resources.get(name); // the resource has not been registered yet: - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { self._resources.set(name, { status: ServerManager.ResourceStatus.REGISTERED, path, - data: undefined + data: undefined, }); self._waitForDownloadComponent.resources.add(name); resourcesToDownload.add(name); - self._psychoJS.logger.debug('registered resource:', name, path); + self._psychoJS.logger.debug("registered resource:", name, path); } // the resource has been registered but is not downloaded yet: else if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) - // else if (typeof pathStatusData.data === 'undefined') - { + { // else if (typeof pathStatusData.data === 'undefined') self._waitForDownloadComponent.resources.add(name); } - } + self._waitForDownloadComponent.status = PsychoJS.Status.STARTED; + // start the download: self._downloadResources(resourcesToDownload); } - // check whether all resources have been downloaded: - for (const name of self._waitForDownloadComponent.resources) + if (self._waitForDownloadComponent.status === PsychoJS.Status.STARTED) { - const pathStatusData = this._resources.get(name); - - // the resource has not been downloaded yet: loop this component - if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) - // if (typeof pathStatusData.data === 'undefined') + // check whether all resources have been downloaded: + for (const name of self._waitForDownloadComponent.resources) { - return Scheduler.Event.FLIP_REPEAT; + const pathStatusData = this._resources.get(name); + + // the resource has not been downloaded yet: loop this component + if (pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED) + { // if (typeof pathStatusData.data === 'undefined') + return Scheduler.Event.FLIP_REPEAT; + } } - } - // all resources have been downloaded: move to the next component: - self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED; - return Scheduler.Event.NEXT; + // all resources have been downloaded: move to the next component: + self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED; + return Scheduler.Event.NEXT; + } }; - } - /** * @typedef ServerManager.UploadDataPromise * @property {string} origin the calling method @@ -661,76 +742,69 @@

Source: core/ServerManager.js

/** * Asynchronously upload experiment data to the pavlovia server. * - * @name module:core.ServerManager#uploadData - * @function - * @public * @param {string} key - the data key (e.g. the name of .csv file) * @param {string} value - the data value (e.g. a string containing the .csv header and records) * @param {boolean} [sync= false] - whether or not to communicate with the server in a synchronous manner - * * @returns {Promise<ServerManager.UploadDataPromise>} the response */ uploadData(key, value, sync = false) { const response = { - origin: 'ServerManager.uploadData', - context: 'when uploading participant\'s results for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadData", + context: "when uploading participant's results for experiment: " + this._psychoJS.config.experiment.fullpath, }; + this._psychoJS.logger.debug("uploading data for experiment: " + this._psychoJS.config.experiment.fullpath); - this._psychoJS.logger.debug('uploading data for experiment: ' + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + - '/sessions/' + this._psychoJS.config.session.token + - '/results'; + const path = `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}/results`; - // synchronous query the pavlovia server: + // synchronously query the pavlovia server: if (sync) { const formData = new FormData(); - formData.append('key', key); - formData.append('value', value); - navigator.sendBeacon(url, formData); + formData.append("key", key); + formData.append("value", value); + navigator.sendBeacon(`${this._psychoJS.config.pavlovia.URL}/api/v2/${path}`, formData); } // asynchronously query the pavlovia server: else { const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const data = { - key, - value - }; + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}/results`, + { key, value }, + "FORM" + ); - jQuery.post(url, data, null, 'json') - .done((serverData, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, {serverData})); - }) - .fail((jqXHR, textStatus, errorThrown) => - { - self.setStatus(ServerManager.Status.ERROR); + const uploadDataResponse = await postResponse.json(); - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + if (postResponse.status !== 200) + { + throw ('error' in uploadDataResponse) ? uploadDataResponse.error : uploadDataResponse; + } - reject(Object.assign(response, {error: errorMsg})); - }); + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, ...uploadDataResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } } - - /** * Asynchronously upload experiment logs to the pavlovia server. * - * @name module:core.ServerManager#uploadLog - * @function - * @public * @param {string} logs - the base64 encoded, compressed, formatted logs * @param {boolean} [compressed=false] - whether or not the logs are compressed * @returns {Promise<ServerManager.UploadDataPromise>} the response @@ -738,19 +812,17 @@

Source: core/ServerManager.js

uploadLog(logs, compressed = false) { const response = { - origin: 'ServerManager.uploadLog', - context: 'when uploading participant\'s log for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadLog", + context: "when uploading participant's log for experiment: " + this._psychoJS.config.experiment.fullpath, }; + this._psychoJS.logger.debug("uploading server log for experiment: " + this._psychoJS.config.experiment.fullpath); - this._psychoJS.logger.debug('uploading server log for experiment: ' + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); - // prepare the POST query: + // prepare a POST query: const info = this.psychoJS.experiment.extraInfo; - const participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); - const filename = participant + '_' + experimentName + '_' + datetime + '.log'; + const filenameWithoutPath = this.psychoJS.experiment.dataFileName.split(/[\\/]/).pop(); + const filename = `${filenameWithoutPath}.log`; const data = { filename, logs, @@ -759,356 +831,327 @@

Source: core/ServerManager.js

// query the pavlovia server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = self._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(self._psychoJS.config.experiment.fullpath) + - '/sessions/' + self._psychoJS.config.session.token + - '/logs'; + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${self._psychoJS.config.session.token}/logs`, + data, + "FORM" + ); - jQuery.post(url, data, null, 'json') - .done((serverData, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, {serverData})); - }) - .fail((jqXHR, textStatus, errorThrown) => + const uploadLogsResponse = await postResponse.json(); + + if (postResponse.status !== 200) { - self.setStatus(ServerManager.Status.ERROR); + throw ('error' in uploadLogsResponse) ? uploadLogsResponse.error : uploadLogsResponse; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); + self.setStatus(ServerManager.Status.READY); + resolve({...response, ...uploadLogsResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } - reject(Object.assign(response, {error: errorMsg})); - }); }); } - - /** - * Asynchronously upload audio data to the pavlovia server. + * Synchronously or asynchronously upload audio data to the pavlovia server. * - * @name module:core.ServerManager#uploadAudio - * @function - * @public - * @param {Blob} audioBlob - the audio blob to be uploaded - * @param {string} tag - additional tag + * @param @param {Object} options + * @param {Blob} options.mediaBlob - the audio or video blob to be uploaded + * @param {string} options.tag - additional tag + * @param {boolean} [options.waitForCompletion=false] - whether or not to wait for completion + * before returning + * @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server + * @param {string} [options.dialogMsg="Please wait a few moments while the data is uploading to the server"] - default message informing the participant to wait for the data to be uploaded to the server * @returns {Promise<ServerManager.UploadDataPromise>} the response */ - async uploadAudio(audioBlob, tag) + async uploadAudioVideo({mediaBlob, tag, waitForCompletion = false, showDialog = false, dialogMsg = "Please wait a few moments while the data is uploading to the server"}) { const response = { - origin: 'ServerManager.uploadAudio', - context: 'when uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager.uploadAudio", + context: "when uploading media data for experiment: " + this._psychoJS.config.experiment.fullpath, }; try { - if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || - this._psychoJS.config.experiment.status !== 'RUNNING' || - this._psychoJS._serverMsg.has('__pilotToken')) + if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER + || this._psychoJS.config.experiment.status !== "RUNNING" + || this._psychoJS._serverMsg.has("__pilotToken")) { - throw 'audio recordings can only be uploaded to the server for experiments running on the server'; + throw "media recordings can only be uploaded to the server for experiments running on the server"; } - this._psychoJS.logger.debug('uploading audio data for experiment: ' + this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug(`uploading media data for experiment: ${this._psychoJS.config.experiment.fullpath}`); this.setStatus(ServerManager.Status.BUSY); + // open pop-up dialog: + if (showDialog) + { + this.psychoJS.gui.dialog({ + warning: dialogMsg, + showOK: false, + }); + } + // prepare the request: const info = this.psychoJS.experiment.extraInfo; - const participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); - const filename = participant + '_' + experimentName + '_' + datetime + '_' + tag; + const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); + const experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name; + const datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr()); + const filename = participant + "_" + experimentName + "_" + datetime + "_" + tag; const formData = new FormData(); - formData.append('audio', audioBlob, filename); - - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + this._psychoJS.config.gitlab.projectId + - '/sessions/' + this._psychoJS.config.session.token + - '/audio'; - - // query the pavlovia server: - const response = await fetch(url, { - method: 'POST', - mode: 'cors', // no-cors, *cors, same-origin - cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached - credentials: 'same-origin', // include, *same-origin, omit - redirect: 'follow', // manual, *follow, error - referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - body: formData + formData.append("media", mediaBlob, filename); + + let url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + + "/sessions/" + this._psychoJS.config.session.token + + "/media"; + + // query the server: + let response = await fetch(url, { + method: "POST", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer", + body: formData, }); - const jsonResponse = await response.json(); + const postMediaResponse = await response.json(); + this._psychoJS.logger.debug(`post media response: ${JSON.stringify(postMediaResponse)}`); // deal with server errors: if (!response.ok) { - throw jsonResponse; + throw postMediaResponse; + } + + // wait until the upload has completed: + if (waitForCompletion) + { + if (!("uploadToken" in postMediaResponse)) + { + throw "incorrect server response: missing uploadToken"; + } + const uploadToken = postMediaResponse['uploadToken']; + + while (true) + { + // wait a bit: + await new Promise(r => + { + setTimeout(r, 1000); + }); + + // check the status of the upload: + url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + + "/sessions/" + this._psychoJS.config.session.token + + "/media/" + uploadToken + "/status"; + + response = await fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + const checkStatusResponse = await response.json(); + this._psychoJS.logger.debug(`check upload status response: ${JSON.stringify(checkStatusResponse)}`); + + if (("status" in checkStatusResponse) && checkStatusResponse["status"] === "COMPLETED") + { + break; + } + } + } + + if (showDialog) + { + this.psychoJS.gui.closeDialog(); } this.setStatus(ServerManager.Status.READY); - return jsonResponse; + return postMediaResponse; } catch (error) { this.setStatus(ServerManager.Status.ERROR); console.error(error); - throw {...response, error}; + throw { ...response, error }; } - } - - /** * List the resources available to the experiment. - - * @name module:core.ServerManager#_listResources - * @function - * @private + * + * @protected */ _listResources() { const response = { - origin: 'ServerManager._listResourcesSession', - context: 'when listing the resources for experiment: ' + this._psychoJS.config.experiment.fullpath + origin: "ServerManager._listResourcesSession", + context: "when listing the resources for experiment: " + this._psychoJS.config.experiment.fullpath, }; - - this._psychoJS.logger.debug('listing the resources for experiment: ' + - this._psychoJS.config.experiment.fullpath); + this._psychoJS.logger.debug(`listing the resources for experiment: ${this._psychoJS.config.experiment.fullpath}`); this.setStatus(ServerManager.Status.BUSY); - // prepare GET data: + // prepare a GET query: const data = { - 'token': this._psychoJS.config.session.token + "token": this._psychoJS.config.session.token, }; - // query pavlovia server: + // query the server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL + - '/api/v2/experiments/' + encodeURIComponent(this._psychoJS.config.experiment.fullpath) + - '/resources'; + try + { + const getResponse = await this._queryServerAPI( + "GET", + `experiments/${this._psychoJS.config.gitlab.projectId}/resources`, + data + ); - jQuery.get(url, data, null, 'json') - .done((data, textStatus) => - { - if (!('resources' in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({ ...response, error: 'unexpected answer from server: no resources' }); - reject(Object.assign(response, {error: 'unexpected answer from server: no resources'})); - } - if (!('resourceDirectory' in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({ ...response, error: 'unexpected answer from server: no resourceDirectory' }); - reject(Object.assign(response, {error: 'unexpected answer from server: no resourceDirectory'})); - } + const getResourcesResponse = await getResponse.json(); - self.setStatus(ServerManager.Status.READY); - // resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory }); - resolve(Object.assign(response, { - resources: data.resources, - resourceDirectory: data.resourceDirectory - })); - }) - .fail((jqXHR, textStatus, errorThrown) => + if (!("resources" in getResourcesResponse)) { self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no resources"; + } + if (!("resourceDirectory" in getResourcesResponse)) + { + self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no resourceDirectory"; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error('error:', errorMsg); - - reject(Object.assign(response, {error: errorMsg})); - }); + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); - } - - /** * Download the specified resources. * * <p>Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.</p> * - * @name module:core.ServerManager#_downloadResources - * @function * @protected * @param {Set} resources - a set of names of previously registered resources */ - _downloadResources(resources) + async _downloadResources(resources) { const response = { - origin: 'ServerManager._downloadResources', - context: 'when downloading resources for experiment: ' + this._psychoJS.config.experiment.name + origin: "ServerManager._downloadResources", + context: "when downloading resources for experiment: " + this._psychoJS.config.experiment.name, }; - this._psychoJS.logger.debug('downloading resources for experiment: ' + this._psychoJS.config.experiment.name); + this._psychoJS.logger.debug("downloading resources for experiment: " + this._psychoJS.config.experiment.name); this.setStatus(ServerManager.Status.BUSY); this.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCES, - count: resources.size - }); - - this._nbLoadedResources = 0; - - - // (*) set-up preload.js: - this._resourceQueue = new createjs.LoadQueue(true, '', true); - - const self = this; - - // the loading of a specific resource has started: - this._resourceQueue.addEventListener("filestart", event => - { - const pathStatusData = self._resources.get(event.item.id); - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; - - self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOADING_RESOURCE, - resource: event.item.id - }); - }); - - // the loading of a specific resource has completed: - this._resourceQueue.addEventListener("fileload", event => - { - const pathStatusData = self._resources.get(event.item.id); - pathStatusData.data = event.result; - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; - - ++ self._nbLoadedResources; - self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.RESOURCE_DOWNLOADED, - resource: event.item.id - }); - }); - - // the loading of all given resources completed: - this._resourceQueue.addEventListener("complete", event => - { - self._resourceQueue.close(); - if (self._nbLoadedResources === resources.size) - { - self.setStatus(ServerManager.Status.READY); - self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED - }); - } + count: resources.size, }); - // error: we throw an exception - this._resourceQueue.addEventListener("error", event => - { - self.setStatus(ServerManager.Status.ERROR); - if (typeof event.item !== 'undefined') - { - const pathStatusData = self._resources.get(event.item.id); - pathStatusData.status = ServerManager.ResourceStatus.ERROR; - throw Object.assign(response, { - error: 'unable to download resource: ' + event.item.id + ' (' + event.title + ')' - }); - } - else - { - console.error(event); - - if (event.title === 'FILE_LOAD_ERROR' && typeof event.data !== 'undefined') - { - const id = event.data.id; - const title = event.data.src; - - throw Object.assign(response, { - error: 'unable to download resource: ' + id + ' (' + title + ')' - }); - } - - else - { - throw Object.assign(response, { - error: 'unspecified download error' - }); - } - - } - }); - - - // (*) dispatch resources to preload.js or howler.js based on extension: - let manifest = []; + // based on the resource extension either (a) add it to the preload manifest, (b) mark it for + // download by howler, or (c) add it to the document fonts + const preloadManifest = []; const soundResources = new Set(); + const fontResources = []; for (const name of resources) { - const nameParts = name.toLowerCase().split('.'); + const nameParts = name.toLowerCase().split("."); const extension = (nameParts.length > 1) ? nameParts.pop() : undefined; // warn the user if the resource does not have any extension: - if (typeof extension === 'undefined') + if (typeof extension === "undefined") { this.psychoJS.logger.warn(`"${name}" does not appear to have an extension, which may negatively impact its loading. We highly recommend you add an extension.`); } const pathStatusData = this._resources.get(name); - if (typeof pathStatusData === 'undefined') + if (typeof pathStatusData === "undefined") { - throw Object.assign(response, {error: name + ' has not been previously registered'}); + throw Object.assign(response, { error: name + " has not been previously registered" }); } if (pathStatusData.status !== ServerManager.ResourceStatus.REGISTERED) { - throw Object.assign(response, {error: name + ' is already downloaded or is currently already downloading'}); + throw Object.assign(response, { error: name + " is already downloaded or is currently already downloading" }); } - // preload.js with forced binary for xls and xlsx: - if (['csv', 'odp', 'xls', 'xlsx'].indexOf(extension) > -1) + const pathParts = pathStatusData.path.toLowerCase().split("."); + const pathExtension = (pathParts.length > 1) ? pathParts.pop() : undefined; + + // preload.js with forced binary: + if (["csv", "odp", "xls", "xlsx", "json"].indexOf(extension) > -1) { - manifest.push(/*new createjs.LoadItem().set(*/{ + preloadManifest.push(/*new createjs.LoadItem().set(*/ { id: name, src: pathStatusData.path, type: createjs.Types.BINARY, - crossOrigin: 'Anonymous' - }/*)*/); + crossOrigin: "Anonymous", + } /*)*/); } - /* ascii .csv are adequately handled in binary format + + /* note: ascii .csv are adequately handled in binary format, no need to treat them separately // forced text for .csv: else if (['csv'].indexOf(resourceExtension) > -1) manifest.push({ id: resourceName, src: resourceName, type: createjs.Types.TEXT }); */ - // sound files are loaded through howler.js: - else if (['mp3', 'mpeg', 'opus', 'ogg', 'oga', 'wav', 'aac', 'caf', 'm4a', 'weba', 'dolby', 'flac'].indexOf(extension) > -1) + // sound files: + else if (["mp3", "mpeg", "opus", "ogg", "oga", "wav", "aac", "caf", "m4a", "weba", "dolby", "flac"].indexOf(extension) > -1) { soundResources.add(name); - if (extension === 'wav') + if (extension === "wav") { this.psychoJS.logger.warn(`wav files are not supported by all browsers. We recommend you convert "${name}" to another format, e.g. mp3`); } } - // preload.js for the other extensions (download type decided by preload.js): + // font files + else if (["ttf", "otf", "woff", "woff2"].indexOf(pathExtension) > -1) + { + fontResources.push(name); + } + + // all other extensions handled by preload.js (download type decided by preload.js): else { - manifest.push(/*new createjs.LoadItem().set(*/{ + preloadManifest.push(/*new createjs.LoadItem().set(*/ { id: name, src: pathStatusData.path, - crossOrigin: 'Anonymous' - }/*)*/); + crossOrigin: "Anonymous", + } /*)*/); } } - - // (*) start loading non-sound resources: - if (manifest.length > 0) + // start loading resources marked for preload.js: + if (preloadManifest.length > 0) { - this._resourceQueue.loadManifest(manifest); + this._preloadQueue.loadManifest(preloadManifest); } else { @@ -1116,156 +1159,353 @@

Source: core/ServerManager.js

{ this.setStatus(ServerManager.Status.READY); this.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED}); + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); } } + // start loading fonts: + for (const name of fontResources) + { + const pathStatusData = this._resources.get(name); + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + this.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOADING_RESOURCE, + resource: name, + }); + + const pathExtension = pathStatusData.path.toLowerCase().split(".").pop(); + try + { + const newFont = await new FontFace(name, `url('${pathStatusData.path}') format('${pathExtension}')`).load(); + document.fonts.add(newFont); + + ++this._nbLoadedResources; + + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + this.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.RESOURCE_DOWNLOADED, + resource: name, + }); - // (*) prepare and start loading sound resources: + if (this._nbLoadedResources === resources.size) + { + this.setStatus(ServerManager.Status.READY); + this.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); + } + } + catch (error) + { + console.error(error); + this.setStatus(ServerManager.Status.ERROR); + pathStatusData.status = ServerManager.ResourceStatus.ERROR; + throw Object.assign(response, { + error: `unable to download resource: ${name}: ${error}` + }); + } + } + + // start loading resources marked for howler.js: + const self = this; for (const name of soundResources) { const pathStatusData = this._resources.get(name); - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; this.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCE, - resource: name + resource: name, }); const howl = new Howl({ src: pathStatusData.path, preload: false, - autoplay: false + autoplay: false, }); - howl.on('load', (event) => + howl.on("load", (event) => { - ++ self._nbLoadedResources; + ++self._nbLoadedResources; pathStatusData.data = howl; - pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCE_DOWNLOADED, - resource: name + resource: name, }); if (self._nbLoadedResources === resources.size) { self.setStatus(ServerManager.Status.READY); self.emit(ServerManager.Event.RESOURCE, { - message: ServerManager.Event.DOWNLOAD_COMPLETED}); + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); } }); - howl.on('loaderror', (id, error) => + howl.on("loaderror", (id, error) => { // throw { ...response, error: 'unable to download resource: ' + name + ' (' + util.toString(error) + ')' }; - throw Object.assign(response, {error: 'unable to download resource: ' + name + ' (' + util.toString(error) + ')'}); + throw Object.assign(response, { error: "unable to download resource: " + name + " (" + util.toString(error) + ")" }); }); howl.load(); } + } + /** + * Setup the preload.js queue, and the associated callbacks. + * + * @protected + */ + _setupPreloadQueue() + { + const response = { + origin: "ServerManager._setupPreloadQueue", + context: "when setting up a preload queue" + }; + + this._preloadQueue = new createjs.LoadQueue(true, "", true); + + const self = this; + + // the loading of a specific resource has started: + this._preloadQueue.addEventListener("filestart", (event) => + { + const pathStatusData = self._resources.get(event.item.id); + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + + self.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOADING_RESOURCE, + resource: event.item.id, + }); + }); + + // the loading of a specific resource has completed: + this._preloadQueue.addEventListener("fileload", (event) => + { + const pathStatusData = self._resources.get(event.item.id); + pathStatusData.data = event.result; + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + + ++self._nbLoadedResources; + self.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.RESOURCE_DOWNLOADED, + resource: event.item.id, + }); + }); + + // the loading of all given resources completed: + this._preloadQueue.addEventListener("complete", (event) => + { + self._preloadQueue.close(); + if (self._nbLoadedResources === self._resources.size) + { + self.setStatus(ServerManager.Status.READY); + self.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); + } + }); + + // error: we throw an exception + this._preloadQueue.addEventListener("error", (event) => + { + self.setStatus(ServerManager.Status.ERROR); + if (typeof event.item !== "undefined") + { + const pathStatusData = self._resources.get(event.item.id); + pathStatusData.status = ServerManager.ResourceStatus.ERROR; + throw Object.assign(response, { + error: "unable to download resource: " + event.item.id + " (" + event.title + ")", + }); + } + else + { + console.error(event); + + if (event.title === "FILE_LOAD_ERROR" && typeof event.data !== "undefined") + { + const id = event.data.id; + const title = event.data.src; + + throw Object.assign(response, { + error: "unable to download resource: " + id + " (" + title + ")", + }); + } + else + { + throw Object.assign(response, { + error: "unspecified download error", + }); + } + } + }); } -} + /** + * Query the pavlovia server API. + * + * @protected + * @param method the HTTP method, i.e. GET, PUT, POST, or DELETE + * @param path the resource path, without the server address + * @param data the data to be sent + * @param {string} [contentType="JSON"] the content type, either JSON or FORM + */ + _queryServerAPI(method, path, data, contentType = "JSON") + { + const fullPath = `${this._psychoJS.config.pavlovia.URL}/api/v2/${path}`; + + if (method === "PUT" || method === "POST" || method === "DELETE") + { + if (contentType === "JSON") + { + return fetch(fullPath, { + method, + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + } + else + { + const formData = new FormData(); + for (const attribute in data) + { + formData.append(attribute, data[attribute]); + } + return fetch(fullPath, { + method, + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: formData + }); + } + } + + if (method === "GET") + { + let url = new URL(fullPath); + url.search = new URLSearchParams(data).toString(); + + return fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + } + + throw { + origin: "ServerManager._queryServer", + context: "when querying the server", + error: "the method should be GET, PUT, POST, or DELETE" + }; + } + +} /** * Server event * * <p>A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).</p> * - * @name module:core.ServerManager#Event * @enum {Symbol} * @readonly - * @public */ ServerManager.Event = { /** * Event type: resource event */ - RESOURCE: Symbol.for('RESOURCE'), + RESOURCE: Symbol.for("RESOURCE"), /** * Event: resources have started to download */ - DOWNLOADING_RESOURCES: Symbol.for('DOWNLOADING_RESOURCES'), + DOWNLOADING_RESOURCES: Symbol.for("DOWNLOADING_RESOURCES"), /** * Event: a specific resource download has started */ - DOWNLOADING_RESOURCE: Symbol.for('DOWNLOADING_RESOURCE'), + DOWNLOADING_RESOURCE: Symbol.for("DOWNLOADING_RESOURCE"), /** * Event: a specific resource has been downloaded */ - RESOURCE_DOWNLOADED: Symbol.for('RESOURCE_DOWNLOADED'), + RESOURCE_DOWNLOADED: Symbol.for("RESOURCE_DOWNLOADED"), /** * Event: resources have all downloaded */ - DOWNLOADS_COMPLETED: Symbol.for('DOWNLOAD_COMPLETED'), + DOWNLOAD_COMPLETED: Symbol.for("DOWNLOAD_COMPLETED"), /** * Event type: status event */ - STATUS: Symbol.for('STATUS') + STATUS: Symbol.for("STATUS"), }; - /** * Server status * - * @name module:core.ServerManager#Status * @enum {Symbol} * @readonly - * @public */ ServerManager.Status = { /** * The manager is ready. */ - READY: Symbol.for('READY'), + READY: Symbol.for("READY"), /** * The manager is busy, e.g. it is downloaded resources. */ - BUSY: Symbol.for('BUSY'), + BUSY: Symbol.for("BUSY"), /** * The manager has encountered an error, e.g. it was unable to download a resource. */ - ERROR: Symbol.for('ERROR') + ERROR: Symbol.for("ERROR"), }; - /** * Resource status * - * @name module:core.ServerManager#ResourceStatus * @enum {Symbol} * @readonly - * @public */ ServerManager.ResourceStatus = { /** - * The resource has been registered. + * There was an error during downloading, or the resource is in an unknown state. */ - REGISTERED: Symbol.for('REGISTERED'), + ERROR: Symbol.for("ERROR"), /** - * The resource is currently downloading. + * The resource has been registered. */ - DOWNLOADING: Symbol.for('DOWNLOADING'), + REGISTERED: Symbol.for("REGISTERED"), /** - * The resource has been downloaded. + * The resource is currently downloading. */ - DOWNLOADED: Symbol.for('DOWNLOADED'), + DOWNLOADING: Symbol.for("DOWNLOADING"), /** - * There was an error during downloading, or the resource is in an unknown state. + * The resource has been downloaded. */ - ERROR: Symbol.for('ERROR'), + DOWNLOADED: Symbol.for("DOWNLOADED"), }; @@ -1274,19 +1514,23 @@

Source: core/ServerManager.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_Window.js.html b/docs/core_Window.js.html index 1587c106..b3c6b261 100644 --- a/docs/core_Window.js.html +++ b/docs/core_Window.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/Window.js - - - + core/Window.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/Window.js

+ + + + +
+ +

core/Window.js

+ @@ -30,37 +54,26 @@

Source: core/Window.js

* Window responsible for displaying the experiment stimuli * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color'; -import {PsychObject} from '../util/PsychObject'; -import {MonotonicClock} from '../util/Clock'; -import {Logger} from "./Logger"; +import * as PIXI from "pixi.js-legacy"; +import {AdjustmentFilter} from "@pixi/filter-adjustment"; +import { MonotonicClock } from "../util/Clock.js"; +import { Color } from "../util/Color.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { Logger } from "./Logger.js"; /** * <p>Window displays the various stimuli of the experiment.</p> * <p>It sets up a [PIXI]{@link http://www.pixijs.com/} renderer, which we use to render the experiment stimuli.</p> * - * @name module:core.Window - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} [options.name] the name of the window - * @param {boolean} [options.fullscr= false] whether or not to go fullscreen - * @param {Color} [options.color= Color('black')] the background color of the window - * @param {string} [options.units= 'pix'] the units of the window - * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done - * before flipping - * @param {boolean} [options.autoLog= true] whether or not to log */ export class Window extends PsychObject { - /** * Getter for monitorFramePeriod. * @@ -73,31 +86,62 @@

Source: core/Window.js

return 1.0 / this.getActualFrameRate(); } + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} [options.name] the name of the window + * @param {boolean} [options.fullscr= false] whether or not to go fullscreen + * @param {Color} [options.color= Color('black')] the background color of the window + * @param {number} [options.gamma= 1] sets the divisor for gamma correction. In other words gamma correction is calculated as pow(rgb, 1/gamma) + * @param {number} [options.contrast= 1] sets the contrast value + * @param {string} [options.units= 'pix'] the units of the window + * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done + * before flipping + * @param {boolean} [options.autoLog= true] whether or not to log + */ constructor({ - psychoJS, - name, - fullscr = false, - color = new Color('black'), - units = 'pix', - waitBlanking = false, - autoLog = true - } = {}) + psychoJS, + name, + fullscr = false, + color = new Color("black"), + gamma = 1, + contrast = 1, + units = "pix", + waitBlanking = false, + autoLog = true, + } = {}) { super(psychoJS, name); // messages to be logged at the next "flip": this._msgToBeLogged = []; + // storing AdjustmentFilter instance to access later; + this._adjustmentFilter = new AdjustmentFilter({ + gamma, + contrast + }); + // list of all elements, in the order they are currently drawn: this._drawList = []; - this._addAttribute('fullscr', fullscr); - this._addAttribute('color', color); - this._addAttribute('units', units); - this._addAttribute('waitBlanking', waitBlanking); - this._addAttribute('autoLog', autoLog); - this._addAttribute('size', []); - + this._addAttribute("fullscr", fullscr); + this._addAttribute("color", color, new Color("black"), () => { + if (this._backgroundSprite) { + this._backgroundSprite.tint = this._color.int; + } + }); + this._addAttribute("gamma", gamma, 1, () => { + this._adjustmentFilter.gamma = this._gamma; + }); + this._addAttribute("contrast", contrast, 1, () => { + this._adjustmentFilter.contrast = this._contrast; + }); + this._addAttribute("units", units); + this._addAttribute("waitBlanking", waitBlanking); + this._addAttribute("autoLog", autoLog); + this._addAttribute("size", []); // setup PIXI: this._setupPixi(); @@ -106,15 +150,14 @@

Source: core/Window.js

this._flipCallbacks = []; - // fullscreen listener: this._windowAlreadyInFullScreen = false; const self = this; - document.addEventListener('fullscreenchange', (event) => + document.addEventListener("fullscreenchange", (event) => { self._windowAlreadyInFullScreen = !!document.fullscreenElement; - console.log('windowAlreadyInFullScreen:', self._windowAlreadyInFullScreen); + console.log("windowAlreadyInFullScreen:", self._windowAlreadyInFullScreen); // the Window and all of the stimuli need to be updated: self._needUpdate = true; @@ -124,22 +167,16 @@

Source: core/Window.js

} }); - if (this._autoLog) { this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); } } - /** * Close the window. * * <p> Note: this actually only removes the canvas used to render the experiment stimuli.</p> - * - * @name module:core.Window#close - * @function - * @public */ close() { @@ -148,33 +185,31 @@

Source: core/Window.js

return; } + this._rootContainer.destroy(); + if (document.body.contains(this._renderer.view)) { document.body.removeChild(this._renderer.view); } // destroy the renderer and the WebGL context: - if (typeof this._renderer.gl !== 'undefined') + if (typeof this._renderer.gl !== "undefined") { - const extension = this._renderer.gl.getExtension('WEBGL_lose_context'); + const extension = this._renderer.gl.getExtension("WEBGL_lose_context"); extension.loseContext(); } this._renderer.destroy(); - window.removeEventListener('resize', this._resizeCallback); - window.removeEventListener('orientationchange', this._resizeCallback); + window.removeEventListener("resize", this._resizeCallback); + window.removeEventListener("orientationchange", this._resizeCallback); this._renderer = null; } - /** * Estimate the frame rate. * - * @name module:core.Window#getActualFrameRate - * @function - * @public * @return {number} rAF based delta time based approximation, 60.0 by default */ getActualFrameRate() @@ -186,13 +221,8 @@

Source: core/Window.js

return fps; } - /** * Take the browser full screen if possible. - * - * @name module:core.Window#adjustScreenSize - * @function - * @public */ adjustScreenSize() { @@ -201,104 +231,92 @@

Source: core/Window.js

// test whether the window is already fullscreen. // this._windowAlreadyInFullScreen = (!window.screenTop && !window.screenY); - if (this.fullscr/* && !this._windowAlreadyInFullScreen*/) + if (this.fullscr /* && !this._windowAlreadyInFullScreen*/) { - this._psychoJS.logger.debug('Resizing Window: ', this._name, 'to full screen.'); + this._psychoJS.logger.debug("Resizing Window: ", this._name, "to full screen."); - if (typeof document.documentElement.requestFullscreen === 'function') + if (typeof document.documentElement.requestFullscreen === "function") { document.documentElement.requestFullscreen() .catch(() => { - this.psychoJS.logger.warn('Unable to go fullscreen.'); + this.psychoJS.logger.warn("Unable to go fullscreen."); }); } - else if (typeof document.documentElement.mozRequestFullScreen === 'function') + else if (typeof document.documentElement.mozRequestFullScreen === "function") { document.documentElement.mozRequestFullScreen(); } - else if (typeof document.documentElement.webkitRequestFullscreen === 'function') + else if (typeof document.documentElement.webkitRequestFullscreen === "function") { document.documentElement.webkitRequestFullscreen(); } - else if (typeof document.documentElement.msRequestFullscreen === 'function') + else if (typeof document.documentElement.msRequestFullscreen === "function") { document.documentElement.msRequestFullscreen(); } else { - this.psychoJS.logger.warn('Unable to go fullscreen.'); + this.psychoJS.logger.warn("Unable to go fullscreen."); } } - } - /** * Take the browser back from full screen if needed. - * - * @name module:core.Window#closeFullScreen - * @function - * @public */ closeFullScreen() { if (this.fullscr) { - this._psychoJS.logger.debug('Resizing Window: ', this._name, 'back from full screen.'); + this._psychoJS.logger.debug("Resizing Window: ", this._name, "back from full screen."); - if (typeof document.exitFullscreen === 'function') + if (typeof document.exitFullscreen === "function") { document.exitFullscreen() .catch(() => { - this.psychoJS.logger.warn('Unable to close fullscreen.'); + this.psychoJS.logger.warn("Unable to close fullscreen."); }); } - else if (typeof document.mozCancelFullScreen === 'function') + else if (typeof document.mozCancelFullScreen === "function") { document.mozCancelFullScreen(); } - else if (typeof document.webkitExitFullscreen === 'function') + else if (typeof document.webkitExitFullscreen === "function") { document.webkitExitFullscreen(); } - else if (typeof document.msExitFullscreen === 'function') + else if (typeof document.msExitFullscreen === "function") { document.msExitFullscreen(); } else { - this.psychoJS.logger.warn('Unable to close fullscreen.'); + this.psychoJS.logger.warn("Unable to close fullscreen."); } } - } - /** * Log a message. * * <p> Note: the message will be time-stamped at the next call to requestAnimationFrame.</p> * - * @name module:core.Window#logOnFlip - * @function - * @public * @param {Object} options * @param {String} options.msg the message to be logged * @param {module:util.Logger.ServerLevel} [level = module:util.Logger.ServerLevel.EXP] the log level * @param {Object} [obj] the object associated with the message */ logOnFlip({ - msg, - level = Logger.ServerLevel.EXP, - obj - } = {}) + msg, + level = Logger.ServerLevel.EXP, + obj, + } = {}) { - this._msgToBeLogged.push({msg, level, obj}); + this._msgToBeLogged.push({ msg, level, obj }); } - /** * Callback function for callOnFlip. * @@ -311,24 +329,32 @@

Source: core/Window.js

* * <p>This is typically used to reset a timer or clock.</p> * - * @name module:core.Window#callOnFlip - * @function - * @public * @param {module:core.Window~OnFlipCallback} flipCallback - callback function. * @param {...*} flipCallbackArgs - arguments for the callback function. */ callOnFlip(flipCallback, ...flipCallbackArgs) { - this._flipCallbacks.push({function: flipCallback, arguments: flipCallbackArgs}); + this._flipCallbacks.push({ function: flipCallback, arguments: flipCallbackArgs }); } + /** + * Add PIXI.DisplayObject to the container displayed on the scene (window) + */ + addPixiObject(pixiObject) + { + this._stimsContainer.addChild(pixiObject); + } + + /** + * Remove PIXI.DisplayObject from the container displayed on the scene (window) + */ + removePixiObject(pixiObject) + { + this._stimsContainer.removeChild(pixiObject); + } /** * Render the stimuli onto the canvas. - * - * @name module:core.Window#render - * @function - * @public */ render() { @@ -337,13 +363,12 @@

Source: core/Window.js

return; } - this._frameCount++; // render the PIXI container: this._renderer.render(this._rootContainer); - if (typeof this._renderer.gl !== 'undefined') + if (typeof this._renderer.gl !== "undefined") { // this is to make sure that the GPU is done rendering, it may not be necessary // [http://www.html5gamedevs.com/topic/27849-detect-when-view-has-been-rendered/] @@ -359,7 +384,7 @@

Source: core/Window.js

// call the callOnFlip functions and remove them: for (let callback of this._flipCallbacks) { - callback['function'](...callback['arguments']); + callback["function"](...callback["arguments"]); } this._flipCallbacks = []; @@ -370,13 +395,10 @@

Source: core/Window.js

this._refresh(); } - /** * Update this window, if need be. * - * @name module:core.Window#_updateIfNeeded - * @function - * @private + * @protected */ _updateIfNeeded() { @@ -385,6 +407,7 @@

Source: core/Window.js

if (this._renderer) { this._renderer.backgroundColor = this._color.int; + this._backgroundSprite.tint = this._color.int; } // we also change the background color of the body since @@ -395,13 +418,10 @@

Source: core/Window.js

} } - /** * Recompute this window's draw list and _container children for the next animation frame. * - * @name module:core.Window#_refresh - * @function - * @private + * @protected */ _refresh() { @@ -411,22 +431,19 @@

Source: core/Window.js

// update it, then put it back for (const stimulus of this._drawList) { - if (stimulus._needUpdate && typeof stimulus._pixi !== 'undefined') + if (stimulus._needUpdate && typeof stimulus._pixi !== "undefined") { - this._rootContainer.removeChild(stimulus._pixi); + this._stimsContainer.removeChild(stimulus._pixi); stimulus._updateIfNeeded(); - this._rootContainer.addChild(stimulus._pixi); + this._stimsContainer.addChild(stimulus._pixi); } } } - /** * Force an update of all stimuli in this window's drawlist. * - * @name module:core.Window#_fullRefresh - * @function - * @private + * @protected */ _fullRefresh() { @@ -440,16 +457,13 @@

Source: core/Window.js

this._refresh(); } - /** * Setup PIXI. * * <p>A new renderer is created and a container is added to it. The renderer's touch and mouse events * are handled by the {@link EventManager}.</p> * - * @name module:core.Window#_setupPixi - * @function - * @private + * @protected */ _setupPixi() { @@ -462,18 +476,42 @@

Source: core/Window.js

width: this._size[0], height: this._size[1], backgroundColor: this.color.int, - resolution: window.devicePixelRatio + powerPreference: "high-performance", + resolution: window.devicePixelRatio, }); - this._renderer.view.style.transform = 'translatez(0)'; - this._renderer.view.style.position = 'absolute'; + this._renderer.view.style.transform = "translatez(0)"; + this._renderer.view.style.position = "absolute"; document.body.appendChild(this._renderer.view); // we also change the background color of the body since the dialog popup may be longer than the window's height: document.body.style.backgroundColor = this._color.hex; + // filters in PIXI work in a slightly unexpected fashion: + // when setting this._rootContainer.filters, filtering itself + // ignores backgroundColor of this._renderer and in addition to that + // all child elements of this._rootContainer ignore backgroundColor when blending. + // To circumvent that creating a separate PIXI.Sprite that serves as background color. + // Then placing all Stims to a separate this._stimsContainer which hovers on top of + // background sprite so that if we need to move all stims at once, the background sprite + // won't get affected. + this._backgroundSprite = new PIXI.Sprite(PIXI.Texture.WHITE); + this._backgroundSprite.tint = this.color.int; + this._backgroundSprite.width = this._size[0]; + this._backgroundSprite.height = this._size[1]; + this._backgroundSprite.anchor.set(.5); + this._stimsContainer = new PIXI.Container(); + this._stimsContainer.sortableChildren = true; + // create a top-level PIXI container: this._rootContainer = new PIXI.Container(); + this._rootContainer.addChild(this._backgroundSprite, this._stimsContainer); + + // sorts children according to their zIndex value. Higher zIndex means it will be moved towards the end of the array, + // and thus rendered on top of previous one. + this._rootContainer.sortableChildren = true; + this._rootContainer.interactive = true; + this._rootContainer.filters = [this._adjustmentFilter]; // set the initial size of the PIXI renderer and the position of the root container: Window._resizePixiRenderer(this); @@ -485,36 +523,35 @@

Source: core/Window.js

this._resizeCallback = (e) => { Window._resizePixiRenderer(this, e); + this._backgroundSprite.width = this._size[0]; + this._backgroundSprite.height = this._size[1]; this._fullRefresh(); }; - window.addEventListener('resize', this._resizeCallback); - window.addEventListener('orientationchange', this._resizeCallback); + window.addEventListener("resize", this._resizeCallback); + window.addEventListener("orientationchange", this._resizeCallback); } - /** * Adjust the size of the renderer and the position of the root container * in response to a change in the browser's size. * - * @name module:core.Window#_resizePixiRenderer - * @function - * @private + * @protected * @param {module:core.Window} pjsWindow - the PsychoJS Window * @param event */ static _resizePixiRenderer(pjsWindow, event) { - pjsWindow._psychoJS.logger.debug('resizing Window: ', pjsWindow._name, 'event:', JSON.stringify(event)); + pjsWindow._psychoJS.logger.debug("resizing Window: ", pjsWindow._name, "event:", JSON.stringify(event)); // update the size of the PsychoJS Window: pjsWindow._size[0] = window.innerWidth; pjsWindow._size[1] = window.innerHeight; // update the PIXI renderer: - pjsWindow._renderer.view.style.width = pjsWindow._size[0] + 'px'; - pjsWindow._renderer.view.style.height = pjsWindow._size[1] + 'px'; - pjsWindow._renderer.view.style.left = '0px'; - pjsWindow._renderer.view.style.top = '0px'; + pjsWindow._renderer.view.style.width = pjsWindow._size[0] + "px"; + pjsWindow._renderer.view.style.height = pjsWindow._size[1] + "px"; + pjsWindow._renderer.view.style.left = "0px"; + pjsWindow._renderer.view.style.top = "0px"; pjsWindow._renderer.resize(pjsWindow._size[0], pjsWindow._size[1]); // setup the container such that (0,0) is at the centre of the window @@ -524,13 +561,10 @@

Source: core/Window.js

pjsWindow._rootContainer.scale.y = -1; } - /** * Send all logged messages to the {@link Logger}. * - * @name module:core.Window#_writeLogOnFlip - * @function - * @private + * @protected */ _writeLogOnFlip() { @@ -542,7 +576,6 @@

Source: core/Window.js

this._msgToBeLogged = []; } - } @@ -551,19 +584,23 @@

Source: core/Window.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/core_WindowMixin.js.html b/docs/core_WindowMixin.js.html index ab3a1765..5ac14b9b 100644 --- a/docs/core_WindowMixin.js.html +++ b/docs/core_WindowMixin.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: core/WindowMixin.js - - - + core/WindowMixin.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + -

Source: core/WindowMixin.js

+ + + + +
+ +

core/WindowMixin.js

+ @@ -30,12 +54,11 @@

Source: core/WindowMixin.js

* Mixin implementing various unit-handling measurement methods. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - /** * <p>This mixin implements various unit-handling measurement methods.</p> * @@ -47,16 +70,15 @@

Source: core/WindowMixin.js

* @mixin * */ -export let WindowMixin = (superclass) => class extends superclass -{ - constructor(args) +export let WindowMixin = (superclass) => + class extends superclass { - super(args); - } - - + constructor(args) + { + super(args); + } - /** + /** * Convert the given length from stimulus unit to pixel units. * * @name module:core.WindowMixin#_getLengthPix @@ -66,85 +88,83 @@

Source: core/WindowMixin.js

* @param {boolean} [integerCoordinates = false] - whether or not to round the length. * @return {number} - the length in pixel units */ - _getLengthPix(length, integerCoordinates = false) - { - let response = { - origin: 'WindowMixin._getLengthPix', - context: 'when converting a length from stimulus unit to pixel units' - }; - - let length_px; - - if (this._units === 'pix') - { - length_px = length; - } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - length_px = length * winSize[1] / 2; // TODO: how do we handle norm when width != height? - } - else if (this._units === 'height') + _getLengthPix(length, integerCoordinates = false) { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - length_px = length * minSize; + let response = { + origin: "WindowMixin._getLengthPix", + context: "when converting a length from stimulus unit to pixel units", + }; + + let length_px; + + if (this._units === "pix") + { + length_px = length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + length_px = length * winSize[1] / 2; // TODO: how do we handle norm when width != height? + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + length_px = length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } + + if (integerCoordinates) + { + return Math.round(length_px); + } + else + { + return length_px; + } } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); - } - - if (integerCoordinates) - { - return Math.round(length_px); - } - else - { - return length_px; - } - } - - - /** - * Convert the given length from pixel units to the stimulus units - * - * @name module:core.WindowMixin#_getLengthUnits - * @function - * @protected - * @param {number} length_px - the length in pixel units - * @return {number} - the length in stimulus units - */ - _getLengthUnits(length_px) - { - let response = { - origin: 'WindowMixin._getLengthUnits', - context: 'when converting a length from pixel unit to stimulus units' - }; - if (this._units === 'pix') - { - return length_px; - } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - const winSize = this.win.size; - return length_px / (winSize[1] / 2); // TODO: how do we handle norm when width != height? - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length_px / minSize; - } - else + /** + * Convert the given length from pixel units to the stimulus units + * + * @name module:core.WindowMixin#_getLengthUnits + * @function + * @protected + * @param {number} length_px - the length in pixel units + * @return {number} - the length in stimulus units + */ + _getLengthUnits(length_px) { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); + let response = { + origin: "WindowMixin._getLengthUnits", + context: "when converting a length from pixel unit to stimulus units", + }; + + if (this._units === "pix") + { + return length_px; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + const winSize = this.win.size; + return length_px / (winSize[1] / 2); // TODO: how do we handle norm when width != height? + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length_px / minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - } - - /** + /** * Convert the given length from stimulus units to pixel units * * @name module:core.WindowMixin#_getHorLengthPix @@ -153,35 +173,35 @@

Source: core/WindowMixin.js

* @param {number} length - the length in stimulus units * @return {number} - the length in pixels */ - _getHorLengthPix(length) - { - let response = { - origin: 'WindowMixin._getHorLengthPix', - context: 'when converting a length from stimulus units to pixel units' - }; - - if (this._units === 'pix') - { - return length; - } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - return length * winSize[0] / 2; - } - else if (this._units === 'height') + _getHorLengthPix(length) { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length * minSize; + let response = { + origin: "WindowMixin._getHorLengthPix", + context: "when converting a length from stimulus units to pixel units", + }; + + if (this._units === "pix") + { + return length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + return length * winSize[0] / 2; + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - else - { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); - } - } - /** + /** * Convert the given length from pixel units to the stimulus units * * @name module:core.WindowMixin#_getVerLengthPix @@ -190,35 +210,34 @@

Source: core/WindowMixin.js

* @param {number} length - the length in pixel units * @return {number} - the length in stimulus units */ - _getVerLengthPix(length) - { - let response = { - origin: 'WindowMixin._getVerLengthPix', - context: 'when converting a length from pixel unit to stimulus units' - }; - - if (this._units === 'pix') - { - return length; - } - else if (typeof this._units === 'undefined' || this._units === 'norm') - { - var winSize = this.win.size; - return length * winSize[1] / 2; - } - else if (this._units === 'height') - { - const minSize = Math.min(this.win.size[0], this.win.size[1]); - return length * minSize; - } - else + _getVerLengthPix(length) { - // throw { ...response, error: 'unable to deal with unit: ' + this._units }; - throw Object.assign(response, {error: 'unable to deal with unit: ' + this._units}); + let response = { + origin: "WindowMixin._getVerLengthPix", + context: "when converting a length from pixel unit to stimulus units", + }; + + if (this._units === "pix") + { + return length; + } + else if (typeof this._units === "undefined" || this._units === "norm") + { + var winSize = this.win.size; + return length * winSize[1] / 2; + } + else if (this._units === "height") + { + const minSize = Math.min(this.win.size[0], this.win.size[1]); + return length * minSize; + } + else + { + // throw { ...response, error: 'unable to deal with unit: ' + this._units }; + throw Object.assign(response, { error: "unable to deal with unit: " + this._units }); + } } - } - -}; + }; @@ -226,19 +245,23 @@

Source: core/WindowMixin.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/data_ExperimentHandler.js.html b/docs/data_ExperimentHandler.js.html index dc6182f8..0ed0b690 100644 --- a/docs/data_ExperimentHandler.js.html +++ b/docs/data_ExperimentHandler.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: data/ExperimentHandler.js - - - + data/ExperimentHandler.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + + + -

Source: data/ExperimentHandler.js

+
+ +

data/ExperimentHandler.js

+ @@ -30,40 +54,27 @@

Source: data/ExperimentHandler.js

* Experiment Handler * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject'; -import {MonotonicClock} from '../util/Clock'; -import * as util from '../util/Util'; - +import * as XLSX from "xlsx"; +import { MonotonicClock } from "../util/Clock.js"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; /** * <p>An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful * for generating a single data file from an experiment with many different loops (e.g. interleaved * staircases or loops within loops.</p> * - * @name module:data.ExperimentHandler - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} options.name - name of the experiment - * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc. */ export class ExperimentHandler extends PsychObject { - /** * Getter for experimentEnded. - * - * @name module:core.Window#experimentEnded - * @function - * @public */ get experimentEnded() { @@ -72,17 +83,12 @@

Source: data/ExperimentHandler.js

/** * Setter for experimentEnded. - * - * @name module:core.Window#experimentEnded - * @function - * @public */ set experimentEnded(ended) { this._experimentEnded = ended; } - /** * Legacy experiment getters. */ @@ -96,16 +102,43 @@

Source: data/ExperimentHandler.js

return this._trialsData; } - + /** + * @memberof module:data + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} options.name - name of the experiment + * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc. + */ constructor({ - psychoJS, - name, - extraInfo - } = {}) + psychoJS, + name, + extraInfo, + dataFileName + } = {}) { super(psychoJS, name); - this._addAttribute('extraInfo', extraInfo); + this._addAttribute("extraInfo", extraInfo); + + // process the extra info: + this._experimentName = (typeof extraInfo.expName === "string" && extraInfo.expName.length > 0) + ? extraInfo.expName + : this.psychoJS.config.experiment.name; + this._participant = (typeof extraInfo.participant === "string" && extraInfo.participant.length > 0) + ? extraInfo.participant + : "PARTICIPANT"; + this._session = (typeof extraInfo.session === "string" && extraInfo.session.length > 0) + ? extraInfo.session + : "SESSION"; + this._datetime = (typeof extraInfo.date !== "undefined") + ? extraInfo.date + : MonotonicClock.getDateStr(); + + this._addAttribute( + "dataFileName", + dataFileName, + `${this._participant}_${this._experimentName}_${this._datetime}` + ); // loop handlers: this._loops = []; @@ -119,31 +152,24 @@

Source: data/ExperimentHandler.js

this._experimentEnded = false; } - /** * Whether or not the current entry (i.e. trial data) is empty. * <p>Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.</p> * - * @name module:data.ExperimentHandler#isEntryEmpty - * @function - * @public * @returns {boolean} whether or not the current entry is empty + * @todo This really should be renamed: IsCurrentEntryNotEmpty */ isEntryEmpty() { return (Object.keys(this._currentTrialData).length > 0); } - /** * Add a loop. * * <p> The loop might be a {@link TrialHandler}, for instance.</p> * <p> Data from this loop will be included in the resulting data files.</p> * - * @name module:data.ExperimentHandler#addLoop - * @function - * @public * @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler */ addLoop(loop) @@ -153,13 +179,9 @@

Source: data/ExperimentHandler.js

loop.experimentHandler = this; } - /** * Remove the given loop from the list of unfinished loops, e.g. when it has completed. * - * @name module:data.ExperimentHandler#removeLoop - * @function - * @public * @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler */ removeLoop(loop) @@ -171,16 +193,12 @@

Source: data/ExperimentHandler.js

} } - /** * Add the key/value pair. * * <p> Multiple key/value pairs can be added to any given entry of the data file. There are * considered part of the same entry until a call to {@link nextEntry} is made. </p> * - * @name module:data.ExperimentHandler#addData - * @function - * @public * @param {Object} key - the key * @param {Object} value - the value */ @@ -200,19 +218,15 @@

Source: data/ExperimentHandler.js

this._currentTrialData[key] = value; } - /** * Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData} * will be associated with the next trial. * - * @name module:data.ExperimentHandler#nextEntry - * @function - * @public - * @param {Object[]} snapshots - array of loop snapshots + * @param {Object | Object[] | undefined} snapshots - array of loop snapshots */ nextEntry(snapshots) { - if (typeof snapshots !== 'undefined') + if (typeof snapshots !== "undefined") { // turn single snapshot into a one-element array: if (!Array.isArray(snapshots)) @@ -231,7 +245,6 @@

Source: data/ExperimentHandler.js

} } } - } // this is to support legacy generated JavaScript code and does not properly handle // loops within loops: @@ -264,7 +277,6 @@

Source: data/ExperimentHandler.js

this._currentTrialData = {}; } - /** * Save the results of the experiment. * @@ -274,21 +286,22 @@

Source: data/ExperimentHandler.js

* </ul> * <p> * - * @name module:data.ExperimentHandler#save - * @function - * @public * @param {Object} options * @param {Array.<Object>} [options.attributes] - the attributes to be saved - * @param {Array.<Object>} [options.sync] - whether or not to communicate with the server in a synchronous manner + * @param {boolean} [options.sync=false] - whether or not to communicate with the server in a synchronous manner + * @param {string} [options.tag=''] - an optional tag to add to the filename to which the data is saved (for CSV and XLSX saving options) + * @param {boolean} [options.clear=false] - whether or not to clear all experiment results immediately after they are saved (this is useful when saving data in separate chunks, throughout an experiment) */ async save({ - attributes = [], - sync = false - } = {}) + attributes = [], + sync = false, + tag = "", + clear = false + } = {}) { - this._psychoJS.logger.info('[PsychoJS] Save experiment results.'); + this._psychoJS.logger.info("[PsychoJS] Save experiment results."); - // (*) get attributes: + // get attributes: if (attributes.length === 0) { attributes = this._trialsKeys.slice(); @@ -314,101 +327,106 @@

Source: data/ExperimentHandler.js

} } + let data = this._trialsData; + // if the experiment data have to be cleared, we first make a copy of them: + if (clear) + { + data = this._trialsData.slice(); + this._trialsData = []; + } - // (*) get various experiment info: - const info = this.extraInfo; - const __experimentName = (typeof info.expName !== 'undefined') ? info.expName : this.psychoJS.config.experiment.name; - const __participant = ((typeof info.participant === 'string' && info.participant.length > 0) ? info.participant : 'PARTICIPANT'); - const __session = ((typeof info.session === 'string' && info.session.length > 0) ? info.session : 'SESSION'); - const __datetime = ((typeof info.date !== 'undefined') ? info.date : MonotonicClock.getDateStr()); - const gitlabConfig = this._psychoJS.config.gitlab; - const __projectId = (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined') ? gitlabConfig.projectId : undefined; - - - // (*) save to a .csv file: + // save to a .csv file: if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV) { // note: we use the XLSX library as it automatically deals with header, takes care of quotes, // newlines, etc. - const worksheet = XLSX.utils.json_to_sheet(this._trialsData); + // TODO only save the given attributes + const worksheet = XLSX.utils.json_to_sheet(data); // prepend BOM - const csv = '\ufeff' + XLSX.utils.sheet_to_csv(worksheet); + const csv = "\ufeff" + XLSX.utils.sheet_to_csv(worksheet); // upload data to the pavlovia server or offer them for download: - const key = __participant + '_' + __experimentName + '_' + __datetime + '.csv'; - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + const filenameWithoutPath = this._dataFileName.split(/[\\/]/).pop(); + const key = `${filenameWithoutPath}${tag}.csv`; + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { return /*await*/ this._psychoJS.serverManager.uploadData(key, csv, sync); } else { - util.offerDataForDownload(key, csv, 'text/csv'); + util.offerDataForDownload(key, csv, "text/csv"); } } - - - // (*) save in the database on the remote server: + // save to the database on the pavlovia server: else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE) { + const gitlabConfig = this._psychoJS.config.gitlab; + const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined; + let documents = []; - for (let r = 0; r < this._trialsData.length; r++) + for (let r = 0; r < data.length; r++) { - let doc = {__projectId, __experimentName, __participant, __session, __datetime}; + let doc = { + __projectId, + __experimentName: this._experimentName, + __participant: this._participant, + __session: this._session, + __datetime: this._datetime + }; for (let h = 0; h < attributes.length; h++) { - doc[attributes[h]] = this._trialsData[r][attributes[h]]; + doc[attributes[h]] = data[r][attributes[h]]; } documents.push(doc); } // upload data to the pavlovia server or offer them for download: - if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && - this._psychoJS.config.experiment.status === 'RUNNING' && - !this._psychoJS._serverMsg.has('__pilotToken')) + if ( + this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + && this._psychoJS.config.experiment.status === "RUNNING" + && !this._psychoJS._serverMsg.has("__pilotToken") + ) { - const key = 'results'; // name of the mongoDB collection + const key = "results"; // name of the mongoDB collection return /*await*/ this._psychoJS.serverManager.uploadData(key, JSON.stringify(documents), sync); } else { - util.offerDataForDownload('results.json', JSON.stringify(documents), 'application/json'); + util.offerDataForDownload("results.json", JSON.stringify(documents), "application/json"); } - } } - /** * Get the attribute names and values for the current trial of a given loop. * <p> Only info relating to the trial execution are returned.</p> * - * @name module:data.ExperimentHandler#_getLoopAttributes - * @function - * @static * @protected * @param {Object} loop - the loop */ static _getLoopAttributes(loop) { // standard trial attributes: - const properties = ['thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'stepSizeCurrent', 'ran', 'order']; + const properties = ["thisRepN", "thisTrialN", "thisN", "thisIndex", "stepSizeCurrent", "ran", "order"]; let attributes = {}; const loopName = loop.name; for (const loopProperty in loop) { if (properties.includes(loopProperty)) { - const key = (loopProperty === 'stepSizeCurrent') ? loopName + '.stepSize' : loopName + '.' + loopProperty; + const key = (loopProperty === "stepSizeCurrent") ? loopName + ".stepSize" : loopName + "." + loopProperty; attributes[key] = loop[loopProperty]; } } // specific trial attributes: - if (typeof loop.getCurrentTrial === 'function') + if (typeof loop.getCurrentTrial === "function") { const currentTrial = loop.getCurrentTrial(); for (const trialProperty in currentTrial) @@ -432,7 +450,7 @@

Source: data/ExperimentHandler.js

else: names.append(loopName+'.thisTrial') vals.append(trial) - + // single StairHandler elif hasattr(loop, 'intensities'): names.append(loopName+'.intensity') @@ -443,41 +461,35 @@

Source: data/ExperimentHandler.js

return attributes; } - } - /** * Experiment result format * - * @name module:core.ServerManager#SaveFormat * @enum {Symbol} * @readonly - * @public */ ExperimentHandler.SaveFormat = { /** * Results are saved to a .csv file */ - CSV: Symbol.for('CSV'), + CSV: Symbol.for("CSV"), /** * Results are saved to a database */ - DATABASE: Symbol.for('DATABASE') + DATABASE: Symbol.for("DATABASE"), }; - /** * Experiment environment. * * @enum {Symbol} * @readonly - * @public */ ExperimentHandler.Environment = { - SERVER: Symbol.for('SERVER'), - LOCAL: Symbol.for('LOCAL') + SERVER: Symbol.for("SERVER"), + LOCAL: Symbol.for("LOCAL"), }; @@ -486,19 +498,23 @@

Source: data/ExperimentHandler.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/data_MultiStairHandler.js.html b/docs/data_MultiStairHandler.js.html new file mode 100644 index 00000000..9fbad0e6 --- /dev/null +++ b/docs/data_MultiStairHandler.js.html @@ -0,0 +1,514 @@ + + + + + + data/MultiStairHandler.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

data/MultiStairHandler.js

+ + + + + + + +
+
+
/**
+ * Multiple Staircase Trial Handler
+ *
+ * @author Alain Pitiot
+ * @version 2021.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd.
+ *   (https://opensciencetools.org)
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import {TrialHandler} from "./TrialHandler.js";
+import {QuestHandler} from "./QuestHandler.js";
+import * as util from "../util/Util.js";
+import seedrandom from "seedrandom";
+
+
+/**
+ * <p>A handler dealing with multiple staircases, simultaneously.</p>
+ *
+ * <p>Note that, at the moment, using the MultiStairHandler requires the jsQuest.js
+ * library to be loaded as a resource, at the start of the experiment.</p>
+ *
+ * @extends TrialHandler
+ */
+export class MultiStairHandler extends TrialHandler
+{
+	/**
+	 * @memberof module:data
+	 * @param {Object} options - the handler options
+	 * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+	 * @param {string} options.varName - the name of the variable / intensity / contrast
+	 * 	/ threshold manipulated by the staircases
+	 * @param {MultiStairHandler.StaircaseType} [options.stairType="simple"] - the
+	 * 	handler type
+	 * @param {Array.<Object> | String} [options.conditions= [undefined] ] - if it is a string,
+	 * 	we treat it as the name of a conditions resource
+	 * @param {module:data.TrialHandler.Method} options.method - the trial method
+	 * @param {number} [options.nTrials=50] - maximum number of trials
+	 * @param {number} options.randomSeed - seed for the random number generator
+	 * @param {string} options.name - name of the handler
+	 * @param {boolean} [options.autoLog= false] - whether or not to log
+	 */
+	constructor({
+		psychoJS,
+		varName,
+		stairType,
+		conditions,
+		method = TrialHandler.Method.RANDOM,
+		nTrials = 50,
+		randomSeed,
+		name,
+		autoLog
+	} = {})
+	{
+		super({
+			psychoJS,
+			name,
+			autoLog,
+			seed: randomSeed,
+			// note: multiStairHandler is a sequential TrialHandler, we deal with randomness
+			// in _nextTrial
+			method: TrialHandler.Method.SEQUENTIAL,
+			trialList: Array(nTrials),
+			nReps: 1
+		});
+
+		// now that we have initialised a sequential TrialHandler, we update method:
+		this._multiMethod = method;
+		this._addAttribute("varName", varName);
+		this._addAttribute("stairType", stairType, MultiStairHandler.StaircaseType.SIMPLE);
+		this._addAttribute("conditions", conditions, [undefined]);
+		this._addAttribute("nTrials", nTrials);
+
+		if (typeof randomSeed !== "undefined")
+		{
+			this._randomNumberGenerator = seedrandom(randomSeed);
+		}
+		else
+		{
+			this._randomNumberGenerator = seedrandom();
+		}
+
+		this._prepareStaircases();
+		this._nextTrial();
+	}
+
+	/**
+	 * Get the current staircase.
+	 *
+	 * @returns {TrialHandler} the current staircase, or undefined if the trial has ended
+	 */
+	get currentStaircase()
+	{
+		return this._currentStaircase;
+	}
+
+	/**
+	 * Get the current intensity.
+	 *
+	 * @returns {number} the intensity of the current staircase, or undefined if the trial has ended
+	 */
+	get intensity()
+	{
+		if (this._currentStaircase instanceof QuestHandler)
+		{
+			return this._currentStaircase.getQuestValue();
+		}
+
+		// TODO similar for simple staircase:
+		// if (this._currentStaircase instanceof StaircaseHandler)
+		// {
+		//    return this._currentStaircase.getStairValue();
+		// }
+
+		return undefined;
+	}
+
+	/**
+	 * Add a response to the current staircase.
+	 *
+	 * @param{number} response - the response to the trial, must be either 0 (incorrect or
+	 * non-detected) or 1 (correct or detected)
+	 * @param{number | undefined} [value] - optional intensity / contrast / threshold
+	 */
+	addResponse(response, value)
+	{
+		// check that response is either 0 or 1:
+		if (response !== 0 && response !== 1)
+		{
+			throw {
+				origin: "MultiStairHandler.addResponse",
+				context: "when adding a trial response",
+				error: `the response must be either 0 or 1, got: ${JSON.stringify(response)}`
+			};
+		}
+
+		this._psychoJS.experiment.addData(this._name+'.response', response);
+
+		if (!this._finished)
+		{
+			// update the current staircase, but do not add the response again:
+			this._currentStaircase.addResponse(response, value, false);
+
+			// move onto the next trial:
+			this._nextTrial();
+		}
+	}
+
+	/**
+	 * Validate the conditions.
+	 *
+	 * @protected
+	 */
+	_validateConditions()
+	{
+		try
+		{
+			// conditions must be a non empty array:
+			if (!Array.isArray(this._conditions) || this._conditions.length === 0)
+			{
+				throw "conditions should be a non empty array of objects";
+			}
+
+			// TODO this is temporary until we have implemented StairHandler:
+			if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE)
+			{
+				throw "'simple' staircases are currently not supported";
+			}
+
+			for (const condition of this._conditions)
+			{
+				// each condition must be an object:
+				if (typeof condition !== "object")
+				{
+					throw "one of the conditions is not an object";
+				}
+
+				// each condition must include certain fields, such as startVal and label:
+				if (!("startVal" in condition))
+				{
+					throw "each condition should include a startVal field";
+				}
+				if (!("label" in condition))
+				{
+					throw "each condition should include a label field";
+				}
+
+				// for QUEST, we also need startValSd:
+				if (this._stairType === MultiStairHandler.StaircaseType.QUEST && !("startValSd" in condition))
+				{
+					throw "QUEST conditions must include a startValSd field";
+				}
+			}
+		}
+		catch (error)
+		{
+			throw {
+				origin: "MultiStairHandler._validateConditions",
+				context: "when validating the conditions",
+				error
+			};
+		}
+	}
+
+	/**
+	 * Setup the staircases, according to the conditions.
+	 *
+	 * @protected
+	 */
+	_prepareStaircases()
+	{
+		try
+		{
+			this._validateConditions();
+
+			this._staircases = [];
+
+			for (const condition of this._conditions)
+			{
+				let handler;
+
+				// QUEST handler:
+				if (this._stairType === MultiStairHandler.StaircaseType.QUEST)
+				{
+					const args = Object.assign({}, condition);
+					args.psychoJS = this._psychoJS;
+					args.varName = this._varName;
+					// label becomes name:
+					args.name = condition.label;
+					args.autoLog = this._autoLog;
+					if (typeof condition.nTrials === "undefined")
+					{
+						args.nTrials = this._nTrials;
+					}
+
+					handler = new QuestHandler(args);
+				}
+
+				// simple StairCase handler:
+				if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE)
+				{
+					// TODO not supported just yet, an exception is raised in _validateConditions
+					continue;
+				}
+
+				this._staircases.push(handler);
+			}
+
+			this._currentPass = [];
+			this._currentStaircase = null;
+		}
+		catch (error)
+		{
+			throw {
+				origin: "MultiStairHandler._prepareStaircases",
+				context: "when preparing the staircases",
+				error
+			};
+		}
+	}
+
+	/**
+	 * Move onto the next trial.
+	 *
+	 * @protected
+	 */
+	_nextTrial()
+	{
+		try
+		{
+			// if the current pass is empty, get a new one:
+			if (this._currentPass.length === 0)
+			{
+				this._currentPass = this._staircases.filter( handler => !handler.finished );
+
+				if (this._multiMethod === TrialHandler.Method.SEQUENTIAL)
+				{
+					// nothing to do
+				}
+				else if (this._multiMethod === TrialHandler.Method.RANDOM)
+				{
+					this._currentPass = util.shuffle(this._currentPass, this._randomNumberGenerator);
+				}
+				else if (this._multiMethod === TrialHandler.Method.FULL_RANDOM)
+				{
+					if (this._currentPass.length > 0)
+					{
+						// select a handler at random:
+						const index = Math.floor(this._randomNumberGenerator() * this._currentPass.length);
+						const handler = this._currentPass[index];
+						this._currentPass = [handler];
+					}
+				}
+			}
+
+
+			// pick the next staircase in the pass:
+			this._currentStaircase = this._currentPass.shift();
+
+
+			// test for termination:
+			if (typeof this._currentStaircase === "undefined")
+			{
+				this._finished = true;
+
+				// update the snapshots associated with the current trial in the trial list:
+				for (let t = 0; t < this._snapshots.length - 1; ++t)
+				{
+					// the current trial is the last defined one:
+					if (typeof this._trialList[t + 1] === "undefined")
+					{
+						this._snapshots[t].finished = true;
+						break;
+					}
+				}
+
+				return;
+			}
+
+
+			// get the value, based on the type of the trial handler:
+			let value = Number.MIN_VALUE;
+			if (this._currentStaircase instanceof QuestHandler)
+			{
+				value = this._currentStaircase.getQuestValue();
+			}
+			// TODO add a test for simple staircase:
+			// if (this._currentStaircase instanceof StaircaseHandler)
+			// {
+			// value = this._currentStaircase.getStairValue();
+			// }
+
+
+			this._psychoJS.logger.debug(`selected staircase: ${this._currentStaircase.name}, estimated value for variable ${this._varName}: ${value}`);
+
+
+			// update the next undefined trial in the trial list, and the associated snapshot:
+			for (let t = 0; t < this._trialList.length; ++t)
+			{
+				if (typeof this._trialList[t] === "undefined")
+				{
+					this._trialList[t] = {
+						[this._name+"."+this._varName]: value,
+						[this._name+".intensity"]: value
+					};
+					for (const attribute of this._currentStaircase._userAttributes)
+					{
+						// "name" becomes "label" again:
+						if (attribute === "name")
+						{
+							this._trialList[t][this._name+".label"] = this._currentStaircase["_name"];
+						}
+						else if (attribute !== "trialList" && attribute !== "extraInfo")
+						{
+							this._trialList[t][this._name+"."+attribute] = this._currentStaircase["_" + attribute];
+						}
+					}
+
+					if (typeof this._snapshots[t] !== "undefined")
+					{
+						let fieldName = /*this._name + "." +*/ this._varName;
+						this._snapshots[t][fieldName] = value;
+						this._snapshots[t].trialAttributes.push(fieldName);
+						fieldName = /*this._name + ".*/ "intensity";
+						this._snapshots[t][fieldName] = value;
+						this._snapshots[t].trialAttributes.push(fieldName);
+
+						for (const attribute of this._currentStaircase._userAttributes)
+						{
+							// "name" becomes "label" again:
+							if (attribute === 'name')
+							{
+								fieldName = /*this._name + ".*/ "label";
+								this._snapshots[t][fieldName] = this._currentStaircase["_name"];
+								this._snapshots[t].trialAttributes.push(fieldName);
+							}
+							else if (attribute !== 'trialList' && attribute !== 'extraInfo')
+							{
+								fieldName = /*this._name+"."+*/ attribute;
+								this._snapshots[t][fieldName] = this._currentStaircase["_" + attribute];
+								this._snapshots[t].trialAttributes.push(fieldName);
+							}
+						}
+					}
+					break;
+				}
+			}
+		}
+		catch (error)
+		{
+			throw {
+				origin: "MultiStairHandler._nextTrial",
+				context: "when moving onto the next trial",
+				error
+			};
+		}
+	}
+}
+
+/**
+ * MultiStairHandler staircase type.
+ *
+ * @enum {Symbol}
+ * @readonly
+ */
+MultiStairHandler.StaircaseType = {
+	/**
+	 * Simple staircase handler.
+	 */
+	SIMPLE: Symbol.for("SIMPLE"),
+
+	/**
+	 * QUEST handler.
+	 */
+	QUEST: Symbol.for("QUEST")
+};
+
+/**
+ * Staircase status.
+ *
+ * @enum {Symbol}
+ * @readonly
+ */
+MultiStairHandler.StaircaseStatus = {
+	/**
+	 * The staircase is currently running.
+	 */
+	RUNNING: Symbol.for("RUNNING"),
+
+	/**
+	 * The staircase is now finished.
+	 */
+	FINISHED: Symbol.for("FINISHED")
+};
+
+
+
+ + + + + + +
+ +
+ +
+ Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
+ + + + + + + + + + + diff --git a/docs/data_QuestHandler.js.html b/docs/data_QuestHandler.js.html new file mode 100644 index 00000000..16e58a3e --- /dev/null +++ b/docs/data_QuestHandler.js.html @@ -0,0 +1,448 @@ + + + + + + data/QuestHandler.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

data/QuestHandler.js

+ + + + + + + +
+
+
/**
+ * Quest Trial Handler
+ *
+ * @author Alain Pitiot & Thomas Pronk
+ * @version 2022.2.3
+ * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import {TrialHandler} from "./TrialHandler.js";
+
+/**
+ * <p>A Trial Handler that implements the Quest algorithm for quick measurement of
+    psychophysical thresholds. QuestHandler relies on the [jsQuest]{@link https://github.com/kurokida/jsQUEST} library, a port of Prof Dennis Pelli's QUEST algorithm by [Daiichiro Kuroki]{@link https://github.com/kurokida}.</p>
+ *
+ * @extends TrialHandler
+ */
+export class QuestHandler extends TrialHandler
+{
+	/**
+	 * @memberof module:data
+	 * @param {Object} options - the handler options
+	 * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
+	 * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST
+	 * @param {number} options.startVal - initial guess for the threshold
+	 * @param {number} options.startValSd - standard deviation of the initial guess
+	 * @param {number} options.minVal - minimum value for the threshold
+	 * @param {number} options.maxVal - maximum value for the threshold
+	 * @param {number} [options.pThreshold=0.82] - threshold criterion expressed as probability of getting a correct response
+	 * @param {number} options.nTrials - maximum number of trials
+	 * @param {number} options.stopInterval - minimum [5%, 95%] confidence interval required for the loop to stop
+	 * @param {QuestHandler.Method} options.method - the QUEST method
+	 * @param {number} [options.beta=3.5] - steepness of the QUEST psychometric function
+	 * @param {number} [options.delta=0.01] - fraction of trials with blind responses
+	 * @param {number} [options.gamma=0.5] - fraction of trails that would generate a correct response when the threshold is infinitely small
+	 * @param {number} [options.grain=0.01] - quantization of the internal table
+	 * @param {string} options.name - name of the handler
+	 * @param {boolean} [options.autoLog= false] - whether or not to log
+	 */
+	constructor({
+		psychoJS,
+		varName,
+		startVal,
+		startValSd,
+		minVal,
+		maxVal,
+		pThreshold,
+		nTrials,
+		stopInterval,
+		method,
+		beta,
+		delta,
+		gamma,
+		grain,
+		name,
+		autoLog
+	} = {})
+	{
+		super({
+			psychoJS,
+			name,
+			autoLog,
+			method: TrialHandler.Method.SEQUENTIAL,
+			trialList: Array(nTrials),
+			nReps: 1
+		});
+
+		this._addAttribute("varName", varName);
+		this._addAttribute("startVal", startVal);
+		this._addAttribute("minVal", minVal, Number.MIN_VALUE);
+		this._addAttribute("maxVal", maxVal, Number.MAX_VALUE);
+		this._addAttribute("startValSd", startValSd);
+		this._addAttribute("pThreshold", pThreshold, 0.82);
+		this._addAttribute("nTrials", nTrials);
+		this._addAttribute("stopInterval", stopInterval, Number.MIN_VALUE);
+		this._addAttribute("beta", beta, 3.5);
+		this._addAttribute("delta", delta, 0.01);
+		this._addAttribute("gamma", gamma, 0.5);
+		this._addAttribute("grain", grain, 0.01);
+		this._addAttribute("method", method, QuestHandler.Method.QUANTILE);
+
+		// setup jsQuest:
+		this._setupJsQuest();
+		this._estimateQuestValue();
+	}
+
+	/**
+	 * Setter for the method attribute.
+	 *
+	 * @param {mixed} method - the method value, PsychoPy-style values ("mean", "median", 
+	 * "quantile") are converted to their respective QuestHandler.Method values
+	 * @param {boolean} log - whether or not to log the change of seed
+	 */
+	 setMethod(method, log)
+	 {
+		let methodMapping = {
+			"quantile": QuestHandler.Method.QUANTILE,
+			"mean": QuestHandler.Method.MEAN,
+			"mode": QuestHandler.Method.MODE
+		};
+		// If method is a key in methodMapping, convert method to corresponding value
+		if (methodMapping.hasOwnProperty(method)) 
+		{
+			method = methodMapping[method];
+		}
+		this._setAttribute("method", method, log);
+	}
+
+	/**
+	 * Add a response and update the PDF.
+	 *
+	 * @param{number} response	- the response to the trial, must be either 0 (incorrect or
+	 * non-detected) or 1 (correct or detected)
+	 * @param{number | undefined} value - optional intensity / contrast / threshold
+	 * @param{boolean} [doAddData = true] - whether or not to add the response as data to the
+	 * 	experiment
+	 */
+	addResponse(response, value, doAddData = true)
+	{
+		// check that response is either 0 or 1:
+		if (response !== 0 && response !== 1)
+		{
+			throw {
+				origin: "QuestHandler.addResponse",
+				context: "when adding a trial response",
+				error: `the response must be either 0 or 1, got: ${JSON.stringify(response)}`
+			};
+		}
+
+		if (doAddData)
+		{
+			this._psychoJS.experiment.addData(this._name + '.response', response);
+		}
+
+		// update the QUEST pdf:
+		if (typeof value !== "undefined")
+		{
+			this._jsQuest = jsQUEST.QuestUpdate(this._jsQuest, value, response);
+		}
+		else
+		{
+			this._jsQuest = jsQUEST.QuestUpdate(this._jsQuest, this._questValue, response);
+		}
+
+		if (!this._finished)
+		{
+			this.next();
+
+			// estimate the next value of the QUEST variable
+			// (and update the trial list and snapshots):
+			this._estimateQuestValue();
+		}
+	}
+
+	/**
+	 * Simulate a response.
+	 *
+	 * @param{number} trueValue - the true, known value of the threshold / contrast / intensity
+	 * @returns{number} the simulated response, 0 or 1
+	 */
+	simulate(trueValue)
+	{
+		const response = jsQUEST.QuestSimulate(this._jsQuest, this._questValue, trueValue);
+
+		// restrict to limits:
+		this._questValue = Math.max(this._minVal, Math.min(this._maxVal, this._questValue));
+
+		this._psychoJS.logger.debug(`simulated response: ${response}`);
+
+		return response;
+	}
+
+	/**
+	 * Get the mean of the Quest posterior PDF.
+	 *
+	 * @returns {number} the mean
+	 */
+	mean()
+	{
+		return jsQUEST.QuestMean(this._jsQuest);
+	}
+
+	/**
+	 * Get the standard deviation of the Quest posterior PDF.
+	 *
+	 * @returns {number} the standard deviation
+	 */
+	sd()
+	{
+		return jsQUEST.QuestSd(this._jsQuest);
+	}
+
+	/**
+	 * Get the mode of the Quest posterior PDF.
+	 *
+	 * @returns {number} the mode
+	 */
+	mode()
+	{
+		const [mode, pdf] = jsQUEST.QuestMode(this._jsQuest);
+		return mode;
+	}
+
+	/**
+	 * Get the standard deviation of the Quest posterior PDF.
+	 *
+	 * @param{number} quantileOrder the quantile order
+	 * @returns {number} the quantile
+	 */
+	quantile(quantileOrder)
+	{
+		return jsQUEST.QuestQuantile(this._jsQuest, quantileOrder);
+	}
+
+	/**
+	 * Get the current value of the variable / contrast / threshold.
+	 *
+	 * @returns {number} the current QUEST value for the variable / contrast / threshold
+	 */
+	getQuestValue()
+	{
+		return this._questValue;
+	}
+
+	/**
+	 * Get the current value of the variable / contrast / threshold.
+	 *
+	 * <p>This is the getter associated to getQuestValue.</p>
+	 *
+	 * @returns {number} the intensity of the current staircase, or undefined if the trial has ended
+	 */
+	get intensity()
+	{
+		return this.getQuestValue();
+	}
+
+	/**
+	 * Get an estimate of the 5%-95% confidence interval (CI).
+	 *
+	 * @param{boolean} [getDifference=false] - if true, return the width of the CI instead of the CI
+	 * @returns{number[] | number} the 5%-95% CI or the width of the CI
+	 */
+	confInterval(getDifference = false)
+	{
+		const CI = [
+			jsQUEST.QuestQuantile(this._jsQuest, 0.05),
+			jsQUEST.QuestQuantile(this._jsQuest, 0.95)
+		];
+
+		if (getDifference)
+		{
+			return Math.abs(CI[0] - CI[1]);
+		}
+		else
+		{
+			return CI;
+		}
+	}
+
+	/**
+	 * Setup the JS Quest object.
+	 *
+	 * @protected
+	 */
+	_setupJsQuest()
+	{
+		this._jsQuest = jsQUEST.QuestCreate(
+			this._startVal,
+			this._startValSd,
+			this._pThreshold,
+			this._beta,
+			this._delta,
+			this._gamma,
+			this._grain);
+	}
+
+	/**
+	 * Estimate the next value of the QUEST variable, based on the current value
+	 * and on the selected QUEST method.
+	 *
+	 * @protected
+	 */
+	_estimateQuestValue()
+	{
+		// estimate the value based on the chosen QUEST method:
+		if (this._method === QuestHandler.Method.QUANTILE)
+		{
+			this._questValue = jsQUEST.QuestQuantile(this._jsQuest);
+		}
+		else if (this._method === QuestHandler.Method.MEAN)
+		{
+			this._questValue = jsQUEST.QuestMean(this._jsQuest);
+		}
+		else if (this._method === QuestHandler.Method.MODE)
+		{
+			const [mode, pdf] = jsQUEST.QuestMode(this._jsQuest);
+			this._questValue = mode;
+		}
+		else
+		{
+			throw {
+				origin: "QuestHandler._estimateQuestValue",
+				context: "when estimating the next value of the QUEST variable",
+				error: `unknown method: ${this._method}, please use: mean, mode, or quantile`
+			};
+		}
+
+		this._psychoJS.logger.debug(`estimated value for QUEST variable ${this._varName}: ${this._questValue}`);
+
+		// check whether we should finish the trial:
+		if (this.thisN > 0 && (this.nRemaining === 0 || this.confInterval(true) < this._stopInterval))
+		{
+			this._finished = true;
+
+			// update the snapshots associated with the current trial in the trial list:
+			for (let t = 0; t < this._snapshots.length - 1; ++t)
+			{
+				// the current trial is the last defined one:
+				if (typeof this._trialList[t + 1] === "undefined")
+				{
+					this._snapshots[t].finished = true;
+					break;
+				}
+			}
+
+			return;
+		}
+
+		// update the next undefined trial in the trial list, and the associated snapshot:
+		for (let t = 0; t < this._trialList.length; ++t)
+		{
+			if (typeof this._trialList[t] === "undefined")
+			{
+				this._trialList[t] = { [this._varName]: this._questValue };
+
+				if (typeof this._snapshots[t] !== "undefined")
+				{
+					this._snapshots[t][this._varName] = this._questValue;
+					this._snapshots[t].trialAttributes.push(this._varName);
+				}
+				break;
+			}
+		}
+	}
+}
+
+/**
+ * QuestHandler method
+ *
+ * @enum {Symbol}
+ * @readonly
+ */
+QuestHandler.Method = {
+	/**
+	 * Quantile threshold estimate.
+	 */
+	QUANTILE: Symbol.for("QUANTILE"),
+
+	/**
+	 * Mean threshold estimate.
+	 */
+	MEAN: Symbol.for("MEAN"),
+
+	/**
+	 * Mode threshold estimate.
+	 */
+	MODE: Symbol.for("MODE")
+};
+
+
+
+ + + + + + +
+ +
+ +
+ Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
+ + + + + + + + + + + diff --git a/docs/data_Shelf.js.html b/docs/data_Shelf.js.html new file mode 100644 index 00000000..b0325191 --- /dev/null +++ b/docs/data_Shelf.js.html @@ -0,0 +1,911 @@ + + + + + + data/Shelf.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

data/Shelf.js

+ + + + + + + +
+
+
/**
+ * Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
+ * server, and can be accessed and manipulated in a concurrent fashion.
+ *
+ * @author Alain Pitiot
+ * @version 2021.2.3
+ * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @license Distributed under the terms of the MIT License
+ */
+
+import {PsychObject} from "../util/PsychObject.js";
+import {PsychoJS} from "../core/PsychoJS.js";
+import {ExperimentHandler} from "./ExperimentHandler";
+import {Scheduler} from "../util/Scheduler.js";
+
+
+/**
+ * <p>Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the
+ * server, and can be accessed and manipulated in a concurrent fashion.</p>
+ *
+ * @extends PsychObject
+ */
+export class Shelf extends PsychObject
+{
+	/**
+	 * Maximum number of components in a key
+	 * @type {number}
+	 * @note this value should mirror that on the server, i.e. the server also checks that the key is valid
+	 */
+	static #MAX_KEY_LENGTH = 10;
+
+	/**
+	 * @memberOf module:data
+	 * @param {Object} options
+	 * @param {module:core.PsychoJS} options.psychoJS 	the PsychoJS instance
+	 * @param {boolean} [options.autoLog= false] 				whether to log
+	 */
+	constructor({psychoJS, autoLog = false } = {})
+	{
+		super(psychoJS);
+
+		this._addAttribute('autoLog', autoLog);
+		this._addAttribute('status', Shelf.Status.READY);
+
+		// minimum period of time, in ms, before two calls to Shelf methods, i.e. throttling:
+		this._throttlingPeriod_ms = 500.0;
+
+		// timestamp of the last actual call to a Shelf method:
+		this._lastCallTimestamp = 0.0;
+		// timestamp of the last scheduled call to a Shelf method:
+		this._lastScheduledCallTimestamp = 0.0;
+	}
+
+	/**
+	 * Get the value of a record of type BOOLEAN associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					 	key as an array of key components
+	 * @param {boolean} options.defaultValue		the default value returned if no record with the given key exists
+	 * 	on the shelf
+	 * @return {Promise<boolean>}								the value associated with the key
+	 * @throws {Object.<string, *>} 						exception if there is a record associated with the given key
+	 * 	but it is not of type BOOLEAN
+	 */
+	getBooleanValue({key, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.BOOLEAN, {defaultValue});
+	}
+
+	/**
+	 * Set the value of a record of type BOOLEAN associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 	key as an array of key components
+	 * @param {boolean} options.value 		the new value
+	 * @return {Promise<boolean>}					the new value
+	 * @throws {Object.<string, *>} 			exception if value is not a boolean, or if there is no record with the given
+	 * 	key, or if there is a record but it is locked or it is not of type BOOLEAN
+	 */
+	setBooleanValue({key, value} = {})
+	{
+		// check the value:
+		if (typeof value !== "boolean")
+		{
+			throw {
+				origin: "Shelf.setIntegerValue",
+				context: `when setting the value of the BOOLEAN record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be a boolean"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "SET",
+			value
+		};
+		return this._updateValue(key, Shelf.Type.BOOLEAN, update);
+	}
+
+	/**
+	 * Flip the value of a record of type BOOLEAN associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @return {Promise<boolean>}				the new, flipped, value
+	 * @throws {Object.<string, *>} 		exception if there is no record with the given key, or
+	 * 	if there is a record but it is not of type BOOLEAN
+	 */
+	flipBooleanValue({key} = {})
+	{
+		// update the value:
+		const update = {
+			action: "FLIP"
+		};
+		return this._updateValue(key, Shelf.Type.BOOLEAN, update);
+	}
+
+	/**
+	 * Get the value of a record of type INTEGER associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 			key as an array of key components
+	 * @param {number} options.defaultValue		the default value returned if no record with the given key
+	 * 	exists on the shelf
+	 * @return {Promise<number>}							the value associated with the key
+	 * @throws {Object.<string, *>} 					exception if there is no record with the given key,
+	 * 	or if there is a record but it is locked or it is not of type BOOLEAN
+	 */
+	getIntegerValue({key, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.INTEGER, {defaultValue});
+	}
+
+	/**
+	 * Set the value of a record of type INTEGER associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @param {number} options.value 		the new value
+	 * @return {Promise<number>} 				the new value
+	 * @throws {Object.<string, *>} 		exception if value is not an integer, or or if there is no record
+	 * 	with the given key, or if there is a record but it is locked or it is not of type INTEGER
+	 */
+	setIntegerValue({key, value} = {})
+	{
+		// check the value:
+		if (!Number.isInteger(value))
+		{
+			throw {
+				origin: "Shelf.setIntegerValue",
+				context: `when setting the value of the INTEGER record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be an integer"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "SET",
+			value
+		};
+		return this._updateValue(key, Shelf.Type.INTEGER, update);
+	}
+
+	/**
+	 * Add a delta to  the value of a record of type INTEGER associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 	key as an array of key components
+	 * @param {number} options.delta 		the delta, positive or negative, to add to the value
+	 * @return {Promise<number>} 					the new value
+	 * @throws {Object.<string, *>} 			exception if delta is not an integer, or if there is no record with the given
+	 * 	key, or if there is a record but it is locked or it is not of type INTEGER
+	 */
+	addIntegerValue({key, delta} = {})
+	{
+		// check the delta:
+		if (!Number.isInteger(delta))
+		{
+			throw {
+				origin: "Shelf.setIntegerValue",
+				context: `when adding a value to the value of the INTEGER record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be an integer"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "ADD",
+			delta
+		};
+		return this._updateValue(key, Shelf.Type.INTEGER, update);
+	}
+
+	/**
+	 * Get the value of a record of type TEXT associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					 	key as an array of key components
+	 * @param {string} options.defaultValue		the default value returned if no record with the given key exists on
+	 * 	the shelf
+	 * @return {Promise<string>}									the value associated with the key
+	 * @throws {Object.<string, *>} 						exception if there is a record associated with the given key but it is
+	 * 	not of type TEXT
+	 */
+	getTextValue({key, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.TEXT, {defaultValue});
+	}
+
+	/**
+	 * Set the value of a record of type TEXT associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 	key as an array of key components
+	 * @param {string} options.value 			the new value
+	 * @return {Promise<string>} 					the new value
+	 * @throws {Object.<string, *>} 			exception if value is not a string, or if there is a record associated
+	 * 	with the given key but it is not of type TEXT
+	 */
+	setTextValue({key, value} = {})
+	{
+		// check the value:
+		if (typeof value !== "string")
+		{
+			throw {
+				origin: "Shelf.setTextValue",
+				context: `when setting the value of the TEXT record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be a string"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "SET",
+			value
+		};
+		return this._updateValue(key, Shelf.Type.TEXT, update);
+	}
+
+	/**
+	 * Get the value of a record of type LIST associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					 			key as an array of key components
+	 * @param {Array.<*>} options.defaultValue		the default value returned if no record with the given key exists on
+	 * 	the shelf
+	 * @return {Promise<Array.<*>>}								the value associated with the key
+	 * @throws {Object.<string, *>} 								exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type LIST
+	 */
+	getListValue({key, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.LIST, {defaultValue});
+	}
+
+	/**
+	 * Set the value of a record of type LIST associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 		key as an array of key components
+	 * @param {Array.<*>} options.value 	the new value
+	 * @return {Promise<Array.<*>>}				the new value
+	 * @throws {Object.<string, *>} 				exception if value is not an array or if there is no record with the given key,
+	 * 	or if there is a record but it is locked or it is not of type LIST
+	 */
+	setListValue({key, value} = {})
+	{
+		// check the value:
+		if (!Array.isArray(value))
+		{
+			throw {
+				origin: "Shelf.setListValue",
+				context: `when setting the value of the LIST record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be an array"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "SET",
+			value
+		};
+		return this._updateValue(key, Shelf.Type.LIST, update);
+	}
+
+	/**
+	 * Append an element, or a list of elements, to the value of a record of type LIST associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @param {*} options.elements 		the element or list of elements to be appended
+	 * @return {Promise<Array.<*>>}		the new value
+	 * @throws {Object.<string, *>} 		exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type LIST
+	 */
+	appendListValue({key, elements} = {})
+	{
+		// update the value:
+		const update = {
+			action: "APPEND",
+			elements
+		};
+		return this._updateValue(key, Shelf.Type.LIST, update);
+	}
+
+	/**
+	 * Pop an element, at the given index, from the value of a record of type LIST associated
+	 * with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key						key as an array of key components
+	 * @param {number} [options.index = -1] 	the index of the element to be popped
+	 * @return {Promise<*>}											the popped element
+	 * @throws {Object.<string, *>} 						exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type LIST
+	 */
+	popListValue({key, index = -1} = {})
+	{
+		// update the value:
+		const update = {
+			action: "POP",
+			index
+		};
+		return this._updateValue(key, Shelf.Type.LIST, update);
+	}
+
+	/**
+	 * Empty the value of a record of type LIST associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @return {Promise<Array.<*>>}		the new, empty value, i.e. []
+	 * @throws {Object.<string, *>} 		exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type LIST
+	 */
+	clearListValue({key} = {})
+	{
+		// update the value:
+		const update = {
+			action: "CLEAR"
+		};
+		return this._updateValue(key, Shelf.Type.LIST, update);
+	}
+
+	/**
+	 * Shuffle the elements of the value of a record of type LIST associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @return {Promise<Array.<*>>}		the new, shuffled value
+	 * @throws {Object.<string, *>} 		exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type LIST
+	 */
+	shuffleListValue({key} = {})
+	{
+		// update the value:
+		const update = {
+			action: "SHUFFLE"
+		};
+		return this._updateValue(key, Shelf.Type.LIST, update);
+	}
+
+
+	/**
+	 * Get the names of the fields in the dictionary record associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		key as an array of key components
+	 * @return {Promise<string[]>}			the list of field names
+	 * @throws {Object.<string, *>} 		exception if there is no record with the given key, or if there is a record
+	 * 	but it is locked or it is not of type DICTIONARY
+	 */
+	async getDictionaryFieldNames({key} = {})
+	{
+		return this._getValue(key, Shelf.Type.DICTIONARY, {fieldNames: true});
+	}
+
+	/**
+	 * Get the value of a given field in the dictionary record associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					 	key as an array of key components
+	 * @param {string} options.fieldName				the name of the field
+	 * @param {boolean} options.defaultValue		the default value returned if no record with the given key exists on
+	 * 	the shelf, or if is a record of type DICTIONARY with the given key but it has no such field
+	 * @return {Promise<*>}											the value of that field
+	 * @throws {Object.<string, *>} 						exception if there is no record with the given key,
+	 * 	or if there is a record but it is locked or it is not of type DICTIONARY
+	 */
+	async getDictionaryFieldValue({key, fieldName, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.DICTIONARY, {fieldName, defaultValue});
+	}
+
+	/**
+	 * Set a field in the dictionary record associated to the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					key as an array of key components
+	 * @param {string} options.fieldName			the name of the field
+	 * @param {*} options.fieldValue					the value of the field
+	 * @return {Promise<Object.<string, *>>}	the updated dictionary
+	 * @throws {Object.<string, *>} 					exception if there is no record with the given key,
+	 * 	or if there is a record but it is locked or it is not of type DICTIONARY
+	 */
+	async setDictionaryFieldValue({key, fieldName, fieldValue} = {})
+	{
+		// update the value:
+		const update = {
+			action: "FIELD_SET",
+			fieldName,
+			fieldValue
+		};
+		return this._updateValue(key, Shelf.Type.DICTIONARY, update);
+	}
+
+	/**
+	 * Get the value of a record of type DICTIONARY associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key		 									key as an array of key components
+	 * @param {Object.<string, *>} options.defaultValue		the default value returned if no record with the given key
+	 * 	exists on the shelf
+	 * @return {Promise<Object.<string, *>>}							the value associated with the key
+	 * @throws {Object.<string, *>} 											exception if there is no record with the given key,
+	 * 	or if there is a record but it is locked or it is not of type DICTIONARY
+	 */
+	getDictionaryValue({key, defaultValue} = {})
+	{
+		return this._getValue(key, Shelf.Type.DICTIONARY, {defaultValue});
+	}
+
+	/**
+	 * Set the value of a record of type DICTIONARY associated with the given key.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key							key as an array of key components
+	 * @param {Object.<string, *>} options.value 	the new value
+	 * @return {Promise<Object.<string, *>>} 			the new value
+	 * @throws {Object.<string, *>} 							exception if value is not an object, or or if there is no record
+	 * 	with the given key, or if there is a record but it is locked or it is not of type DICTIONARY
+	 */
+	setDictionaryValue({key, value} = {})
+	{
+		// check the value:
+		if (typeof value !== "object")
+		{
+			throw {
+				origin: "Shelf.setDictionaryValue",
+				context: `when setting the value of the DICTIONARY record associated with the key: ${JSON.stringify(key)}`,
+				error: "the value should be an object"
+			};
+		}
+
+		// update the value:
+		const update = {
+			action: "SET",
+			value
+		};
+		return this._updateValue(key, Shelf.Type.DICTIONARY, update);
+	}
+
+	/**
+	 * Schedulable component that will block the experiment until the counter associated with the given key
+	 * has been incremented by the given amount.
+	 *
+	 * @param key
+	 * @param increment
+	 * @param callback
+	 * @returns {function(): module:util.Scheduler.Event|Symbol|*} a component that can be scheduled
+	 *
+	 * @example
+	 * const flowScheduler = new Scheduler(psychoJS);
+	 * var experimentCounter = '<>';
+	 * flowScheduler.add(psychoJS.shelf.incrementComponent(['counter'], 1, (value) => experimentCounter = value));
+	 */
+	incrementComponent(key = [], increment = 1, callback)
+	{
+		const response = {
+			origin: 'Shelf.incrementComponent',
+			context: 'when making a component to increment a shelf counter'
+		};
+
+		try
+		{
+			// TODO replace this._incrementComponent by a component with a unique name
+			let incrementComponent = {};
+			incrementComponent.status = PsychoJS.Status.NOT_STARTED;
+			return () =>
+			{
+				if (incrementComponent.status === PsychoJS.Status.NOT_STARTED)
+				{
+					incrementComponent.status = PsychoJS.Status.STARTED;
+					this.increment(key, increment)
+						.then( (newValue) =>
+						{
+							callback(newValue);
+							incrementComponent.status = PsychoJS.Status.FINISHED;
+						});
+				}
+
+				return (incrementComponent.status === PsychoJS.Status.FINISHED) ?
+					Scheduler.Event.NEXT :
+					Scheduler.Event.FLIP_REPEAT;
+			};
+		}
+		catch (error)
+		{
+			this._status = Shelf.Status.ERROR;
+			throw {...response, error};
+		}
+	}
+
+	/**
+	 * Get the name of a group, using a counterbalanced design.
+	 *
+	 * @param {Object} options
+	 * @param {string[]} options.key					key as an array of key components
+	 * @param {string[]} options.groups				the names of the groups
+	 * @param {number[]} options.groupSizes		the size of the groups
+	 * @return {Promise<{string, boolean}>}		an object with the name of the selected group and whether all groups
+	 * 	have been depleted
+	 */
+	async counterBalanceSelect({key, groups, groupSizes} = {})
+	{
+		const response = {
+			origin: 'Shelf.counterBalanceSelect',
+			context: `when getting the name of a group, using a counterbalanced design, with key: ${JSON.stringify(key)}`
+		};
+
+		try
+		{
+			await this._checkAvailability("counterBalanceSelect");
+			this._checkKey(key);
+
+			// prepare the request:
+			const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/counterbalance`;
+			const data = {
+				key,
+				groups,
+				groupSizes
+			};
+
+			// query the server:
+			const putResponse = await fetch(url, {
+				method: 'PUT',
+				mode: 'cors',
+				cache: 'no-cache',
+				credentials: 'same-origin',
+				redirect: 'follow',
+				referrerPolicy: 'no-referrer',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify(data)
+			});
+
+			// convert the response to json:
+			const document = await putResponse.json();
+
+			if (putResponse.status !== 200)
+			{
+				throw ('error' in document) ? document.error : document;
+			}
+
+			// return the updated value:
+			this._status = Shelf.Status.READY;
+			return {
+				group: document.group,
+				finished: document.finished
+			};
+		}
+		catch (error)
+		{
+			this._status = Shelf.Status.ERROR;
+			throw {...response, error};
+		}
+	}
+
+
+	/**
+	 * Update the value associated with the given key.
+	 *
+	 * <p>This is a generic method, typically called from the Shelf helper methods, e.g. setBinaryValue.</p>
+	 *
+	 * @param {string[]} key					 	key as an array of key components
+	 * @param {Shelf.Type} type 				the type of the record associated with the given key
+	 * @param {*} update 							the desired update
+	 * @return {Promise<any>}					the updated value
+	 * @throws {Object.<string, *>} 	exception if there is no record associated with the given key or if there is one
+	 * 	but it is not of the given type
+	 */
+	async _updateValue(key, type, update)
+	{
+		const response = {
+			origin: 'Shelf._updateValue',
+			context: `when updating the value of the ${Symbol.keyFor(type)} record associated with key: ${JSON.stringify(key)}`
+		};
+
+		try
+		{
+			await this._checkAvailability("_updateValue");
+			this._checkKey(key);
+
+			// prepare the request:
+			const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/value`;
+			const data = {
+				key,
+				type: Symbol.keyFor(type),
+				update
+			};
+
+			// query the server:
+			const postResponse = await fetch(url, {
+				method: 'POST',
+				mode: 'cors',
+				cache: 'no-cache',
+				credentials: 'same-origin',
+				redirect: 'follow',
+				referrerPolicy: 'no-referrer',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify(data)
+			});
+
+			// convert the response to json:
+			const document = await postResponse.json();
+
+			if (postResponse.status !== 200)
+			{
+				throw ('error' in document) ? document.error : document;
+			}
+
+			// return the updated value:
+			this._status = Shelf.Status.READY;
+			return document.value;
+		}
+		catch (error)
+		{
+			this._status = Shelf.Status.ERROR;
+			throw {...response, error};
+		}
+	}
+
+	/**
+	 * Get the value associated with the given key.
+	 *
+	 * <p>This is a generic method, typically called from the Shelf helper methods, e.g. getBinaryValue.</p>
+	 *
+	 * @param {string[]} key					 	key as an array of key components
+	 * @param {Shelf.Type} type 				the type of the record associated with the given key
+	 * @param {Object} [options] 			the options, e.g. the default value returned if no record with the
+	 * given key exists on the shelf
+	 * @return {Promise<any>}					the value
+	 * @throws {Object.<string, *>} 	exception if there is a record associated with the given key but it is not of
+	 * 	the given type
+	 */
+	async _getValue(key, type, options)
+	{
+		const response = {
+			origin: 'Shelf._getValue',
+			context: `when getting the value of the ${Symbol.keyFor(type)} record associated with key: ${JSON.stringify(key)}`
+		};
+
+		try
+		{
+			await this._checkAvailability("_getValue");
+			this._checkKey(key);
+
+			// prepare the request:
+			const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/value`;
+			const data = {
+				key,
+				type: Symbol.keyFor(type)
+			};
+
+			if (typeof options !== 'undefined')
+			{
+				for (const attribute in options)
+				{
+					if (typeof options[attribute] !== "undefined")
+					{
+						data[attribute] = options[attribute];
+					}
+				}
+			}
+
+			// query the server:
+			const putResponse = await fetch(url, {
+				method: 'PUT',
+				mode: 'cors',
+				cache: 'no-cache',
+				credentials: 'same-origin',
+				redirect: 'follow',
+				referrerPolicy: 'no-referrer',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify(data)
+			});
+
+			const document = await putResponse.json();
+
+			if (putResponse.status !== 200)
+			{
+				throw ('error' in document) ? document.error : document;
+			}
+
+			// return the value:
+			this._status = Shelf.Status.READY;
+			return document.value;
+		}
+		catch (error)
+		{
+			this._status = Shelf.Status.ERROR;
+			throw {...response, error};
+		}
+	}
+
+	/**
+	 * Check whether it is possible to run a given shelf command.
+	 *
+	 * <p>Since all Shelf methods call _checkAvailability, we also use it as a means to throttle those calls.</p>
+	 *
+	 * @param {string} [methodName=""] - name of the method requiring a check
+	 * @throws {Object.<string, *>} exception if it is not possible to run the given shelf command
+	 */
+	_checkAvailability(methodName = "")
+	{
+		// Shelf requires access to the server, where the key/value pairs are stored:
+		if (this._psychoJS.config.environment !== ExperimentHandler.Environment.SERVER)
+		{
+			throw {
+				origin: 'Shelf._checkAvailability',
+				context: 'when checking whether Shelf is available',
+				error: 'the experiment has to be run on the server: shelf commands are not available locally'
+			};
+		}
+
+		// throttle calls to Shelf methods:
+		const self = this;
+		return new Promise((resolve, reject) =>
+		{
+			const now = performance.now();
+
+			// if the last scheduled call already occurred, schedule this one as soon as possible,
+			// taking into account the throttling period:
+			let timeoutDuration;
+			if (now > self._lastScheduledCallTimestamp)
+			{
+				timeoutDuration = Math.max(0.0, self._throttlingPeriod_ms - (now - self._lastCallTimestamp));
+				self._lastScheduledCallTimestamp = now + timeoutDuration;
+			}
+			// otherwise, schedule it after the next call:
+			else
+			{
+				self._lastScheduledCallTimestamp += self._throttlingPeriod_ms;
+				timeoutDuration = self._lastScheduledCallTimestamp;
+			}
+
+			setTimeout(
+				() => {
+					self._lastCallTimestamp = performance.now();
+					self._status = Shelf.Status.BUSY;
+					resolve();
+					},
+				timeoutDuration
+			);
+		});
+	}
+
+	/**
+	 * Check the validity of the key.
+	 *
+	 * @param {object} key 							key whose validity is to be checked
+	 * @throws {Object.<string, *>} 	exception if the key is invalid
+	 */
+	_checkKey(key)
+	{
+		// the key must be a non empty array:
+		if (!Array.isArray(key) || key.length === 0)
+		{
+			throw 'the key must be a non empty array';
+		}
+
+		if (key.length > Shelf.#MAX_KEY_LENGTH)
+		{
+			throw 'the key consists of too many components';
+		}
+
+		// the only @<component> in the key should be @designer and @experiment
+		// TODO
+	}
+}
+
+/**
+ * Shelf status
+ *
+ * @enum {Symbol}
+ * @readonly
+ */
+Shelf.Status = {
+	/**
+	 * The shelf is ready.
+	 */
+	READY: Symbol.for('READY'),
+
+	/**
+	 * The shelf is busy, e.g. storing or retrieving values.
+	 */
+	BUSY: Symbol.for('BUSY'),
+
+	/**
+	 * The shelf has encountered an error.
+	 */
+	ERROR: Symbol.for('ERROR')
+};
+
+/**
+ * Shelf record types.
+ *
+ * @enum {Symbol}
+ * @readonly
+ */
+Shelf.Type = {
+	INTEGER: Symbol.for('INTEGER'),
+	TEXT: Symbol.for('TEXT'),
+	DICTIONARY: Symbol.for('DICTIONARY'),
+	BOOLEAN: Symbol.for('BOOLEAN'),
+	LIST: Symbol.for('LIST')
+};
+
+
+
+ + + + + + +
+ +
+ +
+ Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
+ + + + + + + + + + + diff --git a/docs/data_TrialHandler.js.html b/docs/data_TrialHandler.js.html index 84726189..3695887e 100644 --- a/docs/data_TrialHandler.js.html +++ b/docs/data_TrialHandler.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: data/TrialHandler.js - - - + data/TrialHandler.js - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + -

Source: data/TrialHandler.js

+ + +
+ +

data/TrialHandler.js

+ @@ -32,40 +56,25 @@

Source: data/TrialHandler.js

* * @author Alain Pitiot * @author Hiroyuki Sogo & Sotiri Bakagiannis - better support for BOM and accented characters - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import seedrandom from 'seedrandom'; -import * as XLSX from 'xlsx'; -import {PsychObject} from '../util/PsychObject'; -import * as util from '../util/Util'; +import seedrandom from "seedrandom"; +import * as XLSX from "xlsx"; +import { PsychObject } from "../util/PsychObject.js"; +import * as util from "../util/Util.js"; /** * <p>A Trial Handler handles the importing and sequencing of conditions.</p> * - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {Array.<Object> | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource - * @param {number} options.nReps - number of repetitions - * @param {module:data.TrialHandler.Method} options.method - the trial method - * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc. - * @param {number} options.seed - seed for the random number generator - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class TrialHandler extends PsychObject { - /** * Getter for experimentHandler. - * - * @name module:core.Window#experimentHandler - * @function - * @public */ get experimentHandler() { @@ -74,45 +83,46 @@

Source: data/TrialHandler.js

/** * Setter for experimentHandler. - * - * @name module:core.Window#experimentHandler - * @function - * @public */ set experimentHandler(exp) { this._experimentHandler = exp; } - /** - * @constructor - * @public + * @param {Object} options - the handler options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {Array.<Object> | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource + * @param {number} options.nReps - number of repetitions + * @param {module:data.TrialHandler.Method} options.method - the trial method + * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc. + * @param {number} options.seed - seed for the random number generator + * @param {boolean} [options.autoLog= false] - whether or not to log * * @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead */ constructor({ - psychoJS, - trialList = [undefined], - nReps, - method = TrialHandler.Method.RANDOM, - extraInfo = [], - seed, - name, - autoLog = true - } = {}) + psychoJS, + trialList = [undefined], + nReps, + method = TrialHandler.Method.RANDOM, + extraInfo = [], + seed, + name, + autoLog = true, + } = {}) { super(psychoJS); - this._addAttribute('trialList', trialList); - this._addAttribute('nReps', nReps); - this._addAttribute('method', method); - this._addAttribute('extraInfo', extraInfo); - this._addAttribute('name', name); - this._addAttribute('autoLog', autoLog); - this._addAttribute('seed', seed); - this._prepareTrialList(trialList); - + this._addAttribute("trialList", trialList); + this._addAttribute("nReps", nReps); + this._addAttribute("method", method); + this._addAttribute("extraInfo", extraInfo); + this._addAttribute("name", name); + this._addAttribute("autoLog", autoLog); + this._addAttribute("seed", seed); + this._prepareTrialList(); + // number of stimuli this.nStim = this.trialList.length; @@ -140,7 +150,6 @@

Source: data/TrialHandler.js

// array of current snapshots: this._snapshots = []; - // setup the trial sequence: this._prepareSequence(); @@ -149,18 +158,17 @@

Source: data/TrialHandler.js

this._finished = false; } - /** * Helps go through each trial in the sequence one by one, mirrors PsychoPy. */ - next() { + next() + { const trialIterator = this[Symbol.iterator](); const { value } = trialIterator.next(); return value; } - /** * Iterator over the trial sequence. * @@ -196,7 +204,7 @@

Source: data/TrialHandler.js

if (this.thisRepN >= this.nReps) { this.thisTrial = null; - return {done: true}; + return { done: true }; } this.thisIndex = this._trialSequence[this.thisRepN][this.thisTrialN]; @@ -209,12 +217,11 @@

Source: data/TrialHandler.js

vals = (self.thisRepN, self.thisTrialN, self.thisTrial) logging.exp(msg % vals, obj=self.thisTrial)*/ - return {value: this.thisTrial, done: false}; - } + return { value: this.thisTrial, done: false }; + }, }; } - /** * Execute the callback for each trial in the sequence. * @@ -236,7 +243,6 @@

Source: data/TrialHandler.js

} } - /** * @typedef {Object} Snapshot * @property {TrialHandler} handler - the trialHandler @@ -258,7 +264,6 @@

Source: data/TrialHandler.js

* * <p>This is typically used in the LoopBegin function, in order to capture the current state of a TrialHandler</p> * - * @public * @return {Snapshot} - a snapshot of the current internal state. */ getSnapshot() @@ -281,12 +286,12 @@

Source: data/TrialHandler.js

getCurrentTrial: () => this.getTrial(currentIndex), getTrial: (index = 0) => this.getTrial(index), - addData: (key, value) => this.addData(key, value) + addData: (key, value) => this.addData(key, value), }; // add to the snapshots the current trial's attributes: const currentTrial = this.getCurrentTrial(); - const excludedAttributes = ['handler', 'name', 'nStim', 'nRemaining', 'thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'ran', 'finished']; + const excludedAttributes = ["handler", "name", "nStim", "nRemaining", "thisRepN", "thisTrialN", "thisN", "thisIndex", "ran", "finished"]; const trialAttributes = []; for (const attribute in currentTrial) { @@ -299,8 +304,8 @@

Source: data/TrialHandler.js

{ this._psychoJS.logger.warn(`attempt to replace the value of protected TrialHandler variable: ${attribute}`); } - snapshot.trialAttributes = trialAttributes; } + snapshot.trialAttributes = trialAttributes; // add the snapshot to the list: this._snapshots.push(snapshot); @@ -308,7 +313,6 @@

Source: data/TrialHandler.js

return snapshot; } - /** * Setter for the seed attribute. * @@ -317,9 +321,9 @@

Source: data/TrialHandler.js

*/ setSeed(seed, log) { - this._setAttribute('seed', seed, log); + this._setAttribute("seed", seed, log); - if (typeof seed !== 'undefined') + if (typeof seed !== "undefined") { this._randomNumberGenerator = seedrandom(seed); } @@ -329,18 +333,16 @@

Source: data/TrialHandler.js

} } - /** - * Set the internal state of this trial handler from the given snapshot. + * Set the internal state of the snapshot's trial handler from the snapshot. * - * @public - * @static - * @param {Snapshot} snapshot - the snapshot from which to update the current internal state. + * @param {Snapshot} snapshot - the snapshot from which to update the current internal state of the + * snapshot's trial handler */ static fromSnapshot(snapshot) { // if snapshot is undefined, do nothing: - if (typeof snapshot === 'undefined') + if (typeof snapshot === "undefined") { return; } @@ -354,17 +356,25 @@

Source: data/TrialHandler.js

snapshot.handler.thisIndex = snapshot.thisIndex; snapshot.handler.ran = snapshot.ran; snapshot.handler._finished = snapshot._finished; - snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial(); - // add to the trial handler the snapshot's trial attributes: + // add the snapshot's trial attributes to a global variable, whose name is derived from + // that of the handler: loops -> thisLoop (note the dropped s): + let name = snapshot.name; + if (name[name.length - 1] === "s") + { + name = name.substr(0, name.length - 1); + } + name = `this${name[0].toUpperCase()}${name.substr(1)}`; + + const value = {}; for (const attribute of snapshot.trialAttributes) { - snapshot.handler[attribute] = snapshot[attribute]; + value[attribute] = snapshot[attribute]; } + window[name] = value; } - /** * Getter for the finished attribute. * @@ -375,7 +385,6 @@

Source: data/TrialHandler.js

return this._finished; } - /** * Setter for the finished attribute. * @@ -384,18 +393,16 @@

Source: data/TrialHandler.js

set finished(isFinished) { this._finished = isFinished; - - this._snapshots.forEach( snapshot => + + this._snapshots.forEach((snapshot) => { snapshot.finished = isFinished; }); } - /** * Get the trial index. * - * @public * @return {number} the current trial index */ getTrialIndex() @@ -403,7 +410,6 @@

Source: data/TrialHandler.js

return this.thisIndex; } - /** * Set the trial index. * @@ -414,14 +420,12 @@

Source: data/TrialHandler.js

this.thisIndex = index; } - /** * Get the attributes of the trials. * * <p>Note: we assume that all trials in the trialList share the same attributes * and consequently consider only the attributes of the first trial.</p> * - * @public * @return {Array.string} the attributes */ getAttributes() @@ -440,11 +444,9 @@

Source: data/TrialHandler.js

return Object.keys(this.trialList[0]); } - /** * Get the current trial. * - * @public * @return {Object} the current trial */ getCurrentTrial() @@ -452,7 +454,6 @@

Source: data/TrialHandler.js

return this.trialList[this.thisIndex]; } - /** * Get the nth trial. * @@ -469,11 +470,9 @@

Source: data/TrialHandler.js

return this.trialList[index]; } - /** * Get the nth future or past trial, without advancing through the trial list. * - * @public * @param {number} [n = 1] - increment * @return {Object|undefined} the future trial (if n is positive) or past trial (if n is negative) * or undefined if attempting to go beyond the last trial. @@ -488,12 +487,10 @@

Source: data/TrialHandler.js

return this.trialList[this.thisIndex + n]; } - /** * Get the nth previous trial. * <p> Note: this is useful for comparisons in n-back tasks.</p> * - * @public * @param {number} [n = -1] - increment * @return {Object|undefined} the past trial or undefined if attempting to go prior to the first trial. */ @@ -502,11 +499,9 @@

Source: data/TrialHandler.js

return getFutureTrial(-abs(n)); } - /** * Add a key/value pair to data about the current trial held by the experiment handler * - * @public * @param {Object} key - the key * @param {Object} value - the value */ @@ -518,7 +513,6 @@

Source: data/TrialHandler.js

} } - /** * Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource. * @@ -546,8 +540,6 @@

Source: data/TrialHandler.js

* '5:' * '-5:-2, 9, 11:5:22' * - * @public - * @static * @param {module:core.ServerManager} serverManager - the server manager * @param {String} resourceName - the name of the resource containing the list of conditions, which must have been registered with the server manager. * @param {Object} [selection = null] - the selection @@ -558,8 +550,8 @@

Source: data/TrialHandler.js

{ try { - let resourceExtension = resourceName.split('.').pop(); - if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1) + const resourceExtension = resourceName.split(".").pop(); + if (["csv", "odp", "xls", "xlsx"].indexOf(resourceExtension) > -1) { // (*) read conditions from resource: const resourceValue = serverManager.getResource(resourceName, true); @@ -568,20 +560,20 @@

Source: data/TrialHandler.js

// which is then read in as a string const decodedResourceMaybe = new Uint8Array(resourceValue); // Could be set to 'buffer' for ASCII .csv - const type = resourceExtension === 'csv' ? 'string' : 'array'; - const decodedResource = type === 'string' ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe; + const type = resourceExtension === "csv" ? "string" : "array"; + const decodedResource = type === "string" ? (new TextDecoder()).decode(decodedResourceMaybe) : decodedResourceMaybe; const workbook = XLSX.read(decodedResource, { type }); // we consider only the first worksheet: if (workbook.SheetNames.length === 0) { - throw 'workbook should contain at least one worksheet'; + throw "workbook should contain at least one worksheet"; } const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // worksheet to array of arrays (the first array contains the fields): - const sheet = XLSX.utils.sheet_to_json(worksheet, {header: 1, blankrows: false}); + const sheet = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false }); const fields = sheet.shift(); // (*) select conditions: @@ -589,9 +581,9 @@

Source: data/TrialHandler.js

// (*) return the selected conditions as an array of 'object as map': // [ - // {field0: value0-0, field1: value0-1, ...} - // {field0: value1-0, field1: value1-1, ...} - // ... + // {field0: value0-0, field1: value0-1, ...} + // {field0: value1-0, field1: value1-1, ...} + // ... // ] let trialList = new Array(selectedRows.length - 1); for (let r = 0; r < selectedRows.length; ++r) @@ -614,7 +606,7 @@

Source: data/TrialHandler.js

value = arrayMaybe; } - if (typeof value === 'string') + if (typeof value === "string") { const numberMaybe = Number.parseFloat(value); @@ -626,7 +618,7 @@

Source: data/TrialHandler.js

else { // Parse doubly escaped line feeds - value = value.replace(/(\n)/g, '\n'); + value = value.replace(/(\n)/g, "\n"); } } @@ -637,74 +629,68 @@

Source: data/TrialHandler.js

return trialList; } - else { - throw 'extension: ' + resourceExtension + ' currently not supported.'; + throw "extension: " + resourceExtension + " currently not supported."; } } catch (error) { throw { - origin: 'TrialHandler.importConditions', + origin: "TrialHandler.importConditions", context: `when importing condition: ${resourceName}`, - error + error, }; } } - /** * Prepare the trial list. * * @protected - * @param {Array.<Object> | String} trialList - a list of trials, or the name of a condition resource + * @returns {void} */ - _prepareTrialList(trialList) + _prepareTrialList() { const response = { - origin: 'TrialHandler._prepareTrialList', - context: 'when preparing the trial list' + origin: "TrialHandler._prepareTrialList", + context: "when preparing the trial list", }; // we treat undefined trialList as a list with a single empty entry: - if (typeof trialList === 'undefined') + if (typeof this._trialList === "undefined") { this.trialList = [undefined]; } - // if trialList is an array, we make sure it is not empty: - else if (Array.isArray(trialList)) + else if (Array.isArray(this._trialList)) { - if (trialList.length === 0) + if (this._trialList.length === 0) { this.trialList = [undefined]; } } - // if trialList is a string, we treat it as the name of the condition resource: - else if (typeof trialList === 'string') + else if (typeof this._trialList === "string") { - this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList); + this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, this._trialList); } - // unknown type: else { throw Object.assign(response, { - error: 'unable to prepare trial list: unknown type: ' + (typeof trialList) + error: `unable to prepare trial list: unknown type: ${(typeof this._trialList)}` }); } } - - /* + /** * Prepare the sequence of trials. * * <p>The returned sequence is a matrix (an array of arrays) of trial indices * with nStim columns and nReps rows. Note that this is the transpose of the * matrix return by PsychoPY. - * + * * Example: with 3 trial and 5 repetitions, we get: * - sequential: * [[0 1 2] @@ -723,25 +709,24 @@

Source: data/TrialHandler.js

* </p> * * @protected - */ + **/ _prepareSequence() { const response = { - origin: 'TrialHandler._prepareSequence', - context: 'when preparing a sequence of trials' + origin: "TrialHandler._prepareSequence", + context: "when preparing a sequence of trials", }; - // get an array of the indices of the elements of trialList : + // get an array of the indices of the elements of trialList: const indices = Array.from(this.trialList.keys()); - if (this.method === TrialHandler.Method.SEQUENTIAL) + if (this._method === TrialHandler.Method.SEQUENTIAL) { this._trialSequence = Array(this.nReps).fill(indices); // transposed version: - //this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); + // this._trialSequence = indices.reduce( (seq, e) => { seq.push( Array(this.nReps).fill(e) ); return seq; }, [] ); } - - else if (this.method === TrialHandler.Method.RANDOM) + else if (this._method === TrialHandler.Method.RANDOM) { this._trialSequence = []; for (let i = 0; i < this.nReps; ++i) @@ -749,11 +734,10 @@

Source: data/TrialHandler.js

this._trialSequence.push(util.shuffle(indices.slice(), this._randomNumberGenerator)); } } - - else if (this.method === TrialHandler.Method.FULL_RANDOM) + else if (this._method === TrialHandler.Method.FULL_RANDOM) { // create a flat sequence with nReps repeats of indices: - let flatSequence = []; + const flatSequence = []; for (let i = 0; i < this.nReps; ++i) { flatSequence.push.apply(flatSequence, indices); @@ -771,42 +755,39 @@

Source: data/TrialHandler.js

} else { - throw Object.assign(response, {error: 'unknown method'}); + throw Object.assign(response, { error: "unknown method" }); } return this._trialSequence; } - } - /** * TrialHandler method * * @enum {Symbol} * @readonly - * @public */ TrialHandler.Method = { /** * Conditions are presented in the order they are given. */ - SEQUENTIAL: Symbol.for('SEQUENTIAL'), + SEQUENTIAL: Symbol.for("SEQUENTIAL"), /** * Conditions are shuffled within each repeat. */ - RANDOM: Symbol.for('RANDOM'), + RANDOM: Symbol.for("RANDOM"), /** * Conditions are fully randomised across all repeats. */ - FULL_RANDOM: Symbol.for('FULL_RANDOM'), + FULL_RANDOM: Symbol.for("FULL_RANDOM"), /** * Same as above, but named to reflect PsychoPy boileplate. */ - FULLRANDOM: Symbol.for('FULL_RANDOM') + FULLRANDOM: Symbol.for("FULL_RANDOM"), }; @@ -815,19 +796,23 @@

Source: data/TrialHandler.js

+ +
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + diff --git a/docs/fonts/Montserrat/Montserrat-Bold.eot b/docs/fonts/Montserrat/Montserrat-Bold.eot new file mode 100644 index 00000000..f2970bbd Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.eot differ diff --git a/docs/fonts/Montserrat/Montserrat-Bold.ttf b/docs/fonts/Montserrat/Montserrat-Bold.ttf new file mode 100644 index 00000000..3bfd79b6 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.ttf differ diff --git a/docs/fonts/Montserrat/Montserrat-Bold.woff b/docs/fonts/Montserrat/Montserrat-Bold.woff new file mode 100644 index 00000000..92607654 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.woff differ diff --git a/docs/fonts/Montserrat/Montserrat-Bold.woff2 b/docs/fonts/Montserrat/Montserrat-Bold.woff2 new file mode 100644 index 00000000..d9940cd1 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Bold.woff2 differ diff --git a/docs/fonts/Montserrat/Montserrat-Regular.eot b/docs/fonts/Montserrat/Montserrat-Regular.eot new file mode 100644 index 00000000..735d12b5 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.eot differ diff --git a/docs/fonts/Montserrat/Montserrat-Regular.ttf b/docs/fonts/Montserrat/Montserrat-Regular.ttf new file mode 100644 index 00000000..5da852a3 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.ttf differ diff --git a/docs/fonts/Montserrat/Montserrat-Regular.woff b/docs/fonts/Montserrat/Montserrat-Regular.woff new file mode 100644 index 00000000..bf918327 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.woff differ diff --git a/docs/fonts/Montserrat/Montserrat-Regular.woff2 b/docs/fonts/Montserrat/Montserrat-Regular.woff2 new file mode 100644 index 00000000..72d13c60 Binary files /dev/null and b/docs/fonts/Montserrat/Montserrat-Regular.woff2 differ diff --git a/docs/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot deleted file mode 100644 index 5d20d916..00000000 Binary files a/docs/fonts/OpenSans-Bold-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg deleted file mode 100644 index 3ed7be4b..00000000 --- a/docs/fonts/OpenSans-Bold-webfont.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff deleted file mode 100644 index 1205787b..00000000 Binary files a/docs/fonts/OpenSans-Bold-webfont.woff and /dev/null differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot deleted file mode 100644 index 1f639a15..00000000 Binary files a/docs/fonts/OpenSans-BoldItalic-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg deleted file mode 100644 index 6a2607b9..00000000 --- a/docs/fonts/OpenSans-BoldItalic-webfont.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff deleted file mode 100644 index ed760c06..00000000 Binary files a/docs/fonts/OpenSans-BoldItalic-webfont.woff and /dev/null differ diff --git a/docs/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot deleted file mode 100644 index 0c8a0ae0..00000000 Binary files a/docs/fonts/OpenSans-Italic-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg deleted file mode 100644 index e1075dcc..00000000 --- a/docs/fonts/OpenSans-Italic-webfont.svg +++ /dev/null @@ -1,1830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff deleted file mode 100644 index ff652e64..00000000 Binary files a/docs/fonts/OpenSans-Italic-webfont.woff and /dev/null differ diff --git a/docs/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot deleted file mode 100644 index 14868406..00000000 Binary files a/docs/fonts/OpenSans-Light-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg deleted file mode 100644 index 11a472ca..00000000 --- a/docs/fonts/OpenSans-Light-webfont.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff deleted file mode 100644 index e7860748..00000000 Binary files a/docs/fonts/OpenSans-Light-webfont.woff and /dev/null differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot deleted file mode 100644 index 8f445929..00000000 Binary files a/docs/fonts/OpenSans-LightItalic-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg deleted file mode 100644 index 431d7e35..00000000 --- a/docs/fonts/OpenSans-LightItalic-webfont.svg +++ /dev/null @@ -1,1835 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff deleted file mode 100644 index 43e8b9e6..00000000 Binary files a/docs/fonts/OpenSans-LightItalic-webfont.woff and /dev/null differ diff --git a/docs/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot deleted file mode 100644 index 6bbc3cf5..00000000 Binary files a/docs/fonts/OpenSans-Regular-webfont.eot and /dev/null differ diff --git a/docs/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg deleted file mode 100644 index 25a39523..00000000 --- a/docs/fonts/OpenSans-Regular-webfont.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff deleted file mode 100644 index e231183d..00000000 Binary files a/docs/fonts/OpenSans-Regular-webfont.woff and /dev/null differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot new file mode 100644 index 00000000..0f24510b Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg new file mode 100644 index 00000000..5384f985 --- /dev/null +++ b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg @@ -0,0 +1,978 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf new file mode 100644 index 00000000..e6c158c2 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff new file mode 100644 index 00000000..d0a1c292 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 new file mode 100644 index 00000000..d2869749 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot new file mode 100644 index 00000000..b4204488 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg new file mode 100644 index 00000000..dee0949f --- /dev/null +++ b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg @@ -0,0 +1,1049 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf new file mode 100644 index 00000000..4d56c337 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff new file mode 100644 index 00000000..4681019d Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff differ diff --git a/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 new file mode 100644 index 00000000..8ddcae37 Binary files /dev/null and b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 differ diff --git a/docs/hardware_Camera.js.html b/docs/hardware_Camera.js.html new file mode 100644 index 00000000..cd01b60e --- /dev/null +++ b/docs/hardware_Camera.js.html @@ -0,0 +1,724 @@ + + + + + + hardware/Camera.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

hardware/Camera.js

+ + + + + + + +
+
+
/** **/
+/**
+ * Manager handling the recording of video signal.
+ *
+ * @author Alain Pitiot
+ * @version 2022.2.0
+ * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org)
+ * @license Distributed under the terms of the MIT License
+ */
+
+import {Clock} from "../util/Clock.js";
+import {PsychObject} from "../util/PsychObject.js";
+import {PsychoJS} from "../core/PsychoJS.js";
+import * as util from "../util/Util.js";
+import {ExperimentHandler} from "../data/ExperimentHandler.js";
+// import {VideoClip} from "./VideoClip";
+
+
+/**
+ * <p>This manager handles the recording of video signal.</p>
+ *
+ * @name module:hardware.Camera
+ * @class
+ * @param {Object} options
+ * @param {module:core.Window} options.win - the associated Window
+ * @param {string} [options.format='video/webm;codecs=vp9'] the video format
+ * @param {Clock} [options.clock= undefined] - an optional clock
+ * @param {boolean} [options.autoLog= false] - whether or not to log
+ *
+ * @todo add video constraints as parameter
+ */
+export class Camera extends PsychObject
+{
+	constructor({win, name, format, clock, autoLog} = {})
+	{
+		super(win._psychoJS);
+
+		this._addAttribute("win", win, undefined);
+		this._addAttribute("name", name, "camera");
+		this._addAttribute("format", format, "video/webm;codecs=vp9", this._onChange);
+		this._addAttribute("clock", clock, new Clock());
+		this._addAttribute("autoLog", autoLog, false);
+		this._addAttribute("status", PsychoJS.Status.NOT_STARTED);
+
+		this._stream = null;
+		this._recorder = null;
+
+		if (this._autoLog)
+		{
+			this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`);
+		}
+	}
+
+	/**
+	 * Prompt the user for permission to use the camera on their device.
+	 *
+	 * @name module:hardware.Camera#authorize
+	 * @function
+	 * @public
+	 * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the
+	 * 	participant to wait for the camera to be initialised
+	 * @param {string} [dialogMsg] - the dialog message
+	 * @returns {boolean} whether or not the camera is ready to record
+	 */
+	async authorize(showDialog = false, dialogMsg = undefined)
+	{
+		const response = {
+			origin: "Camera.authorize",
+			context: "when authorizing access to the device's camera"
+		};
+
+		// open pop-up dialog, if required:
+		if (showDialog)
+		{
+			dialogMsg ??= "Please wait a few moments while the camera initialises. You may need to grant permission to your browser to use the camera.";
+			this.psychoJS.gui.dialog({
+				warning: dialogMsg,
+				showOK: false,
+			});
+		}
+
+		try
+		{
+			// prompt for permission and get a MediaStream:
+			// TODO use size constraints [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia]
+			this._stream = await navigator.mediaDevices.getUserMedia({
+				video: true
+			});
+		}
+		catch (error)
+		{
+			// close the dialog, if need be:
+			if (showDialog)
+			{
+				this.psychoJS.gui.closeDialog();
+			}
+
+			this._status = PsychoJS.Status.ERROR;
+			throw {...response, error};
+		}
+
+		// close the dialog, if need be:
+		if (showDialog)
+		{
+			this.psychoJS.gui.closeDialog();
+		}
+	}
+
+	/**
+	 * Query whether the camera is ready to record.
+	 *
+	 * @name module:hardware.Camera#isReady
+	 * @function
+	 * @public
+	 * @returns {boolean} true if the camera is ready to record, false otherwise
+	 */
+	get isReady()
+	{
+		return (this._recorder !== null);
+	}
+
+	/**
+	 * Get the underlying video stream.
+	 *
+	 * @name module:hardware.Camera#getStream
+	 * @function
+	 * @public
+	 * @returns {MediaStream} the video stream
+	 */
+	getStream()
+	{
+		return this._stream;
+	}
+
+	/**
+	 * Get a video element pointing to the Camera stream.
+	 *
+	 * @name module:hardware.Camera#getVideo
+	 * @function
+	 * @public
+	 * @returns {HTMLVideoElement} a video element
+	 */
+	getVideo()
+	{
+		// note: we need to return a new video each time, since the camera feed can be used by
+		// several stimuli and one of them might pause the feed
+
+		// create a video with the appropriate size:
+		const video = document.createElement("video");
+		this._videos.push(video);
+
+		video.width = this._streamSettings.width;
+		video.height = this._streamSettings.height;
+		video.autoplay = true;
+
+		// prevent clicking:
+		video.onclick = (mouseEvent) =>
+		{
+			mouseEvent.preventDefault();
+			return false;
+		};
+
+		// use the camera stream as source for the video:
+		video.srcObject = this._stream;
+
+		return video;
+	}
+
+	/**
+	 * Open the video stream.
+	 *
+	 * @name module:hardware.Camera#open
+	 * @function
+	 * @public
+	 */
+	open()
+	{
+		if (this._stream === null)
+		{
+			throw {
+				origin: "Camera.open",
+				context: "when opening the camera's video stream",
+				error: "access to the camera has not been authorized, or no camera could be found"
+			};
+		}
+
+		// prepare the recording:
+		this._prepareRecording();
+	}
+
+	/**
+	 * Submit a request to start the recording.
+	 *
+	 * @name module:hardware.Camera#record
+	 * @function
+	 * @public
+	 * @return {Promise} promise fulfilled when the recording actually starts
+	 */
+	record()
+	{
+		// if the camera is currently paused, a call to start resumes it
+		// with a new recording:
+		if (this._status === PsychoJS.Status.PAUSED)
+		{
+			return this.resume({clear: true});
+		}
+
+		if (this._status !== PsychoJS.Status.STARTED)
+		{
+			this._psychoJS.logger.debug("request to start video recording");
+
+			try
+			{
+				if (!this._recorder)
+				{
+					throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+				}
+
+				this._recorder.start();
+
+				// return a promise, which will be satisfied when the recording actually starts, which
+				// is also when the reset of the clock and the change of status takes place
+				const self = this;
+				return new Promise((resolve, reject) =>
+				{
+					self._startCallback = resolve;
+					self._errorCallback = reject;
+				});
+			}
+			catch (error)
+			{
+				this._psychoJS.logger.error("unable to start the video recording: " + JSON.stringify(error));
+				this._status = PsychoJS.Status.ERROR;
+
+				throw {
+					origin: "Camera.record",
+					context: "when starting the video recording for camera: " + this._name,
+					error
+				};
+			}
+
+		}
+
+	}
+
+	/**
+	 * Submit a request to stop the recording.
+	 *
+	 * @name module:hardware.Camera#stop
+	 * @function
+	 * @public
+	 * @param {Object} options
+	 * @return {Promise} promise fulfilled when the recording actually stopped, and the recorded
+	 * 	data was made available
+	 */
+	stop()
+	{
+		if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED)
+		{
+			this._psychoJS.logger.debug("request to stop video recording");
+
+			// stop the videos:
+			for (const video of this._videos)
+			{
+				video.pause();
+			}
+
+			// note: calling the MediaRecorder.stop will first raise a dataavailable event, and then a stop event
+			// ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/stop
+			this._recorder.stop();
+
+			// return a promise, which will be satisfied when the recording actually stops and the data
+			// has been made available:
+			const self = this;
+			return new Promise((resolve, reject) =>
+			{
+				self._stopCallback = resolve;
+				self._errorCallback = reject;
+			});
+		}
+	}
+
+	/**
+	 * Submit a request to pause the recording.
+	 *
+	 * @name module:hardware.Camera#pause
+	 * @function
+	 * @public
+	 * @return {Promise} promise fulfilled when the recording actually paused
+	 */
+	pause()
+	{
+		if (this._status === PsychoJS.Status.STARTED)
+		{
+			this._psychoJS.logger.debug("request to pause video recording");
+
+			try
+			{
+				if (!this._recorder)
+				{
+					throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+				}
+
+				// note: calling the pause method of the MediaRecorder raises a pause event
+				this._recorder.pause();
+
+				// return a promise, which will be satisfied when the recording actually pauses:
+				const self = this;
+				return new Promise((resolve, reject) =>
+				{
+					self._pauseCallback = resolve;
+					self._errorCallback = reject;
+				});
+			}
+			catch (error)
+			{
+				self._psychoJS.logger.error("unable to pause the video recording: " + JSON.stringify(error));
+				this._status = PsychoJS.Status.ERROR;
+
+				throw {
+					origin: "Camera.pause",
+					context: "when pausing the video recording for camera: " + this._name,
+					error
+				};
+			}
+
+		}
+	}
+
+	/**
+	 * Submit a request to resume the recording.
+	 *
+	 * <p>resume has no effect if the recording was not previously paused.</p>
+	 *
+	 * @name module:hardware.Camera#resume
+	 * @function
+	 * @param {Object} options
+	 * @param {boolean} [options.clear= false] whether or not to empty the video buffer before
+	 * 	resuming the recording
+	 * @return {Promise} promise fulfilled when the recording actually resumed
+	 */
+	resume({clear = false } = {})
+	{
+		if (this._status === PsychoJS.Status.PAUSED)
+		{
+			this._psychoJS.logger.debug("request to resume video recording");
+
+			try
+			{
+				if (!this._recorder)
+				{
+					throw "the recorder has not been created yet, possibly because the participant has not given the authorisation to record video";
+				}
+
+				// empty the audio buffer is needed:
+				if (clear)
+				{
+					this._audioBuffer = [];
+					this._videoBuffer.length = 0;
+				}
+
+				this._recorder.resume();
+
+				// return a promise, which will be satisfied when the recording actually resumes:
+				const self = this;
+				return new Promise((resolve, reject) =>
+				{
+					self._resumeCallback = resolve;
+					self._errorCallback = reject;
+				});
+			}
+			catch (error)
+			{
+				self._psychoJS.logger.error("unable to resume the video recording: " + JSON.stringify(error));
+				this._status = PsychoJS.Status.ERROR;
+
+				throw {
+					origin: "Camera.resume",
+					context: "when resuming the video recording for camera: " + this._name,
+					error
+				};
+			}
+
+		}
+	}
+
+	/**
+	 * Submit a request to flush the recording.
+	 *
+	 * @name module:hardware.Camera#flush
+	 * @function
+	 * @public
+	 * @return {Promise} promise fulfilled when the data has actually been made available
+	 */
+	flush()
+	{
+		if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED)
+		{
+			this._psychoJS.logger.debug("request to flush video recording");
+
+			// note: calling the requestData method of the MediaRecorder will raise a
+			// dataavailable event
+			// ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/requestData
+			this._recorder.requestData();
+
+			// return a promise, which will be satisfied when the data has been made available:
+			const self = this;
+			return new Promise((resolve, reject) =>
+			{
+				self._dataAvailableCallback = resolve;
+				self._errorCallback = reject;
+			});
+		}
+	}
+
+	/**
+	 * Get the current video recording as a VideoClip in the given format.
+	 *
+	 * @name module:hardware.Camera#getRecording
+	 * @function
+	 * @public
+	 * @param {string} tag an optional tag for the video clip
+	 * @param {boolean} [flush=false] whether or not to first flush the recording
+	 */
+	async getRecording({tag, flush = false} = {})
+	{
+		// default tag: the name of this Microphone object
+		if (typeof tag === "undefined")
+		{
+			tag = this._name;
+		}
+
+		// TODO
+	}
+
+	/**
+	 * Upload the video recording to the pavlovia server.
+	 *
+	 * @name module:hardware.Camera#_upload
+	 * @function
+	 * @protected
+	 * @param {string} tag an optional tag for the video file
+	 * @param {boolean} [waitForCompletion= false] whether to wait for completion
+	 * 	before returning
+	 * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the participant to wait for the data to be uploaded to the server
+	 * @param {string} [dialogMsg=""] - default message informing the participant to wait for the data to be uploaded to the server
+	 */
+	save({tag, waitForCompletion = false, showDialog = false, dialogMsg = ""} = {})
+	{
+		this._psychoJS.logger.info("[PsychoJS] Save video recording.");
+
+		// default tag: the name of this Camera object
+		if (typeof tag === "undefined")
+		{
+			tag = this._name;
+		}
+
+		// add a format-dependent video extension to the tag:
+		tag += util.extensionFromMimeType(this._format);
+
+		// if the video recording cannot be uploaded, e.g. the experiment is running locally, or
+		// if it is piloting mode, then we offer the video recording as a file for download:
+		if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER ||
+			this._psychoJS.config.experiment.status !== "RUNNING" ||
+			this._psychoJS._serverMsg.has("__pilotToken"))
+		{
+			const videoBlob = new Blob(this._videoBuffer);
+
+			const anchor = document.createElement("a");
+			anchor.href = window.URL.createObjectURL(videoBlob);
+			anchor.download = tag;
+			document.body.appendChild(anchor);
+			anchor.click();
+			document.body.removeChild(anchor);
+
+			return;
+		}
+
+		// upload the blob:
+		const videoBlob = new Blob(this._videoBuffer);
+		return this._psychoJS.serverManager.uploadAudioVideo({
+			mediaBlob: videoBlob,
+			tag,
+			waitForCompletion,
+			showDialog,
+			dialogMsg});
+	}
+
+	/**
+	 * Close the camera stream.
+	 *
+	 * @name module:hardware.Camera#close
+	 * @function
+	 * @public
+	 * @returns {Promise<void>} promise fulfilled when the stream has stopped and is now closed
+	 */
+	async close()
+	{
+		await this.stop();
+
+		this._videos = [];
+		this._stream = null;
+		this._recorder = null;
+	}
+
+	/**
+	 * Callback for changes to the recording settings.
+	 *
+	 * <p>Changes to the settings require the recording to stop and be re-started.</p>
+	 *
+	 * @name module:hardware.Camera#_onChange
+	 * @function
+	 * @protected
+	 */
+	_onChange()
+	{
+		if (this._status === PsychoJS.Status.STARTED)
+		{
+			this.stop();
+		}
+
+		this._prepareRecording();
+
+		this.start();
+	}
+
+	/**
+	 * Prepare the recording.
+	 *
+	 * @name module:hardware.Camera#_prepareRecording
+	 * @function
+	 * @protected
+	 */
+	_prepareRecording()
+	{
+		// empty the video buffer:
+		this._videoBuffer = [];
+		this._recorder = null;
+		this._videos = [];
+
+		// check the actual width and height:
+		this._streamSettings = this._stream.getVideoTracks()[0].getSettings();
+		this._psychoJS.logger.debug(`camera stream settings: ${JSON.stringify(this._streamSettings)}`);
+
+		// check that the specified format is supported, use default if it is not:
+		let options;
+		if (typeof this._format === "string" && MediaRecorder.isTypeSupported(this._format))
+		{
+			options = { type: this._format };
+		}
+		else
+		{
+			this._psychoJS.logger.warn(`The specified video format, ${this._format}, is not supported by this browser, using the default format instead`);
+		}
+
+		// create a video recorder:
+		this._recorder = new MediaRecorder(this._stream, options);
+
+		// setup the callbacks:
+		const self = this;
+
+		// called upon Camera.start(), at which point the audio data starts being gathered
+		// into a blob:
+		this._recorder.onstart = () =>
+		{
+			self._videoBuffer = [];
+			self._videoBuffer.length = 0;
+			self._clock.reset();
+			self._status = PsychoJS.Status.STARTED;
+			self._psychoJS.logger.debug("video recording started");
+
+			// resolve the Camera.start promise:
+			if (self._startCallback)
+			{
+				self._startCallback(self._psychoJS.monotonicClock.getTime());
+			}
+		};
+
+		// called upon Camera.pause():
+		this._recorder.onpause = () =>
+		{
+			self._status = PsychoJS.Status.PAUSED;
+			self._psychoJS.logger.debug("video recording paused");
+
+			// resolve the Camera.pause promise:
+			if (self._pauseCallback)
+			{
+				self._pauseCallback(self._psychoJS.monotonicClock.getTime());
+			}
+		};
+
+		// called upon Camera.resume():
+		this._recorder.onresume = () =>
+		{
+			self._status = PsychoJS.Status.STARTED;
+			self._psychoJS.logger.debug("video recording resumed");
+
+			// resolve the Camera.resume promise:
+			if (self._resumeCallback)
+			{
+				self._resumeCallback(self._psychoJS.monotonicClock.getTime());
+			}
+		};
+
+		// called when video data is available, typically upon Camera.stop() or Camera.flush():
+		this._recorder.ondataavailable = (event) =>
+		{
+			const data = event.data;
+
+			// add data to the buffer:
+			self._videoBuffer.push(data);
+			self._psychoJS.logger.debug("video data added to the buffer");
+
+			// resolve the data available promise, if needed:
+			if (self._dataAvailableCallback)
+			{
+				self._dataAvailableCallback(self._psychoJS.monotonicClock.getTime());
+			}
+		};
+
+		// called upon Camera.stop(), after data has been made available:
+		this._recorder.onstop = () =>
+		{
+			self._psychoJS.logger.debug("video recording stopped");
+			self._status = PsychoJS.Status.STOPPED;
+
+			// resolve the Camera.stop promise:
+			if (self._stopCallback)
+			{
+				self._stopCallback(self._psychoJS.monotonicClock.getTime());
+			}
+		};
+
+		// called upon recording errors:
+		this._recorder.onerror = (event) =>
+		{
+			// TODO
+			self._psychoJS.logger.error("video recording error: " + JSON.stringify(event));
+			self._status = PsychoJS.Status.ERROR;
+		};
+	}
+
+}
+
+
+
+
+
+ + + + + + +
+ +
+ +
+ Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
+ + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html index db316065..0ffe5df3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,53 +1,103 @@ + - JSDoc: Home - - - + Home - PsychoJS API + + + + + + + + + + - - + + + + - + + + + + + +
+ + + + + + + + + +
+

+
+ -

Home

-
-
+ + + + + + + + + +
+

PsychoJS

+

Automated Test (short) +Automated Test (full) +Contributor Covenant

PsychoJS is a JavaScript library that makes it possible to run neuroscience, psychology, and psychophysics experiments in a browser. It is the online counterpart of the PsychoPy Python library.

-

You can create online experiments from the PsychoPy Builder, you can find and adapt existing experiments on pavlovia.org, or create them from scratch: the PsychoJS API is available here.

+

You can create online experiments from the PsychoPy Builder, you can find and adapt existing experiments on pavlovia.org, or create them from scratch.

PsychoJS is an open-source project. You can contribute by submitting pull requests to the PsychoJS GitHub repository, and discuss issues and current and future features on the Online category of the PsychoPy Forum.

Motivation

Many studies in behavioural sciences (e.g. psychology, neuroscience, linguistics or mental health) use computers to present stimuli and record responses in a precise manner. These studies are still typically conducted on small numbers of people in laboratory environments equipped with dedicated hardware.

-

With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a “game changer”. Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical.

+

With high-speed broadband, improved web technologies and smart devices everywhere, studies can now go online without sacrificing too much temporal precision. This is a "game changer". Data can be collected on larger, more varied, international populations. We can study people in environments they do not find intimidating. Experiments can be run multiple times per day, without data collection becoming impractical.

The idea behind PsychoJS is to make PsychoPy experiments available online, from a web page, so participants can run them on any device equipped with a web browser such as desktops, laptops, or tablets. In some circumstance, they can even use their phone!

Getting Started

Running PsychoPy experiments online requires the generation of an index.html file and of a javascript file that contains the code describing the experiment. Those files need to be hosted on a web server to which participants will point their browser in order to run the experiment. The server will also need to host the PsychoJS library.

PsychoPy Builder

The recommended approach to creating experiments is to use PsychoPy Builder to generate the javascript and html files. Many of the existing Builder experiments should "just work", subject to the Components being compatible between PsychoPy and PsychoJS.

JavaScript Code

-

We built the PsychoJS library to make the JavaScript experiment files look and behave in very much the same way as to the Builder-generated Python files. PsychoJS offers classes such as Window and ImageStim, with very similar attributes to their Python equivalents. Experiment designers familiar with the PsychoPy library should feel at home with PsychoJS, and can expect the same level of control they have with PsychoPy, from the structure of the trials/loops all the way down to frame-by-frame updates.

-

There are however notable differences between the PsychoJS and PsychoPy libraries, most of which have to do with the way a web browser interprets and runs JavaScript, deals with resources (such as images, sound or videos), or render stimuli. To manage those web-specific aspect, PsychoJS introduces the concept of Scheduler. As the name indicate, Scheduler's offer a way to organise various PsychoJS along a timeline, such as downloading resources, running a loop, checking for keyboard input, saving experiment results, etc. As an illustration, a Flow in PsychoPy can be conceptualised as a Schedule, with various tasks on it. Some of those tasks, such as trial loops, can also schedule further events (i.e. the individual trials to be run).

-

Under the hood PsychoJS relies on PixiJs to present stimuli and collect responses. PixiJs is a multi-platform, accelerated, 2-D renderer, that runs in most modern browsers. It uses WebGL wherever possible and silently falls back to HTML5 canvas where not. WebGL directly addresses the graphic card, thereby considerably improving the rendering performance.

+

We built the PsychoJS library to make the JavaScript experiment files look and behave in very much the same way as the Builder-generated Python files. PsychoJS offers classes such as Window and ImageStim, with very similar attributes to their Python equivalents. Experiment designers familiar with the PsychoPy library should feel at home with PsychoJS, and can expect the same level of control they have with PsychoPy, from the structure of the trials/loops all the way down to frame-by-frame updates.

+

There are however notable differences between the PsychoJS and PsychoPy libraries, most of which having to do with the way a web browser interprets and runs JavaScript, deals with resources (such as images, sound or videos), or render stimuli. To manage those web-specific aspect, PsychoJS introduces the concept of Scheduler. As the name indicate, Scheduler's offer a way to organise various tasks along a timeline, such as downloading resources, running a loop, checking for keyboard input, saving experiment results, etc. As an illustration, a Flow in PsychoPy can be conceptualised as a Schedule, with various tasks on it. Some of those tasks, such as trial loops, can also schedule further events (i.e. the individual trials to be run). +taskshe hood PsychoJS relies on PixiJS to present stimuli and collect responses. PixiJS is a high performance, multi-platform 2D renderer, that runs in most modern browsers. It uses WebGL wherever possible and silently falls back to HTML5 canvas where not. WebGL directly addresses the graphic card, thereby considerably improving the rendering performance.

Hosting Experiments

A convenient way to make experiment available to participants is to host them on pavlovia.org, an open-science server. PsychoPy Builder offers the possibility of uploading the experiment directly to pavlovia.org.

Which PsychoPy Components are supported by PsychoJS?

-

The list of PsychoPy Builder Components supported by PsychoJS see the PsychoPy/JS online status page

+

For the list of PsychoPy Builder Components supported by PsychoJS see this PsychoPy/JS online status page.

+

API

+

The documentation of the PsychoJS API is available here.

Maintainers

Alain Pitiot - @apitiot

Contributors

The PsychoJS library was initially written by Ilixa with support from the Wellcome Trust. -It is now a collaborative effort, supported by the Chan Zuckerberg Initiative (2020-2021) and Open Science Tools (2020-):

+It is now a collaborative effort, supported by the Chan Zuckerberg Initiative (2020-2021) and Open Science Tools (2020-):

- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + - + \ No newline at end of file diff --git a/docs/module-core.BuilderKeyResponse.html b/docs/module-core.BuilderKeyResponse.html deleted file mode 100644 index 7a692745..00000000 --- a/docs/module-core.BuilderKeyResponse.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - JSDoc: Class: BuilderKeyResponse - - - - - - - - - - -
- -

Class: BuilderKeyResponse

- - - - - - -
- -
- -

- core.BuilderKeyResponse(options)

- - -
- -
-
- - - - - - -

new BuilderKeyResponse(options)

- - - - - - -
- Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard) -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.EventManager.html b/docs/module-core.EventManager.html deleted file mode 100644 index 310e4737..00000000 --- a/docs/module-core.EventManager.html +++ /dev/null @@ -1,1736 +0,0 @@ - - - - - JSDoc: Class: EventManager - - - - - - - - - - -
- -

Class: EventManager

- - - - - - -
- -
- -

- core.EventManager(options)

- -

This manager handles all participant interactions with the experiment, i.e. keyboard, mouse and touch events.

- - -
- -
-
- - - - -

Constructor

- - - -

new EventManager(options)

- - - - - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -

Methods

- - - - - - - -

addMouseListeners(renderer)

- - - - - - -
- Add various mouse listeners to the Pixi renderer of the Window. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
renderer - - -PIXI.Renderer - - - - The Pixi renderer
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

clearEvents()

- - - - - - -
- Clear all events from the event buffer. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
To Do:
-
-
    -
  • handle the attribs argument
  • -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

clearKeys()

- - - - - - -
- Clear all keys from the key buffer. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getKeys(options) → {Array.<string>}

- - - - - - -
- Get the list of keys pressed by the participant. - -

Note: The w3c key-event viewer can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
keyList - - -Array.<string> - - - - - - <optional>
- - - - - -
- - null - - keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely.
timeStamped - - -boolean - - - - - - <optional>
- - - - - -
- - false - - If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time).
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the list of keys that were pressed. -
- - - -
-
- Type -
-
- -Array.<string> - - -
-
- - - - - - - - - - - - - -

getMouseInfo() → {EventManager.MouseInfo}

- - - - - - -
- Get the mouse info. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the mouse info. -
- - - -
-
- Type -
-
- -EventManager.MouseInfo - - -
-
- - - - - - - - - - - - - -

keycode2w3c(keycode) → {string}

- - - - - - -
- Convert a keycode to a W3C UI Event code. -

This is for legacy browsers.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
keycode - - -number - - - - the keycode
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- corresponding W3C UI Event code -
- - - -
-
- Type -
-
- -string - - -
-
- - - - - - - - - - - - - -

pyglet2w3c(pygletKeyList) → {Array.string}

- - - - - - -
- Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values. -

This allows key lists that work in the builder environment to work in psychoJS web experiments.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
pygletKeyList - - -Array.string - - - - the array of pyglet key names
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the w3c keyList -
- - - -
-
- Type -
-
- -Array.string - - -
-
- - - - - - - - - - - - - -

resetMoveClock()

- - - - - - -
- Reset the move clock. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
To Do:
-
-
    -
  • not implemented
  • -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

startMoveClock()

- - - - - - -
- Start the move clock. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
To Do:
-
-
    -
  • not implemented
  • -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

stopMoveClock()

- - - - - - -
- Stop the move clock. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
To Do:
-
-
    -
  • not implemented
  • -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

w3c2pyglet(code) → {string}

- - - - - - -
- Convert a W3C Key Code into a pyglet key. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
code - - -string - - - - W3C Key Code
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- corresponding pyglet key -
- - - -
-
- Type -
-
- -string - - -
-
- - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.GUI.html b/docs/module-core.GUI.html deleted file mode 100644 index aec2dbdf..00000000 --- a/docs/module-core.GUI.html +++ /dev/null @@ -1,1026 +0,0 @@ - - - - - JSDoc: Class: GUI - - - - - - - - - - -
- -

Class: GUI

- - - - - - -
- -
- -

- core.GUI(psychoJS)

- -
Graphic User Interface
- - -
- -
-
- - - - -

Constructor

- - - -

new GUI(psychoJS)

- - - - - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Members

- - - -

(readonly) dialogMargin :Symbol

- - - - -
- Dialog window margins. -
- - - -
Type:
-
    -
  • - -Symbol - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(readonly) dialogMaxSize :Symbol

- - - - -
- Maximal dimensions of the dialog window. -
- - - -
Type:
-
    -
  • - -Symbol - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - -

Methods

- - - - - - - -

dialog(options)

- - - - - - -
- Show a message to the participant in a dialog box. - -

This function can be used to display both warning and error messages.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
message - - -string - - - - - - - - - - - - the message to be displayed
error - - -Object.<string, *> - - - - - - - - - - - - an exception
warning - - -string - - - - - - - - - - - - a warning message
showOK - - -boolean - - - - - - <optional>
- - - - - -
- - true - - specifies whether to show the OK button
onOK - - -GUI.onOK - - - - - - <optional>
- - - - - -
- - function called when the participant presses the OK button
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

DlgFromDict(options)

- - - - - - -
-

Create a dialog box that (a) enables the participant to set some -experimental values (e.g. the session name), (b) shows progress of resource -download, and (c) enables the participant to cancel the experiment.

- -Setting experiment values -

DlgFromDict displays an input field for all values in the dictionary. -It is possible to specify default values e.g.:

-let expName = 'stroop';
-let expInfo = {'participant':'', 'session':'01'};
-psychoJS.schedule(psychoJS.gui.DlgFromDict({dictionary: expInfo, title: expName}));
-

If the participant cancels (by pressing Cancel or by closing the dialog box), then -the dictionary remains unchanged.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
logoUrl - - -String - - - - - - <optional>
- - - - - -
Url of the experiment logo
text - - -String - - - - - - <optional>
- - - - - -
information text
dictionary - - -Object - - - - - - - - - - associative array of values for the participant to set
title - - -String - - - - - - - - - - name of the project
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.KeyPress.html b/docs/module-core.KeyPress.html deleted file mode 100644 index 183f5637..00000000 --- a/docs/module-core.KeyPress.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - JSDoc: Class: KeyPress - - - - - - - - - - -
- -

Class: KeyPress

- - - - - - -
- -
- -

- core.KeyPress(code, tDown, name)

- - -
- -
-
- - - - - - -

new KeyPress(code, tDown, name)

- - - - - - - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
code - - -string - - - - W3C Key Code
tDown - - -number - - - - time of key press (keydown event) relative to the global Monotonic Clock
name - - -string -| - -undefined - - - - pyglet key name
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.Keyboard.html b/docs/module-core.Keyboard.html deleted file mode 100644 index 21f433bf..00000000 --- a/docs/module-core.Keyboard.html +++ /dev/null @@ -1,1386 +0,0 @@ - - - - - JSDoc: Class: Keyboard - - - - - - - - - - -
- -

Class: Keyboard

- - - - - - -
- -
- -

- core.Keyboard(options)

- - -
- -
-
- - - - - - -

new Keyboard(options)

- - - - - - -
-

This manager handles all keyboard events. It is a substitute for the keyboard component of EventManager.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
psychoJS - - -module:core.PsychoJS - - - - - - - - - - - - the PsychoJS instance
bufferSize - - -number - - - - - - <optional>
- - - - - -
- - 10000 - - the maximum size of the circular keyboard event buffer
waitForStart - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to wait for a call to module:core.Keyboard#start -before recording keyboard events
clock - - -Clock - - - - - - <optional>
- - - - - -
- - an optional clock
autoLog - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to log
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Members

- - - -

(readonly) KeyStatus :Symbol

- - - - -
- Keyboard KeyStatus. -
- - - -
Type:
-
    -
  • - -Symbol - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - -

Methods

- - - - - - - -

clearEvents()

- - - - - - -
- Clear all events and resets the circular buffers. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getEvents() → {Array.<Keyboard.KeyEvent>}

- - - - - - -
- Get the list of those keyboard events still in the buffer, i.e. those that have not been -previously cleared by calls to getKeys with clear = true. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the list of events still in the buffer -
- - - -
-
- Type -
-
- -Array.<Keyboard.KeyEvent> - - -
-
- - - - - - - - - - - - - -

getKeys(options) → {Array.<KeyPress>}

- - - - - - -
- Get the list of keys pressed or pushed by the participant. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
keyList - - -Array.<string> - - - - - - <optional>
- - - - - -
- - [] - - the list of keys to consider. If keyList is empty, we consider all keys. -Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy.
waitRelease - - -boolean - - - - - - <optional>
- - - - - -
- - true - - whether or not to include those keys pressed but not released. If -waitRelease = false, key presses without a corresponding key release will have an undefined duration.
clear - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to keep in the buffer the key presses or pushes for a subsequent call to getKeys. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the list of keys that were pressed (keydown followed by keyup) or pushed -(keydown with no subsequent keyup at the time getKeys is called). -
- - - -
-
- Type -
-
- -Array.<KeyPress> - - -
-
- - - - - - - - - - - - - -

includes(keypressList, keyName) → {boolean}

- - - - - - -
- Test whether a list of KeyPress's contains one with a particular name. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
keypressList - - -Array.<module:core.KeyPress> - - - - list of KeyPress's
keyName - - -string - - - - pyglet key name, e.g. 'escape', 'left'
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- whether or not a KeyPress with the given pyglet key name is present in the list -
- - - -
-
- Type -
-
- -boolean - - -
-
- - - - - - - - - - - - - -

start()

- - - - - - -
- Start recording keyboard events. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

stop()

- - - - - - -
- Stop recording keyboard events. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.Logger.html b/docs/module-core.Logger.html deleted file mode 100644 index 55e37858..00000000 --- a/docs/module-core.Logger.html +++ /dev/null @@ -1,810 +0,0 @@ - - - - - JSDoc: Class: Logger - - - - - - - - - - -
- -

Class: Logger

- - - - - - -
- -
- -

- core.Logger(threshold)

- - -
- -
-
- - - - - - -

new Logger(threshold)

- - - - - - -
-

This class handles a variety of loggers, e.g. a browser console one (mostly for debugging), -a remote one, etc.

- -

Note: we use log4javascript for the console logger, and our own for the server logger.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
threshold - - -* - - - - the logging threshold, e.g. log4javascript.Level.ERROR
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -

Members

- - - -

(protected) _getValue

- - - - -
- Get the integer value associated with a logging level. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(protected, readonly) _ServerLevelValue :number

- - - - -
- Server logging level values. - -

We use those values to determine whether a log is to be sent to the server or not.

-
- - - -
Type:
-
    -
  • - -number - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(protected) _throttle

- - - - -
- Check whether or not a log messages must be throttled. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

data

- - - - -
- Log a server message at the DATA level. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

exp

- - - - -
- Log a server message at the EXP level. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

flush

- - - - -
- Flush all server logs to the server. - -

Note: the logs are compressed using Pako's zlib algorithm. -See https://github.com/nodeca/pako for details.

-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

log

- - - - -
- Log a server message. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(readonly) ServerLevel :Symbol

- - - - -
- Server logging level. -
- - - -
Type:
-
    -
  • - -Symbol - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

setLevel

- - - - -
- Change the logging level. -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.MinimalStim.html b/docs/module-core.MinimalStim.html deleted file mode 100644 index 13720ad4..00000000 --- a/docs/module-core.MinimalStim.html +++ /dev/null @@ -1,1098 +0,0 @@ - - - - - JSDoc: Class: MinimalStim - - - - - - - - - - -
- -

Class: MinimalStim

- - - - - - -
- -
- -

- core.MinimalStim(options)

- - -
- -
-
- - - - - - -

new MinimalStim(options)

- - - - - - -
-

MinimalStim is the base class for all stimuli.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
name - - -String - - - - - - - - - - - - the name used when logging messages from this stimulus
win - - -module:core.Window - - - - - - - - - - - - the associated Window
autoDraw - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not the stimulus should be automatically drawn on every frame flip
autoLog - - -boolean - - - - - - <optional>
- - - - - -
- - win.autoLog - - whether or not to log
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - -

Extends

- - - - -
    -
  • PsychObject
  • -
- - - - - - - - - - - - - - - - - -

Methods

- - - - - - - -

(abstract) contains(object, units)

- - - - - - -
- Determine whether an object is inside this stimulus. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
object - - -Object - - - - the object
units - - -String - - - - the stimulus units
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

draw()

- - - - - - -
- Draw this stimulus on the next frame draw. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

hide()

- - - - - - -
- Hide this stimulus on the next frame draw. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

release(logopt)

- - - - - - -
- Release the PIXI representation, if there is one. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
log - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to log
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

setAutoDraw(autoDraw, logopt)

- - - - - - -
- Setter for the autoDraw attribute. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
autoDraw - - -boolean - - - - - - - - - - - - the new value
log - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to log
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.Mouse.html b/docs/module-core.Mouse.html deleted file mode 100644 index 0d724ab7..00000000 --- a/docs/module-core.Mouse.html +++ /dev/null @@ -1,1748 +0,0 @@ - - - - - JSDoc: Class: Mouse - - - - - - - - - - -
- -

Class: Mouse

- - - - - - -
- -
- -

- core.Mouse(options)

- - -
- -
-
- - - - - - -

new Mouse(options)

- - - - - - -
-

This manager handles the interactions between the experiment's stimuli and the mouse.

-

Note: the unit of Mouse is that of its associated Window.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
name - - -String - - - - - - - - - - - - the name used when logging messages from this stimulus
win - - -Window - - - - - - - - - - - - the associated Window
autoLog - - -boolean - - - - - - <optional>
- - - - - -
- - true - - whether or not to log
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
To Do:
-
-
    -
  • visible is not handled at the moment (mouse is always visible)
  • -
-
- -
- - - - - - - - - - - - - - - - - - - - - -
- - -

Extends

- - - - -
    -
  • PsychObject
  • -
- - - - - - - - - - - - - - - - - -

Methods

- - - - - - - -

clickReset(buttonsopt)

- - - - - - -
- Reset the clocks associated to the given mouse buttons. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
buttons - - -Array.number - - - - - - <optional>
- - - - - -
- - [0,1,2] - - the buttons to reset (0: left, 1: center, 2: right)
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

getPos() → {Array.number}

- - - - - - -
- Get the current position of the mouse in mouse/Window units. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the position of the mouse in mouse/Window units -
- - - -
-
- Type -
-
- -Array.number - - -
-
- - - - - - - - - - - - - -

getPressed(getTimeopt) → {Array.number|Array.<Array.number>}

- - - - - - -
- Get the status of each button (pressed or released) and, optionally, the time elapsed between the last call to clickReset and the pressing or releasing of the buttons. - -

Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
getTime - - -boolean - - - - - - <optional>
- - - - - -
- - false - - whether or not to also return timestamps
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps. -
- - - -
-
- Type -
-
- -Array.number -| - -Array.<Array.number> - - -
-
- - - - - - - - - - - - - -

getRel() → {Array.number}

- - - - - - -
- Get the position of the mouse relative to that at the last call to getRel -or getPos, in mouse/Window units. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the relation position of the mouse in mouse/Window units. -
- - - -
-
- Type -
-
- -Array.number - - -
-
- - - - - - - - - - - - - -

getWheelRel() → {Array.number}

- - - - - - -
- Get the travel of the mouse scroll wheel since the last call to getWheelRel. - -

Note: Even though this method returns a [x, y] array, for most wheels/systems y is the only -value that varies.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the mouse scroll wheel travel -
- - - -
-
- Type -
-
- -Array.number - - -
-
- - - - - - - - - - - - - -

isPressedIn(shape, buttonsopt, optionsopt) → {boolean}

- - - - - - -
- Helper method for checking whether a stimulus has had any button presses within bounds. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
shape - - -object -| - -module:visual.VisualStim - - - - - - - - - - A type of visual stimulus or object having a `contains()` method.
buttons - - -object -| - -number - - - - - - <optional>
- - - - - -
The target button index potentially tucked inside an object.
options - - -object - - - - - - <optional>
- - - - - -
-
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
shape - - -object -| - -module:visual.VisualStim - - - - - - <optional>
- - - - - -
buttons - - -number - - - - - - <optional>
- - - - - -
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- Whether button pressed is contained within stimulus. -
- - - -
-
- Type -
-
- -boolean - - -
-
- - - - - - - - - - - - - -

mouseMoved(distanceopt, resetopt) → {boolean}

- - - - - - -
- Determine whether the mouse has moved beyond a certain distance. - -

distance -

    -
  • mouseMoved() or mouseMoved(undefined, false): determine whether the mouse has moved at all since the last -call to getPos
  • -
  • mouseMoved(distance: number, false): determine whether the mouse has travelled further than distance, in terms of line of sight
  • -
  • mouseMoved(distance: [number,number], false): determine whether the mouse has travelled horizontally or vertically further then the given horizontal and vertical distances
  • -

- -

reset -

    -
  • mouseMoved(distance, true): reset the mouse move clock, return false
  • -
  • mouseMoved(distance, 'here'): return false
  • -
  • mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance
  • -

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
distance - - -undefined -| - -number -| - -Array.number - - - - - - <optional>
- - - - - -
- - the distance to which the mouse movement is compared (see above for a full description)
reset - - -boolean -| - -String -| - -Array.number - - - - - - <optional>
- - - - - -
- - false - - see above for a full description
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- see above for a full description -
- - - -
-
- Type -
-
- -boolean - - -
-
- - - - - - - - - - - - - -

mouseMoveTime() → {number}

- - - - - - -
- Get the amount of time elapsed since the last mouse movement. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- the time elapsed since the last mouse movement -
- - - -
-
- Type -
-
- -number - - -
-
- - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-core.PsychoJS.html b/docs/module-core.PsychoJS.html index 9172bd56..db16989d 100644 --- a/docs/module-core.PsychoJS.html +++ b/docs/module-core.PsychoJS.html @@ -1,23 +1,47 @@ + - JSDoc: Class: PsychoJS - - - + PsychoJS - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + + + -

Class: PsychoJS

+
+ +

PsychoJS

+ @@ -28,29 +52,79 @@

Class: PsychoJS

-

- core.PsychoJS(options)

+

+ core. -

PsychoJS manages the lifecycle of an experiment. It initialises the PsychoJS library and its various components (e.g. the ServerManager, the EventManager), and is used by the experiment to schedule the various tasks.

+ PsychoJS +

+ +

PsychoJS initialises the library and its various components (e.g. the ServerManager, the EventManager), and manages +the lifecycle of an experiment.

-
+
+

Constructor

-

new PsychoJS(options)

+ + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + @@ -159,12 +233,12 @@
Properties
- true + true - whether or not to log debug information in the browser console +

whether to log debug information in the browser console

@@ -198,12 +272,12 @@
Properties
- false + false - whether or not to collect the IP information of the participant +

whether to collect the IP information of the participant

@@ -222,37 +296,21 @@
Properties
-
- - - - - - - - - - - - - -
Source:
-
+ +
@@ -260,30 +318,31 @@
Properties
- - - - - - - - - - - + + + + + +

Members

+ + +

(static, readonly) Status :Symbol

+
-
- +
Source:
+
@@ -297,32 +356,25 @@
Properties
-

Members

- - - -

(static, readonly) Status :Symbol

- - + -
- PsychoJS status. -
+ + + -
Type:
-
    -
  • - -Symbol + + -
  • -
+ + + + @@ -562,64 +614,43 @@
Properties:
-
- - - - - - +
+

PsychoJS status.

+
- - - +
Type:
+
    +
  • + +Symbol - - +
  • +
- - - - -
Source:
-
- - + -
- - - - - +

Methods

-

status

- - - - -
- Properties -
+ +

(protected) _captureErrors()

+ @@ -627,7 +658,10 @@

status - +
Source:
+
@@ -651,10 +685,7 @@

statusSource: -
+ @@ -668,29 +699,18 @@

status +

Capture all errors and display them in a pop-up error box.

+

- - - -

Methods

- - - - -

(protected) _captureErrors()

- - -
- Capture all errors and display them in a pop-up error box. -
@@ -704,36 +724,30 @@

(protected) - - - - - - + + - +

(async, protected) _configure(configURL, name)

- - - + +
Source:
@@ -742,42 +756,38 @@

(protected) - - - - - - - - - - - - + + + + + + + - - + -

(async, protected) _configure(configURL, name)

+ +

+ -
- Configure PsychoJS for the running experiment. + +
+

Configure PsychoJS for the running experiment.

@@ -788,6 +798,8 @@

(async, protected) + +
Parameters:
@@ -829,7 +841,7 @@
Parameters:
- the URL of the configuration file +

the URL of the configuration file

@@ -852,7 +864,7 @@
Parameters:
- the name of the experiment +

the name of the experiment

@@ -864,12 +876,38 @@
Parameters:
-
+ + + + + + + + + + + + + + + + +

(async, protected) _getParticipantIPInfo()

+ + + +
+ + +
Source:
+
@@ -891,10 +929,9 @@
Parameters:
-
Source:
-
+ + + @@ -908,6 +945,10 @@
Parameters:
+
+

Get the IP information of the participant, asynchronously.

+

Note: we use http://www.geoplugin.net/json.gp.

+
@@ -923,33 +964,27 @@
Parameters:
- - - - -

(async, protected) _getParticipantIPInfo()

- - -
- Get the IP information of the participant, asynchronously. -

Note: we use http://www.geoplugin.net/json.gp.

-
+ + + +

(protected) _makeStatusTopLevel()

+ @@ -957,7 +992,10 @@

(async,
- +
Source:
+
@@ -981,10 +1019,7 @@

(async, -
Source:
-
+ @@ -998,6 +1033,9 @@

(async, +
+

Make the various Status top level, in order to accommodate PsychoPy's Code Components.

+
@@ -1013,22 +1051,12 @@

(async, - - - - -

getEnvironment() → {ExperimentHandler.Environment|undefined}

- - -
- Get the experiment's environment. -
@@ -1036,8 +1064,14 @@

getEnvi + + + +

getEnvironment() → {ExperimentHandler.Environment|undefined}

+ + @@ -1045,7 +1079,10 @@

getEnvi
- +
Source:
+
@@ -1069,10 +1106,7 @@

getEnvi -
Source:
-
+ @@ -1086,6 +1120,24 @@

getEnvi +
+

Get the experiment's environment.

+
+ + + + + + + + + + + + + + + @@ -1100,12 +1152,12 @@

Returns:
- the environment of the experiment, or undefined +

the environment of the experiment, or undefined

-
+
Type
@@ -1114,34 +1166,74 @@
Returns:
ExperimentHandler.Environment | -undefined +undefined + + + +
+ + + + + + + + + + +

importAttributes(obj)

+ + + + + + +
+ + +
Source:
+
+ + + + - -
+ + + + + - - + -

importAttributes(obj)

+ + + + + +
+ + -
- Make the attributes of the given object those of PsychoJS and those of -the top level variable (e.g. window) as well. +
+

Make the attributes of the given object those of window, such that they become global.

@@ -1152,6 +1244,8 @@

impor + +

Parameters:
@@ -1193,7 +1287,7 @@
Parameters:
- the object whose attributes we will mirror +

the object whose attributes are to become global

@@ -1205,81 +1299,77 @@
Parameters:
-
- - - - - - - - - - - - - -
Source:
-
- + + - +

openWindow(options)

-
- - - - +
+ +
Source:
+
+ + + + + + + + + + + - - + -

openWindow(options)

+ +
+ -
- Open a PsychoJS Window. +
+

Open a PsychoJS Window.

This opens a PIXI canvas.

Note: we can only open one window.

@@ -1292,6 +1382,8 @@

openWindow< + +

Parameters:
@@ -1387,7 +1479,7 @@
Properties
- the name of the window +

the name of the window

@@ -1420,7 +1512,7 @@
Properties
- whether or not to go fullscreen +

whether or not to go fullscreen

@@ -1433,7 +1525,7 @@
Properties
-Color +Color @@ -1453,7 +1545,7 @@
Properties
- the background color of the window +

the background color of the window

@@ -1486,7 +1578,7 @@
Properties
- the units of the window +

the units of the window

@@ -1519,7 +1611,7 @@
Properties
- whether or not to log +

whether or not to log

@@ -1552,8 +1644,8 @@
Properties
- whether or not to wait for all rendering operations to be done -before flipping +

whether or not to wait for all rendering operations to be done +before flipping

@@ -1572,50 +1664,6 @@
Properties
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - @@ -1630,13 +1678,13 @@
Throws:
-
- exception if a window has already been opened +
+

exception if a window has already been opened

-
+
Type
@@ -1658,25 +1706,65 @@
Throws:
- - -

(async) quit(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
-
- Close everything and exit nicely at the end of the experiment, -potentially redirecting to one of the URLs previously specified by setRedirectUrls. + + +
+

Close everything and exit nicely at the end of the experiment, +potentially redirecting to one of the URLs previously specified by setRedirectUrls.

Note: if the resource manager is busy, we inform the participant that he or she needs to wait for a bit.

@@ -1689,6 +1777,8 @@

(async) quitParameters:

@@ -1790,7 +1880,7 @@
Properties
- optional message to be displayed in a dialog box before quitting +

optional message to be displayed in a dialog box before quitting

@@ -1824,12 +1914,12 @@
Properties
- false + false - whether or not the participant has completed the experiment +

whether the participant has completed the experiment

@@ -1848,80 +1938,77 @@
Properties
-
- - - - - - - - - - - - - -
Source:
-
- + + - +

schedule(task, args)

-
- - - - +
+ +
Source:
+
+ + + + + + + + + + + - - + -

schedule(task, args)

+ +
+ -
- Schedule a task. + +
+

Schedule a task.

@@ -1932,6 +2019,8 @@

scheduleParameters:

@@ -1962,13 +2051,18 @@
Parameters:
+ +module:util.Scheduler~Task + + + - the task to be scheduled +

the task to be scheduled

@@ -1980,20 +2074,50 @@
Parameters:
+ +* + + + - + + + + +

arguments for that task

+ + + + + + + + + + + + + + + + + + + + + + + + - arguments for that task - - - - +

scheduleCondition(condition, thenScheduler, elseScheduler)

+ @@ -2001,7 +2125,10 @@
Parameters:
- +
Source:
+
@@ -2025,10 +2152,7 @@
Parameters:
-
Source:
-
+ @@ -2042,6 +2166,9 @@
Parameters:
+
+

Schedule a series of task based on a condition.

+
@@ -2053,35 +2180,6 @@
Parameters:
- - - - - - - - - - -

scheduleCondition(condition, thenScheduler, elseScheduler)

- - - - - - -
- Schedule a series of task based on a condition. -
- - - - - - - - -
Parameters:
@@ -2136,7 +2234,7 @@
Parameters:
-Scheduler +Scheduler @@ -2146,7 +2244,7 @@
Parameters:
- scheduler to run if the condition is true +

scheduler to run if the condition is true

@@ -2159,7 +2257,7 @@
Parameters:
-Scheduler +Scheduler @@ -2169,7 +2267,7 @@
Parameters:
- scheduler to run if the condition is false +

scheduler to run if the condition is false

@@ -2181,80 +2279,77 @@
Parameters:
-
- - - - - - - - - - - - - -
Source:
-
- + + - +

setRedirectUrls(completionUrl, cancellationUrl)

-
- - - - +
+ +
Source:
+
+ + + + + + + + + + + - - + -

setRedirectUrls(completionUrl, cancellationUrl)

+ +
+ -
- Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment. + +
+

Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment.

@@ -2265,6 +2360,8 @@

setRed + +

Parameters:
@@ -2306,7 +2403,7 @@
Parameters:
- the completion URL +

the completion URL

@@ -2329,7 +2426,7 @@
Parameters:
- the cancellation URL +

the cancellation URL

@@ -2341,81 +2438,77 @@
Parameters:
-
- - - - - - - - - - - - - -
Source:
-
- + + - +

(async) start(options, resourcesopt)

-
- - - - +
+ +
Source:
+
+ + + + + + + + + + + - - + -

(async) start(options, resourcesopt)

+ +
-
- Start the experiment. + +
+

Start the experiment.

The resources are specified in the following fashion:

-
+
-

This mixin implements various unit-handling measurement methods.

+ -

Note: (a) this is the equivalent of PsychoPY's WindowMixin. - (b) it will most probably be made obsolete by a fully-integrated unit approach. -

- +
- + +
Source:
+
+ + -
+ @@ -75,10 +105,21 @@

-
Source:
-
+

+ + + + + +

This mixin implements various unit-handling measurement methods.

+

Note: (a) this is the equivalent of PsychoPY's WindowMixin. + (b) it will most probably be made obsolete by a fully-integrated unit approach. +

+ + + + +
@@ -86,12 +127,38 @@

-

+ + + + + + + + + +

Methods

+ + + + +

(protected) _getHorLengthPix(length) → {number}

+ + + + + + +
+ + +
Source:
+
-

@@ -108,23 +175,28 @@

-

Methods

- - + -

(protected) _getHorLengthPix(length) → {number}

+ + + + + + + + -
- Convert the given length from stimulus units to pixel units +
+

Convert the given length from stimulus units to pixel units

@@ -135,6 +207,8 @@

(protected) + +
Parameters:
@@ -176,7 +250,7 @@
Parameters:
- the length in stimulus units +

the length in stimulus units

@@ -188,102 +262,101 @@
Parameters:
-
- - - - - - - - - - +
Returns:
- + +
+
    +
  • the length in pixels
  • +
+
- - -
Source:
-
- - +
+
+ Type +
+
+ +number - - +
+ + + + +

(protected) _getLengthPix(length, integerCoordinatesopt) → {number}

+ +
+ +
Source:
+
+ + + -
Returns:
- - -
- - the length in pixels -
- - - -
-
- Type -
-
- -number + + -
-
+ + + + + - - + -

(protected) _getLengthPix(length, integerCoordinatesopt) → {number}

+ +
+ -
- Convert the given length from stimulus unit to pixel units. + +
+

Convert the given length from stimulus unit to pixel units.

@@ -294,6 +367,8 @@

(protected) Parameters:

@@ -351,7 +426,7 @@
Parameters:
- the length in stimulus units +

the length in stimulus units

@@ -385,12 +460,12 @@
Parameters:
- false + false - whether or not to round the length. +

whether or not to round the length.

@@ -402,102 +477,101 @@
Parameters:
-
- - - - - - - - - - +
Returns:
- + +
+
    +
  • the length in pixel units
  • +
+
- - -
Source:
-
- - +
+
+ Type +
+
+ +number - - +
+ + + + +

(protected) _getLengthUnits(length_px) → {number}

+ +
+ +
Source:
+
+ + + -
Returns:
- - -
- - the length in pixel units -
- - - -
-
- Type -
-
- -number + + -
-
+ + + + + - - + -

(protected) _getLengthUnits(length_px) → {number}

+ +
+ + -
- Convert the given length from pixel units to the stimulus units +
+

Convert the given length from pixel units to the stimulus units

@@ -508,6 +582,8 @@

(protected) < + +
Parameters:
@@ -549,7 +625,7 @@
Parameters:
- the length in pixel units +

the length in pixel units

@@ -561,102 +637,101 @@
Parameters:
-
- - - - - - - - - - +
Returns:
- + +
+
    +
  • the length in stimulus units
  • +
+
- - -
Source:
-
- - +
+
+ Type +
+
+ +number - - +
+ + + + +

(protected) _getVerLengthPix(length) → {number}

+ +
+ +
Source:
+
+ + + -
Returns:
- - -
- - the length in stimulus units -
- - - -
-
- Type -
-
- -number + + -
-
+ + + + + - - + -

(protected) _getVerLengthPix(length) → {number}

+ +
+ -
- Convert the given length from pixel units to the stimulus units + +
+

Convert the given length from pixel units to the stimulus units

@@ -667,6 +742,8 @@

(protected) + +
Parameters:
@@ -708,7 +785,7 @@
Parameters:
- the length in pixel units +

the length in pixel units

@@ -720,50 +797,6 @@
Parameters:
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - @@ -778,12 +811,14 @@
Returns:
- - the length in stimulus units +
    +
  • the length in stimulus units
  • +
-
+
Type
@@ -799,8 +834,6 @@
Returns:
- - @@ -814,19 +847,23 @@
Returns:
+ +

- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + \ No newline at end of file diff --git a/docs/module-core.html b/docs/module-core.html index 4265f1d4..71012493 100644 --- a/docs/module-core.html +++ b/docs/module-core.html @@ -1,23 +1,47 @@ + - JSDoc: Module: core - - - + core - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + -

Module: core

+ + +
+ +

core

+ @@ -33,13 +57,15 @@

Module: core

- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
- - + + + + + + + + \ No newline at end of file diff --git a/docs/module-data.ExperimentHandler.html b/docs/module-data.ExperimentHandler.html deleted file mode 100644 index 10a604a7..00000000 --- a/docs/module-data.ExperimentHandler.html +++ /dev/null @@ -1,1396 +0,0 @@ - - - - - JSDoc: Class: ExperimentHandler - - - - - - - - - - -
- -

Class: ExperimentHandler

- - - - - - -
- -
- -

- data.ExperimentHandler(options)

- - -
- -
-
- - - - - - -

new ExperimentHandler(options)

- - - - - - -
-

An ExperimentHandler keeps track of multiple loops and handlers. It is particularly useful -for generating a single data file from an experiment with many different loops (e.g. interleaved -staircases or loops within loops.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
name - - -string - - - - name of the experiment
extraInfo - - -Object - - - - additional information, such as session name, participant name, etc.
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - -

Extends

- - - - -
    -
  • PsychObject
  • -
- - - - - - - - - - - - - - - - - -

Methods

- - - - - - - -

(protected) _getLoopAttributes(loop)

- - - - - - -
- Get the attribute names and values for the current trial of a given loop. -

Only info relating to the trial execution are returned.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
loop - - -Object - - - - the loop
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

addData(key, value)

- - - - - - -
- Add the key/value pair. - -

Multiple key/value pairs can be added to any given entry of the data file. There are -considered part of the same entry until a call to nextEntry is made.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
key - - -Object - - - - the key
value - - -Object - - - - the value
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

addLoop(loop)

- - - - - - -
- Add a loop. - -

The loop might be a TrialHandler, for instance.

-

Data from this loop will be included in the resulting data files.

-
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
loop - - -Object - - - - the loop, e.g. an instance of TrialHandler or StairHandler
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

isEntryEmpty() → {boolean}

- - - - - - -
- Whether or not the current entry (i.e. trial data) is empty. -

Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.

-
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - -
- whether or not the current entry is empty -
- - - -
-
- Type -
-
- -boolean - - -
-
- - - - - - - - - - - - - -

nextEntry(snapshots)

- - - - - - -
- Inform this ExperimentHandler that the current trial has ended. Further calls to addData -will be associated with the next trial. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
snapshots - - -Array.<Object> - - - - array of loop snapshots
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

removeLoop(loop)

- - - - - - -
- Remove the given loop from the list of unfinished loops, e.g. when it has completed. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
loop - - -Object - - - - the loop, e.g. an instance of TrialHandler or StairHandler
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -

save(options)

- - - - - - -
- Save the results of the experiment. - -
    -
  • For an experiment running locally, the results are offered for immediate download.
  • -
  • For an experiment running on the server, the results are uploaded to the server.
  • -
-

-

- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
options - - -Object - - - - -
Properties
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
attributes - - -Array.<Object> - - - - - - <optional>
- - - - - -
the attributes to be saved
sync - - -Array.<Object> - - - - - - <optional>
- - - - - -
whether or not to communicate with the server in a synchronous manner
- -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
- - - - - \ No newline at end of file diff --git a/docs/module-data.TrialHandler.html b/docs/module-data.TrialHandler.html index a033fe9c..eca42048 100644 --- a/docs/module-data.TrialHandler.html +++ b/docs/module-data.TrialHandler.html @@ -1,23 +1,47 @@ + - JSDoc: Class: TrialHandler - - - + TrialHandler - PsychoJS API + + + + + + + + + + - - + + + + - -
+ + + + + + -

Class: TrialHandler

+
+ +

TrialHandler

+ @@ -28,28 +52,84 @@

Class: TrialHandler

-

- data.TrialHandler(options)

+

+ data. -

A Trial Handler handles the importing and sequencing of conditions.

+ TrialHandler +

+ +

A Trial Handler handles the importing and sequencing of conditions.

-
+
+

Constructor

-

new TrialHandler(options)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
To Do:
+
+
    +
  • extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead
  • +
+
+ +
+ + + + @@ -102,7 +182,7 @@
Parameters:
- +

the handler options

Properties
@@ -160,7 +240,7 @@
Properties
- the PsychoJS instance +

the PsychoJS instance

@@ -197,12 +277,12 @@
Properties
- [undefined] + [undefined] - if it is a string, we treat it as the name of a condition resource +

if it is a string, we treat it as the name of a condition resource

@@ -237,7 +317,7 @@
Properties
- number of repetitions +

number of repetitions

@@ -272,7 +352,7 @@
Properties
- the trial method +

the trial method

@@ -307,7 +387,7 @@
Properties
- additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc. +

additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc.

@@ -342,7 +422,7 @@
Properties
- seed for the random number generator +

seed for the random number generator

@@ -376,12 +456,12 @@
Properties
- false + false - whether or not to log +

whether or not to log

@@ -400,85 +480,65 @@
Properties
-
- - - - - - - - - - - - - - - -
Source:
-
- - + +
-
To Do:
-
-
    -
  • extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead
  • -
-
- -
- - - +

Extends

+ +
    +
  • PsychObject
  • +
+ + + + + + + + +

Members

+ + +

(static, readonly) Method :Symbol

+
-

- +
Source:
+
-

Extends

- - - - -
    -
  • PsychObject
  • -
- @@ -493,32 +553,23 @@

Extends

-

Members

- - - -

(static, readonly) Method :Symbol

- - + -
- TrialHandler method -
- + + -
Type:
-
    -
  • - -Symbol + + -
  • -
+ + + +
@@ -564,7 +615,7 @@
Properties:
- Conditions are presented in the order they are given. +

Conditions are presented in the order they are given.

@@ -587,7 +638,7 @@
Properties:
- Conditions are shuffled within each repeat. +

Conditions are shuffled within each repeat.

@@ -610,7 +661,7 @@
Properties:
- Conditions are fully randomised across all repeats. +

Conditions are fully randomised across all repeats.

@@ -633,7 +684,7 @@
Properties:
- Same as above, but named to reflect PsychoPy boileplate. +

Same as above, but named to reflect PsychoPy boileplate.

@@ -643,10 +694,44 @@
Properties:
+ + +
+

TrialHandler method

+
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + + + + +

experimentHandler

+ + + + +
- +
Source:
+
@@ -670,10 +755,7 @@
Properties:
-
Source:
-
+ @@ -687,19 +769,20 @@
Properties:
+
+

Getter for experimentHandler.

+
+ - - -

finished

-
- Getter for the finished attribute. -
+ + +

experimentHandler

@@ -708,7 +791,10 @@

finished - +
Source:
+
@@ -732,10 +818,7 @@

finishedSource: -
+ @@ -749,19 +832,20 @@

finished +

Setter for experimentHandler.

+

+ - - -

finished

-
- Setter for the finished attribute. -
+ + +

finished

@@ -770,7 +854,10 @@

finished - +
Source:
+
@@ -794,10 +881,7 @@

finishedSource: -
+ @@ -811,29 +895,147 @@

finished +

Getter for the finished attribute.

+

- - - -

Methods

- - - - -

(static) fromSnapshot(snapshot)

- - + + +

finished

+ + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Setter for the finished attribute.

+
+ + + + + + + + + + + + +

Methods

+ + + + + + +

(static) fromSnapshot(snapshot)

+ + + + + + +
+ + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Set the internal state of the snapshot's trial handler from the snapshot.

+
+ -
- Set the internal state of this trial handler from the given snapshot. -
@@ -884,7 +1086,8 @@
Parameters:
- the snapshot from which to update the current internal state. +

the snapshot from which to update the current internal state of the +snapshot's trial handler

@@ -896,84 +1099,79 @@
Parameters:
-
- - - - - - - - - - - - - -
Source:
-
- + + - +

(static) importConditions(serverManager, resourceName, selectionopt) → {Object}

-
- - - - +
+ +
Source:
+
+ + + + + + + + + + + - - + -

(static) importConditions(serverManager, resourceName, selectionopt) → {Object}

+ +
+ -
- Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource. +
+

Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource.

The output is suitable as an input to 'TrialHandler', 'trialTypes' or 'MultiStairHandler' as a 'conditions' list.

-

The resource should contain one row per type of trial needed and one column for each parameter that defines the trial type. The first row should give parameter names, which should: @@ -982,10 +1180,7 @@

(static) begin with a letter (upper or lower case)
  • contain no spaces or other punctuation (underscores are permitted)
  • -

    Note that we only consider the first worksheet for .xls, .xlsx and .odp resource.

    - -

    'selection' is used to select a subset of condition indices to be used It can be a single integer, an array of indices, or a string to be parsed, e.g.: 5 @@ -1004,6 +1199,8 @@

    (static) Parameters:

    @@ -1039,7 +1236,7 @@
    Parameters:
    -module:core.ServerManager +module:core.ServerManager @@ -1061,7 +1258,7 @@
    Parameters:
    - the server manager +

    the server manager

    @@ -1096,7 +1293,7 @@
    Parameters:
    - the name of the resource containing the list of conditions, which must have been registered with the server manager. +

    the name of the resource containing the list of conditions, which must have been registered with the server manager.

    @@ -1130,12 +1327,12 @@
    Parameters:
    - null + null - the selection +

    the selection

    @@ -1147,50 +1344,6 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - @@ -1205,13 +1358,13 @@
    Throws:
    -
    - Throws an exception if importing the conditions failed. +
    +

    Throws an exception if importing the conditions failed.

    -
    +
    Type
    @@ -1234,12 +1387,12 @@
    Returns:
    - the parsed conditions as an array of 'object as map' +

    the parsed conditions as an array of 'object as map'

    -
    +
    Type
    @@ -1255,85 +1408,123 @@
    Returns:
    - - +

    (protected) _prepareSequence()

    + + + + + + +
    + -

    (protected) _prepareTrialList(trialList)

    +
    Source:
    +
    + + -
    - Prepare the trial list. -
    + + + + + + + + + -
    Parameters:
    - - - - - - + + + + + + + + + +
    +

    Prepare the sequence of trials.

    +

    The returned sequence is a matrix (an array of arrays) of trial indices +with nStim columns and nReps rows. Note that this is the transpose of the +matrix return by PsychoPY. +

    Example: with 3 trial and 5 repetitions, we get:

    +
      +
    • sequential: +[[0 1 2] +[0 1 2] +[0 1 2] +[0 1 2] +[0 1 2]]
    • +
    +

    These 3*5 = 15 trials will be returned by the TrialHandler generator

    +
      +
    • with method = 'sequential' in the order: +0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2
    • +
    • with method = 'random' in the order (amongst others): +2, 1, 0, 0, 2, 1, 0, 1, 2, 0, 1, 2, 1, 2, 0
    • +
    • with method = 'fullRandom' in the order (amongst others): +2, 0, 0, 1, 0, 2, 1, 2, 0, 1, 1, 1, 2, 0, 2
    • +
    +

    +
    + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - -
    NameTypeDescription
    trialList - - -Array.<Object> -| -String - - a list of trials, or the name of a condition resource
    +

    (protected) _prepareTrialList() → {void}

    + @@ -1341,7 +1532,10 @@
    Parameters:
    - +
    Source:
    +
    @@ -1365,10 +1559,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1382,6 +1573,19 @@
    Parameters:
    +
    +

    Prepare the trial list.

    +
    + + + + + + + + + + @@ -1396,22 +1600,86 @@
    Parameters:
    + +
    Returns:
    - + + +
    +
    + Type +
    +
    + +void + + +
    +
    + + + + + +

    addData(key, value)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + -
    - Add a key/value pair to data about the current trial held by the experiment handler + + + +
    + + + + + +
    +

    Add a key/value pair to data about the current trial held by the experiment handler

    @@ -1422,6 +1690,8 @@

    addDataParameters:

    @@ -1463,7 +1733,7 @@
    Parameters:
    - the key +

    the key

    @@ -1486,7 +1756,7 @@
    Parameters:
    - the value +

    the value

    @@ -1495,13 +1765,41 @@
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + +

    forEach(callback)

    + + +
    - +
    Source:
    +
    @@ -1525,10 +1823,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1542,6 +1837,9 @@
    Parameters:
    +
    +

    Execute the callback for each trial in the sequence.

    +
    @@ -1553,35 +1851,6 @@
    Parameters:
    - - - - - - - - - - -

    forEach(callback)

    - - - - - - -
    - Execute the callback for each trial in the sequence. -
    - - - - - - - - -
    Parameters:
    @@ -1630,10 +1899,38 @@
    Parameters:
    -
    + + + + + + + + + + + + + + + + + + +

    getAttributes() → {Array.string}

    + + + +
    + + +
    Source:
    +
    @@ -1657,10 +1954,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1674,6 +1968,11 @@
    Parameters:
    +
    +

    Get the attributes of the trials.

    +

    Note: we assume that all trials in the trialList share the same attributes +and consequently consider only the attributes of the first trial.

    +
    @@ -1689,34 +1988,49 @@
    Parameters:
    - - - - -

    getAttributes() → {Array.string}

    - - -
    - Get the attributes of the trials. -

    Note: we assume that all trials in the trialList share the same attributes -and consequently consider only the attributes of the first trial.

    + + + +
    Returns:
    + + +
    +

    the attributes

    +
    +
    + Type +
    +
    + +Array.string + + +
    +
    + + + + + +

    getCurrentTrial() → {Object}

    + @@ -1724,7 +2038,10 @@

    getAttri
    - +
    Source:
    +
    @@ -1748,10 +2065,7 @@

    getAttri -
    Source:
    -
    + @@ -1765,6 +2079,24 @@

    getAttri +
    +

    Get the current trial.

    +
    + + + + + + + + + + + + + + + @@ -1779,18 +2111,18 @@

    Returns:
    - the attributes +

    the current trial

    -
    +
    Type
    -Array.string +Object
    @@ -1800,41 +2132,25 @@
    Returns:
    - - - -

    getCurrentTrial() → {Object}

    - +

    getEarlierTrial(nopt) → {Object|undefined}

    -
    - Get the current trial. -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -1858,10 +2174,7 @@

    getCur -
    Source:
    -
    + @@ -1875,61 +2188,13 @@

    getCur - - - - - - - - - - -

    Returns:
    - - -
    - the current trial +
    +

    Get the nth previous trial.

    +

    Note: this is useful for comparisons in n-back tasks.

    -
    -
    - Type -
    -
    - -Object - - -
    -
    - - - - - - - - - - - - - -

    getEarlierTrial(nopt) → {Object|undefined}

    - - - - - - -
    - Get the nth previous trial. -

    Note: this is useful for comparisons in n-back tasks.

    -
    - @@ -1994,12 +2259,12 @@
    Parameters:
    - -1 + -1 - increment +

    increment

    @@ -2011,105 +2276,102 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - +
    Returns:
    - + +
    +

    the past trial or undefined if attempting to go prior to the first trial.

    +
    - -
    +
    +
    + Type +
    +
    + +Object +| +undefined +
    +
    + + + + +

    getFutureTrial(nopt) → {Object|undefined}

    + -
    Returns:
    - -
    - the past trial or undefined if attempting to go prior to the first trial. -
    +
    + +
    Source:
    +
    + + -
    -
    - Type -
    -
    - -Object -| + -undefined + + -
    -
    + + + + + - - + -

    getFutureTrial(nopt) → {Object|undefined}

    + +
    + -
    - Get the nth future or past trial, without advancing through the trial list. + +
    +

    Get the nth future or past trial, without advancing through the trial list.

    @@ -2120,6 +2382,8 @@

    getFutu + +

    Parameters:
    @@ -2176,12 +2440,12 @@
    Parameters:
    - 1 + 1 - increment +

    increment

    @@ -2193,50 +2457,6 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - @@ -2251,13 +2471,13 @@
    Returns:
    - the future trial (if n is positive) or past trial (if n is negative) -or undefined if attempting to go beyond the last trial. +

    the future trial (if n is positive) or past trial (if n is negative) +or undefined if attempting to go beyond the last trial.

    -
    +
    Type
    @@ -2276,44 +2496,25 @@
    Returns:
    - - -

    getSnapshot() → {Snapshot}

    - -
    - Get a snapshot of the current internal state of the trial handler (e.g. current trial number, -number of trial remaining). - -

    This is typically used in the LoopBegin function, in order to capture the current state of a TrialHandler

    -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -2337,10 +2538,7 @@

    getSnapsho -
    Source:
    -
    + @@ -2354,6 +2552,26 @@

    getSnapsho +
    +

    Get a snapshot of the current internal state of the trial handler (e.g. current trial number, +number of trial remaining).

    +

    This is typically used in the LoopBegin function, in order to capture the current state of a TrialHandler

    +
    + + + + + + + + + + + + + + + @@ -2368,12 +2586,14 @@

    Returns:
    - - a snapshot of the current internal state. +
      +
    • a snapshot of the current internal state.
    • +
    -
    +
    Type
    @@ -2389,23 +2609,64 @@
    Returns:
    - - -

    getTrial(index) → {Object|undefined}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    -
    - Get the nth trial. + + + +
    +

    Get the nth trial.

    @@ -2416,6 +2677,8 @@

    getTrialParameters:

    @@ -2460,12 +2723,12 @@
    Parameters:
    - 0 + 0 - the trial index +

    the trial index

    @@ -2477,10 +2740,63 @@
    Parameters:
    -
    + + + + + + + + + + +
    Returns:
    + + +
    +

    the requested trial or undefined if attempting to go beyond the last trial.

    +
    + + + +
    +
    + Type +
    +
    + +Object +| + +undefined + + +
    +
    + + + + + + + +

    getTrialIndex() → {number}

    + + + + + + +
    + + +
    Source:
    +
    @@ -2504,10 +2820,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -2521,6 +2834,24 @@
    Parameters:
    +
    +

    Get the trial index.

    +
    + + + + + + + + + + + + + + + @@ -2535,21 +2866,18 @@
    Returns:
    - the requested trial or undefined if attempting to go beyond the last trial. +

    the current trial index

    -
    +
    Type
    - -Object -| - -undefined + +number
    @@ -2559,41 +2887,25 @@
    Returns:
    - - - -

    getTrialIndex() → {number}

    - +

    next()

    -
    - Get the trial index. -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -2617,10 +2929,7 @@

    getTrial -
    Source:
    -
    + @@ -2634,6 +2943,9 @@

    getTrial +
    +

    Helps go through each trial in the sequence one by one, mirrors PsychoPy.

    +
    @@ -2644,58 +2956,32 @@

    getTrial -

    Returns:
    - - -
    - the current trial index -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - -

    next()

    - - -
    - Helps go through each trial in the sequence one by one, mirrors PsychoPy. -
    + + + +

    setSeed(seed, log)

    + @@ -2703,7 +2989,10 @@

    next - +
    Source:
    +
    @@ -2727,10 +3016,7 @@

    nextSource:

    -
    + @@ -2744,6 +3030,9 @@

    next +

    Setter for the seed attribute.

    +

    @@ -2755,35 +3044,6 @@

    nextsetSeed(seed, log)

    - - - - - - -
    - Setter for the seed attribute. -
    - - - - - - - - -
    Parameters:
    @@ -2825,7 +3085,7 @@
    Parameters:
    - the seed value +

    the seed value

    @@ -2848,7 +3108,7 @@
    Parameters:
    - whether or not to log the change of seed +

    whether or not to log the change of seed

    @@ -2860,80 +3120,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setTrialIndex(index)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setTrialIndex(index)

    + +
    + + -
    - Set the trial index. +
    +

    Set the trial index.

    @@ -2944,6 +3201,8 @@

    setTrial + +

    Parameters:
    @@ -2985,7 +3244,7 @@
    Parameters:
    - the new trial index +

    the new trial index

    @@ -2995,52 +3254,6 @@
    Parameters:
    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - @@ -3061,36 +3274,20 @@
    Parameters:
    -

    Symbol.iterator()

    - -
    - Iterator over the trial sequence. - -

    This makes it possible to iterate over all trials.

    -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -3114,10 +3311,7 @@

    Symbol -
    Source:
    -
    + @@ -3131,6 +3325,27 @@

    Symbol +
    +

    Iterator over the trial sequence.

    +

    This makes it possible to iterate over all trials.

    +
    + + + + + + + + + +

    Example
    + +
    let handler = new TrialHandler({nReps: 5});
    +for (const thisTrial of handler) { console.log(thisTrial); }
    + + + + @@ -3145,10 +3360,6 @@

    Symbol -

    Example
    - -
    let handler = new TrialHandler({nReps: 5});
    -for (const thisTrial of handler) { console.log(thisTrial); }
    @@ -3165,19 +3376,23 @@
    Example
    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-data.html b/docs/module-data.html index f85ea829..f1515a5b 100644 --- a/docs/module-data.html +++ b/docs/module-data.html @@ -1,23 +1,47 @@ + - JSDoc: Module: data - - - + data - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Module: data

    + + + + +
    + +

    data

    + @@ -33,13 +57,15 @@

    Module: data

    -
    +
    + + +
    -
    @@ -49,18 +75,27 @@

    Module: data

    Classes

    -
    ExperimentHandler
    +
    ExperimentHandler
    TrialHandler
    + +
    MultiStairHandler
    +
    + +
    QuestHandler
    +
    + +
    Shelf
    +
    - - + + @@ -78,18 +113,45 @@

    Snapshot

    +
    -
    Type:
    - + + + + + + + + + + + + + + + + + + + + + + + + + +
    @@ -135,7 +197,7 @@
    Properties:
    - the trialHandler +

    the trialHandler

    @@ -158,7 +220,7 @@
    Properties:
    - the trialHandler name +

    the trialHandler name

    @@ -181,7 +243,7 @@
    Properties:
    - the number of stimuli +

    the number of stimuli

    @@ -204,7 +266,7 @@
    Properties:
    - the total number of trials that will be run +

    the total number of trials that will be run

    @@ -227,7 +289,7 @@
    Properties:
    - the total number of trial remaining +

    the total number of trial remaining

    @@ -250,7 +312,7 @@
    Properties:
    - the current repeat +

    the current repeat

    @@ -273,7 +335,7 @@
    Properties:
    - the current trial number within the current repeat +

    the current trial number within the current repeat

    @@ -296,7 +358,7 @@
    Properties:
    - the total number of trials completed so far +

    the total number of trials completed so far

    @@ -319,7 +381,7 @@
    Properties:
    - the index of the current trial in the conditions list +

    the index of the current trial in the conditions list

    @@ -342,7 +404,7 @@
    Properties:
    - whether or not the trial ran +

    whether or not the trial ran

    @@ -365,7 +427,7 @@
    Properties:
    - whether or not the trials finished +

    whether or not the trials finished

    @@ -388,7 +450,7 @@
    Properties:
    - a list of trial attributes +

    a list of trial attributes

    @@ -398,45 +460,19 @@
    Properties:
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - +
    Type:
    +
      +
    • + +Object - - -
    + + @@ -454,19 +490,23 @@
    Properties:
    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-hardware.Camera.html b/docs/module-hardware.Camera.html new file mode 100644 index 00000000..ad8d4c8f --- /dev/null +++ b/docs/module-hardware.Camera.html @@ -0,0 +1,2531 @@ + + + + + + Camera - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    Camera

    + + + + + + + +
    + +
    + +

    + Camera +

    + + +
    + +
    + +
    + + + + + +

    new Camera(options)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    To Do:
    +
    +
      +
    • add video constraints as parameter
    • +
    +
    + +
    + + + + + +
    +

    This manager handles the recording of video signal.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    win + + +module:core.Window + + + + + + + + + + + +

    the associated Window

    format + + +string + + + + + + <optional>
    + + + + + +
    + + 'video/webm;codecs=vp9' + +

    the video format

    clock + + +Clock + + + + + + <optional>
    + + + + + +
    + +

    an optional clock

    autoLog + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether or not to log

    + +
    + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +

    Methods

    + + + + + + +

    (protected) _onChange()

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Callback for changes to the recording settings.

    +

    Changes to the settings require the recording to stop and be re-started.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    (protected) _prepareRecording()

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Prepare the recording.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    (protected) _upload(tag, waitForCompletionopt, showDialogopt, dialogMsgopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Upload the video recording to the pavlovia server.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    tag + + +string + + + + + + + + + + + +

    an optional tag for the video file

    waitForCompletion + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether to wait for completion +before returning

    showDialog + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether to open a dialog box to inform the participant to wait for the data to be uploaded to the server

    dialogMsg + + +string + + + + + + <optional>
    + + + + + +
    + + "" + +

    default message informing the participant to wait for the data to be uploaded to the server

    + + + + + + + + + + + + + + + + + + + + + + + + +

    authorize(showDialogopt, dialogMsgopt) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Prompt the user for permission to use the camera on their device.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    showDialog + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether to open a dialog box to inform the +participant to wait for the camera to be initialised

    dialogMsg + + +string + + + + + + <optional>
    + + + + + +
    + +

    the dialog message

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    whether or not the camera is ready to record

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    close() → {Promise.<void>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Close the camera stream.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the stream has stopped and is now closed

    +
    + + + +
    +
    + Type +
    +
    + +Promise.<void> + + +
    +
    + + + + + + + + + + +

    flush() → {Promise}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Submit a request to flush the recording.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the data has actually been made available

    +
    + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + +

    getRecording(tag, flushopt)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the current video recording as a VideoClip in the given format.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    tag + + +string + + + + + + + + + + + +

    an optional tag for the video clip

    flush + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether or not to first flush the recording

    + + + + + + + + + + + + + + + + + + + + + + + + +

    getStream() → {MediaStream}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the underlying video stream.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the video stream

    +
    + + + +
    +
    + Type +
    +
    + +MediaStream + + +
    +
    + + + + + + + + + + +

    getVideo() → {HTMLVideoElement}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get a video element pointing to the Camera stream.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    a video element

    +
    + + + +
    +
    + Type +
    +
    + +HTMLVideoElement + + +
    +
    + + + + + + + + + + +

    isReady() → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Query whether the camera is ready to record.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    true if the camera is ready to record, false otherwise

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    open()

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Open the video stream.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    pause() → {Promise}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Submit a request to pause the recording.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the recording actually paused

    +
    + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + +

    record() → {Promise}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Submit a request to start the recording.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the recording actually starts

    +
    + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + +

    resume(options) → {Promise}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Submit a request to resume the recording.

    +

    resume has no effect if the recording was not previously paused.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + + +
    Properties
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    clear + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether or not to empty the video buffer before +resuming the recording

    + +
    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the recording actually resumed

    +
    + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + +

    stop(options) → {Promise}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Submit a request to stop the recording.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    options + + +Object + + + +
    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    promise fulfilled when the recording actually stopped, and the recorded +data was made available

    +
    + + + +
    +
    + Type +
    +
    + +Promise + + +
    +
    + + + + + + + + + + + +
    + +
    + + + + + + +
    + +
    + +
    + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme. +
    + + + + + + + + + + + \ No newline at end of file diff --git a/docs/module-sound.AudioClip.html b/docs/module-sound.AudioClip.html deleted file mode 100644 index 24a9ff04..00000000 --- a/docs/module-sound.AudioClip.html +++ /dev/null @@ -1,900 +0,0 @@ - - - - - JSDoc: Class: AudioClip - - - - - - - - - - -
    - -

    Class: AudioClip

    - - - - - - -
    - -
    - -

    - sound.AudioClip(options)

    - - -
    - -
    -
    - - - - - - -

    new AudioClip(options)

    - - - - - - -
    -

    AudioClip encapsulate an audio recording.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    options - - -Object - - - - -
    Properties
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    psychoJS - - -module:core.PsychoJS - - - - - - - - - - - - the PsychoJS instance
    name - - -String - - - - - - <optional>
    - - - - - -
    - - 'audioclip' - - the name used when logging messages
    format - - -string - - - - - - - - - - - - the format for the audio file
    sampleRateHz - - -number - - - - - - - - - - - - the sampling rate
    data - - -Blob - - - - - - - - - - - - the audio data, in the given format, at the given sampling rate
    autoLog - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to log
    - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -

    Members

    - - - -

    (readonly) Engine :Symbol

    - - - - -
    - Recognition engines. -
    - - - -
    Type:
    -
      -
    • - -Symbol - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - -

    Methods

    - - - - - - - -

    download()

    - - - - - - -
    - Offer the audio clip to the participant as a sound file to download. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    startPlayback()

    - - - - - - -
    - Start playing the audio clip. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    startPlayback()

    - - - - - - -
    - Stop playing the audio clip. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    upload()

    - - - - - - -
    - Upload the audio clip to the pavlovia server. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-sound.Microphone.html b/docs/module-sound.Microphone.html deleted file mode 100644 index 04016a02..00000000 --- a/docs/module-sound.Microphone.html +++ /dev/null @@ -1,1441 +0,0 @@ - - - - - JSDoc: Class: Microphone - - - - - - - - - - -
    - -

    Class: Microphone

    - - - - - - -
    - -
    - -

    - sound.Microphone(options, @param)

    - - -
    - -
    -
    - - - - - - -

    new Microphone(options, @param)

    - - - - - - -
    -

    This manager handles the recording of audio signal.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    options - - -Object - - - - - - - - - - - - -
    Properties
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
    - -
    @param - - -module:core.Window - - - - - - - - - - - - options.win - the associated Window
    options.format - - -string - - - - - - <optional>
    - - - - - -
    - - 'audio/webm;codecs=opus' - - the format for the audio file
    options.sampleRateHz - - -number - - - - - - <optional>
    - - - - - -
    - - 48000 - - the audio sampling rate, in Hz
    options.clock - - -Clock - - - - - - <optional>
    - - - - - -
    - - an optional clock
    options.autoLog - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to log
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -

    Members

    - - - -

    flush

    - - - - -
    - Submit a request to flush the recording. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    pause

    - - - - -
    - Submit a request to pause the recording. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    resume

    - - - - -
    - Submit a request to resume the recording. - -

    resume has no effect if the recording was not previously paused.

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    start

    - - - - -
    - Submit a request to start the recording. - -

    Note that it typically takes 50ms-200ms for the recording to actually starts once -a request to start has been submitted.

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    stop

    - - - - -
    - Submit a request to stop the recording. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - -

    Methods

    - - - - - - - -

    (protected) _onChange()

    - - - - - - -
    - Callback for changes to the recording settings. - -

    Changes to the settings require the recording to stop and be re-started.

    -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (protected) _prepareRecording()

    - - - - - - -
    - Prepare the recording. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    download(filename)

    - - - - - - -
    - Offer the audio recording to the participant as a sound file to download. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    filename - - -string - - - - the filename
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    getRecording(tag, flushopt)

    - - - - - - -
    - Get the current audio recording as an AudioClip in the given format. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    tag - - -string - - - - - - - - - - - - an optional tag for the audio clip
    flush - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to first flush the recording
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    upload(tag)

    - - - - - - -
    - Upload the audio recording to the pavlovia server. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    tag - - -string - - - - an optional tag for the audio file
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-sound.Sound.html b/docs/module-sound.Sound.html index 8f6446e0..336658cf 100644 --- a/docs/module-sound.Sound.html +++ b/docs/module-sound.Sound.html @@ -1,23 +1,47 @@ + - JSDoc: Class: Sound - - - + Sound - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Class: Sound

    +
    + +

    Sound

    + @@ -28,16 +52,17 @@

    Class: Sound

    -

    - sound.Sound(options)

    +

    + sound. -

    This class handles sound playing (tones and tracks)

    - + Sound +

    + +

    This class handles sound playing (tones and tracks)

    • If value is a number then a tone will be generated at that frequency in Hz.
    • It value is a string, it must either be a note in the PsychoPy format (e.g 'A', 'Bfl', 'B', 'C', 'Csh'), in which case an octave must also be given, or the name of the resource track.
    -

    Note: the PsychoPy hamming parameter has not been implemented yet. It might be rather tricky to do so using Tone.js

    @@ -45,19 +70,61 @@

    -
    +
    +

    Constructor

    -

    new Sound(options)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @@ -69,6 +136,22 @@

    new SoundExample

    + +
    [...]
    +const track = new Sound({
    +  win: psychoJS.window,
    +  value: 440,
    +  secs: 0.5
    +});
    +track.setVolume(1.0);
    +track.play(2);
    + + + +
    Parameters:
    @@ -168,7 +251,7 @@
    Properties
    - the name used when logging messages from this stimulus +

    the name used when logging messages from this stimulus

    @@ -203,7 +286,7 @@
    Properties
    - the associated Window +

    the associated Window

    @@ -240,12 +323,12 @@
    Properties
    - 'C' + 'C' - the sound value (see above for a full description) +

    the sound value (see above for a full description)

    @@ -279,12 +362,12 @@
    Properties
    - 4 + 4 - the octave corresponding to the tone (if applicable) +

    the octave corresponding to the tone (if applicable)

    @@ -318,12 +401,12 @@
    Properties
    - 0.5 + 0.5 - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. +

    duration of the tone (in seconds) If secs == -1, the sound will play indefinitely.

    @@ -357,12 +440,12 @@
    Properties
    - 0 + 0 - start of playback for tracks (in seconds) +

    start of playback for tracks (in seconds)

    @@ -396,12 +479,12 @@
    Properties
    - -1 + -1 - end of playback for tracks (in seconds) +

    end of playback for tracks (in seconds)

    @@ -435,12 +518,12 @@
    Properties
    - true + true - whether or not to play the sound or track in stereo +

    whether or not to play the sound or track in stereo

    @@ -474,12 +557,12 @@
    Properties
    - 1.0 + 1.0 - volume of the sound (must be between 0 and 1.0) +

    volume of the sound (must be between 0 and 1.0)

    @@ -513,12 +596,12 @@
    Properties
    - 0 + 0 - how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped. +

    how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped.

    @@ -552,12 +635,12 @@
    Properties
    - true + true - whether or not to log +

    whether or not to log

    @@ -576,51 +659,6 @@
    Properties
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - @@ -634,21 +672,9 @@
    Properties
    -
    Example
    - -
    [...]
    -const track = new Sound({
    -  win: psychoJS.window,
    -  value: 440,
    -  secs: 0.5
    -});
    -track.setVolume(1.0);
    -track.play(2);
    - - - + +
    -

    Extends

    @@ -666,11 +692,11 @@

    Extends

    - - + + @@ -683,34 +709,20 @@

    Methods

    - -

    (protected) _getPlayer() → {SoundPlayer}

    - +

    (protected) _getPlayer() → {SoundPlayer}

    -
    - Identify the appropriate player for the sound. -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -734,10 +746,7 @@

    (protected) Source: -
    + @@ -751,6 +760,24 @@

    (protected) +

    Identify the appropriate player for the sound.

    +

    + + + + + + + + + + + + + + + @@ -765,13 +792,13 @@
    Throws:
    -
    - exception if no appropriate SoundPlayer could be found for the sound +
    +

    exception if no appropriate SoundPlayer could be found for the sound

    -
    +
    Type
    @@ -794,18 +821,18 @@
    Returns:
    - the appropriate SoundPlayer +

    the appropriate SoundPlayer

    -
    +
    Type
    -SoundPlayer +SoundPlayer
    @@ -815,41 +842,25 @@
    Returns:
    - - -

    getDuration() → {number}

    - -
    - Get the duration of the sound, in seconds. -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -873,10 +884,7 @@

    getDuratio -
    Source:
    -
    + @@ -890,6 +898,24 @@

    getDuratio +
    +

    Get the duration of the sound, in seconds.

    +
    + + + + + + + + + + + + + + + @@ -904,12 +930,12 @@

    Returns:
    - the duration of the sound, in seconds +

    the duration of the sound, in seconds

    -
    +
    Type
    @@ -925,51 +951,93 @@
    Returns:
    - - -

    play(loops, logopt)

    - -
    - Start playing the sound. -

    Note: Sounds are played independently from the stimuli of the experiments, i.e. the experiment will not stop until the sound is finished playing. -Repeat calls to play may results in the sounds being played on top of each other.

    -
    +
    + +
    Source:
    +
    + + + + + + + + -
    Parameters:
    - - - - - - + - + + + + + + + + + + + + + + + + +
    +

    Start playing the sound.

    +

    Note: Sounds are played independently from the stimuli of the experiments, i.e. the experiment will not stop until the sound is finished playing. +Repeat calls to play may results in the sounds being played on top of each other.

    +
    + + + + + + + + + + + +
    Parameters:
    + + +
    NameType
    + + + + + + + + + + - - @@ -1012,7 +1080,7 @@
    Parameters:
    - + @@ -1046,12 +1114,12 @@
    Parameters:
    - + @@ -1063,80 +1131,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setLoops(loopsopt, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setLoops(loopsopt, logopt)

    + +
    + + -
    - Set the number of loops. +
    +

    Set the number of loops.

    @@ -1147,6 +1212,8 @@

    setLoopsParameters:

    @@ -1203,12 +1270,12 @@
    Parameters:
    - + @@ -1242,12 +1309,12 @@
    Parameters:
    - + @@ -1259,80 +1326,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setSecs(secsopt, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setSecs(secsopt, logopt)

    + +
    -
    - Set the duration (in seconds) + + +
    +

    Set the duration (in seconds)

    @@ -1343,6 +1407,8 @@

    setSecsParameters:

    @@ -1399,12 +1465,12 @@
    Parameters:
    - + @@ -1438,12 +1504,12 @@
    Parameters:
    - + @@ -1455,80 +1521,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setSound(sound, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setSound(sound, logopt)

    + +
    + -
    - Set the sound value on demand past initialisation. + +
    +

    Set the sound value on demand past initialisation.

    @@ -1539,6 +1602,8 @@

    setSoundParameters:

    @@ -1596,7 +1661,7 @@
    Parameters:
    -
    + @@ -1630,12 +1695,12 @@
    Parameters:
    - + @@ -1647,80 +1712,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setVolume(volume, muteopt, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setVolume(volume, muteopt, logopt)

    + +
    + -
    - Set the playing volume of the sound. + +
    +

    Set the playing volume of the sound.

    @@ -1731,6 +1793,8 @@

    setVolumeParameters:

    @@ -1788,7 +1852,7 @@
    Parameters:
    -
    + @@ -1822,12 +1886,12 @@
    Parameters:
    - + @@ -1861,12 +1925,12 @@
    Parameters:
    - + @@ -1878,80 +1942,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    stop(options)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    stop(options)

    + +
    + -
    - Stop playing the sound immediately. + +
    +

    Stop playing the sound immediately.

    @@ -1962,6 +2023,8 @@

    stop - true + true -

    + @@ -2084,52 +2147,6 @@
    Properties
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - @@ -2156,19 +2173,23 @@
    Properties
    + + - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-sound.SoundPlayer.html b/docs/module-sound.SoundPlayer.html deleted file mode 100644 index 51ecf49a..00000000 --- a/docs/module-sound.SoundPlayer.html +++ /dev/null @@ -1,1048 +0,0 @@ - - - - - JSDoc: Interface: SoundPlayer - - - - - - - - - - -
    - -

    Interface: SoundPlayer

    - - - - - - -
    - -
    - -

    - sound.SoundPlayer

    - - -
    - -
    -
    - - -

    SoundPlayer is an interface for the sound players, who are responsible for actually playing the sounds, i.e. the tracks or the tones.

    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - -
    - - -

    Extends

    - - - - -
      -
    • PsychObject
    • -
    - - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    (abstract, static) accept() → {Object|undefined}

    - - - - - - -
    - Determine whether this player can play the given sound. -
    - - - - - - - - - -
    Parameters:
    - - -
    NameTypeAttributesAttributesDefault how many times to repeat the sound after it plays once. If loops == -1, the sound will repeat indefinitely until stopped.

    how many times to repeat the sound after it plays once. If loops == -1, the sound will repeat indefinitely until stopped.

    - true + true whether or not to log

    whether or not to log

    - 0 + 0 how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.

    how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.

    - true + true whether of not to log

    whether of not to log

    - 0.5 + 0.5 duration of the tone (in seconds) If secs == -1, the sound will play indefinitely.

    duration of the tone (in seconds) If secs == -1, the sound will play indefinitely.

    - true + true whether or not to log

    whether or not to log

    a sound instance to replace the current one

    a sound instance to replace the current one

    - true + true whether or not to log

    whether or not to log

    the volume (values should be between 0 and 1)

    the volume (values should be between 0 and 1)

    - false + false whether or not to mute the sound

    whether or not to mute the sound

    - true + true whether of not to log

    whether of not to log

    whether or not to log

    whether or not to log

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeDescription
    - - -module:sound.Sound - - - - the sound
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - an instance of the SoundPlayer that can play the sound, or undefined if none could be found -
    - - - -
    -
    - Type -
    -
    - -Object -| - -undefined - - -
    -
    - - - - - - - - - - - - - -

    (abstract) getDuration()

    - - - - - - -
    - Get the duration of the sound, in seconds. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (abstract) play(loopsopt)

    - - - - - - -
    - Start playing the sound. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    loops - - -number - - - - - - <optional>
    - - - - - -
    how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (abstract) setDuration()

    - - - - - - -
    - Set the duration of the sound, in seconds. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (abstract) setLoops(loops)

    - - - - - - -
    - Set the number of loops. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    loops - - -number - - - - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (abstract) setVolume(volume, muteopt)

    - - - - - - -
    - Set the volume of the tone. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    volume - - -Integer - - - - - - - - - - - - the volume of the tone
    mute - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to mute the tone
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (abstract) stop()

    - - - - - - -
    - Stop playing the sound immediately. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-sound.TonePlayer.html b/docs/module-sound.TonePlayer.html deleted file mode 100644 index 0950e9e5..00000000 --- a/docs/module-sound.TonePlayer.html +++ /dev/null @@ -1,1528 +0,0 @@ - - - - - JSDoc: Class: TonePlayer - - - - - - - - - - -
    - -

    Class: TonePlayer

    - - - - - - -
    - -
    - -

    - sound.TonePlayer(options)

    - - -
    - -
    -
    - - - - - - -

    new TonePlayer(options)

    - - - - - - -
    -

    This class handles the playing of tones.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    options - - -Object - - - - -
    Properties
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    psychoJS - - -module:core.PsychoJS - - - - - - - - - - - - the PsychoJS instance
    duration_s - - -number - - - - - - <optional>
    - - - - - -
    - - 0.5 - - duration of the tone (in seconds). If duration_s == -1, the sound will play indefinitely.
    note - - -string -| - -number - - - - - - <optional>
    - - - - - -
    - - 'C4' - - note (if string) or frequency (if number)
    volume - - -number - - - - - - <optional>
    - - - - - -
    - - 1.0 - - volume of the tone (must be between 0 and 1.0)
    loops - - -number - - - - - - <optional>
    - - - - - -
    - - 0 - - how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped.
    - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - -

    Extends

    - - - - -
      -
    • SoundPlayer
    • -
    - - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    (protected, static) _initSoundLibrary()

    - - - - - - -
    - Initialise the sound library. - -

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

    -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (static) accept(sound) → {Object|undefined}

    - - - - - - -
    - 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.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    sound - - -module:sound.Sound - - - - the sound
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - an instance of TonePlayer that can play the given sound or undefined otherwise -
    - - - -
    -
    - Type -
    -
    - -Object -| - -undefined - - -
    -
    - - - - - - - - - - - - - -

    getDuration() → {number}

    - - - - - - -
    - Get the duration of the sound. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the duration of the sound, in seconds -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    play(loopsopt)

    - - - - - - -
    - Start playing the sound. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    loops - - -boolean - - - - - - <optional>
    - - - - - -
    how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setDuration(duration_s)

    - - - - - - -
    - Set the duration of the tone. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    duration_s - - -number - - - - the duration of the tone (in seconds) If duration_s == -1, the sound will play indefinitely.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setLoops(loops)

    - - - - - - -
    - Set the number of loops. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    loops - - -number - - - - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setVolume(volume, muteopt)

    - - - - - - -
    - Set the volume of the tone. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    volume - - -Integer - - - - - - - - - - - - the volume of the tone
    mute - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to mute the tone
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    stop()

    - - - - - - -
    - Stop playing the sound immediately. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-sound.TrackPlayer.html b/docs/module-sound.TrackPlayer.html deleted file mode 100644 index eed5e7e4..00000000 --- a/docs/module-sound.TrackPlayer.html +++ /dev/null @@ -1,1627 +0,0 @@ - - - - - JSDoc: Class: TrackPlayer - - - - - - - - - - -
    - -

    Class: TrackPlayer

    - - - - - - -
    - -
    - -

    - sound.TrackPlayer(options)

    - - -
    - -
    -
    - - - - - - -

    new TrackPlayer(options)

    - - - - - - -
    -

    This class handles the playback of sound tracks.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    options - - -Object - - - - -
    Properties
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    psychoJS - - -module:core.PsychoJS - - - - - - - - - - - - the PsychoJS instance
    howl - - -Object - - - - - - - - - - - - the sound object (see https://howlerjs.com/)
    startTime - - -number - - - - - - <optional>
    - - - - - -
    - - 0 - - start of playback (in seconds)
    stopTime - - -number - - - - - - <optional>
    - - - - - -
    - - -1 - - end of playback (in seconds)
    stereo - - -boolean - - - - - - <optional>
    - - - - - -
    - - true - - whether or not to play the sound or track in stereo
    volume - - -number - - - - - - <optional>
    - - - - - -
    - - 1.0 - - volume of the sound (must be between 0 and 1.0)
    loops - - -number - - - - - - <optional>
    - - - - - -
    - - 0 - - how many times to repeat the track or tone after it has played *
    - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    To Do:
    -
    -
      -
    • stopTime is currently not implemented (tracks will play from startTime to finish)
    • - -
    • stereo is currently not implemented
    • -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - -

    Extends

    - - - - -
      -
    • SoundPlayer
    • -
    - - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    (static) accept(sound) → {Object|undefined}

    - - - - - - -
    - Determine whether this player can play the given sound. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    sound - - -module:sound.Sound - - - - the sound, which should be the name of an audio resource - file
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - an instance of TrackPlayer that can play the given track or undefined otherwise -
    - - - -
    -
    - Type -
    -
    - -Object -| - -undefined - - -
    -
    - - - - - - - - - - - - - -

    getDuration() → {number}

    - - - - - - -
    - Get the duration of the sound, in seconds. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the duration of the track, in seconds -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    play(loops, fadeDurationopt)

    - - - - - - -
    - Start playing the sound. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    loops - - -number - - - - - - - - - - - - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
    fadeDuration - - -number - - - - - - <optional>
    - - - - - -
    - - 17 - - how long should the fading in last in ms
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setDuration(duration_s)

    - - - - - - -
    - Set the duration of the default sprite. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    duration_s - - -number - - - - the duration of the track in seconds
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setLoops(loops)

    - - - - - - -
    - Set the number of loops. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    loops - - -number - - - - how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    setVolume(volume, muteopt)

    - - - - - - -
    - Set the volume of the tone. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    volume - - -Integer - - - - - - - - - - - - the volume of the track (must be between 0 and 1.0)
    mute - - -boolean - - - - - - <optional>
    - - - - - -
    - - false - - whether or not to mute the track
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    stop(fadeDurationopt)

    - - - - - - -
    - Stop playing the sound immediately. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    fadeDuration - - -number - - - - - - <optional>
    - - - - - -
    - - 17 - - how long should the fading out last in ms
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-sound.html b/docs/module-sound.html index 9685f8a8..449daf59 100644 --- a/docs/module-sound.html +++ b/docs/module-sound.html @@ -1,23 +1,47 @@ + - JSDoc: Module: sound - - - + sound - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + -

    Module: sound

    + + +
    + +

    sound

    + @@ -33,13 +57,15 @@

    Module: sound

    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-util.Clock.html b/docs/module-util.Clock.html deleted file mode 100644 index 59db5d0c..00000000 --- a/docs/module-util.Clock.html +++ /dev/null @@ -1,495 +0,0 @@ - - - - - JSDoc: Class: Clock - - - - - - - - - - -
    - -

    Class: Clock

    - - - - - - -
    - -
    - -

    - util.Clock()

    - - -
    - -
    -
    - - - - - - -

    new Clock()

    - - - - - - -
    -

    Clock is a MonotonicClock that also offers the possibility of being reset.

    -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - -

    Extends

    - - - - -
      -
    • MonotonicClock
    • -
    - - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    add(deltaTimeopt)

    - - - - - - -
    - Add more time to the clock's 'start' time (t0). - -

    Note: by adding time to t0, the current time is pushed forward (it becomes -smaller). As a consequence, getTime() may return a negative number.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    deltaTime - - -number - - - - - - <optional>
    - - - - - -
    the time to be added to the clock's start time (t0)
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    reset(newTimeopt)

    - - - - - - -
    - Reset the time on the clock. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    newTime - - -number - - - - - - <optional>
    - - - - - -
    - - 0 - - the new time on the clock.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.Color.html b/docs/module-util.Color.html deleted file mode 100644 index 57892e96..00000000 --- a/docs/module-util.Color.html +++ /dev/null @@ -1,1966 +0,0 @@ - - - - - JSDoc: Class: Color - - - - - - - - - - -
    - -

    Class: Color

    - - - - - - -
    - -
    - -

    - util.Color(objopt, colorspaceopt)

    - - -
    - -
    -
    - - - - - - -

    new Color(objopt, colorspaceopt)

    - - - - - - -
    -

    This class handles multiple color spaces, and offers various -static methods for converting colors from one space to another.

    - -

    The constructor accepts the following color representations: -

      -
    • a named color, e.g. 'aliceblue' (the colorspace must be RGB)
    • -
    • an hexadecimal string representation, e.g. '#FF0000' (the colorspace must be RGB)
    • -
    • an hexadecimal number representation, e.g. 0xFF0000 (the colorspace must be RGB)
    • -
    • a triplet of numbers, e.g. [-1, 0, 1], [0, 128, 255] (the numbers must be within the range determined by the colorspace)
    • -
    -

    - -

    Note: internally, colors are represented as a [r,g,b] triplet with r,g,b in [0,1].

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    obj - - -string -| - -number -| - -Array.<number> -| - -undefined - - - - - - <optional>
    - - - - - -
    - - 'black' - - an object representing a color
    colorspace - - -module:util.Color#COLOR_SPACE -| - -undefined - - - - - - <optional>
    - - - - - -
    - - Color.COLOR_SPACE.RGB - - the colorspace of that color
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    To Do:
    -
    -
      -
    • implement HSV, DKL, and LMS colorspaces
    • -
    -
    - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -

    Members

    - - - -

    (readonly) COLOR_SPACE :Symbol

    - - - - -
    - Color spaces. -
    - - - -
    Type:
    -
      -
    • - -Symbol - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    (readonly) NAMED_COLORS :string

    - - - - -
    - Named colors. -
    - - - -
    Type:
    -
      -
    • - -string - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - -

    Methods

    - - - - - - - -

    (static) hex() → {string}

    - - - - - - -
    - Get the hexadecimal color code equivalent of this Color. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the hexadecimal color code equivalent -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) hexToRgb(hex) → {Array.<number>}

    - - - - - - -
    - Get the [0,1] RGB triplet equivalent of the hexadecimal color code. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    hex - - -string - - - - the hexadecimal color code
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the [0,1] RGB triplet equivalent -
    - - - -
    -
    - Type -
    -
    - -Array.<number> - - -
    -
    - - - - - - - - - - - - - -

    (static) hexToRgb255(hex) → {Array.<number>}

    - - - - - - -
    - Get the [0,255] RGB triplet equivalent of the hexadecimal color code. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    hex - - -string - - - - the hexadecimal color code
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the [0,255] RGB triplet equivalent -
    - - - -
    -
    - Type -
    -
    - -Array.<number> - - -
    -
    - - - - - - - - - - - - - -

    (static) int() → {number}

    - - - - - - -
    - Get the integer code equivalent of this Color. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the integer code equivalent -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    (static) rgb() → {Array.<number>}

    - - - - - - -
    - Get the [0,1] RGB triplet equivalent of this Color. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the [0,1] RGB triplet equivalent -
    - - - -
    -
    - Type -
    -
    - -Array.<number> - - -
    -
    - - - - - - - - - - - - - -

    (static) rgb255() → {Array.<number>}

    - - - - - - -
    - Get the [0,255] RGB triplet equivalent of this Color. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the [0,255] RGB triplet equivalent -
    - - - -
    -
    - Type -
    -
    - -Array.<number> - - -
    -
    - - - - - - - - - - - - - -

    (static) rgb255ToHex(rgb255) → {string}

    - - - - - - -
    - Get the hexadecimal color code equivalent of the [0, 255] RGB triplet. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    rgb255 - - -Array.<number> - - - - the [0, 255] RGB triplet
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the hexadecimal color code equivalent -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) rgb255ToInt(rgb255) → {number}

    - - - - - - -
    - Get the integer equivalent of the [0, 255] RGB triplet. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    rgb255 - - -Array.<number> - - - - the [0, 255] RGB triplet
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the integer equivalent -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    (static) rgbToHex(rgb) → {string}

    - - - - - - -
    - Get the hexadecimal color code equivalent of the [0, 1] RGB triplet. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    rgb - - -Array.<number> - - - - the [0, 1] RGB triplet
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the hexadecimal color code equivalent -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) rgbToInt(rgb) → {number}

    - - - - - - -
    - Get the integer equivalent of the [0, 1] RGB triplet. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    rgb - - -Array.<number> - - - - the [0, 1] RGB triplet
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the integer equivalent -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    (static) toString() → {string}

    - - - - - - -
    - String representation of the color, i.e. the hexadecimal representation. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the representation. -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.ColorMixin.html b/docs/module-util.ColorMixin.html index 2c88fb37..bc34d76e 100644 --- a/docs/module-util.ColorMixin.html +++ b/docs/module-util.ColorMixin.html @@ -1,23 +1,47 @@ + - JSDoc: Mixin: ColorMixin - - - + ColorMixin - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Mixin: ColorMixin

    + + + + +
    + +

    ColorMixin

    + @@ -29,25 +53,27 @@

    Mixin: ColorMixin

    - util.ColorMixin

    + util. + + ColorMixin +

    -
    +
    -

    This mixin implement color and contrast changes for visual stimuli

    - - - - +
    - +
    Source:
    +
    @@ -71,10 +97,7 @@

    -
    Source:
    -
    + @@ -85,20 +108,27 @@

    + + + +

    This mixin implement color and contrast changes for visual stimuli

    + + + +
    -
    - - + + @@ -111,16 +141,59 @@

    Methods

    -

    getContrastedColor(color, contrast)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + -
    - Get a new contrasted Color. + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get a new contrasted Color.

    @@ -131,6 +204,8 @@

    get + +

    Parameters:
    @@ -178,7 +253,7 @@
    Parameters:
    - the color +

    the color

    @@ -201,7 +276,7 @@
    Parameters:
    - the contrast (must be between 0 and 1) +

    the contrast (must be between 0 and 1)

    @@ -213,80 +288,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setColor(color, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setColor(color, logopt)

    + +
    + -
    - Setter for Color attribute. + +
    +

    Setter for Color attribute.

    @@ -297,6 +369,8 @@

    setColorParameters:

    @@ -332,7 +406,7 @@
    Parameters:
    -Color +Color @@ -354,7 +428,7 @@
    Parameters:
    - the new color +

    the new color

    @@ -388,12 +462,12 @@
    Parameters:
    - false + false - whether or not to log +

    whether or not to log

    @@ -405,80 +479,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    setContrast(contrast, logopt)

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    setContrast(contrast, logopt)

    + +
    -
    - Setter for Contrast attribute. + + +
    +

    Setter for Contrast attribute.

    @@ -489,6 +560,8 @@

    setContras + +

    Parameters:
    @@ -546,7 +619,7 @@
    Parameters:
    - the new contrast (must be between 0 and 1) +

    the new contrast (must be between 0 and 1)

    @@ -580,12 +653,12 @@
    Parameters:
    - false + false - whether or not to log +

    whether or not to log

    @@ -597,52 +670,6 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - @@ -669,19 +696,23 @@
    Parameters:
    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-util.CountdownTimer.html b/docs/module-util.CountdownTimer.html deleted file mode 100644 index f9e6c8e1..00000000 --- a/docs/module-util.CountdownTimer.html +++ /dev/null @@ -1,667 +0,0 @@ - - - - - JSDoc: Class: CountdownTimer - - - - - - - - - - -
    - -

    Class: CountdownTimer

    - - - - - - -
    - -
    - -

    - util.CountdownTimer(startTimeopt)

    - - -
    - -
    -
    - - - - - - -

    new CountdownTimer(startTimeopt)

    - - - - - - -
    -

    CountdownTimer is a clock counts down from the time of last reset. - - - - - - - - - -

    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    startTime - - -number - - - - - - <optional>
    - - - - - -
    - - 0 - - the start time of the countdown
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - -

    Extends

    - - - - -
      -
    • Clock
    • -
    - - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    add(deltaTimeopt)

    - - - - - - -
    - Add more time to the clock's 'start' time (t0). - -

    Note: by adding time to t0, you push the current time forward (make it -smaller). As a consequence, getTime() may return a negative number.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    deltaTime - - -number - - - - - - <optional>
    - - - - - -
    the time to be added to the clock's start time (t0)
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    getTime() → {number}

    - - - - - - -
    - Get the time currently left on the countdown. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the time left on the countdown (in seconds) -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    reset(newTimeopt)

    - - - - - - -
    - Reset the time on the countdown. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    newTime - - -number - - - - - - <optional>
    - - - - - -
    if newTime is undefined, the countdown time is reset to zero, otherwise we set it -to newTime
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.EventEmitter.html b/docs/module-util.EventEmitter.html deleted file mode 100644 index de2709d9..00000000 --- a/docs/module-util.EventEmitter.html +++ /dev/null @@ -1,1009 +0,0 @@ - - - - - JSDoc: Class: EventEmitter - - - - - - - - - - -
    - -

    Class: EventEmitter

    - - - - - - -
    - -
    - -

    - util.EventEmitter()

    - - -
    - -
    -
    - - - - - - -

    new EventEmitter()

    - - - - - - -
    -

    EventEmitter implements the classic observer/observable pattern.

    - -

    Note: this is heavily inspired by http://www.datchley.name/es6-eventemitter/

    -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    Example
    - -
    let observable = new EventEmitter();
    -let uuid1 = observable.on('change', data => { console.log(data); });
    -observable.emit("change", { a: 1 });
    -observable.off("change", uuid1);
    -observable.emit("change", { a: 1 });
    - - - - -
    - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    emit(name, data) → {boolean}

    - - - - - - -
    - Emit an event with a given name and associated data. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    name - - -String - - - - the name of the event
    data - - -object - - - - the data of the event
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - true if at least one listener has been registered for that event, and false otherwise -
    - - - -
    -
    - Type -
    -
    - -boolean - - -
    -
    - - - - - - - - - - - - - -

    off(name, listener)

    - - - - - - -
    - Remove the listener with the given uuid associated to the given event name. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    name - - -String - - - - the name of the event
    listener - - -module:util.EventEmitter~Listener - - - - a listener called upon emission of the event
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -

    on(name, listener)

    - - - - - - -
    - Register a new listener for events with the given name emitted by this instance. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    name - - -String - - - - the name of the event
    listener - - -module:util.EventEmitter~Listener - - - - a listener called upon emission of the event
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - string - the unique identifier associated with that (event, listener) pair (useful to remove the listener) -
    - - - - - - - - - - - - - - - -

    once(name, listener)

    - - - - - - -
    - Register a new listener for the given event name, and remove it as soon as the event has been emitted. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    name - - -String - - - - the name of the event
    listener - - -module:util.EventEmitter~Listener - - - - a listener called upon emission of the event
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - string - the unique identifier associated with that (event, listener) pair (useful to remove the listener) -
    - - - - - - - - - - - - - -

    Type Definitions

    - - - - - - - -

    Listener(data)

    - - - - - - -
    - Listener called when this instance emits an event for which it is registered. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    data - - -object - - - - the data passed to the listener
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.MixinBuilder.html b/docs/module-util.MixinBuilder.html deleted file mode 100644 index a78343f7..00000000 --- a/docs/module-util.MixinBuilder.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - JSDoc: Class: MixinBuilder - - - - - - - - - - -
    - -

    Class: MixinBuilder

    - - - - - - -
    - -
    - -

    - util.MixinBuilder(superclass)

    - - -
    - -
    -
    - - - - - - -

    new MixinBuilder(superclass)

    - - - - - - -
    - Syntactic sugar for Mixins - -

    This is heavily adapted from: http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    superclass - - -Object - - - -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    Example
    - -
    class BaseClass { ... }
    -let Mixin1 = (superclass) => class extends superclass { ... }
    -let Mixin2 = (superclass) => class extends superclass { ... }
    -class NewClass extends mix(BaseClass).with(Mixin1, Mixin2) { ... }
    - - - - -
    - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.MonotonicClock.html b/docs/module-util.MonotonicClock.html deleted file mode 100644 index e3a0bf89..00000000 --- a/docs/module-util.MonotonicClock.html +++ /dev/null @@ -1,873 +0,0 @@ - - - - - JSDoc: Class: MonotonicClock - - - - - - - - - - -
    - -

    Class: MonotonicClock

    - - - - - - -
    - -
    - -

    - util.MonotonicClock(startTimeopt)

    - - -
    - -
    -
    - - - - - - -

    new MonotonicClock(startTimeopt)

    - - - - - - -
    -

    MonotonicClock offers a convenient way to keep track of time during experiments. An experiment can have as many independent clocks as needed, e.g. one to time responses, another one to keep track of stimuli, etc.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDefaultDescription
    startTime - - -number - - - - - - <optional>
    - - - - - -
    - - <time elapsed since the reference point, i.e. the time when the module was loaded> - - the clock's start time (in ms)
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    (static) getDate(locales, options) → {string}

    - - - - - - -
    - Get the current timestamp with language-sensitive formatting rules applied. - -

    Note: This is just a convenience wrapper around `Intl.DateTimeFormat()`.

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    locales - - -string -| - -array.string - - - - A string with a BCP 47 language tag, or an array of such strings.
    options - - -object - - - - An object with detailed date and time styling information.
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - The current timestamp in the chosen format. -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) getDateStr() → {string}

    - - - - - - -
    - Get the clock's current time in the default format filtering out file name unsafe characters. - -

    Note: This is mostly used as an appendix to the name of the keys save to the server.

    -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - A string representing the current time formatted as YYYY-MM-DD_HH[h]mm.ss.sss -
    - - - -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    getLastResetTime() → {number}

    - - - - - - -
    - Get the current offset being applied to the high resolution timebase used by this Clock. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the offset (in seconds) -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    getReferenceTime() → {number}

    - - - - - - -
    - Get the time elapsed since the reference point. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the time elapsed since the reference point (in seconds) -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -

    getTime() → {number}

    - - - - - - -
    - Get the current time on this clock. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - -
    - the current time (in seconds) -
    - - - -
    -
    - Type -
    -
    - -number - - -
    -
    - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.PsychObject.html b/docs/module-util.PsychObject.html index d2ba8091..adbe04d9 100644 --- a/docs/module-util.PsychObject.html +++ b/docs/module-util.PsychObject.html @@ -1,23 +1,47 @@ + - JSDoc: Class: PsychObject - - - + PsychObject - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Class: PsychObject

    +
    + +

    PsychObject

    + @@ -28,29 +52,78 @@

    Class: PsychObject

    -

    - util.PsychObject(psychoJS, name)

    +

    + util. -

    PsychoObject is the base class for all PsychoJS objects. + PsychObject +

    + +

    PsychoObject is the base class for all PsychoJS objects. It is responsible for handling attributes.

    -
    +
    +

    Constructor

    -

    new PsychObject(psychoJS, name)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + @@ -103,7 +176,7 @@
    Parameters:
    - the PsychoJS instance +

    the PsychoJS instance

    @@ -126,7 +199,7 @@
    Parameters:
    - the name of the object (mostly useful for debugging) +

    the name of the object (mostly useful for debugging)

    @@ -138,78 +211,81 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - -
    Source:
    -
    - - + +
    - +

    Extends

    + + + + + + + + + + +

    Members

    + + +

    psychoJS

    +
    + +
    Source:
    +
    + + + -
    -

    Extends

    - + + -
      -
    • EventEmitter
    • -
    + + @@ -224,17 +300,14 @@

    Extends

    -

    Members

    + - - -

    psychoJS

    -
    - Get the PsychoJS instance. +
    +

    Get the PsychoJS instance.

    @@ -243,10 +316,22 @@

    psychoJSpsychoJS

    + + + + +
    - +
    Source:
    +
    @@ -270,10 +355,7 @@

    psychoJSSource: -
    + @@ -287,16 +369,8 @@

    psychoJSpsychoJS

    - - - - -
    - Setter for the PsychoJS attribute. +
    +

    Setter for the PsychoJS attribute.

    @@ -305,36 +379,31 @@

    psychoJS - - - - + +

    Methods

    - + + - +

    (protected) _addAttribute(name, value, defaultValueopt, onChangeopt)

    - - - - +
    Source:
    @@ -343,34 +412,38 @@

    psychoJS + + + + + - -

    Methods

    - - + -

    (protected) _addAttribute(name, value, defaultValueopt, onChangeopt)

    + +

    + -
    - Add an attribute to this instance (e.g. define setters and getters) and affect a value to it. + +
    +

    Add an attribute to this instance (e.g. define setters and getters) and affect a value to it.

    @@ -381,6 +454,8 @@

    (protected) Parameters:

    @@ -432,7 +507,7 @@
    Parameters:
    - the name of the attribute +

    the name of the attribute

    @@ -463,7 +538,7 @@
    Parameters:
    - the value of the attribute +

    the value of the attribute

    @@ -496,7 +571,7 @@
    Parameters:
    - the default value for the attribute +

    the default value for the attribute

    @@ -529,7 +604,7 @@
    Parameters:
    - function called upon changes to the attribute value +

    function called upon changes to the attribute value

    @@ -541,80 +616,77 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    (protected) _setAttribute(attributeName, attributeValue, logopt, operationopt, stealthopt) → {boolean}

    -
    - - - - +
    + +
    Source:
    +
    + + + + + + + + + + + - - + -

    (protected) _setAttribute(attributeName, attributeValue, logopt, operationopt, stealthopt) → {boolean}

    + +
    + + -
    - Set the value of an attribute. +
    +

    Set the value of an attribute.

    @@ -625,6 +697,8 @@

    (protected) Parameters:

    @@ -682,7 +756,7 @@
    Parameters:
    - the name of the attribute +

    the name of the attribute

    @@ -717,7 +791,7 @@
    Parameters:
    - the value of the attribute +

    the value of the attribute

    @@ -751,12 +825,12 @@
    Parameters:
    - false + false - whether of not to log +

    whether of not to log

    @@ -793,7 +867,7 @@
    Parameters:
    - the binary operation such that the new value of the attribute is the result of the application of the operation to the current value of the attribute and attributeValue +

    the binary operation such that the new value of the attribute is the result of the application of the operation to the current value of the attribute and attributeValue

    @@ -827,12 +901,12 @@
    Parameters:
    - false + false - whether or not to call the potential attribute setters when setting the value of this attribute +

    whether or not to call the potential attribute setters when setting the value of this attribute

    @@ -844,36 +918,246 @@
    Parameters:
    -
    - - - - - - - - - - +
    Returns:
    - + +
    +

    whether or not the value of that attribute has changed (false if the attribute +was not previously set)

    +
    - - + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    emit(name, data) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Emit an event with a given name and associated data.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    name + + +String + + + +

    the name of the event

    data + + +object + + + +

    the data of the event

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    true if at least one listener has been registered for that event, and false otherwise

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    off(name, listener)

    + + + + + + +
    + +
    Source:
    @@ -882,7 +1166,290 @@
    Parameters:
    -
    + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Remove the listener with the given uuid associated to the given event name.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    name + + +String + + + +

    the name of the event

    listener + + +module:util.EventEmitter~Listener + + + +

    a listener called upon emission of the event

    + + + + + + + + + + + + + + + + + + + + + + + + +

    on(name, listener)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + +
    Overrides:
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Register a new listener for events with the given name emitted by this instance.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    name + + +String + + + +

    the name of the event

    listener + + +module:util.EventEmitter~Listener + + + +

    a listener called upon emission of the event

    + @@ -902,47 +1469,78 @@
    Returns:
    - whether or not the value of that attribute has changed (false if the attribute -was not previously set) +

    string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)

    -
    -
    - Type -
    -
    + + + + -boolean + + -
    -
    +

    once(name, listener)

    +
    - - + +
    Source:
    +
    + -

    toString() → {string}

    + + + + +
    Overrides:
    +
    + + + + + + -
    - String representation of the PsychObject. + -

    Note: attribute values are limited to 50 characters.

    + + + + + + + + + +
    + + + + + +
    +

    Register a new listener for the given event name, and remove it as soon as the event has been emitted.

    @@ -955,16 +1553,124 @@

    toStringParameters:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    name + + +String + + + +

    the name of the event

    listener + + +module:util.EventEmitter~Listener + + + +

    a listener called upon emission of the event

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)

    +
    -
    + + + + + +

    toString() → {string}

    + + + + +
    + + +
    Source:
    +
    @@ -984,10 +1690,11 @@

    toStringSource: -
    + + + + + @@ -1001,6 +1708,25 @@

    toString +

    String representation of the PsychObject.

    +

    Note: attribute values are limited to 50 characters.

    +

    + + + + + + + + + + + + + + + @@ -1015,12 +1741,12 @@
    Returns:
    - the representation +

    the representation

    -
    +
    Type
    @@ -1036,8 +1762,6 @@
    Returns:
    - - @@ -1051,19 +1775,23 @@
    Returns:
    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:56 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + \ No newline at end of file diff --git a/docs/module-util.Scheduler.html b/docs/module-util.Scheduler.html deleted file mode 100644 index a3bf3e29..00000000 --- a/docs/module-util.Scheduler.html +++ /dev/null @@ -1,960 +0,0 @@ - - - - - JSDoc: Class: Scheduler - - - - - - - - - - -
    - -

    Class: Scheduler

    - - - - - - -
    - -
    - -

    - util.Scheduler(psychoJS)

    - - -
    - -
    -
    - - - - - - -

    new Scheduler(psychoJS)

    - - - - - - -
    -

    A scheduler helps run the main loop by managing scheduled functions, -called tasks, after each frame is displayed.

    - -

    -Tasks are either another Scheduler, or a -JavaScript functions returning one of the following codes: -

      -
    • Scheduler.Event.NEXT: Move onto the next task *without* rendering the scene first.
    • -
    • Scheduler.Event.FLIP_REPEAT: Render the scene and repeat the task.
    • -
    • Scheduler.Event.FLIP_NEXT: Render the scene and move onto the next task.
    • -
    • Scheduler.Event.QUIT: Quit the scheduler.
    • -
    -

    - -

    It is possible to create sub-schedulers, e.g. to handle loops. -Sub-schedulers are added to a parent scheduler as a normal -task would be by calling scheduler.add(subScheduler).

    - -

    Conditional branching is also available: -scheduler.addConditionalBranches

    -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescription
    psychoJS - - -module:core.PsychoJS - - - - the PsychoJS instance
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - -

    Members

    - - - -

    add

    - - - - -
    - Schedule a new task. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    addConditional

    - - - - -
    - Schedule a series of task or another, based on a condition. - -

    Note: the tasks are sub-schedulers.

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    (readonly) Event :Symbol

    - - - - -
    - Events. -
    - - - -
    Type:
    -
      -
    • - -Symbol - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    start

    - - - - -
    - Start this scheduler. - -

    Note: tasks are run after each animation frame.

    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    status

    - - - - -
    - Get the status of the scheduler. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    (readonly) Status :Symbol

    - - - - -
    - Status. -
    - - - -
    Type:
    -
      -
    • - -Symbol - - -
    • -
    - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - -

    stop

    - - - - -
    - Stop this scheduler. -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - -

    Type Definitions

    - - - - - - - -

    Condition() → {boolean}

    - - - - - - -
    - Condition evaluated when the task is run. -
    - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - -
    Returns:
    - - - - -
    -
    - Type -
    -
    - -boolean - - -
    -
    - - - - - - - - - - - - - -

    Task(argsopt)

    - - - - - - -
    - Task to be run by the scheduler. -
    - - - - - - - - - -
    Parameters:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    NameTypeAttributesDescription
    args - - -* - - - - - - <optional>
    - - - - - -
    optional arguments
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) -
    - - - - - \ No newline at end of file diff --git a/docs/module-util.html b/docs/module-util.html index 20b650d5..9274f044 100644 --- a/docs/module-util.html +++ b/docs/module-util.html @@ -1,23 +1,47 @@ + - JSDoc: Module: util - - - + util - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Module: util

    +
    + +

    util

    + @@ -33,13 +57,15 @@

    Module: util

    -
    +
    + + +
    -
    @@ -49,35 +75,32 @@

    Module: util

    Classes

    -
    Clock
    -
    - -
    Color
    +
    Clock
    -
    CountdownTimer
    +
    Color
    -
    EventEmitter
    +
    CountdownTimer
    -
    MixinBuilder
    +
    EventEmitter
    -
    MonotonicClock
    +
    PsychObject
    -
    PsychObject
    +
    MonotonicClock
    -
    Scheduler
    +
    Scheduler
    - - + +

    Mixins

    @@ -89,87 +112,84 @@

    Mixins

    - - -

    Methods

    +

    Members

    +

    (static) mix

    - + + + + +
    -

    (static) addInfoFromUrl(info)

    +
    Source:
    +
    + + -
    - Add info extracted from the URL to the given dictionary. - -

    We exclude all URL parameters starting with a double underscore -since those are reserved for client/server communication

    -
    + + + + + + + + + -
    Parameters:
    - - - - - - + - + + - - - - - - - - - - - +
    +

    Syntactic sugar for Mixins

    +

    This is heavily adapted from: http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/

    +
    - - - - - +
    Example
    - -
    NameTypeDescription
    info - - -Object - - the dictionary
    +
    class BaseClass { ... }
    +let Mixin1 = (superclass) => class extends superclass { ... }
    +let Mixin2 = (superclass) => class extends superclass { ... }
    +class NewClass extends mix(BaseClass).with(Mixin1, Mixin2) { ... }
    + + + +

    (static, constant) TEXT_DIRECTION

    + @@ -177,7 +197,10 @@
    Parameters:
    - +
    Source:
    +
    @@ -201,10 +224,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -218,38 +238,85 @@
    Parameters:
    +
    +

    Enum that stores possible text directions. +Note that Arabic is the same as RTL but added here to support PsychoPy's +languageStyle enum. Arabic reshaping is handled by the browser automatically.

    +
    + + + + + + + +

    Methods

    + + + +

    (static) addInfoFromUrl(info)

    + +
    + +
    Source:
    +
    + - - + -

    (static) average(input) → {number}

    + + + + + + + + + + + + + + + + + + + + +
    -
    - Calculate the average of the elements in the input array. -If 'input' is not an array, or if it is an empty array, then we return 0. + + +
    +

    Add info extracted from the URL to the given dictionary.

    +

    We exclude all URL parameters starting with a double underscore +since those are reserved for client/server communication

    @@ -260,6 +327,8 @@

    (static) aver + +

    Parameters:
    @@ -285,13 +354,13 @@
    Parameters:
    - input + info -array +Object @@ -301,7 +370,7 @@
    Parameters:
    - an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1] +

    the dictionary

    @@ -313,104 +382,78 @@
    Parameters:
    -
    - - - - - - - - - - - - - -
    Source:
    -
    - + + - +

    (static) average(input) → {number}

    -
    - - - - - - - - - - - -
    Returns:
    +
    - -
    - the average of the elements in the array -
    + +
    Source:
    +
    + + + -
    -
    - Type -
    -
    - -number + + -
    -
    + + + + + - - + -

    (static) count(input, value)

    + +
    -
    - Count the number of elements in the input array that match the given value. -

    Note: count is able to handle NaN, null, as well as any value convertible to a JSON string.

    + +
    +

    Calculate the average of the elements in the input array.

    +

    If 'input' is not an array, or if it is an empty array, then we return 0.

    @@ -421,6 +464,8 @@

    (static) count< + +

    Parameters:
    @@ -462,54 +507,72 @@
    Parameters:
    - the input array +

    an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1]

    + + - - - value - - - - - -Number -| -string -| -object -| -null - - - - - the matching value - - - - -
    +
    Returns:
    + + +
    +

    the average of the elements in the array

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + +

    (static) count(input, value)

    + + + + + + +
    + + +
    Source:
    +
    @@ -533,10 +596,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -550,6 +610,10 @@
    Parameters:
    +
    +

    Count the number of elements in the input array that match the given value.

    +

    Note: count is able to handle NaN, null, as well as any value convertible to a JSON string.

    +
    @@ -560,49 +624,123 @@
    Parameters:
    -
    Returns:
    + +
    Parameters:
    + + + + + + + + + + -
    - the number of matching elements -
    + + + + + + + + + + + + - - + + -

    (static) detectBrowser() → {string}

    - + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + +

    the input array

    value + + +Number +| + +string +| + +object +| + +null + + + +

    the matching value

    -
    - Detect the user's browser. -

    Note: since user agent is easily spoofed, we use a more sophisticated approach, as described here: -https://stackoverflow.com/a/9851769

    + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the number of matching elements

    + + + + +

    (static) detectBrowser() → {string}

    + @@ -610,7 +748,10 @@

    (static) - +
    Source:
    +
    @@ -634,10 +775,7 @@

    (static) Source: -
    + @@ -651,6 +789,26 @@

    (static) +

    Detect the user's browser.

    +

    Note: since user agent is easily spoofed, we use a more sophisticated approach, as described here: +https://stackoverflow.com/a/9851769

    +

    + + + + + + + + + + + + + + + @@ -665,13 +823,13 @@
    Returns:
    - the detected browser, one of 'Opera', 'Firefox', 'Safari', -'IE', 'Edge', 'EdgeChromium', 'Chrome', 'unknown' +

    the detected browser, one of 'Opera', 'Firefox', 'Safari', +'IE', 'Edge', 'EdgeChromium', 'Chrome', 'unknown'

    -
    +
    Type
    @@ -687,25 +845,66 @@
    Returns:
    - - -

    (static) extensionFromMimeType(mimeType) → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    -
    - Return the file extension corresponding to an audio mime type. + + + +
    +

    Return the file extension corresponding to an audio mime type. If the provided mimeType is not a string (e.g. null, undefined, an array) -or unknown, then '.dat' is returned, instead of throwing an exception. +or unknown, then '.dat' is returned, instead of throwing an exception.

    @@ -716,6 +915,8 @@

    (stati + +
    Parameters:
    @@ -757,7 +958,7 @@
    Parameters:
    - the MIME type, e.g. 'audio/webm;codecs=opus' +

    the MIME type, e.g. 'audio/webm;codecs=opus'

    @@ -769,50 +970,6 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - @@ -827,12 +984,12 @@
    Returns:
    - the corresponding file extension, e.g. '.webm' +

    the corresponding file extension, e.g. '.webm'

    -
    +
    Type
    @@ -848,23 +1005,64 @@
    Returns:
    - - -

    (static) flattenArray(array) → {Array.<Object>}

    - -
    - Recursively flatten an array of arrays. + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Recursively flatten an array of arrays.

    @@ -875,6 +1073,8 @@

    (static) Parameters:

    @@ -916,7 +1116,7 @@
    Parameters:
    - the input array of arrays +

    the input array of arrays

    @@ -928,50 +1128,6 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - - - @@ -986,12 +1142,12 @@
    Returns:
    - the flatten array +

    the flatten array

    -
    +
    Type
    @@ -1007,41 +1163,25 @@
    Returns:
    - - - -

    (static) getErrorStack() → {string}

    - +

    (static) getDownloadSpeed(psychoJS, nbDownloadsopt) → {number}

    -
    - Get the error stack of the calling, exception-throwing function. -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -1065,10 +1205,7 @@

    (static) Source: -
    + @@ -1082,60 +1219,13 @@

    (static) Returns:

    - - -
    - the error stack as a string +
    +

    Get an estimate of the download speed, by repeatedly downloading an image file from a distant +server.

    -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) getPositionFromObject(object, units) → {Array.<number>}

    - - - - - - -
    - Get the position of the object, in pixel units -
    - @@ -1157,8 +1247,12 @@
    Parameters:
    Type + Attributes + + Default + Description @@ -1169,46 +1263,75 @@
    Parameters:
    - object + psychoJS -Object +PsychoJS + + + + + + + + + + + + - the input object +

    the instance of PsychoJS

    - units + nbDownloads -string +number + + + <optional>
    + + + + + + + + + + 1 + + + - the units +

    the number of image downloads over which to average +the download speed

    @@ -1220,10 +1343,60 @@
    Parameters:
    -
    + + + + + + + + + + +
    Returns:
    + + +
    +

    the download speed, in megabits per second

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) getErrorStack() → {string}

    + + + +
    + + +
    Source:
    +
    @@ -1247,10 +1420,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1264,6 +1434,9 @@
    Parameters:
    +
    +

    Get the error stack of the calling, exception-throwing function.

    +
    @@ -1274,22 +1447,37 @@
    Parameters:
    -
    Returns:
    - -
    - the position of the object, in pixel units -
    -
    -
    + + + + + + + + + + + +
    Returns:
    + + +
    +

    the error stack as a string

    +
    + + + +
    +
    Type
    -Array.<number> +string
    @@ -1299,23 +1487,64 @@
    Returns:
    - - +

    (static) getPositionFromObject(object, units) → {Array.<number>}

    + -

    (static) getRequestError(jqXHR, textStatus, errorThrown)

    + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + -
    - Get the most informative error from the server response from a jquery server request. +
    +

    Get the position of the object, in pixel units

    @@ -1326,6 +1555,8 @@

    (static) Parameters:

    @@ -1351,61 +1582,100 @@
    Parameters:
    - jqXHR + object + +Object + + + - +

    the input object

    - textStatus + units + +string + + + - +

    the units

    + + - - - errorThrown - - - - - - - - + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position of the object, in pixel units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + - - +

    (static) getRequestError(jqXHR, textStatus, errorThrown)

    + @@ -1413,7 +1683,10 @@
    Parameters:
    - +
    Source:
    +
    @@ -1437,10 +1710,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1454,6 +1724,9 @@
    Parameters:
    +
    +

    Get the most informative error from the server response from a jquery server request.

    +
    @@ -1465,29 +1738,7991 @@
    Parameters:
    +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    jqXHR + +
    textStatus + +
    errorThrown +
    + + + + + + + + + + + + + + + + + + + + + +

    (static) getUrlParameters() → {URLSearchParams}

    + + + + +
    + + +
    Source:
    +
    + + -
    - Get the URL parameters. -
    + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the URL parameters.

    +
    + + + + + + + + + +
    Example
    + +
    const urlParameters = util.getUrlParameters();
    +for (const [key, value] of urlParameters)
    +  console.log(key + ' = ' + value);
    + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the iterable URLSearchParams

    +
    + + + +
    +
    + Type +
    +
    + +URLSearchParams + + +
    +
    + + + + + + + + + + +

    (static) index(input, value)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the index in the input array of the first element that matches the given value.

    +

    Note: index is able to handle NaN, null, as well as any value convertible to a JSON string.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    the input array

    value + + +Number +| + +string +| + +object +| + +null + + + +

    the matching value

    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    + +

    if the input array does not contain any matching element

    + +
    + + + + + +
    Returns:
    + + +
    +

    the index of the first element that matches the value

    +
    + + + + + + + + + + + + +

    (static) isEmpty(x) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Test if x is an 'empty' value.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    x + + +Object + + + +

    the value to test

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    true if x is one of the following: undefined, [], [undefined]

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    (static) isInt(obj) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Test whether an object is either an integer or the string representation of an integer.

    +

    This is adapted from: https://stackoverflow.com/a/14794066

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    obj + + +Object + + + +

    the input object

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    whether or not the object is an integer or the string representation of an integer

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    (static) isNumeric(input) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Check whether a value looks like a number

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +* + + + +

    Some value

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    Whether or not the value can be converted into a number

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    (static) IsPointInsidePolygon(point, vertices) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Check whether a point lies within a polygon

    +

    We are using the algorithm described here: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    point + + +Array.<number> + + + +

    the point

    vertices + + +Object + + + +

    the vertices defining the polygon

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    whether or not the point lies within the polygon

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    (static) makeUuid() → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get a Universally Unique Identifier (RFC4122 version 4)

    +

    See details here: https://www.ietf.org/rfc/rfc4122.txt

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the uuid

    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) offerDataForDownload(filename, data, type)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Offer data as download in the browser.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    filename + + +string + + + +

    the name of the file to be downloaded

    data + + +* + + + +

    the data

    type + + +string + + + +

    the MIME type of the data, e.g. 'text/csv' or 'application/json'

    + + + + + + + + + + + + + + + + + + + + + + + + +

    (static) pad(n, width) → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Pad the given floating-point number with however many 0 needed at the start such that +the padded integer part of the number is of the given width.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    n + +

    the input floating-point number

    width + +

    the desired width

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +
      +
    • the padded number, whose integer part has the given width
    • +
    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) promiseToTupple(promise) → {Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the resulting value of a promise into a tupple.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    promise + + +Promise + + + +

    the promise

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the resulting value in the format [error, return data] +where error is null if there was no error

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) randchoice(array, randomNumberGeneratoropt) → {Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Pick a random value from an array, uses util.shuffle to shuffle the array and returns the last value.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    array + + +Array.<Object> + + + + + + + + + +

    the input 1-D array

    randomNumberGenerator + + +function + + + + + + <optional>
    + + + + + +

    A function used to generated random numbers in the interal [0, 1). Defaults to Math.random

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    a chosen value from the array

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) randint(minopt, max) → {number}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Generates random integers a-la NumPy's in the "half-open" interval [min, max). In other words, from min inclusive to max exclusive. When max is undefined, as is the case by default, results are chosen from [0, min). An error is thrown if max is less than min.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    min + + +number + + + + + + <optional>
    + + + + + +
    + + 0 + +

    lowest integer to be drawn, or highest plus one if max is undefined (default)

    max + + +number + + + + + + + + + + + +

    one above the largest integer to be drawn

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    a random integer in the requested range (signed)

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) range(startopt, stop, stepopt) → {Array.<Number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Create a sequence of integers.

    +

    The sequence is such that the integer at index i is: start + step * i, with i >= 0 and start + step * i < stop

    +

    Note: this is a JavaScript implement of the Python range function, which explains the unusual management of arguments.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    start + + +Number + + + + + + <optional>
    + + + + + +
    + + 0 + +

    the value of start

    stop + + +Number + + + + + + + + + + + +

    the value of stop

    step + + +Number + + + + + + <optional>
    + + + + + +
    + + 1 + +

    the value of step

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the range as an array of numbers

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Number> + + +
    +
    + + + + + + + + + + +

    (static) round(input, places) → {number}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    See:
    +
    + +
    + + + +
    + + + + + +
    +

    Round to a certain number of decimal places.

    +

    This is the Crib Sheet provided solution, but please note that as of 2020 the most popular SO answer is different.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +number + + + +

    the number to be rounded

    places + + +number + + + +

    the max number of decimals desired

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    input rounded to the specified number of decimal places at most

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) selectFromArray(array, selection) → {Object|Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Select values from an array.

    +

    'selection' can be a single integer, an array of indices, or a string to be parsed, e.g.: +

      +
    • 5
    • +
    • [1,2,3,10]
    • +
    • '1,5,10'
    • +
    • '1:2:5'
    • +
    • '5:'
    • +
    • '-5:-2, 9, 11:5:22'
    • +

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    array + + +Array.<Object> + + + +

    the input array

    selection + + +number +| + +Array.<number> +| + +string + + + +

    the selection

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the array of selected items

    +
    + + + +
    +
    + Type +
    +
    + +Object +| + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) shuffle(array, randomNumberGeneratoropt) → {Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Shuffle an array in place using the Fisher-Yastes's modern algorithm

    +

    See details here: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDescription
    array + + +Array.<Object> + + + + + + + + + +

    the input 1-D array

    randomNumberGenerator + + +function + + + + + + <optional>
    + + + + + +

    A function used to generated random numbers in the interal [0, 1). Defaults to Math.random

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the shuffled array

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) sliceArray(array, fromopt, toopt, stepopt) → {Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Slice an array.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    array + + +Array.<Object> + + + + + + + + + + + +

    the input array

    from + + +number + + + + + + <optional>
    + + + + + +
    + + NaN + +

    the start of the slice

    to + + +number + + + + + + <optional>
    + + + + + +
    + + NaN + +

    the end of the slice

    step + + +number + + + + + + <optional>
    + + + + + +
    + + NaN + +

    the step of the slice

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the array slice

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) sort(input) → {array}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Sort the elements of the input array, in increasing alphabetical or numerical order.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    an array of numbers or of strings

    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    + +

    if 'input' is not an array, or if its elements are not consistent in types, or if they are not all either numbers or +strings

    + +
    + + + + + +
    Returns:
    + + +
    +

    the sorted array

    +
    + + + +
    +
    + Type +
    +
    + +array + + +
    +
    + + + + + + + + + + +

    (static) sum(input, start) → {number}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Calculate the sum of the elements in the input array.

    +

    If 'input' is not an array, then we return start.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1]

    start + + +number + + + +

    value added to the sum of numbers (a la Python)

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the sum of the elements in the array + start

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) to_height(pos, posUnit, win) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the position to height units.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    pos + + +Array.<number> + + + +

    the input position

    posUnit + + +string + + + +

    the position units

    win + + +Window + + + +

    the associated Window

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position in height units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) to_norm(pos, posUnit, win) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the position to norm units.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    pos + + +Array.<number> + + + +

    the input position

    posUnit + + +string + + + +

    the position units

    win + + +Window + + + +

    the associated Window

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position in norm units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) to_pixiPoint(pos, posUnit, win, integerCoordinatesopt) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert a position to a PIXI Point.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    pos + + +Array.<number> + + + + + + + + + + + +

    the input position

    posUnit + + +string + + + + + + + + + + + +

    the position units

    win + + +Window + + + + + + + + + + + +

    the associated Window

    integerCoordinates + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether or not to round the PIXI Point coordinates.

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position as a PIXI Point

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) to_px(pos, posUnit, win, integerCoordinatesopt) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the position to pixel units.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    pos + + +Array.<number> + + + + + + + + + + + +

    the input position

    posUnit + + +string + + + + + + + + + + + +

    the position units

    win + + +Window + + + + + + + + + + + +

    the associated Window

    integerCoordinates + + +boolean + + + + + + <optional>
    + + + + + +
    + + false + +

    whether or not to round the position coordinates.

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position in pixel units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) to_unit(pos, posUnit, win, targetUnit) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the position to given units.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    pos + + +Array.<number> + + + +

    the input position

    posUnit + + +string + + + +

    the position units

    win + + +Window + + + +

    the associated Window

    targetUnit + + +string + + + +

    the target units

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position in target units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) to_win(pos, posUnit, win) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert the position to window units.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    pos + + +Array.<number> + + + +

    the input position

    posUnit + + +string + + + +

    the position units

    win + + +Window + + + +

    the associated Window

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position in window units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) toNumerical(obj) → {number|Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert obj to its numerical form.

    +
      +
    • number -> number, e.g. 2 -> 2
    • +
    • [number] -> [number], e.g. [1,2,3] -> [1,2,3]
    • +
    • numeral string -> number, e.g. "8" -> 8
    • +
    • [number | numeral string] -> [number], e.g. [1, 2, "3"] -> [1,2,3]
    • +
    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    obj + + +Object + + + +

    the input object

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the numerical form of the input object

    +
    + + + +
    +
    + Type +
    +
    + +number +| + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) toString(object) → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert an object to its string representation, taking care of symbols.

    +

    Note: if the object is not already a string, we JSON stringify it and detect circularity.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    object + + +Object + + + +

    the input object

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    a string representation of the object or 'Object (circular)'

    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) turnSquareBracketsIntoArrays(input, max) → {array}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Convert a string representing a JSON array, e.g. "[1, 2]" into an array, e.g. ["1","2"]. +This approach overcomes the built-in JSON parsing limitations when it comes to eg. floats +missing the naught prefix, and is able to process several arrays, e.g. "[1,2][3,4]".

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +string + + + +

    string potentially containing JSON arrays

    max + + +string + + + +

    how many matches to return, unwrap resulting array if less than two

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    an array if arrays were found, undefined otherwise

    +
    + + + +
    +
    + Type +
    +
    + +array + + +
    +
    + + + + + + + + + + +

    (protected, inner) _match(value)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Create a boolean function that compares an input element to the given value.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    value + + +Number +| + +string +| + +object +| + +null + + + +

    the matching value

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    a function that compares an input element to the given value

    +
    + + + + + + + + + + + + + +

    + + + + + + + + + +
    + +
    + + + +
    + +
    + +
    + + + + + +
    + + + + + + + +

    Classes

    + +
    +
    Clock
    +
    + +
    Color
    +
    + +
    CountdownTimer
    +
    + +
    EventEmitter
    +
    + +
    PsychObject
    +
    + +
    MonotonicClock
    +
    + +
    Scheduler
    +
    +
    + + + + + +

    Mixins

    + +
    +
    ColorMixin
    +
    +
    + + + + + +

    Members

    + + + +

    (static) mix

    + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Syntactic sugar for Mixins

    +

    This is heavily adapted from: http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/

    +
    + + + + + + + +
    Example
    + +
    class BaseClass { ... }
    +let Mixin1 = (superclass) => class extends superclass { ... }
    +let Mixin2 = (superclass) => class extends superclass { ... }
    +class NewClass extends mix(BaseClass).with(Mixin1, Mixin2) { ... }
    + + + + + +

    (static, constant) TEXT_DIRECTION

    + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Enum that stores possible text directions. +Note that Arabic is the same as RTL but added here to support PsychoPy's +languageStyle enum. Arabic reshaping is handled by the browser automatically.

    +
    + + + + + + + + + + + + +

    Methods

    + + + + + + +

    (static) addInfoFromUrl(info)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Add info extracted from the URL to the given dictionary.

    +

    We exclude all URL parameters starting with a double underscore +since those are reserved for client/server communication

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    info + + +Object + + + +

    the dictionary

    + + + + + + + + + + + + + + + + + + + + + + + + +

    (static) average(input) → {number}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Calculate the average of the elements in the input array.

    +

    If 'input' is not an array, or if it is an empty array, then we return 0.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1]

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the average of the elements in the array

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) count(input, value)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Count the number of elements in the input array that match the given value.

    +

    Note: count is able to handle NaN, null, as well as any value convertible to a JSON string.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    the input array

    value + + +Number +| + +string +| + +object +| + +null + + + +

    the matching value

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the number of matching elements

    +
    + + + + + + + + + + + + +

    (static) detectBrowser() → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Detect the user's browser.

    +

    Note: since user agent is easily spoofed, we use a more sophisticated approach, as described here: +https://stackoverflow.com/a/9851769

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the detected browser, one of 'Opera', 'Firefox', 'Safari', +'IE', 'Edge', 'EdgeChromium', 'Chrome', 'unknown'

    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) extensionFromMimeType(mimeType) → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Return the file extension corresponding to an audio mime type. +If the provided mimeType is not a string (e.g. null, undefined, an array) +or unknown, then '.dat' is returned, instead of throwing an exception.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    mimeType + + +string + + + +

    the MIME type, e.g. 'audio/webm;codecs=opus'

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the corresponding file extension, e.g. '.webm'

    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) flattenArray(array) → {Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Recursively flatten an array of arrays.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    array + + +Array.<Object> + + + +

    the input array of arrays

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the flatten array

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + + + + + + + +

    (static) getDownloadSpeed(psychoJS, nbDownloadsopt) → {number}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get an estimate of the download speed, by repeatedly downloading an image file from a distant +server.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    psychoJS + + +PsychoJS + + + + + + + + + + + +

    the instance of PsychoJS

    nbDownloads + + +number + + + + + + <optional>
    + + + + + +
    + + 1 + +

    the number of image downloads over which to average +the download speed

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the download speed, in megabits per second

    +
    + + + +
    +
    + Type +
    +
    + +number + + +
    +
    + + + + + + + + + + +

    (static) getErrorStack() → {string}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the error stack of the calling, exception-throwing function.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the error stack as a string

    +
    + + + +
    +
    + Type +
    +
    + +string + + +
    +
    + + + + + + + + + + +

    (static) getPositionFromObject(object, units) → {Array.<number>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the position of the object, in pixel units

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    object + + +Object + + + +

    the input object

    units + + +string + + + +

    the units

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the position of the object, in pixel units

    +
    + + + +
    +
    + Type +
    +
    + +Array.<number> + + +
    +
    + + + + + + + + + + +

    (static) getRequestError(jqXHR, textStatus, errorThrown)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the most informative error from the server response from a jquery server request.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    jqXHR + +
    textStatus + +
    errorThrown + +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    (static) getUrlParameters() → {URLSearchParams}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the URL parameters.

    +
    + + + + + + + + + +
    Example
    + +
    const urlParameters = util.getUrlParameters();
    +for (const [key, value] of urlParameters)
    +  console.log(key + ' = ' + value);
    + + + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    the iterable URLSearchParams

    +
    + + + +
    +
    + Type +
    +
    + +URLSearchParams + + +
    +
    + + + + + + + + + + +

    (static) index(input, value)

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Get the index in the input array of the first element that matches the given value.

    +

    Note: index is able to handle NaN, null, as well as any value convertible to a JSON string.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    input + + +array + + + +

    the input array

    value + + +Number +| + +string +| + +object +| + +null + + + +

    the matching value

    + + + + + + + + + + + + + + +
    Throws:
    + + + +
    + +

    if the input array does not contain any matching element

    + +
    + + + + + +
    Returns:
    + + +
    +

    the index of the first element that matches the value

    +
    + + + + + + + + + + + + +

    (static) isEmpty(x) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Test if x is an 'empty' value.

    +
    + + + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    x + + +Object + + + +

    the value to test

    + + + + + + + + + + + + + + + + +
    Returns:
    + + +
    +

    true if x is one of the following: undefined, [], [undefined]

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    + + + + + + + + + + +

    (static) isInt(obj) → {boolean}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Test whether an object is either an integer or the string representation of an integer.

    +

    This is adapted from: https://stackoverflow.com/a/14794066

    +
    @@ -1498,45 +9733,55 @@

    (static) +
    Parameters:
    - + + + + + + - + - + - + - + + + + - + + + + - + - + - -
    Source:
    -
    - + - + + + +
    NameTypeDescription
    obj + + +Object - - + +

    the input object

    - -

    @@ -1556,18 +9801,18 @@
    Returns:
    - the iterable URLSearchParams +

    whether or not the object is an integer or the string representation of an integer

    -
    +
    Type
    -URLSearchParams +boolean
    @@ -1577,32 +9822,64 @@
    Returns:
    + + -
    Example
    -
    const urlParameters = util.getUrlParameters();
    -for (const [key, value] of urlParameters)
    -  console.log(key + ' = ' + value);
    +

    (static) isNumeric(input) → {boolean}

    + - - + + + +
    + + +
    Source:
    +
    + -

    (static) index(input, value)

    + + + + + + + -
    - Get the index in the input array of the first element that matches the given value. + -

    Note: index is able to handle NaN, null, as well as any value convertible to a JSON string.

    + + + + + + + + + + + +
    + + + + + +
    +

    Check whether a value looks like a number

    @@ -1613,6 +9890,8 @@

    (static) index< + +

    Parameters:
    @@ -1644,7 +9923,7 @@
    Parameters:
    -array +* @@ -1654,54 +9933,72 @@
    Parameters:
    - the input array +

    Some value

    + + - - - value - - - - -Number -| -string -| -object -| -null - - - - - the matching value - + + + + + + +
    Returns:
    + + +
    +

    Whether or not the value can be converted into a number

    +
    + + + +
    +
    + Type +
    +
    + +boolean + + +
    +
    - - + + + + + +

    (static) IsPointInsidePolygon(point, vertices) → {boolean}

    + + +
    - +
    Source:
    +
    @@ -1725,10 +10022,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1742,6 +10036,10 @@
    Parameters:
    +
    +

    Check whether a point lies within a polygon

    +

    We are using the algorithm described here: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

    +
    @@ -1750,51 +10048,82 @@
    Parameters:
    -
    Throws:
    - - -
    - - if the input array does not contain any matching element - -
    +
    Parameters:
    + + + + + + -
    Returns:
    + -
    - the index of the first element that matches the value -
    + + + + + + + + + + + + - - + + -

    (static) isEmpty(x) → {boolean}

    - + + + + + + + + + + + + + + + +
    NameTypeDescription
    point + + +Array.<number> + +

    the point

    vertices + + +Object + + + +

    the vertices defining the polygon

    -
    - Test if x is an 'empty' value. -
    @@ -1804,55 +10133,43 @@

    (static) isEm -

    Parameters:
    - - - - - - - - - + +
    Returns:
    +
    +

    whether or not the point lies within the polygon

    +
    - - - - - - - - - +
    +
    + Type +
    +
    + +boolean -
    + - - - - + + - -
    NameTypeDescription
    x - - -Object + + - - the value to test
    +

    (static) makeUuid() → {string}

    + @@ -1860,7 +10177,10 @@
    Parameters:
    - +
    Source:
    +
    @@ -1884,10 +10204,7 @@
    Parameters:
    -
    Source:
    -
    + @@ -1901,6 +10218,25 @@
    Parameters:
    +
    +

    Get a Universally Unique Identifier (RFC4122 version 4)

    +

    See details here: https://www.ietf.org/rfc/rfc4122.txt

    +
    + + + + + + + + + + + + + + + @@ -1915,18 +10251,18 @@
    Returns:
    - true if x is one of the following: undefined, [], [undefined] +

    the uuid

    -
    +
    Type
    -boolean +string
    @@ -1936,24 +10272,64 @@
    Returns:
    - - +

    (static) offerDataForDownload(filename, data, type)

    + -

    (static) isInt(obj) → {boolean}

    + + + + +
    + + +
    Source:
    +
    + + -
    - Test whether an object is either an integer or the string representation of an integer. -

    This is adapted from: https://stackoverflow.com/a/14794066

    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Offer data as download in the browser.

    @@ -1964,6 +10340,8 @@

    (static) isInt< + +

    Parameters:
    @@ -1989,13 +10367,13 @@
    Parameters:
    - obj + filename -Object +string @@ -2005,57 +10383,64 @@
    Parameters:
    - the input object +

    the name of the file to be downloaded

    - - + + + data + + + + +* + + + -
    + - +

    the data

    + - + + + type + - + + + +string - - + + - + - + - +

    the MIME type of the data, e.g. 'text/csv' or 'application/json'

    + + + - - - -
    Source:
    -
    - - - - -
    @@ -2069,51 +10454,65 @@
    Parameters:
    + + + -
    Returns:
    +

    (static) pad(n, width) → {string}

    - -
    - whether or not the object is an integer or the string representation of an integer -
    + -
    -
    - Type -
    -
    - -boolean +
    -
    -
    + +
    Source:
    +
    + + + + + - - + -

    (static) IsPointInsidePolygon(point, vertices) → {boolean}

    + + + -
    - Check whether a point lies within a polygon -

    We are using the algorithm described here: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html

    + + + + + +
    + + + + + +
    +

    Pad the given floating-point number with however many 0 needed at the start such that +the padded integer part of the number is of the given width.

    @@ -2124,6 +10523,8 @@

    (static + +
    Parameters:
    @@ -2149,96 +10550,42 @@
    Parameters:
    - point + n - -Array.<number> - - - - the point +

    the input floating-point number

    - vertices + width - -Object - - - - - - - - - - the vertices defining the polygon - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - + - + - + + +

    the desired width

    + -
    + + + @@ -2258,18 +10605,20 @@
    Returns:
    - whether or not the point lies within the polygon +
      +
    • the padded number, whose integer part has the given width
    • +
    -
    +
    Type
    -boolean +string
    @@ -2279,42 +10628,25 @@
    Returns:
    - - - -

    (static) makeUuid() → {string}

    - +

    (static) promiseToTupple(promise) → {Array.<Object>}

    -
    - Get a Universally Unique Identifier (RFC4122 version 4) -

    See details here: https://www.ietf.org/rfc/rfc4122.txt

    -
    - - - - - - - - - - - -
    - +
    Source:
    +
    @@ -2338,10 +10670,7 @@

    (static) mak -
    Source:
    -
    + @@ -2355,60 +10684,12 @@

    (static) mak - - - - - - - - - - -

    Returns:
    - - -
    - the uuid +
    +

    Convert the resulting value of a promise into a tupple.

    -
    -
    - Type -
    -
    - -string - - -
    -
    - - - - - - - - - - - - - -

    (static) offerDataForDownload(filename, data, type)

    - - - - - - -
    - Offer data as download in the browser. -
    - @@ -2442,13 +10723,13 @@
    Parameters:
    - filename + promise -string +Promise @@ -2458,94 +10739,72 @@
    Parameters:
    - the name of the file to be downloaded +

    the promise

    + + - - - data - - - - - -* - - - - - the data - - - - - type - - - - -string - - - - - the MIME type of the data, e.g. 'text/csv' or 'application/json' - - - - +
    Returns:
    + +
    +

    the resulting value in the format [error, return data] +where error is null if there was no error

    +
    +
    +
    + Type +
    +
    + +Array.<Object> -
    - +
    +
    - - - + + - +

    (static) randchoice(array, randomNumberGeneratoropt) → {Array.<Object>}

    - - - - +
    Source:
    @@ -2554,42 +10813,38 @@
    Parameters:
    -
    - - - - - - - - - - - - + + + + + + + - - + -

    (static) promiseToTupple(promise) → {Array.<Object>}

    + +
    -
    - Convert the resulting value of a promise into a tupple. + + +
    +

    Pick a random value from an array, uses util.shuffle to shuffle the array and returns the last value.

    @@ -2600,6 +10855,8 @@

    (static) Parameters:

    @@ -2613,6 +10870,8 @@
    Parameters:
    Type + Attributes + @@ -2625,131 +10884,168 @@
    Parameters:
    - promise + array -Promise +Array.<Object> + + + + + + + + - the promise +

    the input 1-D array

    - - + + + randomNumberGenerator + + + + +function + + + + + + <optional>
    + -
    + - + + + - + - +

    A function used to generated random numbers in the interal [0, 1). Defaults to Math.random

    + + + - - - - - - - - - -
    Source:
    -
    - - - - -
    +
    Returns:
    + + +
    +

    a chosen value from the array

    +
    + + + +
    +
    + Type +
    +
    + +Array.<Object> + + +
    +
    + + + + +

    (static) randint(minopt, max) → {number}

    + -
    Returns:
    +
    - -
    - the resulting value in the format [error, return data] -where error is null if there was no error -
    + +
    Source:
    +
    + + + -
    -
    - Type -
    -
    - -Array.<Object> + + -
    -
    + + + + + - - + -

    (static) randint(minopt, max) → {number}

    + +
    + -
    - Generates random integers a-la NumPy's in the "half-open" interval [min, max). In other words, from min inclusive to max exclusive. When max is undefined, as is the case by default, results are chosen from [0, min). An error is thrown if max is less than min. + +
    +

    Generates random integers a-la NumPy's in the "half-open" interval [min, max). In other words, from min inclusive to max exclusive. When max is undefined, as is the case by default, results are chosen from [0, min). An error is thrown if max is less than min.

    @@ -2760,6 +11056,8 @@

    (static) rand + +

    Parameters:
    @@ -2816,12 +11114,12 @@
    Parameters:
    - 0 + 0 - lowest integer to be drawn, or highest plus one if max is undefined (default) +

    lowest integer to be drawn, or highest plus one if max is undefined (default)

    @@ -2856,7 +11154,7 @@
    Parameters:
    - one above the largest integer to be drawn +

    one above the largest integer to be drawn

    @@ -2868,105 +11166,100 @@
    Parameters:
    -
    - - - - - - - - - - +
    Returns:
    - + +
    +

    a random integer in the requested range (signed)

    +
    - - -
    Source:
    -
    - - +
    +
    + Type +
    +
    + +number - - +
    + + + + +

    (static) range(startopt, stop, stepopt) → {Array.<Number>}

    + +
    + +
    Source:
    +
    + + + -
    Returns:
    - - -
    - a random integer in the requested range (signed) -
    - - - -
    -
    - Type -
    -
    - -number + + -
    -
    + + + + + - - + -

    (static) range(startopt, stop, stepopt) → {Array.<Number>}

    + +
    -
    - Create a sequence of integers. -The sequence is such that the integer at index i is: start + step * i, with i >= 0 and start + step * i < stop +
    +

    Create a sequence of integers.

    +

    The sequence is such that the integer at index i is: start + step * i, with i >= 0 and start + step * i < stop

    Note: this is a JavaScript implement of the Python range function, which explains the unusual management of arguments.

    @@ -2978,6 +11271,8 @@

    (static) range< + +

    Parameters:
    @@ -3034,12 +11329,12 @@
    Parameters:
    - 0 + 0 - the value of start +

    the value of start

    @@ -3074,7 +11369,7 @@
    Parameters:
    - the value of stop +

    the value of stop

    @@ -3108,12 +11403,12 @@
    Parameters:
    - 1 + 1 - the value of step +

    the value of step

    @@ -3125,104 +11420,107 @@
    Parameters:
    -
    - - - - - - - - - - +
    Returns:
    - + +
    +

    the range as an array of numbers

    +
    - - -
    Source:
    -
    - - +
    +
    + Type +
    +
    + +Array.<Number> - - +
    + + + + +

    (static) round(input, places) → {number}

    + +
    + +
    Source:
    +
    + + + -
    Returns:
    - - -
    - the range as an array of numbers -
    - - + -
    -
    - Type -
    -
    - -Array.<Number> + + -
    -
    + + + + + - - + -

    (static) round(input, places) → {number}

    +
    See:
    +
    + +
    +
    -
    - Round to a certain number of decimal places. -This is the Crib Sheet provided solution, but please note that as of 2020 the most popular SO answer is different. + +
    +

    Round to a certain number of decimal places.

    +

    This is the Crib Sheet provided solution, but please note that as of 2020 the most popular SO answer is different.

    @@ -3233,6 +11531,8 @@

    (static) round< + +

    Parameters:
    @@ -3274,7 +11574,7 @@
    Parameters:
    - the number to be rounded +

    the number to be rounded

    @@ -3297,7 +11597,7 @@
    Parameters:
    - the max number of decimals desired +

    the max number of decimals desired

    @@ -3309,58 +11609,7 @@
    Parameters:
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Source:
    -
    - - - - - -
    See:
    -
    - -
    - - - -
    - - - - - - + @@ -3374,12 +11623,12 @@
    Returns:
    - input rounded to the specified number of decimal places at most +

    input rounded to the specified number of decimal places at most

    -
    +
    Type
    @@ -3395,24 +11644,64 @@
    Returns:
    - - -

    (static) selectFromArray(array, selection) → {Object|Array.<Object>}

    + + + + + + +
    + + +
    Source:
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    -
    - Select values from an array. + + +
    +

    Select values from an array.

    'selection' can be a single integer, an array of indices, or a string to be parsed, e.g.:

    @@ -565,19 +546,23 @@

    Source: sound/Microphone.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/sound_Sound.js.html b/docs/sound_Sound.js.html index c0de238d..a50d0aea 100644 --- a/docs/sound_Sound.js.html +++ b/docs/sound_Sound.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: sound/Sound.js - - - + sound/Sound.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: sound/Sound.js

    + + + + +
    + +

    sound/Sound.js

    + @@ -31,17 +55,16 @@

    Source: sound/Sound.js

    * Sound stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {PsychoJS} from '../core/PsychoJS'; -import {PsychObject} from '../util/PsychObject'; -import {TonePlayer} from './TonePlayer'; -import {TrackPlayer} from './TrackPlayer'; -import {AudioClipPlayer} from './AudioClipPlayer'; - +import { PsychoJS } from "../core/PsychoJS.js"; +import { PsychObject } from "../util/PsychObject.js"; +import { AudioClipPlayer } from "./AudioClipPlayer.js"; +import { TonePlayer } from "./TonePlayer.js"; +import { TrackPlayer } from "./TrackPlayer.js"; /** * <p>This class handles sound playing (tones and tracks)</p> @@ -64,53 +87,54 @@

    Source: sound/Sound.js

    * track.setVolume(1.0); * track.play(2); * - * @class * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {number|string} [options.value= 'C'] - the sound value (see above for a full description) - * @param {number} [options.octave= 4] - the octave corresponding to the tone (if applicable) - * @param {number} [options.secs= 0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. - * @param {number} [options.startTime= 0] - start of playback for tracks (in seconds) - * @param {number} [options.stopTime= -1] - end of playback for tracks (in seconds) - * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo - * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped. - * @param {boolean} [options.autoLog= true] whether or not to log */ export class Sound extends PsychObject { + /** + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {number|string} [options.value= 'C'] - the sound value (see above for a full description) + * @param {number} [options.octave= 4] - the octave corresponding to the tone (if applicable) + * @param {number} [options.secs= 0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. + * @param {number} [options.startTime= 0] - start of playback for tracks (in seconds) + * @param {number} [options.stopTime= -1] - end of playback for tracks (in seconds) + * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo + * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped. + * @param {boolean} [options.autoLog= true] whether or not to log + */ constructor({ - name, - win, - value = 'C', - octave = 4, - secs = 0.5, - startTime = 0, - stopTime = -1, - stereo = true, - volume = 1.0, - loops = 0, - //hamming = true, - autoLog = true - } = {}) + name, + win, + value = "C", + octave = 4, + secs = 0.5, + startTime = 0, + stopTime = -1, + stereo = true, + volume = 1.0, + loops = 0, + // hamming = true, + autoLog = true, + } = {}) { super(win._psychoJS, name); // the SoundPlayer, e.g. TonePlayer: this._player = undefined; - this._addAttribute('win', win); - this._addAttribute('value', value); - this._addAttribute('octave', octave); - this._addAttribute('secs', secs); - this._addAttribute('startTime', startTime); - this._addAttribute('stopTime', stopTime); - this._addAttribute('stereo', stereo); - this._addAttribute('volume', volume); - this._addAttribute('loops', loops); - this._addAttribute('autoLog', autoLog); + this._addAttribute("win", win); + this._addAttribute("value", value); + this._addAttribute("octave", octave); + this._addAttribute("secs", secs); + this._addAttribute("startTime", startTime); + this._addAttribute("stopTime", stopTime); + this._addAttribute("stereo", stereo); + this._addAttribute("volume", volume); + this._addAttribute("loops", loops); + this._addAttribute("autoLog", autoLog); // identify an appropriate player: this._getPlayer(); @@ -118,14 +142,12 @@

    Source: sound/Sound.js

    this.status = PsychoJS.Status.NOT_STARTED; } - /** * Start playing the sound. * * <p> Note: Sounds are played independently from the stimuli of the experiments, i.e. the experiment will not stop until the sound is finished playing. * Repeat calls to play may results in the sounds being played on top of each other.</p> * - * @public * @param {number} loops how many times to repeat the sound after it plays once. If loops == -1, the sound will repeat indefinitely until stopped. * @param {boolean} [log= true] whether or not to log */ @@ -135,27 +157,23 @@

    Source: sound/Sound.js

    this._player.play(loops); } - /** * Stop playing the sound immediately. * - * @public * @param {Object} options * @param {boolean} [options.log= true] - whether or not to log */ stop({ - log = true - } = {}) + log = true, + } = {}) { this._player.stop(); this.status = PsychoJS.Status.STOPPED; } - /** * Get the duration of the sound, in seconds. * - * @public * @return {number} the duration of the sound, in seconds */ getDuration() @@ -163,30 +181,26 @@

    Source: sound/Sound.js

    return this._player.getDuration(); } - /** * Set the playing volume of the sound. * - * @public * @param {number} volume - the volume (values should be between 0 and 1) * @param {boolean} [mute= false] - whether or not to mute the sound * @param {boolean} [log= true] - whether of not to log */ setVolume(volume, mute = false, log = true) { - this._setAttribute('volume', volume, log); + this._setAttribute("volume", volume, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setVolume(volume, mute); } } - /** * Set the sound value on demand past initialisation. * - * @public * @param {object} sound - a sound instance to replace the current one * @param {boolean} [log= true] - whether or not to log */ @@ -194,9 +208,9 @@

    Source: sound/Sound.js

    { if (sound instanceof Sound) { - this._setAttribute('value', sound.value, log); + this._setAttribute("value", sound.value, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player = this._player.constructor.accept(this); } @@ -206,49 +220,44 @@

    Source: sound/Sound.js

    } throw { - origin: 'Sound.setSound', - context: 'when replacing the current sound', - error: 'invalid input, need an instance of the Sound class.' + origin: "Sound.setSound", + context: "when replacing the current sound", + error: "invalid input, need an instance of the Sound class.", }; } - /** * Set the number of loops. * - * @public * @param {number} [loops=0] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. * @param {boolean} [log=true] - whether of not to log */ setLoops(loops = 0, log = true) { - this._setAttribute('loops', loops, log); + this._setAttribute("loops", loops, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setLoops(loops); } } - /** * Set the duration (in seconds) * - * @public * @param {number} [secs=0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. * @param {boolean} [log=true] - whether or not to log */ setSecs(secs = 0.5, log = true) { - this._setAttribute('secs', secs, log); + this._setAttribute("secs", secs, log); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { this._player.setDuration(secs); } } - /** * Identify the appropriate player for the sound. * @@ -259,28 +268,26 @@

    Source: sound/Sound.js

    _getPlayer() { const acceptFns = [ - sound => TonePlayer.accept(sound), - sound => TrackPlayer.accept(sound), - sound => AudioClipPlayer.accept(sound) + (sound) => TonePlayer.accept(sound), + (sound) => TrackPlayer.accept(sound), + (sound) => AudioClipPlayer.accept(sound), ]; for (const acceptFn of acceptFns) { this._player = acceptFn(this); - if (typeof this._player !== 'undefined') + if (typeof this._player !== "undefined") { return this._player; } } throw { - origin: 'SoundPlayer._getPlayer', - context: 'when finding a player for the sound', - error: 'could not find an appropriate player.' + origin: "SoundPlayer._getPlayer", + context: "when finding a player for the sound", + error: "could not find an appropriate player.", }; } - - }
    @@ -289,19 +296,23 @@

    Source: sound/Sound.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/sound_SoundPlayer.js.html b/docs/sound_SoundPlayer.js.html index 0ad77698..c7137e08 100644 --- a/docs/sound_SoundPlayer.js.html +++ b/docs/sound_SoundPlayer.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: sound/SoundPlayer.js - - - + sound/SoundPlayer.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: sound/SoundPlayer.js

    + + + + +
    + +

    sound/SoundPlayer.js

    + @@ -30,37 +54,33 @@

    Source: sound/SoundPlayer.js

    * Sound player interface * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {PsychObject} from '../util/PsychObject'; - +import { PsychObject } from "../util/PsychObject.js"; /** * <p>SoundPlayer is an interface for the sound players, who are responsible for actually playing the sounds, i.e. the tracks or the tones.</p> * - * @name module:sound.SoundPlayer * @interface * @extends PsychObject - * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance */ export class SoundPlayer extends PsychObject { + /** + * @memberOf module:sound + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { super(psychoJS); } - /** * Determine whether this player can play the given sound. * - * @name module:sound.SoundPlayer.accept - * @function - * @static - * @public * @abstract * @param {module:sound.Sound} - the sound * @return {Object|undefined} an instance of the SoundPlayer that can play the sound, or undefined if none could be found @@ -68,111 +88,87 @@

    Source: sound/SoundPlayer.js

    static accept(sound) { throw { - origin: 'SoundPlayer.accept', - context: 'when evaluating whether this player can play a given sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.accept", + context: "when evaluating whether this player can play a given sound", + error: "this method is abstract and should not be called.", }; } - /** * Start playing the sound. * - * @name module:sound.SoundPlayer#play - * @function - * @public * @abstract * @param {number} [loops] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ play(loops) { throw { - origin: 'SoundPlayer.play', - context: 'when starting the playback of a sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.play", + context: "when starting the playback of a sound", + error: "this method is abstract and should not be called.", }; } - /** * Stop playing the sound immediately. * - * @name module:sound.SoundPlayer#stop - * @function - * @public * @abstract */ stop() { throw { - origin: 'SoundPlayer.stop', - context: 'when stopping the playback of a sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.stop", + context: "when stopping the playback of a sound", + error: "this method is abstract and should not be called.", }; } - /** * Get the duration of the sound, in seconds. * - * @name module:sound.SoundPlayer#getDuration - * @function - * @public * @abstract */ getDuration() { throw { - origin: 'SoundPlayer.getDuration', - context: 'when getting the duration of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.getDuration", + context: "when getting the duration of the sound", + error: "this method is abstract and should not be called.", }; } - /** * Set the duration of the sound, in seconds. * - * @name module:sound.SoundPlayer#setDuration - * @function - * @public * @abstract */ setDuration(duration_s) { throw { - origin: 'SoundPlayer.setDuration', - context: 'when setting the duration of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setDuration", + context: "when setting the duration of the sound", + error: "this method is abstract and should not be called.", }; } - /** * Set the number of loops. * - * @name module:sound.SoundPlayer#setLoops - * @function - * @public * @abstract * @param {number} loops - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ setLoops(loops) { throw { - origin: 'SoundPlayer.setLoops', - context: 'when setting the number of loops', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setLoops", + context: "when setting the number of loops", + error: "this method is abstract and should not be called.", }; } - /** * Set the volume of the tone. * - * @name module:sound.SoundPlayer#setVolume - * @function - * @public * @abstract * @param {Integer} volume - the volume of the tone * @param {boolean} [mute= false] - whether or not to mute the tone @@ -180,12 +176,11 @@

    Source: sound/SoundPlayer.js

    setVolume(volume, mute = false) { throw { - origin: 'SoundPlayer.setVolume', - context: 'when setting the volume of the sound', - error: 'this method is abstract and should not be called.' + origin: "SoundPlayer.setVolume", + context: "when setting the volume of the sound", + error: "this method is abstract and should not be called.", }; } - } @@ -194,19 +189,23 @@

    Source: sound/SoundPlayer.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/sound_SpeechRecognition.js.html b/docs/sound_SpeechRecognition.js.html new file mode 100644 index 00000000..a5f4d741 --- /dev/null +++ b/docs/sound_SpeechRecognition.js.html @@ -0,0 +1,460 @@ + + + + + + sound/SpeechRecognition.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    sound/SpeechRecognition.js

    + + + + + + + +
    +
    +
    /**
    + * Manager handling the live transcription of speech into text.
    + *
    + * @author Alain Pitiot
    + * @version 2022.2.3
    + * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @license Distributed under the terms of the MIT License
    + */
    +
    +import {Clock} from "../util/Clock";
    +import {PsychObject} from "../util/PsychObject";
    +import {PsychoJS} from "../core/PsychoJS";
    +
    +
    +/**
    + * Transcript.
    + */
    +export class Transcript
    +{
    +	/**
    +	 * Object holding a transcription result.
    +	 *
    +	 * @param {SpeechRecognition} transcriber - the transcriber
    +	 * @param {string} text - the transcript
    +	 * @param {number} confidence - confidence in the transcript
    +	 */
    +	constructor(transcriber, text = '', confidence = 0.0)
    +	{
    +		// recognised text:
    +		this.text = text;
    +
    +		// confidence in the recognition:
    +		this.confidence = confidence;
    +
    +		// time the speech started, relative to the Transcriber clock:
    +		this.speechStart = transcriber._speechStart;
    +
    +		// time the speech ended, relative to the Transcriber clock:
    +		this.speechEnd = transcriber._speechEnd;
    +
    +		// time a recognition result was produced, relative to the Transcriber clock:
    +		this.time = transcriber._recognitionTime;
    +	}
    +}
    +
    +
    +/**
    + * <p>This manager handles the live transcription of speech into text.</p>
    + *
    + * @extends PsychObject
    + * @todo deal with alternatives, interim results, and recognition errors
    + */
    +export class SpeechRecognition extends PsychObject
    +{
    +	/**
    +	 * <p>This manager handles the live transcription of speech into text.</p>
    +	 *
    +	 * @memberOf module:sound
    +	 * @param {Object} options
    +	 * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
    +	 * @param {String} options.name - the name used when logging messages
    +	 * @param {number} [options.bufferSize= 10000] - the maximum size of the circular transcript buffer
    +	 * @param {String[]} [options.continuous= true] - whether to continuously recognise
    +	 * @param {String[]} [options.lang= 'en-US'] - the spoken language
    +	 * @param {String[]} [options.interimResults= false] - whether to make interim results available
    +	 * @param {String[]} [options.maxAlternatives= 1] - the maximum number of recognition alternatives
    +	 * @param {String[]} [options.tokens= [] ] - the tokens to be recognised. This is experimental technology, not available in all browser.
    +	 * @param {Clock} [options.clock= undefined] - an optional clock
    +	 * @param {boolean} [options.autoLog= false] - whether to log
    +	 *
    +	 * @todo deal with alternatives, interim results, and recognition errors
    +	 */
    +	constructor({psychoJS, name, bufferSize, continuous, lang, interimResults, maxAlternatives, tokens, clock, autoLog} = {})
    +	{
    +		super(psychoJS);
    +
    +		this._addAttribute('name', name, 'speech recognition');
    +		this._addAttribute('bufferSize', bufferSize, 10000);
    +		this._addAttribute('continuous', continuous, true, this._onChange);
    +		this._addAttribute('lang', lang, 'en-US', this._onChange);
    +		this._addAttribute('interimResults', interimResults, false, this._onChange);
    +		this._addAttribute('maxAlternatives', maxAlternatives, 1, this._onChange);
    +		this._addAttribute('tokens', tokens, [], this._onChange);
    +		this._addAttribute('clock', clock, new Clock());
    +		this._addAttribute('autoLog', false, autoLog);
    +		this._addAttribute('status', PsychoJS.Status.NOT_STARTED);
    +
    +		this._prepareRecognition();
    +
    +		if (this._autoLog)
    +		{
    +			this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`);
    +		}
    +	}
    +
    +
    +	/**
    +	 * Start the speech recognition process.
    +	 *
    +	 * @return {Promise} promise fulfilled when the process actually starts
    +	 */
    +	start()
    +	{
    +		if (this._status !== PsychoJS.Status.STARTED)
    +		{
    +			this._psychoJS.logger.debug('request to start the speech recognition process');
    +
    +			try
    +			{
    +				if (!this._recognition)
    +				{
    +					throw 'the speech recognition has not been initialised yet, possibly because the participant has not given the authorisation to record audio';
    +				}
    +
    +				this._recognition.start();
    +
    +				// return a promise, which will be satisfied when the process actually starts,
    +				// which is also when the reset of the clock and the change of status takes place
    +				const self = this;
    +				return new Promise((resolve, reject) =>
    +				{
    +					self._startCallback = resolve;
    +					self._errorCallback = reject;
    +				});
    +			}
    +			catch (error)
    +			{
    +				// TODO Strangely, start sometimes fails with the message that the recognition has already started. It is most probably a bug in the implementation of the Web Speech API. We need to catch this particular error and no throw on this occasion
    +
    +				this._psychoJS.logger.error('unable to start the speech to text transcription: ' + JSON.stringify(error));
    +				this._status = PsychoJS.Status.ERROR;
    +
    +				throw {
    +					origin: 'Transcriber.start',
    +					context: 'when starting the speech to text transcription with transcriber: ' + this._name,
    +					error
    +				};
    +			}
    +
    +		}
    +
    +	}
    +
    +
    +	/**
    +	 * Stop the speech recognition process.
    +	 *
    +	 * @return {Promise} promise fulfilled when the process actually stops
    +	 */
    +	stop()
    +	{
    +		if (this._status === PsychoJS.Status.STARTED)
    +		{
    +			this._psychoJS.logger.debug('request to stop the speech recognition process');
    +
    +			this._recognition.stop();
    +
    +			// return a promise, which will be satisfied when the process actually stops:
    +			const self = this;
    +			return new Promise((resolve, reject) =>
    +			{
    +				self._stopCallback = resolve;
    +				self._errorCallback = reject;
    +			});
    +		}
    +	}
    +
    +
    +	/**
    +	 * Get the list of transcripts still in the buffer, i.e. those that have not been
    +	 * previously cleared by calls to getTranscripts with clear = true.
    +	 *
    +	 * @param {Object} options
    +	 * @param {string[]} [options.transcriptList= []]] - the list of transcripts texts to consider. If transcriptList is empty, we consider all transcripts.
    +	 * @param {boolean} [options.clear= false] - whether or not to keep in the buffer the transcripts for a subsequent call to getTranscripts. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList
    +	 * @return {Transcript[]} the list of transcripts still in the buffer
    +	 */
    +	getTranscripts({
    +									 transcriptList = [],
    +									 clear = true
    +								 } = {})
    +	{
    +		// if nothing in the buffer, return immediately:
    +		if (this._bufferLength === 0)
    +		{
    +			return [];
    +		}
    +
    +		// iterate over the buffer, from start to end, and discard the null transcripts (i.e. those
    +		// previously cleared):
    +		const filteredTranscripts = [];
    +		const bufferWrap = (this._bufferLength === this._bufferSize);
    +		let i = bufferWrap ? this._bufferIndex : -1;
    +		do
    +		{
    +			i = (i + 1) % this._bufferSize;
    +
    +			const transcript = this._circularBuffer[i];
    +			if (transcript)
    +			{
    +				// if the transcriptList is empty of the transcript text is in the transcriptList:
    +				if (transcriptList.length === 0 || transcriptList.includes(transcript.text))
    +				{
    +					filteredTranscripts.push(transcript);
    +
    +					if (clear)
    +					{
    +						this._circularBuffer[i] = null;
    +					}
    +				}
    +			}
    +		} while (i !== this._bufferIndex);
    +
    +		return filteredTranscripts;
    +	}
    +
    +
    +	/**
    +	 * Clear all transcripts and resets the circular buffers.
    +	 */
    +	clearTranscripts()
    +	{
    +		// circular buffer of transcripts:
    +		this._circularBuffer = new Array(this._bufferSize);
    +		this._bufferLength = 0;
    +		this._bufferIndex = -1;
    +	}
    +
    +
    +	/**
    +	 * Callback for changes to the recognition settings.
    +	 *
    +	 * <p>Changes to the recognition settings require the speech recognition process
    +	 * to be stopped and be re-started.</p>
    +	 *
    +	 * @protected
    +	 */
    +	_onChange()
    +	{
    +		if (this._status === PsychoJS.Status.STARTED)
    +		{
    +			this.stop();
    +		}
    +
    +		this._prepareRecognition();
    +
    +		this.start();
    +	}
    +
    +
    +	/**
    +	 * Prepare the speech recognition process.
    +	 *
    +	 * @protected
    +	 */
    +	_prepareRecognition()
    +	{
    +		// setup the circular buffer of transcripts:
    +		this.clearTranscripts();
    +
    +		// recognition settings:
    +		const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    +		this._recognition = new SpeechRecognition();
    +		this._recognition.continuous = this._continuous;
    +		this._recognition.lang = this._lang;
    +		this._recognition.interimResults = this._interimResults;
    +		this._recognition.maxAlternatives = this._maxAlternatives;
    +
    +		// grammar list with tokens added:
    +		if (Array.isArray(this._tokens) && this._tokens.length > 0)
    +		{
    +			const SpeechGrammarList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
    +
    +			// note: we accepts JSGF encoded strings, and relative weight indicator between 0.0 and 1.0
    +			// ref: https://www.w3.org/TR/jsgf/
    +			const name = "NULL";
    +			const grammar = `#JSGF V1.0; grammar ${name}; public <${name}> = ${this._tokens.join('|')};`
    +			const grammarList = new SpeechGrammarList();
    +			grammarList.addFromString(grammar, 1);
    +			this._recognition.grammars = grammarList;
    +		}
    +
    +		// setup the callbacks:
    +		const self = this;
    +
    +		// called when the start of a speech is detected:
    +		this._recognition.onspeechstart = (e) =>
    +		{
    +			this._currentSpeechStart = this._clock.getTime();
    +			self._psychoJS.logger.debug('speech started');
    +		}
    +
    +		// called when the end of a speech is detected:
    +		this._recognition.onspeechend = () =>
    +		{
    +			this._currentSpeechEnd = this._clock.getTime();
    +			// this._recognition.stop();
    +			self._psychoJS.logger.debug('speech ended');
    +		}
    +
    +		// called when the recognition actually started:
    +		this._recognition.onstart = () =>
    +		{
    +			this._clock.reset();
    +			this._status = PsychoJS.Status.STARTED;
    +			self._psychoJS.logger.debug('speech recognition started');
    +
    +			// resolve the SpeechRecognition.start promise, if need be:
    +			if (self._startCallback())
    +			{
    +				self._startCallback({
    +					time: self._psychoJS.monotonicClock.getTime()
    +				});
    +			}
    +		}
    +
    +		// called whenever stop() or abort() are called:
    +		this._recognition.onend = () =>
    +		{
    +			this._status = PsychoJS.Status.STOPPED;
    +			self._psychoJS.logger.debug('speech recognition ended');
    +
    +			// resolve the SpeechRecognition.stop promise, if need be:
    +			if (self._stopCallback)
    +			{
    +				self._stopCallback({
    +					time: self._psychoJS.monotonicClock.getTime()
    +				});
    +			}
    +		}
    +
    +		// called whenever a new result is available:
    +		this._recognition.onresult = (event) =>
    +		{
    +			this._recognitionTime = this._clock.getTime();
    +
    +			// do not process the results if the Recogniser is not STARTED:
    +			if (self._status !== PsychoJS.Status.STARTED)
    +			{
    +				return;
    +			}
    +
    +			// in continuous recognition mode, we need to get the result at resultIndex,
    +			// otherwise we pick the first result
    +			const resultIndex = (self._continuous) ? event.resultIndex : 0;
    +
    +			// TODO at the moment we consider only the first alternative:
    +			const alternativeIndex = 0;
    +
    +			const results = event.results;
    +			const text = results[resultIndex][alternativeIndex].transcript;
    +			const confidence = results[resultIndex][alternativeIndex].confidence;
    +
    +			// create a new transcript:
    +			const transcript = new Transcript(self, text, confidence);
    +
    +			// insert it in the circular transcript buffer:
    +			self._bufferIndex = (self._bufferIndex + 1) % self._bufferSize;
    +			self._bufferLength = Math.min(self._bufferLength + 1, self._bufferSize);
    +			self._circularBuffer[self._bufferIndex] = transcript;
    +
    +			self._psychoJS.logger.debug('speech recognition transcript: ', JSON.stringify(transcript));
    +		}
    +
    +		// called upon recognition errors:
    +		this._recognition.onerror = (event) =>
    +		{
    +			// lack of speech is not an error:
    +			if (event.error === 'no-speech')
    +			{
    +				return;
    +			}
    +
    +			self._psychoJS.logger.error('speech recognition error: ', JSON.stringify(event));
    +			self._status = PsychoJS.Status.ERROR;
    +		}
    +	}
    +
    +}
    +
    +
    +
    +
    +
    + + + + + + +
    + +
    + +
    + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
    + + + + + + + + + + + diff --git a/docs/sound_TonePlayer.js.html b/docs/sound_TonePlayer.js.html index b9c37194..6efc9117 100644 --- a/docs/sound_TonePlayer.js.html +++ b/docs/sound_TonePlayer.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: sound/TonePlayer.js - - - + sound/TonePlayer.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: sound/TonePlayer.js

    + + + + +
    + +

    sound/TonePlayer.js

    + @@ -30,48 +54,51 @@

    Source: sound/TonePlayer.js

    * Tone Player. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import * as Tone from 'tone'; -import {SoundPlayer} from './SoundPlayer'; - +import * as Tone from "tone"; +import { isNumeric } from "../util/Util.js"; +import { SoundPlayer } from "./SoundPlayer.js"; /** * <p>This class handles the playing of tones.</p> * - * @name module:sound.TonePlayer - * @class * @extends SoundPlayer - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {number} [options.duration_s= 0.5] - duration of the tone (in seconds). If duration_s == -1, the sound will play indefinitely. - * @param {string|number} [options.note= 'C4'] - note (if string) or frequency (if number) - * @param {number} [options.volume= 1.0] - volume of the tone (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped. */ export class TonePlayer extends SoundPlayer { + /** + * <p>This class handles the playing of tones.</p> + * + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {number} [options.duration_s= 0.5] - duration of the tone (in seconds). If duration_s == -1, the sound will play indefinitely. + * @param {string|number} [options.note= 'C4'] - note (if string) or frequency (if number) + * @param {number} [options.volume= 1.0] - volume of the tone (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped. + */ constructor({ - psychoJS, - note = 'C4', - duration_s = 0.5, - volume = 1.0, - loops = 0, - soundLibrary = TonePlayer.SoundLibrary.TONE_JS, - autoLog = true - } = {}) + psychoJS, + note = "C4", + duration_s = 0.5, + volume = 1.0, + loops = 0, + soundLibrary = TonePlayer.SoundLibrary.TONE_JS, + autoLog = true, + } = {}) { super(psychoJS); - this._addAttribute('note', note); - this._addAttribute('duration_s', duration_s); - this._addAttribute('volume', volume); - this._addAttribute('loops', loops); - this._addAttribute('soundLibrary', soundLibrary); - this._addAttribute('autoLog', autoLog); + this._addAttribute("note", note); + this._addAttribute("duration_s", duration_s); + this._addAttribute("volume", volume); + this._addAttribute("loops", loops); + this._addAttribute("soundLibrary", soundLibrary); + this._addAttribute("autoLog", autoLog); // initialise the sound library: this._initSoundLibrary(); @@ -85,56 +112,51 @@

    Source: sound/TonePlayer.js

    } } - /** * Determine whether this player can play the given sound. * * <p>Note: if TonePlayer accepts the sound but Tone.js is not available, e.g. if the browser is IE11, * we throw an exception.</p> * - * @name module:sound.TonePlayer.accept - * @function - * @static - * @public * @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 the sound's value is an integer, we interpret it as a frequency: - if ($.isNumeric(sound.value)) + if (isNumeric(sound.value)) { return new TonePlayer({ psychoJS: sound.psychoJS, note: sound.value, duration_s: sound.secs, volume: sound.volume, - loops: sound.loops + loops: sound.loops, }); } // if the sound's value is a string, we check whether it is a note: - if (typeof sound.value === 'string') + if (typeof sound.value === "string") { // mapping between the PsychoPY notes and the standard ones: let psychopyToToneMap = new Map(); - for (const note of ['A', 'B', 'C', 'D', 'E', 'F', 'G']) + for (const note of ["A", "B", "C", "D", "E", "F", "G"]) { psychopyToToneMap.set(note, note); - psychopyToToneMap.set(note + 'fl', note + 'b'); - psychopyToToneMap.set(note + 'sh', note + '#'); + psychopyToToneMap.set(note + "fl", note + "b"); + psychopyToToneMap.set(note + "sh", note + "#"); } // check whether the sound's value is a recognised note: const note = psychopyToToneMap.get(sound.value); - if (typeof note !== 'undefined') + if (typeof note !== "undefined") { return new TonePlayer({ psychoJS: sound.psychoJS, note: note + sound.octave, duration_s: sound.secs, volume: sound.volume, - loops: sound.loops + loops: sound.loops, }); } } @@ -143,13 +165,9 @@

    Source: sound/TonePlayer.js

    return undefined; } - /** * Get the duration of the sound. * - * @name module:sound.TonePlayer#getDuration - * @function - * @public * @return {number} the duration of the sound, in seconds */ getDuration() @@ -157,13 +175,9 @@

    Source: sound/TonePlayer.js

    return this.duration_s; } - /** * Set the duration of the tone. * - * @name module:sound.TonePlayer#setDuration - * @function - * @public * @param {number} duration_s - the duration of the tone (in seconds) If duration_s == -1, the sound will play indefinitely. */ setDuration(duration_s) @@ -171,13 +185,9 @@

    Source: sound/TonePlayer.js

    this.duration_s = duration_s; } - /** * Set the number of loops. * - * @name module:sound.TonePlayer#setLoops - * @function - * @public * @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. */ setLoops(loops) @@ -185,13 +195,9 @@

    Source: sound/TonePlayer.js

    this._loops = loops; } - /** * Set the volume of the tone. * - * @name module:sound.TonePlayer#setVolume - * @function - * @public * @param {Integer} volume - the volume of the tone * @param {boolean} [mute= false] - whether or not to mute the tone */ @@ -201,7 +207,7 @@

    Source: sound/TonePlayer.js

    if (this._soundLibrary === TonePlayer.SoundLibrary.TONE_JS) { - if (typeof this._volumeNode !== 'undefined') + if (typeof this._volumeNode !== "undefined") { this._volumeNode.mute = mute; this._volumeNode.volume.value = -60 + volume * 66; @@ -218,18 +224,14 @@

    Source: sound/TonePlayer.js

    } } - /** * Start playing the sound. * - * @name module:sound.TonePlayer#play - * @function - * @public * @param {boolean} [loops] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ play(loops) { - if (typeof loops !== 'undefined') + if (typeof loops !== "undefined") { this._loops = loops; } @@ -250,7 +252,7 @@

    Source: sound/TonePlayer.js

    playToneCallback = () => { self._webAudioOscillator = self._audioContext.createOscillator(); - self._webAudioOscillator.type = 'sine'; + self._webAudioOscillator.type = "sine"; self._webAudioOscillator.frequency.value = 440; self._webAudioOscillator.connect(self._audioContext.destination); const contextCurrentTime = self._audioContext.currentTime; @@ -264,7 +266,6 @@

    Source: sound/TonePlayer.js

    { playToneCallback(); } - // repeat forever: else if (this.loops === -1) { @@ -272,28 +273,23 @@

    Source: sound/TonePlayer.js

    playToneCallback, this.duration_s, Tone.now(), - Infinity + Infinity, ); } - else // repeat this._loops times: + else { this._toneId = Tone.Transport.scheduleRepeat( playToneCallback, this.duration_s, Tone.now(), - this.duration_s * (this._loops + 1) + this.duration_s * (this._loops + 1), ); } } - /** * Stop playing the sound immediately. - * - * @name module:sound.TonePlayer#stop - * @function - * @public */ stop() { @@ -315,38 +311,35 @@

    Source: sound/TonePlayer.js

    } } - /** * Initialise the sound library. * * <p>Note: if TonePlayer accepts the sound but Tone.js is not available, e.g. if the browser is IE11, * we throw an exception.</p> * - * @name module:sound.TonePlayer._initSoundLibrary - * @function * @protected */ _initSoundLibrary() { const response = { - origin: 'TonePlayer._initSoundLibrary', - context: 'when initialising the sound library' + origin: "TonePlayer._initSoundLibrary", + context: "when initialising the sound library", }; if (this._soundLibrary === TonePlayer.SoundLibrary.TONE_JS) { // check that Tone.js is available: - if (typeof Tone === 'undefined') + if (typeof Tone === "undefined") { throw Object.assign(response, { - error: "Tone.js is not available. A different sound library must be selected. Please contact the experiment designer." + error: "Tone.js is not available. A different sound library must be selected. Please contact the experiment designer.", }); } // start the Tone Transport if it has not started already: - if (typeof Tone !== 'undefined' && Tone.Transport.state !== 'started') + if (typeof Tone !== "undefined" && Tone.Transport.state !== "started") { - this.psychoJS.logger.info('[PsychoJS] start Tone Transport'); + this.psychoJS.logger.info("[PsychoJS] start Tone Transport"); Tone.Transport.start(Tone.now()); // this is necessary to prevent Tone from introducing a delay when triggering a note @@ -357,14 +350,14 @@

    Source: sound/TonePlayer.js

    // create a synth: we use a triangular oscillator with hardly any envelope: this._synthOtions = { oscillator: { - type: 'square' //'triangle' + type: "square", // 'triangle' }, envelope: { attack: 0.001, // 1ms - decay: 0.001, // 1ms + decay: 0.001, // 1ms sustain: 1, - release: 0.001 // 1ms - } + release: 0.001, // 1ms + }, }; this._synth = new Tone.Synth(this._synthOtions); @@ -373,7 +366,7 @@

    Source: sound/TonePlayer.js

    this._synth.connect(this._volumeNode); // connect the volume node to the master output: - if (typeof this._volumeNode.toDestination === 'function') + if (typeof this._volumeNode.toDestination === "function") { this._volumeNode.toDestination(); } @@ -385,15 +378,15 @@

    Source: sound/TonePlayer.js

    else { // create an AudioContext: - if (typeof this._audioContext === 'undefined') + if (typeof this._audioContext === "undefined") { const AudioContext = window.AudioContext || window.webkitAudioContext; // if AudioContext is not available (e.g. on IE), we throw an exception: - if (typeof AudioContext === 'undefined') + if (typeof AudioContext === "undefined") { throw Object.assign(response, { - error: `AudioContext is not available on your browser, ${this._psychoJS.browser}, please contact the experiment designer.` + error: `AudioContext is not available on your browser, ${this._psychoJS.browser}, please contact the experiment designer.`, }); } @@ -401,17 +394,15 @@

    Source: sound/TonePlayer.js

    } } } - } - /** * * @type {{TONE_JS: *, AUDIO_CONTEXT: *}} */ TonePlayer.SoundLibrary = { - AUDIO_CONTEXT: Symbol.for('AUDIO_CONTEXT'), - TONE_JS: Symbol.for('TONE_JS') + AUDIO_CONTEXT: Symbol.for("AUDIO_CONTEXT"), + TONE_JS: Symbol.for("TONE_JS"), }; @@ -420,19 +411,23 @@

    Source: sound/TonePlayer.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/sound_TrackPlayer.js.html b/docs/sound_TrackPlayer.js.html index 4440ed85..ccf9deb1 100644 --- a/docs/sound_TrackPlayer.js.html +++ b/docs/sound_TrackPlayer.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: sound/TrackPlayer.js - - - + sound/TrackPlayer.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: sound/TrackPlayer.js

    + + + + +
    + +

    sound/TrackPlayer.js

    + @@ -30,63 +54,58 @@

    Source: sound/TrackPlayer.js

    * Track Player. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ -import {SoundPlayer} from './SoundPlayer'; - +import { SoundPlayer } from "./SoundPlayer.js"; /** * <p>This class handles the playback of sound tracks.</p> * - * @name module:sound.TrackPlayer - * @class * @extends SoundPlayer - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {Object} options.howl - the sound object (see {@link https://howlerjs.com/}) - * @param {number} [options.startTime= 0] - start of playback (in seconds) - * @param {number} [options.stopTime= -1] - end of playback (in seconds) - * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo - * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played * * @todo stopTime is currently not implemented (tracks will play from startTime to finish) * @todo stereo is currently not implemented */ export class TrackPlayer extends SoundPlayer { + /** + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {Object} options.howl - the sound object (see {@link https://howlerjs.com/}) + * @param {number} [options.startTime= 0] - start of playback (in seconds) + * @param {number} [options.stopTime= -1] - end of playback (in seconds) + * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo + * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played + */ constructor({ - psychoJS, - howl, - startTime = 0, - stopTime = -1, - stereo = true, - volume = 0, - loops = 0 - } = {}) + psychoJS, + howl, + startTime = 0, + stopTime = -1, + stereo = true, + volume = 0, + loops = 0, + } = {}) { super(psychoJS); - this._addAttribute('howl', howl); - this._addAttribute('startTime', startTime); - this._addAttribute('stopTime', stopTime); - this._addAttribute('stereo', stereo); - this._addAttribute('loops', loops); - this._addAttribute('volume', volume); + this._addAttribute("howl", howl); + this._addAttribute("startTime", startTime); + this._addAttribute("stopTime", stopTime); + this._addAttribute("stereo", stereo); + this._addAttribute("loops", loops); + this._addAttribute("volume", volume); this._currentLoopIndex = -1; } - /** * Determine whether this player can play the given sound. * - * @name module:sound.TrackPlayer.accept - * @function - * @static - * @public * @param {module:sound.Sound} sound - the sound, which should be the name of an audio resource * file * @return {Object|undefined} an instance of TrackPlayer that can play the given track or undefined otherwise @@ -94,10 +113,10 @@

    Source: sound/TrackPlayer.js

    static accept(sound) { // if the sound's value is a string, we check whether it is the name of a resource: - if (typeof sound.value === 'string') + if (typeof sound.value === "string") { const howl = sound.psychoJS.serverManager.getResource(sound.value); - if (typeof howl !== 'undefined') + if (typeof howl !== "undefined") { // build the player: const player = new TrackPlayer({ @@ -107,7 +126,7 @@

    Source: sound/TrackPlayer.js

    stopTime: sound.stopTime, stereo: sound.stereo, loops: sound.loops, - volume: sound.volume + volume: sound.volume, }); return player; } @@ -117,13 +136,9 @@

    Source: sound/TrackPlayer.js

    return undefined; } - /** * Get the duration of the sound, in seconds. * - * @name module:sound.TrackPlayer#getDuration - * @function - * @public * @return {number} the duration of the track, in seconds */ getDuration() @@ -131,31 +146,23 @@

    Source: sound/TrackPlayer.js

    return this._howl.duration(); } - /** - * Set the duration of the default sprite. + * Set the duration of the track. * - * @name module:sound.TrackPlayer#setDuration - * @function - * @public * @param {number} duration_s - the duration of the track in seconds */ setDuration(duration_s) { - if (typeof this._howl !== 'undefined') + if (typeof this._howl !== "undefined") { // Unfortunately Howler.js provides duration setting method this._howl._duration = duration_s; } } - /** * Set the volume of the tone. * - * @name module:sound.TrackPlayer#setVolume - * @function - * @public * @param {Integer} volume - the volume of the track (must be between 0 and 1.0) * @param {boolean} [mute= false] - whether or not to mute the track */ @@ -167,13 +174,9 @@

    Source: sound/TrackPlayer.js

    this._howl.mute(mute); } - /** * Set the number of loops. * - * @name module:sound.TrackPlayer#setLoops - * @function - * @public * @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. */ setLoops(loops) @@ -191,19 +194,15 @@

    Source: sound/TrackPlayer.js

    } } - /** * Start playing the sound. * - * @name module:sound.TrackPlayer#play - * @function - * @public * @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 {number} [fadeDuration = 17] - how long should the fading in last in ms */ play(loops, fadeDuration = 17) { - if (typeof loops !== 'undefined') + if (typeof loops !== "undefined") { this.setLoops(loops); } @@ -212,7 +211,7 @@

    Source: sound/TrackPlayer.js

    if (loops > 0) { const self = this; - this._howl.on('end', (event) => + this._howl.on("end", (event) => { ++this._currentLoopIndex; if (self._currentLoopIndex > self._loops) @@ -233,24 +232,20 @@

    Source: sound/TrackPlayer.js

    this._howl.fade(0, this._volume, fadeDuration, this._id); } - /** * Stop playing the sound immediately. * - * @name module:sound.TrackPlayer#stop - * @function - * @public * @param {number} [fadeDuration = 17] - how long should the fading out last in ms */ stop(fadeDuration = 17) { - this._howl.once('fade', (id) => { + this._howl.once("fade", (id) => + { this._howl.stop(id); - this._howl.off('end'); + this._howl.off("end"); }); this._howl.fade(this._howl.volume(), 0, fadeDuration, this._id); } - } @@ -259,19 +254,23 @@

    Source: sound/TrackPlayer.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css deleted file mode 100644 index 7d1729dc..00000000 --- a/docs/styles/jsdoc-default.css +++ /dev/null @@ -1,358 +0,0 @@ -@font-face { - font-family: 'Open Sans'; - font-weight: normal; - font-style: normal; - src: url('../fonts/OpenSans-Regular-webfont.eot'); - src: - local('Open Sans'), - local('OpenSans'), - url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); -} - -@font-face { - font-family: 'Open Sans Light'; - font-weight: normal; - font-style: normal; - src: url('../fonts/OpenSans-Light-webfont.eot'); - src: - local('Open Sans Light'), - local('OpenSans Light'), - url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Light-webfont.woff') format('woff'), - url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg'); -} - -html -{ - overflow: auto; - background-color: #fff; - font-size: 14px; -} - -body -{ - font-family: 'Open Sans', sans-serif; - line-height: 1.5; - color: #4d4e53; - background-color: white; -} - -a, a:visited, a:active { - color: #0095dd; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -header -{ - display: block; - padding: 0px 4px; -} - -tt, code, kbd, samp { - font-family: Consolas, Monaco, 'Andale Mono', monospace; -} - -.class-description { - font-size: 130%; - line-height: 140%; - margin-bottom: 1em; - margin-top: 1em; -} - -.class-description:empty { - margin: 0; -} - -#main { - float: left; - width: 70%; -} - -article dl { - margin-bottom: 40px; -} - -article img { - max-width: 100%; -} - -section -{ - display: block; - background-color: #fff; - padding: 12px 24px; - border-bottom: 1px solid #ccc; - margin-right: 30px; -} - -.variation { - display: none; -} - -.signature-attributes { - font-size: 60%; - color: #aaa; - font-style: italic; - font-weight: lighter; -} - -nav -{ - display: block; - float: right; - margin-top: 28px; - width: 30%; - box-sizing: border-box; - border-left: 1px solid #ccc; - padding-left: 16px; -} - -nav ul { - font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; - font-size: 100%; - line-height: 17px; - padding: 0; - margin: 0; - list-style-type: none; -} - -nav ul a, nav ul a:visited, nav ul a:active { - font-family: Consolas, Monaco, 'Andale Mono', monospace; - line-height: 18px; - color: #4D4E53; -} - -nav h3 { - margin-top: 12px; -} - -nav li { - margin-top: 6px; -} - -footer { - display: block; - padding: 6px; - margin-top: 12px; - font-style: italic; - font-size: 90%; -} - -h1, h2, h3, h4 { - font-weight: 200; - margin: 0; -} - -h1 -{ - font-family: 'Open Sans Light', sans-serif; - font-size: 48px; - letter-spacing: -2px; - margin: 12px 24px 20px; -} - -h2, h3.subsection-title -{ - font-size: 30px; - font-weight: 700; - letter-spacing: -1px; - margin-bottom: 12px; -} - -h3 -{ - font-size: 24px; - letter-spacing: -0.5px; - margin-bottom: 12px; -} - -h4 -{ - font-size: 18px; - letter-spacing: -0.33px; - margin-bottom: 12px; - color: #4d4e53; -} - -h5, .container-overview .subsection-title -{ - font-size: 120%; - font-weight: bold; - letter-spacing: -0.01em; - margin: 8px 0 3px 0; -} - -h6 -{ - font-size: 100%; - letter-spacing: -0.01em; - margin: 6px 0 3px 0; - font-style: italic; -} - -table -{ - border-spacing: 0; - border: 0; - border-collapse: collapse; -} - -td, th -{ - border: 1px solid #ddd; - margin: 0px; - text-align: left; - vertical-align: top; - padding: 4px 6px; - display: table-cell; -} - -thead tr -{ - background-color: #ddd; - font-weight: bold; -} - -th { border-right: 1px solid #aaa; } -tr > th:last-child { border-right: 1px solid #ddd; } - -.ancestors, .attribs { color: #999; } -.ancestors a, .attribs a -{ - color: #999 !important; - text-decoration: none; -} - -.clear -{ - clear: both; -} - -.important -{ - font-weight: bold; - color: #950B02; -} - -.yes-def { - text-indent: -1000px; -} - -.type-signature { - color: #aaa; -} - -.name, .signature { - font-family: Consolas, Monaco, 'Andale Mono', monospace; -} - -.details { margin-top: 14px; border-left: 2px solid #DDD; } -.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } -.details dd { margin-left: 70px; } -.details ul { margin: 0; } -.details ul { list-style-type: none; } -.details li { margin-left: 30px; padding-top: 6px; } -.details pre.prettyprint { margin: 0 } -.details .object-value { padding-top: 0; } - -.description { - margin-bottom: 1em; - margin-top: 1em; -} - -.code-caption -{ - font-style: italic; - font-size: 107%; - margin: 0; -} - -.source -{ - border: 1px solid #ddd; - width: 80%; - overflow: auto; -} - -.prettyprint.source { - width: inherit; -} - -.source code -{ - font-size: 100%; - line-height: 18px; - display: block; - padding: 4px 12px; - margin: 0; - background-color: #fff; - color: #4D4E53; -} - -.prettyprint code span.line -{ - display: inline-block; -} - -.prettyprint.linenums -{ - padding-left: 70px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.prettyprint.linenums ol -{ - padding-left: 0; -} - -.prettyprint.linenums li -{ - border-left: 3px #ddd solid; -} - -.prettyprint.linenums li.selected, -.prettyprint.linenums li.selected * -{ - background-color: lightyellow; -} - -.prettyprint.linenums li * -{ - -webkit-user-select: text; - -moz-user-select: text; - -ms-user-select: text; - user-select: text; -} - -.params .name, .props .name, .name code { - color: #4D4E53; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - font-size: 100%; -} - -.params td.description > p:first-child, -.props td.description > p:first-child -{ - margin-top: 0; - padding-top: 0; -} - -.params td.description > p:last-child, -.props td.description > p:last-child -{ - margin-bottom: 0; - padding-bottom: 0; -} - -.disabled { - color: #454545; -} diff --git a/docs/styles/jsdoc.css b/docs/styles/jsdoc.css new file mode 100644 index 00000000..eb452659 --- /dev/null +++ b/docs/styles/jsdoc.css @@ -0,0 +1,765 @@ +* { + box-sizing: border-box +} + +html, body { + height: 100%; + width: 100%; +} + +body { + color: #4d4e53; + background-color: white; + margin: 0 auto; + padding: 0 20px; + font-family: 'Helvetica Neue', Helvetica, sans-serif; + font-size: 16px; +} + +img { + max-width: 100%; +} + +a, +a:active { + color: #606; + text-decoration: none; +} + +a:hover { + text-decoration: none; +} + +article a { + border-bottom: 1px solid #ddd; +} + +article a:hover, article a:active { + border-bottom-color: #222; +} + +article .description a { + word-break: break-word; +} + +p, ul, ol, blockquote { + margin-bottom: 1em; + line-height: 160%; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Montserrat', sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + color: #000; + font-weight: 400; + margin: 0; +} + +h1 { + font-weight: 300; + font-size: 48px; + margin: 1em 0 .5em; +} + +h1.page-title { + font-size: 48px; + margin: 1em 30px; + line-height: 100%; + word-wrap: break-word; +} + +h2 { + font-size: 24px; + margin: 1.5em 0 .3em; +} + +h3 { + font-size: 24px; + margin: 1.2em 0 .3em; +} + +h4 { + font-size: 18px; + margin: 1em 0 .2em; + color: #4d4e53; +} + +h4.name { + color: #fff; + background: #6d426d; + box-shadow: 0 .25em .5em #d3d3d3; + border-top: 1px solid #d3d3d3; + border-bottom: 1px solid #d3d3d3; + margin: 1.5em 0 0.5em; + padding: .75em 0 .75em 10px; +} + +h4.name a { + color: #fc83ff; +} + +h4.name a:hover { + border-bottom-color: #fc83ff; +} + +h5, .container-overview .subsection-title { + font-size: 120%; + letter-spacing: -0.01em; + margin: 8px 0 3px 0; +} + +h6 { + font-size: 100%; + letter-spacing: -0.01em; + margin: 6px 0 3px 0; + font-style: italic; +} + +.usertext h1 { + font-family: "Source Sans Pro"; + font-size: 24px; + margin: 2.5em 0 1em; + font-weight: 400; +} + +.usertext h2 { + font-family: "Source Sans Pro"; + font-size: 18px; + margin: 2em 0 0.5em; + font-weight: 400; + +} + +.usertext h3 { + font-family: "Source Sans Pro"; + font-size: 15px; + margin: 1.5em 0 0; + font-weight: 400; +} + +.usertext h4 { + font-family: "Source Sans Pro"; + font-size: 14px; + margin: 0 0 0; + font-weight: 400; +} + +.usertext h5 { + font-size: 12px; + margin: 1em 0 0; + font-weight: normal; + color: #666; +} + +.usertext h6 { + font-size: 11px; + margin: 1em 0 0; + font-weight: normal; + font-style: normal; + color: #666; +} + + +tt, code, kbd, samp { + font-family: Consolas, Monaco, 'Andale Mono', monospace; + background: #f4f4f4; + padding: 1px 5px; +} + +.class-description { + font-size: 130%; + line-height: 140%; + margin-bottom: 1em; + margin-top: 1em; +} + +.class-description:empty { + margin: 0 +} + +#main { + float: right; + width: calc(100% - 240px); +} + +header { + display: block +} + +section { + display: block; + background-color: #fff; + padding: 0 0 0 30px; +} + +.variation { + display: none +} + +.signature-attributes { + font-size: 60%; + color: #eee; + font-style: italic; + font-weight: lighter; +} + +nav { + float: left; + display: block; + width: 250px; + background: #fff; + overflow: auto; + position: fixed; + height: 100%; +} + +nav #nav-search{ + width: 210px; + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; + margin-right: 20px; + margin-top: 20px; +} + +nav.wrap a{ + word-wrap: break-word; +} + +nav h3 { + margin-top: 12px; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 700; + line-height: 24px; + margin: 15px 0 10px; + padding: 0; + color: #000; +} + +nav ul { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; + font-size: 100%; + line-height: 17px; + padding: 0; + margin: 0; + list-style-type: none; +} + +nav ul a, +nav ul a:active { + font-family: 'Montserrat', sans-serif; + line-height: 18px; + padding: 0; + display: block; + font-size: 12px; +} + +nav a:hover, +nav a:active { + color: #606; +} + +nav > ul { + padding: 0 10px; +} + +nav > ul > li > a { + color: #606; + margin-top: 10px; +} + +nav ul ul a { + color: hsl(207, 1%, 60%); + border-left: 1px solid hsl(207, 10%, 86%); +} + +nav ul ul a, +nav ul ul a:active { + padding-left: 20px +} + +nav h2 { + font-size: 13px; + margin: 10px 0 0 0; + padding: 0; +} + +nav > h2 > a { + margin: 10px 0 -10px; + color: #606 !important; +} + +footer { + color: hsl(0, 0%, 28%); + margin-left: 250px; + display: block; + padding: 15px; + font-style: italic; + font-size: 90%; +} + +.ancestors { + color: #999 +} + +.ancestors a { + color: #999 !important; +} + +.clear { + clear: both +} + +.important { + font-weight: bold; + color: #950B02; +} + +.yes-def { + text-indent: -1000px +} + +.type-signature { + color: #CA79CA +} + +.type-signature:last-child { + color: #eee; +} + +.name, .signature { + font-family: Consolas, Monaco, 'Andale Mono', monospace +} + +.signature { + color: #fc83ff; +} + +.details { + margin-top: 6px; + border-left: 2px solid #DDD; + line-height: 20px; + font-size: 14px; +} + +.details dt { + width: auto; + float: left; + padding-left: 10px; +} + +.details dd { + margin-left: 70px; + margin-top: 6px; + margin-bottom: 6px; +} + +.details ul { + margin: 0 +} + +.details ul { + list-style-type: none +} + +.details pre.prettyprint { + margin: 0 +} + +.details .object-value { + padding-top: 0 +} + +.description { + margin-bottom: 1em; + margin-top: 1em; +} + +.code-caption { + font-style: italic; + font-size: 107%; + margin: 0; +} + +.prettyprint { + font-size: 14px; + overflow: auto; +} + +.prettyprint.source { + width: inherit; + line-height: 18px; + display: block; + background-color: #0d152a; + color: #aeaeae; +} + +.prettyprint code { + line-height: 18px; + display: block; + background-color: #0d152a; + color: #4D4E53; +} + +.prettyprint > code { + padding: 15px; +} + +.prettyprint .linenums code { + padding: 0 15px +} + +.prettyprint .linenums li:first-of-type code { + padding-top: 15px +} + +.prettyprint code span.line { + display: inline-block +} + +.prettyprint.linenums { + padding-left: 70px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.prettyprint.linenums ol { + padding-left: 0 +} + +.prettyprint.linenums li { + border-left: 3px #34446B solid; +} + +.prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { + background-color: #34446B; +} + +.prettyprint.linenums li * { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.prettyprint.linenums li code:empty:after { + content:""; + display:inline-block; + width:0px; +} + +table { + border-spacing: 0; + border: 1px solid #ddd; + border-collapse: collapse; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + width: 100%; + font-size: 14px; + margin: 1em 0; +} + +td, th { + margin: 0px; + text-align: left; + vertical-align: top; + padding: 10px; + display: table-cell; +} + +thead tr, thead tr { + background-color: #fff; + font-weight: bold; + border-bottom: 1px solid #ddd; +} + +.params .type { + white-space: nowrap; +} + +.params code { + white-space: pre; +} + +.params td, .params .name, .props .name, .name code { + color: #4D4E53; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 100%; +} + +.params td { + border-top: 1px solid #eee +} + +.params td.description > p:first-child, .props td.description > p:first-child { + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, .props td.description > p:last-child { + margin-bottom: 0; + padding-bottom: 0; +} + +span.param-type, .params td .param-type, .param-type dd { + color: #606; + font-family: Consolas, Monaco, 'Andale Mono', monospace +} + +.param-type dt, .param-type dd { + display: inline-block +} + +.param-type { + margin: 14px 0; +} + +.disabled { + color: #454545 +} + +/* navicon button */ +.navicon-button { + display: none; + position: relative; + padding: 2.0625rem 1.5rem; + transition: 0.25s; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + opacity: .8; +} +.navicon-button .navicon:before, .navicon-button .navicon:after { + transition: 0.25s; +} +.navicon-button:hover { + transition: 0.5s; + opacity: 1; +} +.navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { + transition: 0.25s; +} +.navicon-button:hover .navicon:before { + top: .825rem; +} +.navicon-button:hover .navicon:after { + top: -.825rem; +} + +/* navicon */ +.navicon { + position: relative; + width: 2.5em; + height: .3125rem; + background: #000; + transition: 0.3s; + border-radius: 2.5rem; +} +.navicon:before, .navicon:after { + display: block; + content: ""; + height: .3125rem; + width: 2.5rem; + background: #000; + position: absolute; + z-index: -1; + transition: 0.3s 0.25s; + border-radius: 1rem; +} +.navicon:before { + top: .625rem; +} +.navicon:after { + top: -.625rem; +} + +/* open */ +.nav-trigger:checked + label:not(.steps) .navicon:before, +.nav-trigger:checked + label:not(.steps) .navicon:after { + top: 0 !important; +} + +.nav-trigger:checked + label .navicon:before, +.nav-trigger:checked + label .navicon:after { + transition: 0.5s; +} + +/* Minus */ +.nav-trigger:checked + label { + -webkit-transform: scale(0.75); + transform: scale(0.75); +} + +/* × and + */ +.nav-trigger:checked + label.plus .navicon, +.nav-trigger:checked + label.x .navicon { + background: transparent; +} + +.nav-trigger:checked + label.plus .navicon:before, +.nav-trigger:checked + label.x .navicon:before { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + background: #FFF; +} + +.nav-trigger:checked + label.plus .navicon:after, +.nav-trigger:checked + label.x .navicon:after { + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFF; +} + +.nav-trigger:checked + label.plus { + -webkit-transform: scale(0.75) rotate(45deg); + transform: scale(0.75) rotate(45deg); +} + +.nav-trigger:checked ~ nav { + left: 0 !important; +} + +.nav-trigger:checked ~ .overlay { + display: block; +} + +.nav-trigger { + position: fixed; + top: 0; + clip: rect(0, 0, 0, 0); +} + +.overlay { + display: none; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + background: hsla(0, 0%, 0%, 0.5); + z-index: 1; +} + +/* nav level */ +.level-hide { + display: none; +} +html[data-search-mode] .level-hide { + display: block; +} + + +@media only screen and (max-width: 680px) { + body { + overflow-x: hidden; + } + + nav { + background: #FFF; + width: 250px; + height: 100%; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: -250px; + z-index: 3; + padding: 0 10px; + transition: left 0.2s; + } + + .navicon-button { + display: inline-block; + position: fixed; + top: 1.5em; + right: 0; + z-index: 2; + } + + #main { + width: 100%; + } + + #main h1.page-title { + margin: 1em 0; + } + + #main section { + padding: 0; + } + + footer { + margin-left: 0; + } +} + +/** Add a '#' to static members */ +[data-type="member"] a::before { + content: '#'; + display: inline-block; + margin-left: -14px; + margin-right: 5px; +} + +#disqus_thread{ + margin-left: 30px; +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 400; + src: url('../fonts/Montserrat/Montserrat-Regular.eot'); /* IE9 Compat Modes */ + src: url('../fonts/Montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/Montserrat/Montserrat-Regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/Montserrat/Montserrat-Regular.woff') format('woff'), /* Pretty Modern Browsers */ + url('../fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); /* Safari, Android, iOS */ +} + +@font-face { + font-family: 'Montserrat'; + font-style: normal; + font-weight: 700; + src: url('../fonts/Montserrat/Montserrat-Bold.eot'); /* IE9 Compat Modes */ + src: url('../fonts/Montserrat/Montserrat-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/Montserrat/Montserrat-Bold.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/Montserrat/Montserrat-Bold.woff') format('woff'), /* Pretty Modern Browsers */ + url('../fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); /* Safari, Android, iOS */ +} + +@font-face { + font-family: 'Source Sans Pro'; + src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot'); + src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2') format('woff2'), + url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff') format('woff'), + url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf') format('truetype'), + url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Source Sans Pro'; + src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot'); + src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2') format('woff2'), + url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff') format('woff'), + url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf') format('truetype'), + url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); + font-weight: 300; + font-style: normal; + +} \ No newline at end of file diff --git a/docs/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css deleted file mode 100644 index 5a2526e3..00000000 --- a/docs/styles/prettify-jsdoc.css +++ /dev/null @@ -1,111 +0,0 @@ -/* JSDoc prettify.js theme */ - -/* plain text */ -.pln { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* string content */ -.str { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a keyword */ -.kwd { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a comment */ -.com { - font-weight: normal; - font-style: italic; -} - -/* a type name */ -.typ { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* a literal value */ -.lit { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* punctuation */ -.pun { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* lisp open bracket */ -.opn { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* lisp close bracket */ -.clo { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a markup tag name */ -.tag { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a markup attribute name */ -.atn { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a markup attribute value */ -.atv { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a declaration */ -.dec { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a variable name */ -.var { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* a function name */ -.fun { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* Specify class=linenums on a pre to get line numbering */ -ol.linenums { - margin-top: 0; - margin-bottom: 0; -} diff --git a/docs/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css deleted file mode 100644 index b6f92a78..00000000 --- a/docs/styles/prettify-tomorrow.css +++ /dev/null @@ -1,132 +0,0 @@ -/* Tomorrow Theme */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* Pretty printing styles. Used with prettify.js. */ -/* SPAN elements with the classes below are added by prettyprint. */ -/* plain text */ -.pln { - color: #4d4d4c; } - -@media screen { - /* string content */ - .str { - color: #718c00; } - - /* a keyword */ - .kwd { - color: #8959a8; } - - /* a comment */ - .com { - color: #8e908c; } - - /* a type name */ - .typ { - color: #4271ae; } - - /* a literal value */ - .lit { - color: #f5871f; } - - /* punctuation */ - .pun { - color: #4d4d4c; } - - /* lisp open bracket */ - .opn { - color: #4d4d4c; } - - /* lisp close bracket */ - .clo { - color: #4d4d4c; } - - /* a markup tag name */ - .tag { - color: #c82829; } - - /* a markup attribute name */ - .atn { - color: #f5871f; } - - /* a markup attribute value */ - .atv { - color: #3e999f; } - - /* a declaration */ - .dec { - color: #f5871f; } - - /* a variable name */ - .var { - color: #c82829; } - - /* a function name */ - .fun { - color: #4271ae; } } -/* Use higher contrast and text-weight for printable form. */ -@media print, projection { - .str { - color: #060; } - - .kwd { - color: #006; - font-weight: bold; } - - .com { - color: #600; - font-style: italic; } - - .typ { - color: #404; - font-weight: bold; } - - .lit { - color: #044; } - - .pun, .opn, .clo { - color: #440; } - - .tag { - color: #006; - font-weight: bold; } - - .atn { - color: #404; } - - .atv { - color: #060; } } -/* Style */ -/* -pre.prettyprint { - background: white; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - font-size: 12px; - line-height: 1.5; - border: 1px solid #ccc; - padding: 10px; } -*/ - -/* Specify class=linenums on a pre to get line numbering */ -ol.linenums { - margin-top: 0; - margin-bottom: 0; } - -/* IE indents via margin-left */ -li.L0, -li.L1, -li.L2, -li.L3, -li.L4, -li.L5, -li.L6, -li.L7, -li.L8, -li.L9 { - /* */ } - -/* Alternate shading for lines */ -li.L1, -li.L3, -li.L5, -li.L7, -li.L9 { - /* */ } diff --git a/docs/styles/prettify.css b/docs/styles/prettify.css new file mode 100644 index 00000000..d9521ec8 --- /dev/null +++ b/docs/styles/prettify.css @@ -0,0 +1,79 @@ +.pln { + color: #ddd; +} + +/* string content */ +.str { + color: #61ce3c; +} + +/* a keyword */ +.kwd { + color: #fbde2d; +} + +/* a comment */ +.com { + color: #aeaeae; +} + +/* a type name */ +.typ { + color: #8da6ce; +} + +/* a literal value */ +.lit { + color: #fbde2d; +} + +/* punctuation */ +.pun { + color: #ddd; +} + +/* lisp open bracket */ +.opn { + color: #000000; +} + +/* lisp close bracket */ +.clo { + color: #000000; +} + +/* a markup tag name */ +.tag { + color: #8da6ce; +} + +/* a markup attribute name */ +.atn { + color: #fbde2d; +} + +/* a markup attribute value */ +.atv { + color: #ddd; +} + +/* a declaration */ +.dec { + color: #EF5050; +} + +/* a variable name */ +.var { + color: #c82829; +} + +/* a function name */ +.fun { + color: #4271ae; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} diff --git a/docs/util_Clock.js.html b/docs/util_Clock.js.html index db55ea54..32b518cf 100644 --- a/docs/util_Clock.js.html +++ b/docs/util_Clock.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/Clock.js - - - + util/Clock.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: util/Clock.js

    +
    + +

    util/Clock.js

    + @@ -30,33 +54,28 @@

    Source: util/Clock.js

    * Clock component. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - /** * <p>MonotonicClock offers a convenient way to keep track of time during experiments. An experiment can have as many independent clocks as needed, e.g. one to time responses, another one to keep track of stimuli, etc.</p> - * - * @name module:util.MonotonicClock - * @class - * @param {number} [startTime= <time elapsed since the reference point, i.e. the time when the module was loaded>] - the clock's start time (in ms) */ export class MonotonicClock { + /** + * @memberof module:util + * @param {number} [startTime= <time elapsed since the reference point, i.e. the time when the module was loaded>] - the clock's start time (in ms) + */ constructor(startTime = MonotonicClock.getReferenceTime()) { this._timeAtLastReset = startTime; } - /** * Get the current time on this clock. * - * @name module:util.MonotonicClock#getTime - * @function - * @public * @return {number} the current time (in seconds) */ getTime() @@ -64,13 +83,9 @@

    Source: util/Clock.js

    return MonotonicClock.getReferenceTime() - this._timeAtLastReset; } - /** * Get the current offset being applied to the high resolution timebase used by this Clock. * - * @name module:util.MonotonicClock#getLastResetTime - * @function - * @public * @return {number} the offset (in seconds) */ getLastResetTime() @@ -78,13 +93,9 @@

    Source: util/Clock.js

    return this._timeAtLastReset; } - /** * Get the time elapsed since the reference point. * - * @name module:util.MonotonicClock#getReferenceTime - * @function - * @public * @return {number} the time elapsed since the reference point (in seconds) */ static getReferenceTime() @@ -93,32 +104,27 @@

    Source: util/Clock.js

    // return (new Date().getTime()) / 1000.0 - MonotonicClock._referenceTime; } - /** * Get the current timestamp with language-sensitive formatting rules applied. * * <p>Note: This is just a convenience wrapper around `Intl.DateTimeFormat()`.</p> * - * @name module:util.MonotonicClock.getDate - * @function - * @public - * @static * @param {string|array.string} locales - A string with a BCP 47 language tag, or an array of such strings. * @param {object} options - An object with detailed date and time styling information. * @return {string} The current timestamp in the chosen format. */ - static getDate(locales = 'en-CA', optionsMaybe) + static getDate(locales = "en-CA", optionsMaybe) { const date = new Date(); const options = Object.assign({ hour12: false, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - fractionalSecondDigits: 3 + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "numeric", + minute: "numeric", + second: "numeric", + fractionalSecondDigits: 3, }, optionsMaybe); const dateTimeFormat = new Intl.DateTimeFormat(locales, options); @@ -131,10 +137,6 @@

    Source: util/Clock.js

    * * <p>Note: This is mostly used as an appendix to the name of the keys save to the server.</p> * - * @name module:util.MonotonicClock.getDateStr - * @function - * @public - * @static * @return {string} A string representing the current time formatted as YYYY-MM-DD_HH[h]mm.ss.sss */ static getDateStr() @@ -142,37 +144,34 @@

    Source: util/Clock.js

    // yyyy-mm-dd, hh:mm:ss.sss return MonotonicClock.getDate() // yyyy-mm-dd_hh:mm:ss.sss - .replace(', ', '_') + .replace(", ", "_") // yyyy-mm-dd_hh[h]mm:ss.sss - .replace(':', 'h') + .replace(":", "h") // yyyy-mm-dd_hh[h]mm.ss.sss - .replace(':', '.'); + .replace(":", "."); } } - /** * The clock's referenceTime is the time when the module was loaded (in seconds). * - * @name module:util.MonotonicClock._referenceTime - * @readonly - * @private + * @protected * @type {number} */ MonotonicClock._referenceTime = performance.now() / 1000.0; // MonotonicClock._referenceTime = new Date().getTime() / 1000.0; - /** * <p>Clock is a MonotonicClock that also offers the possibility of being reset.</p> * - * @name module:util.Clock - * @class * @extends MonotonicClock */ export class Clock extends MonotonicClock { + /** + * @memberof module:util + */ constructor() { super(); @@ -181,10 +180,6 @@

    Source: util/Clock.js

    /** * Reset the time on the clock. * - * - * @name module:util.Clock#reset - * @function - * @public * @param {number} [newTime= 0] the new time on the clock. */ reset(newTime = 0) @@ -192,16 +187,12 @@

    Source: util/Clock.js

    this._timeAtLastReset = MonotonicClock.getReferenceTime() + newTime; } - /** * Add more time to the clock's 'start' time (t0). * * <p>Note: by adding time to t0, the current time is pushed forward (it becomes * smaller). As a consequence, getTime() may return a negative number.</p> * - * @name module:util.Clock#add - * @function - * @public * @param {number} [deltaTime] the time to be added to the clock's start time (t0) */ add(deltaTime) @@ -210,17 +201,17 @@

    Source: util/Clock.js

    } } - /** * <p>CountdownTimer is a clock counts down from the time of last reset.</p. * - * @name module:util.CountdownTimer - * @class * @extends Clock - * @param {number} [startTime= 0] - the start time of the countdown */ export class CountdownTimer extends Clock { + /** + * @memberof module:util + * @param {number} [startTime= 0] - the start time of the countdown + */ constructor(startTime = 0) { super(); @@ -233,16 +224,12 @@

    Source: util/Clock.js

    } } - /** * Add more time to the clock's 'start' time (t0). * * <p>Note: by adding time to t0, you push the current time forward (make it * smaller). As a consequence, getTime() may return a negative number.</p> * - * @name module:util.CountdownTimer#add - * @function - * @public * @param {number} [deltaTime] the time to be added to the clock's start time (t0) */ add(deltaTime) @@ -250,19 +237,15 @@

    Source: util/Clock.js

    this._timeAtLastReset += deltaTime; } - /** * Reset the time on the countdown. * - * @name module:util.CountdownTimer#reset - * @function - * @public * @param {number} [newTime] - if newTime is undefined, the countdown time is reset to zero, otherwise we set it * to newTime */ reset(newTime = undefined) { - if (typeof newTime == 'undefined') + if (typeof newTime == "undefined") { this._timeAtLastReset = MonotonicClock.getReferenceTime() + this._countdown_duration; } @@ -273,13 +256,9 @@

    Source: util/Clock.js

    } } - /** * Get the time currently left on the countdown. * - * @name module:util.CountdownTimer#getTime - * @function - * @public * @return {number} the time left on the countdown (in seconds) */ getTime() @@ -287,7 +266,6 @@

    Source: util/Clock.js

    return this._timeAtLastReset - MonotonicClock.getReferenceTime(); } } - @@ -295,19 +273,23 @@

    Source: util/Clock.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_Color.js.html b/docs/util_Color.js.html index dda5aceb..cd2e50cf 100644 --- a/docs/util_Color.js.html +++ b/docs/util_Color.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/Color.js - - - + util/Color.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: util/Color.js

    +
    + +

    util/Color.js

    + @@ -30,12 +54,11 @@

    Source: util/Color.js

    * Color management. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - /** * <p>This class handles multiple color spaces, and offers various * static methods for converting colors from one space to another.</p> @@ -51,36 +74,35 @@

    Source: util/Color.js

    * * <p>Note: internally, colors are represented as a [r,g,b] triplet with r,g,b in [0,1].</p> * - * @name module:util.Color - * @class - * @param {string|number|Array.<number>|undefined} [obj= 'black'] - an object representing a color - * @param {module:util.Color#COLOR_SPACE|undefined} [colorspace=Color.COLOR_SPACE.RGB] - the colorspace of that color - * * @todo implement HSV, DKL, and LMS colorspaces */ export class Color { - - constructor(obj = 'black', colorspace = Color.COLOR_SPACE.RGB) + /** + * @memberof module:util + * @param {string|number|Array.<number>|undefined} [obj= 'black'] - an object representing a color + * @param {module:util.Color#COLOR_SPACE|undefined} [colorspace=Color.COLOR_SPACE.RGB] - the colorspace of that color + */ + constructor(obj = "black", colorspace = Color.COLOR_SPACE.RGB) { const response = { - origin: 'Color', - context: 'when defining a color' + origin: "Color", + context: "when defining a color", }; // named color (e.g. 'seagreen') or string hexadecimal representation (e.g. '#FF0000'): // note: we expect the color space to be RGB - if (typeof obj == 'string') + if (typeof obj == "string") { if (colorspace !== Color.COLOR_SPACE.RGB) { throw Object.assign(response, { - error: 'the colorspace must be RGB for a named color' + error: "the colorspace must be RGB for a named color", }); } // hexademical representation: - if (obj[0] === '#') + if (obj[0] === "#") { this._hex = obj; } @@ -89,7 +111,7 @@

    Source: util/Color.js

    { if (!(obj.toLowerCase() in Color.NAMED_COLORS)) { - throw Object.assign(response, {error: 'unknown named color: ' + obj}); + throw Object.assign(response, { error: "unknown named color: " + obj }); } this._hex = Color.NAMED_COLORS[obj.toLowerCase()]; @@ -97,23 +119,21 @@

    Source: util/Color.js

    this._rgb = Color.hexToRgb(this._hex); } - // hexadecimal number representation (e.g. 0xFF0000) // note: we expect the color space to be RGB - else if (typeof obj == 'number') + else if (typeof obj == "number") { if (colorspace !== Color.COLOR_SPACE.RGB) { throw Object.assign(response, { - error: 'the colorspace must be RGB for' + - ' a' + - ' named color' + error: "the colorspace must be RGB for" + + " a" + + " named color", }); } this._rgb = Color._intToRgb(obj); } - // array of numbers: else if (Array.isArray(obj)) { @@ -152,23 +172,20 @@

    Source: util/Color.js

    break; default: - throw Object.assign(response, {error: 'unknown colorspace: ' + colorspace}); + throw Object.assign(response, { error: "unknown colorspace: " + colorspace }); } } - else if (obj instanceof Color) { this._rgb = obj._rgb.slice(); } - } + this._rgbFull = this._rgb.map(c => c * 2 - 1); + } /** * Get the [0,1] RGB triplet equivalent of this Color. * - * @name module:util.Color.rgb - * @function - * @public * @return {Array.<number>} the [0,1] RGB triplet equivalent */ get rgb() @@ -176,13 +193,19 @@

    Source: util/Color.js

    return this._rgb; } + /** + * Get the [-1,1] RGB triplet equivalent of this Color. + * + * @return {Array.<number>} the [-1,1] RGB triplet equivalent + */ + get rgbFull() + { + return this._rgbFull; + } /** * Get the [0,255] RGB triplet equivalent of this Color. * - * @name module:util.Color.rgb255 - * @function - * @public * @return {Array.<number>} the [0,255] RGB triplet equivalent */ get rgb255() @@ -190,18 +213,14 @@

    Source: util/Color.js

    return [Math.round(this._rgb[0] * 255.0), Math.round(this._rgb[1] * 255.0), Math.round(this._rgb[2] * 255.0)]; } - /** * Get the hexadecimal color code equivalent of this Color. * - * @name module:util.Color.hex - * @function - * @public * @return {string} the hexadecimal color code equivalent */ get hex() { - if (typeof this._hex === 'undefined') + if (typeof this._hex === "undefined") { this._hex = Color._rgbToHex(this._rgb); } @@ -211,21 +230,17 @@

    Source: util/Color.js

    /** * Get the integer code equivalent of this Color. * - * @name module:util.Color.int - * @function - * @public * @return {number} the integer code equivalent */ get int() { - if (typeof this._int === 'undefined') + if (typeof this._int === "undefined") { this._int = Color._rgbToInt(this._rgb); } return this._int; } - /* get hsv() { if (typeof this._hsv === 'undefined') @@ -244,28 +259,19 @@

    Source: util/Color.js

    } */ - /** * String representation of the color, i.e. the hexadecimal representation. * - * @name module:util.Color.toString - * @function * @return {string} the representation. - * */ toString() { return this.hex; } - /** * Get the [0,255] RGB triplet equivalent of the hexadecimal color code. * - * @name module:util.Color.hexToRgb255 - * @function - * @static - * @public * @param {string} hex - the hexadecimal color code * @return {Array.<number>} the [0,255] RGB triplet equivalent */ @@ -275,23 +281,18 @@

    Source: util/Color.js

    if (result == null) { throw { - origin: 'Color.hexToRgb255', - context: 'when converting an hexadecimal color code to its 255- or [0,1]-based RGB color representation', - error: 'unable to parse the argument: wrong type or wrong code' + origin: "Color.hexToRgb255", + context: "when converting an hexadecimal color code to its 255- or [0,1]-based RGB color representation", + error: "unable to parse the argument: wrong type or wrong code", }; } return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; } - /** * Get the [0,1] RGB triplet equivalent of the hexadecimal color code. * - * @name module:util.Color.hexToRgb - * @function - * @static - * @public * @param {string} hex - the hexadecimal color code * @return {Array.<number>} the [0,1] RGB triplet equivalent */ @@ -301,22 +302,17 @@

    Source: util/Color.js

    return [r255 / 255.0, g255 / 255.0, b255 / 255.0]; } - /** * Get the hexadecimal color code equivalent of the [0, 255] RGB triplet. * - * @name module:util.Color.rgb255ToHex - * @function - * @static - * @public * @param {Array.<number>} rgb255 - the [0, 255] RGB triplet * @return {string} the hexadecimal color code equivalent */ static rgb255ToHex(rgb255) { const response = { - origin: 'Color.rgb255ToHex', - context: 'when converting an rgb triplet to its hexadecimal color representation' + origin: "Color.rgb255ToHex", + context: "when converting an rgb triplet to its hexadecimal color representation", }; try @@ -326,26 +322,21 @@

    Source: util/Color.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the hexadecimal color code equivalent of the [0, 1] RGB triplet. * - * @name module:util.Color.rgbToHex - * @function - * @static - * @public * @param {Array.<number>} rgb - the [0, 1] RGB triplet * @return {string} the hexadecimal color code equivalent */ static rgbToHex(rgb) { const response = { - origin: 'Color.rgbToHex', - context: 'when converting an rgb triplet to its hexadecimal color representation' + origin: "Color.rgbToHex", + context: "when converting an rgb triplet to its hexadecimal color representation", }; try @@ -355,26 +346,21 @@

    Source: util/Color.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the integer equivalent of the [0, 1] RGB triplet. * - * @name module:util.Color.rgbToInt - * @function - * @static - * @public * @param {Array.<number>} rgb - the [0, 1] RGB triplet * @return {number} the integer equivalent */ static rgbToInt(rgb) { const response = { - origin: 'Color.rgbToInt', - context: 'when converting an rgb triplet to its integer representation' + origin: "Color.rgbToInt", + context: "when converting an rgb triplet to its integer representation", }; try @@ -384,26 +370,21 @@

    Source: util/Color.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the integer equivalent of the [0, 255] RGB triplet. * - * @name module:util.Color.rgb255ToInt - * @function - * @static - * @public * @param {Array.<number>} rgb255 - the [0, 255] RGB triplet * @return {number} the integer equivalent */ static rgb255ToInt(rgb255) { const response = { - origin: 'Color.rgb255ToInt', - context: 'when converting an rgb triplet to its integer representation' + origin: "Color.rgb255ToInt", + context: "when converting an rgb triplet to its integer representation", }; try { @@ -412,20 +393,16 @@

    Source: util/Color.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - /** * Get the hexadecimal color code equivalent of the [0, 255] RGB triplet. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._rgb255ToHex - * @function - * @static - * @private + * @protected * @param {Array.<number>} rgb255 - the [0, 255] RGB triplet * @return {string} the hexadecimal color code equivalent */ @@ -434,16 +411,12 @@

    Source: util/Color.js

    return "#" + ((1 << 24) + (rgb255[0] << 16) + (rgb255[1] << 8) + rgb255[2]).toString(16).slice(1); } - /** * Get the hexadecimal color code equivalent of the [0, 1] RGB triplet. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._rgbToHex - * @function - * @static - * @private + * @protected * @param {Array.<number>} rgb - the [0, 1] RGB triplet * @return {string} the hexadecimal color code equivalent */ @@ -453,16 +426,12 @@

    Source: util/Color.js

    return Color._rgb255ToHex(rgb255); } - /** * Get the integer equivalent of the [0, 1] RGB triplet. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._rgbToInt - * @function - * @static - * @private + * @protected * @param {Array.<number>} rgb - the [0, 1] RGB triplet * @return {number} the integer equivalent */ @@ -472,16 +441,12 @@

    Source: util/Color.js

    return Color._rgb255ToInt(rgb255); } - /** * Get the integer equivalent of the [0, 255] RGB triplet. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._rgb255ToInt - * @function - * @static - * @private + * @protected * @param {Array.<number>} rgb255 - the [0, 255] RGB triplet * @return {number} the integer equivalent */ @@ -490,16 +455,12 @@

    Source: util/Color.js

    return rgb255[0] * 0x10000 + rgb255[1] * 0x100 + rgb255[2]; } - /** * Get the [0, 255] based RGB triplet equivalent of the integer color code. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._intToRgb255 - * @function - * @static - * @private + * @protected * @param {number} hex - the integer color code * @return {Array.<number>} the [0, 255] RGB equivalent */ @@ -512,16 +473,12 @@

    Source: util/Color.js

    return [r255, g255, b255]; } - /** * Get the [0, 1] based RGB triplet equivalent of the integer color code. * * <p>Note: this is the fast, unsafe version which does not check for argument sanity</p> * - * @name module:util.Color._intToRgb - * @function - * @static - * @private + * @protected * @param {number} hex - the integer color code * @return {Array.<number>} the [0, 1] RGB equivalent */ @@ -535,49 +492,44 @@

    Source: util/Color.js

    /** * Check that the argument is an array of numbers of size 3, and, potentially, that its elements fall within the range. * - * @name module:util.Color._checkTypeAndRange - * @function - * @static - * @private + * @protected * @param {any} arg - the argument * @param {Array.<number>} [range] - the lower and higher bounds of the range * @return {boolean} whether the argument is an array of numbers of size 3, and, potentially, whether its elements fall within the range (if range is not undefined) */ static _checkTypeAndRange(arg, range = undefined) { - if (!Array.isArray(arg) || arg.length !== 3 || - typeof arg[0] !== 'number' || typeof arg[1] !== 'number' || typeof arg[2] !== 'number') + if ( + !Array.isArray(arg) || arg.length !== 3 + || typeof arg[0] !== "number" || typeof arg[1] !== "number" || typeof arg[2] !== "number" + ) { - throw 'the argument should be an array of numbers of length 3'; + throw "the argument should be an array of numbers of length 3"; } - if (typeof range !== 'undefined' && (arg[0] < range[0] || arg[0] > range[1] || arg[1] < range[0] || arg[1] > range[1] || arg[2] < range[0] || arg[2] > range[1])) + if (typeof range !== "undefined" && (arg[0] < range[0] || arg[0] > range[1] || arg[1] < range[0] || arg[1] > range[1] || arg[2] < range[0] || arg[2] > range[1])) { - throw 'the color components should all belong to [' + range[0] + ', ' + range[1] + ']'; + throw "the color components should all belong to [" + range[0] + ", " + range[1] + "]"; } } } - /** * Color spaces. * - * @name module:util.Color#COLOR_SPACE * @enum {Symbol} * @readonly - * @public */ Color.COLOR_SPACE = { /** * RGB colorspace: [r,g,b] with r,g,b in [-1, 1] */ - RGB: Symbol.for('RGB'), + RGB: Symbol.for("RGB"), /** * RGB255 colorspace: [r,g,b] with r,g,b in [0, 255] */ - RGB255: Symbol.for('RGB255'), - + RGB255: Symbol.for("RGB255"), /* HSV: Symbol.for('HSV'), DKL: Symbol.for('DKL'), @@ -585,163 +537,160 @@

    Source: util/Color.js

    */ }; - /** * Named colors. * - * @name module:util.Color#NAMED_COLORS * @enum {string} * @readonly - * @public */ Color.NAMED_COLORS = { - 'aliceblue': '#F0F8FF', - 'antiquewhite': '#FAEBD7', - 'aqua': '#00FFFF', - 'aquamarine': '#7FFFD4', - 'azure': '#F0FFFF', - 'beige': '#F5F5DC', - 'bisque': '#FFE4C4', - 'black': '#000000', - 'blanchedalmond': '#FFEBCD', - 'blue': '#0000FF', - 'blueviolet': '#8A2BE2', - 'brown': '#A52A2A', - 'burlywood': '#DEB887', - 'cadetblue': '#5F9EA0', - 'chartreuse': '#7FFF00', - 'chocolate': '#D2691E', - 'coral': '#FF7F50', - 'cornflowerblue': '#6495ED', - 'cornsilk': '#FFF8DC', - 'crimson': '#DC143C', - 'cyan': '#00FFFF', - 'darkblue': '#00008B', - 'darkcyan': '#008B8B', - 'darkgoldenrod': '#B8860B', - 'darkgray': '#A9A9A9', - 'darkgrey': '#A9A9A9', - 'darkgreen': '#006400', - 'darkkhaki': '#BDB76B', - 'darkmagenta': '#8B008B', - 'darkolivegreen': '#556B2F', - 'darkorange': '#FF8C00', - 'darkorchid': '#9932CC', - 'darkred': '#8B0000', - 'darksalmon': '#E9967A', - 'darkseagreen': '#8FBC8B', - 'darkslateblue': '#483D8B', - 'darkslategray': '#2F4F4F', - 'darkslategrey': '#2F4F4F', - 'darkturquoise': '#00CED1', - 'darkviolet': '#9400D3', - 'deeppink': '#FF1493', - 'deepskyblue': '#00BFFF', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1E90FF', - 'firebrick': '#B22222', - 'floralwhite': '#FFFAF0', - 'forestgreen': '#228B22', - 'fuchsia': '#FF00FF', - 'gainsboro': '#DCDCDC', - 'ghostwhite': '#F8F8FF', - 'gold': '#FFD700', - 'goldenrod': '#DAA520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#ADFF2F', - 'honeydew': '#F0FFF0', - 'hotpink': '#FF69B4', - 'indianred': '#CD5C5C', - 'indigo': '#4B0082', - 'ivory': '#FFFFF0', - 'khaki': '#F0E68C', - 'lavender': '#E6E6FA', - 'lavenderblush': '#FFF0F5', - 'lawngreen': '#7CFC00', - 'lemonchiffon': '#FFFACD', - 'lightblue': '#ADD8E6', - 'lightcoral': '#F08080', - 'lightcyan': '#E0FFFF', - 'lightgoldenrodyellow': '#FAFAD2', - 'lightgray': '#D3D3D3', - 'lightgrey': '#D3D3D3', - 'lightgreen': '#90EE90', - 'lightpink': '#FFB6C1', - 'lightsalmon': '#FFA07A', - 'lightseagreen': '#20B2AA', - 'lightskyblue': '#87CEFA', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#B0C4DE', - 'lightyellow': '#FFFFE0', - 'lime': '#00FF00', - 'limegreen': '#32CD32', - 'linen': '#FAF0E6', - 'magenta': '#FF00FF', - 'maroon': '#800000', - 'mediumaquamarine': '#66CDAA', - 'mediumblue': '#0000CD', - 'mediumorchid': '#BA55D3', - 'mediumpurple': '#9370DB', - 'mediumseagreen': '#3CB371', - 'mediumslateblue': '#7B68EE', - 'mediumspringgreen': '#00FA9A', - 'mediumturquoise': '#48D1CC', - 'mediumvioletred': '#C71585', - 'midnightblue': '#191970', - 'mintcream': '#F5FFFA', - 'mistyrose': '#FFE4E1', - 'moccasin': '#FFE4B5', - 'navajowhite': '#FFDEAD', - 'navy': '#000080', - 'oldlace': '#FDF5E6', - 'olive': '#808000', - 'olivedrab': '#6B8E23', - 'orange': '#FFA500', - 'orangered': '#FF4500', - 'orchid': '#DA70D6', - 'palegoldenrod': '#EEE8AA', - 'palegreen': '#98FB98', - 'paleturquoise': '#AFEEEE', - 'palevioletred': '#DB7093', - 'papayawhip': '#FFEFD5', - 'peachpuff': '#FFDAB9', - 'peru': '#CD853F', - 'pink': '#FFC0CB', - 'plum': '#DDA0DD', - 'powderblue': '#B0E0E6', - 'purple': '#800080', - 'red': '#FF0000', - 'rosybrown': '#BC8F8F', - 'royalblue': '#4169E1', - 'saddlebrown': '#8B4513', - 'salmon': '#FA8072', - 'sandybrown': '#F4A460', - 'seagreen': '#2E8B57', - 'seashell': '#FFF5EE', - 'sienna': '#A0522D', - 'silver': '#C0C0C0', - 'skyblue': '#87CEEB', - 'slateblue': '#6A5ACD', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#FFFAFA', - 'springgreen': '#00FF7F', - 'steelblue': '#4682B4', - 'tan': '#D2B48C', - 'teal': '#008080', - 'thistle': '#D8BFD8', - 'tomato': '#FF6347', - 'turquoise': '#40E0D0', - 'violet': '#EE82EE', - 'wheat': '#F5DEB3', - 'white': '#FFFFFF', - 'whitesmoke': '#F5F5F5', - 'yellow': '#FFFF00', - 'yellowgreen': '#9ACD32' + "aliceblue": "#F0F8FF", + "antiquewhite": "#FAEBD7", + "aqua": "#00FFFF", + "aquamarine": "#7FFFD4", + "azure": "#F0FFFF", + "beige": "#F5F5DC", + "bisque": "#FFE4C4", + "black": "#000000", + "blanchedalmond": "#FFEBCD", + "blue": "#0000FF", + "blueviolet": "#8A2BE2", + "brown": "#A52A2A", + "burlywood": "#DEB887", + "cadetblue": "#5F9EA0", + "chartreuse": "#7FFF00", + "chocolate": "#D2691E", + "coral": "#FF7F50", + "cornflowerblue": "#6495ED", + "cornsilk": "#FFF8DC", + "crimson": "#DC143C", + "cyan": "#00FFFF", + "darkblue": "#00008B", + "darkcyan": "#008B8B", + "darkgoldenrod": "#B8860B", + "darkgray": "#A9A9A9", + "darkgrey": "#A9A9A9", + "darkgreen": "#006400", + "darkkhaki": "#BDB76B", + "darkmagenta": "#8B008B", + "darkolivegreen": "#556B2F", + "darkorange": "#FF8C00", + "darkorchid": "#9932CC", + "darkred": "#8B0000", + "darksalmon": "#E9967A", + "darkseagreen": "#8FBC8B", + "darkslateblue": "#483D8B", + "darkslategray": "#2F4F4F", + "darkslategrey": "#2F4F4F", + "darkturquoise": "#00CED1", + "darkviolet": "#9400D3", + "deeppink": "#FF1493", + "deepskyblue": "#00BFFF", + "dimgray": "#696969", + "dimgrey": "#696969", + "dodgerblue": "#1E90FF", + "firebrick": "#B22222", + "floralwhite": "#FFFAF0", + "forestgreen": "#228B22", + "fuchsia": "#FF00FF", + "gainsboro": "#DCDCDC", + "ghostwhite": "#F8F8FF", + "gold": "#FFD700", + "goldenrod": "#DAA520", + "gray": "#808080", + "grey": "#808080", + "green": "#008000", + "greenyellow": "#ADFF2F", + "honeydew": "#F0FFF0", + "hotpink": "#FF69B4", + "indianred": "#CD5C5C", + "indigo": "#4B0082", + "ivory": "#FFFFF0", + "khaki": "#F0E68C", + "lavender": "#E6E6FA", + "lavenderblush": "#FFF0F5", + "lawngreen": "#7CFC00", + "lemonchiffon": "#FFFACD", + "lightblue": "#ADD8E6", + "lightcoral": "#F08080", + "lightcyan": "#E0FFFF", + "lightgoldenrodyellow": "#FAFAD2", + "lightgray": "#D3D3D3", + "lightgrey": "#D3D3D3", + "lightgreen": "#90EE90", + "lightpink": "#FFB6C1", + "lightsalmon": "#FFA07A", + "lightseagreen": "#20B2AA", + "lightskyblue": "#87CEFA", + "lightslategray": "#778899", + "lightslategrey": "#778899", + "lightsteelblue": "#B0C4DE", + "lightyellow": "#FFFFE0", + "lime": "#00FF00", + "limegreen": "#32CD32", + "linen": "#FAF0E6", + "magenta": "#FF00FF", + "maroon": "#800000", + "mediumaquamarine": "#66CDAA", + "mediumblue": "#0000CD", + "mediumorchid": "#BA55D3", + "mediumpurple": "#9370DB", + "mediumseagreen": "#3CB371", + "mediumslateblue": "#7B68EE", + "mediumspringgreen": "#00FA9A", + "mediumturquoise": "#48D1CC", + "mediumvioletred": "#C71585", + "midnightblue": "#191970", + "mintcream": "#F5FFFA", + "mistyrose": "#FFE4E1", + "moccasin": "#FFE4B5", + "navajowhite": "#FFDEAD", + "navy": "#000080", + "oldlace": "#FDF5E6", + "olive": "#808000", + "olivedrab": "#6B8E23", + "orange": "#FFA500", + "orangered": "#FF4500", + "orchid": "#DA70D6", + "palegoldenrod": "#EEE8AA", + "palegreen": "#98FB98", + "paleturquoise": "#AFEEEE", + "palevioletred": "#DB7093", + "papayawhip": "#FFEFD5", + "peachpuff": "#FFDAB9", + "peru": "#CD853F", + "pink": "#FFC0CB", + "plum": "#DDA0DD", + "powderblue": "#B0E0E6", + "purple": "#800080", + "red": "#FF0000", + "rosybrown": "#BC8F8F", + "royalblue": "#4169E1", + "saddlebrown": "#8B4513", + "salmon": "#FA8072", + "sandybrown": "#F4A460", + "seagreen": "#2E8B57", + "seashell": "#FFF5EE", + "sienna": "#A0522D", + "silver": "#C0C0C0", + "skyblue": "#87CEEB", + "slateblue": "#6A5ACD", + "slategray": "#708090", + "slategrey": "#708090", + "snow": "#FFFAFA", + "springgreen": "#00FF7F", + "steelblue": "#4682B4", + "tan": "#D2B48C", + "teal": "#008080", + "thistle": "#D8BFD8", + "tomato": "#FF6347", + "turquoise": "#40E0D0", + "violet": "#EE82EE", + "wheat": "#F5DEB3", + "white": "#FFFFFF", + "whitesmoke": "#F5F5F5", + "yellow": "#FFFF00", + "yellowgreen": "#9ACD32", }; @@ -750,19 +699,23 @@

    Source: util/Color.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_ColorMixin.js.html b/docs/util_ColorMixin.js.html index 110bf767..c2dbac3d 100644 --- a/docs/util_ColorMixin.js.html +++ b/docs/util_ColorMixin.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/ColorMixin.js - - - + util/ColorMixin.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + -

    Source: util/ColorMixin.js

    + + +
    + +

    util/ColorMixin.js

    + @@ -30,14 +54,12 @@

    Source: util/ColorMixin.js

    * Color Mixin. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {Color} from './Color'; - +import { Color } from "./Color.js"; /** * <p>This mixin implement color and contrast changes for visual stimuli</p> @@ -45,66 +67,60 @@

    Source: util/ColorMixin.js

    * @name module:util.ColorMixin * @mixin */ -export let ColorMixin = (superclass) => class extends superclass -{ - constructor(args) +export let ColorMixin = (superclass) => + class extends superclass { - super(args); - } - + constructor(args) + { + super(args); + } - /** + /** * Setter for Color attribute. * * @name module:util.ColorMixin#setColor * @function - * @public * @param {Color} color - the new color * @param {boolean} [log= false] - whether or not to log */ - setColor(color, log) - { - this._setAttribute('color', color, log); - - this._needUpdate = true; - this._needPixiUpdate = true; - } + setColor(color, log) + { + this._setAttribute("color", color, log); + this._needUpdate = true; + this._needPixiUpdate = true; + } - /** + /** * Setter for Contrast attribute. * * @name module:util.ColorMixin#setContrast * @function - * @public * @param {number} contrast - the new contrast (must be between 0 and 1) * @param {boolean} [log= false] - whether or not to log */ - setContrast(contrast, log) - { - this._setAttribute('contrast', contrast, log); - - this._needUpdate = true; - this._needPixiUpdate = true; - } + setContrast(contrast, log) + { + this._setAttribute("contrast", contrast, log); + this._needUpdate = true; + this._needPixiUpdate = true; + } - /** + /** * Get a new contrasted Color. * * @name module:util.ColorMixin#getContrastedColor * @function - * @public * @param {string|number|Array.<number>} color - the color * @param {number} contrast - the contrast (must be between 0 and 1) */ - getContrastedColor(color, contrast) - { - const rgb = color.rgb.map(c => (c * 2.0 - 1.0) * contrast); - return new Color(rgb, Color.COLOR_SPACE.RGB); - } - -}; + getContrastedColor(color, contrast) + { + const rgb = color.rgb.map((c) => (c * 2.0 - 1.0) * contrast); + return new Color(rgb, Color.COLOR_SPACE.RGB); + } + }; @@ -112,19 +128,23 @@

    Source: util/ColorMixin.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_EventEmitter.js.html b/docs/util_EventEmitter.js.html index 83f200e6..cb64774d 100644 --- a/docs/util_EventEmitter.js.html +++ b/docs/util_EventEmitter.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/EventEmitter.js - - - + util/EventEmitter.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: util/EventEmitter.js

    + + + + +
    + +

    util/EventEmitter.js

    + @@ -30,23 +54,18 @@

    Source: util/EventEmitter.js

    * Event Emitter. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as util from './Util'; - +import * as util from "./Util.js"; /** * <p>EventEmitter implements the classic observer/observable pattern.</p> * * <p>Note: this is heavily inspired by http://www.datchley.name/es6-eventemitter/</p> * - * @name module:util.EventEmitter - * @class - * * @example * let observable = new EventEmitter(); * let uuid1 = observable.on('change', data => { console.log(data); }); @@ -56,13 +75,15 @@

    Source: util/EventEmitter.js

    */ export class EventEmitter { + /** + * @memberof module:util + */ constructor() { this._listeners = new Map(); this._onceUuids = new Map(); } - /** * Listener called when this instance emits an event for which it is registered. * @@ -70,13 +91,9 @@

    Source: util/EventEmitter.js

    * @param {object} data - the data passed to the listener */ - /** * Register a new listener for events with the given name emitted by this instance. * - * @name module:util.EventEmitter#on - * @function - * @public * @param {String} name - the name of the event * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event * @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener) @@ -84,9 +101,9 @@

    Source: util/EventEmitter.js

    on(name, listener) { // check that the listener is a function: - if (typeof listener !== 'function') + if (typeof listener !== "function") { - throw new TypeError('listener must be a function'); + throw new TypeError("listener must be a function"); } // generate a new uuid: @@ -97,18 +114,14 @@

    Source: util/EventEmitter.js

    { this._listeners.set(name, []); } - this._listeners.get(name).push({uuid, listener}); + this._listeners.get(name).push({ uuid, listener }); return uuid; } - /** * Register a new listener for the given event name, and remove it as soon as the event has been emitted. * - * @name module:util.EventEmitter#once - * @function - * @public * @param {String} name - the name of the event * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event * @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener) @@ -126,13 +139,9 @@

    Source: util/EventEmitter.js

    return uuid; } - /** * Remove the listener with the given uuid associated to the given event name. * - * @name module:util.EventEmitter#off - * @function - * @public * @param {String} name - the name of the event * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event */ @@ -142,19 +151,15 @@

    Source: util/EventEmitter.js

    if (relevantUuidListeners && relevantUuidListeners.length) { - this._listeners.set(name, relevantUuidListeners.filter(uuidlistener => (uuidlistener.uuid != uuid))); + this._listeners.set(name, relevantUuidListeners.filter((uuidlistener) => (uuidlistener.uuid != uuid))); return true; } return false; } - /** * Emit an event with a given name and associated data. * - * @name module:util.EventEmitter#emit - * @function - * @public * @param {String} name - the name of the event * @param {object} data - the data of the event * @return {boolean} true if at least one listener has been registered for that event, and false otherwise @@ -166,11 +171,11 @@

    Source: util/EventEmitter.js

    { let onceUuids = this._onceUuids.get(name); let self = this; - relevantUuidListeners.forEach(({uuid, listener}) => + relevantUuidListeners.forEach(({ uuid, listener }) => { listener(data); - if (typeof onceUuids !== 'undefined' && onceUuids.includes(uuid)) + if (typeof onceUuids !== "undefined" && onceUuids.includes(uuid)) { self.off(name, uuid); } @@ -180,8 +185,6 @@

    Source: util/EventEmitter.js

    return false; } - - } @@ -190,19 +193,23 @@

    Source: util/EventEmitter.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_Pixi.js.html b/docs/util_Pixi.js.html new file mode 100644 index 00000000..89af4430 --- /dev/null +++ b/docs/util_Pixi.js.html @@ -0,0 +1,114 @@ + + + + + + util/Pixi.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    util/Pixi.js

    + + + + + + + +
    +
    +
    /**
    + * PIXI utilities.
    + *
    + * @authors Alain Pitiot, Sotiri Bakagiannis, Thomas Pronk
    + * @version 2022.2.0
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @license Distributed under the terms of the MIT License
    + */
    +
    +import * as PIXI from "pixi.js-legacy";
    +import { to_px } from "./Util.js";
    +
    +/**
    + * Convert a position to a PIXI Point.
    + *
    + * @name module:util.to_pixiPoint
    + * @function
    + * @param {number[]} pos - the input position
    + * @param {string} posUnit - the position units
    + * @param {Window} win - the associated Window
    + * @param {boolean} [integerCoordinates = false] - whether or not to round the PIXI Point coordinates.
    + * @returns {number[]} the position as a PIXI Point
    + */
    +export function to_pixiPoint(pos, posUnit, win, integerCoordinates = false)
    +{
    +	const pos_px = to_px(pos, posUnit, win);
    +	if (integerCoordinates)
    +	{
    +		return new PIXI.Point(Math.round(pos_px[0]), Math.round(pos_px[1]));
    +	}
    +	else
    +	{
    +		return new PIXI.Point(pos_px[0], pos_px[1]);
    +	}
    +}
    +
    +
    +
    + + + + + + +
    + +
    + +
    + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
    + + + + + + + + + + + diff --git a/docs/util_PsychObject.js.html b/docs/util_PsychObject.js.html index d36549a2..98202168 100644 --- a/docs/util_PsychObject.js.html +++ b/docs/util_PsychObject.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/PsychObject.js - - - + util/PsychObject.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: util/PsychObject.js

    +
    + +

    util/PsychObject.js

    + @@ -31,27 +55,26 @@

    Source: util/PsychObject.js

    * Core Object. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {EventEmitter} from './EventEmitter'; -import * as util from './Util'; - +import { EventEmitter } from "./EventEmitter.js"; +import * as util from "./Util.js"; /** * <p>PsychoObject is the base class for all PsychoJS objects. * It is responsible for handling attributes.</p> * - * @class * @extends EventEmitter - * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance - * @param {string} name - the name of the object (mostly useful for debugging) */ export class PsychObject extends EventEmitter { + /** + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + * @param {string} name - the name of the object (mostly useful for debugging) + */ constructor(psychoJS, name) { super(); @@ -60,18 +83,16 @@

    Source: util/PsychObject.js

    this._userAttributes = new Set(); // name: - if (typeof name === 'undefined') + if (typeof name === "undefined") { name = this.constructor.name; } - this._addAttribute('name', name); + this._addAttribute("name", name); } - /** * Get the PsychoJS instance. * - * @public * @return {PsychoJS} the PsychoJS instance */ get psychoJS() @@ -79,11 +100,9 @@

    Source: util/PsychObject.js

    return this._psychoJS; } - /** * Setter for the PsychoJS attribute. * - * @public * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance */ set psychoJS(psychoJS) @@ -91,49 +110,46 @@

    Source: util/PsychObject.js

    this._psychoJS = psychoJS; } - /** * String representation of the PsychObject. * * <p>Note: attribute values are limited to 50 characters.</p> * - * @public * @return {string} the representation */ toString() { - let representation = this.constructor.name + '( '; + let representation = this.constructor.name + "( "; let addComma = false; for (const attribute of this._userAttributes) { if (addComma) { - representation += ', '; + representation += ", "; } addComma = true; - let value = util.toString(this['_' + attribute]); + let value = util.toString(this["_" + attribute]); const l = value.length; if (l > 50) { - if (value[l - 1] === ')') + if (value[l - 1] === ")") { - value = value.substring(0, 50) + '~)'; + value = value.substring(0, 50) + "~)"; } else { - value = value.substring(0, 50) + '~'; + value = value.substring(0, 50) + "~"; } } - representation += attribute + '=' + value; + representation += attribute + "=" + value; } - representation += ' )'; + representation += " )"; return representation; } - /** * Set the value of an attribute. * @@ -149,31 +165,30 @@

    Source: util/PsychObject.js

    _setAttribute(attributeName, attributeValue, log = false, operation = undefined, stealth = false) { const response = { - origin: 'PsychObject.setAttribute', - context: 'when setting the attribute of an object' + origin: "PsychObject.setAttribute", + context: "when setting the attribute of an object", }; - if (typeof attributeName == 'undefined') + if (typeof attributeName == "undefined") { throw Object.assign(response, { - error: 'the attribute name cannot be' + - ' undefined' + error: "the attribute name cannot be" + + " undefined", }); } - if (typeof attributeValue == 'undefined') + if (typeof attributeValue == "undefined") { - this._psychoJS.logger.warn('setting the value of attribute: ' + attributeName + ' in PsychObject: ' + this._name + ' as: undefined'); + this._psychoJS.logger.warn("setting the value of attribute: " + attributeName + " in PsychObject: " + this._name + " as: undefined"); } // (*) apply operation to old and new values: - if (typeof operation !== 'undefined' && this.hasOwnProperty('_' + attributeName)) + if (typeof operation !== "undefined" && this.hasOwnProperty("_" + attributeName)) { - let oldValue = this['_' + attributeName]; + let oldValue = this["_" + attributeName]; // operations can only be applied to numbers and array of numbers (which can be empty): - if (typeof attributeValue == 'number' || (Array.isArray(attributeValue) && (attributeValue.length === 0 || typeof attributeValue[0] == 'number'))) + if (typeof attributeValue == "number" || (Array.isArray(attributeValue) && (attributeValue.length === 0 || typeof attributeValue[0] == "number"))) { - // value is an array: if (Array.isArray(attributeValue)) { @@ -183,160 +198,158 @@

    Source: util/PsychObject.js

    if (attributeValue.length !== oldValue.length) { throw Object.assign(response, { - error: 'old and new' + - ' value should have' + - ' the same size when they are both arrays' + error: "old and new" + + " value should have" + + " the same size when they are both arrays", }); } switch (operation) { - case '': + case "": // no change to value; break; - case '+': + case "+": attributeValue = attributeValue.map((v, i) => oldValue[i] + v); break; - case '*': + case "*": attributeValue = attributeValue.map((v, i) => oldValue[i] * v); break; - case '-': + case "-": attributeValue = attributeValue.map((v, i) => oldValue[i] - v); break; - case '/': + case "/": attributeValue = attributeValue.map((v, i) => oldValue[i] / v); break; - case '**': + case "**": attributeValue = attributeValue.map((v, i) => oldValue[i] ** v); break; - case '%': + case "%": attributeValue = attributeValue.map((v, i) => oldValue[i] % v); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } - } - else // old value is a scalar + else { switch (operation) { - case '': + case "": // no change to value; break; - case '+': - attributeValue = attributeValue.map(v => oldValue + v); + case "+": + attributeValue = attributeValue.map((v) => oldValue + v); break; - case '*': - attributeValue = attributeValue.map(v => oldValue * v); + case "*": + attributeValue = attributeValue.map((v) => oldValue * v); break; - case '-': - attributeValue = attributeValue.map(v => oldValue - v); + case "-": + attributeValue = attributeValue.map((v) => oldValue - v); break; - case '/': - attributeValue = attributeValue.map(v => oldValue / v); + case "/": + attributeValue = attributeValue.map((v) => oldValue / v); break; - case '**': - attributeValue = attributeValue.map(v => oldValue ** v); + case "**": + attributeValue = attributeValue.map((v) => oldValue ** v); break; - case '%': - attributeValue = attributeValue.map(v => oldValue % v); + case "%": + attributeValue = attributeValue.map((v) => oldValue % v); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' value: ' + JSON.stringify(attributeValue) + ' for' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " value: " + JSON.stringify(attributeValue) + " for" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } } } - else // value is a scalar + else { // old value is an array if (Array.isArray(oldValue)) { switch (operation) { - case '': - attributeValue = oldValue.map(v => attributeValue); + case "": + attributeValue = oldValue.map((v) => attributeValue); break; - case '+': - attributeValue = oldValue.map(v => v + attributeValue); + case "+": + attributeValue = oldValue.map((v) => v + attributeValue); break; - case '*': - attributeValue = oldValue.map(v => v * attributeValue); + case "*": + attributeValue = oldValue.map((v) => v * attributeValue); break; - case '-': - attributeValue = oldValue.map(v => v - attributeValue); + case "-": + attributeValue = oldValue.map((v) => v - attributeValue); break; - case '/': - attributeValue = oldValue.map(v => v / attributeValue); + case "/": + attributeValue = oldValue.map((v) => v / attributeValue); break; - case '**': - attributeValue = oldValue.map(v => v ** attributeValue); + case "**": + attributeValue = oldValue.map((v) => v ** attributeValue); break; - case '%': - attributeValue = oldValue.map(v => v % attributeValue); + case "%": + attributeValue = oldValue.map((v) => v % attributeValue); break; default: throw Object.assign(response, { - error: 'unsupported' + - ' operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } - } - else // old value is a scalar + else { switch (operation) { - case '': + case "": // no change to value; break; - case '+': + case "+": attributeValue = oldValue + attributeValue; break; - case '*': + case "*": attributeValue = oldValue * attributeValue; break; - case '-': + case "-": attributeValue = oldValue - attributeValue; break; - case '/': + case "/": attributeValue = oldValue / attributeValue; break; - case '**': + case "**": attributeValue = oldValue ** attributeValue; break; - case '%': + case "%": attributeValue = oldValue % attributeValue; break; default: throw Object.assign(response, { - error: 'unsupported' + - ' value: ' + JSON.stringify(attributeValue) + ' for operation: ' + operation + ' when setting: ' + attributeName + ' in: ' + this.name + error: "unsupported" + + " value: " + JSON.stringify(attributeValue) + " for operation: " + operation + " when setting: " + attributeName + " in: " + this.name, }); } } } - } else { - throw Object.assign(response, {error: 'operation: ' + operation + ' is invalid for old value: ' + JSON.stringify(oldValue) + ' and new value: ' + JSON.stringify(attributeValue)}); + throw Object.assign(response, { + error: "operation: " + operation + " is invalid for old value: " + JSON.stringify(oldValue) + " and new value: " + JSON.stringify(attributeValue), + }); } } - // (*) log if appropriate: - if (!stealth && (log || this._autoLog) && (typeof this.win !== 'undefined')) + if (!stealth && (log || this._autoLog) && (typeof this.win !== "undefined")) { const msg = this.name + ": " + attributeName + " = " + util.toString(attributeValue); this.win.logOnFlip({ @@ -345,13 +358,12 @@

    Source: util/PsychObject.js

    }); } - // (*) set the value of the attribute and return whether it has changed: - const previousAttributeValue = this['_' + attributeName]; - this['_' + attributeName] = attributeValue; + const previousAttributeValue = this["_" + attributeName]; + this["_" + attributeName] = attributeValue; // Things seem OK without this check except for 'vertices' - if (typeof previousAttributeValue === 'undefined') + if (typeof previousAttributeValue === "undefined") { // Not that any of the following lines should throw, but evaluating // `this._vertices.map` on `ShapeStim._getVertices_px()` seems to @@ -370,10 +382,9 @@

    Source: util/PsychObject.js

    // `Util.toString()` might try, but fail to stringify in a meaningful way are assigned // an 'Object (circular)' string representation. For being opaque as to their raw // value, those types of input are liable to produce PIXI updates. - return prev === 'Object (circular)' || next === 'Object (circular)' || prev !== next; + return prev === "Object (circular)" || next === "Object (circular)" || prev !== next; } - /** * Add an attribute to this instance (e.g. define setters and getters) and affect a value to it. * @@ -383,20 +394,21 @@

    Source: util/PsychObject.js

    * @param {object} [defaultValue] - the default value for the attribute * @param {function} [onChange] - function called upon changes to the attribute value */ - _addAttribute(name, value, defaultValue = undefined, onChange = () => {}) + _addAttribute(name, value, defaultValue = undefined, onChange = () => + {}) { - const getPropertyName = 'get' + name[0].toUpperCase() + name.substr(1); - if (typeof this[getPropertyName] === 'undefined') + const getPropertyName = "get" + name[0].toUpperCase() + name.substr(1); + if (typeof this[getPropertyName] === "undefined") { - this[getPropertyName] = () => this['_' + name]; + this[getPropertyName] = () => this["_" + name]; } - const setPropertyName = 'set' + name[0].toUpperCase() + name.substr(1); - if (typeof this[setPropertyName] === 'undefined') + const setPropertyName = "set" + name[0].toUpperCase() + name.substr(1); + if (typeof this[setPropertyName] === "undefined") { this[setPropertyName] = (value, log = false) => { - if (typeof value === 'undefined' || value === null) + if (typeof value === "undefined" || value === null) { value = defaultValue; } @@ -410,7 +422,7 @@

    Source: util/PsychObject.js

    else { // deal with default value: - if (typeof value === 'undefined' || value === null) + if (typeof value === "undefined" || value === null) { value = defaultValue; } @@ -425,18 +437,16 @@

    Source: util/PsychObject.js

    set(value) { this[setPropertyName](value); - } + }, }); - // note: we use this[name] instead of this['_' + name] since a this.set<Name> method may available // in the object, in which case we need to call it this[name] = value; - //this['_' + name] = value; + // this['_' + name] = value; this._userAttributes.add(name); } - } @@ -445,19 +455,23 @@

    Source: util/PsychObject.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_Scheduler.js.html b/docs/util_Scheduler.js.html index 3351df52..d90dd1b9 100644 --- a/docs/util_Scheduler.js.html +++ b/docs/util_Scheduler.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/Scheduler.js - - - + util/Scheduler.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: util/Scheduler.js

    + + + + +
    + +

    util/Scheduler.js

    + @@ -30,18 +54,17 @@

    Source: util/Scheduler.js

    * Scheduler. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - /** * <p>A scheduler helps run the main loop by managing scheduled functions, * called tasks, after each frame is displayed.</p> * * <p> - * Tasks are either another [Scheduler]{@link module:util.Scheduler}, or a + * Tasks are either another [Scheduler]{@link Scheduler}, or a * JavaScript functions returning one of the following codes: * <ul> * <li>Scheduler.Event.NEXT: Move onto the next task *without* rendering the scene first.</li> @@ -53,19 +76,17 @@

    Source: util/Scheduler.js

    * * <p> It is possible to create sub-schedulers, e.g. to handle loops. * Sub-schedulers are added to a parent scheduler as a normal - * task would be by calling [scheduler.add(subScheduler)]{@link module:util.Scheduler#add}.</p> + * task would be by calling [scheduler.add(subScheduler)]{@link Scheduler#add}.</p> * * <p> Conditional branching is also available: - * [scheduler.addConditionalBranches]{@link module:util.Scheduler#addConditional}</p> - * - * - * @name module:util.Scheduler - * @class - * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance - * + * [scheduler.addConditionalBranches]{@link Scheduler#addConditional}</p> */ export class Scheduler { + /** + * @memberof module:util + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { this._psychoJS = psychoJS; @@ -81,32 +102,26 @@

    Source: util/Scheduler.js

    this._status = Scheduler.Status.STOPPED; } - /** * Get the status of the scheduler. * - * @name module:util.Scheduler#status - * @public - * @returns {module:util.Scheduler#Status} the status of the scheduler + * @returns {Scheduler#Status} the status of the scheduler */ get status() { return this._status; } - /** * Task to be run by the scheduler. * - * @callback module:util.Scheduler~Task + * @callback Scheduler~Task * @param {*} [args] optional arguments */ /** * Schedule a new task. * - * @name module:util.Scheduler#add - * @public - * @param {module:util.Scheduler~Task | module:util.Scheduler} task - the task to be scheduled + * @param {Scheduler~Task | Scheduler} task - the task to be scheduled * @param {...*} args - arguments for that task */ add(task, ...args) @@ -115,28 +130,25 @@

    Source: util/Scheduler.js

    this._argsList.push(args); } - /** * Condition evaluated when the task is run. * - * @callback module:util.Scheduler~Condition + * @callback Scheduler~Condition * @return {boolean} */ /** * Schedule a series of task or another, based on a condition. * - * <p>Note: the tasks are [sub-schedulers]{@link module:util.Scheduler}.</p> + * <p>Note: the tasks are [sub-schedulers]{@link Scheduler}.</p> * - * @name module:util.Scheduler#addConditional - * @public - * @param {module:util.Scheduler~Condition} condition - the condition - * @param {module:util.Scheduler} thenScheduler - the [Scheduler]{@link module:util.Scheduler} to be run if the condition is satisfied - * @param {module:util.Scheduler} elseScheduler - the [Scheduler]{@link module:util.Scheduler} to be run if the condition is not satisfied + * @param {Scheduler~Condition} condition - the condition + * @param {Scheduler} thenScheduler - the [Scheduler]{@link Scheduler} to be run if the condition is satisfied + * @param {Scheduler} elseScheduler - the [Scheduler]{@link Scheduler} to be run if the condition is not satisfied */ addConditional(condition, thenScheduler, elseScheduler) { const self = this; - let task = function () + let task = function() { if (condition()) { @@ -153,14 +165,10 @@

    Source: util/Scheduler.js

    this.add(task); } - /** * Start this scheduler. * * <p>Note: tasks are run after each animation frame.</p> - * - * @name module:util.Scheduler#start - * @public */ async start() { @@ -201,12 +209,8 @@

    Source: util/Scheduler.js

    requestAnimationFrame(update); } - /** * Stop this scheduler. - * - * @name module:util.Scheduler#stop - * @public */ stop() { @@ -215,13 +219,12 @@

    Source: util/Scheduler.js

    this._stopAtNextUpdate = true; } - /** * Run the next scheduled tasks, in sequence, until a rendering of the scene is requested. * - * @name module:util.Scheduler#_runNextTasks + * @name Scheduler#_runNextTasks * @private - * @return {module:util.Scheduler#Event} the state of the scheduler after the last task ran + * @return {Scheduler#Event} the state of the scheduler after the last task ran */ async _runNextTasks() { @@ -237,9 +240,8 @@

    Source: util/Scheduler.js

    } // if there is no current task, we look for the next one in the list or quit if there is none: - if (typeof this._currentTask == 'undefined') + if (typeof this._currentTask == "undefined") { - // a task is available in the taskList: if (this._taskList.length > 0) { @@ -287,64 +289,56 @@

    Source: util/Scheduler.js

    this._currentTask = undefined; this._currentArgs = undefined; } - } return state; } - } - /** * Events. * - * @name module:util.Scheduler#Event * @enum {Symbol} * @readonly - * @public */ Scheduler.Event = { /** * Move onto the next task *without* rendering the scene first. */ - NEXT: Symbol.for('NEXT'), + NEXT: Symbol.for("NEXT"), /** * Render the scene and repeat the task. */ - FLIP_REPEAT: Symbol.for('FLIP_REPEAT'), + FLIP_REPEAT: Symbol.for("FLIP_REPEAT"), /** * Render the scene and move onto the next task. */ - FLIP_NEXT: Symbol.for('FLIP_NEXT'), + FLIP_NEXT: Symbol.for("FLIP_NEXT"), /** * Quit the scheduler. */ - QUIT: Symbol.for('QUIT') + QUIT: Symbol.for("QUIT"), }; - /** * Status. * - * @name module:util.Scheduler#Status * @enum {Symbol} * @readonly - * @public */ Scheduler.Status = { /** * The Scheduler is running. */ - RUNNING: Symbol.for('RUNNING'), + RUNNING: Symbol.for("RUNNING"), /** * The Scheduler is stopped. */ - STOPPED: Symbol.for('STOPPED') + STOPPED: Symbol.for("STOPPED"), }; @@ -353,19 +347,23 @@

    Source: util/Scheduler.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/util_Util.js.html b/docs/util_Util.js.html index ec8ea611..8331df3a 100644 --- a/docs/util_Util.js.html +++ b/docs/util_Util.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: util/Util.js - - - + util/Util.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + -

    Source: util/Util.js

    + + +
    + +

    util/Util.js

    + @@ -26,25 +50,21 @@

    Source: util/Util.js

    -
    /**
    +            
    /** @module util */
    +/**
      * Various utilities.
      *
      * @authors Alain Pitiot, Sotiri Bakagiannis, Thomas Pronk
    - * @version 2021.2.0
    - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @version 2022.2.3
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
      * @license Distributed under the terms of the MIT License
      */
     
    -import * as PIXI from 'pixi.js-legacy';
    -
    -
     /**
      * Syntactic sugar for Mixins
      *
      * <p>This is heavily adapted from: http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/</p>
      *
    - * @name module:util.MixinBuilder
    - * @class
      * @param {Object} superclass
      *
      * @example
    @@ -63,7 +83,6 @@ 

    Source: util/Util.js

    } /** - * * @param mixins * @returns {*} */ @@ -73,13 +92,9 @@

    Source: util/Util.js

    } } - /** * Convert the resulting value of a promise into a tupple. * - * @name module:util.promiseToTupple - * @function - * @public * @param {Promise} promise - the promise * @return {Object[]} the resulting value in the format [error, return data] * where error is null if there was no error @@ -87,43 +102,35 @@

    Source: util/Util.js

    export function promiseToTupple(promise) { return promise - .then(data => [null, data]) - .catch(error => [error, null]); + .then((data) => [null, data]) + .catch((error) => [error, null]); } - /** * Get a Universally Unique Identifier (RFC4122 version 4) * <p> See details here: https://www.ietf.org/rfc/rfc4122.txt</p> * - * @name module:util.makeUuid - * @function - * @public * @return {string} the uuid */ export function makeUuid() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - const r = Math.random() * 16 | 0, v = (c === 'x') ? r : (r & 0x3 | 0x8); + const r = Math.random() * 16 | 0, v = (c === "x") ? r : (r & 0x3 | 0x8); return v.toString(16); }); } - /** * Get the error stack of the calling, exception-throwing function. * - * @name module:util.getErrorStack - * @function - * @public * @return {string} the error stack as a string */ export function getErrorStack() { try { - throw Error(''); + throw Error(""); } catch (error) { @@ -131,23 +138,19 @@

    Source: util/Util.js

    let stack = error.stack.split("\n"); stack.splice(1, 1); - return JSON.stringify(stack.join('\n')); + return JSON.stringify(stack.join("\n")); } } - /** * Test if x is an 'empty' value. * - * @name module:util.isEmpty - * @function - * @public * @param {Object} x the value to test * @return {boolean} true if x is one of the following: undefined, [], [undefined] */ export function isEmpty(x) { - if (typeof x === 'undefined') + if (typeof x === "undefined") { return true; } @@ -159,7 +162,7 @@

    Source: util/Util.js

    { return true; } - if (x.length === 1 && typeof x[0] === 'undefined') + if (x.length === 1 && typeof x[0] === "undefined") { return true; } @@ -167,87 +170,82 @@

    Source: util/Util.js

    return false; } - /** * Detect the user's browser. * * <p> Note: since user agent is easily spoofed, we use a more sophisticated approach, as described here: * https://stackoverflow.com/a/9851769</p> * - * @name module:util.detectBrowser - * @function - * @public * @return {string} the detected browser, one of 'Opera', 'Firefox', 'Safari', * 'IE', 'Edge', 'EdgeChromium', 'Chrome', 'unknown' */ export function detectBrowser() { // Opera 8.0+ - const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + const isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(" OPR/") >= 0; if (isOpera) { - return 'Opera'; + return "Opera"; } // Firefox 1.0+ - const isFirefox = (typeof InstallTrigger !== 'undefined'); + const isFirefox = (typeof InstallTrigger !== "undefined"); if (isFirefox) { - return 'Firefox'; + return "Firefox"; } - // Safari 3.0+ "[object HTMLElementConstructor]" - const isSafari = /constructor/i.test(window.HTMLElement) || (function (p) + // Safari 3.0+ "[object HTMLElementConstructor]" + const isSafari = /constructor/i.test(window.HTMLElement) || (function(p) { return p.toString() === "[object SafariRemoteNotification]"; - })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)); + })(!window["safari"] || (typeof safari !== "undefined" && safari.pushNotification)); if (isSafari) { - return 'Safari'; + return "Safari"; } // Internet Explorer 6-11 // const isIE6 = !window.XMLHttpRequest; // const isIE7 = document.all && window.XMLHttpRequest && !XDomainRequest && !window.opera; // const isIE8 = document.documentMode==8; - const isIE = /*@cc_on!@*/false || !!document.documentMode; + const isIE = /*@cc_on!@*/ false || !!document.documentMode; if (isIE) { - return 'IE'; + return "IE"; } // Edge 20+ const isEdge = !isIE && !!window.StyleMedia; if (isEdge) { - return 'Edge'; + return "Edge"; } // Chrome 1+ const isChrome = window.chrome; if (isChrome) { - return 'Chrome'; + return "Chrome"; } // Chromium-based Edge: const isEdgeChromium = isChrome && (navigator.userAgent.indexOf("Edg") !== -1); if (isEdgeChromium) { - return 'EdgeChromium'; + return "EdgeChromium"; } // Blink engine detection const isBlink = (isChrome || isOpera) && !!window.CSS; if (isBlink) { - return 'Blink'; + return "Blink"; } - return 'unknown'; + return "unknown"; } - /** * Convert obj to its numerical form. * @@ -258,33 +256,29 @@

    Source: util/Util.js

    * <li>[number | numeral string] -> [number], e.g. [1, 2, "3"] -> [1,2,3]</li> * </ul> * - * @name module:util.toNumerical - * @function - * @public * @param {Object} obj - the input object * @return {number | number[]} the numerical form of the input object */ export function toNumerical(obj) { const response = { - origin: 'util.toNumerical', - context: 'when converting an object to its numerical form' + origin: "util.toNumerical", + context: "when converting an object to its numerical form", }; try { - if (obj === null) { - throw 'unable to convert null to a number'; + throw "unable to convert null to a number"; } - if (typeof obj === 'undefined') + if (typeof obj === "undefined") { - throw 'unable to convert undefined to a number'; + throw "unable to convert undefined to a number"; } - if (typeof obj === 'number') + if (typeof obj === "number") { return obj; } @@ -313,28 +307,34 @@

    Source: util/Util.js

    return arrayMaybe.map(convertToNumber); } - if (typeof obj === 'string') + if (typeof obj === "string") { return convertToNumber(obj); } - throw 'unable to convert the object to a number'; + throw "unable to convert the object to a number"; } catch (error) { throw Object.assign(response, { error }); } - } +/** + * Check whether a value looks like a number + * + * @param {*} input - Some value + * @return {boolean} Whether or not the value can be converted into a number + */ +export function isNumeric(input) +{ + return Number.isNaN(Number(input)) === false; +} /** * Check whether a point lies within a polygon * <p>We are using the algorithm described here: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html</p> * - * @name module:util.IsPointInsidePolygon - * @function - * @public * @param {number[]} point - the point * @param {Object} vertices - the vertices defining the polygon * @return {boolean} whether or not the point lies within the polygon @@ -359,21 +359,18 @@

    Source: util/Util.js

    return isInside; } - /** * Shuffle an array in place using the Fisher-Yastes's modern algorithm * <p>See details here: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm</p> * - * @name module:util.shuffle - * @function - * @public * @param {Object[]} array - the input 1-D array * @param {Function} [randomNumberGenerator = undefined] - A function used to generated random numbers in the interal [0, 1). Defaults to Math.random * @return {Object[]} the shuffled array */ export function shuffle(array, randomNumberGenerator = undefined) { - if (randomNumberGenerator === undefined) { + if (randomNumberGenerator === undefined) + { randomNumberGenerator = Math.random; } for (let i = array.length - 1; i > 0; i--) @@ -384,14 +381,26 @@

    Source: util/Util.js

    return array; } - +/** + * Pick a random value from an array, uses `util.shuffle` to shuffle the array and returns the last value. + * + * @param {Object[]} array - the input 1-D array + * @param {Function} [randomNumberGenerator = undefined] - A function used to generated random numbers in the interal [0, 1). Defaults to Math.random + * @return {Object[]} a chosen value from the array + */ +export function randchoice(array, randomNumberGenerator = undefined) +{ + if (randomNumberGenerator === undefined) + { + randomNumberGenerator = Math.random; + } + const j = Math.floor(randomNumberGenerator() * array.length); + return array[j] +} /** * Get the position of the object, in pixel units * - * @name module:util.getPositionFromObject - * @function - * @public * @param {Object} object - the input object * @param {string} units - the units * @returns {number[]} the position of the object, in pixel units @@ -399,21 +408,21 @@

    Source: util/Util.js

    export function getPositionFromObject(object, units) { const response = { - origin: 'util.getPositionFromObject', - context: 'when getting the position of an object' + origin: "util.getPositionFromObject", + context: "when getting the position of an object", }; try { - if (typeof object === 'undefined') + if (typeof object === "undefined") { - throw 'cannot get the position of an undefined object'; + throw "cannot get the position of an undefined object"; } let objectWin = undefined; // the object has a getPos function: - if (typeof object.getPos === 'function') + if (typeof object.getPos === "function") { units = object.units; objectWin = object.win; @@ -425,18 +434,13 @@

    Source: util/Util.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Convert the position to pixel units. * - * @name module:util.to_px - * @function - * @public * @param {number[]} pos - the input position * @param {string} posUnit - the position units * @param {Window} win - the associated Window @@ -446,28 +450,28 @@

    Source: util/Util.js

    export function to_px(pos, posUnit, win, integerCoordinates = false) { const response = { - origin: 'util.to_px', - context: 'when converting a position to pixel units' + origin: "util.to_px", + context: "when converting a position to pixel units", }; let pos_px; - if (posUnit === 'pix') + if (posUnit === "pix") { pos_px = pos; } - else if (posUnit === 'norm') + else if (posUnit === "norm") { pos_px = [pos[0] * win.size[0] / 2.0, pos[1] * win.size[1] / 2.0]; } - else if (posUnit === 'height') + else if (posUnit === "height") { const minSize = Math.min(win.size[0], win.size[1]); pos_px = [pos[0] * minSize, pos[1] * minSize]; } else { - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } if (integerCoordinates) @@ -480,13 +484,9 @@

    Source: util/Util.js

    } } - /** * Convert the position to norm units. * - * @name module:util.to_norm - * @function - * @public * @param {number[]} pos - the input position * @param {string} posUnit - the position units * @param {Window} win - the associated Window @@ -494,32 +494,28 @@

    Source: util/Util.js

    */ export function to_norm(pos, posUnit, win) { - const response = {origin: 'util.to_norm', context: 'when converting a position to norm units'}; + const response = { origin: "util.to_norm", context: "when converting a position to norm units" }; - if (posUnit === 'norm') + if (posUnit === "norm") { return pos; } - if (posUnit === 'pix') + if (posUnit === "pix") { return [pos[0] / (win.size[0] / 2.0), pos[1] / (win.size[1] / 2.0)]; } - if (posUnit === 'height') + if (posUnit === "height") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] * minSize / (win.size[0] / 2.0), pos[1] * minSize / (win.size[1] / 2.0)]; } - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } - /** * Convert the position to height units. * - * @name module:util.to_height - * @function - * @public * @param {number[]} pos - the input position * @param {string} posUnit - the position units * @param {Window} win - the associated Window @@ -528,35 +524,31 @@

    Source: util/Util.js

    export function to_height(pos, posUnit, win) { const response = { - origin: 'util.to_height', - context: 'when converting a position to height units' + origin: "util.to_height", + context: "when converting a position to height units", }; - if (posUnit === 'height') + if (posUnit === "height") { return pos; } - if (posUnit === 'pix') + if (posUnit === "pix") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] / minSize, pos[1] / minSize]; } - if (posUnit === 'norm') + if (posUnit === "norm") { const minSize = Math.min(win.size[0], win.size[1]); return [pos[0] * win.size[0] / 2.0 / minSize, pos[1] * win.size[1] / 2.0 / minSize]; } - throw Object.assign(response, {error: `unknown position units: ${posUnit}`}); + throw Object.assign(response, { error: `unknown position units: ${posUnit}` }); } - /** * Convert the position to window units. * - * @name module:util.to_win - * @function - * @public * @param {number[]} pos - the input position * @param {string} posUnit - the position units * @param {Window} win - the associated Window @@ -564,19 +556,19 @@

    Source: util/Util.js

    */ export function to_win(pos, posUnit, win) { - const response = {origin: 'util.to_win', context: 'when converting a position to window units'}; + const response = { origin: "util.to_win", context: "when converting a position to window units" }; try { - if (win._units === 'pix') + if (win._units === "pix") { return to_px(pos, posUnit, win); } - if (win._units === 'norm') + if (win._units === "norm") { return to_norm(pos, posUnit, win); } - if (win._units === 'height') + if (win._units === "height") { return to_height(pos, posUnit, win); } @@ -585,17 +577,13 @@

    Source: util/Util.js

    } catch (error) { - throw Object.assign(response, {response, error}); + throw Object.assign(response, { response, error }); } } - /** * Convert the position to given units. * - * @name module:util.to_unit - * @function - * @public * @param {number[]} pos - the input position * @param {string} posUnit - the position units * @param {Window} win - the associated Window @@ -604,19 +592,19 @@

    Source: util/Util.js

    */ export function to_unit(pos, posUnit, win, targetUnit) { - const response = {origin: 'util.to_unit', context: 'when converting a position to different units'}; + const response = { origin: "util.to_unit", context: "when converting a position to different units" }; try { - if (targetUnit === 'pix') + if (targetUnit === "pix") { return to_px(pos, posUnit, win); } - if (targetUnit === 'norm') + if (targetUnit === "norm") { return to_norm(pos, posUnit, win); } - if (targetUnit === 'height') + if (targetUnit === "height") { return to_height(pos, posUnit, win); } @@ -625,67 +613,37 @@

    Source: util/Util.js

    } catch (error) { - throw Object.assign(response, {error}); - } -} - - -/** - * Convert a position to a PIXI Point. - * - * @name module:util.to_pixiPoint - * @function - * @public - * @param {number[]} pos - the input position - * @param {string} posUnit - the position units - * @param {Window} win - the associated Window - * @param {boolean} [integerCoordinates = false] - whether or not to round the PIXI Point coordinates. - * @returns {number[]} the position as a PIXI Point - */ -export function to_pixiPoint(pos, posUnit, win, integerCoordinates = false) -{ - const pos_px = to_px(pos, posUnit, win); - if (integerCoordinates) - { - return new PIXI.Point(Math.round(pos_px[0]), Math.round(pos_px[1])); - } - else - { - return new PIXI.Point(pos_px[0], pos_px[1]); + throw Object.assign(response, { error }); } } - /** * Convert an object to its string representation, taking care of symbols. * * <p>Note: if the object is not already a string, we JSON stringify it and detect circularity.</p> * - * @name module:util.toString - * @function - * @public * @param {Object} object - the input object * @return {string} a string representation of the object or 'Object (circular)' */ export function toString(object) { - if (typeof object === 'undefined') + if (typeof object === "undefined") { - return 'undefined'; + return "undefined"; } if (!object) { - return 'null'; + return "null"; } - if (typeof object === 'string') + if (typeof object === "string") { return object; } // if the object is a class and has a toString method: - if (object.constructor.toString().substring(0, 5) === 'class' && typeof object.toString === 'function') + if (object.constructor.toString().substring(0, 5) === "class" && typeof object.toString === "function") { return object.toString(); } @@ -694,7 +652,7 @@

    Source: util/Util.js

    { const symbolReplacer = (key, value) => { - if (typeof value === 'symbol') + if (typeof value === "symbol") { value = Symbol.keyFor(value); } @@ -704,53 +662,48 @@

    Source: util/Util.js

    } catch (e) { - return 'Object (circular)'; + return "Object (circular)"; } } - if (!String.prototype.format) { - String.prototype.format = function () + String.prototype.format = function() { var args = arguments; return this - .replace(/{(\d+)}/g, function (match, number) + .replace(/{(\d+)}/g, function(match, number) { - return typeof args[number] != 'undefined' ? args[number] : match; + return typeof args[number] != "undefined" ? args[number] : match; }) - .replace(/{([$_a-zA-Z][$_a-zA-Z0-9]*)}/g, function (match, name) + .replace(/{([$_a-zA-Z][$_a-zA-Z0-9]*)}/g, function(match, name) { - //console.log("n=" + name + " args[0][name]=" + args[0][name]); + // console.log("n=" + name + " args[0][name]=" + args[0][name]); return args.length > 0 && args[0][name] !== undefined ? args[0][name] : match; }); }; } - /** * Get the most informative error from the server response from a jquery server request. * - * @name module:util.getRequestError - * @function - * @public * @param jqXHR * @param textStatus * @param errorThrown */ export function getRequestError(jqXHR, textStatus, errorThrown) { - let errorMsg = 'unknown error'; + let errorMsg = "unknown error"; - if (typeof jqXHR.responseJSON !== 'undefined') + if (typeof jqXHR.responseJSON !== "undefined") { errorMsg = jqXHR.responseJSON; } - else if (typeof jqXHR.responseText !== 'undefined') + else if (typeof jqXHR.responseText !== "undefined") { errorMsg = jqXHR.responseText; } - else if (typeof errorThrown !== 'undefined') + else if (typeof errorThrown !== "undefined") { errorMsg = errorThrown; } @@ -758,14 +711,10 @@

    Source: util/Util.js

    return errorMsg; } - /** * Test whether an object is either an integer or the string representation of an integer. * <p>This is adapted from: https://stackoverflow.com/a/14794066</p> * - * @name module:util.isInt - * @function - * @public * @param {Object} obj - the input object * @returns {boolean} whether or not the object is an integer or the string representation of an integer */ @@ -780,13 +729,9 @@

    Source: util/Util.js

    return (x | 0) === x; } - /** * Get the URL parameters. * - * @name module:util.getUrlParameters - * @function - * @public * @returns {URLSearchParams} the iterable URLSearchParams * * @example @@ -807,16 +752,12 @@

    Source: util/Util.js

    return urlMap;*/ } - /** * Add info extracted from the URL to the given dictionary. * * <p>We exclude all URL parameters starting with a double underscore * since those are reserved for client/server communication</p> * - * @name module:util.addInfoFromUrl - * @function - * @public * @param {Object} info - the dictionary */ export function addInfoFromUrl(info) @@ -828,7 +769,7 @@

    Source: util/Util.js

    // for (const [key, value] of infoFromUrl) infoFromUrl.forEach((value, key) => { - if (key.indexOf('__') !== 0) + if (key.indexOf("__") !== 0) { info[key] = value; } @@ -837,7 +778,6 @@

    Source: util/Util.js

    return info; } - /** * Select values from an array. * @@ -851,37 +791,38 @@

    Source: util/Util.js

    * <li>'-5:-2, 9, 11:5:22'</li> * </ul></p> * - * @name module:util.selectFromArray - * @function - * @public * @param {Array.<Object>} array - the input array * @param {number | Array.<number> | string} selection - the selection * @returns {Object | Array.<Object>} the array of selected items */ export function selectFromArray(array, selection) { - - // if selection is an integer, or a string representing an integer, we treat it as an index in the array - // and return that entry: + // if selection is an integer, or a string representing an integer, we treat it + // as an index in the array and return that entry: if (isInt(selection)) { return [array[parseInt(selection)]]; - }// if selection is an array, we treat it as a list of indices + } + + // if selection is an array, we treat it as a list of indices // and return an array with the entries corresponding to those indices: else if (Array.isArray(selection)) { - // Pick out `array` items matching indices contained in `selection` in order - return selection.map(i => array[i]); - }// if selection is a string, we decode it: - else if (typeof selection === 'string') + return selection.map( (i) => array[i] ); + } + + // if selection is a string: + else if (typeof selection === "string") { - if (selection.indexOf(',') > -1) + if (selection.indexOf(",") > -1) { - return selection.split(',').map(a => selectFromArray(array, a)); - }// return flattenArray( selection.split(',').map(a => selectFromArray(array, a)) ); - else if (selection.indexOf(':') > -1) + const selectionAsArray = selection.split(",").map( (i) => parseInt(i) ); + return selectFromArray(array, selectionAsArray); + } + + else if (selection.indexOf(":") > -1) { - let sliceParams = selection.split(':').map(a => parseInt(a)); + let sliceParams = selection.split(":").map((a) => parseInt(a)); if (sliceParams.length === 3) { return sliceArray(array, sliceParams[0], sliceParams[2], sliceParams[1]); @@ -892,24 +833,19 @@

    Source: util/Util.js

    } } } - else { throw { - origin: 'selectFromArray', - context: 'when selecting entries from an array', - error: 'unknown selection type: ' + (typeof selection) + origin: "selectFromArray", + context: "when selecting entries from an array", + error: "unknown selection type: " + (typeof selection), }; } } - /** * Recursively flatten an array of arrays. * - * @name module:util.flattenArray - * @function - * @public * @param {Array.<Object>} array - the input array of arrays * @returns {Array.<Object>} the flatten array */ @@ -921,17 +857,13 @@

    Source: util/Util.js

    flat.push((Array.isArray(next) && Array.isArray(next[0])) ? flattenArray(next) : next); return flat; }, - [] + [], ); } - /** * Slice an array. * - * @name module:util.sliceArray - * @function - * @public * @param {Array.<Object>} array - the input array * @param {number} [from= NaN] - the start of the slice * @param {number} [to= NaN] - the end of the slice @@ -972,27 +904,23 @@

    Source: util/Util.js

    } } - /** * Offer data as download in the browser. * - * @name module:util.offerDataForDownload - * @function - * @public * @param {string} filename - the name of the file to be downloaded * @param {*} data - the data * @param {string} type - the MIME type of the data, e.g. 'text/csv' or 'application/json' */ export function offerDataForDownload(filename, data, type) { - const blob = new Blob([data], {type}); + const blob = new Blob([data], { type }); if (window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveBlob(blob, filename); } else { - const anchor = document.createElement('a'); + const anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(blob); anchor.download = filename; document.body.appendChild(anchor); @@ -1001,15 +929,11 @@

    Source: util/Util.js

    } } - /** * Convert a string representing a JSON array, e.g. "[1, 2]" into an array, e.g. ["1","2"]. * This approach overcomes the built-in JSON parsing limitations when it comes to eg. floats * missing the naught prefix, and is able to process several arrays, e.g. "[1,2][3,4]". * - * @name module:util.turnSquareBracketsIntoArrays - * @function - * @public * @param {string} input - string potentially containing JSON arrays * @param {string} max - how many matches to return, unwrap resulting array if less than two * @returns {array} an array if arrays were found, undefined otherwise @@ -1035,14 +959,13 @@

    Source: util/Util.js

    // Reformat content for each match const matches = matchesMaybe.map((data) => - { - return data - // Remove the square brackets - .replace(/[\[\]]+/g, '') - // Eat up space after comma - .split(/[, ]+/); - } - ); + { + return data + // Remove the square brackets + .replace(/[\[\]]+/g, "") + // Eat up space after comma + .split(/[, ]+/); + }); if (max < 2) { @@ -1052,13 +975,9 @@

    Source: util/Util.js

    return matches; } - /** * Generates random integers a-la NumPy's in the "half-open" interval [min, max). In other words, from min inclusive to max exclusive. When max is undefined, as is the case by default, results are chosen from [0, min). An error is thrown if max is less than min. * - * @name module:util.randint - * @function - * @public * @param {number} [min = 0] - lowest integer to be drawn, or highest plus one if max is undefined (default) * @param {number} max - one above the largest integer to be drawn * @returns {number} a random integer in the requested range (signed) @@ -1068,7 +987,7 @@

    Source: util/Util.js

    let lo = min; let hi = max; - if (typeof max === 'undefined') + if (typeof max === "undefined") { hi = lo; lo = 0; @@ -1077,24 +996,20 @@

    Source: util/Util.js

    if (hi < lo) { throw { - origin: 'util.randint', - context: 'when generating a random integer', - error: 'min should be <= max' + origin: "util.randint", + context: "when generating a random integer", + error: "min should be <= max", }; } return Math.floor(Math.random() * (hi - lo)) + lo; } - /** * Round to a certain number of decimal places. * * This is the Crib Sheet provided solution, but please note that as of 2020 the most popular SO answer is different. * - * @name module:util.round - * @function - * @public * @see {@link https://stackoverflow.com/questions/11832914|Stack Overflow} * @param {number} input - the number to be rounded * @param {number} places - the max number of decimals desired @@ -1105,15 +1020,11 @@

    Source: util/Util.js

    return +(Math.round(`${input}e+${places}`) + `e-${places}`); } - /** * Calculate the sum of the elements in the input array. * * If 'input' is not an array, then we return start. * - * @name module:util.sum - * @function - * @public * @param {array} input - an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1] * @param {number} start - value added to the sum of numbers (a la Python) * @returns {number} the sum of the elements in the array + start @@ -1129,22 +1040,18 @@

    Source: util/Util.js

    return input // type cast everything as a number - .map(value => Number(value)) + .map((value) => Number(value)) // drop non numeric looking entries (note: needs transpiling for IE11) - .filter(value => Number.isNaN(value) === false) + .filter((value) => Number.isNaN(value) === false) // add up each successive entry, starting with start .reduce(add, start); } - /** * Calculate the average of the elements in the input array. * * If 'input' is not an array, or if it is an empty array, then we return 0. * - * @name module:util.average - * @function - * @public * @param {array} input - an array of numbers, or of objects that can be cast into a number, e.g. ['1', 2.5, 3e1] * @returns {number} the average of the elements in the array */ @@ -1163,13 +1070,9 @@

    Source: util/Util.js

    return sum(input, 0) / input.length; } - /** * Sort the elements of the input array, in increasing alphabetical or numerical order. - * - * @name module:util.sort - * @function - * @public + * * @param {array} input - an array of numbers or of strings * @return {array} the sorted array * @throws if 'input' is not an array, or if its elements are not consistent in types, or if they are not all either numbers or @@ -1178,49 +1081,45 @@

    Source: util/Util.js

    export function sort(input) { const response = { - origin: 'util.sort', - context: 'when sorting the elements of an array' + origin: "util.sort", + context: "when sorting the elements of an array", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } // check the type and consistency of the array, and sort it accordingly: - const isNumberArray = input.every(element => typeof element === "number"); + const isNumberArray = input.every((element) => typeof element === "number"); if (isNumberArray) { return input.sort((a, b) => (a - b)); } - const isStringArray = input.every(element => typeof element === "string"); + const isStringArray = input.every((element) => typeof element === "string"); if (isStringArray) { return input.sort(); } - - throw 'the input array should either consist entirely of strings or of numbers'; + + throw "the input array should either consist entirely of strings or of numbers"; } catch (error) { - throw {...response, error}; - } - } - - + throw { ...response, error }; + } +} + /** * Create a sequence of integers. - * + * * The sequence is such that the integer at index i is: start + step * i, with i >= 0 and start + step * i < stop - * + * * <p> Note: this is a JavaScript implement of the Python range function, which explains the unusual management of arguments.</p> * - * @name module:util.range - * @function - * @public * @param {Number} [start=0] - the value of start * @param {Number} stop - the value of stop * @param {Number} [step=1] - the value of step @@ -1229,8 +1128,8 @@

    Source: util/Util.js

    export function range(...args) { const response = { - origin: 'util.range', - context: 'when building a range of numbers' + origin: "util.range", + context: "when building a range of numbers", }; try @@ -1240,9 +1139,10 @@

    Source: util/Util.js

    switch (args.length) { case 0: - throw 'at least one argument is required'; + throw "at least one argument is required"; // 1 arg: start = 0, stop = arg, step = 1 + case 1: start = 0; stop = args[0]; @@ -1250,6 +1150,7 @@

    Source: util/Util.js

    break; // 2 args: start = arg1, stop = arg2 + case 2: start = args[0]; stop = args[1]; @@ -1257,6 +1158,7 @@

    Source: util/Util.js

    break; // 3 args: + case 3: start = args[0]; stop = args[1]; @@ -1264,17 +1166,20 @@

    Source: util/Util.js

    break; default: - throw 'range requires at least one and at most 3 arguments' + throw "range requires at least one and at most 3 arguments"; } - if (!Number.isInteger(start)) { - throw 'start should be an integer'; + if (!Number.isInteger(start)) + { + throw "start should be an integer"; } - if (!Number.isInteger(stop)) { - throw 'stop should be an integer'; + if (!Number.isInteger(stop)) + { + throw "stop should be an integer"; } - if (!Number.isInteger(step)) { - throw 'step should be an integer'; + if (!Number.isInteger(step)) + { + throw "step should be an integer"; } // if start >= stop, the range is empty: @@ -1292,33 +1197,30 @@

    Source: util/Util.js

    } catch (error) { - throw {...response, error}; + throw { ...response, error }; } } - /** * Create a boolean function that compares an input element to the given value. - * - * @name module:util._match - * @function - * @private + * + * @protected * @param {Number|string|object|null} value the matching value * @return {} a function that compares an input element to the given value */ function _match(value) { const response = { - origin: 'util._match', - context: 'when creating a function that compares an input element to the given value' + origin: "util._match", + context: "when creating a function that compares an input element to the given value", }; try { // function: - if (typeof value === 'function') + if (typeof value === "function") { - throw 'the value cannot be a function'; + throw "the value cannot be a function"; } // NaN: @@ -1334,19 +1236,19 @@

    Source: util/Util.js

    } // object: we compare using JSON.stringify - if (typeof value === 'object') + if (typeof value === "object") { const jsonValue = JSON.stringify(value); - if (typeof jsonValue === 'undefined') + if (typeof jsonValue === "undefined") { - throw 'value could not be converted to a JSON string'; + throw "value could not be converted to a JSON string"; } return (element) => { const jsonElement = JSON.stringify(element); return (jsonElement === jsonValue); - } + }; } // everything else: @@ -1354,81 +1256,94 @@

    Source: util/Util.js

    } catch (error) { - throw {...response, error}; - } - } - + throw { ...response, error }; + } +} - /** +/** * Count the number of elements in the input array that match the given value. - * + * * <p> Note: count is able to handle NaN, null, as well as any value convertible to a JSON string.</p> - * - * @name module:util.count - * @function - * @public + * * @param {array} input the input array * @param {Number|string|object|null} value the matching value * @returns the number of matching elements */ - export function count(input, value) - { +export function count(input, value) +{ const response = { - origin: 'util.count', - context: 'when counting how many elements in the input array match the given value' + origin: "util.count", + context: "when counting how many elements in the input array match the given value", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } const match = _match(value); let nbMatches = 0; - input.forEach(element => + input.forEach((element) => + { + if (match(element)) { - if (match(element)) - { - ++ nbMatches; - } - }); + ++nbMatches; + } + }); return nbMatches; } catch (error) { - throw {...response, error}; + throw { ...response, error }; + } +} + +/** + * Pad the given floating-point number with however many 0 needed at the start such that + * the padded integer part of the number is of the given width. + * + * @param n - the input floating-point number + * @param width - the desired width + * @returns {string} - the padded number, whose integer part has the given width + */ +export function pad(n, width = 2) +{ + const integerPart = Number.parseInt(n); + + let decimalPart = (n+'').match(/\.[0-9]*/); + if (!decimalPart) + { + decimalPart = ''; } - } - - /** + return (integerPart+'').padStart(width,'0') + decimalPart; +} + +/** * Get the index in the input array of the first element that matches the given value. - * + * * <p> Note: index is able to handle NaN, null, as well as any value convertible to a JSON string.</p> - * - * @name module:util.index - * @function - * @public + * * @param {array} input the input array * @param {Number|string|object|null} value the matching value * @returns the index of the first element that matches the value * @throws if the input array does not contain any matching element */ - export function index(input, value) - { +export function index(input, value) +{ const response = { - origin: 'util.index', - context: 'when getting the index in the input array of the first element that matches the given value' + origin: "util.index", + context: "when getting the index in the input array of the first element that matches the given value", }; try { if (!Array.isArray(input)) { - throw 'the input argument should be an array'; + throw "the input argument should be an array"; } const match = _match(value); @@ -1436,54 +1351,119 @@

    Source: util/Util.js

    if (index === -1) { - throw 'no element in the input array matches the value'; + throw "no element in the input array matches the value"; } return index; - } catch (error) { - throw {...response, error}; + throw { ...response, error }; } - } - +} /** * Return the file extension corresponding to an audio mime type. * If the provided mimeType is not a string (e.g. null, undefined, an array) * or unknown, then '.dat' is returned, instead of throwing an exception. * - * @name module:util.extensionFromMimeType - * @function - * @public * @param {string} mimeType the MIME type, e.g. 'audio/webm;codecs=opus' * @return {string} the corresponding file extension, e.g. '.webm' */ export function extensionFromMimeType(mimeType) { - if (typeof mimeType !== 'string') + if (typeof mimeType !== "string") { - return '.dat'; + return ".dat"; } - if (mimeType.indexOf('audio/webm') === 0) + if (mimeType.indexOf("audio/webm") === 0) { - return '.webm'; + return ".webm"; } - if (mimeType.indexOf('audio/ogg') === 0) + if (mimeType.indexOf("audio/ogg") === 0) { - return '.ogg'; + return ".ogg"; } - if (mimeType.indexOf('audio/wav') === 0) + if (mimeType.indexOf("audio/wav") === 0) { - return '.wav'; + return ".wav"; } - return '.dat'; + if (mimeType.indexOf("video/webm") === 0) + { + return ".webm"; + } + + return ".dat"; +} + +/** + * Get an estimate of the download speed, by repeatedly downloading an image file from a distant + * server. + * + * @param {PsychoJS} psychoJS the instance of PsychoJS + * @param {number} [nbDownloads = 1] the number of image downloads over which to average + * the download speed + * @return {number} the download speed, in megabits per second + */ +export async function getDownloadSpeed(psychoJS, nbDownloads = 1) +{ + // url of the image to download and size of the image in bits: + // TODO use a variety of files, with different sizes + const imageUrl = "https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg"; + const imageSize_b = 2707459 * 8; + + return new Promise( (resolve, reject) => + { + let downloadTimeAccumulator = 0; + let downloadCounter = 0; + + const download = new Image(); + download.onload = () => + { + const toc = performance.now(); + downloadTimeAccumulator += (toc-tic); + ++ downloadCounter; + + if (downloadCounter === nbDownloads) + { + const speed_bps = (imageSize_b * nbDownloads) / (downloadTimeAccumulator / 1000); + resolve(speed_bps / 1024 / 1024); + } + else + { + tic = performance.now(); + download.src = `${imageUrl}?salt=${tic}`; + } + } + + download.onerror = (event) => + { + const errorMsg = `unable to estimate the download speed: ${JSON.stringify(event)}`; + psychoJS.logger.error(errorMsg); + reject(errorMsg); + } + + let tic = performance.now(); + download.src = `${imageUrl}?salt=${tic}`; + }); } + +/** + * Enum that stores possible text directions. + * Note that Arabic is the same as RTL but added here to support PsychoPy's + * languageStyle enum. Arabic reshaping is handled by the browser automatically. + * + * @enum + */ +export const TEXT_DIRECTION = { + LTR: "ltr", + RTL: "rtl", + Arabic: "rtl" +};
    @@ -1491,19 +1471,23 @@

    Source: util/Util.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_ButtonStim.js.html b/docs/visual_ButtonStim.js.html index 76fa7cff..4ed56fdf 100644 --- a/docs/visual_ButtonStim.js.html +++ b/docs/visual_ButtonStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/ButtonStim.js - - - + visual/ButtonStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/ButtonStim.js

    + + + + +
    + +

    visual/ButtonStim.js

    + @@ -30,65 +54,112 @@

    Source: visual/ButtonStim.js

    * Button Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {TextBox} from './TextBox.js'; -import {Mouse} from '../core/Mouse.js'; - +import { Mouse } from "../core/Mouse.js"; +import { TextBox } from "./TextBox.js"; /** * <p>ButtonStim visual stimulus.</p> * - * @name module:visual.ButtonStim - * @class * @extends TextBox - * @param {Object} options - * @param {module:core.Window} options.win - the associated Window - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {string} [options.text=""] - the text to be rendered - * @param {string} [options.font= "Arial"] - the font family - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text - * @param {string} [options.anchor= "center"] - horizontal alignment - * @param {string} [options.units= "norm"] - the units of the text size and position - * @param {Color} [options.color= Color("white")] the background color - * @param {Color} [options.fillColor= Color("darkgrey")] the fill color - * @param {Color} [options.borderColor= Color("white")] the border color - * @param {Color} [options.borderWidth= 0] the border width - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.letterHeight= undefined] - the height of the text - * @param {boolean} [options.bold= true] - whether or not the text is bold - * @param {boolean} [options.italic= false] - whether or not the text is italic - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class ButtonStim extends TextBox { - constructor({win, name, text, font, pos, size, padding, anchor = 'center', units, color, fillColor = 'darkgrey', borderColor, borderWidth = 0, opacity, letterHeight, bold = true, italic, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {module:core.Window} options.win - the associated Window + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {string} [options.text=""] - the text to be rendered + * @param {string} [options.font= "Arial"] - the font family + * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text + * @param {string} [options.anchor= "center"] - horizontal alignment + * @param {string} [options.units= "norm"] - the units of the text size and position + * @param {Color} [options.color= Color("white")] the background color + * @param {Color} [options.fillColor= Color("darkgrey")] the fill color + * @param {Color} [options.borderColor= Color("white")] the border color + * @param {Color} [options.borderWidth= 0] the border width + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.letterHeight= undefined] - the height of the text + * @param {boolean} [options.bold= true] - whether or not the text is bold + * @param {boolean} [options.italic= false] - whether or not the text is italic + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor( + { + win, + name, + text, + font, + pos, + size, + padding, + anchor = "center", + units, + color, + fillColor = "darkgrey", + borderColor, + borderWidth = 0, + opacity, + letterHeight, + bold = true, + italic, + autoDraw, + autoLog, + } = {}, + ) { - super({win, name, text, font, pos, size, padding, anchor, units, color, fillColor, borderColor, borderWidth, opacity, letterHeight, bold, italic, alignment: 'center', autoDraw, autoLog}); - - this.psychoJS.logger.debug('create a new Button with name: ', name); - - this.listener = new Mouse({name, win, autoLog}); + super({ + win, + name, + text, + font, + pos, + size, + padding, + anchor, + units, + color, + fillColor, + borderColor, + borderWidth, + opacity, + letterHeight, + bold, + italic, + alignment: "center", + autoDraw, + autoLog, + }); + + this.psychoJS.logger.debug("create a new Button with name: ", name); + + this.listener = new Mouse({ name, win, autoLog }); this._addAttribute( - 'wasClicked', - false + "wasClicked", + false, ); // Arrays to store times of clicks on and off this._addAttribute( - 'timesOn', - [] + "timesOn", + [], + ); + + this._addAttribute( + "timesOff", + [], ); this._addAttribute( - 'timesOff', - [] + "numClicks", + 0, ); if (this._autoLog) @@ -97,12 +168,9 @@

    Source: visual/ButtonStim.js

    } } - - /** * How many times has this button been clicked on? * - * @name module:visual.ButtonStim#numClicks * @returns {number} the number of times the button has been clicked on */ get numClicks() @@ -110,19 +178,15 @@

    Source: visual/ButtonStim.js

    return this.timesOn.length; } - - /** * Is this button currently being clicked on? * - * @name module:visual.ButtonStim#isClicked * @returns {boolean} whether or not the button is being clicked on */ get isClicked() { return this.listener.isPressedIn(this, [1, 0, 0]); } - }
    @@ -131,19 +195,23 @@

    Source: visual/ButtonStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_FaceDetector.js.html b/docs/visual_FaceDetector.js.html new file mode 100644 index 00000000..e0815c98 --- /dev/null +++ b/docs/visual_FaceDetector.js.html @@ -0,0 +1,378 @@ + + + + + + visual/FaceDetector.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    visual/FaceDetector.js

    + + + + + + + +
    +
    +
    /**
    + * Manager handling the detecting of faces in video streams.
    + *
    + * @author Alain Pitiot
    + * @version 2022.2.3
    + * @copyright (c) 2021 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @license Distributed under the terms of the MIT License
    + */
    +
    +import {PsychoJS} from "../core/PsychoJS.js";
    +import * as util from "../util/Util.js";
    +import { to_pixiPoint } from "../util/Pixi.js";
    +import {Color} from "../util/Color.js";
    +import {Camera} from "../hardware/Camera.js";
    +import {VisualStim} from "./VisualStim.js";
    +import * as PIXI from "pixi.js-legacy";
    +
    +
    +/**
    + * <p>This manager handles the detecting of faces in video streams. FaceDetector relies on the
    + * [Face-API library]{@link https://github.com/justadudewhohacks/face-api.js} developed by
    + * [Vincent Muehler]{@link https://github.com/justadudewhohacks}.</p>
    + *
    + * @extends VisualStim
    + */
    +export class FaceDetector extends VisualStim
    +{
    +	/**
    +	 * @memberOf module:visual
    +	 * @param {Object} options
    +	 * @param {String} options.name - the name used when logging messages from the detector
    +	 * @param {module:core.Window} options.win - the associated Window
    +	 * @param {string | HTMLVideoElement | module:visual.Camera} options.input - the name of a
    +	 * 	movie resource or of a HTMLVideoElement or of a Camera component
    +	 * @param {string} [options.faceApiUrl= 'face-api.js'] - the Url of the face-api library
    +	 * @param {string} [options.modelDir= 'models'] - the directory where to find the face detection models
    +	 * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices)
    +	 * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
    +	 * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position
    +	 * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    +	 * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified)
    +	 * @param {number} [options.opacity= 1.0] - the opacity
    +	 * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    +	 * @param {boolean} [options.autoLog= false] - whether or not to log
    +	 */
    +	constructor({name, win, input, modelDir, faceApiUrl, units, ori, opacity, pos, size, autoDraw, autoLog} = {})
    +	{
    +		super({name, win, units, ori, opacity, pos, size, autoDraw, autoLog});
    +
    +		// TODO deal with onChange (see MovieStim and Camera)
    +		this._addAttribute("input", input, undefined);
    +		this._addAttribute("faceApiUrl", faceApiUrl, "face-api.js");
    +		this._addAttribute("modelDir", modelDir, "models");
    +		this._addAttribute("autoLog", autoLog, false);
    +		this._addAttribute("status", PsychoJS.Status.NOT_STARTED);
    +
    +		// init face-api:
    +		this._initFaceApi();
    +
    +		if (this._autoLog)
    +		{
    +			this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`);
    +		}
    +	}
    +
    +	/**
    +	 * Query whether or not the face detector is ready to detect.
    +	 *
    +	 * @returns {boolean} whether or not the face detector is ready to detect
    +	 */
    +	isReady()
    +	{
    +		return this._modelsLoaded;
    +	}
    +
    +	/**
    +	 * Setter for the video attribute.
    +	 *
    +	 * @param {string | HTMLVideoElement | module:visual.Camera} input - the name of a
    +	 * movie resource or a HTMLVideoElement or a Camera component
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */
    +	setInput(input, log = false)
    +	{
    +		const response = {
    +			origin: "FaceDetector.setInput",
    +			context: "when setting the video of FaceDetector: " + this._name
    +		};
    +
    +		try
    +		{
    +			// movie is undefined: that's fine but we raise a warning in case this is
    +			// a symptom of an actual problem
    +			if (typeof input === "undefined")
    +			{
    +				this.psychoJS.logger.warn("setting the movie of MovieStim: " + this._name + " with argument: undefined.");
    +				this.psychoJS.logger.debug("set the movie of MovieStim: " + this._name + " as: undefined");
    +			}
    +			else
    +			{
    +				// if movie is a string, then it should be the name of a resource, which we get:
    +				if (typeof input === "string")
    +				{
    +					// TODO create a movie with that resource, and use the movie as input
    +				}
    +
    +				// if movie is an instance of camera, get a video element from it:
    +				else if (input instanceof Camera)
    +				{
    +					const video = input.getVideo();
    +					// TODO remove previous one if there is one
    +					// document.body.appendChild(video);
    +					input = video;
    +				}
    +
    +				// check that video is now an HTMLVideoElement
    +				if (!(input instanceof HTMLVideoElement))
    +				{
    +					throw input.toString() + " is not a video";
    +				}
    +
    +				this.psychoJS.logger.debug(`set the video of FaceDetector: ${this._name} as: src= ${input.src}, size= ${input.videoWidth}x${input.videoHeight}, duration= ${input.duration}s`);
    +
    +				// ensure we have only one onended listener per HTMLVideoElement, since we can have several
    +				// MovieStim with the same underlying HTMLVideoElement
    +				// https://stackoverflow.com/questions/11455515
    +				if (!input.onended)
    +				{
    +					input.onended = () =>
    +					{
    +						this.status = PsychoJS.Status.FINISHED;
    +					};
    +				}
    +			}
    +
    +			this._setAttribute("input", input, log);
    +			this._needUpdate = true;
    +			this._needPixiUpdate = true;
    +		}
    +		catch (error)
    +		{
    +			throw Object.assign(response, {error});
    +		}
    +	}
    +
    +
    +	/**
    +	 * Start detecting faces.
    +	 *
    +	 * @param {number} period - the detection period, in ms (e.g. 100 ms for 10Hz)
    +	 * @param detectionCallback - the callback triggered when detection results are available
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */
    +	start(period, detectionCallback, log = false)
    +	{
    +		this.status = PsychoJS.Status.STARTED;
    +
    +		if (typeof this._detectionId !== "undefined")
    +		{
    +			clearInterval(this._detectionId);
    +			this._detectionId = undefined;
    +		}
    +
    +		this._detectionId = setInterval(
    +			async () =>
    +			{
    +				this._detections = await faceapi.detectAllFaces(
    +					this._input,
    +					new faceapi.TinyFaceDetectorOptions()
    +				)
    +				.withFaceLandmarks()
    +				.withFaceExpressions();
    +
    +				this._needUpdate = true;
    +				this._needPixiUpdate = true;
    +
    +				detectionCallback(this._detections);
    +			},
    +			period);
    +	}
    +
    +	/**
    +	 * Stop detecting faces.
    +	 *
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */
    +	stop(log = false)
    +	{
    +		this.status = PsychoJS.Status.NOT_STARTED;
    +
    +		if (typeof this._detectionId !== "undefined")
    +		{
    +			clearInterval(this._detectionId);
    +			this._detectionId = undefined;
    +		}
    +	}
    +
    +	/**
    +	 * Init the Face-API library.
    +	 *
    +	 * @protected
    +	 */
    +	async _initFaceApi()
    +	{
    +/*
    +		// load the library:
    +		await this._psychoJS.serverManager.prepareResources([
    +			{
    +				"name": "face-api.js",
    +				"path": this.faceApiUrl,
    +				"download": true
    +			}
    +		]);
    +*/
    +
    +		// load the models:
    +		this._modelsLoaded = false;
    +		await faceapi.nets.tinyFaceDetector.loadFromUri(this._modelDir);
    +		await faceapi.nets.faceLandmark68Net.loadFromUri(this._modelDir);
    +		await faceapi.nets.faceRecognitionNet.loadFromUri(this._modelDir);
    +		await faceapi.nets.faceExpressionNet.loadFromUri(this._modelDir);
    +		this._modelsLoaded = true;
    +	}
    +
    +	/**
    +	 * Update the visual representation of the detected faces, if necessary.
    +	 *
    +	 * @protected
    +	 */
    +	_updateIfNeeded()
    +	{
    +		if (!this._needUpdate)
    +		{
    +			return;
    +		}
    +		this._needUpdate = false;
    +
    +		if (this._needPixiUpdate)
    +		{
    +			this._needPixiUpdate = false;
    +
    +			if (typeof this._pixi !== "undefined")
    +			{
    +				this._pixi.destroy(true);
    +			}
    +			this._pixi = new PIXI.Container();
    +			this._pixi.interactive = true;
    +
    +			this._body = new PIXI.Graphics();
    +			this._body.interactive = true;
    +			this._pixi.addChild(this._body);
    +
    +			const size_px = util.to_px(this.size, this.units, this.win);
    +			if (typeof this._detections !== "undefined")
    +			{
    +				for (const detection of this._detections)
    +				{
    +					const landmarks = detection.landmarks;
    +					const imageWidth = detection.alignedRect.imageWidth;
    +					const imageHeight = detection.alignedRect.imageHeight;
    +
    +					for (const position of landmarks.positions)
    +					{
    +						this._body.beginFill(new Color("red").int, this._opacity);
    +						this._body.drawCircle(
    +							position._x / imageWidth * size_px[0] - size_px[0] / 2,
    +							position._y / imageHeight * size_px[1] - size_px[1] / 2,
    +							2);
    +						this._body.endFill();
    +					}
    +				}
    +			}
    +
    +		}
    +
    +
    +		this._pixi.scale.x = 1;
    +		this._pixi.scale.y = -1;
    +
    +		this._pixi.rotation = -this.ori * Math.PI / 180;
    +		this._pixi.position = to_pixiPoint(this.pos, this.units, this.win);
    +
    +		this._pixi.alpha = this._opacity;
    +	}
    +
    +	/**
    +	 * Estimate the bounding box.
    +	 *
    +	 * @override
    +	 * @protected
    +	 */
    +	_estimateBoundingBox()
    +	{
    +		// TODO
    +	}
    +
    +}
    +
    +
    +
    +
    +
    + + + + + + +
    + +
    + +
    + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
    + + + + + + + + + + + diff --git a/docs/visual_Form.js.html b/docs/visual_Form.js.html index 5a007b66..e72e28c2 100644 --- a/docs/visual_Form.js.html +++ b/docs/visual_Form.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/Form.js - - - + visual/Form.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: visual/Form.js

    +
    + +

    visual/Form.js

    + @@ -30,154 +54,180 @@

    Source: visual/Form.js

    * Form Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; -import {TrialHandler} from '../data/TrialHandler'; -import {TextStim} from './TextStim'; -import {TextBox} from './TextBox'; -import {VisualStim} from './VisualStim'; -import {Slider} from './Slider'; - - +import * as PIXI from "pixi.js-legacy"; +import { TrialHandler } from "../data/TrialHandler.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { Slider } from "./Slider.js"; +import { TextBox } from "./TextBox.js"; +import { TextStim } from "./TextStim.js"; +import { VisualStim } from "./VisualStim.js"; /** * Form stimulus. * - * @name module:visual.Form - * @class * @extends module:visual.VisualStim * @mixes module:util.ColorMixin - * - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {number[]} [options.pos= [0, 0]] - the position of the center of the slider - * @param {number[]} options.size - the size of the slider, e.g. [1, 0.1] for an horizontal slider - * @param {string} [options.units= 'height'] - the units of the Slider position, and font size - * - * @param {Color} [options.color= Color('LightGray')] the color of the slider - * @param {number} [options.contrast= 1.0] - the contrast of the slider - * @param {number} [options.opacity= 1.0] - the opacity of the slider - * @param {number} [options.depth= 0] - the depth (i.e. the z order), note that the text, radio buttons and slider elements are at depth + 1 - * - * @param {number[]} [options.items= []] - the array of labels - * @param {number} [options.itemPadding= 0.05] - the granularity - * - * @param {string} [options.font= 'Arial'] - the text font - * @param {string} [options.fontFamily= 'Helvetica'] - the text font - * @param {boolean} [options.bold= true] - whether or not the font of the labels is bold - * @param {boolean} [options.italic= false] - whether or not the font of the labels is italic - * @param {number} [options.fontSize] - the font size of the labels (in form units), the default fontSize - * depends on the Form units: 14 for 'pix', 0.03 otherwise - * - * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every - * frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class Form extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, pos, size, units, borderColor, fillColor, itemColor, markerColor, responseColor, color, contrast, opacity, depth, items, randomize, itemPadding, font, fontFamily, bold, italic, fontSize, clipMask, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {number[]} [options.pos= [0, 0]] - the position of the center of the slider + * @param {number[]} options.size - the size of the slider, e.g. [1, 0.1] for an horizontal slider + * @param {string} [options.units= 'height'] - the units of the Slider position, and font size + * + * @param {Color} [options.color= Color('LightGray')] the color of the slider + * @param {number} [options.contrast= 1.0] - the contrast of the slider + * @param {number} [options.opacity= 1.0] - the opacity of the slider + * @param {number} [options.depth= 0] - the depth (i.e. the z order), note that the text, radio buttons and slider elements are at depth - 1 + * + * @param {number[]} [options.items= []] - the array of labels + * @param {number} [options.itemPadding= 0.05] - the granularity + * + * @param {string} [options.font= 'Arial'] - the text font + * @param {string} [options.fontFamily= 'Helvetica'] - the text font + * @param {boolean} [options.bold= true] - whether or not the font of the labels is bold + * @param {boolean} [options.italic= false] - whether or not the font of the labels is italic + * @param {number} [options.fontSize] - the font size of the labels (in form units), the default fontSize + * depends on the Form units: 14 for 'pix', 0.03 otherwise + * + * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every + * frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor( + { + name, + win, + pos, + size, + units, + borderColor, + fillColor, + itemColor, + markerColor, + responseColor, + color, + contrast, + opacity, + depth, + items, + randomize, + itemPadding, + font, + fontFamily, + bold, + italic, + fontSize, + clipMask, + autoDraw, + autoLog, + } = {}, + ) { - super({name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog}); + super({ name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog }); this._addAttribute( - 'itemPadding', + "itemPadding", itemPadding, - util.to_unit([20, 0], 'pix', win, this._units)[0], - this._onChange(true, false) + util.to_unit([20, 0], "pix", win, this._units)[0], + this._onChange(true, false), ); // colors: this._addAttribute( - 'color', + "color", // Same as itemColor color, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'borderColor', + "borderColor", borderColor, fillColor, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'fillColor', + "fillColor", fillColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'itemColor', + "itemColor", itemColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'markerColor', + "markerColor", markerColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'responseColor', + "responseColor", responseColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); // fonts: this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); // Not in use at present this._addAttribute( - 'fontFamily', + "fontFamily", fontFamily, - 'Helvetica', - this._onChange(true, true) + "Helvetica", + this._onChange(true, true), ); this._addAttribute( - 'fontSize', + "fontSize", fontSize, - (this._units === 'pix') ? 14 : 0.03, - this._onChange(true, true) + (this._units === "pix") ? 14 : 0.03, + this._onChange(true, true), ); this._addAttribute( - 'bold', + "bold", bold, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - this._onChange(true, true) + this._onChange(true, true), ); // callback to deal with changes to items: @@ -193,16 +243,17 @@

    Source: visual/Form.js

    }; this._addAttribute( - 'items', + "items", items, [], - onItemChange); + onItemChange, + ); this._addAttribute( - 'randomize', + "randomize", randomize, false, - onItemChange); - + onItemChange, + ); this._scrollbarWidth = 0.02; this._responseTextHeightRatio = 0.8; @@ -219,14 +270,8 @@

    Source: visual/Form.js

    } } - - /** * Force a refresh of the stimulus. - * - * @name module:visual.Form#refresh - * @function - * @public */ refresh() { @@ -245,14 +290,9 @@

    Source: visual/Form.js

    } } - - /** * Overridden draw that also calls the draw method of all form elements. * - * @name module:visual.Form#draw - * @function - * @public * @override */ draw() @@ -287,14 +327,9 @@

    Source: visual/Form.js

    this._scrollbar.draw(); } - - /** * Overridden hide that also calls the hide method of all form elements. * - * @name module:visual.Form#hide - * @function - * @public * @override */ hide() @@ -303,7 +338,7 @@

    Source: visual/Form.js

    super.hide(); // hide the stimuli: - if (typeof this._items !== 'undefined') + if (typeof this._items !== "undefined") { for (let i = 0; i < this._items.length; ++i) { @@ -325,18 +360,12 @@

    Source: visual/Form.js

    } } - - /** * Reset the form. - * - * @name module:visual.Form#reset - * @function - * @public */ reset() { - this.psychoJS.logger.debug('reset Form: ', this._name); + this.psychoJS.logger.debug("reset Form: ", this._name); // reset the stimuli: for (let i = 0; i < this._items.length; ++i) @@ -354,14 +383,9 @@

    Source: visual/Form.js

    this._needUpdate = true; } - - /** * Collate the questions and responses into a single dataset. * - * @name module:visual.Form#getData - * @function - * @public * @return {object} - the dataset with all questions and responses. */ getData() @@ -379,9 +403,9 @@

    Source: visual/Form.js

    item.response = responseStim.getRating(); item.rt = responseStim.getRT(); - if (typeof item.response === 'undefined') + if (typeof item.response === "undefined") { - ++ nbIncompleteResponse; + ++nbIncompleteResponse; } } else if (item.type === Form.Types.FREE_TEXT) @@ -391,7 +415,7 @@

    Source: visual/Form.js

    if (item.response.length === 0) { - ++ nbIncompleteResponse; + ++nbIncompleteResponse; } } } @@ -399,42 +423,41 @@

    Source: visual/Form.js

    this._items._complete = (nbIncompleteResponse === 0); - // return a copy of this._items: - return this._items.map(item => Object.assign({}, item)); + return this._items.map((item) => Object.assign({}, item)); } /** * Check if the form is complete. * - * @name module:visual.Form#formComplete - * @function - * @public * @return {boolean} - whether there are any remaining incomplete responses. */ formComplete() { - //same as complete but might be used by some experiments before 2020.2 + // same as complete but might be used by some experiments before 2020.2 this.getData(); return this._items._complete; } /** * Add the form data to the given experiment. * - * @name module:visual.Form#addDataToExp - * @function - * @public * @param {module:data.ExperimentHandler} experiment - the experiment into which to insert the form data * @param {string} [format= 'rows'] - whether to insert the data as rows or as columns */ - addDataToExp(experiment, format = 'rows') + addDataToExp(experiment, format = "rows") { - const addAsColumns = ['cols', 'columns'].includes(format.toLowerCase()); + const addAsColumns = ["cols", "columns"].includes(format.toLowerCase()); const data = this.getData(); const _doNotSave = [ - 'itemCtrl', 'responseCtrl', - 'itemColor', 'options', 'ticks', 'tickLabels', - 'responseWidth', 'responseColor', 'layout' + "itemCtrl", + "responseCtrl", + "itemColor", + "options", + "ticks", + "tickLabels", + "responseWidth", + "responseColor", + "layout", ]; for (const item of this.getData()) @@ -447,7 +470,7 @@

    Source: visual/Form.js

    const columnName = (addAsColumns) ? `${this._name}[${index}]${field}` : `${this._name}${field}`; experiment.addData(columnName, item[field]); } - ++ index; + ++index; } if (!addAsColumns) @@ -462,20 +485,16 @@

    Source: visual/Form.js

    } } - - /** * Import and process the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * - * @name module:visual.Form#_processItems - * @function - * @private + * @protected */ _processItems() { const response = { - origin: 'Form._processItems', - context: 'when processing the form items' + origin: "Form._processItems", + context: "when processing the form items", }; try @@ -483,7 +502,7 @@

    Source: visual/Form.js

    if (this._autoLog) { // note: we use the same log message as PsychoPy even though we called this method differently - this._psychoJS.experimentLogger.exp('Importing items...'); + this._psychoJS.experimentLogger.exp("Importing items..."); } // import the items: @@ -501,24 +520,20 @@

    Source: visual/Form.js

    catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Import the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * - * @name module:visual.Form#_importItems - * @function - * @private + * @protected */ _importItems() { const response = { - origin: 'Form._importItems', - context: 'when importing the form items' + origin: "Form._importItems", + context: "when importing the form items", }; try @@ -526,17 +541,15 @@

    Source: visual/Form.js

    const itemsType = typeof this._items; // we treat undefined items as a list with a single default entry: - if (itemsType === 'undefined') + if (itemsType === "undefined") { this._items = [Form._defaultItems]; } - // if items is a string, we treat it as the name of a resource file and import it: - else if (itemsType === 'string') + else if (itemsType === "string") { this._items = TrialHandler.importConditions(this._psychoJS.serverManager, this._items); } - // unknown items type: else { @@ -548,29 +561,24 @@

    Source: visual/Form.js

    { this._items = [Form._defaultItems]; } - } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Sanitize the form items: check that the keys are valid, and fill in default values. * - * @name module:visual.Form#_sanitizeItems - * @function - * @private + * @protected */ _sanitizeItems() { const response = { - origin: 'Form._sanitizeItems', - context: 'when sanitizing the form items' + origin: "Form._sanitizeItems", + context: "when sanitizing the form items", }; try @@ -579,7 +587,7 @@

    Source: visual/Form.js

    for (const item of this._items) { // old style forms have questionText instead of itemText: - if (typeof item.questionText !== 'undefined') + if (typeof item.questionText !== "undefined") { item.itemText = item.questionText; delete item.questionText; @@ -588,12 +596,11 @@

    Source: visual/Form.js

    delete item.questionWidth; // for items of type 'rating, the ticks are in 'options' instead of in 'ticks': - if (item.type === 'rating' || item.type === 'slider') + if (item.type === "rating" || item.type === "slider") { item.ticks = item.options; item.options = undefined; } - } } @@ -611,9 +618,8 @@

    Source: visual/Form.js

    missingKeys.add(key); item[key] = Form._defaultItems[key]; } - // undefined value: - else if (typeof item[key] === 'undefined') + else if (typeof item[key] === "undefined") { // TODO: options = '' for FREE_TEXT item[key] = Form._defaultItems[key]; @@ -623,16 +629,17 @@

    Source: visual/Form.js

    if (missingKeys.size > 0) { - this._psychoJS.logger.warn(`Missing headers: ${Array.from(missingKeys).join(', ')}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(', ')}`); + this._psychoJS.logger.warn( + `Missing headers: ${Array.from(missingKeys).join(", ")}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(", ")}`, + ); } - // check the types and options: const formTypes = Object.getOwnPropertyNames(Form.Types); for (const item of this._items) { // convert type to upper case, replace spaces by underscores - item.type = item.type.toUpperCase().replace(' ', '_'); + item.type = item.type.toUpperCase().replace(" ", "_"); // check that the type is valid: if (!formTypes.includes(item.type)) @@ -641,9 +648,9 @@

    Source: visual/Form.js

    } // Support the 'radio' type found on older versions of PsychoPy - if (item.type === 'RADIO') + if (item.type === "RADIO") { - item.type = 'CHOICE'; + item.type = "CHOICE"; } // convert item type to symbol: @@ -652,18 +659,17 @@

    Source: visual/Form.js

    // turn the option into an array and check length, where applicable: if (item.type === Form.Types.CHOICE) { - item.options = item.options.split(','); + item.options = item.options.split(","); if (item.options.length < 2) { throw `at least two choices should be provided for choice item: ${item.itemText}`; } } - // turn the ticks and tickLabels into arrays, where applicable: else if (item.type === Form.Types.RATING || item.type === Form.Types.SLIDER) { - item.ticks = item.ticks.split(',').map( (_,t) => parseInt(t) ); - item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(',') : []; + item.ticks = item.ticks.split(",").map((_, t) => parseInt(t)); + item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(",") : []; } // TODO @@ -672,7 +678,7 @@

    Source: visual/Form.js

    } // check the layout: - const formLayouts = ['HORIZ', 'VERT']; + const formLayouts = ["HORIZ", "VERT"]; for (const item of this._items) { // convert layout to upper case: @@ -685,23 +691,19 @@

    Source: visual/Form.js

    } // convert item layout to symbol: - item.layout = (item.layout === 'HORIZ') ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL; + item.layout = (item.layout === "HORIZ") ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL; } } catch (error) { // throw { ...response, error }; - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Estimate the bounding box. * - * @name module:visual.Form#_estimateBoundingBox - * @function * @override * @protected */ @@ -712,18 +714,14 @@

    Source: visual/Form.js

    this._pos[0] - this._size[0] / 2.0, this._pos[1] - this._size[1] / 2.0, this._size[0], - this._size[1] + this._size[1], ); } - - /** * Setup the stimuli, and the scrollbar. * - * @name module:visual.Form#_setupStimuli - * @function - * @private + * @protected */ _setupStimuli() { @@ -733,7 +731,7 @@

    Source: visual/Form.js

    } // clean up the previously setup stimuli: - if (typeof this._visual !== 'undefined') + if (typeof this._visual !== "undefined") { for (const textStim of this._visual.textStims) { @@ -751,31 +749,30 @@

    Source: visual/Form.js

    textStims: [], responseStims: [], visibles: [], - stimuliTotalHeight: 0 + stimuliTotalHeight: 0, }; // instantiate the clip mask that will be used by all stimuli: this._stimuliClipMask = new PIXI.Graphics(); - // default stimulus options: const textStimOption = { win: this._win, - name: 'item text', + name: "item text", font: this.font, units: this._units, - alignHoriz: 'left', - alignVert: 'top', + alignHoriz: "left", + alignVert: "top", height: this._fontSize, color: this.itemColor, ori: 0, opacity: 1, - depth: this._depth + 1, - clipMask: this._stimuliClipMask + depth: this._depth - 1, + clipMask: this._stimuliClipMask, }; const sliderOption = { win: this._win, - name: 'choice response', + name: "choice response", units: this._units, flip: false, // Not part of Slider options as things stand @@ -788,23 +785,23 @@

    Source: visual/Form.js

    color: this.responseColor, markerColor: this.markerColor, opacity: 1, - depth: this._depth + 1, + depth: this._depth - 1, clipMask: this._stimuliClipMask, - granularity: 1 + granularity: 1, }; const textBoxOption = { win: this._win, - name: 'free text response', + name: "free text response", units: this._units, - anchor: 'left-top', + anchor: "left-top", flip: false, opacity: 1, - depth: this._depth + 1, + depth: this._depth - 1, font: this.font, letterHeight: this._fontSize * this._responseTextHeightRatio, bold: false, italic: false, - alignment: 'left', + alignment: "left", color: this.responseColor, fillColor: this.fillColor, contrast: 1.0, @@ -812,17 +809,16 @@

    Source: visual/Form.js

    borderWidth: 0.002, padding: 0.01, editable: true, - clipMask: this._stimuliClipMask + clipMask: this._stimuliClipMask, }; // we use for the slider's tick size the height of a word: - const textStim = new TextStim(Object.assign(textStimOption, { text: 'Ag', pos: [0, 0]})); + const textStim = new TextStim(Object.assign(textStimOption, { text: "Ag", pos: [0, 0] })); const textMetrics_px = textStim.getTextMetrics(); const sliderTickSize = this._getLengthUnits(textMetrics_px.height) / 2; textStim.release(false); - - let stimulusOffset = - this._itemPadding; + let stimulusOffset = -this._itemPadding; for (const item of this._items) { // initially, all items are invisible: @@ -833,8 +829,10 @@

    Source: visual/Form.js

    // - description: <padding> + <item> + <padding> + <scrollbar> = this._size[0] // - choice with vert layout: <padding> + <item> + <padding> + <scrollbar> = this._size[0] let rowWidth; - if (item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION || - (item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL)) + if ( + item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION + || (item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL) + ) { rowWidth = (this._size[0] - this._itemPadding * 2 - this._scrollbarWidth); } @@ -845,12 +843,13 @@

    Source: visual/Form.js

    } // item text - const itemWidth = rowWidth * item.itemWidth; + const itemWidth = rowWidth * item.itemWidth; const textStim = new TextStim( Object.assign(textStimOption, { text: item.itemText, - wrapWidth: itemWidth - })); + wrapWidth: itemWidth, + }), + ); textStim._relativePos = [this._itemPadding, stimulusOffset]; const textHeight = textStim.boundingBox.height; this._visual.textStims.push(textStim); @@ -874,7 +873,7 @@

    Source: visual/Form.js

    } else { - sliderSize = [sliderTickSize, (sliderTickSize*1.5) * item.options.length]; + sliderSize = [sliderTickSize, (sliderTickSize * 1.5) * item.options.length]; compact = false; flip = true; } @@ -909,23 +908,23 @@

    Source: visual/Form.js

    labels, ticks, compact, - flip - }) + flip, + }), ); responseHeight = responseStim.boundingBox.height; if (item.layout === Form.Layout.HORIZONTAL) { responseStim._relativePos = [ this._itemPadding * 2 + itemWidth + responseWidth / 2, - stimulusOffset - //- Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering) + stimulusOffset, + // - Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering) ]; } else { responseStim._relativePos = [ - this._itemPadding * 2 + itemWidth, //this._itemPadding + sliderTickSize, - stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding + this._itemPadding * 2 + itemWidth, // this._itemPadding + sliderTickSize, + stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding, ]; // since rowHeight will be the max of itemHeight and responseHeight, we need to alter responseHeight @@ -933,20 +932,19 @@

    Source: visual/Form.js

    responseHeight += textHeight + this._itemPadding; } } - // FREE TEXT else if (item.type === Form.Types.FREE_TEXT) { responseStim = new TextBox( Object.assign(textBoxOption, { text: item.options, - size: [responseWidth, -1] - }) + size: [responseWidth, -1], + }), ); responseHeight = responseStim.boundingBox.height; responseStim._relativePos = [ this._itemPadding * 2 + itemWidth, - stimulusOffset + stimulusOffset, ]; } @@ -959,46 +957,39 @@

    Source: visual/Form.js

    } this._visual.stimuliTotalHeight = stimulusOffset; - // scrollbar // note: we add this Form as a dependent stimulus such that the Form is redrawn whenever // the slider is updated this._scrollbar = new Slider({ win: this._win, - name: 'scrollbar', + name: "scrollbar", units: this._units, color: this.itemColor, - depth: this._depth + 1, + depth: this._depth - 1, pos: [0, 0], size: [this._scrollbarWidth, this._size[1]], style: [Slider.Style.SLIDER], ticks: [0, -this._visual.stimuliTotalHeight / this._size[1]], - dependentStims: [this] + dependentStims: [this], }); this._prevScrollbarMarkerPos = 0; this._scrollbar.setMarkerPos(this._prevScrollbarMarkerPos); - // estimate the bounding box: this._estimateBoundingBox(); - if (this._autoLog) { this._psychoJS.experimentLogger.exp(`Layout set for: ${this.name}`); } } - - /** * Update the form visual representation, if necessary. * - * This estimate which stimuli are visible, and updates the decorations. + * <p>This estimate which stimuli are visible, and updates the decorations.</p> * - * @name module:visual.Slider#_updateIfNeeded - * @function - * @private + * @protected */ _updateIfNeeded() { @@ -1018,30 +1009,30 @@

    Source: visual/Form.js

    [this._leftEdge, this._topEdge], this.units, this.win, - true); + true, + ); [this._rightEdge_px, this._bottomEdge_px] = util.to_px( [this._rightEdge, this._bottomEdge], this.units, this.win, - true); + true, + ); this._itemPadding_px = this._getLengthPix(this._itemPadding); this._scrollbarWidth_px = this._getLengthPix(this._scrollbarWidth, true); this._size_px = util.to_px(this._size, this.units, this.win, true); - // update the stimuli clip mask // note: the clip mask is in screen coordinates this._stimuliClipMask.clear(); this._stimuliClipMask.beginFill(0xFFFFFF); this._stimuliClipMask.drawRect( - this._win._rootContainer.position.x + this._leftEdge_px + 2, - this._win._rootContainer.position.y + this._bottomEdge_px + 2, + this._win._stimsContainer.position.x + this._leftEdge_px + 2, + this._win._stimsContainer.position.y + this._bottomEdge_px + 2, this._size_px[0] - 4, - this._size_px[1] - 6 + this._size_px[1] - 6, ); this._stimuliClipMask.endFill(); - // position the scrollbar and get the scrollbar offset, in form units: this._scrollbar.setPos([this._rightEdge - this._scrollbarWidth / 2, this._pos[1]], false); this._scrollbar.setOpacity(0.5); @@ -1052,14 +1043,10 @@

    Source: visual/Form.js

    this._updateDecorations(); } - - /** * Update the visible stimuli. * - * @name module:visual.Form#_updateVisibleStimuli - * @function - * @private + * @protected */ _updateVisibleStimuli() { @@ -1069,7 +1056,7 @@

    Source: visual/Form.js

    const textStim = this._visual.textStims[i]; const textStimPos = [ this._leftEdge + textStim._relativePos[0], - this._topEdge + textStim._relativePos[1] - this._scrollbarOffset + this._topEdge + textStim._relativePos[1] - this._scrollbarOffset, ]; textStim.setPos(textStimPos); @@ -1079,7 +1066,7 @@

    Source: visual/Form.js

    { const responseStimPos = [ this._leftEdge + responseStim._relativePos[0], - this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset + this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset, ]; responseStim.setPos(responseStimPos); } @@ -1105,21 +1092,16 @@

    Source: visual/Form.js

    this._visual.visibles[i] = false; } } - } - - /** * Update the form decorations (bounding box, lines between items, etc.) * - * @name module:visual.Form#_updateDecorations - * @function - * @private + * @protected */ _updateDecorations() { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -1128,15 +1110,14 @@

    Source: visual/Form.js

    this._pixi.scale.x = 1; this._pixi.scale.y = 1; this._pixi.rotation = 0; - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.alpha = this._opacity; - this._pixi.zIndex = this._depth; + this._pixi.zIndex = -this._depth; // apply the form clip mask (n.b., that is not the stimuli clip mask): this._pixi.mask = this._clipMask; - // form background: this._pixi.lineStyle(1, new Color(this.borderColor).int, this._opacity, 0.5); // this._decorations.beginFill(this._barFillColor.int, this._opacity); @@ -1149,7 +1130,7 @@

    Source: visual/Form.js

    this._decorations = new PIXI.Graphics(); this._pixi.addChild(this._decorations); this._decorations.mask = this._stimuliClipMask; - this._decorations.lineStyle(1, new Color('gray').int, this._opacity, 0.5); + this._decorations.lineStyle(1, new Color("gray").int, this._opacity, 0.5); this._decorations.alpha = 0.5; for (let i = 0; i < this._items.length; ++i) @@ -1163,83 +1144,71 @@

    Source: visual/Form.js

    const textStim = this._visual.textStims[i]; const textStimPos = [ this._leftEdge + textStim._relativePos[0], - this._topEdge + textStim._relativePos[1] - this._scrollbarOffset + this._topEdge + textStim._relativePos[1] - this._scrollbarOffset, ]; const textStimPos_px = util.to_px(textStimPos, this._units, this._win); - this._decorations.beginFill(new Color('darkgray').int); + this._decorations.beginFill(new Color("darkgray").int); this._decorations.drawRect( textStimPos_px[0] - this._itemPadding_px / 2, textStimPos_px[1] + this._itemPadding_px / 2, this._size_px[0] - this._itemPadding_px - this._scrollbarWidth_px, - -this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px + -this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px, ); this._decorations.endFill(); } } } - - } } - - /** * Form item types. * * @enum {Symbol} * @readonly - * @public */ Form.Types = { - HEADING: Symbol.for('HEADING'), - DESCRIPTION: Symbol.for('DESCRIPTION'), - RATING: Symbol.for('RATING'), - SLIDER: Symbol.for('SLIDER'), - FREE_TEXT: Symbol.for('FREE_TEXT'), - CHOICE: Symbol.for('CHOICE'), - RADIO: Symbol.for('RADIO') + HEADING: Symbol.for("HEADING"), + DESCRIPTION: Symbol.for("DESCRIPTION"), + RATING: Symbol.for("RATING"), + SLIDER: Symbol.for("SLIDER"), + FREE_TEXT: Symbol.for("FREE_TEXT"), + CHOICE: Symbol.for("CHOICE"), + RADIO: Symbol.for("RADIO"), }; - - /** * Form item layout. * * @enum {Symbol} * @readonly - * @public */ Form.Layout = { - HORIZONTAL: Symbol.for('HORIZONTAL'), - VERTICAL: Symbol.for('VERTICAL') + HORIZONTAL: Symbol.for("HORIZONTAL"), + VERTICAL: Symbol.for("VERTICAL"), }; - - /** * Default form item. * * @readonly - * @private + * @protected * */ Form._defaultItems = { - 'itemText': 'Default question', - 'type': 'rating', - 'options': 'Yes, No', - 'tickLabels': '', - 'itemWidth': 0.7, - 'itemColor': 'white', - - 'responseWidth': 0.3, - 'responseColor': 'white', - - 'index': 0, - 'layout': 'horiz' + "itemText": "Default question", + "type": "rating", + "options": "Yes, No", + "tickLabels": "", + "itemWidth": 0.7, + "itemColor": "white", + + "responseWidth": 0.3, + "responseColor": "white", + + "index": 0, + "layout": "horiz", }; - - @@ -1247,19 +1216,23 @@

    Source: visual/Form.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_GratingStim.js.html b/docs/visual_GratingStim.js.html new file mode 100644 index 00000000..f0d6c7f8 --- /dev/null +++ b/docs/visual_GratingStim.js.html @@ -0,0 +1,850 @@ + + + + + + visual/GratingStim.js - PsychoJS API + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    visual/GratingStim.js

    + + + + + + + +
    +
    +
    /**
    + * Grating Stimulus.
    + *
    + * @author Nikita Agafonov
    + * @version 2021.2.3
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @license Distributed under the terms of the MIT License
    + */
    +
    +import * as PIXI from "pixi.js-legacy";
    +import {AdjustmentFilter} from "@pixi/filter-adjustment";
    +import { Color } from "../util/Color.js";
    +import { to_pixiPoint } from "../util/Pixi.js";
    +import * as util from "../util/Util.js";
    +import { VisualStim } from "./VisualStim.js";
    +import defaultQuadVert from "./shaders/defaultQuad.vert";
    +import imageShader from "./shaders/imageShader.frag";
    +import sinShader from "./shaders/sinShader.frag";
    +import sqrShader from "./shaders/sqrShader.frag";
    +import sawShader from "./shaders/sawShader.frag";
    +import triShader from "./shaders/triShader.frag";
    +import sinXsinShader from "./shaders/sinXsinShader.frag";
    +import sqrXsqrShader from "./shaders/sqrXsqrShader.frag";
    +import circleShader from "./shaders/circleShader.frag";
    +import gaussShader from "./shaders/gaussShader.frag";
    +import crossShader from "./shaders/crossShader.frag";
    +import radRampShader from "./shaders/radRampShader.frag";
    +import raisedCosShader from "./shaders/raisedCosShader.frag";
    +import radialStim from "./shaders/radialShader.frag";
    +
    +/**
    + * Grating Stimulus.
    + *
    + * @extends VisualStim
    + */
    +export class GratingStim extends VisualStim
    +{
    +	/**
    +	 * An object that keeps shaders source code and default uniform values for them.
    +	 * Shader source code is later used for construction of shader programs to create respective visual stimuli.
    +	 *
    +	 * @type {Object}
    +	 * @property {Object} imageShader - Renders provided image with applied effects (coloring, phase, frequency).
    +	 * @property {String} imageShader.shader - shader source code for the image based grating stimuli.
    +	 * @property {Object} imageShader.uniforms - default uniforms for the image based shader.
    +	 * @property {float} imageShader.uniforms.uFreq=1.0 - how much times image repeated within grating stimuli.
    +	 * @property {float} imageShader.uniforms.uPhase=0.0 - offset of the image along X axis.
    +	 * @property {float} imageShader.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} sin - Creates 2d sine wave image as if 1d sine graph was extended across Z axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Sine_wave}
    +	 * @property {String} sin.shader - shader source code for the sine wave stimuli
    +	 * @property {Object} sin.uniforms - default uniforms for sine wave shader
    +	 * @property {float} sin.uniforms.uFreq=1.0 - frequency of sine wave.
    +	 * @property {float} sin.uniforms.uPhase=0.0 - phase of sine wave.
    +	 * @property {float} sin.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} sqr - Creates 2d square wave image as if 1d square graph was extended across Z axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Square_wave}
    +	 * @property {String} sqr.shader - shader source code for the square wave stimuli
    +	 * @property {Object} sqr.uniforms - default uniforms for square wave shader
    +	 * @property {float} sqr.uniforms.uFreq=1.0 - frequency of square wave.
    +	 * @property {float} sqr.uniforms.uPhase=0.0 - phase of square wave.
    +	 * @property {float} sqr.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} saw - Creates 2d sawtooth wave image as if 1d sawtooth graph was extended across Z axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Sawtooth_wave}
    +	 * @property {String} saw.shader - shader source code for the sawtooth wave stimuli
    +	 * @property {Object} saw.uniforms - default uniforms for sawtooth wave shader
    +	 * @property {float} saw.uniforms.uFreq=1.0 - frequency of sawtooth wave.
    +	 * @property {float} saw.uniforms.uPhase=0.0 - phase of sawtooth wave.
    +	 * @property {float} saw.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} tri - Creates 2d triangle wave image as if 1d triangle graph was extended across Z axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Triangle_wave}
    +	 * @property {String} tri.shader - shader source code for the triangle wave stimuli
    +	 * @property {Object} tri.uniforms - default uniforms for triangle wave shader
    +	 * @property {float} tri.uniforms.uFreq=1.0 - frequency of triangle wave.
    +	 * @property {float} tri.uniforms.uPhase=0.0 - phase of triangle wave.
    +	 * @property {float} tri.uniforms.uPeriod=1.0 - period of triangle wave.
    +	 * @property {float} tri.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} sinXsin - Creates an image of two 2d sine waves multiplied with each other.
    +	 * {@link https://en.wikipedia.org/wiki/Sine_wave}
    +	 * @property {String} sinXsin.shader - shader source code for the two multiplied sine waves stimuli
    +	 * @property {Object} sinXsin.uniforms - default uniforms for shader
    +	 * @property {float} sinXsin.uniforms.uFreq=1.0 - frequency of sine wave (both of them).
    +	 * @property {float} sinXsin.uniforms.uPhase=0.0 - phase of sine wave (both of them).
    +	 * @property {float} sinXsin.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} sqrXsqr - Creates an image of two 2d square waves multiplied with each other.
    +	 * {@link https://en.wikipedia.org/wiki/Square_wave}
    +	 * @property {String} sqrXsqr.shader - shader source code for the two multiplied sine waves stimuli
    +	 * @property {Object} sqrXsqr.uniforms - default uniforms for shader
    +	 * @property {float} sqrXsqr.uniforms.uFreq=1.0 - frequency of sine wave (both of them).
    +	 * @property {float} sqrXsqr.uniforms.uPhase=0.0 - phase of sine wave (both of them).
    +	 * @property {float} sqrXsqr.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} circle - Creates a filled circle shape with sharp edges.
    +	 * @property {String} circle.shader - shader source code for filled circle.
    +	 * @property {Object} circle.uniforms - default uniforms for shader.
    +	 * @property {float} circle.uniforms.uRadius=1.0 - Radius of the circle. Ranges [0.0, 1.0], where 0.0 is circle so tiny it results in empty stim
    +	 * and 1.0 is circle that spans from edge to edge of the stim.
    +	 * @property {float} circle.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} gauss - Creates a 2d Gaussian image as if 1d Gaussian graph was rotated arount Y axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Gaussian_function}
    +	 * @property {String} gauss.shader - shader source code for Gaussian shader
    +	 * @property {Object} gauss.uniforms - default uniforms for shader
    +	 * @property {float} gauss.uniforms.uA=1.0 - A constant for gaussian formula (see link).
    +	 * @property {float} gauss.uniforms.uB=0.0 - B constant for gaussian formula (see link).
    +	 * @property {float} gauss.uniforms.uC=0.16 - C constant for gaussian formula (see link).
    +	 * @property {float} gauss.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} cross - Creates a filled cross shape with sharp edges.
    +	 * @property {String} cross.shader - shader source code for cross shader
    +	 * @property {Object} cross.uniforms - default uniforms for shader
    +	 * @property {float} cross.uniforms.uThickness=0.2 - Thickness of the cross. Ranges [0.0, 1.0], where 0.0 thickness makes a cross so thin it becomes
    +	 * invisible and results in an empty stim and 1.0 makes it so thick it fills the entire stim.
    +	 * @property {float} cross.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} radRamp - Creates 2d radial ramp image.
    +	 * @property {String} radRamp.shader - shader source code for radial ramp shader
    +	 * @property {Object} radRamp.uniforms - default uniforms for shader
    +	 * @property {float} radRamp.uniforms.uSqueeze=1.0 - coefficient that helps to modify size of the ramp. Ranges [0.0, Infinity], where 0.0 results in ramp being so large
    +	 * it fills the entire stim and Infinity makes it so tiny it's invisible.
    +	 * @property {float} radRamp.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 *
    +	 * @property {Object} raisedCos - Creates 2d raised-cosine image as if 1d raised-cosine graph was rotated around Y axis and observed from above.
    +	 * {@link https://en.wikipedia.org/wiki/Raised-cosine_filter}
    +	 * @property {String} raisedCos.shader - shader source code for raised-cosine shader
    +	 * @property {Object} raisedCos.uniforms - default uniforms for shader
    +	 * @property {float} raisedCos.uniforms.uBeta=0.25 - roll-off factor (see link).
    +	 * @property {float} raisedCos.uniforms.uPeriod=0.625 - reciprocal of the symbol-rate (see link).
    +	 * @property {float} raisedCos.uniforms.uAlpha=1.0 - value of the alpha channel.
    +	 */
    +	static #SHADERS = {
    +		imageShader: {
    +			shader: imageShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		sin: {
    +			shader: sinShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		sqr: {
    +			shader: sqrShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		saw: {
    +			shader: sawShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		tri: {
    +			shader: triShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uPeriod: 1.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		sinXsin: {
    +			shader: sinXsinShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		sqrXsqr: {
    +			shader: sqrXsqrShader,
    +			uniforms: {
    +				uFreq: 1.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		circle: {
    +			shader: circleShader,
    +			uniforms: {
    +				uRadius: 1.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		gauss: {
    +			shader: gaussShader,
    +			uniforms: {
    +				uA: 1.0,
    +				uB: 0.0,
    +				uC: 0.16,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		cross: {
    +			shader: crossShader,
    +			uniforms: {
    +				uThickness: 0.2,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		radRamp: {
    +			shader: radRampShader,
    +			uniforms: {
    +				uSqueeze: 1.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		raisedCos: {
    +			shader: raisedCosShader,
    +			uniforms: {
    +				uBeta: 0.25,
    +				uPeriod: 0.625,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		},
    +		radialStim: {
    +			shader: radialStim,
    +			uniforms: {
    +				uFreq: 20.0,
    +				uPhase: 0.0,
    +				uColor: [1., 1., 1.],
    +				uAlpha: 1.0
    +			}
    +		}
    +	};
    +
    +	/**
    +	 * Default size of the Grating Stimuli in pixels.
    +	 *
    +	 * @type {Array}
    +	 * @default [256, 256]
    +	 */
    +	static #DEFAULT_STIM_SIZE_PX = [256, 256]; // in pixels
    +
    +	static #BLEND_MODES_MAP = {
    +		avg: PIXI.BLEND_MODES.NORMAL,
    +		add: PIXI.BLEND_MODES.ADD,
    +		mul: PIXI.BLEND_MODES.MULTIPLY,
    +		screen: PIXI.BLEND_MODES.SCREEN
    +	};
    +
    +	/**
    +	 * @memberOf module:visual
    +	 * @param {Object} options
    +	 * @param {String} options.name - the name used when logging messages from this stimulus
    +	 * @param {Window} options.win - the associated Window
    +	 * @param {String | HTMLImageElement} [options.tex="sin"] - the name of the predefined grating texture or image resource or the HTMLImageElement corresponding to the texture
    +	 * @param {String | HTMLImageElement} [options.mask] - the name of the mask resource or HTMLImageElement corresponding to the mask
    +	 * @param {String} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices)
    +	 * @param {number} [options.sf=1.0] - spatial frequency of the function used in grating stimulus
    +	 * @param {number} [options.phase=0.0] - phase of the function used in grating stimulus, multiples of period of that function
    +	 * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
    +	 * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    +	 * @param {number} [options.size] - the size of the rendered image (DEFAULT_STIM_SIZE_PX will be used if size is not specified)
    +	 * @param {Color} [options.color= "white"] - Foreground color of the stimulus. Can be String like "red" or "#ff0000" or Number like 0xff0000.
    +	 * @param {number} [options.opacity= 1.0] - Set the opacity of the stimulus. Determines how visible the stimulus is relative to background.
    +	 * @param {number} [options.contrast= 1.0] - Set the contrast of the stimulus, i.e. scales how far the stimulus deviates from the middle grey. Ranges [-1, 1].
    +	 * @param {number} [options.depth= 0] - the depth (i.e. the z order)
    +	 * @param {boolean} [options.interpolate= false] - Whether to interpolate (linearly) the texture in the stimulus. Currently supports only image based gratings.
    +	 * @param {String} [options.blendmode= "avg"] - blend mode of the stimulus, determines how the stimulus is blended with the background. Supported values: "avg", "add", "mul", "screen".
    +	 * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    +	 * @param {boolean} [options.autoLog= false] - whether or not to log
    +	 */
    +	constructor({
    +		name,
    +		tex = "sin",
    +		win,
    +		mask,
    +		pos,
    +		units,
    +		sf = 1.0,
    +		ori,
    +		phase,
    +		size,
    +		color,
    +		colorSpace,
    +		opacity,
    +		contrast = 1,
    +		depth,
    +		interpolate,
    +		blendmode,
    +		autoDraw,
    +		autoLog,
    +		maskParams
    +	} = {})
    +	{
    +		super({ name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog });
    +
    +		this._adjustmentFilter = new AdjustmentFilter({
    +			contrast
    +		});
    +		this._addAttribute("tex", tex);
    +		this._addAttribute("mask", mask);
    +		this._addAttribute("SF", sf, GratingStim.#SHADERS[tex] ? GratingStim.#SHADERS[tex].uniforms.uFreq || 1.0 : 1.0);
    +		this._addAttribute("phase", phase, GratingStim.#SHADERS[tex] ? GratingStim.#SHADERS[tex].uniforms.uPhase || 0.0 : 0.0);
    +		this._addAttribute("color", color, "white");
    +		this._addAttribute("colorSpace", colorSpace, "RGB");
    +		this._addAttribute("contrast", contrast, 1.0, () => {
    +			this._adjustmentFilter.contrast = this._contrast;
    +		});
    +		this._addAttribute("blendmode", blendmode, "avg");
    +		this._addAttribute("interpolate", interpolate, false);
    +
    +		// estimate the bounding box:
    +		this._estimateBoundingBox();
    +
    +		if (this._autoLog)
    +		{
    +			this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`);
    +		}
    +
    +		if (!Array.isArray(this.size) || this.size.length === 0) {
    +			this.size = util.to_unit(GratingStim.#DEFAULT_STIM_SIZE_PX, "pix", this.win, this.units);
    +		}
    +		this._size_px = util.to_px(this.size, this.units, this.win);
    +	}
    +
    +	/**
    +	 * Setter for the tex attribute.
    +	 *
    +	 * @param {HTMLImageElement | string} tex - the name of built in shader function or name of the image resource or HTMLImageElement corresponding to the image
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */
    +	setTex(tex, log = false)
    +	{
    +		const response = {
    +			origin: "GratingStim.setTex",
    +			context: "when setting the tex of GratingStim: " + this._name,
    +		};
    +
    +		try
    +		{
    +			let hasChanged = false;
    +
    +			// tex is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem
    +			if (typeof tex === "undefined")
    +			{
    +				this.psychoJS.logger.warn("setting the tex of GratingStim: " + this._name + " with argument: undefined.");
    +				this.psychoJS.logger.debug("set the tex of GratingStim: " + this._name + " as: undefined");
    +			}
    +			else if (GratingStim.#SHADERS[tex] !== undefined)
    +			{
    +				// tex is a string and it is one of predefined functions available in shaders
    +				this.psychoJS.logger.debug("the tex is one of predefined functions. Set the tex of GratingStim: " + this._name + " as: " + tex);
    +				const curFuncName = this.getTex();
    +				hasChanged = curFuncName ? curFuncName !== tex : true;
    +			}
    +			else
    +			{
    +				// tex is a string: it should be the name of a resource, which we load
    +				if (typeof tex === "string")
    +				{
    +					tex = this.psychoJS.serverManager.getResource(tex);
    +				}
    +
    +				// tex should now be an actual HTMLImageElement: we raise an error if it is not
    +				if (!(tex instanceof HTMLImageElement))
    +				{
    +					throw "the argument: " + tex.toString() + " is not an image\" }";
    +				}
    +
    +				this.psychoJS.logger.debug("set the tex of GratingStim: " + this._name + " as: src= " + tex.src + ", size= " + tex.width + "x" + tex.height);
    +				const existingImage = this.getTex();
    +				hasChanged = existingImage ? existingImage.src !== tex.src : true;
    +			}
    +
    +			this._setAttribute("tex", tex, log);
    +
    +			if (hasChanged)
    +			{
    +				this._onChange(true, true)();
    +			}
    +		}
    +		catch (error)
    +		{
    +			throw Object.assign(response, { error });
    +		}
    +	}
    +
    +	/**
    +	 * Setter for the mask attribute.
    +	 *
    +	 * @param {HTMLImageElement | string} mask - the name of the mask resource or HTMLImageElement corresponding to the mask
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */
    +	setMask(mask, log = false)
    +	{
    +		const response = {
    +			origin: "GratingStim.setMask",
    +			context: "when setting the mask of GratingStim: " + this._name,
    +		};
    +
    +		try
    +		{
    +			// mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem
    +			if (typeof mask === "undefined")
    +			{
    +				this.psychoJS.logger.warn("setting the mask of GratingStim: " + this._name + " with argument: undefined.");
    +				this.psychoJS.logger.debug("set the mask of GratingStim: " + this._name + " as: undefined");
    +			}
    +			else if (GratingStim.#SHADERS[mask] !== undefined)
    +			{
    +				// mask is a string and it is one of predefined functions available in shaders
    +				this.psychoJS.logger.debug("the mask is one of predefined functions. Set the mask of GratingStim: " + this._name + " as: " + mask);
    +			}
    +			else
    +			{
    +				// mask is a string: it should be the name of a resource, which we load
    +				if (typeof mask === "string")
    +				{
    +					mask = this.psychoJS.serverManager.getResource(mask);
    +				}
    +
    +				// mask should now be an actual HTMLImageElement: we raise an error if it is not
    +				if (!(mask instanceof HTMLImageElement))
    +				{
    +					throw "the argument: " + mask.toString() + " is not an image\" }";
    +				}
    +
    +				this.psychoJS.logger.debug("set the mask of GratingStim: " + this._name + " as: src= " + mask.src + ", size= " + mask.width + "x" + mask.height);
    +			}
    +
    +			this._setAttribute("mask", mask, log);
    +
    +			this._onChange(true, false)();
    +		}
    +		catch (error)
    +		{
    +			throw Object.assign(response, { error });
    +		}
    +	}
    +
    +	/**
    +	 * Get the size of the display image, which is either that of the GratingStim or that of the image
    +	 * it contains.
    +	 *
    +	 * @protected
    +	 * @return {number[]} the size of the displayed image
    +	 */
    +	_getDisplaySize()
    +	{
    +		let displaySize = this.size;
    +
    +		if (typeof displaySize === "undefined")
    +		{
    +			// use the size of the pixi element, if we have access to it:
    +			if (typeof this._pixi !== "undefined" && this._pixi.width > 0)
    +			{
    +				const pixiContainerSize = [this._pixi.width, this._pixi.height];
    +				displaySize = util.to_unit(pixiContainerSize, "pix", this.win, this.units);
    +			}
    +		}
    +
    +		return displaySize;
    +	}
    +
    +	/**
    +	 * Estimate the bounding box.
    +	 *
    +	 * @override
    +	 * @protected
    +	 */
    +	_estimateBoundingBox()
    +	{
    +		const size = this._getDisplaySize();
    +		if (typeof size !== "undefined")
    +		{
    +			this._boundingBox = new PIXI.Rectangle(
    +				this._pos[0] - size[0] / 2,
    +				this._pos[1] - size[1] / 2,
    +				size[0],
    +				size[1],
    +			);
    +		}
    +
    +		// TODO take the orientation into account
    +	}
    +
    +	/**
    +	 * Generate PIXI.Mesh object based on provided shader function name and uniforms.
    +	 * 
    +	 * @protected
    +	 * @param {String} shaderName - name of the shader. Must be one of the SHADERS
    +	 * @param {Object} uniforms - a set of uniforms to supply to the shader. Mixed together with default uniform values.
    +	 * @return {Pixi.Mesh} Pixi.Mesh object that represents shader and later added to the scene.
    +	 */
    +	_getPixiMeshFromPredefinedShaders (shaderName = "", uniforms = {}) {
    +		const geometry = new PIXI.Geometry();
    +		geometry.addAttribute(
    +			"aVertexPosition",
    +			[
    +				0, 0,
    +				this._size_px[0], 0,
    +				this._size_px[0], this._size_px[1],
    +				0, this._size_px[1]
    +			],
    +			2
    +		);
    +		geometry.addAttribute(
    +			"aUvs",
    +			[0, 0, 1, 0, 1, 1, 0, 1],
    +			2
    +		);
    +		geometry.addIndex([0, 1, 2, 0, 2, 3]);
    +		const vertexSrc = defaultQuadVert;
    +		const fragmentSrc = GratingStim.#SHADERS[shaderName].shader;
    +		const uniformsFinal = Object.assign({}, GratingStim.#SHADERS[shaderName].uniforms, uniforms);
    +		const shader = PIXI.Shader.from(vertexSrc, fragmentSrc, uniformsFinal);
    +		return new PIXI.Mesh(geometry, shader);
    +	}
    +
    +	/**
    +	 * Set phase value for the function.
    +	 * 
    +	 * @param {number} phase - phase value
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */ 
    +	setPhase (phase, log = false) {
    +		this._setAttribute("phase", phase, log);
    +		if (this._pixi instanceof PIXI.Mesh) {
    +			this._pixi.shader.uniforms.uPhase = -phase;
    +		}
    +	}
    +
    +	/**
    +	 * Set color space value for the grating stimulus.
    +	 * 
    +	 * @param {String} colorSpaceVal - color space value
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */ 
    +	setColorSpace (colorSpaceVal = "RGB", log = false) {
    +		let colorSpaceValU = colorSpaceVal.toUpperCase();
    +		if (Color.COLOR_SPACE[colorSpaceValU] === undefined) {
    +			colorSpaceValU = "RGB";
    +		}
    +		const hasChanged = this._setAttribute("colorSpace", colorSpaceValU, log);
    +		if (hasChanged) {
    +			this.setColor(this._color);
    +		}
    +	}
    +
    +	/**
    +	 * Set foreground color value for the grating stimulus.
    +	 * 
    +	 * @param {Color} colorVal - color value, can be String like "red" or "#ff0000" or Number like 0xff0000.
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */ 
    +	setColor (colorVal = "white", log = false) {
    +		const colorObj = (colorVal instanceof Color) ? colorVal : new Color(colorVal, Color.COLOR_SPACE[this._colorSpace])
    +		this._setAttribute("color", colorObj, log);
    +		if (this._pixi instanceof PIXI.Mesh) {
    +			this._pixi.shader.uniforms.uColor = colorObj.rgbFull;
    +		}
    +	}
    +
    +	/**
    +	 * Determines how visible the stimulus is relative to background.
    +	 * 
    +	 * @param {number} [opacity=1] opacity - The value should be a single float ranging 1.0 (opaque) to 0.0 (transparent).
    +	 * @param {boolean} [log= false] - whether of not to log
    +	 */ 
    +	setOpacity (opacity = 1, log = false) {
    +		this._setAttribute("opacity", opacity, log);
    +		if (this._pixi instanceof PIXI.Mesh) {
    +			this._pixi.shader.uniforms.uAlpha = opacity;
    +		}
    +	}
    +
    +	/**
    +	 * Set spatial frequency value for the function.
    +	 * 
    +	 * @param {number} sf - spatial frequency value
    +	 * @param {boolean} [log=false] - whether or not to log
    +	 */ 
    +	setSF (sf, log = false) {
    +		this._setAttribute("SF", sf, log);
    +		if (this._pixi instanceof PIXI.Mesh) {
    +			this._pixi.shader.uniforms.uFreq = sf;
    +		}
    +	}
    +
    +	/**
    +	 * Set blend mode of the grating stimulus.
    +	 * 
    +	 * @param {String} blendMode - blend mode, can be one of the following: ["avg", "add", "mul", "screen"].
    +	 * @param {boolean} [log=false] - whether or not to log
    +	 */ 
    +	setBlendmode (blendMode = "avg", log = false) {
    +		this._setAttribute("blendmode", blendMode, log);
    +		if (this._pixi !== undefined) {
    +			let pixiBlendMode = GratingStim.#BLEND_MODES_MAP[blendMode];
    +			if (pixiBlendMode === undefined) {
    +				pixiBlendMode = PIXI.BLEND_MODES.NORMAL;
    +			}
    +			if (this._pixi.filters) {
    +				this._pixi.filters[this._pixi.filters.length - 1].blendMode = pixiBlendMode;
    +			} else {
    +				this._pixi.blendMode = pixiBlendMode;
    +			}
    +		}
    +	}
    +
    +	/**
    +	 * Whether to interpolate (linearly) the texture in the stimulus.
    +	 * 
    +	 * @param {boolean} interpolate - interpolate or not.
    +	 * @param {boolean} [log=false] - whether or not to log
    +	 */ 
    +	setInterpolate (interpolate = false, log = false) {
    +		this._setAttribute("interpolate", interpolate, log);
    +		if (this._pixi instanceof PIXI.Mesh && this._pixi.shader.uniforms.uTex instanceof PIXI.Texture) {
    +			this._pixi.shader.uniforms.uTex.baseTexture.scaleMode = interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST;
    +			this._pixi.shader.uniforms.uTex.baseTexture.update();
    +		}
    +	}
    +
    +	/**
    +	 * Update the stimulus, if necessary.
    +	 *
    +	 * @protected
    +	 */
    +	_updateIfNeeded()
    +	{
    +		if (!this._needUpdate)
    +		{
    +			return;
    +		}
    +		this._needUpdate = false;
    +
    +		// update the PIXI representation, if need be:
    +		if (this._needPixiUpdate)
    +		{
    +			this._needPixiUpdate = false;
    +			let shaderName;
    +			let shaderUniforms;
    +			let currentUniforms = {};
    +			if (typeof this._pixi !== "undefined")
    +			{
    +				if (this._pixi instanceof PIXI.Mesh) {
    +					Object.assign(currentUniforms, this._pixi.shader.uniforms);
    +				}
    +				this._pixi.destroy(true);
    +			}
    +			this._pixi = undefined;
    +
    +			// no image to draw: return immediately
    +			if (typeof this._tex === "undefined")
    +			{
    +				return;
    +			}
    +
    +			if (this._tex instanceof HTMLImageElement)
    +			{
    +				// Not using PIXI.Texture.from() on purpose, as it caches both PIXI.Texture and PIXI.BaseTexture.
    +				// As a result of that we can have multiple GratingStim instances using same PIXI.BaseTexture,
    +				// thus changing texture related properties like interpolation, or calling _pixi.destroy(true)
    +				// will affect all GratingStims who happen to share that BaseTexture.
    +				shaderName = "imageShader";
    +				let shaderTex = new PIXI.Texture(new PIXI.BaseTexture(this._tex, {
    +					wrapMode: PIXI.WRAP_MODES.REPEAT,
    +					scaleMode: this._interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST
    +				}));
    +				shaderUniforms = {
    +					uTex: shaderTex,
    +					uFreq: this._SF,
    +					uPhase: this._phase,
    +					uColor: this._color.rgbFull
    +				};
    +			}
    +			else
    +			{
    +				shaderName = this._tex;
    +				shaderUniforms = {
    +					uFreq: this._SF,
    +					uPhase: this._phase,
    +					uColor: this._color.rgbFull
    +				};
    +			}
    +			this._pixi = this._getPixiMeshFromPredefinedShaders(shaderName, Object.assign(shaderUniforms, currentUniforms));
    +			this._pixi.pivot.set(this._pixi.width * 0.5, this._pixi.width * 0.5);
    +			this._pixi.filters = [this._adjustmentFilter];
    +
    +			// add a mask if need be:
    +			if (typeof this._mask !== "undefined")
    +			{
    +				if (this._mask instanceof HTMLImageElement)
    +				{
    +					// Building new PIXI.BaseTexture each time we create a mask. See notes on shader texture creation above.
    +					this._pixi.mask = PIXI.Sprite.from(new PIXI.Texture(new PIXI.BaseTexture(this._mask)));
    +					this._pixi.mask.width = this._size_px[0];
    +					this._pixi.mask.height = this._size_px[1];
    +					this._pixi.addChild(this._pixi.mask);
    +				}
    +				else
    +				{
    +					// for some reason setting PIXI.Mesh as .mask doesn't do anything,
    +					// rendering mask to texture for further use.
    +					const maskMesh = this._getPixiMeshFromPredefinedShaders(this._mask);
    +					const rt = PIXI.RenderTexture.create({
    +						width: this._size_px[0],
    +						height: this._size_px[1],
    +						scaleMode: this._interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST
    +					});
    +					this.win._renderer.render(maskMesh, {
    +						renderTexture: rt
    +					});
    +					const maskSprite = new PIXI.Sprite.from(rt);
    +					this._pixi.mask = maskSprite;
    +					this._pixi.addChild(maskSprite);
    +				}
    +			}
    +
    +			// since _pixi.width may not be immediately available but the rest of the code needs its value
    +			// we arrange for repeated calls to _updateIfNeeded until we have a width:
    +			if (this._pixi.width === 0)
    +			{
    +				this._needUpdate = true;
    +				this._needPixiUpdate = true;
    +				return;
    +			}
    +		}
    +
    +		this._pixi.zIndex = -this._depth;
    +		this.opacity = this._opacity;
    +
    +		// set the scale:
    +		const displaySize = this._getDisplaySize();
    +		this._size_px = util.to_px(displaySize, this.units, this.win);
    +		const scaleX = this._size_px[0] / this._pixi.width;
    +		const scaleY = this._size_px[1] / this._pixi.height;
    +		this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX;
    +		this._pixi.scale.y = this.flipVert ? scaleY : -scaleY;
    +
    +		// set the position, rotation, and anchor (image centered on pos):
    +		let pos = to_pixiPoint(this.pos, this.units, this.win);
    +		this._pixi.position.set(pos.x, pos.y);
    +		this._pixi.rotation = -this.ori * Math.PI / 180;
    +
    +		// re-estimate the bounding box, as the texture's width may now be available:
    +		this._estimateBoundingBox();
    +	}
    +}
    +
    +
    +
    + + + + + + +
    + +
    + +
    + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme. +
    + + + + + + + + + + + diff --git a/docs/visual_ImageStim.js.html b/docs/visual_ImageStim.js.html index 11a5937d..1359b6b3 100644 --- a/docs/visual_ImageStim.js.html +++ b/docs/visual_ImageStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/ImageStim.js - - - + visual/ImageStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/ImageStim.js

    + + + + +
    + +

    visual/ImageStim.js

    + @@ -30,96 +54,96 @@

    Source: visual/ImageStim.js

    * Image Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 + * @version 2021.2.3 * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; - +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; +import {Camera} from "../hardware"; /** * Image Stimulus. * - * @name module:visual.ImageStim - * @class * @extends VisualStim * @mixes ColorMixin - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {Window} options.win - the associated Window - * @param {string | HTMLImageElement} options.image - the name of the image resource or the HTMLImageElement corresponding to the image - * @param {string | HTMLImageElement} options.mask - the name of the mask resource or HTMLImageElement corresponding to the mask - * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices) - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus - * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position - * @param {number} [options.ori= 0.0] - the orientation (in degrees) - * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified) - * @param {Color} [options.color= 'white'] the background color - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.contrast= 1.0] - the contrast - * @param {number} [options.depth= 0] - the depth (i.e. the z order) - * @param {number} [options.texRes= 128] - the resolution of the text - * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated - * @param {boolean} [options.flipHoriz= false] - whether or not to flip horizontally - * @param {boolean} [options.flipVert= false] - whether or not to flip vertically - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class ImageStim extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {Window} options.win - the associated Window + * @param {string | HTMLImageElement} options.image - the name of the image resource or the HTMLImageElement corresponding to the image + * @param {string | HTMLImageElement} options.mask - the name of the mask resource or HTMLImageElement corresponding to the mask + * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices) + * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus + * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position + * @param {number} [options.ori= 0.0] - the orientation (in degrees) + * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified) + * @param {Color} [options.color= 'white'] the background color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.contrast= 1.0] - the contrast + * @param {number} [options.depth= 0] - the depth (i.e. the z order) + * @param {number} [options.texRes= 128] - the resolution of the text + * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated + * @param {boolean} [options.flipHoriz= false] - whether or not to flip horizontally + * @param {boolean} [options.flipVert= false] - whether or not to flip vertically + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor({ name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog } = {}) { - super({name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog }); this._addAttribute( - 'image', - image + "image", + image, ); this._addAttribute( - 'mask', - mask + "mask", + mask, ); this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'texRes', + "texRes", texRes, 128, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, - false, - this._onChange(true, false) + false ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - this._onChange(false, false) + this._onChange(false, false), ); // estimate the bounding box: @@ -131,52 +155,62 @@

    Source: visual/ImageStim.js

    } } - - /** * Setter for the image attribute. * - * @name module:visual.ImageStim#setImage - * @public * @param {HTMLImageElement | string} image - the name of the image resource or HTMLImageElement corresponding to the image * @param {boolean} [log= false] - whether of not to log */ setImage(image, log = false) { const response = { - origin: 'ImageStim.setImage', - context: 'when setting the image of ImageStim: ' + this._name + origin: "ImageStim.setImage", + context: "when setting the image of ImageStim: " + this._name, }; try { // image is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem - if (typeof image === 'undefined') + if (typeof image === "undefined") { - this.psychoJS.logger.warn('setting the image of ImageStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn("setting the image of ImageStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: undefined"); } else { // image is a string: it should be the name of a resource, which we load - if (typeof image === 'string') + if (typeof image === "string") { image = this.psychoJS.serverManager.getResource(image); } - // image should now be an actual HTMLImageElement: we raise an error if it is not - if (!(image instanceof HTMLImageElement)) + if (image instanceof Camera) { - throw 'the argument: ' + image.toString() + ' is not an image" }'; + const video = image.getVideo(); + // TODO remove previous one if there is one + // document.body.appendChild(video); + image = video; } - this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: src= ' + image.src + ', size= ' + image.width + 'x' + image.height); + // image should now be either an HTMLImageElement or an HTMLVideoElement: + if (image instanceof HTMLImageElement) + { + this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: src= " + image.src + ", size= " + image.width + "x" + image.height); + } + else if (image instanceof HTMLVideoElement) + { + this.psychoJS.logger.debug(`set the image of ImageStim: ${this._name} as: src= ${image.src}, size= ${image.videoWidth}x${image.videoHeight}, duration= ${image.duration}s`); + } + else + { + throw "the argument: " + image.toString() + ' is neither an image nor a video" }'; + } } const existingImage = this.getImage(); const hasChanged = existingImage ? existingImage.src !== image.src : true; - this._setAttribute('image', image, log); + this._setAttribute("image", image, log); if (hasChanged) { @@ -185,39 +219,35 @@

    Source: visual/ImageStim.js

    } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Setter for the mask attribute. * - * @name module:visual.ImageStim#setMask - * @public * @param {HTMLImageElement | string} mask - the name of the mask resource or HTMLImageElement corresponding to the mask * @param {boolean} [log= false] - whether of not to log */ setMask(mask, log = false) { const response = { - origin: 'ImageStim.setMask', - context: 'when setting the mask of ImageStim: ' + this._name + origin: "ImageStim.setMask", + context: "when setting the mask of ImageStim: " + this._name, }; try { // mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem - if (typeof mask === 'undefined') + if (typeof mask === "undefined") { - this.psychoJS.logger.warn('setting the mask of ImageStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn("setting the mask of ImageStim: " + this._name + " with argument: undefined."); + this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: undefined"); } else { // mask is a string: it should be the name of a resource, which we load - if (typeof mask === 'string') + if (typeof mask === "string") { mask = this.psychoJS.serverManager.getResource(mask); } @@ -225,55 +255,62 @@

    Source: visual/ImageStim.js

    // mask should now be an actual HTMLImageElement: we raise an error if it is not if (!(mask instanceof HTMLImageElement)) { - throw 'the argument: ' + mask.toString() + ' is not an image" }'; + throw "the argument: " + mask.toString() + ' is not an image" }'; } - this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: src= ' + mask.src + ', size= ' + mask.width + 'x' + mask.height); + this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: src= " + mask.src + ", size= " + mask.width + "x" + mask.height); } - this._setAttribute('mask', mask, log); + this._setAttribute("mask", mask, log); this._onChange(true, false)(); } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - + /** + * Whether to interpolate (linearly) the texture in the stimulus. + * + * @param {boolean} interpolate - interpolate or not. + * @param {boolean} [log=false] - whether or not to log + */ + setInterpolate (interpolate = false, log = false) { + this._setAttribute("interpolate", interpolate, log); + if (this._pixi instanceof PIXI.Sprite) { + this._pixi.texture.baseTexture.scaleMode = interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST; + this._pixi.texture.baseTexture.update(); + } + } /** * Estimate the bounding box. * - * @name module:visual.ImageStim#_estimateBoundingBox - * @function * @override * @protected */ _estimateBoundingBox() { const size = this._getDisplaySize(); - if (typeof size !== 'undefined') + if (typeof size !== "undefined") { this._boundingBox = new PIXI.Rectangle( this._pos[0] - size[0] / 2, this._pos[1] - size[1] / 2, size[0], - size[1] + size[1], ); } // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * - * @name module:visual.ImageStim#_updateIfNeeded - * @private + * @protected */ _updateIfNeeded() { @@ -288,27 +325,48 @@

    Source: visual/ImageStim.js

    { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } this._pixi = undefined; // no image to draw: return immediately - if (typeof this._image === 'undefined') + if (typeof this._image === "undefined") { return; } - const baseTexture = new PIXI.BaseTexture(this._image); + // deal with both static images and videos: + if (this._image instanceof HTMLImageElement) + { + // Not using PIXI.Texture.from() on purpose, as it caches both PIXI.Texture and PIXI.BaseTexture. + // As a result of that we can have multiple ImageStim instances using same PIXI.BaseTexture, + // thus changing texture related properties like interpolation, or calling _pixi.destroy(true) + // will affect all ImageStims who happen to share that BaseTexture. + const texOpts = + { + scaleMode: this._interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST + }; + this._texture = new PIXI.Texture(new PIXI.BaseTexture(this._image, texOpts)); + } + else if (this._image instanceof HTMLVideoElement) + { + const texOpts = + { + resourceOptions: { autoPlay: true }, + scaleMode: this._interpolate ? PIXI.SCALE_MODES.LINEAR : PIXI.SCALE_MODES.NEAREST + }; + this._texture = new PIXI.Texture(new PIXI.BaseTexture(this._image, texOpts)); + } - this._texture = new PIXI.Texture(baseTexture); this._pixi = PIXI.Sprite.from(this._texture); // add a mask if need be: - if (typeof this._mask !== 'undefined') + if (typeof this._mask !== "undefined") { - this._pixi.mask = PIXI.Sprite.from(this._mask); + // Building new PIXI.BaseTexture each time we create a mask. See notes on this._texture creation above. + this._pixi.mask = PIXI.Sprite.from(new PIXI.Texture(new PIXI.BaseTexture(this._mask))); // a 0.5, 0.5 anchor is required for the mask to be aligned with the image this._pixi.mask.anchor.x = 0.5; @@ -336,7 +394,7 @@

    Source: visual/ImageStim.js

    // this._pixi.filters = [colorFilter]; } - this._pixi.zIndex = this._depth; + this._pixi.zIndex = -this._depth; this._pixi.alpha = this.opacity; // set the scale: @@ -348,8 +406,8 @@

    Source: visual/ImageStim.js

    this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; // set the position, rotation, and anchor (image centered on pos): - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); - this._pixi.rotation = this.ori * Math.PI / 180; + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); + this._pixi.rotation = -this.ori * Math.PI / 180; this._pixi.anchor.x = 0.5; this._pixi.anchor.y = 0.5; @@ -357,34 +415,29 @@

    Source: visual/ImageStim.js

    this._estimateBoundingBox(); } - - /** * Get the size of the display image, which is either that of the ImageStim or that of the image * it contains. * - * @name module:visual.ImageStim#_getDisplaySize - * @private + * @protected * @return {number[]} the size of the displayed image */ _getDisplaySize() { let displaySize = this.size; - if (typeof displaySize === 'undefined') + if (typeof displaySize === "undefined") { // use the size of the texture, if we have access to it: - if (typeof this._texture !== 'undefined' && this._texture.width > 0) + if (typeof this._texture !== "undefined" && this._texture.width > 0) { const textureSize = [this._texture.width, this._texture.height]; - displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); + displaySize = util.to_unit(textureSize, "pix", this.win, this.units); } } return displaySize; } - - } @@ -393,19 +446,23 @@

    Source: visual/ImageStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_MovieStim.js.html b/docs/visual_MovieStim.js.html index 4de27240..18cd7342 100644 --- a/docs/visual_MovieStim.js.html +++ b/docs/visual_MovieStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/MovieStim.js - - - + visual/MovieStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/MovieStim.js

    + + + + +
    + +

    visual/MovieStim.js

    + @@ -30,128 +54,129 @@

    Source: visual/MovieStim.js

    * Movie Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; -import {PsychoJS} from "../core/PsychoJS"; +import * as PIXI from "pixi.js-legacy"; +import { PsychoJS } from "../core/PsychoJS.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; +import {Camera} from "../hardware/Camera.js"; /** * Movie Stimulus. * - * @name module:visual.MovieStim - * @class * @extends VisualStim - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {string | HTMLVideoElement} options.movie - the name of the movie resource or the HTMLVideoElement corresponding to the movie - * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices) - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus - * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position - * @param {number} [options.ori= 0.0] - the orientation (in degrees) - * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified) - * @param {Color} [options.color= Color('white')] the background color - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.contrast= 1.0] - the contrast - * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated - * @param {boolean} [options.flipHoriz= false] - whether or not to flip horizontally - * @param {boolean} [options.flipVert= false] - whether or not to flip vertically - * @param {boolean} [options.loop= false] - whether or not to loop the movie - * @param {number} [options.volume= 1.0] - the volume of the audio track (must be between 0.0 and 1.0) - * @param {boolean} [options.noAudio= false] - whether or not to play the audio - * @param {boolean} [options.autoPlay= true] - whether or not to autoplay the video - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log - * * @todo autoPlay does not work for the moment. */ export class MovieStim extends VisualStim { - constructor({name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {string | HTMLVideoElement | module:visual.Camera} movie - the name of a + * movie resource or of a HTMLVideoElement or of a Camera component + * @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices) + * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus + * @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position + * @param {number} [options.ori= 0.0] - the orientation (in degrees) + * @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified) + * @param {Color} [options.color= Color('white')] the background color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.contrast= 1.0] - the contrast + * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated + * @param {boolean} [options.flipHoriz= false] - whether or not to flip horizontally + * @param {boolean} [options.flipVert= false] - whether or not to flip vertically + * @param {boolean} [options.loop= false] - whether or not to loop the movie + * @param {number} [options.volume= 1.0] - the volume of the audio track (must be between 0.0 and 1.0) + * @param {boolean} [options.noAudio= false] - whether or not to play the audio + * @param {boolean} [options.autoPlay= true] - whether or not to autoplay the video + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor({ name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog } = {}) { - super({name, win, units, ori, opacity, pos, size, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, pos, size, autoDraw, autoLog }); - this.psychoJS.logger.debug('create a new MovieStim with name: ', name); + this.psychoJS.logger.debug("create a new MovieStim with name: ", name); // movie and movie control: this._addAttribute( - 'movie', - movie + "movie", + movie, ); this._addAttribute( - 'volume', + "volume", volume, 1.0, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'noAudio', + "noAudio", noAudio, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'autoPlay', + "autoPlay", autoPlay, true, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, false, - this._onChange(true, false) + this._onChange(true, false), ); // colors: this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'loop', + "loop", loop, false, - this._onChange(false, false) + this._onChange(false, false), ); - // estimate the bounding box: this._estimateBoundingBox(); // check whether the fastSeek method on HTMLVideoElement is implemented: - const videoElement = document.createElement('video'); - this._hasFastSeek = (typeof videoElement.fastSeek === 'function'); + const videoElement = document.createElement("video"); + this._hasFastSeek = (typeof videoElement.fastSeek === "function"); if (this._autoLog) { @@ -159,70 +184,84 @@

    Source: visual/MovieStim.js

    } } - - /** * Setter for the movie attribute. * - * @name module:visual.MovieStim#setMovie - * @public - * @param {string | HTMLVideoElement} movie - the name of the movie resource or the HTMLVideoElement corresponding to the movie + * @param {string | HTMLVideoElement | module:visual.Camera} movie - the name of a + * movie resource or of a HTMLVideoElement or of a Camera component * @param {boolean} [log= false] - whether of not to log */ setMovie(movie, log = false) { const response = { - origin: 'MovieStim.setMovie', - context: 'when setting the movie of MovieStim: ' + this._name + origin: "MovieStim.setMovie", + context: `when setting the movie of MovieStim: ${this._name}`, }; try { - // movie is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem - if (typeof movie === 'undefined') + // movie is undefined: that's fine but we raise a warning in case this is + // a symptom of an actual problem + if (typeof movie === "undefined") { - this.psychoJS.logger.warn('setting the movie of MovieStim: ' + this._name + ' with argument: undefined.'); - this.psychoJS.logger.debug('set the movie of MovieStim: ' + this._name + ' as: undefined'); + this.psychoJS.logger.warn( + `setting the movie of MovieStim: ${this._name} with argument: undefined.`); + this.psychoJS.logger.debug(`set the movie of MovieStim: ${this._name} as: undefined`); } + else { - // movie is a string: it should be the name of a resource, which we load - if (typeof movie === 'string') + // if movie is a string, then it should be the name of a resource, which we get: + if (typeof movie === "string") { movie = this.psychoJS.serverManager.getResource(movie); } - // movie should now be an actual HTMLVideoElement: we raise an error if it is not + // if movie is an instance of camera, get a video element from it: + else if (movie instanceof Camera) + { + // old behaviour: feeding a Camera to MovieStim plays the live stream: + const video = movie.getVideo(); + // TODO remove previous one if there is one + movie = video; + + /* + // new behaviour: feeding a Camera to MovieStim replays the video previously recorded by the Camera: + const video = movie.getRecording(); + movie = video; + */ + } + + // check that movie is now an HTMLVideoElement if (!(movie instanceof HTMLVideoElement)) { - throw 'the argument: ' + movie.toString() + ' is not a video" }'; + throw `${movie.toString()} is not a video`; } this.psychoJS.logger.debug(`set the movie of MovieStim: ${this._name} as: src= ${movie.src}, size= ${movie.videoWidth}x${movie.videoHeight}, duration= ${movie.duration}s`); - } - // Make sure just one listener attached across instances - // https://stackoverflow.com/questions/11455515 - if (!movie.onended) - { - movie.onended = () => + // ensure we have only one onended listener per HTMLVideoElement, since we can have several + // MovieStim with the same underlying HTMLVideoElement + // https://stackoverflow.com/questions/11455515 + if (!movie.onended) { - this.status = PsychoJS.Status.FINISHED; - }; + movie.onended = () => + { + this.status = PsychoJS.Status.FINISHED; + }; + } } - this._setAttribute('movie', movie, log); + this._setAttribute("movie", movie, log); this._needUpdate = true; this._needPixiUpdate = true; } catch (error) { - throw Object.assign(response, {error}); + throw Object.assign(response, { error }); } } - - /** * Reset the stimulus. * @@ -235,8 +274,6 @@

    Source: visual/MovieStim.js

    this.seek(0, log); } - - /** * Start playing the movie. * @@ -251,18 +288,17 @@

    Source: visual/MovieStim.js

    if (playPromise !== undefined) { - playPromise.catch((error) => { + playPromise.catch((error) => + { throw { - origin: 'MovieStim.play', + origin: "MovieStim.play", context: `when attempting to play MovieStim: ${this._name}`, - error + error, }; }); } } - - /** * Pause the movie. * @@ -274,8 +310,6 @@

    Source: visual/MovieStim.js

    this._movie.pause(); } - - /** * Stop the movie and reset to 0s. * @@ -288,8 +322,6 @@

    Source: visual/MovieStim.js

    this.seek(0, log); } - - /** * Jump to a specific timepoint * @@ -303,9 +335,9 @@

    Source: visual/MovieStim.js

    if (timePoint < 0 || timePoint > this._movie.duration) { throw { - origin: 'MovieStim.seek', + origin: "MovieStim.seek", context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, - error: `the timepoint does not belong to [0, ${this._movie.duration}` + error: `the timepoint does not belong to [0, ${this._movie.duration}`, }; } @@ -322,47 +354,40 @@

    Source: visual/MovieStim.js

    catch (error) { throw { - origin: 'MovieStim.seek', + origin: "MovieStim.seek", context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, - error + error, }; } } } - - /** * Estimate the bounding box. * - * @name module:visual.ImageStim#_estimateBoundingBox - * @function * @override * @protected */ _estimateBoundingBox() { const size = this._getDisplaySize(); - if (typeof size !== 'undefined') + if (typeof size !== "undefined") { this._boundingBox = new PIXI.Rectangle( - this._pos[0] - size[0] / 2, - this._pos[1] - size[1] / 2, + this._pos[0] - (size[0] / 2), + this._pos[1] - (size[1] / 2), size[0], - size[1] + size[1], ); } // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * - * @name module:visual.MovieStim#_updateIfNeeded - * @private + * @protected */ _updateIfNeeded() { @@ -377,20 +402,20 @@

    Source: visual/MovieStim.js

    { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { // Leave original video in place // https://pixijs.download/dev/docs/PIXI.Sprite.html#destroy this._pixi.destroy({ children: true, texture: true, - baseTexture: false + baseTexture: false, }); } this._pixi = undefined; // no movie to draw: return immediately - if (typeof this._movie === 'undefined') + if (typeof this._movie === "undefined") { return; } @@ -428,8 +453,8 @@

    Source: visual/MovieStim.js

    this._pixi.scale.y = this.flipVert ? scaleY : -scaleY; // set the position, rotation, and anchor (movie centered on pos): - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); - this._pixi.rotation = this.ori * Math.PI / 180; + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); + this._pixi.rotation = -this.ori * Math.PI / 180; this._pixi.anchor.x = 0.5; this._pixi.anchor.y = 0.5; @@ -437,34 +462,29 @@

    Source: visual/MovieStim.js

    this._estimateBoundingBox(); } - - /** * Get the size of the display image, which is either that of the ImageStim or that of the image * it contains. * - * @name module:visual.ImageStim#_getDisplaySize - * @private + * @protected * @return {number[]} the size of the displayed image */ _getDisplaySize() { let displaySize = this.size; - if (typeof displaySize === 'undefined') + if (typeof displaySize === "undefined") { // use the size of the texture, if we have access to it: - if (typeof this._texture !== 'undefined' && this._texture.width > 0) + if (typeof this._texture !== "undefined" && this._texture.width > 0) { const textureSize = [this._texture.width, this._texture.height]; - displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); + displaySize = util.to_unit(textureSize, "pix", this.win, this.units); } } return displaySize; } - - } @@ -473,19 +493,23 @@

    Source: visual/MovieStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_Polygon.js.html b/docs/visual_Polygon.js.html index e19cc5dc..68c88722 100644 --- a/docs/visual_Polygon.js.html +++ b/docs/visual_Polygon.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/Polygon.js - - - + visual/Polygon.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/Polygon.js

    + + + + +
    + +

    visual/Polygon.js

    + @@ -30,44 +54,45 @@

    Source: visual/Polygon.js

    * Polygonal Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {ShapeStim} from './ShapeStim'; -import {Color} from '../util/Color'; - +import { Color } from "../util/Color.js"; +import { ShapeStim } from "./ShapeStim.js"; /** * <p>Polygonal visual stimulus.</p> * - * @name module:visual.Polygon - * @class * @extends ShapeStim - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {Window} options.win - the associated Window - * @param {number} [options.lineWidth= 1.5] - the line width - * @param {Color} [options.lineColor= Color('white')] the line color - * @param {Color} options.fillColor - the fill color - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.edges= 3] - the number of edges of the polygon - * @param {number} [options.radius= 0.5] - the radius of the polygon - * @param {Array.<number>} [options.pos= [0, 0]] - the position - * @param {number} [options.size= 1.0] - the size - * @param {number} [options.ori= 0.0] - the orientation (in degrees) - * @param {string} options.units - the units of the stimulus vertices, size and position - * @param {number} [options.contrast= 1.0] - the contrast - * @param {number} [options.depth= 0] - the depth - * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class Polygon extends ShapeStim { - constructor({name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) + /** + * <p>Polygonal visual stimulus.</p> + * + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {Window} options.win - the associated Window + * @param {number} [options.lineWidth= 1.5] - the line width + * @param {Color} [options.lineColor= Color('white')] the line color + * @param {Color} options.fillColor - the fill color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.edges= 3] - the number of edges of the polygon + * @param {number} [options.radius= 0.5] - the radius of the polygon + * @param {Array.<number>} [options.pos= [0, 0]] - the position + * @param {number} [options.size= 1.0] - the size + * @param {number} [options.ori= 0.0] - the orientation (in degrees) + * @param {string} options.units - the units of the stimulus vertices, size and position + * @param {number} [options.contrast= 1.0] - the contrast + * @param {number} [options.depth= 0] - the depth + * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor({ name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {}) { super({ name, @@ -84,20 +109,20 @@

    Source: visual/Polygon.js

    depth, interpolate, autoDraw, - autoLog + autoLog, }); - this._psychoJS.logger.debug('create a new Polygon with name: ', name); + this._psychoJS.logger.debug("create a new Polygon with name: ", name); this._addAttribute( - 'edges', + "edges", edges, - 3 + 3, ); this._addAttribute( - 'radius', + "radius", radius, - 0.5 + 0.5, ); this._updateVertices(); @@ -108,19 +133,15 @@

    Source: visual/Polygon.js

    } } - - /** * Setter for the radius attribute. * - * @name module:visual.Polygon#setRadius - * @public * @param {number} radius - the polygon radius * @param {boolean} [log= false] - whether of not to log */ setRadius(radius, log = false) { - const hasChanged = this._setAttribute('radius', radius, log); + const hasChanged = this._setAttribute("radius", radius, log); if (hasChanged) { @@ -128,19 +149,15 @@

    Source: visual/Polygon.js

    } } - - /** * Setter for the edges attribute. * - * @name module:visual.Polygon#setEdges - * @public * @param {number} edges - the number of edges * @param {boolean} [log= false] - whether of not to log */ setEdges(edges, log = false) { - const hasChanged = this._setAttribute('edges', Math.round(edges), log); + const hasChanged = this._setAttribute("edges", Math.round(edges), log); if (hasChanged) { @@ -148,17 +165,15 @@

    Source: visual/Polygon.js

    } } - - /** * Update the vertices. * + * @protected * @name module:visual.Polygon#_updateVertices - * @private */ _updateVertices() { - this._psychoJS.logger.debug('update the vertices of Polygon: ', this.name); + this._psychoJS.logger.debug("update the vertices of Polygon: ", this.name); const angle = 2.0 * Math.PI / this._edges; const vertices = []; @@ -169,7 +184,6 @@

    Source: visual/Polygon.js

    this.setVertices(vertices); } - } @@ -178,19 +192,23 @@

    Source: visual/Polygon.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_Rect.js.html b/docs/visual_Rect.js.html index d43efb9e..b682c7fc 100644 --- a/docs/visual_Rect.js.html +++ b/docs/visual_Rect.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/Rect.js - - - + visual/Rect.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/Rect.js

    + + + + +
    + +

    visual/Rect.js

    + @@ -30,44 +54,43 @@

    Source: visual/Rect.js

    * Rectangular Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import {ShapeStim} from './ShapeStim'; -import {Color} from '../util/Color'; - +import { Color } from "../util/Color.js"; +import { ShapeStim } from "./ShapeStim.js"; /** * <p>Rectangular visual stimulus.</p> * - * @name module:visual.Rect - * @class * @extends ShapeStim - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {number} [options.lineWidth= 1.5] - the line width - * @param {Color} [options.lineColor= 'white'] the line color - * @param {Color} [options.fillColor= undefined] - the fill color - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.width= 0.5] - the width of the rectangle - * @param {number} [options.height= 0.5] - the height of the rectangle - * @param {Array.<number>} [options.pos= [0, 0]] - the position - * @param {number} [options.size= 1.0] - the size - * @param {number} [options.ori= 0.0] - the orientation (in degrees) - * @param {string} [options.units= "height"] - the units of the stimulus vertices, size and position - * @param {number} [options.contrast= 1.0] - the contrast - * @param {number} [options.depth= 0] - the depth - * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class Rect extends ShapeStim { - constructor({name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {number} [options.lineWidth= 1.5] - the line width + * @param {Color} [options.lineColor= 'white'] the line color + * @param {Color} [options.fillColor= undefined] - the fill color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.width= 0.5] - the width of the rectangle + * @param {number} [options.height= 0.5] - the height of the rectangle + * @param {Array.<number>} [options.pos= [0, 0]] - the position + * @param {number} [options.size= 1.0] - the size + * @param {number} [options.ori= 0.0] - the orientation (in degrees) + * @param {string} [options.units= "height"] - the units of the stimulus vertices, size and position + * @param {number} [options.contrast= 1.0] - the contrast + * @param {number} [options.depth= 0] - the depth + * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor({ name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {}) { super({ name, @@ -84,20 +107,20 @@

    Source: visual/Rect.js

    depth, interpolate, autoDraw, - autoLog + autoLog, }); - this._psychoJS.logger.debug('create a new Rect with name: ', name); + this._psychoJS.logger.debug("create a new Rect with name: ", name); this._addAttribute( - 'width', + "width", width, - 0.5 + 0.5, ); this._addAttribute( - 'height', + "height", height, - 0.5 + 0.5, ); this._updateVertices(); @@ -108,21 +131,17 @@

    Source: visual/Rect.js

    } } - - /** * Setter for the width attribute. * - * @name module:visual.Rect#setWidth - * @public * @param {number} width - the rectangle width * @param {boolean} [log= false] - whether of not to log */ setWidth(width, log = false) { - this._psychoJS.logger.debug('set the width of Rect: ', this.name, 'to: ', width); + this._psychoJS.logger.debug("set the width of Rect: ", this.name, "to: ", width); - const hasChanged = this._setAttribute('width', width, log); + const hasChanged = this._setAttribute("width", width, log); if (hasChanged) { @@ -130,21 +149,17 @@

    Source: visual/Rect.js

    } } - - /** * Setter for the height attribute. * - * @name module:visual.Rect#setHeight - * @public * @param {number} height - the rectangle height * @param {boolean} [log= false] - whether of not to log */ setHeight(height, log = false) { - this._psychoJS.logger.debug('set the height of Rect: ', this.name, 'to: ', height); + this._psychoJS.logger.debug("set the height of Rect: ", this.name, "to: ", height); - const hasChanged = this._setAttribute('height', height, log); + const hasChanged = this._setAttribute("height", height, log); if (hasChanged) { @@ -152,17 +167,14 @@

    Source: visual/Rect.js

    } } - - /** * Update the vertices. * - * @name module:visual.Rect#_updateVertices - * @private + * @protected */ _updateVertices() { - this._psychoJS.logger.debug('update the vertices of Rect: ', this.name); + this._psychoJS.logger.debug("update the vertices of Rect: ", this.name); const halfWidth = this._width / 2.0; const halfHeight = this._height / 2.0; @@ -171,10 +183,9 @@

    Source: visual/Rect.js

    [-halfWidth, -halfHeight], [halfWidth, -halfHeight], [halfWidth, halfHeight], - [-halfWidth, halfHeight] + [-halfWidth, halfHeight], ]); } - } @@ -183,19 +194,23 @@

    Source: visual/Rect.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_ShapeStim.js.html b/docs/visual_ShapeStim.js.html index d49977d7..1071ab46 100644 --- a/docs/visual_ShapeStim.js.html +++ b/docs/visual_ShapeStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/ShapeStim.js - - - + visual/ShapeStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: visual/ShapeStim.js

    +
    + +

    visual/ShapeStim.js

    + @@ -26,55 +50,55 @@

    Source: visual/ShapeStim.js

    -
    /** @module visual */
    -/**
    +            
    /**
      * Basic Shape Stimulus.
      *
      * @author Alain Pitiot
    - * @version 2021.2.0
    - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @version 2022.2.3
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
      * @license Distributed under the terms of the MIT License
      */
     
    -
    -import * as PIXI from 'pixi.js-legacy';
    -import {VisualStim} from './VisualStim';
    -import {Color} from '../util/Color';
    -import {ColorMixin} from '../util/ColorMixin';
    -import * as util from '../util/Util';
    -import {WindowMixin} from "../core/WindowMixin";
    -
    +import * as PIXI from "pixi.js-legacy";
    +import { WindowMixin } from "../core/WindowMixin.js";
    +import { Color } from "../util/Color.js";
    +import { ColorMixin } from "../util/ColorMixin.js";
    +import { to_pixiPoint } from "../util/Pixi.js";
    +import * as util from "../util/Util.js";
    +import { VisualStim } from "./VisualStim.js";
     
     /**
      * <p>This class provides the basic functionality of shape stimuli.</p>
      *
    - * @class
      * @extends VisualStim
      * @mixes ColorMixin
    - * @param {Object} options
    - * @param {String} options.name - the name used when logging messages from this stimulus
    - * @param {module:core.Window} options.win - the associated Window
    - * @param {number} options.lineWidth - the line width
    - * @param {Color} [options.lineColor= 'white'] the line color
    - * @param {Color} options.fillColor - the fill color
    - * @param {number} [options.opacity= 1.0] - the opacity
    - * @param {Array.<Array.<number>>} [options.vertices= [[-0.5, 0], [0, 0.5], [0.5, 0]]] - the shape vertices
    - * @param {boolean} [options.closeShape= true] - whether or not the shape is closed
    - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the shape
    - * @param {number} [options.size= 1.0] - the size
    - * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    - * @param {string} options.units - the units of the stimulus vertices, size and position
    - * @param {number} [options.contrast= 1.0] - the contrast
    - * @param {number} [options.depth= 0] - the depth
    - * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated
    - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    - * @param {boolean} [options.autoLog= false] - whether or not to log
      */
     export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
     {
    -	constructor({name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {})
    +	/**
    +	 * @memberOf module:visual
    +	 * @param {Object} options
    +	 * @param {String} options.name - the name used when logging messages from this stimulus
    +	 * @param {module:core.Window} options.win - the associated Window
    +	 * @param {number} options.lineWidth - the line width
    +	 * @param {Color} [options.lineColor= 'white'] the line color
    +	 * @param {Color} options.fillColor - the fill color
    +	 * @param {number} [options.opacity= 1.0] - the opacity
    +	 * @param {Array.<Array.<number>>} [options.vertices= [[-0.5, 0], [0, 0.5], [0.5, 0]]] - the shape vertices
    +	 * @param {boolean} [options.closeShape= true] - whether or not the shape is closed
    +	 * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the shape
    +	 * @param {number} [options.size= 1.0] - the size
    +	 * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    +	 * @param {string} options.units - the units of the stimulus vertices, size and position
    +	 * @param {number} [options.contrast= 1.0] - the contrast
    +	 * @param {number} [options.depth= 0] - the depth
    +	 * @param {boolean} [options.interpolate= true] - whether or not the shape is interpolated
    +	 * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    +	 * @param {boolean} [options.autoLog= false] - whether or not to log
    +	 */
    +	constructor({ name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {})
     	{
    -		super({name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog});
    +		super({ name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog });
     
     		// the PIXI polygon corresponding to the vertices, in pixel units:
     		this._pixiPolygon_px = undefined;
    @@ -82,79 +106,75 @@ 

    Source: visual/ShapeStim.js

    this._vertices_px = undefined; // shape: - if (typeof size === 'undefined' || size === null) + if (typeof size === "undefined" || size === null) { this.size = [1.0, 1.0]; } this._addAttribute( - 'vertices', + "vertices", vertices, - [[-0.5, 0], [0, 0.5], [0.5, 0]] + [[-0.5, 0], [0, 0.5], [0.5, 0]], ); this._addAttribute( - 'closeShape', + "closeShape", closeShape, true, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'interpolate', + "interpolate", interpolate, true, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'lineWidth', + "lineWidth", lineWidth, 1.5, - this._onChange(true, true) + this._onChange(true, true), ); // colors: this._addAttribute( - 'lineColor', + "lineColor", lineColor, - 'white', - this._onChange(true, false) + "white", + this._onChange(true, false), ); this._addAttribute( - 'fillColor', + "fillColor", fillColor, undefined, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); } - - /** * Setter for the vertices attribute. * - * @name module:visual.ShapeStim#setVertices - * @public * @param {Array.<Array.<number>>} vertices - the vertices * @param {boolean} [log= false] - whether of not to log */ setVertices(vertices, log = false) { const response = { - origin: 'ShapeStim.setVertices', - context: 'when setting the vertices of ShapeStim: ' + this._name + origin: "ShapeStim.setVertices", + context: "when setting the vertices of ShapeStim: " + this._name, }; - this._psychoJS.logger.debug('set the vertices of ShapeStim:', this.name); + this._psychoJS.logger.debug("set the vertices of ShapeStim:", this.name); try { // if vertices is a string, we check whether it is a known shape: - if (typeof vertices === 'string') + if (typeof vertices === "string") { if (vertices in ShapeStim.KnownShapes) { @@ -166,25 +186,21 @@

    Source: visual/ShapeStim.js

    } } - this._setAttribute('vertices', vertices, log); + this._setAttribute("vertices", vertices, log); this._onChange(true, true)(); } catch (error) { - throw Object.assign(response, {error: error}); + throw Object.assign(response, { error: error }); } } - - /** * Determine whether an object is inside the bounding box of the ShapeStim. * * This is overridden in order to provide a finer inclusion test. * - * @name module:visual.ShapeStim#contains - * @public * @override * @param {Object} object - the object * @param {string} units - the units @@ -195,29 +211,25 @@

    Source: visual/ShapeStim.js

    // get the position of the object, in pixel coordinates: const objectPos_px = util.getPositionFromObject(object, units); - if (typeof objectPos_px === 'undefined') + if (typeof objectPos_px === "undefined") { throw { - origin: 'VisualStim.contains', - context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), - error: 'unable to determine the position of the object' + origin: "VisualStim.contains", + context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object), + error: "unable to determine the position of the object", }; } // test for inclusion: const pos_px = util.to_px(this.pos, this.units, this.win); this._getVertices_px(); - const polygon_px = this._vertices_px.map(v => [v[0] + pos_px[0], v[1] + pos_px[1]]); + const polygon_px = this._vertices_px.map((v) => [v[0] + pos_px[0], v[1] + pos_px[1]]); return util.IsPointInsidePolygon(objectPos_px, polygon_px); } - - /** * Estimate the bounding box. * - * @name module:visual.ShapeStim#_estimateBoundingBox - * @function * @override * @protected */ @@ -230,7 +242,7 @@

    Source: visual/ShapeStim.js

    Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY + Number.NEGATIVE_INFINITY, ]; for (const vertex of this._vertices_px) { @@ -244,19 +256,16 @@

    Source: visual/ShapeStim.js

    this._pos[0] + this._getLengthUnits(limits_px[0]), this._pos[1] + this._getLengthUnits(limits_px[1]), this._getLengthUnits(limits_px[2] - limits_px[0]), - this._getLengthUnits(limits_px[3] - limits_px[1]) + this._getLengthUnits(limits_px[3] - limits_px[1]), ); // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * - * @name module:visual.ShapeStim#_updateIfNeeded - * @private + * @protected */ _updateIfNeeded() { @@ -271,7 +280,7 @@

    Source: visual/ShapeStim.js

    { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } @@ -283,30 +292,29 @@

    Source: visual/ShapeStim.js

    // prepare the polygon in the given color and opacity: this._pixi = new PIXI.Graphics(); this._pixi.lineStyle(this._lineWidth, this._lineColor.int, this._opacity, 0.5); - if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) + if (typeof this._fillColor !== "undefined" && this._fillColor !== null) { const contrastedColor = this.getContrastedColor(new Color(this._fillColor), this._contrast); this._pixi.beginFill(contrastedColor.int, this._opacity); } this._pixi.drawPolygon(this._pixiPolygon_px); - if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) + if (typeof this._fillColor !== "undefined" && this._fillColor !== null) { this._pixi.endFill(); } + + this._pixi.zIndex = -this._depth; } // set polygon position and rotation: - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); - this._pixi.rotation = this.ori * Math.PI / 180.0; + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); + this._pixi.rotation = -this.ori * Math.PI / 180.0; } - - /** * Get the PIXI polygon (in pixel units) corresponding to the vertices. * - * @name module:visual.ShapeStim#_getPolygon - * @private + * @protected * @return {Object} the PIXI polygon corresponding to this stimulus vertices. */ _getPixiPolygon() @@ -339,12 +347,9 @@

    Source: visual/ShapeStim.js

    return this._pixiPolygon_px; } - - /** * Get the vertices in pixel units. * - * @name module:visual.ShapeStim#_getVertices_px * @protected * @return {Array.<number[]>} the vertices (in pixel units) */ @@ -352,48 +357,47 @@

    Source: visual/ShapeStim.js

    { // handle flipping: let flip = [1.0, 1.0]; - if ('_flipHoriz' in this && this._flipHoriz) + if ("_flipHoriz" in this && this._flipHoriz) { flip[0] = -1.0; } - if ('_flipVert' in this && this._flipVert) + if ("_flipVert" in this && this._flipVert) { flip[1] = -1.0; } // handle size, flipping, and convert to pixel units: - this._vertices_px = this._vertices.map(v => util.to_px( - [v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]], - this._units, - this._win) + this._vertices_px = this._vertices.map((v) => + util.to_px( + [v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]], + this._units, + this._win, + ) ); return this._vertices_px; } - } - /** * Known shapes. * * @readonly - * @public */ ShapeStim.KnownShapes = { cross: [ [-0.1, +0.5], // up [+0.1, +0.5], [+0.1, +0.1], - [+0.5, +0.1], // right + [+0.5, +0.1], // right [+0.5, -0.1], [+0.1, -0.1], - [+0.1, -0.5], // down + [+0.1, -0.5], // down [-0.1, -0.5], [-0.1, -0.1], - [-0.5, -0.1], // left + [-0.5, -0.1], // left [-0.5, +0.1], - [-0.1, +0.1] + [-0.1, +0.1], ], star7: [ @@ -410,9 +414,8 @@

    Source: visual/ShapeStim.js

    [-0.49, -0.11], [-0.19, 0.04], [-0.39, 0.31], - [-0.09, 0.18] - ] - + [-0.09, 0.18], + ], };
    @@ -421,19 +424,23 @@

    Source: visual/ShapeStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_Slider.js.html b/docs/visual_Slider.js.html index 78640eee..a7391242 100644 --- a/docs/visual_Slider.js.html +++ b/docs/visual_Slider.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/Slider.js - - - + visual/Slider.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/Slider.js

    + + + + +
    + +

    visual/Slider.js

    + @@ -30,65 +54,26 @@

    Source: visual/Slider.js

    * Slider Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import {WindowMixin} from '../core/WindowMixin'; -import {Clock} from '../util/Clock'; -import * as util from '../util/Util'; -import {PsychoJS} from "../core/PsychoJS"; - +import * as PIXI from "pixi.js-legacy"; +import { PsychoJS } from "../core/PsychoJS.js"; +import { WindowMixin } from "../core/WindowMixin.js"; +import { Clock } from "../util/Clock.js"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** * Slider stimulus. * - * @name module:visual.Slider - * @class * @extends module:visual.VisualStim * @mixes module:util.ColorMixin - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {number[]} [options.pos= [0, 0]] - the position of the center of the slider - * @param {number[]} options.size - the size of the slider, e.g. [1, 0.1] for an horizontal slider - * @param {number} [options.ori = 0.0] - the orientation (in degrees) - * @param {string} [options.units= 'height'] - the units of the Slider position, and font size - * - * @param {Color} [options.color= Color('LightGray')] the color of the slider - * @param {number} [options.contrast= 1.0] - the contrast of the slider - * @param {number} [options.opacity= 1.0] - the opacity of the slider - * - * @param {string} [options.style= [Slider.Style.RATING]] - the slider style - * @param {number[]} [options.ticks= [1,2,3,4,5]] - the array of ticks - * @param {number[]} [options.labels= []] - the array of labels - * @param {number} [options.granularity= 0] - the granularity - * @param {boolean} [options.flip= false] - whether or not to flip the position of the marker, ticks, - * and labels with respect to the central bar - * @param {boolean} [options.readOnly= false] - whether or not the slider is read only - * - * @param {string} [options.font= 'Arial'] - the text font - * @param {boolean} [options.bold= true] - whether or not the font of the labels is bold - * @param {boolean} [options.italic= false] - whether or not the font of the labels is italic - * @param {number} [options.fontSize] - the font size of the labels (in pixels), the default fontSize depends on the - * Slider's units: 14 for 'pix', 0.03 otherwise - * - * @param {boolean} [options.compact= false] - whether or not the slider is compact, i.e. whether all graphical - * elements (e.g. labels) fit within its size - * - * @param {PIXI.Graphics} options.clipMask - the clip mask - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every - * frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log - * - * @param {core.MinimalStim[]} [options.dependentStims = [] ] - the list of dependent stimuli, - * which must be updated when this Slider is updated, e.g. a Form. * * @todo check that parameters are valid, e.g. ticks are an array of numbers, etc. * @todo readOnly @@ -97,9 +82,79 @@

    Source: visual/Slider.js

    */ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) { - constructor({name, win, pos, size, ori, units, color, markerColor, lineColor, contrast, opacity, style, ticks, labels, granularity, flip, readOnly, font, bold, italic, fontSize, compact, clipMask, autoDraw, autoLog, dependentStims} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {number[]} [options.pos= [0, 0]] - the position of the center of the slider + * @param {number[]} options.size - the size of the slider, e.g. [1, 0.1] for an horizontal slider + * @param {number} [options.ori = 0.0] - the orientation (in degrees) + * @param {string} [options.units= 'height'] - the units of the Slider position, and font size + * + * @param {Color} [options.color= Color('LightGray')] the color of the slider + * @param {number} [options.contrast= 1.0] - the contrast of the slider + * @param {number} [options.opacity= 1.0] - the opacity of the slider + * + * @param {string} [options.style= [Slider.Style.RATING]] - the slider style + * @param {number[]} [options.ticks= [1,2,3,4,5]] - the array of ticks + * @param {number[]} [options.labels= []] - the array of labels + * @param {number} [options.granularity= 0] - the granularity + * @param {boolean} [options.flip= false] - whether or not to flip the position of the marker, ticks, + * and labels with respect to the central bar + * @param {boolean} [options.readOnly= false] - whether or not the slider is read only + * + * @param {string} [options.font= 'Arial'] - the text font + * @param {boolean} [options.bold= true] - whether or not the font of the labels is bold + * @param {boolean} [options.italic= false] - whether or not the font of the labels is italic + * @param {number} [options.fontSize] - the font size of the labels (in pixels), the default fontSize depends on the + * Slider's units: 14 for 'pix', 0.03 otherwise + * + * @param {boolean} [options.compact= false] - whether or not the slider is compact, i.e. whether all graphical + * elements (e.g. labels) fit within its size + * + * @param {PIXI.Graphics} options.clipMask - the clip mask + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every + * frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + * + * @param {core.MinimalStim[]} [options.dependentStims = [] ] - the list of dependent stimuli, + * which must be updated when this Slider is updated, e.g. a Form. + */ + constructor( + { + name, + win, + pos, + size, + ori, + units, + color, + markerColor, + lineColor, + contrast, + opacity, + depth, + style, + ticks, + labels, + startValue, + granularity, + flip, + readOnly, + font, + bold, + italic, + fontSize, + compact, + clipMask, + autoDraw, + autoLog, + dependentStims, + } = {}, + ) { - super({name, win, units, ori, opacity, pos, size, clipMask, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog }); this._needMarkerUpdate = false; @@ -123,119 +178,122 @@

    Source: visual/Slider.js

    }; this._addAttribute( - 'style', + "style", style, [Slider.Style.RATING], - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'ticks', + "ticks", ticks, [1, 2, 3, 4, 5], - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'labels', + "labels", labels, [], - onChange(true, true, true) + onChange(true, true, true), + ); + this._addAttribute( + "startValue", + startValue, + undefined ); this._addAttribute( - 'granularity', + "granularity", granularity, 0, - this._onChange(false, false) + this._onChange(false, false), ); this._addAttribute( - 'readOnly', + "readOnly", readOnly, - false + false, ); this._addAttribute( - 'compact', + "compact", compact, false, - this._onChange(true, true) + this._onChange(true, true), ); // font: this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); this._addAttribute( - 'fontSize', + "fontSize", fontSize, - (this._units === 'pix') ? 14 : 0.03, - this._onChange(true, true) + (this._units === "pix") ? 14 : 0.03, + this._onChange(true, true), ); this._addAttribute( - 'bold', + "bold", bold, true, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - this._onChange(true, true) + this._onChange(true, true), ); this._addAttribute( - 'flip', + "flip", flip, false, - this._onChange(true, true) + this._onChange(true, true), ); // color: this._addAttribute( - 'color', + "color", color, - 'lightgray', - this._onChange(true, false) + "lightgray", + this._onChange(true, false), ); this._addAttribute( - 'lineColor', + "lineColor", lineColor, - 'lightgray', - this._onChange(true, false) + "lightgray", + this._onChange(true, false), ); this._addAttribute( - 'markerColor', + "markerColor", markerColor, - 'red', - this._onChange(true, false) + "red", + this._onChange(true, false), ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, - this._onChange(true, false) + this._onChange(true, false), ); this._addAttribute( - 'dependentStims', + "dependentStims", dependentStims, [], - this._onChange(false, false) + this._onChange(false, false), ); - - // slider rating (which might be different from the visible marker rating): - this._addAttribute('rating', undefined); + this._addAttribute("rating", undefined); // visible marker rating (which might be different from the actual rating): - this._addAttribute('markerPos', undefined); + this._addAttribute("markerPos", undefined); // full history of ratings and response times: - this._addAttribute('history', []); + this._addAttribute("history", []); // various graphical components: - this._addAttribute('lineAspectRatio', 0.01); + this._addAttribute("lineAspectRatio", 0.01); // check for attribute conflicts, missing values, etc.: this._sanitizeAttributes(); @@ -245,20 +303,20 @@

    Source: visual/Slider.js

    // the internal response clock, used to time the marker change events: this._responseClock = new Clock(); + this._pixiLabels = []; if (this._autoLog) { this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); } - } - + this._handlePointerDownBinded = this._handlePointerDown.bind(this); + this._handlePointerUpBinded = this._handlePointerUp.bind(this); + this._handlePointerMoveBinded = this._handlePointerMove.bind(this); + } /** * Force a refresh of the stimulus. - * - * @name module:visual.Slider#refresh - * @public */ refresh() { @@ -267,17 +325,12 @@

    Source: visual/Slider.js

    this._needMarkerUpdate = true; } - - /** * Reset the slider. - * - * @name module:visual.Slider#reset - * @public */ reset() { - this.psychoJS.logger.debug('reset Slider: ', this._name); + this.psychoJS.logger.debug("reset Slider: ", this._name); this._markerPos = undefined; this._history = []; @@ -289,19 +342,25 @@

    Source: visual/Slider.js

    this._needUpdate = true; // the marker should be invisible when markerPos is undefined: - if (typeof this._marker !== 'undefined') + if (typeof this._marker !== "undefined") { this._marker.alpha = 0; } } - + /** + * Query whether or not the marker is currently being dragged. + * + * @returns {boolean} whether or not the marker is being dragged + */ + isMarkerDragging() + { + return this._markerDragging; + } /** * Get the current value of the rating. * - * @name module:visual.Slider#getRating - * @public * @returns {number | undefined} the rating or undefined if there is none */ getRating() @@ -317,13 +376,9 @@

    Source: visual/Slider.js

    } } - - /** * Get the response time of the most recent change to the rating. * - * @name module:visual.Slider#getRT - * @public * @returns {number | undefined} the response time (in second) or undefined if there is none */ getRT() @@ -339,22 +394,17 @@

    Source: visual/Slider.js

    } } - - /** * Setter for the readOnly attribute. * * <p>Read-only sliders are half-opaque and do not provide responses.</p> * - * - * @name module:visual.Slider#setReadOnly - * @public * @param {boolean} [readOnly= true] - whether or not the slider is read-only * @param {boolean} [log= false] - whether of not to log */ setReadOnly(readOnly = true, log = false) { - const hasChanged = this._setAttribute('readOnly', readOnly, log); + const hasChanged = this._setAttribute("readOnly", readOnly, log); if (hasChanged) { @@ -372,17 +422,12 @@

    Source: visual/Slider.js

    } } - - /** * Setter for the markerPos attribute. * * <p>Setting markerPos changes the visible position of the marker to the specified rating * but does not change the actual rating returned by the slider.</p> * - * - * @name module:visual.Slider#setMarkerPos - * @public * @param {number} displayedRating - the displayed rating * @param {boolean} [log= false] - whether of not to log */ @@ -399,15 +444,11 @@

    Source: visual/Slider.js

    } } - - /** * Setter for the rating attribute. * * <p>Setting the rating does not change the visible position of the marker.</p> * - * @name module:visual.Slider#setRating - * @public * @param {number} rating - the rating * @param {boolean} [log= false] - whether of not to log */ @@ -420,68 +461,77 @@

    Source: visual/Slider.js

    rating = this._labels[Math.round(rating)]; } - this._setAttribute('rating', rating, log); + this._setAttribute("rating", rating, log); } - + /** + * Setter for the orientation attribute. + * + * @param {number} ori - the orientation in degree with 0 as the vertical position, positive values rotate clockwise. + * @param {boolean} [log= false] - whether of not to log + */ + setOri (ori = 0, log = false) + { + const oriChanged = this._setAttribute("ori", ori, log); + if (oriChanged) + { + this._pixi.rotation = -this._ori * Math.PI / 180; + let i; + for (i = 0; i < this._pixiLabels.length; ++i) + { + this._pixiLabels[i].rotation = -(this._ori + this._labelOri) * Math.PI / 180; + } + } + } /** Let `borderColor` alias `lineColor` to parallel PsychoPy */ - set borderColor(color) { + set borderColor(color) + { this.lineColor = color; } - - - setBorderColor(color) { + setBorderColor(color) + { this.setLineColor(color); } - - - get borderColor() { + get borderColor() + { return this.lineColor; } - - - getBorderColor() { + getBorderColor() + { return this.getLineColor(); } - /** Let `fillColor` alias `markerColor` to parallel PsychoPy */ - set fillColor(color) { + set fillColor(color) + { this.markerColor = color; } - - - setFillColor(color) { + setFillColor(color) + { this.setMarkerColor(color); } - - - get fillColor() { + get fillColor() + { return this.markerColor; } - - - getFillColor() { + getFillColor() + { return this.getMarkerColor(); } - - /** * Estimate the bounding box. * * @note this method calculates the position of the labels, since that is necessary to the estimation of * the bounding box. * - * @name module:visual.Slider#_estimateBoundingBox - * @function * @override * @protected */ @@ -489,18 +539,18 @@

    Source: visual/Slider.js

    { // setup the slider's style (taking into account the Window dimension, etc.): this._setupStyle(); - + // calculate various values in pixel units: this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._fontSize_px = this._getLengthPix(this._fontSize); - this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); + this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v)); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); const pos_px = util.to_px(this._pos, this._units, this._win); const size_px = util.to_px(this._size, this._units, this._win); // calculate the position of the ticks: const tickPositions = this._ratingToPos(this._ticks); - this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); + this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win)); // left, top, right, bottom limits: const limits_px = [0, 0, size_px[0], size_px[1]]; @@ -517,7 +567,7 @@

    Source: visual/Slider.js

    for (let l = 0; l < this._labels.length; ++l) { - const tickPositionIndex = Math.round( l / (this._labels.length - 1) * (this._ticks.length - 1) ); + const tickPositionIndex = Math.round(l / (this._labels.length - 1) * (this._ticks.length - 1)); this._labelPositions_px[l] = this._tickPositions_px[tickPositionIndex]; const labelBounds = PIXI.TextMetrics.measureText(this._labels[l].toString(), labelTextStyle); @@ -533,7 +583,7 @@

    Source: visual/Slider.js

    this._labelPositions_px[l][1] += this._tickSize_px[1]; } - if (this._style.indexOf(Slider.Style.LABELS45) === -1) + if (this._style.indexOf(Slider.Style.LABELS_45) === -1) { this._labelPositions_px[l][0] -= labelBounds.width / 2; if (this._compact) @@ -542,8 +592,10 @@

    Source: visual/Slider.js

    } // ensure that that labels are not overlapping: - if (prevLabelBounds && - (this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0])) + if ( + prevLabelBounds + && (this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0]) + ) { if (prevNonOverlapOffset === 0) { @@ -603,52 +655,89 @@

    Source: visual/Slider.js

    this._getLengthUnits(position_px.x + limits_px[0]), this._getLengthUnits(position_px.y + limits_px[1]), this._getLengthUnits(limits_px[2] - limits_px[0]), - this._getLengthUnits(limits_px[3] - limits_px[1]) + this._getLengthUnits(limits_px[3] - limits_px[1]), ); } - - /** * Sanitize the slider attributes: check for attribute conflicts, missing values, etc. * - * @name module:visual.Slider#_sanitizeAttributes - * @function * @protected */ _sanitizeAttributes() { + this._isSliderStyle = false; + this._frozenMarker = false; + // convert potential string styles into Symbols: - this._style.forEach( (style, index) => + this._style.forEach((style, index) => { - if (typeof style === 'string') + if (typeof style === "string") { this._style[index] = Symbol.for(style.toUpperCase()); } }); - // TODO: only two ticks for SLIDER type, non-empty ticks, that RADIO is also categorical, etc. + // TODO: non-empty ticks, RADIO is also categorical, etc. + + // SLIDER style: two ticks, first one is zero, second one is > 1 + if (this._style.indexOf(Slider.Style.SLIDER) > -1) + { + this._isSliderStyle = true; + + // more than 2 ticks: cut to two + if (this._ticks.length > 2) + { + this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER and more than two ticks. We cut the ticks to 2.`); + this._ticks = this._ticks.slice(0, 2); + } + + // less than 2 ticks: error + if (this._ticks.length < 2) + { + throw { + origin: "Slider._sanitizeAttributes", + context: "when sanitizing the attributes of Slider: " + this._name, + error: "less than 2 ticks were given for a slider of type: SLIDER" + } + } + + // first tick different from zero: change it to zero + if (this._ticks[0] !== 0) + { + this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER but the first tick is not 0. We changed it to 0.`); + this._ticks[0] = 0; + } + + // second tick smaller than 1: change it to 1 + if (this._ticks[1] < 1) + { + this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER but the second tick is less than 1. We changed it to 1.`); + this._ticks[1] = 1; + } + + // second tick is 1: the marker is frozen + if (this._ticks[1] === 1) + { + this._frozenMarker = true; + } + + } // deal with categorical sliders: this._isCategorical = (this._ticks.length === 0); if (this._isCategorical) { - this._ticks = [...Array(this._labels.length)].map( (_, i) => i ); + this._ticks = [...Array(this._labels.length)].map((_, i) => i); this._granularity = 1.0; } - } - - /** * Set the current rating. * * <p>Setting the rating does also change the visible position of the marker.</p> * - * @name module:visual.Slider#recordRating - * @function - * @public * @param {number} rating - the rating * @param {number} [responseTime] - the reaction time * @param {boolean} [log= false] - whether of not to log @@ -656,7 +745,7 @@

    Source: visual/Slider.js

    recordRating(rating, responseTime = undefined, log = false) { // get response time: - if (typeof responseTime === 'undefined') + if (typeof responseTime === "undefined") { responseTime = this._responseClock.getTime(); } @@ -667,20 +756,29 @@

    Source: visual/Slider.js

    this.setRating(rating, log); // add rating and response time to history: - this._history.push({rating: this._rating, responseTime}); - this.psychoJS.logger.debug('record a new rating: ', this._rating, 'with response time: ', responseTime, 'for Slider: ', this._name); + this._history.push({ rating: this._rating, responseTime }); + this.psychoJS.logger.debug("record a new rating: ", this._rating, "with response time: ", responseTime, "for Slider: ", this._name); // update slider: this._needMarkerUpdate = true; this._needUpdate = true; } + /** + * Release the PIXI representation, if there is one. + * + * @param {boolean} [log= false] - whether or not to log + */ + release (log = false) + { + this._removeEventListeners(); + super.release(log); + } /** * Update the stimulus, if necessary. * - * @name module:visual.Slider#_updateIfNeeded - * @private + * @protected */ _updateIfNeeded() { @@ -697,10 +795,11 @@

    Source: visual/Slider.js

    this._pixi.scale.x = 1; this._pixi.scale.y = -1; - this._pixi.rotation = this._ori * Math.PI / 180; + this._pixi.rotation = -this._ori * Math.PI / 180; this._pixi.position = this._getPosition_px(); this._pixi.alpha = this._opacity; + this._pixi.zIndex = -this._depth; // make sure that the dependent Stimuli are also updated: for (const dependentStim of this._dependentStims) @@ -709,19 +808,19 @@

    Source: visual/Slider.js

    } } - /** * Estimate the position of the slider, taking the compactness into account. * - * @name module:visual.Slider#_getPosition_px * @return {number[]} - the position of the slider, in pixels - * @private + * @protected */ _getPosition_px() { - const position = util.to_pixiPoint(this.pos, this.units, this.win, true); - if (this._compact && - (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1)) + const position = to_pixiPoint(this.pos, this.units, this.win, true); + if ( + this._compact + && (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1) + ) { if (this._isHorizontal()) { @@ -736,13 +835,11 @@

    Source: visual/Slider.js

    return position; } - - /** * Update the position of the marker if necessary. * * @name module:visual.Slider#_updateMarker - * @private + * @protected */ _updateMarker() { @@ -752,12 +849,12 @@

    Source: visual/Slider.js

    } this._needMarkerUpdate = false; - if (typeof this._marker !== 'undefined') + if (typeof this._marker !== "undefined") { - if (typeof this._markerPos !== 'undefined') + if (typeof this._markerPos !== "undefined") { const visibleMarkerPos = this._ratingToPos([this._markerPos]); - this._marker.position = util.to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); + this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); this._marker.alpha = 1; } else @@ -767,14 +864,101 @@

    Source: visual/Slider.js

    } } + /** + * Handle pointerdown event. + * + * @protected + */ + _handlePointerDown (e) { + if (e.data.pointerType === "mouse" && e.data.button !== 0) + { + return; + } + + this._markerDragging = true; + if (!this._frozenMarker) + { + const mouseLocalPos_px = e.data.getLocalPosition(this._pixi); + const rating = this._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); + this.setMarkerPos(rating); + } + + e.stopPropagation(); + } + + /** + * Handle pointermove event. + * + * @protected + */ + _handlePointerMove (e) + { + if (this._markerDragging) + { + if (!this._frozenMarker) + { + const mouseLocalPos_px = e.data.getLocalPosition(this._pixi); + const rating = this._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); + this.setMarkerPos(rating); + } + + e.stopPropagation(); + } + } + + /** + * Handle pointerup event. + * + * @protected + */ + _handlePointerUp (e) + { + if (this._markerDragging) + { + this._markerDragging = false; + + if (!this._frozenMarker) + { + const mouseLocalPos_px = e.data.getLocalPosition(this._pixi); + const rating = this._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); + this.recordRating(rating); + } + + e.stopPropagation(); + } + } + /** + * Add event listeners. + * + * @protected + */ + _addEventListeners () + { + this._pixi.on("pointerdown", this._handlePointerDownBinded); + this._win._rootContainer.on("pointermove", this._handlePointerMoveBinded); + this._win._rootContainer.on("pointerup", this._handlePointerUpBinded); + } + + /** + * Remove event listeners. + * + * @protected + */ + _removeEventListeners () + { + if (this._pixi) + { + this._pixi.off("pointerdown", this._handlePointerDownBinded); + } + this._win._rootContainer.off("pointermove", this._handlePointerMoveBinded); + this._win._rootContainer.off("pointerup", this._handlePointerUpBinded); + } /** * Setup the PIXI components of the slider (bar, ticks, labels, marker, etc.). * - * @name module:visual.Slider#_setupSlider - * @function - * @private + * @protected */ _setupSlider() { @@ -786,18 +970,17 @@

    Source: visual/Slider.js

    this._setupStyle(); - // calculate various values in pixel units: this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._fontSize_px = this._getLengthPix(this._fontSize); - this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); + this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v)); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); const tickPositions = this._ratingToPos(this._ticks); - this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); + this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win)); - - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { + this._removeEventListeners(); this._pixi.destroy(true); } this._pixi = new PIXI.Container(); @@ -809,17 +992,17 @@

    Source: visual/Slider.js

    this._body.interactive = true; this._pixi.addChild(this._body); - // ensure that pointer events will be captured along the slider body, even outside of // marker and labels: if (this._tickType === Slider.Shape.DISC) { const maxTickSize_px = Math.max(this._tickSize_px[0], this._tickSize_px[1]); this._body.hitArea = new PIXI.Rectangle( - -this._barSize_px[0] / 2 - maxTickSize_px, - -this._barSize_px[1] / 2 - maxTickSize_px, - this._barSize_px[0] + maxTickSize_px * 2, - this._barSize_px[1] + maxTickSize_px * 2); + -(this._barSize_px[0] + maxTickSize_px) * 0.5, + -(this._barSize_px[1] + maxTickSize_px) * 0.5, + this._barSize_px[0] + maxTickSize_px, + this._barSize_px[1] + maxTickSize_px, + ); } else { @@ -827,7 +1010,8 @@

    Source: visual/Slider.js

    -this._barSize_px[0] / 2 - this._tickSize_px[0] / 2, -this._barSize_px[1] / 2 - this._tickSize_px[1] / 2, this._barSize_px[0] + this._tickSize_px[0], - this._barSize_px[1] + this._tickSize_px[1]); + this._barSize_px[1] + this._tickSize_px[1], + ); } // central bar: @@ -841,23 +1025,20 @@

    Source: visual/Slider.js

    // markers: this._setupMarker(); + this._addEventListeners(); } - - /** * Setup the central bar. * - * @name module:visual.Slider#_setupBar - * @function - * @private + * @protected */ _setupBar() { if (this._barLineWidth_px > 0) { this._body.lineStyle(this._barLineWidth_px, this._barLineColor.int, 1, 0.5); - if (typeof this._barFillColor !== 'undefined') + if (typeof this._barFillColor !== "undefined") { this._body.beginFill(this._barFillColor.int, 1); } @@ -865,27 +1046,23 @@

    Source: visual/Slider.js

    Math.round(-this._barSize_px[0] / 2), Math.round(-this._barSize_px[1] / 2), Math.round(this._barSize_px[0]), - Math.round(this._barSize_px[1]) + Math.round(this._barSize_px[1]), ); - if (typeof this._barFillColor !== 'undefined') + if (typeof this._barFillColor !== "undefined") { this._body.endFill(); } } } - - /** * Setup the marker, and the associated mouse events. * - * @name module:visual.Slider#_setupMarker - * @function - * @private + * @protected */ _setupMarker() { -/* this is now deprecated and replaced by _body.hitArea + /* this is now deprecated and replaced by _body.hitArea // transparent rectangle necessary to capture pointer events outside of marker and labels: const eventCaptureRectangle = new PIXI.Graphics(); eventCaptureRectangle.beginFill(0, 0); @@ -897,11 +1074,31 @@

    Source: visual/Slider.js

    ); eventCaptureRectangle.endFill(); this._pixi.addChild(eventCaptureRectangle); -*/ + */ // marker: this._marker = new PIXI.Graphics(); - this._marker.alpha = 0; // invisible until markerPos is defined + let markerVal = undefined; + + if (Number.isFinite(this._rating)) + { + markerVal = this._rating; + } + else if (Number.isFinite(this._startValue)) + { + markerVal = this._startValue; + } + + if (Number.isFinite(markerVal)) + { + this._markerPos = this._granularise(markerVal); + const visibleMarkerPos = this._ratingToPos([this._markerPos]); + this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); + } + else + { + this._marker.alpha = 0; // invisible until markerPos is defined + } this._marker.interactive = true; this._pixi.addChild(this._marker); @@ -948,13 +1145,13 @@

    Source: visual/Slider.js

    } else if (this._markerType === Slider.Shape.BOX) { - this._marker.lineStyle(1, this.getContrastedColor(this._markerColor, 0.5).int, 1, 0.5); + this._marker.lineStyle(1, this.getContrastedColor(this._markerColor, 0.5).int, 1, 0); this._marker.beginFill(this._markerColor.int, 1); this._marker.drawRect( Math.round(-this._markerSize_px[0] / 2), Math.round(-this._markerSize_px[1] / 2), this._markerSize_px[0], - this._markerSize_px[1] + this._markerSize_px[1], ); this._marker.endFill(); @@ -962,114 +1159,37 @@

    Source: visual/Slider.js

    // this._marker.drawCircle(0, 0, this._markerSize_px[0] / 3); } - // marker mouse events: const self = this; self._markerDragging = false; - this._marker.pointerdown = this._marker.mousedown = this._marker.touchstart = (event) => + // mouse wheel over slider: + if (this._isSliderStyle) { - if (event.data.button === 0) - { - self._markerDragging = true; - /* not quite right, just yet (as of May 2020) - // set markerPos, but not rating: - const mouseLocalPos_px = event.data.getLocalPosition(self._pixi); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self._markerPos = self._granularise(rating); - - self._needMarkerUpdate = true; - */ - } - - event.stopPropagation(); - }; + self._pointerIsOver = false; - // pointer was released inside the marker: if we were dragging, we record the rating - this._marker.pointerup = this._marker.mouseup = this._marker.touchend = (event) => - { - if (self._markerDragging) + this._pixi.pointerover = this._pixi.mouseover = (event) => { - self._markerDragging = false; - - const mouseLocalPos_px = event.data.getLocalPosition(self._pixi); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self.recordRating(rating); - + self._pointerIsOver = true; event.stopPropagation(); } - }; - - // pointer was released outside of the marker: cancel the dragging - this._marker.pointerupoutside = this._marker.mouseupoutside = this._marker.touchendoutside = (event) => - { - if (self._markerDragging) - { - const mouseLocalPos_px = event.data.getLocalPosition(self._pixi); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self.recordRating(rating); - - self._markerDragging = false; - - event.stopPropagation(); - } - }; - - // pointer is moving: if we are dragging, we move the marker position - this._marker.pointermove = (event) => - { - if (self._markerDragging) + this._pixi.pointerout = this._pixi.mouseout = (event) => { - const mouseLocalPos_px = event.data.getLocalPosition(self._pixi); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self.setMarkerPos(rating); - + self._pointerIsOver = false; event.stopPropagation(); } - }; - - // (*) slider mouse events outside of marker - // note: this only works thanks to eventCaptureRectangle - /* not quite right just yet (as of May 2020) - this._pixi.pointerdown = this._pixi.mousedown = this._pixi.touchstart = (event) => - { - if (event.data.button === 0) + /*renderer.view.addEventListener("wheel", (event) => { - self._markerDragging = true; - - // set markerPos, but not rating: - const mouseLocalPos_px = event.data.getLocalPosition(self._body); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self._markerPos = self._granularise(rating); - - // update the marker: - self._needMarkerUpdate = true; - self._updateMarker(); - } - - event.stopPropagation(); - }; - */ - this._pixi.pointerup = this._pixi.mouseup = this._pixi.touchend = (event) => - { - const mouseLocalPos_px = event.data.getLocalPosition(self._body); - const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]); - self.recordRating(rating); - - event.stopPropagation(); - }; + }*/ + } } - - /** * Setup the ticks. * - * @name module:visual.Slider#_setupTicks - * @function - * @private + * @protected */ _setupTicks() { @@ -1093,71 +1213,60 @@

    Source: visual/Slider.js

    else if (this._tickType === Slider.Shape.DISC) { this._body.beginFill(this._tickColor.int, 1); - this._body.drawCircle(tickPosition_px[0], tickPosition_px[1], maxTickSize); + this._body.drawCircle(tickPosition_px[0], tickPosition_px[1], maxTickSize * 0.5); this._body.endFill(); } } } - - /** * Get the PIXI Text Style applied to the PIXI.Text labels. * - * @name module:visual.Slider#_getTextStyle - * @function - * @private + * @protected */ _getTextStyle() { this._fontSize_px = this._getLengthPix(this._fontSize); - + return new PIXI.TextStyle({ fontFamily: this._font, fontSize: Math.round(this._fontSize_px), - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", fill: this.getContrastedColor(this._labelColor, this._contrast).hex, - align: 'center', + align: "center", }); } - - /** * Setup the labels. * - * @name module:visual.Slider#_setupTicks - * @function - * @private + * @protected */ _setupLabels() { const labelTextStyle = this._getTextStyle(); + this._pixiLabels = new Array(this._labels.length); for (let l = 0; l < this._labels.length; ++l) { - const labelText = new PIXI.Text(this._labels[l], labelTextStyle); - labelText.position.x = this._labelPositions_px[l][0]; - labelText.position.y = this._labelPositions_px[l][1]; - labelText.rotation = (this._ori + this._labelOri) * Math.PI / 180; - labelText.anchor = this._labelAnchor; - labelText.alpha = 1; - - this._pixi.addChild(labelText); + this._pixiLabels[l] = new PIXI.Text(this._labels[l], labelTextStyle); + this._pixiLabels[l].position.x = this._labelPositions_px[l][0]; + this._pixiLabels[l].position.y = this._labelPositions_px[l][1]; + this._pixiLabels[l].rotation = -(this._ori + this._labelOri) * Math.PI / 180; + this._pixiLabels[l].anchor = this._labelAnchor; + this._pixiLabels[l].alpha = 1; + + this._pixi.addChild(this._pixiLabels[l]); } } - - /** * Apply a particular style to the slider. * * @note: We are mirroring PsychoPy here, rather than using a skin approach. * - * @name module:visual.Slider#_setupStyle - * @function - * @private + * @protected */ _setupStyle() { @@ -1185,7 +1294,8 @@

    Source: visual/Slider.js

    this._tickType = Slider.Shape.LINE; this._tickColor = (!skin.TICK_COLOR) ? new Color(this._lineColor) : skin.TICK_COLOR; - if (this.markerColor === undefined) { + if (this.markerColor === undefined) + { this.markerColor = skin.MARKER_COLOR; } @@ -1198,7 +1308,6 @@

    Source: visual/Slider.js

    this._labelOri = 0; - // rating: if (this._style.indexOf(Slider.Style.RATING) > -1) { @@ -1211,8 +1320,8 @@

    Source: visual/Slider.js

    this._markerType = Slider.Shape.TRIANGLE; if (!this._skin.MARKER_SIZE) { - this._markerSize = this._markerSize.map(s => s * 2); - } + this._markerSize = this._markerSize.map((s) => s * 2); + } } // slider: @@ -1221,9 +1330,9 @@

    Source: visual/Slider.js

    this._markerType = Slider.Shape.BOX; if (!this._skin.MARKER_SIZE) { - this._markerSize = (this._isHorizontal()) ? - [this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]] : - [this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])]; + this._markerSize = (this._isHorizontal()) + ? [this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]] + : [this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])]; } this._barSize = [this._size[0], this._size[1]]; this._barFillColor = this.getContrastedColor(new Color(this.color), 0.5); @@ -1241,7 +1350,7 @@

    Source: visual/Slider.js

    */ // labels45: - if (this._style.indexOf(Slider.Style.LABELS45) > -1) + if (this._style.indexOf(Slider.Style.LABELS_45) > -1) { this._labelOri = -45; if (this._flip) @@ -1259,22 +1368,19 @@

    Source: visual/Slider.js

    { this._barLineWidth_px = 0; this._tickType = Slider.Shape.DISC; + this.granularity = 1.0; if (!this._skin.MARKER_SIZE) { - this._markerSize = this._markerSize.map(s => s * 0.7); + this._markerSize = this._markerSize.map((s) => s * 0.7); + } } } - } - - /** * Convert an array of ratings into an array of [x,y] positions (in Slider units, with 0 at the center of the Slider) * - * @name module:visual.Slider#_ratingToPos - * @function - * @private + * @protected * @param {number[]} ratings - the array of ratings * @returns {Array.<Array.<number>>} the positions corresponding to the ratings (in Slider units, * with 0 at the center of the Slider) @@ -1287,20 +1393,22 @@

    Source: visual/Slider.js

    // in compact mode the circular markers of RADIO sliders must fit within the width: if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) { - return ratings.map(v => [ - ((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1]*2) - - (this._size[0] / 2) + this._tickSize[1], - 0]); + return ratings.map((v) => [ + ((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1] * 2) + - (this._size[0] / 2) + this._tickSize[1], + 0, + ]); } else if (this._style.indexOf(Slider.Style.SLIDER) > -1) { - return ratings.map(v => [ + return ratings.map((v) => [ ((v - this._ticks[0]) / range - 0.5) * (this._size[0] - this._markerSize[0]), - 0]); + 0, + ]); } else { - return ratings.map(v => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]); + return ratings.map((v) => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]); } } else @@ -1308,31 +1416,30 @@

    Source: visual/Slider.js

    // in compact mode the circular markers of RADIO sliders must fit within the height: if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) { - return ratings.map(v => [0, - ((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0]*2) - - (this._size[1] / 2) + this._tickSize[0]]); + return ratings.map((v) => [ + 0, + ((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0] * 2) + - (this._size[1] / 2) + this._tickSize[0], + ]); } else if (this._style.indexOf(Slider.Style.SLIDER) > -1) { - return ratings.map(v => [ + return ratings.map((v) => [ 0, - ((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1])]); + ((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1]), + ]); } else { - return ratings.map(v => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]); + return ratings.map((v) => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]); } } } - - /** * Convert a [x,y] position, in pixel units, relative to the slider, into a rating. * - * @name module:visual.Slider#_posToRating - * @function - * @private + * @protected * @param {number[]} pos_px - the [x,y] position, in pixel units, relative to the slider. * @returns {number} the corresponding rating. */ @@ -1357,7 +1464,14 @@

    Source: visual/Slider.js

    { if (this._style.indexOf(Slider.Style.SLIDER) > -1) { - return (pos_px[1] / (size_px[1] - markerSize_px[1]) + 0.5) * range + this._ticks[0]; + if (size_px[1] === markerSize_px[1]) + { + + } + else + { + return (pos_px[1] / (size_px[1] - markerSize_px[1]) + 0.5) * range + this._ticks[0]; + } } else { @@ -1366,16 +1480,12 @@

    Source: visual/Slider.js

    } } - - /** * Determine whether the slider is horizontal. * * <p>The slider is horizontal is its x-axis size is larger than its y-axis size.</p> * - * @name module:visual.Slider#_isHorizontal - * @function - * @private + * @protected * @returns {boolean} whether or not the slider is horizontal */ _isHorizontal() @@ -1383,20 +1493,16 @@

    Source: visual/Slider.js

    return (this._size[0] > this._size[1]); } - - /** * Calculate the rating once granularity has been taken into account. * - * @name module:visual.Slider#_granularise - * @function - * @private + * @protected * @param {number} rating - the input rating * @returns {number} the new rating with granularity applied */ _granularise(rating) { - if (typeof rating === 'undefined') + if (typeof rating === "undefined") { return undefined; } @@ -1409,68 +1515,58 @@

    Source: visual/Slider.js

    return rating; } - } - /** * Shape of the marker and of the ticks. * - * @name module:visual.Slider#Shape * @enum {Symbol} * @readonly - * @public */ Slider.Shape = { - DISC: Symbol.for('DISC'), - TRIANGLE: Symbol.for('TRIANGLE'), - LINE: Symbol.for('LINE'), - BOX: Symbol.for('BOX') + DISC: Symbol.for("DISC"), + TRIANGLE: Symbol.for("TRIANGLE"), + LINE: Symbol.for("LINE"), + BOX: Symbol.for("BOX"), }; - /** * Styles. * - * @name module:visual.Slider#Style * @enum {Symbol} * @readonly - * @public */ Slider.Style = { - RATING: Symbol.for('RATING'), - TRIANGLE_MARKER: Symbol.for('TRIANGLEMARKER'), - SLIDER: Symbol.for('SLIDER'), - WHITE_ON_BLACK: Symbol.for('WHITEONBLACK'), - LABELS45: Symbol.for('LABELS45'), - RADIO: Symbol.for('RADIO') + RATING: Symbol.for("RATING"), + TRIANGLE_MARKER: Symbol.for("TRIANGLE_MARKER"), + SLIDER: Symbol.for("SLIDER"), + WHITE_ON_BLACK: Symbol.for("WHITE_ON_BLACK"), + LABELS_45: Symbol.for("LABELS_45"), + RADIO: Symbol.for("RADIO"), }; - /** * Skin. * - * @name module:visual.Slider#Skin * @enum {any} * @readonly - * @public * * @note a null value indicates that the value is calculated when the style is setup, rather than simply assigned. */ Slider.Skin = { MARKER_SIZE: null, STANDARD: { - MARKER_COLOR: new Color('red'), + MARKER_COLOR: new Color("red"), BAR_LINE_COLOR: null, TICK_COLOR: null, - LABEL_COLOR: null + LABEL_COLOR: null, }, WHITE_ON_BLACK: { - MARKER_COLOR: new Color('white'), - BAR_LINE_COLOR: new Color('black'), - TICK_COLOR: new Color('black'), - LABEL_COLOR: new Color('black') - } + MARKER_COLOR: new Color("white"), + BAR_LINE_COLOR: new Color("black"), + TICK_COLOR: new Color("black"), + LABEL_COLOR: new Color("black"), + }, };
    @@ -1479,19 +1575,23 @@

    Source: visual/Slider.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_TextBox.js.html b/docs/visual_TextBox.js.html index 3fe12652..904f279f 100644 --- a/docs/visual_TextBox.js.html +++ b/docs/visual_TextBox.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/TextBox.js - - - + visual/TextBox.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + -

    Source: visual/TextBox.js

    + + + + +
    + +

    visual/TextBox.js

    + @@ -29,171 +53,210 @@

    Source: visual/TextBox.js

    /**
      * Editable TextBox Stimulus.
      *
    - * @author Alain Pitiot
    - * @version 2021.2.0
    - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @author Alain Pitiot, Nikita Agafonov
    + * @version 2022.2.3
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
      * @license Distributed under the terms of the MIT License
      */
     
    +import * as PIXI from "pixi.js-legacy";
    +import { Color } from "../util/Color.js";
    +import { ColorMixin } from "../util/ColorMixin.js";
    +import * as util from "../util/Util.js";
    +import { ButtonStim } from "./ButtonStim.js";
    +import { TextInput } from "./TextInput.js";
    +import { VisualStim } from "./VisualStim.js";
     
    -import * as PIXI from 'pixi.js-legacy';
    -import {VisualStim} from './VisualStim';
    -import {Color} from '../util/Color';
    -import {ColorMixin} from '../util/ColorMixin';
    -import {TextInput} from './TextInput';
    -import {ButtonStim} from './ButtonStim.js';
    -import * as util from '../util/Util';
    -
    -// TODO finish documenting all options
     /**
    - * @name module:visual.TextBox
    - * @class
      * @extends VisualStim
      * @mixes ColorMixin
    - * @param {Object} options
    - * @param {String} options.name - the name used when logging messages from this stimulus
    - * @param {module:core.Window} options.win - the associated Window
    - * @param {string} [options.text=""] - the text to be rendered
    - * @param {string} [options.font= "Arial"] - the font family
    - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text
    - *
    - * @param {Color} [options.color= Color('white')] the background color
    - * @param {number} [options.opacity= 1.0] - the opacity
    - * @param {number} [options.depth= 0] - the depth (i.e. the z order)
    - * @param {number} [options.contrast= 1.0] - the contrast
    - * @param {string} [options.units= "norm"] - the units of the text size and position
    - * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    - * @param {number} [options.height= 0.1] - the height of the text
    - * @param {boolean} [options.bold= false] - whether or not the text is bold
    - * @param {boolean} [options.italic= false] - whether or not the text is italic
    - * @param {string} [options.anchor = 'left'] - horizontal alignment
    - *
    - * @param {boolean} [options.multiline= false] - whether or not a textarea is used
    - * @param {boolean} [options.autofocus= true] - whether or not the first input should receive focus by default
    - * @param {boolean} [options.flipHoriz= false] - whether or not to flip the text horizontally
    - * @param {boolean} [options.flipVert= false] - whether or not to flip the text vertically
    - * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask
    - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    - * @param {boolean} [options.autoLog= false] - whether or not to log
      */
     export class TextBox extends util.mix(VisualStim).with(ColorMixin)
     {
    -	constructor({name, win, pos, anchor, size, units, ori, opacity, depth, text, font, letterHeight, bold, italic, alignment, color, contrast, flipHoriz, flipVert, fillColor, borderColor, borderWidth, padding, editable, multiline, autofocus, clipMask, autoDraw, autoLog} = {})
    +	/**
    +	 * @memberOf module:visual
    +	 * @param {Object} options
    +	 * @param {String} options.name - the name used when logging messages from this stimulus
    +	 * @param {module:core.Window} options.win - the associated Window
    +	 * @param {string} [options.text=""] - the text to be rendered
    +	 * @param {string} [options.font= "Arial"] - the font family
    +	 * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text
    +	 *
    +	 * @param {Color} [options.color= Color('white')] color of the text
    +	 * @param {number} [options.opacity= 1.0] - the opacity
    +	 * @param {number} [options.depth= 0] - the depth (i.e. the z order)
    +	 * @param {number} [options.contrast= 1.0] - the contrast
    +	 * @param {string} [options.units= "norm"] - the units of the text size and position
    +	 * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    +	 * @param {number} [options.letterHeight= <default value>] - the height of the text
    +	 * @param {boolean} [options.bold= false] - whether or not the text is bold
    +	 * @param {boolean} [options.italic= false] - whether or not the text is italic
    +	 * @param {string} [options.anchor = 'left'] - horizontal alignment
    +	 *
    +	 * @param {boolean} [options.multiline= false] - whether or not a multiline element is used
    +	 * @param {boolean} [options.autofocus= true] - whether or not the first input should receive focus by default
    +	 * @param {boolean} [options.flipHoriz= false] - whether or not to flip the text horizontally
    +	 * @param {boolean} [options.flipVert= false] - whether or not to flip the text vertically
    +	 * @param {Color} [options.fillColor= undefined] - fill color of the text-box
    +	 * @param {String} [options.languageStyle= "LTR"] - sets the direction property of the text inputs. Possible values ["LTR", "RTL", "Arabic"]. "Arabic" is added for consistency with PsychoPy
    +	 * @param {Color} [options.borderColor= undefined] - border color of the text-box
    +	 * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask
    +	 * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    +	 * @param {boolean} [options.autoLog= false] - whether or not to log
    +	 * @param {boolean} [options.fitToContent = false] - whether or not to resize itself automaitcally to fit to the text content
    +	 */
    +	constructor(
    +		{
    +			name,
    +			win,
    +			pos,
    +			anchor,
    +			size,
    +			units,
    +			ori,
    +			opacity,
    +			depth,
    +			text,
    +			font,
    +			letterHeight,
    +			bold,
    +			italic,
    +			alignment,
    +			color,
    +			contrast,
    +			flipHoriz,
    +			flipVert,
    +			fillColor,
    +			languageStyle,
    +			borderColor,
    +			borderWidth,
    +			padding,
    +			editable,
    +			multiline,
    +			autofocus,
    +			clipMask,
    +			autoDraw,
    +			autoLog,
    +			fitToContent
    +		} = {},
    +	)
     	{
    -		super({name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog});
    +		super({ name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog });
     
     		this._addAttribute(
    -			'text',
    +			"text",
     			text,
    -			'',
    -			this._onChange(true, true)
    +			""
     		);
     		this._addAttribute(
    -			'placeholder',
    +			"placeholder",
     			text,
    -			'',
    -			this._onChange(true, true)
    -		);		
    +			"",
    +			this._onChange(true, true),
    +		);
     		this._addAttribute(
    -			'anchor',
    +			"anchor",
     			anchor,
    -			'center',
    -			this._onChange(false, true)
    +			"center"
     		);
     		this._addAttribute(
    -			'flipHoriz',
    +			"flipHoriz",
     			flipHoriz,
     			false,
    -			this._onChange(false, false)
    +			this._onChange(false, false),
     		);
     		this._addAttribute(
    -			'flipVert',
    +			"flipVert",
     			flipVert,
     			false,
    -			this._onChange(false, false)
    +			this._onChange(false, false),
     		);
     
     		// font:
     		this._addAttribute(
    -			'font',
    +			"font",
     			font,
    -			'Arial',
    -			this._onChange(true, true)
    +			"Arial"
     		);
     		this._addAttribute(
    -			'letterHeight',
    +			"letterHeight",
     			letterHeight,
    -			this._getDefaultLetterHeight(),
    -			this._onChange(true, true)
    +			this._getDefaultLetterHeight()
     		);
     		this._addAttribute(
    -			'bold',
    +			"bold",
     			bold,
     			false,
    -			this._onChange(true, true)
    +			this._onChange(true, true),
     		);
     		this._addAttribute(
    -			'italic',
    +			"italic",
     			italic,
     			false,
    -			this._onChange(true, true)
    +			this._onChange(true, true),
     		);
     		this._addAttribute(
    -			'alignment',
    +			"alignment",
     			alignment,
    -			'left',
    -			this._onChange(true, true)
    +			"center"
    +		);
    +		this._addAttribute(
    +			"languageStyle",
    +			languageStyle,
    +			"LTR"
     		);
     
     		// colors:
     		this._addAttribute(
    -			'color',
    +			"color",
     			color,
    -			'white',
    -			this._onChange(true, false)
    +			undefined
     		);
     		this._addAttribute(
    -			'fillColor',
    +			"fillColor",
     			fillColor,
    -			'lightgrey',
    -			this._onChange(true, false)
    +			undefined
     		);
     		this._addAttribute(
    -			'borderColor',
    +			"borderColor",
     			borderColor,
    -			this.fillColor,
    -			this._onChange(true, false)
    +			undefined
     		);
     		this._addAttribute(
    -			'contrast',
    +			"contrast",
     			contrast,
     			1.0,
    -			this._onChange(true, false)
    +			this._onChange(true, false),
     		);
     
     		// default border width: 1px
     		this._addAttribute(
    -			'borderWidth',
    +			"borderWidth",
     			borderWidth,
    -			util.to_unit([1, 0], 'pix', win, this._units)[0],
    -			this._onChange(true, true)
    +			util.to_unit([1, 0], "pix", win, this._units)[0],
    +			this._onChange(true, true),
     		);
     		// default padding: half of the letter height
     		this._addAttribute(
    -			'padding',
    +			"padding",
     			padding,
     			this._letterHeight / 2.0,
    -			this._onChange(true, true)
    +			this._onChange(true, true),
     		);
     
    -		this._addAttribute('multiline', multiline, false, this._onChange(true, true));
    -		this._addAttribute('editable', editable, false, this._onChange(true, true));
    -		this._addAttribute('autofocus', autofocus, true, this._onChange(true, false));
    -			// this._setAttribute({
    -			// 	name: 'vertices',
    -			// 	value: vertices,
    -			// 	assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) )
    -			// 	log);
    +		this._addAttribute("multiline", multiline, false, this._onChange(true, true));
    +		this._addAttribute("editable", editable, false, this._onChange(true, true));
    +		this._addAttribute("autofocus", autofocus, true, this._onChange(true, false));
    +		// this._setAttribute({
    +		// 	name: 'vertices',
    +		// 	value: vertices,
    +		// 	assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) )
    +		// 	log);
    +
    +		this._addAttribute("fitToContent", fitToContent, false);
    +		// setting size again since fitToContent field becomes available only at this point
    +		// and setSize called from super class would not have a proper effect
    +		this.setSize(size);
     
     		// estimate the bounding box:
     		this._estimateBoundingBox();
    @@ -204,45 +267,84 @@ 

    Source: visual/TextBox.js

    } } - /** * Clears the current text value or sets it back to match the placeholder. - * - * @name module:visual.TextBox#reset - * @public */ reset() { - const text = this.editable ? '' : this.placeholder; - this.setText(this.placeholder); } - - /** * Clears the current text value. - * - * @name module:visual.TextBox#clear - * @public */ clear() { this.setText(); } + /** + * Setter for the alignment attribute. + * + * @param {boolean} alignment - alignment of the text + * @param {boolean} [log= false] - whether or not to log + */ + setAlignment(alignment = "center", log = false) + { + this._setAttribute("alignment", alignment, log); + if (this._pixi !== undefined) { + let alignmentStyles = TextBox._alignmentToFlexboxMap.get(alignment); + if (!alignmentStyles) { + alignmentStyles = ["center", "center"]; + } + this._pixi.setInputStyle("justifyContent", alignmentStyles[0]); + this._pixi.setInputStyle("textAlign", alignmentStyles[1]); + } + } + /** + * Setter for the languageStyle attribute. + * + * @param {String} languageStyle - text direction in textbox, accepts values ["LTR", "RTL", "Arabic"] + * @param {boolean} [log= false] - whether or not to log + */ + setLanguageStyle (languageStyle = "LTR", log = false) { + this._setAttribute("languageStyle", languageStyle, log); + let langDir = util.TEXT_DIRECTION[languageStyle]; + if (langDir === undefined) + { + langDir = util.TEXT_DIRECTION["LTR"]; + } + if (this._pixi !== undefined) + { + this._pixi.setInputStyle("direction", langDir); + } + } + + /** + * Setter for the anchor attribute. + * + * @param {boolean} anchor - anchor of the textbox + * @param {boolean} [log= false] - whether or not to log + */ + setAnchor (anchor = "center", log = false) + { + this._setAttribute("anchor", anchor, log); + if (this._pixi !== undefined) { + const anchorUnits = this._getAnchor(); + this._pixi.anchor.x = anchorUnits[0]; + this._pixi.anchor.y = anchorUnits[1]; + } + } /** * For tweaking the underlying input value. * - * @name module:visual.TextBox#setText - * @public * @param {string} text */ - setText(text = '') + setText(text = "") { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.text = text; } @@ -250,17 +352,45 @@

    Source: visual/TextBox.js

    this._text = text; } + /** + * Set the font for textbox. + * + * @param {string} font - the font family + * @param {boolean} [log = false] - whether to log + */ + setFont(font = "Arial", log = false) + { + this._setAttribute("font", font, log); + if (this._pixi !== undefined) + { + this._pixi.setInputStyle("fontFamily", font); + } + } + + /** + * Set letterHeight (font size) for textbox. + * + * @param {string} [fontSize = <default value>] - the size of the font + * @param {boolean} [log = false] - whether to log + */ + setLetterHeight(fontSize = this._getDefaultLetterHeight(), log = false) + { + this._setAttribute("letterHeight", fontSize, log); + const fontSize_px = this._getLengthPix(fontSize); + if (this._pixi !== undefined) + { + this._pixi.setInputStyle("fontSize", `${fontSize_px}px`); + } + } /** * For accessing the underlying input value. * - * @name module:visual.TextBox#getText - * @public * @return {string} - the current text value of the underlying input element. */ getText() { - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { return this._pixi.text; } @@ -268,38 +398,93 @@

    Source: visual/TextBox.js

    return this._text; } + /** + * Setter for the color attribute. + * + * @param {boolean} color - color of the text + * @param {boolean} [log= false] - whether or not to log + */ + setColor (color, log = false) + { + this._setAttribute('color', color, log); + this._needUpdate = true; + this._needPixiUpdate = true; + } + + /** + * Setter for the fillColor attribute. + * + * @param {boolean} fillColor - fill color of the text box + * @param {boolean} [log= false] - whether or not to log + */ + setFillColor (fillColor, log = false) + { + this._setAttribute('fillColor', fillColor, log); + this._needUpdate = true; + this._needPixiUpdate = true; + } + + /** + * Setter for the borderColor attribute. + * + * @param {Color} borderColor - border color of the text box + * @param {boolean} [log= false] - whether or not to log + */ + setBorderColor (borderColor, log = false) + { + this._setAttribute('borderColor', borderColor, log); + this._needUpdate = true; + this._needPixiUpdate = true; + } + + /** + * Setter for the fitToContent attribute. + * + * @param {boolean} fitToContent - whether or not to autoresize textbox to fit to text content + * @param {boolean} [log= false] - whether or not to log + */ + setFitToContent (fitToContent, log = false) + { + this._setAttribute("fitToContent", fitToContent, log); + const width_px = Math.abs(Math.round(this._getLengthPix(this._size[0]))); + const height_px = Math.abs(Math.round(this._getLengthPix(this._size[1]))); + if (this._pixi !== undefined) { + this._pixi.setInputStyle("width", fitToContent ? "auto" : `${width_px}px`); + this._pixi.setInputStyle("height", fitToContent ? "auto" : `${height_px}px`); + } + } /** * Setter for the size attribute. * - * @name module:visual.TextBox#setSize - * @public * @param {boolean} size - whether or not to wrap the text at the given width - * @param {boolean} [log= false] - whether of not to log + * @param {boolean} [log= false] - whether or not to log */ setSize(size, log) { // test with the size is undefined, or [undefined, undefined]: let isSizeUndefined = ( - (typeof size === 'undefined') || (size === null) || - ( Array.isArray(size) && size.every( v => typeof v === 'undefined' || v === null) ) - ); + (typeof size === "undefined") || (size === null) + || (Array.isArray(size) && size.every((v) => typeof v === "undefined" || v === null)) + ); + + this.fitToContent = isSizeUndefined; if (isSizeUndefined) { size = TextBox._defaultSizeMap.get(this._units); - if (typeof size === 'undefined') + if (typeof size === "undefined") { throw { - origin: 'TextBox.setSize', - context: 'when setting the size of TextBox: ' + this._name, - error: 'no default size for unit: ' + this._units + origin: "TextBox.setSize", + context: "when setting the size of TextBox: " + this._name, + error: "no default size for unit: " + this._units, }; } } - const hasChanged = this._setAttribute('size', size, log); + const hasChanged = this._setAttribute("size", size, log); if (hasChanged) { @@ -311,12 +496,27 @@

    Source: visual/TextBox.js

    } } - + /** + * Add event listeners to text-box object. Method is called internally upon object construction. + * + * @protected + */ + _addEventListeners () + { + this._pixi.on("input", (textContent) => { + this._text = textContent; + if (this._fitToContent) + { + // make sure that size attribute is updated when fitToContent = true + const size = util.to_unit([this._pixi.width, this._pixi.height], "pix", this._win, this._units); + this._setAttribute("size", size, false); + } + }); + } /** * Get the default letter height given the stimulus' units. * - * @name module:visual.TextBox#_getDefaultLetterHeight * @return {number} - the letter height corresponding to this stimulus' units. * @protected */ @@ -324,56 +524,68 @@

    Source: visual/TextBox.js

    { const height = TextBox._defaultLetterHeightMap.get(this._units); - if (typeof height === 'undefined') + if (typeof height === "undefined") { throw { - origin: 'TextBox._getDefaultLetterHeight', - context: 'when getting the default height of TextBox: ' + this._name, - error: 'no default letter height for unit: ' + this._units + origin: "TextBox._getDefaultLetterHeight", + context: "when getting the default height of TextBox: " + this._name, + error: "no default letter height for unit: " + this._units, }; } return height; } - - /** * Get the TextInput options applied to the PIXI.TextInput. * - * @name module:visual.TextBox#_getTextInputOptions - * @private + * @protected */ _getTextInputOptions() { const letterHeight_px = Math.round(this._getLengthPix(this._letterHeight)); const padding_px = Math.round(this._getLengthPix(this._padding)); - const width_px = Math.round(this._getLengthPix(this._size[0])); const borderWidth_px = Math.round(this._getLengthPix(this._borderWidth)); - const height_px = Math.round(this._getLengthPix(this._size[1])); - const multiline = this._multiline; + const width_px = Math.abs(Math.round(this._getLengthPix(this._size[0]))); + const height_px = Math.abs(Math.round(this._getLengthPix(this._size[1]))); + let alignmentStyles = TextBox._alignmentToFlexboxMap.get(this._alignment); + if (!alignmentStyles) { + alignmentStyles = ["center", "center"]; + } return { + // input style properties eventually become CSS, so same syntax applies input: { + display: "flex", + flexDirection: "column", fontFamily: this._font, - fontSize: letterHeight_px + 'px', - color: new Color(this._color).hex, - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', - - padding: padding_px + 'px', - multiline, + fontSize: `${letterHeight_px}px`, + color: this._color === undefined || this._color === null ? 'transparent' : new Color(this._color).hex, + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", + direction: util.TEXT_DIRECTION[this._languageStyle], + justifyContent: alignmentStyles[0], + textAlign: alignmentStyles[1], + padding: `${padding_px}px`, + multiline: this._multiline, text: this._text, - height: multiline ? (height_px - 2 * padding_px) + 'px' : undefined, - width: (width_px - 2 * padding_px) + 'px' + height: this._fitToContent ? "auto" : (this._multiline ? `${height_px}px` : undefined), + width: this._fitToContent ? "auto" : `${width_px}px`, + maxWidth: `${this.win.size[0]}px`, + maxHeight: `${this.win.size[1]}px`, + overflow: "hidden", + pointerEvents: "none" }, + // box style properties eventually become PIXI.Graphics settings, so same syntax applies box: { fill: new Color(this._fillColor).int, + alpha: this._fillColor === undefined || this._fillColor === null ? 0 : 1, rounded: 5, stroke: { color: new Color(this._borderColor).int, - width: borderWidth_px - } + width: borderWidth_px, + alpha: this._borderColor === undefined || this._borderColor === null ? 0 : 1 + }, /*default: { fill: new Color(this._fillColor).int, rounded: 5, @@ -398,17 +610,13 @@

    Source: visual/TextBox.js

    width: borderWidth_px } }*/ - } + }, }; } - - /** * Estimate the bounding box. * - * @name module:visual.TextBox#_estimateBoundingBox - * @function * @override * @protected */ @@ -423,20 +631,16 @@

    Source: visual/TextBox.js

    this._pos[0] - anchor[0] * this._size[0], this._pos[1] - anchor[1] * boxHeight, this._size[0], - boxHeight + boxHeight, ); // TODO take the orientation into account } - - /** * Update the stimulus, if necessary. * - * @name module:visual.TextBox#_updateIfNeeded - * @private - * + * @protected * @todo take size into account */ _updateIfNeeded() @@ -452,24 +656,33 @@

    Source: visual/TextBox.js

    { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + let enteredText = ""; + // at this point this._pixi might exist but is removed from the scene, in such cases this._pixi.text + // does not retain the information about new lines etc. so we go with a local copy of entered text + if (this._pixi !== undefined && this._pixi.parent !== null) { + enteredText = this._pixi.text; + } else { + enteredText = this._text; + } + + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } - // Get the currently entered text - let enteredText = this._pixi !== undefined? this._pixi.text: ''; - // Create new TextInput + + // Create new TextInput this._pixi = new TextInput(this._getTextInputOptions()); // listeners required for regular textboxes, but may cause problems with button stimuli if (!(this instanceof ButtonStim)) { this._pixi._addListeners(); + this._addEventListeners(); } // check if other TextBox instances are already in focus const { _drawList = [] } = this.psychoJS.window; - const otherTextBoxWithFocus = _drawList.some(item => item instanceof TextBox && item._pixi && item._pixi._hasFocus()); + const otherTextBoxWithFocus = _drawList.some((item) => item instanceof TextBox && item._pixi && item._pixi._hasFocus()); if (this._autofocus && !otherTextBoxWithFocus) { this._pixi._onSurrogateFocus(); @@ -480,7 +693,7 @@

    Source: visual/TextBox.js

    } if (this._editable) { - this.text = enteredText; + this.text = enteredText; this._pixi.placeholder = this._placeholder; } else @@ -491,29 +704,23 @@

    Source: visual/TextBox.js

    this._pixi.disabled = !this._editable; - const anchor = this._getAnchor(); - this._pixi.pivot.x = anchor[0] * this._pixi.width; - this._pixi.pivot.y = anchor[1] * this._pixi.height; - + // now when this._pixi is available, setting anchor again to trigger internal to this._pixi mechanisms + this.anchor = this._anchor; this._pixi.scale.x = this._flipHoriz ? -1 : 1; this._pixi.scale.y = this._flipVert ? 1 : -1; - this._pixi.rotation = this._ori * Math.PI / 180; + this._pixi.rotation = -this._ori * Math.PI / 180; [this._pixi.x, this._pixi.y] = util.to_px(this._pos, this._units, this._win); this._pixi.alpha = this._opacity; - this._pixi.zIndex = this._depth; + this._pixi.zIndex = -this._depth; // apply the clip mask: this._pixi.mask = this._clipMask; } - - /** * Convert the anchor attribute into numerical values. * - * @name module:visual.TextBox#_getAnchor - * @function * @protected * @return {number[]} - the anchor, as an array of numbers in [0,1] */ @@ -521,67 +728,73 @@

    Source: visual/TextBox.js

    { const anchor = [0.5, 0.5]; - if (this._anchor.indexOf('left') > -1) + if (this._anchor.indexOf("left") > -1) { anchor[0] = 0; } - else if (this._anchor.indexOf('right') > -1) + else if (this._anchor.indexOf("right") > -1) { anchor[0] = 1; } - if (this._anchor.indexOf('top') > -1) + if (this._anchor.indexOf("top") > -1) { anchor[1] = 0; } - else if (this._anchor.indexOf('bottom') > -1) + else if (this._anchor.indexOf("bottom") > -1) { anchor[1] = 1; } return anchor; } - - } +TextBox._alignmentToFlexboxMap = new Map([ + ["center", ["center", "center"]], + ["top-center", ["flex-start", "center"]], + ["bottom-center", ["flex-end", "center"]], + ["center-left", ["center", "left"]], + ["center-right", ["center", "right"]], + ["top-left", ["flex-start", "left"]], + ["top-right", ["flex-start", "right"]], + ["bottom-left", ["flex-end", "left"]], + ["bottom-right", ["flex-end", "right"]] +]); /** * <p>This map associates units to default letter height.</p> * - * @name module:visual.TextBox#_defaultLetterHeightMap * @readonly - * @private + * @protected */ TextBox._defaultLetterHeightMap = new Map([ - ['cm', 1.0], - ['deg', 1.0], - ['degs', 1.0], - ['degFlatPos', 1.0], - ['degFlat', 1.0], - ['norm', 0.1], - ['height', 0.2], - ['pix', 20], - ['pixels', 20] + ["cm", 1.0], + ["deg", 1.0], + ["degs", 1.0], + ["degFlatPos", 1.0], + ["degFlat", 1.0], + ["norm", 0.1], + ["height", 0.2], + ["pix", 20], + ["pixels", 20], ]); - /** * <p>This map associates units to default sizes.</p> * - * @name module:visual.TextBox#_defaultSizeMap * @readonly - * @private + * @protected */ TextBox._defaultSizeMap = new Map([ - ['cm', [15.0, -1]], - ['deg', [15.0, -1]], - ['degs', [15.0, -1]], - ['degFlatPos', [15.0, -1]], - ['degFlat', [15.0, -1]], - ['norm', [1, -1]], - ['height', [1, -1]], - ['pix', [500, -1]], - ['pixels', [500, -1]] + ["cm", [15.0, -1]], + ["deg", [15.0, -1]], + ["degs", [15.0, -1]], + ["degFlatPos", [15.0, -1]], + ["degFlat", [15.0, -1]], + ["norm", [1, -1]], + ["height", [1, -1]], + ["pix", [500, -1]], + ["pixels", [500, -1]], ]);
    @@ -590,19 +803,23 @@

    Source: visual/TextBox.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_TextStim.js.html b/docs/visual_TextStim.js.html index f9c5d69a..4007ef45 100644 --- a/docs/visual_TextStim.js.html +++ b/docs/visual_TextStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/TextStim.js - - - + visual/TextStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: visual/TextStim.js

    +
    + +

    visual/TextStim.js

    + @@ -30,55 +54,81 @@

    Source: visual/TextStim.js

    * Text Stimulus. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ - -import * as PIXI from 'pixi.js-legacy'; -import {VisualStim} from './VisualStim'; -import {Color} from '../util/Color'; -import {ColorMixin} from '../util/ColorMixin'; -import * as util from '../util/Util'; - +import * as PIXI from "pixi.js-legacy"; +import { Color } from "../util/Color.js"; +import { ColorMixin } from "../util/ColorMixin.js"; +import { to_pixiPoint } from "../util/Pixi.js"; +import * as util from "../util/Util.js"; +import { VisualStim } from "./VisualStim.js"; /** - * @name module:visual.TextStim - * @class + * <p>TextStim handles text stimuli.</p> + * * @extends VisualStim * @mixes ColorMixin - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {string} [options.text="Hello World"] - the text to be rendered - * @param {string} [options.font= "Arial"] - the font family - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text - * @param {Color} [options.color= 'white'] the background color - * @param {number} [options.opacity= 1.0] - the opacity - * @param {number} [options.depth= 0] - the depth (i.e. the z order) - * @param {number} [options.contrast= 1.0] - the contrast - * @param {string} [options.units= "norm"] - the units of the text size and position - * @param {number} options.ori - the orientation (in degrees) - * @param {number} [options.height= 0.1] - the height of the text - * @param {boolean} [options.bold= false] - whether or not the text is bold - * @param {boolean} [options.italic= false] - whether or not the text is italic - * @param {string} [options.alignHoriz = 'center'] - horizontal alignment - * @param {string} [options.alignVert = 'center'] - vertical alignment - * @param {boolean} options.wrapWidth - whether or not to wrap the text horizontally - * @param {boolean} [options.flipHoriz= false] - whether or not to flip the text horizontally - * @param {boolean} [options.flipVert= false] - whether or not to flip the text vertically - * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= false] - whether or not to log - * * @todo vertical alignment, and orientation are currently NOT implemented */ export class TextStim extends util.mix(VisualStim).with(ColorMixin) { - constructor({name, win, text, font, pos, color, opacity, depth, contrast, units, ori, height, bold, italic, alignHoriz, alignVert, wrapWidth, flipHoriz, flipVert, clipMask, autoDraw, autoLog} = {}) + /** + * @memberOf module:visual + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {string} [options.text="Hello World"] - the text to be rendered + * @param {string} [options.font= "Arial"] - the font family + * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text + * @param {Color} [options.color= 'white'] the background color + * @param {number} [options.opacity= 1.0] - the opacity + * @param {number} [options.depth= 0] - the depth (i.e. the z order) + * @param {number} [options.contrast= 1.0] - the contrast + * @param {string} [options.units= "norm"] - the units of the text size and position + * @param {number} options.ori - the orientation (in degrees) + * @param {number} [options.height= 0.1] - the height of the text + * @param {boolean} [options.bold= false] - whether or not the text is bold + * @param {boolean} [options.italic= false] - whether or not the text is italic + * @param {string} [options.alignHoriz = 'center'] - horizontal alignment + * @param {string} [options.alignVert = 'center'] - vertical alignment + * @param {boolean} options.wrapWidth - whether or not to wrap the text horizontally + * @param {boolean} [options.flipHoriz= false] - whether or not to flip the text horizontally + * @param {boolean} [options.flipVert= false] - whether or not to flip the text vertically + * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= false] - whether or not to log + */ + constructor( + { + name, + win, + text, + font, + pos, + color, + opacity, + depth, + contrast, + units, + ori, + height, + bold, + italic, + alignHoriz, + alignVert, + wrapWidth, + flipHoriz, + flipVert, + clipMask, + autoDraw, + autoLog, + } = {}, + ) { - super({name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog}); + super({ name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog }); // callback to deal with text metrics invalidation: const onChange = (withPixi = false, withBoundingBox = false, withMetrics = false) => @@ -96,81 +146,78 @@

    Source: visual/TextStim.js

    // text and font: this._addAttribute( - 'text', + "text", text, - 'Hello World', - onChange(true, true, true) + "Hello World", + onChange(true, true, true), ); this._addAttribute( - 'alignHoriz', + "alignHoriz", alignHoriz, - 'center', - onChange(true, true, true) + "center", + onChange(true, true, true), ); this._addAttribute( - 'alignVert', + "alignVert", alignVert, - 'center', - onChange(true, true, true) + "center", + onChange(true, true, true), ); this._addAttribute( - 'flipHoriz', + "flipHoriz", flipHoriz, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'flipVert', + "flipVert", flipVert, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'font', + "font", font, - 'Arial', - this._onChange(true, true) + "Arial", + this._onChange(true, true), ); this._addAttribute( - 'height', + "height", height, this._getDefaultLetterHeight(), - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'wrapWidth', + "wrapWidth", wrapWidth, this._getDefaultWrapWidth(), - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'bold', + "bold", bold, false, - onChange(true, true, true) + onChange(true, true, true), ); this._addAttribute( - 'italic', + "italic", italic, false, - onChange(true, true, true) + onChange(true, true, true), ); - - // color: this._addAttribute( - 'color', + "color", color, - 'white', - this._onChange(true, false) + "white" + // this._onChange(true, false) ); this._addAttribute( - 'contrast', + "contrast", contrast, 1.0, this._onChange(true, false) ); - // estimate the bounding box (using TextMetrics): this._estimateBoundingBox(); @@ -180,84 +227,144 @@

    Source: visual/TextStim.js

    } } - - /** * Get the metrics estimated for the text and style. * - * Note: getTextMetrics does not require the PIXI representation of the stimulus to be instantiated, - * unlike getSize(). - * - * @name module:visual.TextStim#getTextMetrics - * @public + * Note: getTextMetrics does not require the PIXI representation of the stimulus + * to be instantiated, unlike getSize(). */ getTextMetrics() { - if (typeof this._textMetrics === 'undefined') + if (typeof this._textMetrics === "undefined") { this._textMetrics = PIXI.TextMetrics.measureText(this._text, this._getTextStyle()); + + // since PIXI.TextMetrics does not give us the actual bounding box of the text + // (e.g. the height is really just the ascent + descent of the font), we use measureText: + const textMetricsCanvas = document.createElement('canvas'); + document.body.appendChild(textMetricsCanvas); + + const ctx = textMetricsCanvas.getContext("2d"); + ctx.font = this._getTextStyle().toFontString(); + ctx.textBaseline = "alphabetic"; + ctx.textAlign = "left"; + this._textMetrics.boundingBox = ctx.measureText(this._text); + + document.body.removeChild(textMetricsCanvas); } return this._textMetrics; } - - /** * Get the default letter height given the stimulus' units. * - * @name module:visual.TextStim#_getDefaultLetterHeight - * @return {number} - the letter height corresponding to this stimulus' units. * @protected */ _getDefaultLetterHeight() { const height = TextStim._defaultLetterHeightMap.get(this._units); - if (typeof height === 'undefined') + if (typeof height === "undefined") { throw { - origin: 'TextStim._getDefaultLetterHeight', - context: 'when getting the default height of TextStim: ' + this._name, - error: 'no default letter height for unit: ' + this._units + origin: "TextStim._getDefaultLetterHeight", + context: "when getting the default height of TextStim: " + this._name, + error: "no default letter height for unit: " + this._units, }; } return height; } - - /** * Get the default wrap width given the stimulus' units. * - * @name module:visual.TextStim#_getDefaultWrapWidth - * @return {number} - the wrap width corresponding to this stimulus' units. * @protected */ _getDefaultWrapWidth() { const wrapWidth = TextStim._defaultWrapWidthMap.get(this._units); - if (typeof wrapWidth === 'undefined') + if (typeof wrapWidth === "undefined") { throw { - origin: 'TextStim._getDefaultWrapWidth', - context: 'when getting the default wrap width of TextStim: ' + this._name, - error: 'no default wrap width for unit: ' + this._units + origin: "TextStim._getDefaultWrapWidth", + context: "when getting the default wrap width of TextStim: " + this._name, + error: "no default wrap width for unit: " + this._units, }; } return wrapWidth; } + /** + * Get the bounding gox. + * + * @protected + * @param {boolean} [tight= false] - whether or not to fit as closely as possible to the text + * @return {number[]} - the bounding box, in the units of this TextStim + */ + getBoundingBox(tight = false) + { + if (tight) + { + const textMetrics_px = this.getTextMetrics(); + let left_px = this._pos[0] - textMetrics_px.boundingBox.actualBoundingBoxLeft; + let top_px = this._pos[1] + textMetrics_px.fontProperties.descent - textMetrics_px.boundingBox.actualBoundingBoxDescent; + const width_px = textMetrics_px.boundingBox.actualBoundingBoxRight + textMetrics_px.boundingBox.actualBoundingBoxLeft; + const height_px = textMetrics_px.boundingBox.actualBoundingBoxAscent + textMetrics_px.boundingBox.actualBoundingBoxDescent; + + // adjust the bounding box position by taking into account the anchoring of the text: + const boundingBox_px = this._getBoundingBox_px(); + switch (this._alignHoriz) + { + case "left": + // nothing to do + break; + case "right": + // TODO + break; + default: + case "center": + left_px -= (boundingBox_px.width - width_px) / 2; + } + switch (this._alignVert) + { + case "top": + // TODO + break; + case "bottom": + // nothing to do + break; + default: + case "center": + top_px -= (boundingBox_px.height - height_px) / 2; + } + // convert from pixel to this stimulus' units: + const leftTop = util.to_unit( + [left_px, top_px], + "pix", + this._win, + this._units); + const dimensions = util.to_unit( + [width_px, height_px], + "pix", + this._win, + this._units); + + return new PIXI.Rectangle(leftTop[0], leftTop[1], dimensions[0], dimensions[1]); + } + else + { + return this._boundingBox.clone(); + } + } /** * Estimate the bounding box. * - * @name module:visual.TextStim#_estimateBoundingBox - * @function * @override * @protected */ @@ -265,55 +372,68 @@

    Source: visual/TextStim.js

    { // size of the text, irrespective of the orientation: const textMetrics = this.getTextMetrics(); - const textSize = util.to_unit( + const textSize = util.to_unit( [textMetrics.width, textMetrics.height], - 'pix', + "pix", this._win, - this._units + this._units, ); // take the alignment into account: const anchor = this._getAnchor(); this._boundingBox = new PIXI.Rectangle( this._pos[0] - anchor[0] * textSize[0], - this._pos[1] - anchor[1] * textSize[1], + this._pos[1] - textSize[1] + anchor[1] * textSize[1], textSize[0], - textSize[1] + textSize[1], ); // TODO take the orientation into account } - - /** * Get the PIXI Text Style applied to the PIXI.Text * - * @name module:visual.TextStim#_getTextStyle - * @private + * @protected */ _getTextStyle() { return new PIXI.TextStyle({ fontFamily: this._font, fontSize: Math.round(this._getLengthPix(this._height)), - fontWeight: (this._bold) ? 'bold' : 'normal', - fontStyle: (this._italic) ? 'italic' : 'normal', + fontWeight: (this._bold) ? "bold" : "normal", + fontStyle: (this._italic) ? "italic" : "normal", fill: this.getContrastedColor(new Color(this._color), this._contrast).hex, align: this._alignHoriz, - wordWrap: (typeof this._wrapWidth !== 'undefined'), - wordWrapWidth: (typeof this._wrapWidth !== 'undefined') ? this._getHorLengthPix(this._wrapWidth) : 0 + wordWrap: (typeof this._wrapWidth !== "undefined"), + wordWrapWidth: (typeof this._wrapWidth !== "undefined") ? this._getHorLengthPix(this._wrapWidth) : 0, }); } + /** + * Setter for the color attribute. + * + * @param {undefined | null | number} color - the color + * @param {boolean} [log= false] - whether of not to log + */ + setColor(color, log = false) + { + const hasChanged = this._setAttribute("color", color, log); + if (hasChanged) + { + if (typeof this._pixi !== "undefined") + { + this._pixi.style = this._getTextStyle(); + this._needUpdate = true; + } + } + } /** * Update the stimulus, if necessary. * - * @name module:visual.TextStim#_updateIfNeeded - * @function - * @private + * @protected */ _updateIfNeeded() { @@ -328,11 +448,13 @@

    Source: visual/TextStim.js

    { this._needPixiUpdate = false; - if (typeof this._pixi !== 'undefined') + if (typeof this._pixi !== "undefined") { this._pixi.destroy(true); } this._pixi = new PIXI.Text(this._text, this._getTextStyle()); + // TODO is updateText necessary? + // this._pixi.updateText(); } const anchor = this._getAnchor(); @@ -341,38 +463,36 @@

    Source: visual/TextStim.js

    this._pixi.scale.x = this._flipHoriz ? -1 : 1; this._pixi.scale.y = this._flipVert ? 1 : -1; - this._pixi.rotation = this._ori * Math.PI / 180; - this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win); + this._pixi.rotation = -this._ori * Math.PI / 180; + this._pixi.position = to_pixiPoint(this.pos, this.units, this.win); this._pixi.alpha = this._opacity; - this._pixi.zIndex = this._depth; + this._pixi.zIndex = -this._depth; // apply the clip mask: this._pixi.mask = this._clipMask; - // update the size attributes: - this._size = [ - this._getLengthUnits(Math.abs(this._pixi.width)), - this._getLengthUnits(Math.abs(this._pixi.height)) - ]; + // update the size attribute: + this._size = util.to_unit( + [Math.abs(this._pixi.width), Math.abs(this._pixi.height)], + "pix", + this._win, + this._units + ); // refine the estimate of the bounding box: this._boundingBox = new PIXI.Rectangle( this._pos[0] - anchor[0] * this._size[0], - this._pos[1] - anchor[1] * this._size[1], + this._pos[1] - this._size[1] + anchor[1] * this._size[1], this._size[0], - this._size[1] + this._size[1], ); } - - /** * Convert the alignment attributes into an anchor. * - * @name module:visual.TextStim#_getAnchor - * @function - * @private + * @protected * @return {number[]} - the anchor */ _getAnchor() @@ -381,74 +501,67 @@

    Source: visual/TextStim.js

    switch (this._alignHoriz) { - case 'left': + case "left": anchor.push(0); break; - case 'right': + case "right": anchor.push(1); break; default: - case 'center': + case "center": anchor.push(0.5); } switch (this._alignVert) { - case 'top': + case "top": anchor.push(0); break; - case 'bottom': + case "bottom": anchor.push(1); break; default: - case 'center': + case "center": anchor.push(0.5); } return anchor; } - } - - /** * <p>This map associates units to default letter height.</p> * - * @name module:visual.TextStim#_defaultLetterHeightMap * @readonly - * @private + * @protected */ TextStim._defaultLetterHeightMap = new Map([ - ['cm', 1.0], - ['deg', 1.0], - ['degs', 1.0], - ['degFlatPos', 1.0], - ['degFlat', 1.0], - ['norm', 0.1], - ['height', 0.2], - ['pix', 20], - ['pixels', 20] + ["cm", 1.0], + ["deg", 1.0], + ["degs", 1.0], + ["degFlatPos", 1.0], + ["degFlat", 1.0], + ["norm", 0.1], + ["height", 0.2], + ["pix", 20], + ["pixels", 20], ]); - - /** * <p>This map associates units to default wrap width.</p> * - * @name module:visual.TextStim#_defaultLetterHeightMap * @readonly - * @private + * @protected */ TextStim._defaultWrapWidthMap = new Map([ - ['cm', 15.0], - ['deg', 15.0], - ['degs', 15.0], - ['degFlatPos', 15.0], - ['degFlat', 15.0], - ['norm', 1], - ['height', 1], - ['pix', 500], - ['pixels', 500] + ["cm", 15.0], + ["deg", 15.0], + ["degs", 15.0], + ["degFlatPos", 15.0], + ["degFlat", 15.0], + ["norm", 1], + ["height", 1], + ["pix", 500], + ["pixels", 500], ]); @@ -457,19 +570,23 @@

    Source: visual/TextStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/docs/visual_VisualStim.js.html b/docs/visual_VisualStim.js.html index e7a8429c..e8be6178 100644 --- a/docs/visual_VisualStim.js.html +++ b/docs/visual_VisualStim.js.html @@ -1,23 +1,47 @@ + - JSDoc: Source: visual/VisualStim.js - - - + visual/VisualStim.js - PsychoJS API + + + + + + + + + + - - + + + + - -
    + + + + + + -

    Source: visual/VisualStim.js

    +
    + +

    visual/VisualStim.js

    + @@ -26,93 +50,91 @@

    Source: visual/VisualStim.js

    -
    /**
    +            
    /** @module visual **/
    +/**
      * Base class for all visual stimuli.
      *
      * @author Alain Pitiot
    - * @version 2021.2.0
    - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org)
    + * @version 2022.2.0
    + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
      * @license Distributed under the terms of the MIT License
      */
     
    -
    -import * as PIXI from 'pixi.js-legacy';
    -import {MinimalStim} from '../core/MinimalStim';
    -import {WindowMixin} from '../core/WindowMixin';
    -import * as util from '../util/Util';
    -
    +import * as PIXI from "pixi.js-legacy";
    +import { MinimalStim } from "../core/MinimalStim.js";
    +import { WindowMixin } from "../core/WindowMixin.js";
    +import * as util from "../util/Util.js";
     
     /**
      * Base class for all visual stimuli.
      *
    - * @name module:visual.VisualStim
    - * @class
      * @extends MinimalStim
      * @mixes WindowMixin
    - * @param {Object} options
    - * @param {String} options.name - the name used when logging messages from this stimulus
    - * @param {module:core.Window} options.win - the associated Window
    - * @param {string} [options.units= "height"] - the units of the stimulus (e.g. for size, position, vertices)
    - * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    - * @param {number} [options.opacity= 1.0] - the opacity
    - * @param {number} [options.depth= 0] - the depth (i.e. the z order)
    - * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
    - * @param {number} [options.size= 1.0] - the size
    - * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask
    - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    - * @param {boolean} [options.autoLog= false] - whether or not to log
      */
     export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
     {
    -	constructor({name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog} = {})
    +	/**
    +	 * @param {Object} options
    +	 * @param {String} options.name - the name used when logging messages from this stimulus
    +	 * @param {module:core.Window} options.win - the associated Window
    +	 * @param {string} [options.units= "height"] - the units of the stimulus (e.g. for size, position, vertices)
    +	 * @param {number} [options.ori= 0.0] - the orientation (in degrees)
    +	 * @param {number} [options.opacity= 1.0] - the opacity
    +	 * @param {number} [options.depth= 0] - the depth (i.e. the z order)
    +	 * @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
    +	 * @param {number} [options.size= 1.0] - the size
    +	 * @param {PIXI.Graphics} [options.clipMask= null] - the clip mask
    +	 * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
    +	 * @param {boolean} [options.autoLog= false] - whether or not to log
    +	 */
    +	constructor({ name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog } = {})
     	{
    -		super({win, name, autoDraw, autoLog});
    +		super({ win, name, autoDraw, autoLog });
     
     		this._addAttribute(
    -			'units',
    +			"units",
     			units,
    -			(typeof win !== 'undefined' && win !== null) ? win.units : 'height',
    -			this._onChange(true, true)
    +			(typeof win !== "undefined" && win !== null) ? win.units : "height",
    +			this._onChange(true, true),
     		);
     		this._addAttribute(
    -			'pos',
    +			"pos",
     			pos,
    -			[0, 0]
    +			[0, 0],
     		);
     		this._addAttribute(
    -			'size',
    +			"size",
     			size,
    -			undefined
    +			undefined,
     		);
     		this._addAttribute(
    -			'ori',
    +			"ori",
     			ori,
    -			0.0
    +			0.0,
     		);
     		this._addAttribute(
    -			'opacity',
    +			"opacity",
     			opacity,
     			1.0,
    -			this._onChange(true, false)
    +			this._onChange(true, false),
     		);
     		this._addAttribute(
    -			'depth',
    +			"depth",
     			depth,
     			0,
    -			this._onChange(false, false)
    +			this._onChange(false, false),
     		);
     		this._addAttribute(
    -			'clipMask',
    +			"clipMask",
     			clipMask,
     			null,
    -			this._onChange(false, false)
    +			this._onChange(false, false),
     		);
     
     		// bounding box of the stimulus, in stimulus units
     		// note: boundingBox does not take the orientation into account
    -		this._addAttribute('boundingBox', PIXI.Rectangle.EMPTY);
    +		this._addAttribute("boundingBox", PIXI.Rectangle.EMPTY);
     
    -		
     		// the stimulus need to be updated:
     		this._needUpdate = true;
     
    @@ -120,35 +142,26 @@ 

    Source: visual/VisualStim.js

    this._needPixiUpdate = true; } - - /** * Force a refresh of the stimulus. * * refresh() is called, in particular, when the Window is resized. - * - * @name module:visual.VisualStim#refresh - * @public */ refresh() { this._onChange(true, true)(); } - - /** * Setter for the size attribute. * - * @name module:visual.VisualStim#setSize - * @public * @param {undefined | null | number | number[]} size - the stimulus size * @param {boolean} [log= false] - whether of not to log */ setSize(size, log = false) { // size is either undefined, null, or a tuple of numbers: - if (typeof size !== 'undefined' && size !== null) + if (typeof size !== "undefined" && size !== null) { size = util.toNumerical(size); if (!Array.isArray(size)) @@ -157,7 +170,7 @@

    Source: visual/VisualStim.js

    } } - const hasChanged = this._setAttribute('size', size, log); + const hasChanged = this._setAttribute("size", size, log); if (hasChanged) { @@ -165,62 +178,69 @@

    Source: visual/VisualStim.js

    } } - - /** * Setter for the orientation attribute. * - * @name module:visual.VisualStim#setOri - * @public * @param {number} ori - the orientation in degree with 0 as the vertical position, positive values rotate clockwise. * @param {boolean} [log= false] - whether of not to log */ setOri(ori, log = false) { - const hasChanged = this._setAttribute('ori', ori, log); + const hasChanged = this._setAttribute("ori", ori, log); if (hasChanged) { let radians = -ori * 0.017453292519943295; - this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], - [Math.sin(radians), Math.cos(radians)]]; - - this._onChange(true, true)(); + this._rotationMatrix = [ + [Math.cos(radians), -Math.sin(radians)], + [Math.sin(radians), Math.cos(radians)] + ]; + + if (this._pixi instanceof PIXI.DisplayObject) { + this._pixi.rotation = -ori * Math.PI / 180; + } else { + this._onChange(true, true)(); + } } } - - /** * Setter for the position attribute. * - * @name module:visual.VisualStim#setPos - * @public * @param {Array.<number>} pos - position of the center of the stimulus, in stimulus units * @param {boolean} [log= false] - whether of not to log */ setPos(pos, log = false) { const prevPos = this._pos; - const hasChanged = this._setAttribute('pos', util.toNumerical(pos), log); + const hasChanged = this._setAttribute("pos", util.toNumerical(pos), log); if (hasChanged) { this._needUpdate = true; - + // update the bounding box, without calling _estimateBoundingBox: this._boundingBox.x += this._pos[0] - prevPos[0]; this._boundingBox.y += this._pos[1] - prevPos[1]; } } - + /** + * Setter for the depth attribute. + * + * @param {Array.<number>} depth - order in which stimuli is rendered, kind of css's z-index with a negative sign. + * @param {boolean} [log= false] - whether of not to log + */ + setDepth (depth = 0, log = false) { + this._setAttribute("depth", depth, log); + if (this._pixi) { + this._pixi.zIndex = -this._depth; + } + } /** * Determine whether an object is inside the bounding box of the stimulus. * - * @name module:visual.VisualStim#contains - * @public * @param {Object} object - the object * @param {string} units - the units * @return {boolean} whether or not the object is inside the bounding box of the stimulus @@ -230,12 +250,12 @@

    Source: visual/VisualStim.js

    // get the position of the object, in pixel coordinates: const objectPos_px = util.getPositionFromObject(object, units); - if (typeof objectPos_px === 'undefined') + if (typeof objectPos_px === "undefined") { throw { - origin: 'VisualStim.contains', - context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), - error: 'unable to determine the position of the object' + origin: "VisualStim.contains", + context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object), + error: "unable to determine the position of the object", }; } @@ -243,77 +263,68 @@

    Source: visual/VisualStim.js

    return this._getBoundingBox_px().contains(objectPos_px[0], objectPos_px[1]); } - - /** * Estimate the bounding box. * - * @name module:visual.VisualStim#_estimateBoundingBox - * @function * @protected */ _estimateBoundingBox() { throw { - origin: 'VisualStim._estimateBoundingBox', + origin: "VisualStim._estimateBoundingBox", context: `when estimating the bounding box of visual stimulus: ${this._name}`, - error: 'this method is abstract and should not be called.' + error: "this method is abstract and should not be called.", }; } - - /** * Get the bounding box in pixel coordinates * - * @name module:visual.VisualStim#contains - * @function * @protected * @returns {PIXI.Rectangle} the bounding box, in pixel coordinates */ _getBoundingBox_px() { - if (this._units === 'pix') + if (this._units === "pix") { return this._boundingBox.clone(); } - else if (this._units === 'norm') + else if (this._units === "norm") { return new PIXI.Rectangle( this._boundingBox.x * this._win.size[0] / 2, this._boundingBox.y * this._win.size[1] / 2, this._boundingBox.width * this._win.size[0] / 2, - this._boundingBox.height * this._win.size[1] / 2 + this._boundingBox.height * this._win.size[1] / 2, ); } - else if (this._units === 'height') + else if (this._units === "height") { const minSize = Math.min(this._win.size[0], this._win.size[1]); return new PIXI.Rectangle( this._boundingBox.x * minSize, this._boundingBox.y * minSize, this._boundingBox.width * minSize, - this._boundingBox.height * minSize + this._boundingBox.height * minSize, ); } else { - throw Object.assign(response, {error: `unknown units: ${this._units}`}); + throw Object.assign(response, { error: `unknown units: ${this._units}` }); } } - - /** * Generate a callback that prepares updates to the stimulus. - * This is typically called in the constructor of a stimulus, when attributes are added with _addAttribute. + * This is typically called in the constructor of a stimulus, when attributes are added + * with _addAttribute. * - * @name module:visual.VisualStim#_onChange - * @function - * @param {boolean} [withPixi = false] - whether or not the PIXI representation must also be updated - * @param {boolean} [withBoundingBox = false] - whether or not to immediately estimate the bounding box - * @return {Function} * @protected + * @param {boolean} [withPixi = false] - whether or not the PIXI representation must + * also be updated + * @param {boolean} [withBoundingBox = false] - whether or not to immediately estimate + * the bounding box + * @return {Function} */ _onChange(withPixi = false, withBoundingBox = false) { @@ -330,7 +341,6 @@

    Source: visual/VisualStim.js

    } }; } - }
    @@ -339,19 +349,23 @@

    Source: visual/VisualStim.js

    + +
    - -
    - Documentation generated by JSDoc 3.6.7 on Mon Jun 21 2021 07:34:20 GMT+0200 (Central European Summer Time) + Documentation generated by JSDoc 3.6.7 on Mon Aug 01 2022 10:19:55 GMT+0200 (Central European Summer Time) using the docdash theme.
    - - + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 23eaa798..cb6071b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,18 @@ { "name": "psychojs", - "version": "2021.2.x", + "version": "2022.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "psychojs", - "version": "2021.2.x", + "version": "2022.2.0", "license": "MIT", "dependencies": { + "@pixi/filter-adjustment": "^4.1.3", + "a11y-dialog": "^7.5.0", + "docdash": "^1.2.0", + "esbuild-plugin-glsl": "^1.0.5", "howler": "^2.2.1", "log4javascript": "github:Ritzlgrmft/log4javascript", "pako": "^1.0.10", @@ -337,6 +341,15 @@ "@pixi/utils": "6.0.4" } }, + "node_modules/@pixi/filter-adjustment": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-4.1.3.tgz", + "integrity": "sha512-W+NhPiZRYKoRToa5+tkU95eOw8gnS5dfIp3ZP+pLv2mdER9RI+4xHxp1uLHMqUYZViTaMdZIIoVOuCgHFPYCbQ==", + "peerDependencies": { + "@pixi/constants": "^6.0.0", + "@pixi/core": "^6.0.0" + } + }, "node_modules/@pixi/filter-alpha": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-6.0.4.tgz", @@ -631,6 +644,14 @@ "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz", "integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==" }, + "node_modules/a11y-dialog": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.5.0.tgz", + "integrity": "sha512-UF7cy4lfZQtvjRV5N4xdWFba+Pb1qW6FPp0p58dLjMTJ4PwIGGekTbmqUt3etBBRo9HbTqhlNsXQhzIuXeJpng==", + "dependencies": { + "focusable-selectors": "^0.3.1" + } + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -908,6 +929,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/docdash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/docdash/-/docdash-1.2.0.tgz", + "integrity": "sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -957,12 +983,22 @@ "version": "0.12.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.5.tgz", "integrity": "sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" } }, + "node_modules/esbuild-plugin-glsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esbuild-plugin-glsl/-/esbuild-plugin-glsl-1.1.0.tgz", + "integrity": "sha512-OBzCa/nRy/Vbm62DBzBnV25p1BfTpvFf2SP2Vv9Ls38sdEEuHzhYT5xTOh3Ghu+77VI4iZsOam19cmjwq5RcJQ==", + "engines": { + "node": ">= 0.10.18" + }, + "peerDependencies": { + "esbuild": "0.x.x" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1445,6 +1481,11 @@ "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "dev": true }, + "node_modules/focusable-selectors": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/focusable-selectors/-/focusable-selectors-0.3.1.tgz", + "integrity": "sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw==" + }, "node_modules/frac": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", @@ -2690,6 +2731,12 @@ "@pixi/utils": "6.0.4" } }, + "@pixi/filter-adjustment": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-4.1.3.tgz", + "integrity": "sha512-W+NhPiZRYKoRToa5+tkU95eOw8gnS5dfIp3ZP+pLv2mdER9RI+4xHxp1uLHMqUYZViTaMdZIIoVOuCgHFPYCbQ==", + "requires": {} + }, "@pixi/filter-alpha": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-6.0.4.tgz", @@ -2984,6 +3031,14 @@ "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz", "integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==" }, + "a11y-dialog": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/a11y-dialog/-/a11y-dialog-7.5.0.tgz", + "integrity": "sha512-UF7cy4lfZQtvjRV5N4xdWFba+Pb1qW6FPp0p58dLjMTJ4PwIGGekTbmqUt3etBBRo9HbTqhlNsXQhzIuXeJpng==", + "requires": { + "focusable-selectors": "^0.3.1" + } + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -3192,6 +3247,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "docdash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/docdash/-/docdash-1.2.0.tgz", + "integrity": "sha512-IYZbgYthPTspgqYeciRJNPhSwL51yer7HAwDXhF5p+H7mTDbPvY3PCk/QDjNxdPCpWkaJVFC4t7iCNB/t9E5Kw==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3230,8 +3290,13 @@ "esbuild": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.5.tgz", - "integrity": "sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw==", - "dev": true + "integrity": "sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw==" + }, + "esbuild-plugin-glsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esbuild-plugin-glsl/-/esbuild-plugin-glsl-1.1.0.tgz", + "integrity": "sha512-OBzCa/nRy/Vbm62DBzBnV25p1BfTpvFf2SP2Vv9Ls38sdEEuHzhYT5xTOh3Ghu+77VI4iZsOam19cmjwq5RcJQ==", + "requires": {} }, "escape-string-regexp": { "version": "1.0.5", @@ -3586,6 +3651,11 @@ "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "dev": true }, + "focusable-selectors": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/focusable-selectors/-/focusable-selectors-0.3.1.tgz", + "integrity": "sha512-5JLtr0e1YJIfmnVlpLiG+av07dd0Xkf/KfswsXcei5KmLfdwOysTQsjF058ynXniujb1fvev7nql1x+CkC5ikw==" + }, "frac": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", diff --git a/package.json b/package.json index a4a95b9b..8c7e2cba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psychojs", - "version": "2022.1.1", + "version": "2022.3.1", "private": true, "description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments", "license": "MIT", @@ -17,7 +17,7 @@ "scripts": { "build": "npm run build:js && npm run build:css && npm run build:docs", "build:css": "node ./scripts/build.css.cjs", - "build:docs": "jsdoc src -r -d docs --readme README.md", + "build:docs": "jsdoc src -c jsdoc.json & cp jsdoc.css docs/styles/", "build:js": "node ./scripts/build.js.cjs", "fmt": "dprint fmt", "lint": "npm run lint:js && npm run lint:css", @@ -27,6 +27,10 @@ "start": "npm run build" }, "dependencies": { + "@pixi/filter-adjustment": "^4.1.3", + "a11y-dialog": "^7.5.0", + "docdash": "^1.2.0", + "esbuild-plugin-glsl": "^1.0.5", "howler": "^2.2.1", "log4javascript": "github:Ritzlgrmft/log4javascript", "pako": "^1.0.10", diff --git a/scripts/build.js.cjs b/scripts/build.js.cjs index 825e0cd0..d7d720be 100644 --- a/scripts/build.js.cjs +++ b/scripts/build.js.cjs @@ -1,9 +1,18 @@ -const { buildSync } = require("esbuild"); -const pkg = require("psychojs/package.json"); +const { buildSync, build } = require("esbuild"); +const { glsl } = require("esbuild-plugin-glsl"); +const pkg = require("../package.json"); const versionMaybe = process.env.npm_config_outver; const dirMaybe = process.env.npm_config_outdir; const [, , , dir = dirMaybe || "out", version = versionMaybe || pkg.version] = process.argv; +let shouldWatchDir = false; + +for (var i = 0; i < process.argv.length; i++) { + if (process.argv[i] === '-w') { + shouldWatchDir = true; + break; + } +} [ // The ESM bundle @@ -20,13 +29,19 @@ const [, , , dir = dirMaybe || "out", version = versionMaybe || pkg.version] = p }, ].forEach(function(options) { - buildSync({ ...this, ...options }); + build({ ...this, ...options }) + .then(()=> { + if (shouldWatchDir) { + console.log('watching...') + } + }); }, { // Shared options banner: { js: `/*! For license information please see psychojs-${version}.js.LEGAL.txt */`, }, bundle: true, + watch: shouldWatchDir, sourcemap: true, entryPoints: ["src/index.js"], minifySyntax: true, @@ -36,4 +51,9 @@ const [, , , dir = dirMaybe || "out", version = versionMaybe || pkg.version] = p "es2017", "node14", ], + plugins: [ + glsl({ + minify: true + }) + ] }); diff --git a/src/core/EventManager.js b/src/core/EventManager.js index 0b83000e..c9f82550 100644 --- a/src/core/EventManager.js +++ b/src/core/EventManager.js @@ -2,8 +2,8 @@ * Manager handling the keyboard and mouse/touch events. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -11,16 +11,15 @@ import { Clock, MonotonicClock } from "../util/Clock.js"; import { PsychoJS } from "./PsychoJS.js"; /** - * @class *

    This manager handles all participant interactions with the experiment, i.e. keyboard, mouse and touch events.

    - * - * @name module:core.EventManager - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance */ export class EventManager { + /** + * @memberof module:core + * @param {Object} psychoJS + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { this._psychoJS = psychoJS; @@ -57,9 +56,6 @@ export class EventManager * *

    Note: The w3c [key-event viewer]{@link https://w3c.github.io/uievents/tools/key-event-viewer.html} can be used to see possible values for the items in the keyList given the user's keyboard and chosen layout. The "key" and "code" columns in the UI Events fields are the relevant values for the keyList argument.

    * - * @name module:core.EventManager#getKeys - * @function - * @public * @param {Object} options * @param {string[]} [options.keyList= null] - keyList allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If no keyList is given, all keys will be checked and the key buffer will be cleared completely. * @param {boolean} [options.timeStamped= false] - If true will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time). @@ -137,9 +133,6 @@ export class EventManager /** * Get the mouse info. * - * @name module:core.EventManager#getMouseInfo - * @function - * @public * @return {EventManager.MouseInfo} the mouse info. */ getMouseInfo() @@ -150,10 +143,6 @@ export class EventManager /** * Clear all events from the event buffer. * - * @name module:core.EventManager#clearEvents - * @function - * @public - * * @todo handle the attribs argument */ clearEvents(attribs) @@ -163,10 +152,6 @@ export class EventManager /** * Clear all keys from the key buffer. - * - * @name module:core.EventManager#clearKeys - * @function - * @public */ clearKeys() { @@ -176,10 +161,6 @@ export class EventManager /** * Start the move clock. * - * @name module:core.EventManager#startMoveClock - * @function - * @public - * * @todo not implemented */ startMoveClock() @@ -189,10 +170,6 @@ export class EventManager /** * Stop the move clock. * - * @name module:core.EventManager#stopMoveClock - * @function - * @public - * * @todo not implemented */ stopMoveClock() @@ -202,10 +179,6 @@ export class EventManager /** * Reset the move clock. * - * @name module:core.EventManager#resetMoveClock - * @function - * @public - * * @todo not implemented */ resetMoveClock() @@ -215,9 +188,6 @@ export class EventManager /** * Add various mouse listeners to the Pixi renderer of the {@link Window}. * - * @name module:core.EventManager#addMouseListeners - * @function - * @public * @param {PIXI.Renderer} renderer - The Pixi renderer */ addMouseListeners(renderer) @@ -319,9 +289,7 @@ export class EventManager /** * Add key listeners to the document. * - * @name module:core.EventManager#_addKeyListeners - * @function - * @private + * @protected */ _addKeyListeners() { @@ -359,9 +327,6 @@ export class EventManager * Convert a keylist that uses pyglet key names to one that uses W3C KeyboardEvent.code values. *

    This allows key lists that work in the builder environment to work in psychoJS web experiments.

    * - * @name module:core.EventManager#pyglet2w3c - * @function - * @public * @param {Array.string} pygletKeyList - the array of pyglet key names * @return {Array.string} the w3c keyList */ @@ -386,10 +351,6 @@ export class EventManager /** * Convert a W3C Key Code into a pyglet key. * - * @name module:core.EventManager#w3c2pyglet - * @function - * @public - * @static * @param {string} code - W3C Key Code * @returns {string} corresponding pyglet key */ @@ -409,10 +370,6 @@ export class EventManager * Convert a keycode to a W3C UI Event code. *

    This is for legacy browsers.

    * - * @name module:core.EventManager#keycode2w3c - * @function - * @public - * @static * @param {number} keycode - the keycode * @returns {string} corresponding W3C UI Event code */ @@ -430,9 +387,8 @@ export class EventManager *

    Unfortunately, it is not very fine-grained: for instance, there is no difference between Alt Left and Alt * Right, or between Enter and Numpad Enter. Use at your own risk (or upgrade your browser...).

    * - * @name module:core.EventManager#_keycodeMap * @readonly - * @private + * @protected * @type {Object.} */ EventManager._keycodeMap = { @@ -518,9 +474,8 @@ EventManager._keycodeMap = { * This map associates pyglet key names to the corresponding W3C KeyboardEvent codes values. *

    More information can be found [here]{@link https://www.w3.org/TR/uievents-code}

    * - * @name module:core.EventManager#_pygletMap * @readonly - * @private + * @protected * @type {Object.} */ EventManager._pygletMap = { @@ -619,9 +574,8 @@ EventManager._pygletMap = { /** *

    This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names. * - * @name module:core.EventManager#_reversePygletMap * @readonly - * @private + * @protected * @type {Object.} */ EventManager._reversePygletMap = {}; @@ -629,8 +583,6 @@ EventManager._reversePygletMap = {}; /** * Utility class used by the experiment scripts to keep track of a clock and of the current status (whether or not we are currently checking the keyboard) * - * @name module:core.BuilderKeyResponse - * @class * @param {Object} options * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance */ diff --git a/src/core/GUI.js b/src/core/GUI.js index cc3bbcf2..90bd3d19 100644 --- a/src/core/GUI.js +++ b/src/core/GUI.js @@ -3,8 +3,8 @@ * * @author Alain Pitiot * @author Sijia Zhao - fine-grained resource loading - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2021.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -15,22 +15,37 @@ import { Scheduler } from "../util/Scheduler.js"; import * as util from "../util/Util.js"; import { PsychoJS } from "./PsychoJS.js"; import { ServerManager } from "./ServerManager.js"; +import A11yDialog from "a11y-dialog"; /** - * @class - * Graphic User Interface - * - * @name module:core.GUI - * @class - * @param {module:core.PsychoJS} psychoJS the PsychoJS instance + *

    GUI manages the various pop-up dialog boxes that guide the participant, throughout the + * lifecycle of the experiment, e.g. at the start while the resources are downloading, or at the + * end when the data is uploading to the server

    */ export class GUI { + /** + * Default settings for GUI. + * + * @type {Object} + */ + static DEFAULT_SETTINGS = { + DlgFromDict: { + // The dialog box shows an OK button. The button becomes enable when all registered resources + // have been downloaded. Participants must click on the OK button to move on with the experiment. + requireParticipantClick: true + } + }; + get dialogComponent() { return this._dialogComponent; } + /** + * @memberof module:core + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + */ constructor(psychoJS) { this._psychoJS = psychoJS; @@ -40,8 +55,6 @@ export class GUI { this._onResourceEvents(signal); }); - - this._dialogScalingFactor = 0; } /** @@ -58,37 +71,39 @@ export class GUI *

    If the participant cancels (by pressing Cancel or by closing the dialog box), then * the dictionary remains unchanged.

    * - * @name module:core.GUI#DlgFromDict - * @function - * @public * @param {Object} options * @param {String} [options.logoUrl] - Url of the experiment logo * @param {String} [options.text] - information text * @param {Object} options.dictionary - associative array of values for the participant to set * @param {String} options.title - name of the project + * @param {boolean} [options.requireParticipantClick=true] - whether the participant must click on the OK + * button, when it becomes enabled, to move on with the experiment */ DlgFromDict({ logoUrl, text, dictionary, title, + requireParticipantClick = GUI.DEFAULT_SETTINGS.DlgFromDict.requireParticipantClick }) { // get info from URL: const infoFromUrl = util.getUrlParameters(); - this._progressMsg = " "; this._progressBarMax = 0; this._allResourcesDownloaded = false; this._requiredKeys = []; this._setRequiredKeys = new Map(); + this._progressMessage = " "; + this._requireParticipantClick = requireParticipantClick; + this._dictionary = dictionary; - // prepare PsychoJS component: + // prepare a PsychoJS component: this._dialogComponent = {}; this._dialogComponent.status = PsychoJS.Status.NOT_STARTED; const dialogClock = new Clock(); - const self = this; + const self = this; return () => { const t = dialogClock.getTime(); @@ -100,38 +115,30 @@ export class GUI // if the experiment is licensed, and running on the license rather than on credit, // we use the license logo: - if ( - self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER + if (self._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER && typeof self._psychoJS.config.experiment.license !== "undefined" && self._psychoJS.config.experiment.runMode === "LICENSE" - && typeof self._psychoJS.config.experiment.license.institutionLogo !== "undefined" - ) + && typeof self._psychoJS.config.experiment.license.institutionLogo !== "undefined") { logoUrl = self._psychoJS.config.experiment.license.institutionLogo; } - // prepare jquery UI dialog box: - let htmlCode = '
    '; + // prepare the markup for the a11y-dialog: + let markup = "
    "; + + // replace root by the markup code: const dialogElement = document.getElementById("root"); - dialogElement.innerHTML = htmlCode; + dialogElement.innerHTML = markup; + + // init and open the dialog box: + const dialogDiv = document.getElementById("experiment-dialog"); + self._dialog = new A11yDialog(dialogDiv); + self._dialog.show(); + + // button callbacks: + self._dialogComponent.button = "Cancel"; + self._cancelButton = document.getElementById("dialogCancel"); + self._cancelButton.onclick = self._onCancelExperiment.bind(self); + if (self._requireParticipantClick) + { + self._okButton = document.getElementById("dialogOK"); + self._okButton.onclick = self._onStartExperiment.bind(self); + } + self._closeButton = document.getElementById("dialogClose"); + self._closeButton.onclick = self._onCancelExperiment.bind(self); + + // update the OK button status: + self._updateDialog(); + + self._progressMsg = document.getElementById("progressMsg"); + self._progressBar = document.getElementById("progressBar"); + self._updateProgressBar(); // setup change event handlers for all required keys: this._requiredKeys.forEach((keyId) => @@ -212,79 +250,6 @@ export class GUI input.oninput = (event) => GUI._onKeyChange(self, event); } }); - - // init and open the dialog box: - self._dialogComponent.button = "Cancel"; - jQuery("#expDialog").dialog({ - width: "500", - - autoOpen: true, - modal: false, - closeOnEscape: false, - resizable: false, - draggable: false, - - buttons: [ - { - id: "buttonCancel", - text: "Cancel", - click: function() - { - self._dialogComponent.button = "Cancel"; - jQuery("#expDialog").dialog("close"); - }, - }, - { - id: "buttonOk", - text: "Ok", - click: function() - { - // update dictionary: - Object.keys(dictionary).forEach((key, keyIdx) => - { - const input = document.getElementById("form-input-" + keyIdx); - if (input) - { - dictionary[key] = input.value; - } - }); - - self._dialogComponent.button = "OK"; - jQuery("#expDialog").dialog("close"); - - // Tackle browser demands on having user action initiate audio context - Tone.start(); - - // switch to full screen if requested: - self._psychoJS.window.adjustScreenSize(); - - // Clear events (and keypresses) accumulated during the dialog - self._psychoJS.eventManager.clearEvents(); - }, - }, - ], - - // close is called by both buttons and when the user clicks on the cross: - close: function() - { - // jQuery.unblockUI(); - jQuery(this).dialog("destroy").remove(); - self._dialogComponent.status = PsychoJS.Status.FINISHED; - }, - }) - // change colour of title bar - .prev(".ui-dialog-titlebar").css("background", "green"); - - // update the OK button status: - self._updateOkButtonStatus(); - - // block UI until user has pressed dialog button: - // note: block UI does not allow for text to be entered in the dialog form boxes, alas! - // jQuery.blockUI({ message: "", baseZ: 1}); - - // show dialog box: - jQuery("#progressbar").progressbar({ value: self._progressBarCurrentValue }); - jQuery("#progressbar").progressbar("option", "max", self._progressBarMax); } if (self._dialogComponent.status === PsychoJS.Status.FINISHED) @@ -301,20 +266,22 @@ export class GUI /** * @callback GUI.onOK */ + /** + * @callback GUI.onCancel + */ /** * Show a message to the participant in a dialog box. * - *

    This function can be used to display both warning and error messages.

    + *

    This function can be used to display ordinary, warning, and error messages.

    * - * @name module:core.GUI#dialog - * @function - * @public * @param {Object} options * @param {string} options.message - the message to be displayed * @param {Object.} options.error - an exception * @param {string} options.warning - a warning message - * @param {boolean} [options.showOK=true] - specifies whether to show the OK button + * @param {boolean} [options.showOK=true] - whether to show the OK button * @param {GUI.onOK} [options.onOK] - function called when the participant presses the OK button + * @param {boolean} [options.showCancel=false] - whether to show the Cancel button + * @param {GUI.onCancel} [options.onCancel] - function called when the participant presses the Cancel button */ dialog({ message, @@ -322,13 +289,17 @@ export class GUI error, showOK = true, onOK, + showCancel = false, + onCancel } = {}) { // close the previously opened dialog box, if there is one: this.closeDialog(); - let htmlCode; - let titleColour; + // prepare the markup for the a11y-dialog: + let markup = ""; - // replace root by the html code: + // replace root by the markup code: const dialogElement = document.getElementById("root"); - dialogElement.innerHTML = htmlCode; + dialogElement.innerHTML = markup; // init and open the dialog box: - const self = this; - jQuery("#msgDialog").dialog({ - dialogClass: "no-close", + const dialogDiv = document.getElementById("experiment-dialog"); + this._dialog = new A11yDialog(dialogDiv); + this._dialog.show(); - width: "500", + // button callbacks: + if (showOK) + { + this._okButton = document.getElementById("dialogOK"); + this._okButton.onclick = () => + { + this.closeDialog(); - autoOpen: true, - modal: false, - closeOnEscape: false, - resizable: false, - draggable: false, + // execute callback function: + if (typeof onOK !== "undefined") + { + onOK(); + } + }; + } + if (showCancel) + { + this._cancelButton = document.getElementById("dialogCancel"); + this._cancelButton.onclick = () => + { + this.closeDialog(); - buttons: (!showOK) ? [] : [{ - id: "buttonOk", - text: "Ok", - click: function() + // execute callback function: + if (typeof onCancel !== "undefined") { - jQuery(this).dialog("destroy").remove(); + onCancel(); + } + }; + } + } - // execute callback function: - if (typeof onOK !== "undefined") - { - onOK(); - } - }, - }], - }) - // change colour of title bar - .prev(".ui-dialog-titlebar").css("background", titleColour); + /** + *

    Create a dialog box with a progress bar, to inform the participant of + * the last stages of the experiment: upload of results, of log, and closing + * of session.

    + * + * @param {Object} options + * @param {String} [options.text] - information text + */ + finishDialog({ text = "", nbSteps = 0 }) + { + this.closeDialog(); + + // prepare the markup for the a11y-dialog: + let markup = ""; + + // replace root by the markup code: + const dialogElement = document.getElementById("root"); + dialogElement.innerHTML = markup; + + // init and open the dialog box: + const dialogDiv = document.getElementById("experiment-dialog"); + this._dialog = new A11yDialog(dialogDiv); + this._dialog.show(); + + this._progressMsg = document.getElementById("progressMsg"); + this._progressBar = document.getElementById("progressBar"); + + this._progressMessage = " "; + this._progressBarCurrentValue = 0; + this._progressBarMax = nbSteps; + this._updateProgressBar(); + } + + finishDialogNextStep(text) + { + this._setProgressMessage(text); + ++ this._progressBarCurrentValue; + this._updateProgressBar(); } /** * Close the previously opened dialog box, if there is one. - * - * @name module:core.GUI#closeDialog - * @function - * @public */ closeDialog() { - const expDialog = jQuery("#expDialog"); - if (expDialog.length) + if (this._dialog) { - expDialog.dialog("destroy").remove(); + this._dialog.hide(); } - const msgDialog = jQuery("#msgDialog"); - if (msgDialog.length) + } + + /** + * Set the progress message. + * + * @protected + * @param {string} message the message + */ + _setProgressMessage(message) + { + this._progressMessage = message; + if (typeof this._progressMsg !== "undefined") { - msgDialog.dialog("destroy").remove(); + this._progressMsg.innerText = message; } } /** - * Listener for resource event from the [Server Manager]{@link ServerManager}. + * Update the progress bar. * - * @name module:core.GUI#_onResourceEvents - * @function - * @private - * @param {Object.} signal the signal + * @protected + */ + _updateProgressBar() + { + if (typeof this._progressBar !== "undefined") + { + this._progressBar.style.width = `${Math.round(this._progressBarCurrentValue * 100.0 / this._progressBarMax)}%`; + } + } + + /** + * Callback triggered when the participant presses the Cancel button + * + * @protected + */ + _onCancelExperiment() + { + this._dialogComponent.button = "Cancel"; + + this._dialog.hide(); + this._dialog = null; + this._dialogComponent.status = PsychoJS.Status.FINISHED; + } + + /** + * Callback triggered when the participant presses the OK button + * + * @protected + */ + _onStartExperiment() + { + this._dialogComponent.button = "OK"; + + // update the dictionary: + Object.keys(this._dictionary).forEach((key, keyIdx) => + { + const input = document.getElementById("form-input-" + keyIdx); + if (input) + { + this._dictionary[key] = input.value; + } + }); + + + // Start Tone here, since a user action is required to initiate the audio context: + Tone.start(); + + // switch to full screen if requested: + this._psychoJS.window.adjustScreenSize(); + + // clear all events (and keypresses) accumulated until now: + this._psychoJS.eventManager.clearEvents(); + + this._dialog.hide(); + this._dialog = null; + this._dialogComponent.status = PsychoJS.Status.FINISHED; + } + + /** + * Callback triggered upon a resource event from the [Server Manager]{@link module:core.ServerManager}. + * + * @protected + * @param {Object.} signal - the ServerManager's signal */ _onResourceEvents(signal) { @@ -480,16 +577,19 @@ export class GUI { // for each resource, we have a 'downloading resource' and a 'resource downloaded' message: this._progressBarMax = signal.count * 2; - jQuery("#progressbar").progressbar("option", "max", this._progressBarMax); - this._progressBarCurrentValue = 0; + this._updateProgressBar(); } // all the resources have been downloaded: show the ok button else if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED) { this._allResourcesDownloaded = true; - jQuery("#progressMsg").text("all resources downloaded."); - this._updateOkButtonStatus(); + this._progressBarMax = 100; + this._progressBarCurrentValue = 100; + this._updateProgressBar(); + this._setProgressMessage("all resources downloaded."); + + this._updateDialog(); } // update progress bar: else if ( @@ -505,68 +605,72 @@ export class GUI if (signal.message === ServerManager.Event.RESOURCE_DOWNLOADED) { - jQuery("#progressMsg").text("downloaded " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2)); + this._setProgressMessage(`downloaded ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`); } else { - jQuery("#progressMsg").text("downloading " + (this._progressBarCurrentValue / 2) + " / " + (this._progressBarMax / 2)); + this._setProgressMessage(`downloading ${this._progressBarCurrentValue / 2} / ${this._progressBarMax / 2}`); } - // $("#progressMsg").text(signal.resource + ': downloaded.'); - jQuery("#progressbar").progressbar("option", "value", this._progressBarCurrentValue); + + this._updateProgressBar(); } // unknown message: we just display it else { - jQuery("#progressMsg").text(signal.message); + this._progressMsg.innerHTML = signal.message; } } /** - * Update the status of the OK button. + * Update the dialog box. * - * @name module:core.GUI#_updateOkButtonStatus - * @param [changeFocus = false] - whether or not to change the focus to the OK button - * @function - * @private + * @protected + * @param [changeOKButtonFocus = false] - whether to change the focus to the OK button */ - _updateOkButtonStatus(changeFocus = true) + _updateDialog(changeOKButtonFocus = true) { - if ( - (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL) - || (this._allResourcesDownloaded && this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length) - ) + const allRequirementsFulfilled = this._allResourcesDownloaded + && (this._setRequiredKeys && this._setRequiredKeys.size >= this._requiredKeys.length); + + // if the participant is required to click on the OK button: + if (this._requireParticipantClick) { - if (changeFocus) - { - jQuery("#buttonOk").button("option", "disabled", false).focus(); - } - else + if (typeof this._okButton !== "undefined") { - jQuery("#buttonOk").button("option", "disabled", false); + // locally the OK button is always enabled, otherwise only if all requirements have been fulfilled: + if (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.LOCAL || allRequirementsFulfilled) + { + this._okButton.classList.add("dialog-button"); + this._okButton.classList.remove("disabled"); + if (changeOKButtonFocus) + { + this._okButton.focus(); + } + } + else + { + this._okButton.classList.add("dialog-button", "disabled"); + } } + + return; } - else - { - jQuery("#buttonOk").button("option", "disabled", true); - } - // strangely, changing the disabled option sometimes fails to update the ui, - // so we need to hide it and show it again: - jQuery("#buttonOk").hide(0, () => + + // if all requirements are fulfilled and the participant is not required to click on the OK button, + // then we close the dialog box and move on with the experiment: + if (allRequirementsFulfilled) { - jQuery("#buttonOk").show(); - }); + this._onStartExperiment(); + } } /** - * Listener for change event for required keys. + * Callback triggered upon change event (for required keys). * - * @name module:core.GUI#_onKeyChange - * @function - * @static - * @private + * @protected * @param {module:core.GUI} gui - this GUI - * @param {Event} event - event + * @param {Event} event - the key's event */ static _onKeyChange(gui, event) { @@ -582,15 +686,15 @@ export class GUI gui._setRequiredKeys.delete(event.target); } - gui._updateOkButtonStatus(false); + gui._updateDialog(false); } /** - * Get a more user-friendly html message. + * Get the user-friendly html message associated to a pavlovia.or server error code. * + * @protected * @param {number} errorCode - the pavlovia.org server error code - * @private - * @return {{htmlCode: string, titleColour: string}} a user-friendly error message + * @return {{class: string, title: string, text: string}} a user-friendly error message */ _userFriendlyError(errorCode) { @@ -599,127 +703,98 @@ export class GUI // INTERNAL_ERROR case 1: return { - htmlCode: - '

    Oops we encountered an internal server error.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    ', - titleColour: "red", + class: "dialog-error", + title: "Error", + text: "

    Oops we encountered an internal server error.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    " }; // MONGODB_ERROR - case 2: return { - htmlCode: - '

    Oops we encountered a database error.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    ', - titleColour: "red", + class: "dialog-error", + title: "Error", + text: "

    Oops we encountered a database error.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    " }; // STATUS_NONE - case 20: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} does not have any status and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} does not have any status and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.

    ` }; // STATUS_INACTIVE - case 21: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} is currently inactive and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} is currently inactive and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the status must be changed to RUNNING for participants to be able to run it.

    ` }; // STATUS_DELETED - case 22: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} has been deleted and cannot be run.

    If you are the experiment designer, either go to your experiment page and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.

    Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} has been deleted and cannot be run.

    If you are the experiment designer, either go to your experiment page and change the experiment status to either PILOTING or RUNNING, or generate a new experiment.

    Otherwise please contact the experiment designer to let him or her know that the experiment has been deleted and cannot be run any longer.

    ` }; // STATUS_ARCHIVED - case 23: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} has been archived and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} has been archived and cannot be run.

    If you are the experiment designer, go to your experiment page and change the experiment status to either PILOTING or RUNNING.

    Otherwise please contact the experiment designer to let him or her know that the experiment has been archived and cannot be run at the moment.

    ` }; // PILOTING_NO_TOKEN - case 30: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} is currently in PILOTING mode but the pilot token is missing from the URL.

    If you are the experiment designer, you can pilot it by pressing the pilot button on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} is currently in PILOTING mode but the pilot token is missing from the URL.

    If you are the experiment designer, you can pilot it by pressing the pilot button on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.

    ` }; // PILOTING_INVALID_TOKEN - case 31: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} cannot be run because the pilot token in the URL is invalid, possibly because it has expired.

    If you are the experiment designer, you can generate a new token by pressing the pilot button on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} cannot be run because the pilot token in the URL is invalid, possibly because it has expired.

    If you are the experiment designer, you can generate a new token by pressing the pilot button on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment status must be changed to RUNNING for participants to be able to run it.

    ` }; // LICENSE_EXPIRED - case 50: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} is covered by a license that has expired.

    If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your experiment page, where you will also be able to change its running mode to CREDIT.

    Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} is covered by a license that has expired.

    If you are the experiment designer, you can either contact the license manager to inquire about the expiration, or you can run your experiments using credits. You will find all relevant details about the license on your experiment page, where you will also be able to change its running mode to CREDIT.

    Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license having expired.

    ` }; // LICENSE_APPROVAL_NEEDED - case 51: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} is covered by a license that requires one or more documents to be approved before the experiment can be run.

    If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} is covered by a license that requires one or more documents to be approved before the experiment can be run.

    If you are the experiment designer, please contact the license manager and ask him or her which documents must be approved. You will find all relevant details about the license on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that there is an issue with the experiment's license requiring documents to be approved.

    ` }; // CREDIT_NOT_ENOUGH - case 60: return { - htmlCode: - `

    ${this._psychoJS.config.experiment.fullpath} does not have any assigned credit left and cannot be run.

    If you are the experiment designer, you can assign more credits to it on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.

    `, - titleColour: "orange", + class: "dialog-warning", + title: "Warning", + text: `

    ${this._psychoJS.config.experiment.fullpath} does not have any assigned credit left and cannot be run.

    If you are the experiment designer, you can assign more credits to it on your experiment page.

    Otherwise please contact the experiment designer to let him or her know that the experiment requires more assigned credits to run.

    ` }; default: return { - htmlCode: - `

    Unfortunately we encountered an unspecified error (error code: ${errorCode}.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    `, - titleColour: "red", + class: "dialog-error", + title: "Error", + text: `

    Unfortunately we encountered an unspecified error (error code: ${errorCode}.

    Try to run the experiment again. If the error persists, contact the experiment designer.

    ` }; } } } -/** - * Maximal dimensions of the dialog window. - * - * @name module:core.GUI#dialogMaxSize - * @enum {Symbol} - * @readonly - * @public - */ -GUI.dialogMaxSize = [500, 600]; - -/** - * Dialog window margins. - * - * @name module:core.GUI#dialogMargin - * @enum {Symbol} - * @readonly - * @public - */ -GUI.dialogMargin = [50, 50]; diff --git a/src/core/Keyboard.js b/src/core/Keyboard.js index 32d86bb8..dd2427df 100644 --- a/src/core/Keyboard.js +++ b/src/core/Keyboard.js @@ -2,8 +2,8 @@ * Manager handling the keyboard events. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -13,15 +13,16 @@ import { EventManager } from "./EventManager.js"; import { PsychoJS } from "./PsychoJS.js"; /** - * @name module:core.KeyPress - * @class - * - * @param {string} code - W3C Key Code - * @param {number} tDown - time of key press (keydown event) relative to the global Monotonic Clock - * @param {string | undefined} name - pyglet key name + * */ export class KeyPress { + /** + * @memberof module:core + * @param {string} code - W3C Key Code + * @param {number} tDown - time of key press (keydown event) relative to the global Monotonic Clock + * @param {string | undefined} name - pyglet key name + */ constructor(code, tDown, name) { this.code = code; @@ -39,18 +40,20 @@ export class KeyPress /** *

    This manager handles all keyboard events. It is a substitute for the keyboard component of EventManager.

    * - * @name module:core.Keyboard - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {number} [options.bufferSize= 10000] - the maximum size of the circular keyboard event buffer - * @param {boolean} [options.waitForStart= false] - whether or not to wait for a call to module:core.Keyboard#start - * before recording keyboard events - * @param {Clock} [options.clock= undefined] - an optional clock - * @param {boolean} [options.autoLog= false] - whether or not to log + * @extends PsychObject */ export class Keyboard extends PsychObject { + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {number} [options.bufferSize= 10000] - the maximum size of the circular keyboard event buffer + * @param {boolean} [options.waitForStart= false] - whether or not to wait for a call to module:core.Keyboard#start + * before recording keyboard events + * @param {Clock} [options.clock= undefined] - an optional clock + * @param {boolean} [options.autoLog= false] - whether or not to log + */ constructor({ psychoJS, bufferSize = 10000, @@ -82,11 +85,6 @@ export class Keyboard extends PsychObject /** * Start recording keyboard events. - * - * @name module:core.Keyboard#start - * @function - * @public - * */ start() { @@ -95,11 +93,6 @@ export class Keyboard extends PsychObject /** * Stop recording keyboard events. - * - * @name module:core.Keyboard#stop - * @function - * @public - * */ stop() { @@ -119,9 +112,6 @@ export class Keyboard extends PsychObject * Get the list of those keyboard events still in the buffer, i.e. those that have not been * previously cleared by calls to getKeys with clear = true. * - * @name module:core.Keyboard#getEvents - * @function - * @public * @return {Keyboard.KeyEvent[]} the list of events still in the buffer */ getEvents() @@ -151,9 +141,6 @@ export class Keyboard extends PsychObject /** * Get the list of keys pressed or pushed by the participant. * - * @name module:core.Keyboard#getKeys - * @function - * @public * @param {Object} options * @param {string[]} [options.keyList= []]] - the list of keys to consider. If keyList is empty, we consider all keys. * Note that we use pyglet keys here, to make the PsychoJs code more homogeneous with PsychoPy. @@ -300,9 +287,6 @@ export class Keyboard extends PsychObject /** * Clear all events and resets the circular buffers. - * - * @name module:core.Keyboard#clearEvents - * @function */ clearEvents() { @@ -319,9 +303,6 @@ export class Keyboard extends PsychObject /** * Test whether a list of KeyPress's contains one with a particular name. * - * @name module:core.Keyboard#includes - * @function - * @static * @param {module:core.KeyPress[]} keypressList - list of KeyPress's * @param {string } keyName - pyglet key name, e.g. 'escape', 'left' * @return {boolean} whether or not a KeyPress with the given pyglet key name is present in the list @@ -340,9 +321,7 @@ export class Keyboard extends PsychObject /** * Add key listeners to the document. * - * @name module:core.Keyboard#_addKeyListeners - * @function - * @private + * @protected */ _addKeyListeners() { @@ -455,10 +434,8 @@ export class Keyboard extends PsychObject /** * Keyboard KeyStatus. * - * @name module:core.Keyboard#KeyStatus * @enum {Symbol} * @readonly - * @public */ Keyboard.KeyStatus = { KEY_DOWN: Symbol.for("KEY_DOWN"), diff --git a/src/core/Logger.js b/src/core/Logger.js index a2440730..f90d48c0 100644 --- a/src/core/Logger.js +++ b/src/core/Logger.js @@ -2,8 +2,8 @@ * Logger * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -18,13 +18,14 @@ import * as util from "../util/Util.js"; * a remote one, etc.

    * *

    Note: we use log4javascript for the console logger, and our own for the server logger.

    - * - * @name module:core.Logger - * @class - * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR */ export class Logger { + /** + * @memberof module:core + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + * @param {*} threshold - the logging threshold, e.g. log4javascript.Level.ERROR + */ constructor(psychoJS, threshold) { this._psychoJS = psychoJS; @@ -69,8 +70,6 @@ export class Logger /** * Change the logging level. * - * @name module:core.Logger#setLevel - * @public * @param {module:core.Logger.ServerLevel} serverLevel - the new logging level */ setLevel(serverLevel) @@ -82,8 +81,6 @@ export class Logger /** * Log a server message at the EXP level. * - * @name module:core.Logger#exp - * @public * @param {string} msg - the message to be logged. * @param {number} [time] - the logging time * @param {object} [obj] - the associated object (e.g. a Trial) @@ -96,8 +93,6 @@ export class Logger /** * Log a server message at the DATA level. * - * @name module:core.Logger#data - * @public * @param {string} msg - the message to be logged. * @param {number} [time] - the logging time * @param {object} [obj] - the associated object (e.g. a Trial) @@ -110,8 +105,6 @@ export class Logger /** * Log a server message. * - * @name module:core.Logger#log - * @public * @param {string} msg - the message to be logged. * @param {module:core.Logger.ServerLevel} level - logging level * @param {number} [time] - the logging time @@ -150,9 +143,7 @@ export class Logger /** * Check whether or not a log messages must be throttled. * - * @name module:core.Logger#_throttle * @protected - * * @param {number} time - the time of the latest log message * @return {boolean} whether or not to log the message */ @@ -228,9 +219,6 @@ export class Logger * *

    Note: the logs are compressed using Pako's zlib algorithm. * See https://github.com/nodeca/pako for details.

    - * - * @name module:core.Logger#flush - * @public */ async flush() { @@ -296,8 +284,7 @@ export class Logger /** * Create a custom console layout. * - * @name module:core.Logger#_customConsoleLayout - * @private + * @protected * @return {*} the custom layout */ _customConsoleLayout() @@ -368,7 +355,6 @@ export class Logger /** * Get the integer value associated with a logging level. * - * @name module:core.Logger#_getValue * @protected * @param {module:core.Logger.ServerLevel} level - the logging level * @return {number} - the value associated with the logging level, or 30 is the logging level is unknown. @@ -383,10 +369,8 @@ export class Logger /** * Server logging level. * - * @name module:core.Logger#ServerLevel * @enum {Symbol} * @readonly - * @public * * @note These are similar to PsychoPy's logging levels, as defined in logging.py */ @@ -406,7 +390,6 @@ Logger.ServerLevel = { * *

    We use those values to determine whether a log is to be sent to the server or not.

    * - * @name module:core.Logger#_ServerLevelValue * @enum {number} * @readonly * @protected diff --git a/src/core/MinimalStim.js b/src/core/MinimalStim.js index 215ed447..20672f35 100644 --- a/src/core/MinimalStim.js +++ b/src/core/MinimalStim.js @@ -2,8 +2,8 @@ * Base class for all stimuli. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.0 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -14,17 +14,18 @@ import { PsychoJS } from "./PsychoJS.js"; /** *

    MinimalStim is the base class for all stimuli.

    * - * @name module:core.MinimalStim - * @class * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip - * @param {boolean} [options.autoLog= win.autoLog] - whether or not to log */ export class MinimalStim extends PsychObject { + /** + * @memberof module:core + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip + * @param {boolean} [options.autoLog= win.autoLog] - whether to log + */ constructor({ name, win, autoDraw, autoLog } = {}) { super(win._psychoJS, name); @@ -55,11 +56,8 @@ export class MinimalStim extends PsychObject /** * Setter for the autoDraw attribute. * - * @name module:core.MinimalStim#setAutoDraw - * @function - * @public * @param {boolean} autoDraw - the new value - * @param {boolean} [log= false] - whether or not to log + * @param {boolean} [log= false] - whether to log */ setAutoDraw(autoDraw, log = false) { @@ -79,10 +77,6 @@ export class MinimalStim extends PsychObject /** * Draw this stimulus on the next frame draw. - * - * @name module:core.MinimalStim#draw - * @function - * @public */ draw() { @@ -101,7 +95,7 @@ export class MinimalStim extends PsychObject } else { - this.win._rootContainer.addChild(this._pixi); + this._win.addPixiObject(this._pixi); this.win._drawList.push(this); } } @@ -111,9 +105,9 @@ export class MinimalStim extends PsychObject // from the window container, update it, then put it back: if (this._needUpdate && typeof this._pixi !== "undefined") { - this.win._rootContainer.removeChild(this._pixi); + this._win.removePixiObject(this._pixi); this._updateIfNeeded(); - this.win._rootContainer.addChild(this._pixi); + this._win.addPixiObject(this._pixi); } } } @@ -123,10 +117,6 @@ export class MinimalStim extends PsychObject /** * Hide this stimulus on the next frame draw. - * - * @name module:core.MinimalStim#hide - * @function - * @public */ hide() { @@ -140,7 +130,7 @@ export class MinimalStim extends PsychObject // if the stimulus has a pixi representation, remove it from the root container: if (typeof this._pixi !== "undefined") { - this._win._rootContainer.removeChild(this._pixi); + this._win.removePixiObject(this._pixi); } } this.status = PsychoJS.Status.STOPPED; @@ -150,10 +140,7 @@ export class MinimalStim extends PsychObject /** * Determine whether an object is inside this stimulus. * - * @name module:core.MinimalStim#contains - * @function * @abstract - * @public * @param {Object} object - the object * @param {String} units - the stimulus units */ @@ -169,11 +156,7 @@ export class MinimalStim extends PsychObject /** * Release the PIXI representation, if there is one. * - * @name module:core.MinimalStim#release - * @function - * @public - * - * @param {boolean} [log= false] - whether or not to log + * @param {boolean} [log= false] - whether to log */ release(log = false) { @@ -192,10 +175,8 @@ export class MinimalStim extends PsychObject * * Note: this is an abstract function, which should not be called. * - * @name module:core.MinimalStim#_updateIfNeeded - * @function * @abstract - * @private + * @protected */ _updateIfNeeded() { diff --git a/src/core/Mouse.js b/src/core/Mouse.js index ab854318..2ebc0b92 100644 --- a/src/core/Mouse.js +++ b/src/core/Mouse.js @@ -3,8 +3,8 @@ * * @author Alain Pitiot * @author Sotiri Bakagiannis - isPressedIn - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -16,18 +16,17 @@ import { PsychoJS } from "./PsychoJS.js"; *

    This manager handles the interactions between the experiment's stimuli and the mouse.

    *

    Note: the unit of Mouse is that of its associated Window.

    * - * @name module:core.Mouse - * @class - * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {Window} options.win - the associated Window - * @param {boolean} [options.autoLog= true] - whether or not to log - * * @todo visible is not handled at the moment (mouse is always visible) */ export class Mouse extends PsychObject { + /** + * @memberof module:core + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {Window} options.win - the associated Window + * @param {boolean} [options.autoLog= true] - whether or not to log + */ constructor({ name, win, @@ -54,9 +53,6 @@ export class Mouse extends PsychObject /** * Get the current position of the mouse in mouse/Window units. * - * @name module:core.Mouse#getPos - * @function - * @public * @return {Array.number} the position of the mouse in mouse/Window units */ getPos() @@ -79,9 +75,6 @@ export class Mouse extends PsychObject * Get the position of the mouse relative to that at the last call to getRel * or getPos, in mouse/Window units. * - * @name module:core.Mouse#getRel - * @function - * @public * @return {Array.number} the relation position of the mouse in mouse/Window units. */ getRel() @@ -105,9 +98,6 @@ export class Mouse extends PsychObject *

    Note: Even though this method returns a [x, y] array, for most wheels/systems y is the only * value that varies.

    * - * @name module:core.Mouse#getWheelRel - * @function - * @public * @return {Array.number} the mouse scroll wheel travel */ getWheelRel() @@ -127,9 +117,6 @@ export class Mouse extends PsychObject * *

    Note: clickReset is typically called at stimulus onset. When the participant presses a button, the time elapsed since the clickReset is stored internally and can be accessed any time afterwards with getPressed.

    * - * @name module:core.Mouse#getPressed - * @function - * @public * @param {boolean} [getTime= false] whether or not to also return timestamps * @return {Array.number | Array.} either an array of size 3 with the status (1 for pressed, 0 for released) of each mouse button [left, center, right], or a tuple with that array and another array of size 3 with the timestamps. */ @@ -150,9 +137,6 @@ export class Mouse extends PsychObject /** * Helper method for checking whether a stimulus has had any button presses within bounds. * - * @name module:core.Mouse#isPressedIn - * @function - * @public * @param {object|module:visual.VisualStim} shape A type of visual stimulus or object having a `contains()` method. * @param {object|number} [buttons] The target button index potentially tucked inside an object. * @param {object} [options] @@ -222,9 +206,6 @@ export class Mouse extends PsychObject *
  • mouseMoved(distance, [x: number, y: number]: artifically set the previous mouse position to the given coordinates and determine whether the mouse moved further than the given distance
  • *

    * - * @name module:core.Mouse#mouseMoved - * @function - * @public * @param {undefined|number|Array.number} [distance] - the distance to which the mouse movement is compared (see above for a full description) * @param {boolean|String|Array.number} [reset= false] - see above for a full description * @return {boolean} see above for a full description @@ -319,9 +300,6 @@ export class Mouse extends PsychObject /** * Get the amount of time elapsed since the last mouse movement. * - * @name module:core.Mouse#mouseMoveTime - * @function - * @public * @return {number} the time elapsed since the last mouse movement */ mouseMoveTime() @@ -332,9 +310,6 @@ export class Mouse extends PsychObject /** * Reset the clocks associated to the given mouse buttons. * - * @name module:core.Mouse#clickReset - * @function - * @public * @param {Array.number} [buttons= [0,1,2]] the buttons to reset (0: left, 1: center, 2: right) */ clickReset(buttons = [0, 1, 2]) diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index a9e1ff62..ef5ef7f1 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -3,8 +3,8 @@ * Main component of the PsychoJS library. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -18,21 +18,14 @@ import { GUI } from "./GUI.js"; import { Logger } from "./Logger.js"; import { ServerManager } from "./ServerManager.js"; import { Window } from "./Window.js"; -// import {Shelf} from "../data/Shelf"; +import {Shelf} from "../data/Shelf"; /** - *

    PsychoJS manages the lifecycle of an experiment. It initialises the PsychoJS library and its various components (e.g. the {@link ServerManager}, the {@link EventManager}), and is used by the experiment to schedule the various tasks.

    - * - * @class - * @param {Object} options - * @param {boolean} [options.debug= true] whether or not to log debug information in the browser console - * @param {boolean} [options.collectIP= false] whether or not to collect the IP information of the participant + *

    PsychoJS initialises the library and its various components (e.g. the [ServerManager]{@link module:core.ServerManager}, the [EventManager]{@link module:core.EventManager}), and manages + * the lifecycle of an experiment.

    */ export class PsychoJS { - /** - * Properties - */ get status() { return this._status; @@ -109,25 +102,33 @@ export class PsychoJS return this._browser; } - // get shelf() - // { - // return this._shelf; - // } + get shelf() + { + return this._shelf; + } /** - * @constructor - * @public + * @param {Object} options + * @param {boolean} [options.debug= true] whether to log debug information in the browser console + * @param {boolean} [options.collectIP= false] whether to collect the IP information of the participant */ constructor({ debug = true, collectIP = false, hosts = [], topLevelStatus = true, + autoStartScheduler = true, + saveResults = true, + captureErrors = true, + checkWebGLSupport = false } = {}) { // logging: this._logger = new Logger(this, (debug) ? log4javascript.Level.DEBUG : log4javascript.Level.INFO); - this._captureErrors(); + if (captureErrors) + { + this._captureErrors(); + } // detect the browser: this._browser = util.detectBrowser(); @@ -158,15 +159,15 @@ export class PsychoJS // Window: this._window = undefined; - // // Shelf: - // this._shelf = new Shelf(this); + // Shelf: + this._shelf = new Shelf({psychoJS: this}); // redirection URLs: this._cancellationUrl = undefined; this._completionUrl = undefined; // status: - this._status = PsychoJS.Status.NOT_CONFIGURED; + this.status = PsychoJS.Status.NOT_CONFIGURED; // make the PsychoJS.Status accessible from the top level of the generated experiment script // in order to accommodate PsychoPy's Code Components @@ -175,11 +176,21 @@ export class PsychoJS this._makeStatusTopLevel(); } + // whether to start the scheduler when the experiment starts: + this._autoStartScheduler = autoStartScheduler; + + // whether to check for actual hardware accelerated WebGL support: + this._checkWebGLSupport = checkWebGLSupport; + + // whether to save results at the end of the experiment: + this._saveResults = saveResults; + this.logger.info("[PsychoJS] Initialised."); - this.logger.info("[PsychoJS] @version 2022.1.2"); + this.logger.info("[PsychoJS] @version 2022.3.0"); // hide the initialisation message: - jQuery("#root").addClass("is-ready"); + const root = document.getElementById("root"); + root.classList.add("is-ready"); } /** @@ -211,13 +222,12 @@ export class PsychoJS * @param {boolean} [options.waitBlanking] whether or not to wait for all rendering operations to be done * before flipping * @throws {Object.} exception if a window has already been opened - * - * @public */ openWindow({ name, fullscr, color, + gamma, units, waitBlanking, autoLog, @@ -239,6 +249,7 @@ export class PsychoJS name, fullscr, color, + gamma, units, waitBlanking, autoLog, @@ -260,9 +271,8 @@ export class PsychoJS /** * Schedule a task. * - * @param task - the task to be scheduled - * @param args - arguments for that task - * @public + * @param {module:util.Scheduler~Task} task - the task to be scheduled + * @param {*} [args] - arguments for that task */ schedule(task, args) { @@ -279,9 +289,8 @@ export class PsychoJS * Schedule a series of task based on a condition. * * @param {PsychoJS.condition} condition - * @param {Scheduler} thenScheduler scheduler to run if the condition is true - * @param {Scheduler} elseScheduler scheduler to run if the condition is false - * @public + * @param {Scheduler} thenScheduler - scheduler to run if the condition is true + * @param {Scheduler} elseScheduler - scheduler to run if the condition is false */ scheduleCondition(condition, thenScheduler, elseScheduler) { @@ -309,10 +318,15 @@ export class PsychoJS * @param {string} [options.expName=UNKNOWN] - the name of the experiment * @param {Object.} [options.expInfo] - additional information about the experiment * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @async - * @public */ - async start({ configURL = "config.json", expName = "UNKNOWN", expInfo = {}, resources = [], dataFileName } = {}) + async start({ + configURL = "config.json", + expName = "UNKNOWN", + expInfo = {}, + resources = [], + dataFileName, + surveyId} = {} + ) { this.logger.debug(); @@ -355,7 +369,16 @@ export class PsychoJS if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) { // open a session: - await this._serverManager.openSession(); + const params = {}; + if (this._serverMsg.has("__pilotToken")) + { + params.pilotToken = this._serverMsg.get("__pilotToken"); + } + if (typeof surveyId !== "undefined") + { + params.surveyId = surveyId; + } + await this._serverManager.openSession(params); // warn the user when they attempt to close the tab or browser: this.beforeunloadCallback = (event) => @@ -377,7 +400,7 @@ export class PsychoJS if (self._config.session.status === "OPEN") { // save the incomplete results if need be: - if (self._config.experiment.saveIncompleteResults) + if (self._config.experiment.saveIncompleteResults && self._saveResults) { self._experiment.save({ sync: true }); } @@ -396,14 +419,43 @@ export class PsychoJS // start the asynchronous download of resources: this._serverManager.prepareResources(resources); - // start the experiment: - this.logger.info("[PsychoJS] Start Experiment."); - await this._scheduler.start(); + // if WebGL is not actually available, warn the participant and ask them whether they want to go ahead + if (this._checkWebGLSupport && !Window.checkWebGLSupport()) + { + // add an entry to experiment results to warn the designer about a potential WebGL issue: + this._experiment.addData('hardware_acceleration', 'NOT SUPPORTED'); + this._experiment.nextEntry(); + + this._gui.dialog({ + warning: "It appears that hardware acceleration is either not supported by your browser or currently switched off.
    As a consequence, this experiment will be rendered using software emulation and advanced features, such as gratings and gamma correction, will not be available.

    You may want to press Cancel, change your browser settings, and reload the experiment. Otherwise press OK to proceed as is.", + showCancel: true, + onCancel: () => + { + this.quit(); + }, + onOK: () => + { + this.status = PsychoJS.Status.STARTED; + this.logger.info("[PsychoJS] Start Experiment (software emulation mode)."); + this._scheduler.start(); + } + }); + } + else + { + if (this._autoStartScheduler) + { + this.status = PsychoJS.Status.STARTED; + this.logger.info("[PsychoJS] Start Experiment."); + this._scheduler.start(); + } + } + } catch (error) { - // this._gui.dialog({ error: { ...response, error } }); - this._gui.dialog({ error: Object.assign(response, { error }) }); + this.status = PsychoJS.Status.ERROR; + throw { ...response, error }; } } @@ -421,7 +473,6 @@ export class PsychoJS * local to index.html unless they are prepended with a protocol. * * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @public */ waitForResources(resources = []) { @@ -442,11 +493,9 @@ export class PsychoJS } /** - * Make the attributes of the given object those of PsychoJS and those of - * the top level variable (e.g. window) as well. + * Make the attributes of the given object those of window, such that they become global. * - * @param {Object.} obj the object whose attributes we will mirror - * @public + * @param {Object.} obj the object whose attributes are to become global */ importAttributes(obj) { @@ -459,7 +508,6 @@ export class PsychoJS for (const attribute in obj) { - // this[attribute] = obj[attribute]; window[attribute] = obj[attribute]; } } @@ -473,16 +521,17 @@ export class PsychoJS * * @param {Object} options * @param {string} [options.message] - optional message to be displayed in a dialog box before quitting - * @param {boolean} [options.isCompleted = false] - whether or not the participant has completed the experiment - * @async - * @public + * @param {boolean} [options.isCompleted = false] - whether the participant has completed the experiment */ - async quit({ message, isCompleted = false } = {}) + async quit({ message, isCompleted = false, closeWindow = true, showOK = true } = {}) { this.logger.info("[PsychoJS] Quit."); + const response = { origin: "PsychoJS.quit", context: "when terminating the experiment" }; + this._experiment.experimentEnded = true; - this._status = PsychoJS.Status.FINISHED; + this.status = PsychoJS.Status.STOPPED; + const isServerEnv = (this.getEnvironment() === ExperimentHandler.Environment.SERVER); try { @@ -490,74 +539,92 @@ export class PsychoJS this._scheduler.stop(); // remove the beforeunload listener: - if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) + if (isServerEnv) { window.removeEventListener("beforeunload", this.beforeunloadCallback); } // save the results and the logs of the experiment: - this.gui.dialog({ - warning: "Closing the session. Please wait a few moments.", - showOK: false, + this.gui.finishDialog({ + text: "Terminating the experiment. Please wait a few moments...", + nbSteps: ((this._saveResults) ? 2 : 0) + ((isServerEnv) ? 1 : 0) }); + if (isCompleted || this._config.experiment.saveIncompleteResults) { - if (!this._serverMsg.has("__noOutput")) + if (this._saveResults) { + this.gui.finishDialogNextStep("saving results"); await this._experiment.save(); + this.gui.finishDialogNextStep("saving logs"); await this._logger.flush(); } } // close the session: - if (this.getEnvironment() === ExperimentHandler.Environment.SERVER) + if (isServerEnv) { + this.gui.finishDialogNextStep("closing the session"); await this._serverManager.closeSession(isCompleted); } - // thank participant for waiting and either quit or redirect: - let text = "Thank you for your patience.

    "; - text += (typeof message !== "undefined") ? message : "Goodbye!"; - const self = this; - this._gui.dialog({ - message: text, - onOK: () => + // thank participant for waiting, and either quit or redirect: + const onTerminate = () => + { + if (closeWindow) { // close the window: - self._window.close(); + this._window.close(); // remove everything from the browser window: while (document.body.hasChildNodes()) { document.body.removeChild(document.body.lastChild); } + } - // return from fullscreen if we were there: - this._window.closeFullScreen(); + // return from fullscreen if we were there: + this._window.closeFullScreen(); - // redirect if redirection URLs have been provided: - if (isCompleted && typeof self._completionUrl !== "undefined") - { - window.location = self._completionUrl; - } - else if (!isCompleted && typeof self._cancellationUrl !== "undefined") - { - window.location = self._cancellationUrl; - } - }, - }); + this.status = PsychoJS.Status.FINISHED; + + // redirect if redirection URLs have been provided: + if (isCompleted && typeof this._completionUrl !== "undefined") + { + window.location = this._completionUrl; + } + else if (!isCompleted && typeof this._cancellationUrl !== "undefined") + { + window.location = this._cancellationUrl; + } + }; + + if (showOK) + { + let text = "Thank you for your patience.

    "; + text += (typeof message !== "undefined") ? message : "Goodbye!"; + this._gui.dialog({ + message: text, + onOK: onTerminate + }); + } + else + { + this._gui.closeDialog(); + onTerminate(); + } } catch (error) { - console.error(error); - this._gui.dialog({ error }); + this.status = PsychoJS.Status.ERROR; + throw { ...response, error }; + // this._gui.dialog({ error: { ...response, error } }); } } /** * Configure PsychoJS for the running experiment. * - * @async * @protected * @param {string} configURL - the URL of the configuration file * @param {string} name - the name of the experiment @@ -573,7 +640,7 @@ export class PsychoJS { this.status = PsychoJS.Status.CONFIGURING; - // if the experiment is running from an approved hosts, e.e pavlovia.org, + // if the experiment is running from an approved host, e.g pavlovia.org, // we read the configuration file: const experimentUrl = window.location.href; const isHost = this._hosts.some(url => experimentUrl.indexOf(url) === 0); @@ -624,7 +691,7 @@ export class PsychoJS this._config.environment = ExperimentHandler.Environment.SERVER; } - // otherwise we create an ad-hoc configuration: + // otherwise, we create an ad-hoc configuration: else { this._config = { @@ -648,6 +715,12 @@ export class PsychoJS } }); + // note: __noOutput is typically used for automated testing + if (this._serverMsg.has("__noOutput")) + { + this._saveResults = false; + } + this.status = PsychoJS.Status.CONFIGURED; this.logger.debug("configuration:", util.toString(this._config)); } @@ -676,15 +749,28 @@ export class PsychoJS this._IP = {}; try { - const geoResponse = await jQuery.get("http://www.geoplugin.net/json.gp"); - const geoData = JSON.parse(geoResponse); + const url = "http://www.geoplugin.net/json.gp"; + const getResponse = await fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + if (getResponse.status !== 200) + { + throw `unable to obtain the IP of the participant: ${response.statusText}`; + } + const geoData = await getResponse.json(); + this._IP = { IP: geoData.geoplugin_request, country: geoData.geoplugin_countryName, latitude: geoData.geoplugin_latitude, longitude: geoData.geoplugin_longitude, }; - this.logger.debug("IP information of the participant: " + util.toString(this._IP)); + this.logger.debug("IP information of the participant:", util.toString(this._IP)); } catch (error) { @@ -695,7 +781,6 @@ export class PsychoJS /** * Capture all errors and display them in a pop-up error box. - * * @protected */ _captureErrors() @@ -704,7 +789,16 @@ export class PsychoJS const self = this; window.onerror = function(message, source, lineno, colno, error) - { + {console.log('@@@', message) + // check for ResizeObserver loop limit exceeded error: + // ref: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded + if (message === "ResizeObserver loop limit exceeded" || + message === "ResizeObserver loop completed with undelivered notifications.") + { + console.warn(message); + return true; + } + console.error(error); document.body.setAttribute( @@ -718,7 +812,14 @@ export class PsychoJS }), ); - self._gui.dialog({ "error": error }); + if (error !== null) + { + self._gui.dialog({"error": error}); + } + else + { + self._gui.dialog({"error": message}); + } return true; }; @@ -727,12 +828,12 @@ export class PsychoJS console.error(error?.reason); if (error?.reason?.stack === undefined) { - // No stack? Error thrown by PsychoJS; stringify whole error + // No stack? Error thrown by PsychoJS: stringify whole error document.body.setAttribute("data-error", JSON.stringify(error?.reason)); } else { - // Yes stack? Error thrown by JS; stringify stack + // Yes stack? Error thrown by JS: stringify stack document.body.setAttribute("data-error", JSON.stringify(error?.reason?.stack)); } self._gui.dialog({ error: error?.reason }); @@ -742,7 +843,7 @@ export class PsychoJS /** * Make the various Status top level, in order to accommodate PsychoPy's Code Components. - * @private + * @protected */ _makeStatusTopLevel() { @@ -758,7 +859,6 @@ export class PsychoJS * * @enum {Symbol} * @readonly - * @public * * @note PsychoPy is currently moving away from STOPPED and replacing STOPPED by FINISHED. * For backward compatibility reasons, we are keeping diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 074674c2..1b59a994 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -1,9 +1,11 @@ /** - * Manager responsible for the communication between the experiment running in the participant's browser and the pavlovia.org server. + * Manager responsible for the communication between the experiment running in the participant's browser and the + * pavlovia.org server. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. + * (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -16,28 +18,31 @@ import { Scheduler } from "../util/Scheduler.js"; import { PsychoJS } from "./PsychoJS.js"; /** - *

    This manager handles all communications between the experiment running in the participant's browser and the [pavlovia.org]{@link http://pavlovia.org} server, in an asynchronous manner.

    - *

    It is responsible for reading the configuration file of an experiment, for opening and closing a session, for listing and downloading resources, and for uploading results, logs, and audio recordings.

    + *

    This manager handles all communications between the experiment running in the participant's browser and the + * [pavlovia.org]{@link http://pavlovia.org} server, in an asynchronous manner.

    + *

    It is responsible for reading the configuration file of an experiment, for opening and closing a session, for + * listing and downloading resources, and for uploading results, logs, and audio recordings.

    * - * @name module:core.ServerManager - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class ServerManager extends PsychObject { - /**************************************************************************** + /** * Used to indicate to the ServerManager that all resources must be registered (and * subsequently downloaded) * - * @type {symbol} + * @type {Symbol} * @readonly * @public */ static ALL_RESOURCES = Symbol.for("ALL_RESOURCES"); + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {boolean} [options.autoLog= false] - whether or not to log + */ constructor({ psychoJS, autoLog = false, @@ -53,26 +58,21 @@ export class ServerManager extends PsychObject this._nbLoadedResources = 0; this._setupPreloadQueue(); - this._addAttribute("autoLog", autoLog); this._addAttribute("status", ServerManager.Status.READY); } - /**************************************************************************** + /** * @typedef ServerManager.GetConfigurationPromise * @property {string} origin the calling method * @property {string} context the context * @property {Object.} [config] the configuration * @property {Object.} [error] an error message if we could not read the configuration file */ - /**************************************************************************** + /** * Read the configuration file for the experiment. * - * @name module:core.ServerManager#getConfiguration - * @function - * @public * @param {string} configURL - the URL of the configuration file - * * @returns {Promise} the response */ getConfiguration(configURL) @@ -84,130 +84,138 @@ export class ServerManager extends PsychObject this._psychoJS.logger.debug("reading the configuration file: " + configURL); const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - jQuery.get(configURL, "json") - .done((config, textStatus) => + try + { + const getResponse = await fetch(configURL, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + if (getResponse.status === 404) { - // resolve({ ...response, config }); - resolve(Object.assign(response, { config })); - }) - .fail((jqXHR, textStatus, errorThrown) => + throw "the configuration file could not be found"; + } + else if (getResponse.status !== 200) { - self.setStatus(ServerManager.Status.ERROR); + throw `unable to read the configuration file: status= ${getResponse.status}`; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); + // the configuration file should be valid json: + const config = await getResponse.json(); + resolve(Object.assign(response, { config })); + } + catch (error) + { + self.setStatus(ServerManager.Status.ERROR); + console.error("error:", error); - reject(Object.assign(response, { error: errorMsg })); - }); + reject(Object.assign(response, { error })); + } }); } - /**************************************************************************** + /** * @typedef ServerManager.OpenSessionPromise * @property {string} origin the calling method * @property {string} context the context * @property {string} [token] the session token * @property {Object.} [error] an error message if we could not open the session */ - /**************************************************************************** + /** * Open a session for this experiment on the remote PsychoJS manager. * - * @name module:core.ServerManager#openSession - * @function - * @public + * @param {Object} params - the open session parameters + * * @returns {Promise} the response */ - openSession() + openSession(params = {}) { const response = { origin: "ServerManager.openSession", context: "when opening a session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug("opening a session for experiment: " + this._psychoJS.config.experiment.fullpath); this.setStatus(ServerManager.Status.BUSY); - // prepare POST query: - let data = {}; - if (this._psychoJS._serverMsg.has("__pilotToken")) - { - data.pilotToken = this._psychoJS._serverMsg.get("__pilotToken"); - } - - // query pavlovia server: + // query the server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL - + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId - + "/sessions"; - jQuery.post(url, data, null, "json") - .done((data, textStatus) => - { - if (!("token" in data)) - { - self.setStatus(ServerManager.Status.ERROR); - reject(Object.assign(response, { error: "unexpected answer from server: no token" })); - // reject({...response, error: 'unexpected answer from server: no token'}); - } - if (!("experiment" in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({...response, error: 'unexpected answer from server: no experiment'}); - reject(Object.assign(response, { error: "unexpected answer from server: no experiment" })); - } + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions`, + params, + "FORM" + ); - self._psychoJS.config.session = { - token: data.token, - status: "OPEN", - }; - self._psychoJS.config.experiment.status = data.experiment.status2; - self._psychoJS.config.experiment.saveFormat = Symbol.for(data.experiment.saveFormat); - self._psychoJS.config.experiment.saveIncompleteResults = data.experiment.saveIncompleteResults; - self._psychoJS.config.experiment.license = data.experiment.license; - self._psychoJS.config.experiment.runMode = data.experiment.runMode; - - // secret keys for various services, e.g. Google Speech API - if ("keys" in data.experiment) - { - self._psychoJS.config.experiment.keys = data.experiment.keys; - } - else - { - self._psychoJS.config.experiment.keys = []; - } + const openSessionResponse = await postResponse.json(); - self.setStatus(ServerManager.Status.READY); - // resolve({ ...response, token: data.token, status: data.status }); - resolve(Object.assign(response, { token: data.token, status: data.status })); - }) - .fail((jqXHR, textStatus, errorThrown) => + if (postResponse.status !== 200) + { + throw ('error' in openSessionResponse) ? openSessionResponse.error : openSessionResponse; + } + if (!("token" in openSessionResponse)) + { + self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from the server: no token"; + } + if (!("experiment" in openSessionResponse)) { self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no experiment"; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); + self._psychoJS.config.session = { + token: openSessionResponse.token, + status: "OPEN", + }; + const experiment = openSessionResponse.experiment; + self._psychoJS.config.experiment.status = experiment.status2; + self._psychoJS.config.experiment.saveFormat = Symbol.for(experiment.saveFormat); + self._psychoJS.config.experiment.saveIncompleteResults = experiment.saveIncompleteResults; + self._psychoJS.config.experiment.license = experiment.license; + self._psychoJS.config.experiment.runMode = experiment.runMode; + + // secret keys for various services, e.g. Google Speech API + if ("keys" in experiment) + { + self._psychoJS.config.experiment.keys = experiment.keys; + } + else + { + self._psychoJS.config.experiment.keys = []; + } - reject(Object.assign(response, { error: errorMsg })); - }); + self.setStatus(ServerManager.Status.READY); + resolve({...response, token: openSessionResponse.token, status: openSessionResponse.status }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } - /**************************************************************************** + /** * @typedef ServerManager.CloseSessionPromise * @property {string} origin the calling method * @property {string} context the context - * @property {Object.} [error] an error message if we could not close the session (e.g. if it has not previously been opened) + * @property {Object.} [error] an error message if we could not close the session (e.g. if it has not + * previously been opened) */ - /**************************************************************************** + /** * Close the session for this experiment on the remote PsychoJS manager. * - * @name module:core.ServerManager#closeSession - * @function - * @public * @param {boolean} [isCompleted= false] - whether or not the experiment was completed * @param {boolean} [sync= false] - whether or not to communicate with the server in a synchronous manner * @returns {Promise | void} the response @@ -218,78 +226,61 @@ export class ServerManager extends PsychObject origin: "ServerManager.closeSession", context: "when closing the session for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug("closing the session for experiment: " + this._psychoJS.config.experiment.name); this.setStatus(ServerManager.Status.BUSY); - // prepare DELETE query: - const url = this._psychoJS.config.pavlovia.URL - + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId - + "/sessions/" + this._psychoJS.config.session.token; - - // synchronous query the pavlovia server: + // synchronously query the pavlovia server: if (sync) { - /* This is now deprecated in most browsers. - const request = new XMLHttpRequest(); - request.open("DELETE", url, false); - request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - request.send(JSON.stringify(data)); - */ - /* This does not work in Chrome because of a CORS bug - await fetch(url, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json;charset=UTF-8' }, - body: JSON.stringify(data), - // keepalive makes it possible for the request to outlive the page (e.g. when the participant closes the tab) - keepalive: true - }); - */ + const url = this._psychoJS.config.pavlovia.URL + + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + + "/sessions/" + this._psychoJS.config.session.token + "/delete"; const formData = new FormData(); formData.append("isCompleted", isCompleted); - navigator.sendBeacon(url + "/delete", formData); + + navigator.sendBeacon(url, formData); this._psychoJS.config.session.status = "CLOSED"; } // asynchronously query the pavlovia server: else { const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - jQuery.ajax({ - url, - type: "delete", - data: { isCompleted }, - dataType: "json", - }) - .done((data, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - self._psychoJS.config.session.status = "CLOSED"; + try + { + const deleteResponse = await this._queryServerAPI( + "DELETE", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}`, + { isCompleted }, + "FORM" + ); - // resolve({ ...response, data }); - resolve(Object.assign(response, { data })); - }) - .fail((jqXHR, textStatus, errorThrown) => - { - self.setStatus(ServerManager.Status.ERROR); + const closeSessionResponse = await deleteResponse.json(); - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); + if (deleteResponse.status !== 200) + { + throw ('error' in closeSessionResponse) ? closeSessionResponse.error : closeSessionResponse; + } - reject(Object.assign(response, { error: errorMsg })); - }); + self.setStatus(ServerManager.Status.READY); + self._psychoJS.config.session.status = "CLOSED"; + resolve({ ...response, ...closeSessionResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } } - /**************************************************************************** + /** * Get the value of a resource. * - * @name module:core.ServerManager#getResource - * @function - * @public * @param {string} name - name of the requested resource * @param {boolean} [errorIfNotDownloaded = false] whether or not to throw an exception if the * resource status is not DOWNLOADED @@ -323,7 +314,7 @@ export class ServerManager extends PsychObject return pathStatusData.data; } - /**************************************************************************** + /** * Get the status of a single resource or the reduced status of an array of resources. * *

    If an array of resources is given, getResourceStatus returns a single, reduced status @@ -338,11 +329,9 @@ export class ServerManager extends PsychObject * *

    * - * @name module:core.ServerManager#getResourceStatus - * @function - * @public * @param {string | string[]} names names of the resources whose statuses are requested - * @return {core.ServerManager.ResourceStatus} status of the resource if there is only one, or reduced status otherwise + * @return {module:core.ServerManager.ResourceStatus} status of the resource if there is only one, or reduced status + * otherwise * @throws {Object.} if at least one of the names is not that of a previously * registered resource */ @@ -392,12 +381,8 @@ export class ServerManager extends PsychObject return reducedStatus; } - /**************************************************************************** + /** * Set the resource manager status. - * - * @name module:core.ServerManager#setStatus - * @function - * @public */ setStatus(status) { @@ -425,12 +410,9 @@ export class ServerManager extends PsychObject return this._status; } - /**************************************************************************** + /** * Reset the resource manager status to ServerManager.Status.READY. * - * @name module:core.ServerManager#resetStatus - * @function - * @public * @return {ServerManager.Status.READY} the new status */ resetStatus() @@ -438,7 +420,7 @@ export class ServerManager extends PsychObject return this.setStatus(ServerManager.Status.READY); } - /**************************************************************************** + /** * Prepare resources for the experiment: register them with the server manager and possibly * start downloading them right away. * @@ -451,10 +433,8 @@ export class ServerManager extends PsychObject *
  • If resources is null, then we do not download any resources
  • * * - * @name module:core.ServerManager#prepareResources - * @param {String | Array.<{name: string, path: string, download: boolean} | String | Symbol>} [resources=[]] - the list of resources or a single resource - * @function - * @public + * @param {String | Array.<{name: string, path: string, download: boolean} | String | Symbol>} [resources=[]] - the + * list of resources or a single resource */ async prepareResources(resources = []) { @@ -523,17 +503,68 @@ export class ServerManager extends PsychObject throw "resources must be manually specified when the experiment is running locally: ALL_RESOURCES cannot be used"; } - // convert those resources that are only a string to an object with name and path: + // pre-process the resources: for (let r = 0; r < resources.length; ++r) { const resource = resources[r]; + + // convert those resources that are only a string to an object with name and path: if (typeof resource === "string") { resources[r] = { name: resource, path: resource, download: true + }; + } + + // deal with survey models: + if ("surveyId" in resource) + { + // survey models can only be downloaded if the experiment is hosted on the pavlovia.org server: + if (this._psychoJS.config.environment !== ExperimentHandler.Environment.SERVER) + { + throw "survey models cannot be downloaded when the experiment is running locally"; } + + // we add a .sid extension so _downloadResources knows what to download the associated + // survey model from the server + resources[r] = { + name: `${resource["surveyId"]}.sid`, + path: resource["surveyId"], + download: true + }; + } + + // deal with survey libraries: + if ("surveyLibrary" in resource) + { + // add the SurveyJS and PsychoJS Survey .js and .css resources: + resources[r] = { + name: "jquery-3.6.0.min.js", + path: "./lib/vendors/jquery-3.6.0.min.js", + download: true + }; + resources.push({ + name: "survey.jquery-1.9.50.min.js", + path: "./lib/vendors/survey.jquery-1.9.50.min.js", + download: true + }); + resources.push({ + name: "survey.defaultV2-1.9.50.min.css", + path: "./lib/vendors/survey.defaultV2-1.9.50.min.css", + download: true + }); + resources.push({ + name: "survey.widgets.css", + path: "./lib/vendors/survey.widgets.css", + download: true + }); + resources.push({ + name: "survey.grey_style.css", + path: "./lib/vendors/survey.grey_style.css", + download: true + }); } } @@ -603,13 +634,10 @@ export class ServerManager extends PsychObject } } - /**************************************************************************** + /** * Block the experiment until the specified resources have been downloaded. * - * @name module:core.ServerManager#waitForResources * @param {Array.<{name: string, path: string}>} [resources=[]] - the list of resources - * @function - * @public */ waitForResources(resources = []) { @@ -653,7 +681,7 @@ export class ServerManager extends PsychObject && (path.indexOf("pavlovia.org") === -1) ) { - path = "https://devlovia.org/api/v2/proxy/" + path; + path = "https://pavlovia.org/api/v2/proxy/" + path; } const pathStatusData = this._resources.get(name); @@ -705,22 +733,18 @@ export class ServerManager extends PsychObject }; } - /**************************************************************************** + /** * @typedef ServerManager.UploadDataPromise * @property {string} origin the calling method * @property {string} context the context * @property {Object.} [error] an error message if we could not upload the data */ - /**************************************************************************** + /** * Asynchronously upload experiment data to the pavlovia server. * - * @name module:core.ServerManager#uploadData - * @function - * @public * @param {string} key - the data key (e.g. the name of .csv file) * @param {string} value - the data value (e.g. a string containing the .csv header and records) * @param {boolean} [sync= false] - whether or not to communicate with the server in a synchronous manner - * * @returns {Promise} the response */ uploadData(key, value, sync = false) @@ -729,59 +753,57 @@ export class ServerManager extends PsychObject origin: "ServerManager.uploadData", context: "when uploading participant's results for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug("uploading data for experiment: " + this._psychoJS.config.experiment.fullpath); + this.setStatus(ServerManager.Status.BUSY); - const url = this._psychoJS.config.pavlovia.URL - + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) - + "/sessions/" + this._psychoJS.config.session.token - + "/results"; + const path = `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}/results`; - // synchronous query the pavlovia server: + // synchronously query the pavlovia server: if (sync) { const formData = new FormData(); formData.append("key", key); formData.append("value", value); - navigator.sendBeacon(url, formData); + navigator.sendBeacon(`${this._psychoJS.config.pavlovia.URL}/api/v2/${path}`, formData); } // asynchronously query the pavlovia server: else { const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const data = { - key, - value, - }; - - jQuery.post(url, data, null, "json") - .done((serverData, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, { serverData })); - }) - .fail((jqXHR, textStatus, errorThrown) => + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${this._psychoJS.config.session.token}/results`, + { key, value }, + "FORM" + ); + const uploadDataResponse = await postResponse.json(); + + if (postResponse.status !== 200) { - self.setStatus(ServerManager.Status.ERROR); - - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); + throw ('error' in uploadDataResponse) ? uploadDataResponse.error : uploadDataResponse; + } - reject(Object.assign(response, { error: errorMsg })); - }); + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, ...uploadDataResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } } - /**************************************************************************** + /** * Asynchronously upload experiment logs to the pavlovia server. * - * @name module:core.ServerManager#uploadLog - * @function - * @public * @param {string} logs - the base64 encoded, compressed, formatted logs * @param {boolean} [compressed=false] - whether or not the logs are compressed * @returns {Promise} the response @@ -792,62 +814,65 @@ export class ServerManager extends PsychObject origin: "ServerManager.uploadLog", context: "when uploading participant's log for experiment: " + this._psychoJS.config.experiment.fullpath, }; - this._psychoJS.logger.debug("uploading server log for experiment: " + this._psychoJS.config.experiment.fullpath); + this.setStatus(ServerManager.Status.BUSY); - // prepare the POST query: + // prepare a POST query: const info = this.psychoJS.experiment.extraInfo; - const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); - const experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name; - const datetime = ((typeof info.date !== "undefined") ? info.date : MonotonicClock.getDateStr()); - const filename = participant + "_" + experimentName + "_" + datetime + ".log"; + const filenameWithoutPath = this.psychoJS.experiment.dataFileName.split(/[\\/]/).pop(); + const filename = `${filenameWithoutPath}.log`; const data = { filename, logs, - compressed, + compressed }; // query the pavlovia server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = self._psychoJS.config.pavlovia.URL - + "/api/v2/experiments/" + encodeURIComponent(self._psychoJS.config.experiment.fullpath) - + "/sessions/" + self._psychoJS.config.session.token - + "/logs"; + try + { + const postResponse = await this._queryServerAPI( + "POST", + `experiments/${this._psychoJS.config.gitlab.projectId}/sessions/${self._psychoJS.config.session.token}/logs`, + data, + "FORM" + ); - jQuery.post(url, data, null, "json") - .done((serverData, textStatus) => - { - self.setStatus(ServerManager.Status.READY); - resolve(Object.assign(response, { serverData })); - }) - .fail((jqXHR, textStatus, errorThrown) => + const uploadLogsResponse = await postResponse.json(); + + if (postResponse.status !== 200) { - self.setStatus(ServerManager.Status.ERROR); + throw ('error' in uploadLogsResponse) ? uploadLogsResponse.error : uploadLogsResponse; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); + self.setStatus(ServerManager.Status.READY); + resolve({...response, ...uploadLogsResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } - reject(Object.assign(response, { error: errorMsg })); - }); }); } - /**************************************************************************** + /** * Synchronously or asynchronously upload audio data to the pavlovia server. * - * @name module:core.ServerManager#uploadAudioVideo - * @function - * @public * @param @param {Object} options * @param {Blob} options.mediaBlob - the audio or video blob to be uploaded * @param {string} options.tag - additional tag * @param {boolean} [options.waitForCompletion=false] - whether or not to wait for completion * before returning - * @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server - * @param {string} [options.dialogMsg="Please wait a few moments while the data is uploading to the server"] - default message informing the participant to wait for the data to be uploaded to the server + * @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to + * wait for the data to be uploaded to the server + * @param {string} [options.dialogMsg="Please wait a few moments while the data is uploading to the server"] - + * default message informing the participant to wait for the data to be uploaded to the server * @returns {Promise} the response */ async uploadAudioVideo({mediaBlob, tag, waitForCompletion = false, showDialog = false, dialogMsg = "Please wait a few moments while the data is uploading to the server"}) @@ -970,12 +995,140 @@ export class ServerManager extends PsychObject } } - /**************************************************************************** + /** + * Asynchronously upload a survey response to the pavlovia server. + * + * @returns {Promise} a promise resolved when the survey response has been uploaded + */ + async uploadSurveyResponse(surveyId, surveyResponse, isComplete) + { + const response = { + origin: "ServerManager.uploadSurveyResponse", + context: `when uploading the survey response for experiment: ${this._psychoJS.config.experiment.fullpath} and survey: ${surveyId}` + }; + + if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || + this._psychoJS.config.experiment.status !== "RUNNING" || + this._psychoJS._serverMsg.has("__pilotToken")) + { + throw "survey responses can only be uploaded to the server for experiments running on the server"; + } + + this._psychoJS.logger.debug(`uploading a survey response for experiment: ${this._psychoJS.config.experiment.fullpath} and survey: ${surveyId}`); + this.setStatus(ServerManager.Status.BUSY); + + const self = this; + return new Promise(async (resolve, reject) => + { + try + { + const info = this._psychoJS.experiment.extraInfo; + const participant = (typeof info.participant === "string" && info.participant.length > 0) ? + info.participant : + "PARTICIPANT"; + + const postResponse = await this._queryServerAPI( + "POST", + `surveys/${surveyId}`, + { + experimentId: this._psychoJS.config.gitlab.projectId, + sessionToken: this._psychoJS.config.session.token, + participant: participant, + surveyResponse, + isComplete + }, + "JSON" + ); + const uploadDataResponse = await postResponse.json(); + + if (postResponse.status !== 200) + { + throw ('error' in uploadDataResponse) ? uploadDataResponse.error : uploadDataResponse; + } + + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, ...uploadDataResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } + }); + } + + /** + * Asynchronously get a survey's experiment parameters from the pavlovia server, and update experimentInfo + * + * @note only those fields not previously defined in experimentInfo are updated + * + * @param surveyId + * @param experimentInfo + * @returns {Promise} a promise resolved when the survey experiment parameters have been downloaded + */ + async getSurveyExperimentParameters(surveyId, experimentInfo) + { + const response = { + origin: "ServerManager.getSurveyExperimentParameters", + context: `when downloading the experiment parameters for survey: ${surveyId}` + }; + + if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER) + { + throw "survey experiment parameters cannot be downloaded when the experiment is running locally"; + } + + this._psychoJS.logger.debug(`downloading the experiment parameters of survey: ${surveyId}`); + this.setStatus(ServerManager.Status.BUSY); + + const self = this; + return new Promise(async (resolve, reject) => + { + try + { + const getResponse = await this._queryServerAPI( + "GET", + `surveys/${surveyId}/experiment` + ); + const getExperimentParametersResponse = await getResponse.json(); + + if (getResponse.status !== 200) + { + throw ('error' in getExperimentParametersResponse) ? getExperimentParametersResponse.error : getExperimentParametersResponse; + } + + if (getExperimentParametersResponse["experimentParameters"] === null) + { + throw "either there is no survey with the given id, or it is not currently active"; + } + + // update the info with the survey experiment parameters: + const experimentParameters = getExperimentParametersResponse['experimentParameters']; + for (const parameter in experimentParameters) + { + if (typeof experimentInfo[parameter] === "undefined") + { + experimentInfo[parameter] = experimentParameters[parameter]; + } + } + + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, ...getExperimentParametersResponse }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } + }); + } + + /** * List the resources available to the experiment. * - * @name module:core.ServerManager#_listResources - * @function - * @private + * @protected */ _listResources() { @@ -983,69 +1136,57 @@ export class ServerManager extends PsychObject origin: "ServerManager._listResourcesSession", context: "when listing the resources for experiment: " + this._psychoJS.config.experiment.fullpath, }; - - this._psychoJS.logger.debug( - "listing the resources for experiment: " - + this._psychoJS.config.experiment.fullpath, - ); + this._psychoJS.logger.debug(`listing the resources for experiment: ${this._psychoJS.config.experiment.fullpath}`); this.setStatus(ServerManager.Status.BUSY); - // prepare GET data: + // prepare a GET query: const data = { "token": this._psychoJS.config.session.token, }; - // query pavlovia server: + // query the server: const self = this; - return new Promise((resolve, reject) => + return new Promise(async (resolve, reject) => { - const url = this._psychoJS.config.pavlovia.URL - + "/api/v2/experiments/" + encodeURIComponent(this._psychoJS.config.experiment.fullpath) - + "/resources"; + try + { + const getResponse = await this._queryServerAPI( + "GET", + `experiments/${this._psychoJS.config.gitlab.projectId}/resources`, + data + ); - jQuery.get(url, data, null, "json") - .done((data, textStatus) => - { - if (!("resources" in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({ ...response, error: 'unexpected answer from server: no resources' }); - reject(Object.assign(response, { error: "unexpected answer from server: no resources" })); - } - if (!("resourceDirectory" in data)) - { - self.setStatus(ServerManager.Status.ERROR); - // reject({ ...response, error: 'unexpected answer from server: no resourceDirectory' }); - reject(Object.assign(response, { error: "unexpected answer from server: no resourceDirectory" })); - } + const getResourcesResponse = await getResponse.json(); - self.setStatus(ServerManager.Status.READY); - // resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory }); - resolve(Object.assign(response, { - resources: data.resources, - resourceDirectory: data.resourceDirectory, - })); - }) - .fail((jqXHR, textStatus, errorThrown) => + if (!("resources" in getResourcesResponse)) { self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no resources"; + } + if (!("resourceDirectory" in getResourcesResponse)) + { + self.setStatus(ServerManager.Status.ERROR); + throw "unexpected answer from server: no resourceDirectory"; + } - const errorMsg = util.getRequestError(jqXHR, textStatus, errorThrown); - console.error("error:", errorMsg); - - reject(Object.assign(response, { error: errorMsg })); - }); + self.setStatus(ServerManager.Status.READY); + resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory }); + } + catch (error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + reject({...response, error}); + } }); } - /**************************************************************************** + /** * Download the specified resources. * *

    Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.

    * - * @name module:core.ServerManager#_downloadResources - * @function * @protected * @param {Set} resources - a set of names of previously registered resources */ @@ -1065,10 +1206,12 @@ export class ServerManager extends PsychObject }); // based on the resource extension either (a) add it to the preload manifest, (b) mark it for - // download by howler, or (c) add it to the document fonts + // download by howler, (c) add it to the document fonts, or (d) download the associated survey model + // from the server const preloadManifest = []; const soundResources = new Set(); const fontResources = []; + const surveyModelResources = []; for (const name of resources) { const nameParts = name.toLowerCase().split("."); @@ -1121,12 +1264,18 @@ export class ServerManager extends PsychObject } } - // font files + // font files: else if (["ttf", "otf", "woff", "woff2"].indexOf(pathExtension) > -1) { fontResources.push(name); } + // survey models: + else if (["sid"].indexOf(extension) > -1) + { + surveyModelResources.push(name); + } + // all other extensions handled by preload.js (download type decided by preload.js): else { @@ -1197,8 +1346,64 @@ export class ServerManager extends PsychObject } } - // start loading resources marked for howler.js: + // start loading the survey models: const self = this; + for (const name of surveyModelResources) + { + const pathStatusData = this._resources.get(name); + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADING; + this.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOADING_RESOURCE, + resource: name, + }); + + try + { + const getResponse = await this._queryServerAPI("GET", `surveys/${pathStatusData.path}/model`); + + const getModelResponse = await getResponse.json(); + + if (getResponse.status !== 200) + { + const error = ("error" in getModelResponse) ? getModelResponse.error : getModelResponse; + throw util.toString(error); + } + + if (getModelResponse["model"] === null) + { + throw "either there is no survey with the given id, or it is not currently active"; + } + + ++self._nbLoadedResources; + + // note: we encode the json model as a string since it will be decoded in Survey.setModel, + // just like the model loaded directly from a resource by preloadJS + pathStatusData.data = new TextEncoder().encode(JSON.stringify(getModelResponse['model'])); + + pathStatusData.status = ServerManager.ResourceStatus.DOWNLOADED; + self.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.RESOURCE_DOWNLOADED, + resource: name, + }); + + if (self._nbLoadedResources === resources.size) + { + self.setStatus(ServerManager.Status.READY); + self.emit(ServerManager.Event.RESOURCE, { + message: ServerManager.Event.DOWNLOAD_COMPLETED, + }); + } + } + catch(error) + { + console.error(error); + self.setStatus(ServerManager.Status.ERROR); + throw { ...response, error: `unable to download resource: ${name}: ${util.toString(error)}` }; + } + } + + // start loading resources marked for howler.js: + // TODO load them sequentially, not all at once! for (const name of soundResources) { const pathStatusData = this._resources.get(name); @@ -1243,11 +1448,9 @@ export class ServerManager extends PsychObject } } - /**************************************************************************** + /** * Setup the preload.js queue, and the associated callbacks. * - * @name module:core.ServerManager#_setupPreloadQueue - * @function * @protected */ _setupPreloadQueue() @@ -1335,18 +1538,88 @@ export class ServerManager extends PsychObject }); } + /** + * Query the pavlovia server API. + * + * @protected + * @param method the HTTP method, i.e. GET, PUT, POST, or DELETE + * @param path the resource path, without the server address + * @param data the data to be sent + * @param {string} [contentType="JSON"] the content type, either JSON or FORM + */ + _queryServerAPI(method, path, data, contentType = "JSON") + { + const fullPath = `${this._psychoJS.config.pavlovia.URL}/api/v2/${path}`; + + if (method === "PUT" || method === "POST" || method === "DELETE") + { + if (contentType === "JSON") + { + return fetch(fullPath, { + method, + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + } + else + { + const formData = new FormData(); + for (const attribute in data) + { + formData.append(attribute, data[attribute]); + } + + return fetch(fullPath, { + method, + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + body: formData + }); + } + } + + if (method === "GET") + { + let url = new URL(fullPath); + url.search = new URLSearchParams(data).toString(); + + return fetch(url, { + method: "GET", + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + redirect: "follow", + referrerPolicy: "no-referrer" + }); + } + + throw { + origin: "ServerManager._queryServer", + context: "when querying the server", + error: "the method should be GET, PUT, POST, or DELETE" + }; + } } -/**************************************************************************** +/** * Server event * - *

    A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).

    + *

    A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource + * related event (e.g. download started, download is completed).

    * - * @name module:core.ServerManager#Event * @enum {Symbol} * @readonly - * @public */ ServerManager.Event = { /** @@ -1372,7 +1645,7 @@ ServerManager.Event = { /** * Event: resources have all downloaded */ - DOWNLOADS_COMPLETED: Symbol.for("DOWNLOAD_COMPLETED"), + DOWNLOAD_COMPLETED: Symbol.for("DOWNLOAD_COMPLETED"), /** * Event type: status event @@ -1380,13 +1653,11 @@ ServerManager.Event = { STATUS: Symbol.for("STATUS"), }; -/**************************************************************************** +/** * Server status * - * @name module:core.ServerManager#Status * @enum {Symbol} * @readonly - * @public */ ServerManager.Status = { /** @@ -1405,13 +1676,11 @@ ServerManager.Status = { ERROR: Symbol.for("ERROR"), }; -/**************************************************************************** +/** * Resource status * - * @name module:core.ServerManager#ResourceStatus * @enum {Symbol} * @readonly - * @public */ ServerManager.ResourceStatus = { /** diff --git a/src/core/Window.js b/src/core/Window.js index 7b11cc6d..cb6acbec 100644 --- a/src/core/Window.js +++ b/src/core/Window.js @@ -1,13 +1,14 @@ /** * Window responsible for displaying the experiment stimuli * - * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @author Alain Pitiot & Nikita Agafonov + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ import * as PIXI from "pixi.js-legacy"; +import {AdjustmentFilter} from "@pixi/filter-adjustment"; import { MonotonicClock } from "../util/Clock.js"; import { Color } from "../util/Color.js"; import { PsychObject } from "../util/PsychObject.js"; @@ -17,38 +18,60 @@ import { Logger } from "./Logger.js"; *

    Window displays the various stimuli of the experiment.

    *

    It sets up a [PIXI]{@link http://www.pixijs.com/} renderer, which we use to render the experiment stimuli.

    * - * @name module:core.Window - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} [options.name] the name of the window - * @param {boolean} [options.fullscr= false] whether or not to go fullscreen - * @param {Color} [options.color= Color('black')] the background color of the window - * @param {string} [options.units= 'pix'] the units of the window - * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done - * before flipping - * @param {boolean} [options.autoLog= true] whether or not to log */ export class Window extends PsychObject { + /** + * Check whether PsychoJS/Pixi.js is actually using WebGL in the participant's browser, i.e. + * hardware acceleration, rather than software emulation or Pixi.js' canvas fallback. + * + * @return true if WebGL is supported and false if it is not or if it is supported + * only through software emulation + */ + static checkWebGLSupport() + { + // Note: in order to detect whether the participant's browser has hardware acceleration turned off + // we set FAIL_IF_MAJOR_PERFORMANCE_CAVEAT to true. This ensures that the WebGL context creation that + // takes place in PIXI.utils.isWebGLSupported fails if the performance is low, which is typically the case + // with software emulation. + // See details here: https://registry.khronos.org/webgl/specs/latest/1.0/#5.2 + PIXI.settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = true; + return PIXI.utils.isWebGLSupported(); + } + /** * Getter for monitorFramePeriod. * * @name module:core.Window#monitorFramePeriod - * @function - * @public + * @return the estimated monitor frame period */ get monitorFramePeriod() { return 1.0 / this.getActualFrameRate(); } + /** + * @memberof module:core + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} [options.name] the name of the window + * @param {boolean} [options.fullscr= false] whether or not to go fullscreen + * @param {Color} [options.color= Color('black')] the background color of the window + * @param {number} [options.gamma= 1] sets the divisor for gamma correction. In other words gamma correction is calculated as pow(rgb, 1/gamma) + * @param {number} [options.contrast= 1] sets the contrast value + * @param {string} [options.units= 'pix'] the units of the window + * @param {boolean} [options.waitBlanking= false] whether or not to wait for all rendering operations to be done + * before flipping + * @param {boolean} [options.autoLog= true] whether or not to log + */ constructor({ psychoJS, name, fullscr = false, color = new Color("black"), + gamma = 1, + contrast = 1, units = "pix", waitBlanking = false, autoLog = true, @@ -59,11 +82,27 @@ export class Window extends PsychObject // messages to be logged at the next "flip": this._msgToBeLogged = []; + // storing AdjustmentFilter instance to access later; + this._adjustmentFilter = new AdjustmentFilter({ + gamma, + contrast + }); + // list of all elements, in the order they are currently drawn: this._drawList = []; this._addAttribute("fullscr", fullscr); - this._addAttribute("color", color); + this._addAttribute("color", color, new Color("black"), () => { + if (this._backgroundSprite) { + this._backgroundSprite.tint = this._color.int; + } + }); + this._addAttribute("gamma", gamma, 1, () => { + this._adjustmentFilter.gamma = this._gamma; + }); + this._addAttribute("contrast", contrast, 1, () => { + this._adjustmentFilter.contrast = this._contrast; + }); this._addAttribute("units", units); this._addAttribute("waitBlanking", waitBlanking); this._addAttribute("autoLog", autoLog); @@ -103,10 +142,6 @@ export class Window extends PsychObject * Close the window. * *

    Note: this actually only removes the canvas used to render the experiment stimuli.

    - * - * @name module:core.Window#close - * @function - * @public */ close() { @@ -115,6 +150,8 @@ export class Window extends PsychObject return; } + this._rootContainer.destroy(); + if (document.body.contains(this._renderer.view)) { document.body.removeChild(this._renderer.view); @@ -138,9 +175,6 @@ export class Window extends PsychObject /** * Estimate the frame rate. * - * @name module:core.Window#getActualFrameRate - * @function - * @public * @return {number} rAF based delta time based approximation, 60.0 by default */ getActualFrameRate() @@ -154,10 +188,6 @@ export class Window extends PsychObject /** * Take the browser full screen if possible. - * - * @name module:core.Window#adjustScreenSize - * @function - * @public */ adjustScreenSize() { @@ -199,10 +229,6 @@ export class Window extends PsychObject /** * Take the browser back from full screen if needed. - * - * @name module:core.Window#closeFullScreen - * @function - * @public */ closeFullScreen() { @@ -242,9 +268,6 @@ export class Window extends PsychObject * *

    Note: the message will be time-stamped at the next call to requestAnimationFrame.

    * - * @name module:core.Window#logOnFlip - * @function - * @public * @param {Object} options * @param {String} options.msg the message to be logged * @param {module:util.Logger.ServerLevel} [level = module:util.Logger.ServerLevel.EXP] the log level @@ -271,9 +294,6 @@ export class Window extends PsychObject * *

    This is typically used to reset a timer or clock.

    * - * @name module:core.Window#callOnFlip - * @function - * @public * @param {module:core.Window~OnFlipCallback} flipCallback - callback function. * @param {...*} flipCallbackArgs - arguments for the callback function. */ @@ -282,12 +302,24 @@ export class Window extends PsychObject this._flipCallbacks.push({ function: flipCallback, arguments: flipCallbackArgs }); } + /** + * Add PIXI.DisplayObject to the container displayed on the scene (window) + */ + addPixiObject(pixiObject) + { + this._stimsContainer.addChild(pixiObject); + } + + /** + * Remove PIXI.DisplayObject from the container displayed on the scene (window) + */ + removePixiObject(pixiObject) + { + this._stimsContainer.removeChild(pixiObject); + } + /** * Render the stimuli onto the canvas. - * - * @name module:core.Window#render - * @function - * @public */ render() { @@ -331,9 +363,7 @@ export class Window extends PsychObject /** * Update this window, if need be. * - * @name module:core.Window#_updateIfNeeded - * @function - * @private + * @protected */ _updateIfNeeded() { @@ -342,6 +372,7 @@ export class Window extends PsychObject if (this._renderer) { this._renderer.backgroundColor = this._color.int; + this._backgroundSprite.tint = this._color.int; } // we also change the background color of the body since @@ -355,9 +386,7 @@ export class Window extends PsychObject /** * Recompute this window's draw list and _container children for the next animation frame. * - * @name module:core.Window#_refresh - * @function - * @private + * @protected */ _refresh() { @@ -369,9 +398,9 @@ export class Window extends PsychObject { if (stimulus._needUpdate && typeof stimulus._pixi !== "undefined") { - this._rootContainer.removeChild(stimulus._pixi); + this._stimsContainer.removeChild(stimulus._pixi); stimulus._updateIfNeeded(); - this._rootContainer.addChild(stimulus._pixi); + this._stimsContainer.addChild(stimulus._pixi); } } } @@ -379,9 +408,7 @@ export class Window extends PsychObject /** * Force an update of all stimuli in this window's drawlist. * - * @name module:core.Window#_fullRefresh - * @function - * @private + * @protected */ _fullRefresh() { @@ -401,9 +428,7 @@ export class Window extends PsychObject *

    A new renderer is created and a container is added to it. The renderer's touch and mouse events * are handled by the {@link EventManager}.

    * - * @name module:core.Window#_setupPixi - * @function - * @private + * @protected */ _setupPixi() { @@ -411,11 +436,18 @@ export class Window extends PsychObject this._size[0] = window.innerWidth; this._size[1] = window.innerHeight; + if (this._psychoJS._checkWebGLSupport) + { + // see checkWebGLSupport() method for details. + PIXI.settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = true; + } + // create a PIXI renderer and add it to the document: this._renderer = PIXI.autoDetectRenderer({ width: this._size[0], height: this._size[1], backgroundColor: this.color.int, + powerPreference: "high-performance", resolution: window.devicePixelRatio, }); this._renderer.view.style.transform = "translatez(0)"; @@ -425,9 +457,32 @@ export class Window extends PsychObject // we also change the background color of the body since the dialog popup may be longer than the window's height: document.body.style.backgroundColor = this._color.hex; + // filters in PIXI work in a slightly unexpected fashion: + // when setting this._rootContainer.filters, filtering itself + // ignores backgroundColor of this._renderer and in addition to that + // all child elements of this._rootContainer ignore backgroundColor when blending. + // To circumvent that creating a separate PIXI.Sprite that serves as background color. + // Then placing all Stims to a separate this._stimsContainer which hovers on top of + // background sprite so that if we need to move all stims at once, the background sprite + // won't get affected. + this._backgroundSprite = new PIXI.Sprite(PIXI.Texture.WHITE); + this._backgroundSprite.tint = this.color.int; + this._backgroundSprite.width = this._size[0]; + this._backgroundSprite.height = this._size[1]; + this._backgroundSprite.anchor.set(.5); + this._stimsContainer = new PIXI.Container(); + this._stimsContainer.sortableChildren = true; + // create a top-level PIXI container: this._rootContainer = new PIXI.Container(); + this._rootContainer.addChild(this._backgroundSprite, this._stimsContainer); + + // sorts children according to their zIndex value. Higher zIndex means it will be moved towards the end of the array, + // and thus rendered on top of previous one. + this._rootContainer.sortableChildren = true; + this._rootContainer.interactive = true; + this._rootContainer.filters = [this._adjustmentFilter]; // set the initial size of the PIXI renderer and the position of the root container: Window._resizePixiRenderer(this); @@ -439,6 +494,8 @@ export class Window extends PsychObject this._resizeCallback = (e) => { Window._resizePixiRenderer(this, e); + this._backgroundSprite.width = this._size[0]; + this._backgroundSprite.height = this._size[1]; this._fullRefresh(); }; window.addEventListener("resize", this._resizeCallback); @@ -449,9 +506,7 @@ export class Window extends PsychObject * Adjust the size of the renderer and the position of the root container * in response to a change in the browser's size. * - * @name module:core.Window#_resizePixiRenderer - * @function - * @private + * @protected * @param {module:core.Window} pjsWindow - the PsychoJS Window * @param event */ @@ -480,9 +535,7 @@ export class Window extends PsychObject /** * Send all logged messages to the {@link Logger}. * - * @name module:core.Window#_writeLogOnFlip - * @function - * @private + * @protected */ _writeLogOnFlip() { diff --git a/src/core/WindowMixin.js b/src/core/WindowMixin.js index e03be111..41100dfc 100644 --- a/src/core/WindowMixin.js +++ b/src/core/WindowMixin.js @@ -2,8 +2,8 @@ * Mixin implementing various unit-handling measurement methods. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ diff --git a/src/data/ExperimentHandler.js b/src/data/ExperimentHandler.js index 10ecf91b..7a305789 100644 --- a/src/data/ExperimentHandler.js +++ b/src/data/ExperimentHandler.js @@ -2,8 +2,8 @@ * Experiment Handler * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -17,22 +17,12 @@ import * as util from "../util/Util.js"; * for generating a single data file from an experiment with many different loops (e.g. interleaved * staircases or loops within loops.

    * - * @name module:data.ExperimentHandler - * @class * @extends PsychObject - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} options.name - name of the experiment - * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc. */ export class ExperimentHandler extends PsychObject { /** * Getter for experimentEnded. - * - * @name module:data.ExperimentHandler#experimentEnded - * @function - * @public */ get experimentEnded() { @@ -41,10 +31,6 @@ export class ExperimentHandler extends PsychObject /** * Setter for experimentEnded. - * - * @name module:data.ExperimentHandler#experimentEnded - * @function - * @public */ set experimentEnded(ended) { @@ -64,6 +50,13 @@ export class ExperimentHandler extends PsychObject return this._trialsData; } + /** + * @memberof module:data + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} options.name - name of the experiment + * @param {Object} options.extraInfo - additional information, such as session name, participant name, etc. + */ constructor({ psychoJS, name, @@ -111,9 +104,6 @@ export class ExperimentHandler extends PsychObject * Whether or not the current entry (i.e. trial data) is empty. *

    Note: this is mostly useful at the end of an experiment, in order to ensure that the last entry is saved.

    * - * @name module:data.ExperimentHandler#isEntryEmpty - * @function - * @public * @returns {boolean} whether or not the current entry is empty * @todo This really should be renamed: IsCurrentEntryNotEmpty */ @@ -128,9 +118,6 @@ export class ExperimentHandler extends PsychObject *

    The loop might be a {@link TrialHandler}, for instance.

    *

    Data from this loop will be included in the resulting data files.

    * - * @name module:data.ExperimentHandler#addLoop - * @function - * @public * @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler */ addLoop(loop) @@ -143,9 +130,6 @@ export class ExperimentHandler extends PsychObject /** * Remove the given loop from the list of unfinished loops, e.g. when it has completed. * - * @name module:data.ExperimentHandler#removeLoop - * @function - * @public * @param {Object} loop - the loop, e.g. an instance of TrialHandler or StairHandler */ removeLoop(loop) @@ -163,9 +147,6 @@ export class ExperimentHandler extends PsychObject *

    Multiple key/value pairs can be added to any given entry of the data file. There are * considered part of the same entry until a call to {@link nextEntry} is made.

    * - * @name module:data.ExperimentHandler#addData - * @function - * @public * @param {Object} key - the key * @param {Object} value - the value */ @@ -189,9 +170,6 @@ export class ExperimentHandler extends PsychObject * Inform this ExperimentHandler that the current trial has ended. Further calls to {@link addData} * will be associated with the next trial. * - * @name module:data.ExperimentHandler#nextEntry - * @function - * @public * @param {Object | Object[] | undefined} snapshots - array of loop snapshots */ nextEntry(snapshots) @@ -256,9 +234,6 @@ export class ExperimentHandler extends PsychObject * *

    * - * @name module:data.ExperimentHandler#save - * @function - * @public * @param {Object} options * @param {Array.} [options.attributes] - the attributes to be saved * @param {boolean} [options.sync=false] - whether or not to communicate with the server in a synchronous manner @@ -283,7 +258,7 @@ export class ExperimentHandler extends PsychObject const loop = this._loops[l]; const loopAttributes = ExperimentHandler._getLoopAttributes(loop); - for (let a in loopAttributes) + for (const a in loopAttributes) { if (loopAttributes.hasOwnProperty(a)) { @@ -338,14 +313,14 @@ export class ExperimentHandler extends PsychObject else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE) { const gitlabConfig = this._psychoJS.config.gitlab; - const projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined; + const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined; let documents = []; for (let r = 0; r < data.length; r++) { let doc = { - projectId, + __projectId, __experimentName: this._experimentName, __participant: this._participant, __session: this._session, @@ -380,9 +355,6 @@ export class ExperimentHandler extends PsychObject * Get the attribute names and values for the current trial of a given loop. *

    Only info relating to the trial execution are returned.

    * - * @name module:data.ExperimentHandler#_getLoopAttributes - * @function - * @static * @protected * @param {Object} loop - the loop */ @@ -442,10 +414,8 @@ export class ExperimentHandler extends PsychObject /** * Experiment result format * - * @name module:core.ServerManager#SaveFormat * @enum {Symbol} * @readonly - * @public */ ExperimentHandler.SaveFormat = { /** @@ -464,7 +434,6 @@ ExperimentHandler.SaveFormat = { * * @enum {Symbol} * @readonly - * @public */ ExperimentHandler.Environment = { SERVER: Symbol.for("SERVER"), diff --git a/src/data/MultiStairHandler.js b/src/data/MultiStairHandler.js index a53cc4a0..a2b9b51c 100644 --- a/src/data/MultiStairHandler.js +++ b/src/data/MultiStairHandler.js @@ -1,10 +1,9 @@ -/** @module data */ /** * Multiple Staircase Trial Handler * * @author Alain Pitiot - * @version 2021.2.1 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. + * @version 2021.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. * (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -22,27 +21,25 @@ import seedrandom from "seedrandom"; *

    Note that, at the moment, using the MultiStairHandler requires the jsQuest.js * library to be loaded as a resource, at the start of the experiment.

    * - * @class module.data.MultiStairHandler * @extends TrialHandler - * @param {Object} options - the handler options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} options.varName - the name of the variable / intensity / contrast - * / threshold manipulated by the staircases - * @param {module:data.MultiStairHandler.StaircaseType} [options.stairType="simple"] - the - * handler type - * @param {Array. | String} [options.conditions= [undefined] ] - if it is a string, - * we treat it as the name of a conditions resource - * @param {module:data.TrialHandler.Method} options.method - the trial method - * @param {number} [options.nTrials=50] - maximum number of trials - * @param {number} options.randomSeed - seed for the random number generator - * @param {string} options.name - name of the handler - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class MultiStairHandler extends TrialHandler { /** - * @constructor - * @public + * @memberof module:data + * @param {Object} options - the handler options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} options.varName - the name of the variable / intensity / contrast + * / threshold manipulated by the staircases + * @param {MultiStairHandler.StaircaseType} [options.stairType="simple"] - the + * handler type + * @param {Array. | String} [options.conditions= [undefined] ] - if it is a string, + * we treat it as the name of a conditions resource + * @param {module:data.TrialHandler.Method} options.method - the trial method + * @param {number} [options.nTrials=50] - maximum number of trials + * @param {number} options.randomSeed - seed for the random number generator + * @param {string} options.name - name of the handler + * @param {boolean} [options.autoLog= false] - whether or not to log */ constructor({ psychoJS, @@ -88,16 +85,43 @@ export class MultiStairHandler extends TrialHandler this._nextTrial(); } + /** + * Get the current staircase. + * + * @returns {TrialHandler} the current staircase, or undefined if the trial has ended + */ + get currentStaircase() + { + return this._currentStaircase; + } + + /** + * Get the current intensity. + * + * @returns {number} the intensity of the current staircase, or undefined if the trial has ended + */ + get intensity() + { + if (this._currentStaircase instanceof QuestHandler) + { + return this._currentStaircase.getQuestValue(); + } + + // TODO similar for simple staircase: + // if (this._currentStaircase instanceof StaircaseHandler) + // { + // return this._currentStaircase.getStairValue(); + // } + + return undefined; + } + /** * Add a response to the current staircase. * - * @name module:data.MultiStairHandler#addResponse - * @function - * @public * @param{number} response - the response to the trial, must be either 0 (incorrect or * non-detected) or 1 (correct or detected) * @param{number | undefined} [value] - optional intensity / contrast / threshold - * @returns {void} */ addResponse(response, value) { @@ -126,10 +150,7 @@ export class MultiStairHandler extends TrialHandler /** * Validate the conditions. * - * @name module:data.MultiStairHandler#_validateConditions - * @function * @protected - * @returns {void} */ _validateConditions() { @@ -185,10 +206,7 @@ export class MultiStairHandler extends TrialHandler /** * Setup the staircases, according to the conditions. * - * @name module:data.MultiStairHandler#_prepareStaircases - * @function * @protected - * @returns {void} */ _prepareStaircases() { @@ -245,10 +263,7 @@ export class MultiStairHandler extends TrialHandler /** * Move onto the next trial. * - * @name module:data.MultiStairHandler#_nextTrial - * @function * @protected - * @returns {void} */ _nextTrial() { @@ -344,10 +359,10 @@ export class MultiStairHandler extends TrialHandler if (typeof this._snapshots[t] !== "undefined") { - let fieldName = this._name + "." + this._varName; + let fieldName = /*this._name + "." +*/ this._varName; this._snapshots[t][fieldName] = value; this._snapshots[t].trialAttributes.push(fieldName); - fieldName = this._name + ".intensity"; + fieldName = /*this._name + ".*/ "intensity"; this._snapshots[t][fieldName] = value; this._snapshots[t].trialAttributes.push(fieldName); @@ -356,13 +371,13 @@ export class MultiStairHandler extends TrialHandler // "name" becomes "label" again: if (attribute === 'name') { - fieldName = this._name + ".label"; + fieldName = /*this._name + ".*/ "label"; this._snapshots[t][fieldName] = this._currentStaircase["_name"]; this._snapshots[t].trialAttributes.push(fieldName); } else if (attribute !== 'trialList' && attribute !== 'extraInfo') { - fieldName = this._name+"."+attribute; + fieldName = /*this._name+"."+*/ attribute; this._snapshots[t][fieldName] = this._currentStaircase["_" + attribute]; this._snapshots[t].trialAttributes.push(fieldName); } @@ -388,7 +403,6 @@ export class MultiStairHandler extends TrialHandler * * @enum {Symbol} * @readonly - * @public */ MultiStairHandler.StaircaseType = { /** @@ -407,7 +421,6 @@ MultiStairHandler.StaircaseType = { * * @enum {Symbol} * @readonly - * @public */ MultiStairHandler.StaircaseStatus = { /** diff --git a/src/data/QuestHandler.js b/src/data/QuestHandler.js index 77949345..067e5263 100644 --- a/src/data/QuestHandler.js +++ b/src/data/QuestHandler.js @@ -1,10 +1,9 @@ -/** @module data */ /** * Quest Trial Handler * * @author Alain Pitiot & Thomas Pronk - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -15,31 +14,29 @@ import {TrialHandler} from "./TrialHandler.js"; *

    A Trial Handler that implements the Quest algorithm for quick measurement of psychophysical thresholds. QuestHandler relies on the [jsQuest]{@link https://github.com/kurokida/jsQUEST} library, a port of Prof Dennis Pelli's QUEST algorithm by [Daiichiro Kuroki]{@link https://github.com/kurokida}.

    * - * @class module.data.QuestHandler * @extends TrialHandler - * @param {Object} options - the handler options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST - * @param {number} options.startVal - initial guess for the threshold - * @param {number} options.startValSd - standard deviation of the initial guess - * @param {number} options.minVal - minimum value for the threshold - * @param {number} options.maxVal - maximum value for the threshold - * @param {number} [options.pThreshold=0.82] - threshold criterion expressed as probability of getting a correct response - * @param {number} options.nTrials - maximum number of trials - * @param {number} options.stopInterval - minimum [5%, 95%] confidence interval required for the loop to stop - * @param {module:data.QuestHandler.Method} options.method - the QUEST method - * @param {number} [options.beta=3.5] - steepness of the QUEST psychometric function - * @param {number} [options.delta=0.01] - fraction of trials with blind responses - * @param {number} [options.gamma=0.5] - fraction of trails that would generate a correct response when the threshold is infinitely small - * @param {number} [options.grain=0.01] - quantization of the internal table - * @param {string} options.name - name of the handler - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class QuestHandler extends TrialHandler { /** - * @constructor - * @public + * @memberof module:data + * @param {Object} options - the handler options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST + * @param {number} options.startVal - initial guess for the threshold + * @param {number} options.startValSd - standard deviation of the initial guess + * @param {number} options.minVal - minimum value for the threshold + * @param {number} options.maxVal - maximum value for the threshold + * @param {number} [options.pThreshold=0.82] - threshold criterion expressed as probability of getting a correct response + * @param {number} options.nTrials - maximum number of trials + * @param {number} options.stopInterval - minimum [5%, 95%] confidence interval required for the loop to stop + * @param {QuestHandler.Method} options.method - the QUEST method + * @param {number} [options.beta=3.5] - steepness of the QUEST psychometric function + * @param {number} [options.delta=0.01] - fraction of trials with blind responses + * @param {number} [options.gamma=0.5] - fraction of trails that would generate a correct response when the threshold is infinitely small + * @param {number} [options.grain=0.01] - quantization of the internal table + * @param {string} options.name - name of the handler + * @param {boolean} [options.autoLog= false] - whether or not to log */ constructor({ psychoJS, @@ -113,15 +110,11 @@ export class QuestHandler extends TrialHandler /** * Add a response and update the PDF. * - * @name module:data.QuestHandler#addResponse - * @function - * @public * @param{number} response - the response to the trial, must be either 0 (incorrect or * non-detected) or 1 (correct or detected) * @param{number | undefined} value - optional intensity / contrast / threshold * @param{boolean} [doAddData = true] - whether or not to add the response as data to the * experiment - * @returns {void} */ addResponse(response, value, doAddData = true) { @@ -163,9 +156,6 @@ export class QuestHandler extends TrialHandler /** * Simulate a response. * - * @name module:data.QuestHandler#simulate - * @function - * @public * @param{number} trueValue - the true, known value of the threshold / contrast / intensity * @returns{number} the simulated response, 0 or 1 */ @@ -184,9 +174,6 @@ export class QuestHandler extends TrialHandler /** * Get the mean of the Quest posterior PDF. * - * @name module:data.QuestHandler#mean - * @function - * @public * @returns {number} the mean */ mean() @@ -197,9 +184,6 @@ export class QuestHandler extends TrialHandler /** * Get the standard deviation of the Quest posterior PDF. * - * @name module:data.QuestHandler#sd - * @function - * @public * @returns {number} the standard deviation */ sd() @@ -210,9 +194,6 @@ export class QuestHandler extends TrialHandler /** * Get the mode of the Quest posterior PDF. * - * @name module:data.QuestHandler#mode - * @function - * @public * @returns {number} the mode */ mode() @@ -224,9 +205,6 @@ export class QuestHandler extends TrialHandler /** * Get the standard deviation of the Quest posterior PDF. * - * @name module:data.QuestHandler#quantile - * @function - * @public * @param{number} quantileOrder the quantile order * @returns {number} the quantile */ @@ -238,9 +216,6 @@ export class QuestHandler extends TrialHandler /** * Get the current value of the variable / contrast / threshold. * - * @name module:data.QuestHandler#getQuestValue - * @function - * @public * @returns {number} the current QUEST value for the variable / contrast / threshold */ getQuestValue() @@ -248,12 +223,21 @@ export class QuestHandler extends TrialHandler return this._questValue; } + /** + * Get the current value of the variable / contrast / threshold. + * + *

    This is the getter associated to getQuestValue.

    + * + * @returns {number} the intensity of the current staircase, or undefined if the trial has ended + */ + get intensity() + { + return this.getQuestValue(); + } + /** * Get an estimate of the 5%-95% confidence interval (CI). * - * @name module:data.QuestHandler#confInterval - * @function - * @public * @param{boolean} [getDifference=false] - if true, return the width of the CI instead of the CI * @returns{number[] | number} the 5%-95% CI or the width of the CI */ @@ -277,10 +261,7 @@ export class QuestHandler extends TrialHandler /** * Setup the JS Quest object. * - * @name module:data.QuestHandler#_setupJsQuest - * @function * @protected - * @returns {void} */ _setupJsQuest() { @@ -298,10 +279,7 @@ export class QuestHandler extends TrialHandler * Estimate the next value of the QUEST variable, based on the current value * and on the selected QUEST method. * - * @name module:data.QuestHandler#_estimateQuestValue - * @function * @protected - * @returns {void} */ _estimateQuestValue() { @@ -372,7 +350,6 @@ export class QuestHandler extends TrialHandler * * @enum {Symbol} * @readonly - * @public */ QuestHandler.Method = { /** diff --git a/src/data/Shelf.js b/src/data/Shelf.js new file mode 100644 index 00000000..4635a8dd --- /dev/null +++ b/src/data/Shelf.js @@ -0,0 +1,832 @@ +/** + * Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the + * server, and can be accessed and manipulated in a concurrent fashion. + * + * @author Alain Pitiot + * @version 2021.2.3 + * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + */ + +import {PsychObject} from "../util/PsychObject.js"; +import {PsychoJS} from "../core/PsychoJS.js"; +import {ExperimentHandler} from "./ExperimentHandler"; +import {Scheduler} from "../util/Scheduler.js"; + + +/** + *

    Shelf handles persistent key/value pairs, or records, which are stored in the shelf collection on the + * server, and can be accessed and manipulated in a concurrent fashion.

    + * + * @extends PsychObject + */ +export class Shelf extends PsychObject +{ + /** + * Maximum number of components in a key + * @type {number} + * @note this value should mirror that on the server, i.e. the server also checks that the key is valid + */ + static #MAX_KEY_LENGTH = 10; + + /** + * @memberOf module:data + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS the PsychoJS instance + * @param {boolean} [options.autoLog= false] whether to log + */ + constructor({psychoJS, autoLog = false } = {}) + { + super(psychoJS); + + this._addAttribute('autoLog', autoLog); + this._addAttribute('status', Shelf.Status.READY); + + // minimum period of time, in ms, before two calls to Shelf methods, i.e. throttling: + this._throttlingPeriod_ms = 500.0; + + // timestamp of the last actual call to a Shelf method: + this._lastCallTimestamp = 0.0; + // timestamp of the last scheduled call to a Shelf method: + this._lastScheduledCallTimestamp = 0.0; + } + + /** + * Get the value of a record of type BOOLEAN associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {boolean} options.defaultValue the default value returned if no record with the given key exists + * on the shelf + * @return {Promise} the value associated with the key + * @throws {Object.} exception if there is a record associated with the given key + * but it is not of type BOOLEAN + */ + getBooleanValue({key, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.BOOLEAN, {defaultValue}); + } + + /** + * Set the value of a record of type BOOLEAN associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {boolean} options.value the new value + * @return {Promise} the new value + * @throws {Object.} exception if value is not a boolean, or if there is no record with the given + * key, or if there is a record but it is locked or it is not of type BOOLEAN + */ + setBooleanValue({key, value} = {}) + { + // check the value: + if (typeof value !== "boolean") + { + throw { + origin: "Shelf.setIntegerValue", + context: `when setting the value of the BOOLEAN record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be a boolean" + }; + } + + // update the value: + const update = { + action: "SET", + value + }; + return this._updateValue(key, Shelf.Type.BOOLEAN, update); + } + + /** + * Flip the value of a record of type BOOLEAN associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @return {Promise} the new, flipped, value + * @throws {Object.} exception if there is no record with the given key, or + * if there is a record but it is not of type BOOLEAN + */ + flipBooleanValue({key} = {}) + { + // update the value: + const update = { + action: "FLIP" + }; + return this._updateValue(key, Shelf.Type.BOOLEAN, update); + } + + /** + * Get the value of a record of type INTEGER associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {number} options.defaultValue the default value returned if no record with the given key + * exists on the shelf + * @return {Promise} the value associated with the key + * @throws {Object.} exception if there is no record with the given key, + * or if there is a record but it is locked or it is not of type BOOLEAN + */ + getIntegerValue({key, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.INTEGER, {defaultValue}); + } + + /** + * Set the value of a record of type INTEGER associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {number} options.value the new value + * @return {Promise} the new value + * @throws {Object.} exception if value is not an integer, or or if there is no record + * with the given key, or if there is a record but it is locked or it is not of type INTEGER + */ + setIntegerValue({key, value} = {}) + { + // check the value: + if (!Number.isInteger(value)) + { + throw { + origin: "Shelf.setIntegerValue", + context: `when setting the value of the INTEGER record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be an integer" + }; + } + + // update the value: + const update = { + action: "SET", + value + }; + return this._updateValue(key, Shelf.Type.INTEGER, update); + } + + /** + * Add a delta to the value of a record of type INTEGER associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {number} options.delta the delta, positive or negative, to add to the value + * @return {Promise} the new value + * @throws {Object.} exception if delta is not an integer, or if there is no record with the given + * key, or if there is a record but it is locked or it is not of type INTEGER + */ + addIntegerValue({key, delta} = {}) + { + // check the delta: + if (!Number.isInteger(delta)) + { + throw { + origin: "Shelf.setIntegerValue", + context: `when adding a value to the value of the INTEGER record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be an integer" + }; + } + + // update the value: + const update = { + action: "ADD", + delta + }; + return this._updateValue(key, Shelf.Type.INTEGER, update); + } + + /** + * Get the value of a record of type TEXT associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {string} options.defaultValue the default value returned if no record with the given key exists on + * the shelf + * @return {Promise} the value associated with the key + * @throws {Object.} exception if there is a record associated with the given key but it is + * not of type TEXT + */ + getTextValue({key, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.TEXT, {defaultValue}); + } + + /** + * Set the value of a record of type TEXT associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {string} options.value the new value + * @return {Promise} the new value + * @throws {Object.} exception if value is not a string, or if there is a record associated + * with the given key but it is not of type TEXT + */ + setTextValue({key, value} = {}) + { + // check the value: + if (typeof value !== "string") + { + throw { + origin: "Shelf.setTextValue", + context: `when setting the value of the TEXT record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be a string" + }; + } + + // update the value: + const update = { + action: "SET", + value + }; + return this._updateValue(key, Shelf.Type.TEXT, update); + } + + /** + * Get the value of a record of type LIST associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {Array.<*>} options.defaultValue the default value returned if no record with the given key exists on + * the shelf + * @return {Promise>} the value associated with the key + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type LIST + */ + getListValue({key, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.LIST, {defaultValue}); + } + + /** + * Set the value of a record of type LIST associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {Array.<*>} options.value the new value + * @return {Promise>} the new value + * @throws {Object.} exception if value is not an array or if there is no record with the given key, + * or if there is a record but it is locked or it is not of type LIST + */ + setListValue({key, value} = {}) + { + // check the value: + if (!Array.isArray(value)) + { + throw { + origin: "Shelf.setListValue", + context: `when setting the value of the LIST record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be an array" + }; + } + + // update the value: + const update = { + action: "SET", + value + }; + return this._updateValue(key, Shelf.Type.LIST, update); + } + + /** + * Append an element, or a list of elements, to the value of a record of type LIST associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {*} options.elements the element or list of elements to be appended + * @return {Promise>} the new value + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type LIST + */ + appendListValue({key, elements} = {}) + { + // update the value: + const update = { + action: "APPEND", + elements + }; + return this._updateValue(key, Shelf.Type.LIST, update); + } + + /** + * Pop an element, at the given index, from the value of a record of type LIST associated + * with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {number} [options.index = -1] the index of the element to be popped + * @return {Promise<*>} the popped element + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type LIST + */ + popListValue({key, index = -1} = {}) + { + // update the value: + const update = { + action: "POP", + index + }; + return this._updateValue(key, Shelf.Type.LIST, update); + } + + /** + * Empty the value of a record of type LIST associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @return {Promise>} the new, empty value, i.e. [] + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type LIST + */ + clearListValue({key} = {}) + { + // update the value: + const update = { + action: "CLEAR" + }; + return this._updateValue(key, Shelf.Type.LIST, update); + } + + /** + * Shuffle the elements of the value of a record of type LIST associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @return {Promise>} the new, shuffled value + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type LIST + */ + shuffleListValue({key} = {}) + { + // update the value: + const update = { + action: "SHUFFLE" + }; + return this._updateValue(key, Shelf.Type.LIST, update); + } + + + /** + * Get the names of the fields in the dictionary record associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @return {Promise} the list of field names + * @throws {Object.} exception if there is no record with the given key, or if there is a record + * but it is locked or it is not of type DICTIONARY + */ + async getDictionaryFieldNames({key} = {}) + { + return this._getValue(key, Shelf.Type.DICTIONARY, {fieldNames: true}); + } + + /** + * Get the value of a given field in the dictionary record associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {string} options.fieldName the name of the field + * @param {boolean} options.defaultValue the default value returned if no record with the given key exists on + * the shelf, or if is a record of type DICTIONARY with the given key but it has no such field + * @return {Promise<*>} the value of that field + * @throws {Object.} exception if there is no record with the given key, + * or if there is a record but it is locked or it is not of type DICTIONARY + */ + async getDictionaryFieldValue({key, fieldName, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.DICTIONARY, {fieldName, defaultValue}); + } + + /** + * Set a field in the dictionary record associated to the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {string} options.fieldName the name of the field + * @param {*} options.fieldValue the value of the field + * @return {Promise>} the updated dictionary + * @throws {Object.} exception if there is no record with the given key, + * or if there is a record but it is locked or it is not of type DICTIONARY + */ + async setDictionaryFieldValue({key, fieldName, fieldValue} = {}) + { + // update the value: + const update = { + action: "FIELD_SET", + fieldName, + fieldValue + }; + return this._updateValue(key, Shelf.Type.DICTIONARY, update); + } + + /** + * Get the value of a record of type DICTIONARY associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {Object.} options.defaultValue the default value returned if no record with the given key + * exists on the shelf + * @return {Promise>} the value associated with the key + * @throws {Object.} exception if there is no record with the given key, + * or if there is a record but it is locked or it is not of type DICTIONARY + */ + getDictionaryValue({key, defaultValue} = {}) + { + return this._getValue(key, Shelf.Type.DICTIONARY, {defaultValue}); + } + + /** + * Set the value of a record of type DICTIONARY associated with the given key. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {Object.} options.value the new value + * @return {Promise>} the new value + * @throws {Object.} exception if value is not an object, or or if there is no record + * with the given key, or if there is a record but it is locked or it is not of type DICTIONARY + */ + setDictionaryValue({key, value} = {}) + { + // check the value: + if (typeof value !== "object") + { + throw { + origin: "Shelf.setDictionaryValue", + context: `when setting the value of the DICTIONARY record associated with the key: ${JSON.stringify(key)}`, + error: "the value should be an object" + }; + } + + // update the value: + const update = { + action: "SET", + value + }; + return this._updateValue(key, Shelf.Type.DICTIONARY, update); + } + + /** + * Schedulable component that will block the experiment until the counter associated with the given key + * has been incremented by the given amount. + * + * @param key + * @param increment + * @param callback + * @returns {function(): module:util.Scheduler.Event|Symbol|*} a component that can be scheduled + * + * @example + * const flowScheduler = new Scheduler(psychoJS); + * var experimentCounter = '<>'; + * flowScheduler.add(psychoJS.shelf.incrementComponent(['counter'], 1, (value) => experimentCounter = value)); + */ + incrementComponent(key = [], increment = 1, callback) + { + const response = { + origin: 'Shelf.incrementComponent', + context: 'when making a component to increment a shelf counter' + }; + + try + { + // TODO replace this._incrementComponent by a component with a unique name + let incrementComponent = {}; + incrementComponent.status = PsychoJS.Status.NOT_STARTED; + return () => + { + if (incrementComponent.status === PsychoJS.Status.NOT_STARTED) + { + incrementComponent.status = PsychoJS.Status.STARTED; + this.increment(key, increment) + .then( (newValue) => + { + callback(newValue); + incrementComponent.status = PsychoJS.Status.FINISHED; + }); + } + + return (incrementComponent.status === PsychoJS.Status.FINISHED) ? + Scheduler.Event.NEXT : + Scheduler.Event.FLIP_REPEAT; + }; + } + catch (error) + { + this._status = Shelf.Status.ERROR; + throw {...response, error}; + } + } + + /** + * Get the name of a group, using a counterbalanced design. + * + * @param {Object} options + * @param {string[]} options.key key as an array of key components + * @param {string[]} options.groups the names of the groups + * @param {number[]} options.groupSizes the size of the groups + * @return {Promise<{string, boolean}>} an object with the name of the selected group and whether all groups + * have been depleted + */ + async counterBalanceSelect({key, groups, groupSizes} = {}) + { + const response = { + origin: 'Shelf.counterBalanceSelect', + context: `when getting the name of a group, using a counterbalanced design, with key: ${JSON.stringify(key)}` + }; + + try + { + await this._checkAvailability("counterBalanceSelect"); + this._checkKey(key); + + // prepare the request: + const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/counterbalance`; + const data = { + key, + groups, + groupSizes + }; + + // query the server: + const putResponse = await fetch(url, { + method: 'PUT', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + // convert the response to json: + const document = await putResponse.json(); + + if (putResponse.status !== 200) + { + throw ('error' in document) ? document.error : document; + } + + // return the updated value: + this._status = Shelf.Status.READY; + return { + group: document.group, + finished: document.finished + }; + } + catch (error) + { + this._status = Shelf.Status.ERROR; + throw {...response, error}; + } + } + + + /** + * Update the value associated with the given key. + * + *

    This is a generic method, typically called from the Shelf helper methods, e.g. setBinaryValue.

    + * + * @param {string[]} key key as an array of key components + * @param {Shelf.Type} type the type of the record associated with the given key + * @param {*} update the desired update + * @return {Promise} the updated value + * @throws {Object.} exception if there is no record associated with the given key or if there is one + * but it is not of the given type + */ + async _updateValue(key, type, update) + { + const response = { + origin: 'Shelf._updateValue', + context: `when updating the value of the ${Symbol.keyFor(type)} record associated with key: ${JSON.stringify(key)}` + }; + + try + { + await this._checkAvailability("_updateValue"); + this._checkKey(key); + + // prepare the request: + const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/value`; + const data = { + key, + type: Symbol.keyFor(type), + update + }; + + // query the server: + const postResponse = await fetch(url, { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + // convert the response to json: + const document = await postResponse.json(); + + if (postResponse.status !== 200) + { + throw ('error' in document) ? document.error : document; + } + + // return the updated value: + this._status = Shelf.Status.READY; + return document.value; + } + catch (error) + { + this._status = Shelf.Status.ERROR; + throw {...response, error}; + } + } + + /** + * Get the value associated with the given key. + * + *

    This is a generic method, typically called from the Shelf helper methods, e.g. getBinaryValue.

    + * + * @param {string[]} key key as an array of key components + * @param {Shelf.Type} type the type of the record associated with the given key + * @param {Object} [options] the options, e.g. the default value returned if no record with the + * given key exists on the shelf + * @return {Promise} the value + * @throws {Object.} exception if there is a record associated with the given key but it is not of + * the given type + */ + async _getValue(key, type, options) + { + const response = { + origin: 'Shelf._getValue', + context: `when getting the value of the ${Symbol.keyFor(type)} record associated with key: ${JSON.stringify(key)}` + }; + + try + { + await this._checkAvailability("_getValue"); + this._checkKey(key); + + // prepare the request: + const url = `${this._psychoJS.config.pavlovia.URL}/api/v2/shelf/${this._psychoJS.config.session.token}/value`; + const data = { + key, + type: Symbol.keyFor(type) + }; + + if (typeof options !== 'undefined') + { + for (const attribute in options) + { + if (typeof options[attribute] !== "undefined") + { + data[attribute] = options[attribute]; + } + } + } + + // query the server: + const putResponse = await fetch(url, { + method: 'PUT', + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + const document = await putResponse.json(); + + if (putResponse.status !== 200) + { + throw ('error' in document) ? document.error : document; + } + + // return the value: + this._status = Shelf.Status.READY; + return document.value; + } + catch (error) + { + this._status = Shelf.Status.ERROR; + throw {...response, error}; + } + } + + /** + * Check whether it is possible to run a given shelf command. + * + *

    Since all Shelf methods call _checkAvailability, we also use it as a means to throttle those calls.

    + * + * @param {string} [methodName=""] - name of the method requiring a check + * @throws {Object.} exception if it is not possible to run the given shelf command + */ + _checkAvailability(methodName = "") + { + // Shelf requires access to the server, where the key/value pairs are stored: + if (this._psychoJS.config.environment !== ExperimentHandler.Environment.SERVER) + { + throw { + origin: 'Shelf._checkAvailability', + context: 'when checking whether Shelf is available', + error: 'the experiment has to be run on the server: shelf commands are not available locally' + }; + } + + // throttle calls to Shelf methods: + const self = this; + return new Promise((resolve, reject) => + { + const now = performance.now(); + + // if the last scheduled call already occurred, schedule this one as soon as possible, + // taking into account the throttling period: + let timeoutDuration; + if (now > self._lastScheduledCallTimestamp) + { + timeoutDuration = Math.max(0.0, self._throttlingPeriod_ms - (now - self._lastCallTimestamp)); + self._lastScheduledCallTimestamp = now + timeoutDuration; + } + // otherwise, schedule it after the next call: + else + { + self._lastScheduledCallTimestamp += self._throttlingPeriod_ms; + timeoutDuration = self._lastScheduledCallTimestamp; + } + + setTimeout( + () => { + self._lastCallTimestamp = performance.now(); + self._status = Shelf.Status.BUSY; + resolve(); + }, + timeoutDuration + ); + }); + } + + /** + * Check the validity of the key. + * + * @param {object} key key whose validity is to be checked + * @throws {Object.} exception if the key is invalid + */ + _checkKey(key) + { + // the key must be a non empty array: + if (!Array.isArray(key) || key.length === 0) + { + throw 'the key must be a non empty array'; + } + + if (key.length > Shelf.#MAX_KEY_LENGTH) + { + throw 'the key consists of too many components'; + } + + // the only @ in the key should be @designer and @experiment + // TODO + } +} + +/** + * Shelf status + * + * @enum {Symbol} + * @readonly + */ +Shelf.Status = { + /** + * The shelf is ready. + */ + READY: Symbol.for('READY'), + + /** + * The shelf is busy, e.g. storing or retrieving values. + */ + BUSY: Symbol.for('BUSY'), + + /** + * The shelf has encountered an error. + */ + ERROR: Symbol.for('ERROR') +}; + +/** + * Shelf record types. + * + * @enum {Symbol} + * @readonly + */ +Shelf.Type = { + INTEGER: Symbol.for('INTEGER'), + TEXT: Symbol.for('TEXT'), + DICTIONARY: Symbol.for('DICTIONARY'), + BOOLEAN: Symbol.for('BOOLEAN'), + LIST: Symbol.for('LIST') +}; diff --git a/src/data/TrialHandler.js b/src/data/TrialHandler.js index ff10c92f..c531d7d7 100644 --- a/src/data/TrialHandler.js +++ b/src/data/TrialHandler.js @@ -4,8 +4,8 @@ * * @author Alain Pitiot * @author Hiroyuki Sogo & Sotiri Bakagiannis - better support for BOM and accented characters - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -17,25 +17,12 @@ import * as util from "../util/Util.js"; /** *

    A Trial Handler handles the importing and sequencing of conditions.

    * - * @class * @extends PsychObject - * @param {Object} options - the handler options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {Array. | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource - * @param {number} options.nReps - number of repetitions - * @param {module:data.TrialHandler.Method} options.method - the trial method - * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc. - * @param {number} options.seed - seed for the random number generator - * @param {boolean} [options.autoLog= false] - whether or not to log */ export class TrialHandler extends PsychObject { /** * Getter for experimentHandler. - * - * @name module:core.Window#experimentHandler - * @function - * @public */ get experimentHandler() { @@ -44,10 +31,6 @@ export class TrialHandler extends PsychObject /** * Setter for experimentHandler. - * - * @name module:core.Window#experimentHandler - * @function - * @public */ set experimentHandler(exp) { @@ -55,8 +38,14 @@ export class TrialHandler extends PsychObject } /** - * @constructor - * @public + * @param {Object} options - the handler options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {Array. | String} [options.trialList= [undefined] ] - if it is a string, we treat it as the name of a condition resource + * @param {number} options.nReps - number of repetitions + * @param {module:data.TrialHandler.Method} options.method - the trial method + * @param {Object} options.extraInfo - additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc. + * @param {number} options.seed - seed for the random number generator + * @param {boolean} [options.autoLog= false] - whether or not to log * * @todo extraInfo is not taken into account, we use the expInfo of the ExperimentHandler instead */ @@ -223,7 +212,6 @@ export class TrialHandler extends PsychObject * *

    This is typically used in the LoopBegin function, in order to capture the current state of a TrialHandler

    * - * @public * @return {Snapshot} - a snapshot of the current internal state. */ getSnapshot() @@ -294,11 +282,10 @@ export class TrialHandler extends PsychObject } /** - * Set the internal state of this trial handler from the given snapshot. + * Set the internal state of the snapshot's trial handler from the snapshot. * - * @public - * @static - * @param {Snapshot} snapshot - the snapshot from which to update the current internal state. + * @param {Snapshot} snapshot - the snapshot from which to update the current internal state of the + * snapshot's trial handler */ static fromSnapshot(snapshot) { @@ -317,7 +304,6 @@ export class TrialHandler extends PsychObject snapshot.handler.thisIndex = snapshot.thisIndex; snapshot.handler.ran = snapshot.ran; snapshot.handler._finished = snapshot._finished; - snapshot.handler.thisTrial = snapshot.handler.getCurrentTrial(); // add the snapshot's trial attributes to a global variable, whose name is derived from @@ -365,7 +351,6 @@ export class TrialHandler extends PsychObject /** * Get the trial index. * - * @public * @return {number} the current trial index */ getTrialIndex() @@ -389,7 +374,6 @@ export class TrialHandler extends PsychObject *

    Note: we assume that all trials in the trialList share the same attributes * and consequently consider only the attributes of the first trial.

    * - * @public * @return {Array.string} the attributes */ getAttributes() @@ -411,7 +395,6 @@ export class TrialHandler extends PsychObject /** * Get the current trial. * - * @public * @return {Object} the current trial */ getCurrentTrial() @@ -438,7 +421,6 @@ export class TrialHandler extends PsychObject /** * Get the nth future or past trial, without advancing through the trial list. * - * @public * @param {number} [n = 1] - increment * @return {Object|undefined} the future trial (if n is positive) or past trial (if n is negative) * or undefined if attempting to go beyond the last trial. @@ -457,7 +439,6 @@ export class TrialHandler extends PsychObject * Get the nth previous trial. *

    Note: this is useful for comparisons in n-back tasks.

    * - * @public * @param {number} [n = -1] - increment * @return {Object|undefined} the past trial or undefined if attempting to go prior to the first trial. */ @@ -469,7 +450,6 @@ export class TrialHandler extends PsychObject /** * Add a key/value pair to data about the current trial held by the experiment handler * - * @public * @param {Object} key - the key * @param {Object} value - the value */ @@ -508,8 +488,6 @@ export class TrialHandler extends PsychObject * '5:' * '-5:-2, 9, 11:5:22' * - * @public - * @static * @param {module:core.ServerManager} serverManager - the server manager * @param {String} resourceName - the name of the resource containing the list of conditions, which must have been registered with the server manager. * @param {Object} [selection = null] - the selection @@ -617,7 +595,6 @@ export class TrialHandler extends PsychObject /** * Prepare the trial list. * - * @function * @protected * @returns {void} */ @@ -655,7 +632,7 @@ export class TrialHandler extends PsychObject } } - /* + /** * Prepare the sequence of trials. * *

    The returned sequence is a matrix (an array of arrays) of trial indices @@ -680,7 +657,7 @@ export class TrialHandler extends PsychObject *

    * * @protected - */ + **/ _prepareSequence() { const response = { @@ -738,7 +715,6 @@ export class TrialHandler extends PsychObject * * @enum {Symbol} * @readonly - * @public */ TrialHandler.Method = { /** diff --git a/src/data/index.js b/src/data/index.js index 5598fa32..1bffe5ef 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -2,4 +2,4 @@ export * from "./ExperimentHandler.js"; export * from "./TrialHandler.js"; export * from "./QuestHandler.js"; export * from "./MultiStairHandler.js"; -//export * from "./Shelf.js"; +export * from "./Shelf.js"; diff --git a/src/visual/Camera.js b/src/hardware/Camera.js similarity index 76% rename from src/visual/Camera.js rename to src/hardware/Camera.js index b9d74098..8d3b1c25 100644 --- a/src/visual/Camera.js +++ b/src/hardware/Camera.js @@ -1,9 +1,10 @@ +/** **/ /** * Manager handling the recording of video signal. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.0 + * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -18,15 +19,11 @@ import {ExperimentHandler} from "../data/ExperimentHandler.js"; /** *

    This manager handles the recording of video signal.

    * - * @name module:visual.Camera + * @name module:hardware.Camera * @class * @param {Object} options * @param {module:core.Window} options.win - the associated Window * @param {string} [options.format='video/webm;codecs=vp9'] the video format - * @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the - * participant to wait for the camera to be initialised - * @param {string} [options.dialogMsg="Please wait a few moments while the camera initialises"] - - * default message informing the participant to wait for the camera to initialise * @param {Clock} [options.clock= undefined] - an optional clock * @param {boolean} [options.autoLog= false] - whether or not to log * @@ -34,11 +31,7 @@ import {ExperimentHandler} from "../data/ExperimentHandler.js"; */ export class Camera extends PsychObject { - /** - * @constructor - * @public - */ - constructor({win, name, format, showDialog, dialogMsg = "Please wait a few moments while the camera initialises", clock, autoLog} = {}) + constructor({win, name, format, clock, autoLog} = {}) { super(win._psychoJS); @@ -49,48 +42,87 @@ export class Camera extends PsychObject this._addAttribute("autoLog", autoLog, false); this._addAttribute("status", PsychoJS.Status.NOT_STARTED); - // open pop-up dialog: + this._stream = null; + this._recorder = null; + + if (this._autoLog) + { + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + } + } + + /** + * Prompt the user for permission to use the camera on their device. + * + * @name module:hardware.Camera#authorize + * @function + * @public + * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the + * participant to wait for the camera to be initialised + * @param {string} [dialogMsg] - the dialog message + * @returns {boolean} whether or not the camera is ready to record + */ + async authorize(showDialog = false, dialogMsg = undefined) + { + const response = { + origin: "Camera.authorize", + context: "when authorizing access to the device's camera" + }; + + // open pop-up dialog, if required: if (showDialog) { + dialogMsg ??= "Please wait a few moments while the camera initialises. You may need to grant permission to your browser to use the camera."; this.psychoJS.gui.dialog({ warning: dialogMsg, showOK: false, }); } - // prepare the recording: - this._prepareRecording().then( () => + try + { + // prompt for permission and get a MediaStream: + // TODO use size constraints [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia] + this._stream = await navigator.mediaDevices.getUserMedia({ + video: true + }); + } + catch (error) { + // close the dialog, if need be: if (showDialog) { this.psychoJS.gui.closeDialog(); } - }) - if (this._autoLog) + this._status = PsychoJS.Status.ERROR; + throw {...response, error}; + } + + // close the dialog, if need be: + if (showDialog) { - this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + this.psychoJS.gui.closeDialog(); } } /** - * Query whether or not the camera is ready to record. + * Query whether the camera is ready to record. * - * @name module:visual.Camera#isReady + * @name module:hardware.Camera#isReady * @function * @public - * @returns {boolean} whether or not the camera is ready to record + * @returns {boolean} true if the camera is ready to record, false otherwise */ - isReady() + get isReady() { return (this._recorder !== null); } - /** * Get the underlying video stream. * - * @name module:visual.Camera#getStream + * @name module:hardware.Camera#getStream * @function * @public * @returns {MediaStream} the video stream @@ -100,11 +132,10 @@ export class Camera extends PsychObject return this._stream; } - /** * Get a video element pointing to the Camera stream. * - * @name module:visual.Camera#getVideo + * @name module:hardware.Camera#getVideo * @function * @public * @returns {HTMLVideoElement} a video element @@ -135,16 +166,37 @@ export class Camera extends PsychObject return video; } + /** + * Open the video stream. + * + * @name module:hardware.Camera#open + * @function + * @public + */ + open() + { + if (this._stream === null) + { + throw { + origin: "Camera.open", + context: "when opening the camera's video stream", + error: "access to the camera has not been authorized, or no camera could be found" + }; + } + + // prepare the recording: + this._prepareRecording(); + } /** * Submit a request to start the recording. * - * @name module:visual.Camera#start + * @name module:hardware.Camera#record * @function * @public - * @return {Promise} promise fulfilled when the recording actually started + * @return {Promise} promise fulfilled when the recording actually starts */ - start() + record() { // if the camera is currently paused, a call to start resumes it // with a new recording: @@ -153,7 +205,6 @@ export class Camera extends PsychObject return this.resume({clear: true}); } - if (this._status !== PsychoJS.Status.STARTED) { this._psychoJS.logger.debug("request to start video recording"); @@ -182,7 +233,7 @@ export class Camera extends PsychObject this._status = PsychoJS.Status.ERROR; throw { - origin: "Camera.start", + origin: "Camera.record", context: "when starting the video recording for camera: " + this._name, error }; @@ -192,20 +243,17 @@ export class Camera extends PsychObject } - /** * Submit a request to stop the recording. * - * @name module:visual.Camera#stop + * @name module:hardware.Camera#stop * @function * @public * @param {Object} options - * @param {string} [options.filename] the name of the file to which the video recording - * will be saved * @return {Promise} promise fulfilled when the recording actually stopped, and the recorded * data was made available */ - stop({filename} = {}) + stop() { if (this._status === PsychoJS.Status.STARTED || this._status === PsychoJS.Status.PAUSED) { @@ -217,12 +265,7 @@ export class Camera extends PsychObject video.pause(); } - this._stopOptions = { - filename - }; - - // note: calling the stop method of the MediaRecorder will first raise - // a dataavailable event, and then a stop event + // note: calling the MediaRecorder.stop will first raise a dataavailable event, and then a stop event // ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/stop this._recorder.stop(); @@ -237,11 +280,10 @@ export class Camera extends PsychObject } } - /** * Submit a request to pause the recording. * - * @name module:visual.Camera#pause + * @name module:hardware.Camera#pause * @function * @public * @return {Promise} promise fulfilled when the recording actually paused @@ -285,13 +327,12 @@ export class Camera extends PsychObject } } - /** * Submit a request to resume the recording. * *

    resume has no effect if the recording was not previously paused.

    * - * @name module:visual.Camera#resume + * @name module:hardware.Camera#resume * @function * @param {Object} options * @param {boolean} [options.clear= false] whether or not to empty the video buffer before @@ -343,11 +384,10 @@ export class Camera extends PsychObject } } - /** * Submit a request to flush the recording. * - * @name module:visual.Camera#flush + * @name module:hardware.Camera#flush * @function * @public * @return {Promise} promise fulfilled when the data has actually been made available @@ -373,43 +413,42 @@ export class Camera extends PsychObject } } - /** - * Offer the audio recording to the participant as a video file to download. + * Get the current video recording as a VideoClip in the given format. * - * @name module:visual.Camera#download + * @name module:hardware.Camera#getRecording * @function * @public - * @param {string} filename - the filename of the video file + * @param {string} tag an optional tag for the video clip + * @param {boolean} [flush=false] whether or not to first flush the recording */ - download(filename = "video.webm") + async getRecording({tag, flush = false} = {}) { - const videoBlob = new Blob(this._videoBuffer); + // default tag: the name of this Microphone object + if (typeof tag === "undefined") + { + tag = this._name; + } - const anchor = document.createElement("a"); - anchor.href = window.URL.createObjectURL(videoBlob); - anchor.download = filename; - document.body.appendChild(anchor); - anchor.click(); - document.body.removeChild(anchor); + // TODO } - /** * Upload the video recording to the pavlovia server. * - * @name module:visual.Camera#upload + * @name module:hardware.Camera#_upload * @function - * @public - * @param @param {Object} options - * @param {string} options.tag an optional tag for the video file - * @param {boolean} [options.waitForCompletion= false] whether or not to wait for completion + * @protected + * @param {string} tag an optional tag for the video file + * @param {boolean} [waitForCompletion= false] whether to wait for completion * before returning - * @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server - * @param {string} [options.dialogMsg=""] - default message informing the participant to wait for the data to be uploaded to the server + * @param {boolean} [showDialog=false] - whether to open a dialog box to inform the participant to wait for the data to be uploaded to the server + * @param {string} [dialogMsg=""] - default message informing the participant to wait for the data to be uploaded to the server */ - async upload({tag, waitForCompletion = false, showDialog = false, dialogMsg = ""} = {}) + save({tag, waitForCompletion = false, showDialog = false, dialogMsg = ""} = {}) { + this._psychoJS.logger.info("[PsychoJS] Save video recording."); + // default tag: the name of this Camera object if (typeof tag === "undefined") { @@ -419,14 +458,22 @@ export class Camera extends PsychObject // add a format-dependent video extension to the tag: tag += util.extensionFromMimeType(this._format); - // if the video recording cannot be uploaded, e.g. the experiment is running locally, or // if it is piloting mode, then we offer the video recording as a file for download: if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || this._psychoJS.config.experiment.status !== "RUNNING" || this._psychoJS._serverMsg.has("__pilotToken")) { - return this.download(tag); + const videoBlob = new Blob(this._videoBuffer); + + const anchor = document.createElement("a"); + anchor.href = window.URL.createObjectURL(videoBlob); + anchor.download = tag; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + + return; } // upload the blob: @@ -439,34 +486,29 @@ export class Camera extends PsychObject dialogMsg}); } - /** - * Get the current video recording as a VideoClip in the given format. + * Close the camera stream. * - * @name module:visual.Camera#getRecording + * @name module:hardware.Camera#close * @function * @public - * @param {string} tag an optional tag for the video clip - * @param {boolean} [flush=false] whether or not to first flush the recording + * @returns {Promise} promise fulfilled when the stream has stopped and is now closed */ - async getRecording({tag, flush = false} = {}) + async close() { - // default tag: the name of this Microphone object - if (typeof tag === "undefined") - { - tag = this._name; - } + await this.stop(); - // TODO + this._videos = []; + this._stream = null; + this._recorder = null; } - /** * Callback for changes to the recording settings. * *

    Changes to the settings require the recording to stop and be re-started.

    * - * @name module:visual.Camera#_onChange + * @name module:hardware.Camera#_onChange * @function * @protected */ @@ -482,32 +524,24 @@ export class Camera extends PsychObject this.start(); } - /** * Prepare the recording. * - * @name module:visual.Camera#_prepareRecording + * @name module:hardware.Camera#_prepareRecording * @function * @protected */ - async _prepareRecording() + _prepareRecording() { // empty the video buffer: this._videoBuffer = []; this._recorder = null; this._videos = []; - // create a new stream with ideal dimensions: - // TODO use size constraints - this._stream = await navigator.mediaDevices.getUserMedia({ - video: true - }); - // check the actual width and height: this._streamSettings = this._stream.getVideoTracks()[0].getSettings(); this._psychoJS.logger.debug(`camera stream settings: ${JSON.stringify(this._streamSettings)}`); - // check that the specified format is supported, use default if it is not: let options; if (typeof this._format === "string" && MediaRecorder.isTypeSupported(this._format)) @@ -519,11 +553,9 @@ export class Camera extends PsychObject this._psychoJS.logger.warn(`The specified video format, ${this._format}, is not supported by this browser, using the default format instead`); } - // create a video recorder: this._recorder = new MediaRecorder(this._stream, options); - // setup the callbacks: const self = this; @@ -537,7 +569,7 @@ export class Camera extends PsychObject self._status = PsychoJS.Status.STARTED; self._psychoJS.logger.debug("video recording started"); - // resolve the Microphone.start promise: + // resolve the Camera.start promise: if (self._startCallback) { self._startCallback(self._psychoJS.monotonicClock.getTime()); @@ -550,7 +582,7 @@ export class Camera extends PsychObject self._status = PsychoJS.Status.PAUSED; self._psychoJS.logger.debug("video recording paused"); - // resolve the Microphone.pause promise: + // resolve the Camera.pause promise: if (self._pauseCallback) { self._pauseCallback(self._psychoJS.monotonicClock.getTime()); @@ -563,7 +595,7 @@ export class Camera extends PsychObject self._status = PsychoJS.Status.STARTED; self._psychoJS.logger.debug("video recording resumed"); - // resolve the Microphone.resume promise: + // resolve the Camera.resume promise: if (self._resumeCallback) { self._resumeCallback(self._psychoJS.monotonicClock.getTime()); @@ -590,21 +622,13 @@ export class Camera extends PsychObject this._recorder.onstop = () => { self._psychoJS.logger.debug("video recording stopped"); - self._status = PsychoJS.Status.NOT_STARTED; + self._status = PsychoJS.Status.STOPPED; - // resolve the Microphone.stop promise: + // resolve the Camera.stop promise: if (self._stopCallback) { self._stopCallback(self._psychoJS.monotonicClock.getTime()); } - - // treat stop options if there are any: - - // download to a file, immediately offered to the participant: - if (typeof self._stopOptions.filename === "string") - { - self.download(self._stopOptions.filename); - } }; // called upon recording errors: @@ -614,7 +638,6 @@ export class Camera extends PsychObject self._psychoJS.logger.error("video recording error: " + JSON.stringify(event)); self._status = PsychoJS.Status.ERROR; }; - } } diff --git a/src/hardware/index.js b/src/hardware/index.js new file mode 100644 index 00000000..c8804936 --- /dev/null +++ b/src/hardware/index.js @@ -0,0 +1 @@ +export * from "./Camera.js"; diff --git a/src/index.css b/src/index.css index 930210cc..c903ea8c 100644 --- a/src/index.css +++ b/src/index.css @@ -10,96 +10,197 @@ body { margin: 0; } + +/* Initialisation message (which will disappear behind the canvas) */ +#root::after { + content: "initialising the experiment..."; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +#root.is-ready::after { + content: "" +} + + /* Project and resource dialogs */ -label, -input, -select { - box-sizing: border-box; - display: block; - padding-bottom: 0.5em; + +.dialog-container label, +.dialog-container input, +.dialog-container select { + box-sizing: border-box; + display: block; + padding-bottom: 0.5em; } -input.text, -select.text { - margin-bottom: 1em; - padding: 0.5em; - width: 100%; +.dialog-container input.text, +.dialog-container select.text { + margin-bottom: 1em; + padding: 0.5em; + width: 100%; } -fieldset { - border: 0; - margin-top: 1em; - padding: 0; +.dialog-container fieldset { + border: 0; + margin-top: 1em; + padding: 0; } -a, -a:active, -a:focus, -a:visited { - color: #007eb7; - outline: 0; +.dialog-container, +.dialog-overlay { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; } -a:hover { - color: #000; +.dialog-container { + z-index: 2; + display: flex; } -.progress { - box-sizing: border-box; - padding: 0.5em 0; +.dialog-container[aria-hidden='true'] { + display: none; } -.logo { - display: block; - margin: 0 auto 1em; - max-height: 20vh; - max-width: 100%; +.dialog-overlay { + background-color: rgba(0, 0, 0, 0.2); } -.ui-dialog { - left: auto !important; +.dialog-content { margin: auto; - max-width: 88vw; + z-index: 2; position: relative; - top: auto !important; + + width: 500px; + max-width: 88vw; + padding: 0.5em; + border-radius: 2px; + + font-family: 'Open Sans', sans-serif; + color: #333333; + + background-color: #EEEEEE; + border-color: #CCCCCC; + box-shadow: 1px 1px 3px #555555; } -/* Don't display close button in the top right corner of the box */ -.ui-dialog.no-close .ui-dialog-titlebar-close { - display: none; +.dialog-title { + padding: 0.5em; + margin-bottom: 1em; + + background-color: #009900; + border-radius: 2px; +} + +.dialog-warning { + background-color: #FF9900 !important; +} + +.dialog-error { + background-color: #FF0000 !important; } -.ui-dialog .ui-dialog-content { - margin-top: 1em; - max-height: calc(100vh - 12em) !important; - overflow-y: auto; +.dialog-title p { + margin: 0; + padding: 0; + font-weight: bold; } -.ui-dialog .ui-dialog-buttonpane { - /* Avoid padding related overflow */ +.dialog-close { + position: absolute; + top: 0.7em; + right: 0.7em; + border: 0; + padding: 0; + border-radius: 2px; + + width: 1.1em; + height: 1.1em; + + color: #333333; + background-color: #FFFFFF; + font-weight: bold; + font-size: 1.25em; + + text-align: center; + cursor: pointer; + transition: 0.15s; +} + +.progress-msg { box-sizing: border-box; + padding: 0.5em 0; } -@media only screen and (max-width: 1080px) { - .ui-dialog .ui-dialog-buttonpane { - padding-top: 1em; - } +.progress-container { + padding: 0.2em; + border: 1px solid #555555; + border-radius: 2px; + + background-color: #FFFFFF; } -/* Initialisation message (which will disappear behind the canvas) */ -#root::after { - content: "initialising the experiment..."; - left: 50%; - position: fixed; - top: 50%; - transform: translate(-50%, -50%); +.progress-bar { + width: 0; + height: 1.5em; + + background-color: #CCCCCC; +} + +.dialog-button { + padding: 0.5em 1em 0.5em 1em; + margin: 0.5em 0.5em 0.5em 0; + border: 1px solid #555555; + border-radius: 2px; + + font-size: 0.9em; + color: #000000; + background-color: #FFFFFF; + + cursor: pointer; +} + +.dialog-button:hover { + background-color: #EEEEEE; } -/* Initialisation message for IE11 */ -@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - #root::after { - color: #a05000; - content: "initialising the experiment... | Internet Explorer / Edge [beta]"; - font-weight: bold; - } +.dialog-button:active { + background-color: #CCCCCC; } + +.dialog-button:focus { + border: 1px solid #000000; +} + +.disabled { + border: 1px solid #AAAAAA; + color: #AAAAAA; + background-color: #EEEEEE; + + cursor: not-allowed; + pointer-events: none; +} + +.logo { + display: block; + margin: 0 auto 1em; + max-height: 20vh; + max-width: 100%; +} + +a, +a:active, +a:focus, +a:visited { + color: #007eb7; + outline: 0; +} + +a:hover { + color: #000; +} + diff --git a/src/index.js b/src/index.js index d9a74bd4..f05c5f8f 100644 --- a/src/index.js +++ b/src/index.js @@ -3,3 +3,4 @@ export * as core from './core/index.js'; export * as data from './data/index.js'; export * as visual from './visual/index.js'; export * as sound from './sound/index.js'; +export * as hardware from './hardware/index.js'; diff --git a/src/sound/AudioClip.js b/src/sound/AudioClip.js index da15d496..d8d675b5 100644 --- a/src/sound/AudioClip.js +++ b/src/sound/AudioClip.js @@ -2,7 +2,7 @@ * AudioClip encapsulates an audio recording. * * @author Alain Pitiot and Sotiri Bakagiannis - * @version 2021.2.0 + * @version 2022.2.3 * @copyright (c) 2021 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -15,18 +15,20 @@ import * as util from "../util/Util.js"; /** *

    AudioClip encapsulates an audio recording.

    * - * @name module:sound.AudioClip - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {String} [options.name= 'audioclip'] - the name used when logging messages - * @param {string} options.format the format for the audio file - * @param {number} options.sampleRateHz - the sampling rate - * @param {Blob} options.data - the audio data, in the given format, at the given sampling rate - * @param {boolean} [options.autoLog= false] - whether or not to log + * @extends PsychObject */ export class AudioClip extends PsychObject { + /** + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {String} [options.name= 'audioclip'] - the name used when logging messages + * @param {string} options.format the format for the audio file + * @param {number} options.sampleRateHz - the sampling rate + * @param {Blob} options.data - the audio data, in the given format, at the given sampling rate + * @param {boolean} [options.autoLog= false] - whether or not to log + */ constructor({ psychoJS, name, sampleRateHz, format, data, autoLog } = {}) { super(psychoJS); @@ -53,9 +55,6 @@ export class AudioClip extends PsychObject /** * Set the volume of the playback. * - * @name module:sound.AudioClip#setVolume - * @function - * @public * @param {number} volume - the volume of the playback (must be between 0.0 and 1.0) */ setVolume(volume) @@ -66,8 +65,6 @@ export class AudioClip extends PsychObject /** * Start playing the audio clip. * - * @name module:sound.AudioClip#startPlayback - * @function * @public */ async startPlayback() @@ -102,9 +99,6 @@ export class AudioClip extends PsychObject /** * Stop playing the audio clip. * - * @name module:sound.AudioClip#startPlayback - * @function - * @public * @param {number} [fadeDuration = 17] - how long the fading out should last, in ms */ async stopPlayback(fadeDuration = 17) @@ -118,9 +112,6 @@ export class AudioClip extends PsychObject /** * Get the duration of the audio clip, in seconds. * - * @name module:sound.AudioClip#getDuration - * @function - * @public * @returns {Promise} the duration of the audio clip */ async getDuration() @@ -134,8 +125,6 @@ export class AudioClip extends PsychObject /** * Upload the audio clip to the pavlovia server. * - * @name module:sound.AudioClip#upload - * @function * @public */ upload() @@ -165,10 +154,6 @@ export class AudioClip extends PsychObject /** * Offer the audio clip to the participant as a sound file to download. - * - * @name module:sound.AudioClip#download - * @function - * @public */ download(filename = "audio.webm") { @@ -187,7 +172,7 @@ export class AudioClip extends PsychObject * @param {Symbol} options.engine - the speech-to-text engine * @param {String} options.languageCode - the BCP-47 language code for the recognition, * e.g. 'en-GB' - * @return {Promise<>} a promise resolving to the transcript and associated + * @return {Promise} a promise resolving to the transcript and associated * transcription confidence */ async transcribe({ engine, languageCode } = {}) @@ -239,9 +224,10 @@ export class AudioClip extends PsychObject * * ref: https://cloud.google.com/speech-to-text/docs/reference/rest/v1/speech/recognize * + * @protected * @param {String} transcriptionKey - the secret key to the Google service * @param {String} languageCode - the BCP-47 language code for the recognition, e.g. 'en-GB' - * @return {Promise<>} a promise resolving to the transcript and associated + * @return {Promise} a promise resolving to the transcript and associated * transcription confidence */ _GoogleTranscribe(transcriptionKey, languageCode) @@ -306,6 +292,7 @@ export class AudioClip extends PsychObject /** * Decode the formatted audio data (e.g. webm) into a 32bit float PCM audio buffer. * + * @protected */ _decodeAudio() { @@ -386,6 +373,7 @@ export class AudioClip extends PsychObject * const dataAsString = String.fromCharCode.apply(null, new Uint8Array(buffer)); * base64Data = window.btoa(dataAsString); * + * @protected * @param arrayBuffer - the input buffer * @return {string} the base64 encoded input buffer */ @@ -453,10 +441,8 @@ export class AudioClip extends PsychObject /** * Recognition engines. * - * @name module:sound.AudioClip#Engine * @enum {Symbol} * @readonly - * @public */ AudioClip.Engine = { /** @@ -470,7 +456,6 @@ AudioClip.Engine = { * * @enum {Symbol} * @readonly - * @public */ AudioClip.Status = { CREATED: Symbol.for("CREATED"), diff --git a/src/sound/AudioClipPlayer.js b/src/sound/AudioClipPlayer.js index 082a71ad..81792f6b 100644 --- a/src/sound/AudioClipPlayer.js +++ b/src/sound/AudioClipPlayer.js @@ -2,8 +2,8 @@ * AudioClip Player. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -13,20 +13,21 @@ import { SoundPlayer } from "./SoundPlayer.js"; /** *

    This class handles the playback of an audio clip, e.g. a microphone recording.

    * - * @name module:sound.AudioClipPlayer - * @class * @extends SoundPlayer - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {Object} options.audioClip - the module:sound.AudioClip - * @param {number} [options.startTime= 0] - start of playback (in seconds) - * @param {number} [options.stopTime= -1] - end of playback (in seconds) - * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo - * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played * */ export class AudioClipPlayer extends SoundPlayer { + /** + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {Object} options.audioClip - the module:sound.AudioClip + * @param {number} [options.startTime= 0] - start of playback (in seconds) + * @param {number} [options.stopTime= -1] - end of playback (in seconds) + * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo + * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played * + */ constructor({ psychoJS, audioClip, @@ -52,40 +53,26 @@ export class AudioClipPlayer extends SoundPlayer /** * Determine whether this player can play the given sound. * - * @name module:sound.AudioClipPlayer.accept - * @function - * @static - * @public - * @param {module:sound.Sound} sound - the sound object, which should be an AudioClip - * @return {Object|undefined} an instance of AudioClipPlayer if sound is an AudioClip or undefined otherwise + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + * @param {string} value - the sound value, which should be the name of an audio resource + * file + * @return {Object|boolean} argument needed to instantiate a AudioClipPlayer that can play the given sound + * or false otherwise */ - static accept(sound) + static accept(psychoJS, value) { - if (sound.value instanceof AudioClip) + if (value instanceof AudioClip) { - // build the player: - const player = new AudioClipPlayer({ - psychoJS: sound.psychoJS, - audioClip: sound.value, - startTime: sound.startTime, - stopTime: sound.stopTime, - stereo: sound.stereo, - loops: sound.loops, - volume: sound.volume, - }); - return player; + return { audioClip: value }; } // AudioClipPlayer is not an appropriate player for the given sound: - return undefined; + return false; } /** * Get the duration of the AudioClip, in seconds. * - * @name module:sound.AudioClipPlayer#getDuration - * @function - * @public * @return {number} the duration of the clip, in seconds */ getDuration() @@ -96,9 +83,6 @@ export class AudioClipPlayer extends SoundPlayer /** * Set the duration of the audio clip. * - * @name module:sound.AudioClipPlayer#setDuration - * @function - * @public * @param {number} duration_s - the duration of the clip in seconds */ setDuration(duration_s) @@ -115,9 +99,6 @@ export class AudioClipPlayer extends SoundPlayer /** * Set the volume of the playback. * - * @name module:sound.AudioClipPlayer#setVolume - * @function - * @public * @param {number} volume - the volume of the playback (must be between 0.0 and 1.0) * @param {boolean} [mute= false] - whether or not to mute the playback */ @@ -131,9 +112,6 @@ export class AudioClipPlayer extends SoundPlayer /** * Set the number of loops. * - * @name module:sound.AudioClipPlayer#setLoops - * @function - * @public * @param {number} loops - how many times to repeat the clip after it has played once. If loops == -1, the clip will repeat indefinitely until stopped. */ setLoops(loops) @@ -144,12 +122,26 @@ export class AudioClipPlayer extends SoundPlayer // TODO } + /** + * Set the audio clip. + * + * @param {Object} options.audioClip - the module:sound.AudioClip. + */ + setAudioClip(audioClip) + { + if (audioClip instanceof AudioClip) + { + if (this._audioClip !== undefined) + { + this.stop(); + } + this._audioClip = audioClip; + } + } + /** * Start playing the sound. * - * @name module:sound.AudioClipPlayer#play - * @function - * @public * @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 {number} [fadeDuration = 17] - how long should the fading in last in ms */ @@ -172,9 +164,6 @@ export class AudioClipPlayer extends SoundPlayer /** * Stop playing the sound immediately. * - * @name module:sound.AudioClipPlayer#stop - * @function - * @public * @param {number} [fadeDuration = 17] - how long the fading out should last, in ms */ stop(fadeDuration = 17) diff --git a/src/sound/Microphone.js b/src/sound/Microphone.js index 2f839c6a..1d1f9d3a 100644 --- a/src/sound/Microphone.js +++ b/src/sound/Microphone.js @@ -2,8 +2,8 @@ * Manager handling the recording of audio signal. * * @author Alain Pitiot and Sotiri Bakagiannis - * @version 2021.2.0 - * @copyright (c) 2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -17,18 +17,20 @@ import { AudioClip } from "./AudioClip.js"; /** *

    This manager handles the recording of audio signal.

    * - * @name module:sound.Microphone - * @class - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param @param {module:core.Window} options.win - the associated Window - * @param {string} [options.format='audio/webm;codecs=opus'] the format for the audio file - * @param {number} [options.sampleRateHz= 48000] - the audio sampling rate, in Hz - * @param {Clock} [options.clock= undefined] - an optional clock - * @param {boolean} [options.autoLog= false] - whether or not to log + * @extends PsychObject */ export class Microphone extends PsychObject { + /** + * @memberOf module:sound + * @param {Object} options + * @param {module:core.Window} options.win - the associated Window + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {string} [options.format='audio/webm;codecs=opus'] the format for the audio file + * @param {number} [options.sampleRateHz= 48000] - the audio sampling rate, in Hz + * @param {Clock} [options.clock= undefined] - an optional clock + * @param {boolean} [options.autoLog= false] - whether to log + */ constructor({ win, name, format, sampleRateHz, clock, autoLog } = {}) { super(win._psychoJS); @@ -56,8 +58,6 @@ export class Microphone extends PsychObject *

    Note that it typically takes 50ms-200ms for the recording to actually starts once * a request to start has been submitted.

    * - * @name module:sound.Microphone#start - * @public * @return {Promise} promise fulfilled when the recording actually started */ start() @@ -108,8 +108,6 @@ export class Microphone extends PsychObject /** * Submit a request to stop the recording. * - * @name module:sound.Microphone#stop - * @public * @param {Object} options * @param {string} [options.filename] the name of the file to which the audio recording will be * saved @@ -145,8 +143,6 @@ export class Microphone extends PsychObject /** * Submit a request to pause the recording. * - * @name module:sound.Microphone#pause - * @public * @return {Promise} promise fulfilled when the recording actually paused */ pause() @@ -192,7 +188,6 @@ export class Microphone extends PsychObject * *

    resume has no effect if the recording was not previously paused.

    * - * @name module:sound.Microphone#resume * @param {Object} options * @param {boolean} [options.clear= false] whether or not to empty the audio buffer before * resuming the recording @@ -245,8 +240,6 @@ export class Microphone extends PsychObject /** * Submit a request to flush the recording. * - * @name module:sound.Microphone#flush - * @public * @return {Promise} promise fulfilled when the data has actually been made available */ flush() @@ -273,9 +266,6 @@ export class Microphone extends PsychObject /** * Offer the audio recording to the participant as a sound file to download. * - * @name module:sound.Microphone#download - * @function - * @public * @param {string} filename the filename */ download(filename = "audio.webm") @@ -293,9 +283,6 @@ export class Microphone extends PsychObject /** * Upload the audio recording to the pavlovia server. * - * @name module:sound.Microphone#upload - * @function - * @public * @param {string} tag an optional tag for the audio file */ async upload({ tag } = {}) @@ -331,9 +318,6 @@ export class Microphone extends PsychObject /** * Get the current audio recording as an AudioClip in the given format. * - * @name module:sound.Microphone#getRecording - * @function - * @public * @param {string} tag an optional tag for the audio clip * @param {boolean} [flush=false] whether or not to first flush the recording */ @@ -361,8 +345,6 @@ export class Microphone extends PsychObject * *

    Changes to the settings require the recording to stop and be re-started.

    * - * @name module:sound.Microphone#_onChange - * @function * @protected */ _onChange() @@ -380,8 +362,6 @@ export class Microphone extends PsychObject /** * Prepare the recording. * - * @name module:sound.Microphone#_prepareRecording - * @function * @protected */ async _prepareRecording() diff --git a/src/sound/Sound.js b/src/sound/Sound.js index 51f1b01b..145eb781 100644 --- a/src/sound/Sound.js +++ b/src/sound/Sound.js @@ -2,9 +2,9 @@ /** * Sound stimulus. * - * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @author Alain Pitiot, Nikita Agafonov + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -35,23 +35,24 @@ import { TrackPlayer } from "./TrackPlayer.js"; * track.setVolume(1.0); * track.play(2); * - * @class * @extends PsychObject - * @param {Object} options - * @param {String} options.name - the name used when logging messages from this stimulus - * @param {module:core.Window} options.win - the associated Window - * @param {number|string} [options.value= 'C'] - the sound value (see above for a full description) - * @param {number} [options.octave= 4] - the octave corresponding to the tone (if applicable) - * @param {number} [options.secs= 0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. - * @param {number} [options.startTime= 0] - start of playback for tracks (in seconds) - * @param {number} [options.stopTime= -1] - end of playback for tracks (in seconds) - * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo - * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped. - * @param {boolean} [options.autoLog= true] whether or not to log */ export class Sound extends PsychObject { + /** + * @param {Object} options + * @param {String} options.name - the name used when logging messages from this stimulus + * @param {module:core.Window} options.win - the associated Window + * @param {number|string} [options.value= 'C'] - the sound value (see above for a full description) + * @param {number} [options.octave= 4] - the octave corresponding to the tone (if applicable) + * @param {number} [options.secs= 0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. + * @param {number} [options.startTime= 0] - start of playback for tracks (in seconds) + * @param {number} [options.stopTime= -1] - end of playback for tracks (in seconds) + * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo + * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played once. If loops == -1, the track or tone will repeat indefinitely until stopped. + * @param {boolean} [options.autoLog= true] whether or not to log + */ constructor({ name, win, @@ -73,7 +74,6 @@ export class Sound extends PsychObject this._player = undefined; this._addAttribute("win", win); - this._addAttribute("value", value); this._addAttribute("octave", octave); this._addAttribute("secs", secs); this._addAttribute("startTime", startTime); @@ -83,8 +83,9 @@ export class Sound extends PsychObject this._addAttribute("loops", loops); this._addAttribute("autoLog", autoLog); - // identify an appropriate player: - this._getPlayer(); + // note: setValue will identify the appropriate SoundPlayer and possibly instantiate it + // consequently _addAtribute("value") needs to be the last one so the other attributes are already set + this._addAttribute("value", value); this.status = PsychoJS.Status.NOT_STARTED; } @@ -95,9 +96,8 @@ export class Sound extends PsychObject *

    Note: Sounds are played independently from the stimuli of the experiments, i.e. the experiment will not stop until the sound is finished playing. * Repeat calls to play may results in the sounds being played on top of each other.

    * - * @public * @param {number} loops how many times to repeat the sound after it plays once. If loops == -1, the sound will repeat indefinitely until stopped. - * @param {boolean} [log= true] whether or not to log + * @param {boolean} [log= true] whether to log */ play(loops, log = true) { @@ -108,9 +108,8 @@ export class Sound extends PsychObject /** * Stop playing the sound immediately. * - * @public * @param {Object} options - * @param {boolean} [options.log= true] - whether or not to log + * @param {boolean} [options.log= true] - whether to log */ stop({ log = true, @@ -123,7 +122,6 @@ export class Sound extends PsychObject /** * Get the duration of the sound, in seconds. * - * @public * @return {number} the duration of the sound, in seconds */ getDuration() @@ -134,10 +132,9 @@ export class Sound extends PsychObject /** * Set the playing volume of the sound. * - * @public * @param {number} volume - the volume (values should be between 0 and 1) * @param {boolean} [mute= false] - whether or not to mute the sound - * @param {boolean} [log= true] - whether of not to log + * @param {boolean} [log= true] - whether to log */ setVolume(volume, mute = false, log = true) { @@ -150,40 +147,108 @@ export class Sound extends PsychObject } /** - * Set the sound value on demand past initialisation. + * Set the sound value. * - * @public * @param {object} sound - a sound instance to replace the current one - * @param {boolean} [log= true] - whether or not to log + * @param {boolean} [log= true] - whether to log */ setSound(sound, log = true) { - if (sound instanceof Sound) + if (!(sound instanceof Sound)) { - this._setAttribute("value", sound.value, log); + throw { + origin: "Sound.setSound", + context: "when setting the sound", + error: "the argument should be an instance of the Sound class.", + }; + } - if (typeof this._player !== "undefined") + this._setAttribute("value", sound.value, log); + + if (typeof this._player !== "undefined") + { + this._player = this._player.constructor.accept(this); + } + + return this; + } + + /** + * Set the sound value. + * + * @param {number|string} [value = "C"] - the sound value + * @param {number} [octave = 4] - the octave corresponding to the tone (if applicable) + * @param {boolean} [log=true] - whether to log + */ + setValue(value = "C", octave = 4, log = true) + { + this._setAttribute("value", value, log); + + const args = { + psychoJS: this._psychoJS, + stereo: this._stereo, + volume: this._volume, + loops: this._loops, + startTime: this._startTime, + stopTime: this._stopTime, + secs: this._secs + } + + let playerArgs = TonePlayer.accept(value, octave); + if (playerArgs) + { + if (this._player instanceof TonePlayer) { - this._player = this._player.constructor.accept(this); + this._player.setTone(value, octave); } + else + { + this._player = new TonePlayer(Object.assign(args, playerArgs)); + } + return; + } - // Be fluent? - return this; + playerArgs = TrackPlayer.accept(this._psychoJS, value); + if (playerArgs) + { + if (this._player instanceof TrackPlayer) + { + this._player.setTrack(value); + } + else + { + this._player = new TrackPlayer(Object.assign(args, playerArgs)); + } + return; + } + + playerArgs = AudioClipPlayer.accept(this._psychoJS, value); + if (playerArgs) + { + if (this._player instanceof AudioClipPlayer) + { + this._player.setAudioClip(value); + } + else + { + this._player = new AudioClipPlayer(Object.assign(args, playerArgs)); + } + return; } throw { - origin: "Sound.setSound", - context: "when replacing the current sound", - error: "invalid input, need an instance of the Sound class.", + origin: "Sound.setValue", + context: "when setting the sound value", + error: "could not find an appropriate player.", }; + } /** * Set the number of loops. * - * @public * @param {number} [loops=0] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. - * @param {boolean} [log=true] - whether of not to log + * @param {boolean} [log=true] - whether to log */ setLoops(loops = 0, log = true) { @@ -198,9 +263,8 @@ export class Sound extends PsychObject /** * Set the duration (in seconds) * - * @public * @param {number} [secs=0.5] - duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. - * @param {boolean} [log=true] - whether or not to log + * @param {boolean} [log=true] - whether to log */ setSecs(secs = 0.5, log = true) { diff --git a/src/sound/SoundPlayer.js b/src/sound/SoundPlayer.js index 4ba5bdb3..77d610c4 100644 --- a/src/sound/SoundPlayer.js +++ b/src/sound/SoundPlayer.js @@ -2,8 +2,8 @@ * Sound player interface * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -12,44 +12,23 @@ import { PsychObject } from "../util/PsychObject.js"; /** *

    SoundPlayer is an interface for the sound players, who are responsible for actually playing the sounds, i.e. the tracks or the tones.

    * - * @name module:sound.SoundPlayer * @interface * @extends PsychObject - * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance */ export class SoundPlayer extends PsychObject { - constructor(psychoJS) - { - super(psychoJS); - } - /** - * Determine whether this player can play the given sound. - * - * @name module:sound.SoundPlayer.accept - * @function - * @static - * @public - * @abstract - * @param {module:sound.Sound} - the sound - * @return {Object|undefined} an instance of the SoundPlayer that can play the sound, or undefined if none could be found + * @memberOf module:sound + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance */ - static accept(sound) + constructor(psychoJS) { - throw { - origin: "SoundPlayer.accept", - context: "when evaluating whether this player can play a given sound", - error: "this method is abstract and should not be called.", - }; + super(psychoJS); } /** * Start playing the sound. * - * @name module:sound.SoundPlayer#play - * @function - * @public * @abstract * @param {number} [loops] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ @@ -65,9 +44,6 @@ export class SoundPlayer extends PsychObject /** * Stop playing the sound immediately. * - * @name module:sound.SoundPlayer#stop - * @function - * @public * @abstract */ stop() @@ -82,9 +58,6 @@ export class SoundPlayer extends PsychObject /** * Get the duration of the sound, in seconds. * - * @name module:sound.SoundPlayer#getDuration - * @function - * @public * @abstract */ getDuration() @@ -99,9 +72,6 @@ export class SoundPlayer extends PsychObject /** * Set the duration of the sound, in seconds. * - * @name module:sound.SoundPlayer#setDuration - * @function - * @public * @abstract */ setDuration(duration_s) @@ -116,9 +86,6 @@ export class SoundPlayer extends PsychObject /** * Set the number of loops. * - * @name module:sound.SoundPlayer#setLoops - * @function - * @public * @abstract * @param {number} loops - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ @@ -134,9 +101,6 @@ export class SoundPlayer extends PsychObject /** * Set the volume of the tone. * - * @name module:sound.SoundPlayer#setVolume - * @function - * @public * @abstract * @param {Integer} volume - the volume of the tone * @param {boolean} [mute= false] - whether or not to mute the tone diff --git a/src/sound/SpeechRecognition.js b/src/sound/SpeechRecognition.js new file mode 100644 index 00000000..53dfee3d --- /dev/null +++ b/src/sound/SpeechRecognition.js @@ -0,0 +1,381 @@ +/** + * Manager handling the live transcription of speech into text. + * + * @author Alain Pitiot + * @version 2022.2.3 + * @copyright (c) 2022 Open Science Tools Ltd. (https://opensciencetools.org) + * @license Distributed under the terms of the MIT License + */ + +import {Clock} from "../util/Clock"; +import {PsychObject} from "../util/PsychObject"; +import {PsychoJS} from "../core/PsychoJS"; + + +/** + * Transcript. + */ +export class Transcript +{ + /** + * Object holding a transcription result. + * + * @param {SpeechRecognition} transcriber - the transcriber + * @param {string} text - the transcript + * @param {number} confidence - confidence in the transcript + */ + constructor(transcriber, text = '', confidence = 0.0) + { + // recognised text: + this.text = text; + + // confidence in the recognition: + this.confidence = confidence; + + // time the speech started, relative to the Transcriber clock: + this.speechStart = transcriber._speechStart; + + // time the speech ended, relative to the Transcriber clock: + this.speechEnd = transcriber._speechEnd; + + // time a recognition result was produced, relative to the Transcriber clock: + this.time = transcriber._recognitionTime; + } +} + + +/** + *

    This manager handles the live transcription of speech into text.

    + * + * @extends PsychObject + * @todo deal with alternatives, interim results, and recognition errors + */ +export class SpeechRecognition extends PsychObject +{ + /** + *

    This manager handles the live transcription of speech into text.

    + * + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {String} options.name - the name used when logging messages + * @param {number} [options.bufferSize= 10000] - the maximum size of the circular transcript buffer + * @param {String[]} [options.continuous= true] - whether to continuously recognise + * @param {String[]} [options.lang= 'en-US'] - the spoken language + * @param {String[]} [options.interimResults= false] - whether to make interim results available + * @param {String[]} [options.maxAlternatives= 1] - the maximum number of recognition alternatives + * @param {String[]} [options.tokens= [] ] - the tokens to be recognised. This is experimental technology, not available in all browser. + * @param {Clock} [options.clock= undefined] - an optional clock + * @param {boolean} [options.autoLog= false] - whether to log + * + * @todo deal with alternatives, interim results, and recognition errors + */ + constructor({psychoJS, name, bufferSize, continuous, lang, interimResults, maxAlternatives, tokens, clock, autoLog} = {}) + { + super(psychoJS); + + this._addAttribute('name', name, 'speech recognition'); + this._addAttribute('bufferSize', bufferSize, 10000); + this._addAttribute('continuous', continuous, true, this._onChange); + this._addAttribute('lang', lang, 'en-US', this._onChange); + this._addAttribute('interimResults', interimResults, false, this._onChange); + this._addAttribute('maxAlternatives', maxAlternatives, 1, this._onChange); + this._addAttribute('tokens', tokens, [], this._onChange); + this._addAttribute('clock', clock, new Clock()); + this._addAttribute('autoLog', false, autoLog); + this._addAttribute('status', PsychoJS.Status.NOT_STARTED); + + this._prepareRecognition(); + + if (this._autoLog) + { + this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${this.toString()}`); + } + } + + + /** + * Start the speech recognition process. + * + * @return {Promise} promise fulfilled when the process actually starts + */ + start() + { + if (this._status !== PsychoJS.Status.STARTED) + { + this._psychoJS.logger.debug('request to start the speech recognition process'); + + try + { + if (!this._recognition) + { + throw 'the speech recognition has not been initialised yet, possibly because the participant has not given the authorisation to record audio'; + } + + this._recognition.start(); + + // return a promise, which will be satisfied when the process actually starts, + // which is also when the reset of the clock and the change of status takes place + const self = this; + return new Promise((resolve, reject) => + { + self._startCallback = resolve; + self._errorCallback = reject; + }); + } + catch (error) + { + // TODO Strangely, start sometimes fails with the message that the recognition has already started. It is most probably a bug in the implementation of the Web Speech API. We need to catch this particular error and no throw on this occasion + + this._psychoJS.logger.error('unable to start the speech to text transcription: ' + JSON.stringify(error)); + this._status = PsychoJS.Status.ERROR; + + throw { + origin: 'Transcriber.start', + context: 'when starting the speech to text transcription with transcriber: ' + this._name, + error + }; + } + + } + + } + + + /** + * Stop the speech recognition process. + * + * @return {Promise} promise fulfilled when the process actually stops + */ + stop() + { + if (this._status === PsychoJS.Status.STARTED) + { + this._psychoJS.logger.debug('request to stop the speech recognition process'); + + this._recognition.stop(); + + // return a promise, which will be satisfied when the process actually stops: + const self = this; + return new Promise((resolve, reject) => + { + self._stopCallback = resolve; + self._errorCallback = reject; + }); + } + } + + + /** + * Get the list of transcripts still in the buffer, i.e. those that have not been + * previously cleared by calls to getTranscripts with clear = true. + * + * @param {Object} options + * @param {string[]} [options.transcriptList= []]] - the list of transcripts texts to consider. If transcriptList is empty, we consider all transcripts. + * @param {boolean} [options.clear= false] - whether or not to keep in the buffer the transcripts for a subsequent call to getTranscripts. If a keyList has been given and clear = true, we only remove from the buffer those keys in keyList + * @return {Transcript[]} the list of transcripts still in the buffer + */ + getTranscripts({ + transcriptList = [], + clear = true + } = {}) + { + // if nothing in the buffer, return immediately: + if (this._bufferLength === 0) + { + return []; + } + + // iterate over the buffer, from start to end, and discard the null transcripts (i.e. those + // previously cleared): + const filteredTranscripts = []; + const bufferWrap = (this._bufferLength === this._bufferSize); + let i = bufferWrap ? this._bufferIndex : -1; + do + { + i = (i + 1) % this._bufferSize; + + const transcript = this._circularBuffer[i]; + if (transcript) + { + // if the transcriptList is empty of the transcript text is in the transcriptList: + if (transcriptList.length === 0 || transcriptList.includes(transcript.text)) + { + filteredTranscripts.push(transcript); + + if (clear) + { + this._circularBuffer[i] = null; + } + } + } + } while (i !== this._bufferIndex); + + return filteredTranscripts; + } + + + /** + * Clear all transcripts and resets the circular buffers. + */ + clearTranscripts() + { + // circular buffer of transcripts: + this._circularBuffer = new Array(this._bufferSize); + this._bufferLength = 0; + this._bufferIndex = -1; + } + + + /** + * Callback for changes to the recognition settings. + * + *

    Changes to the recognition settings require the speech recognition process + * to be stopped and be re-started.

    + * + * @protected + */ + _onChange() + { + if (this._status === PsychoJS.Status.STARTED) + { + this.stop(); + } + + this._prepareRecognition(); + + this.start(); + } + + + /** + * Prepare the speech recognition process. + * + * @protected + */ + _prepareRecognition() + { + // setup the circular buffer of transcripts: + this.clearTranscripts(); + + // recognition settings: + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + this._recognition = new SpeechRecognition(); + this._recognition.continuous = this._continuous; + this._recognition.lang = this._lang; + this._recognition.interimResults = this._interimResults; + this._recognition.maxAlternatives = this._maxAlternatives; + + // grammar list with tokens added: + if (Array.isArray(this._tokens) && this._tokens.length > 0) + { + const SpeechGrammarList = window.SpeechGrammarList || window.webkitSpeechGrammarList; + + // note: we accepts JSGF encoded strings, and relative weight indicator between 0.0 and 1.0 + // ref: https://www.w3.org/TR/jsgf/ + const name = "NULL"; + const grammar = `#JSGF V1.0; grammar ${name}; public <${name}> = ${this._tokens.join('|')};` + const grammarList = new SpeechGrammarList(); + grammarList.addFromString(grammar, 1); + this._recognition.grammars = grammarList; + } + + // setup the callbacks: + const self = this; + + // called when the start of a speech is detected: + this._recognition.onspeechstart = (e) => + { + this._currentSpeechStart = this._clock.getTime(); + self._psychoJS.logger.debug('speech started'); + } + + // called when the end of a speech is detected: + this._recognition.onspeechend = () => + { + this._currentSpeechEnd = this._clock.getTime(); + // this._recognition.stop(); + self._psychoJS.logger.debug('speech ended'); + } + + // called when the recognition actually started: + this._recognition.onstart = () => + { + this._clock.reset(); + this._status = PsychoJS.Status.STARTED; + self._psychoJS.logger.debug('speech recognition started'); + + // resolve the SpeechRecognition.start promise, if need be: + if (self._startCallback()) + { + self._startCallback({ + time: self._psychoJS.monotonicClock.getTime() + }); + } + } + + // called whenever stop() or abort() are called: + this._recognition.onend = () => + { + this._status = PsychoJS.Status.STOPPED; + self._psychoJS.logger.debug('speech recognition ended'); + + // resolve the SpeechRecognition.stop promise, if need be: + if (self._stopCallback) + { + self._stopCallback({ + time: self._psychoJS.monotonicClock.getTime() + }); + } + } + + // called whenever a new result is available: + this._recognition.onresult = (event) => + { + this._recognitionTime = this._clock.getTime(); + + // do not process the results if the Recogniser is not STARTED: + if (self._status !== PsychoJS.Status.STARTED) + { + return; + } + + // in continuous recognition mode, we need to get the result at resultIndex, + // otherwise we pick the first result + const resultIndex = (self._continuous) ? event.resultIndex : 0; + + // TODO at the moment we consider only the first alternative: + const alternativeIndex = 0; + + const results = event.results; + const text = results[resultIndex][alternativeIndex].transcript; + const confidence = results[resultIndex][alternativeIndex].confidence; + + // create a new transcript: + const transcript = new Transcript(self, text, confidence); + + // insert it in the circular transcript buffer: + self._bufferIndex = (self._bufferIndex + 1) % self._bufferSize; + self._bufferLength = Math.min(self._bufferLength + 1, self._bufferSize); + self._circularBuffer[self._bufferIndex] = transcript; + + self._psychoJS.logger.debug('speech recognition transcript: ', JSON.stringify(transcript)); + } + + // called upon recognition errors: + this._recognition.onerror = (event) => + { + // lack of speech is not an error: + if (event.error === 'no-speech') + { + return; + } + + self._psychoJS.logger.error('speech recognition error: ', JSON.stringify(event)); + self._status = PsychoJS.Status.ERROR; + } + } + +} + + diff --git a/src/sound/TonePlayer.js b/src/sound/TonePlayer.js index a5521628..75c7d08b 100644 --- a/src/sound/TonePlayer.js +++ b/src/sound/TonePlayer.js @@ -2,8 +2,8 @@ * Tone Player. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ @@ -14,22 +14,25 @@ import { SoundPlayer } from "./SoundPlayer.js"; /** *

    This class handles the playing of tones.

    * - * @name module:sound.TonePlayer - * @class * @extends SoundPlayer - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {number} [options.duration_s= 0.5] - duration of the tone (in seconds). If duration_s == -1, the sound will play indefinitely. - * @param {string|number} [options.note= 'C4'] - note (if string) or frequency (if number) - * @param {number} [options.volume= 1.0] - volume of the tone (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped. */ export class TonePlayer extends SoundPlayer { + /** + *

    This class handles the playing of tones.

    + * + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {number} [options.secs= 0.5] - duration of the tone (in seconds). If secs == -1, the sound will play indefinitely. + * @param {string|number} [options.note= 'C4'] - note (if string) or frequency (if number) + * @param {number} [options.volume= 1.0] - volume of the tone (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the tone after it has played once. If loops == -1, the tone will repeat indefinitely until stopped. + */ constructor({ psychoJS, note = "C4", - duration_s = 0.5, + secs = 0.5, volume = 1.0, loops = 0, soundLibrary = TonePlayer.SoundLibrary.TONE_JS, @@ -39,7 +42,7 @@ export class TonePlayer extends SoundPlayer super(psychoJS); this._addAttribute("note", note); - this._addAttribute("duration_s", duration_s); + this._addAttribute("duration_s", secs); this._addAttribute("volume", volume); this._addAttribute("loops", loops); this._addAttribute("soundLibrary", soundLibrary); @@ -63,29 +66,21 @@ export class TonePlayer extends SoundPlayer *

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

    * - * @name module:sound.TonePlayer.accept - * @function - * @static - * @public - * @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 frequency or note + * @param {number} octave - the octave corresponding to the tone + * @return {Object|boolean} argument needed to instantiate a TonePlayer that can play the given sound + * or false otherwise */ - static accept(sound) + static accept(value, octave) { // 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 { note: value } } // 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(); @@ -97,29 +92,20 @@ 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 { note: note + octave }; } } - // TonePlayer is not an appropriate player for the given sound: - return undefined; + // the value does not seem to correspond to a tone we can play: + return false; } /** * Get the duration of the sound. * - * @name module:sound.TonePlayer#getDuration - * @function - * @public * @return {number} the duration of the sound, in seconds */ getDuration() @@ -130,22 +116,16 @@ export class TonePlayer extends SoundPlayer /** * Set the duration of the tone. * - * @name module:sound.TonePlayer#setDuration - * @function - * @public - * @param {number} duration_s - the duration of the tone (in seconds) If duration_s == -1, the sound will play indefinitely. + * @param {number} secs - the duration of the tone (in seconds) If secs == -1, the sound will play indefinitely. */ - setDuration(duration_s) + setDuration(secs) { - this.duration_s = duration_s; + this.duration_s = secs; } /** * Set the number of loops. * - * @name module:sound.TonePlayer#setLoops - * @function - * @public * @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. */ setLoops(loops) @@ -156,9 +136,6 @@ export class TonePlayer extends SoundPlayer /** * Set the volume of the tone. * - * @name module:sound.TonePlayer#setVolume - * @function - * @public * @param {Integer} volume - the volume of the tone * @param {boolean} [mute= false] - whether or not to mute the tone */ @@ -185,12 +162,26 @@ export class TonePlayer extends SoundPlayer } } + /** + * Set the note for tone. + * + * @param {string|number} value - potential frequency or note + * @param {number} octave - the octave corresponding to the tone + */ + setTone(value = "C", octave = 4) + { + const args = TonePlayer.accept(value, octave); + this._note = args.note; + + if (typeof this._synth !== "undefined") + { + this._synth.setNote(this._note); + } + } + /** * Start playing the sound. * - * @name module:sound.TonePlayer#play - * @function - * @public * @param {boolean} [loops] - how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped. */ play(loops) @@ -254,10 +245,6 @@ export class TonePlayer extends SoundPlayer /** * Stop playing the sound immediately. - * - * @name module:sound.TonePlayer#stop - * @function - * @public */ stop() { @@ -285,8 +272,6 @@ export class TonePlayer extends SoundPlayer *

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

    * - * @name module:sound.TonePlayer._initSoundLibrary - * @function * @protected */ _initSoundLibrary() diff --git a/src/sound/TrackPlayer.js b/src/sound/TrackPlayer.js index a5dcbdaf..dd9774c9 100644 --- a/src/sound/TrackPlayer.js +++ b/src/sound/TrackPlayer.js @@ -2,32 +2,34 @@ * Track Player. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ import { SoundPlayer } from "./SoundPlayer.js"; +import { Howl } from "howler"; /** *

    This class handles the playback of sound tracks.

    * - * @name module:sound.TrackPlayer - * @class * @extends SoundPlayer - * @param {Object} options - * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance - * @param {Object} options.howl - the sound object (see {@link https://howlerjs.com/}) - * @param {number} [options.startTime= 0] - start of playback (in seconds) - * @param {number} [options.stopTime= -1] - end of playback (in seconds) - * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo - * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) - * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played * * @todo stopTime is currently not implemented (tracks will play from startTime to finish) * @todo stereo is currently not implemented */ export class TrackPlayer extends SoundPlayer { + /** + * @memberOf module:sound + * @param {Object} options + * @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance + * @param {Object} options.howl - the sound object (see {@link https://howlerjs.com/}) + * @param {number} [options.startTime= 0] - start of playback (in seconds) + * @param {number} [options.stopTime= -1] - end of playback (in seconds) + * @param {boolean} [options.stereo= true] whether or not to play the sound or track in stereo + * @param {number} [options.volume= 1.0] - volume of the sound (must be between 0 and 1.0) + * @param {number} [options.loops= 0] - how many times to repeat the track or tone after it has played + */ constructor({ psychoJS, howl, @@ -53,46 +55,48 @@ export class TrackPlayer extends SoundPlayer /** * Determine whether this player can play the given sound. * - * @name module:sound.TrackPlayer.accept - * @function - * @static - * @public - * @param {module:sound.Sound} sound - the sound, which should be the name of an audio resource + * @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. + * + * @param {module:core.PsychoJS} psychoJS - the PsychoJS instance + * @param {string} value - the sound value, which should be the name of an audio resource * file - * @return {Object|undefined} an instance of TrackPlayer that can play the given track or undefined otherwise + * @return {Object|boolean} argument needed to instantiate a TrackPlayer that can play the given sound + * or false otherwise */ - static accept(sound) + static accept(psychoJS, value) { - // if the sound's value is a string, we check whether it is the name of a resource: - if (typeof sound.value === "string") + // value should be a string: + if (typeof value === "string") { - const howl = sound.psychoJS.serverManager.getResource(sound.value); + // check whether the value is the name of a resource: + const howl = psychoJS.serverManager.getResource(value); if (typeof howl !== "undefined") { - // build the player: - const player = new TrackPlayer({ - psychoJS: sound.psychoJS, - howl: howl, - startTime: sound.startTime, - stopTime: sound.stopTime, - stereo: sound.stereo, - loops: sound.loops, - volume: sound.volume, - }); - return player; + return { howl }; } } // TonePlayer is not an appropriate player for the given sound: - return undefined; + return false; } /** * Get the duration of the sound, in seconds. * - * @name module:sound.TrackPlayer#getDuration - * @function - * @public * @return {number} the duration of the track, in seconds */ getDuration() @@ -103,9 +107,6 @@ export class TrackPlayer extends SoundPlayer /** * Set the duration of the track. * - * @name module:sound.TrackPlayer#setDuration - * @function - * @public * @param {number} duration_s - the duration of the track in seconds */ setDuration(duration_s) @@ -120,9 +121,6 @@ export class TrackPlayer extends SoundPlayer /** * Set the volume of the tone. * - * @name module:sound.TrackPlayer#setVolume - * @function - * @public * @param {Integer} volume - the volume of the track (must be between 0 and 1.0) * @param {boolean} [mute= false] - whether or not to mute the track */ @@ -137,9 +135,6 @@ export class TrackPlayer extends SoundPlayer /** * Set the number of loops. * - * @name module:sound.TrackPlayer#setLoops - * @function - * @public * @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. */ setLoops(loops) @@ -157,12 +152,39 @@ export class TrackPlayer extends SoundPlayer } } + /** + * Set new track to play. + * + * @param {Object|string} track - a track resource name or Howl object (see {@link https://howlerjs.com/}) + */ + setTrack(track) + { + let newHowl = undefined; + + if (typeof track === "string") + { + newHowl = this.psychoJS.serverManager.getResource(track); + } + 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. * - * @name module:sound.TrackPlayer#play - * @function - * @public * @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 {number} [fadeDuration = 17] - how long should the fading in last in ms */ @@ -201,9 +223,6 @@ export class TrackPlayer extends SoundPlayer /** * Stop playing the sound immediately. * - * @name module:sound.TrackPlayer#stop - * @function - * @public * @param {number} [fadeDuration = 17] - how long should the fading out last in ms */ stop(fadeDuration = 17) diff --git a/src/sound/index.js b/src/sound/index.js index 81637d7c..979ef53e 100644 --- a/src/sound/index.js +++ b/src/sound/index.js @@ -2,8 +2,7 @@ export * from "./Sound.js"; export * from "./SoundPlayer.js"; export * from "./TonePlayer.js"; export * from "./TrackPlayer.js"; - export * from "./AudioClip.js"; export * from "./AudioClipPlayer.js"; export * from "./Microphone.js"; -// export * from './Transcriber.js'; +export * from './SpeechRecognition.js'; diff --git a/src/util/Clock.js b/src/util/Clock.js index 5259a46b..3e92b5da 100644 --- a/src/util/Clock.js +++ b/src/util/Clock.js @@ -2,20 +2,20 @@ * Clock component. * * @author Alain Pitiot - * @version 2021.2.0 - * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2021 Open Science Tools Ltd. (https://opensciencetools.org) + * @version 2022.2.3 + * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org) * @license Distributed under the terms of the MIT License */ /** *

    MonotonicClock offers a convenient way to keep track of time during experiments. An experiment can have as many independent clocks as needed, e.g. one to time responses, another one to keep track of stimuli, etc.

    - * - * @name module:util.MonotonicClock - * @class - * @param {number} [startTime=