diff --git a/README.md b/README.md index d2d083b..bcfeba2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PsychoJs 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. -It is also a git submodule: https://github.com/psychopy/psychopy +It is also a git submodule: [psychopy/psychojs](https://github.com/psychopy/psychojs) ## Motivation @@ -55,4 +55,4 @@ The PsychoJs library is written and maintained by Ilixa Ltd. (http://www.ilixa.c ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. diff --git a/docs/ExperimentHandler.html b/docs/ExperimentHandler.html new file mode 100644 index 0000000..8e27413 --- /dev/null +++ b/docs/ExperimentHandler.html @@ -0,0 +1,999 @@ + + + + + JSDoc: Class: ExperimentHandler + + + + + + + + + + +
+ +

Class: ExperimentHandler

+ + + + + + +
+ +
+ +

ExperimentHandler(attribs)

+ +
Create a new experiment handler. + +

A container class for keeping track of multiple loops/handlers + + Useful for generating a single data file from an experiment with many + different loops (e.g. interleaved staircases or loops within loops + + :usage: + + exp = ExperimentHandler({'name' : 'Face Preference', 'version' = '0.1.0'}) +

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new ExperimentHandler(attribs)

+ + + + + + +
+ Constructor +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
attribs + + +Object + + + + associative array used to store the following parameters: +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + name of the experiment
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(static) Repository

+ + + + +
+ Repositories +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

experimentEnded

+ + + + +
+ Properties +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + +

Methods

+ + + + + + + +

addData()

+ + + + + + +
+ Add the data with a given name to the current experiment. + + Typically the user does not need to use this function; if you added + your data to the loop and had already added the loop to the + experiment then the loop will automatically inform the experiment + that it has received data. + + Multiple data name/value pairs can be added to any given entry of + the data file and is considered part of the same entry until the + nextEntry() call is made. + + e.g.:: + + #add some data for this trial + exp.addData('resp.rt', 0.8) + exp.addData('resp.key', 'k') + #end of trial - move to next line in data output + exp.nextEntry() +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

addLoop()

+ + + + + + +
+ Add a loop such as a :class:`~psychopy.data.TrialHandler` or :class:`~psychopy.data.StairHandler` +Data from this loop will be included in the resulting data files. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

getLoopAttributes(loop)

+ + + + + + +
+ Returns the attribute names and values for the current trial of a particular loop. +Does not return data inputs from the subject, only info relating to the trial +execution. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loop + + +Object + + + + the loop
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

nextEntry()

+ + + + + + +
+ Calling nextEntry indicates to the ExperimentHandler that the +current trial has ended and so further addData() calls correspond +to the next trial. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

removeLoop()

+ + + + + + +
+ Remove this loop from the list of unfinished loops, e.g. when it has completed +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) save()

+ + + + + + +
+ Save the results of the experiment. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/ImageStim_ImageStim.html b/docs/ImageStim_ImageStim.html new file mode 100644 index 0000000..193d729 --- /dev/null +++ b/docs/ImageStim_ImageStim.html @@ -0,0 +1,168 @@ + + + + + JSDoc: Class: ImageStim + + + + + + + + + + +
+ +

Class: ImageStim

+ + + + + + +
+ +
+ +

ImageStim()

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

new ImageStim()

+ + + + + + +
+ Constructor +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/TextStim_TextStim.html b/docs/TextStim_TextStim.html new file mode 100644 index 0000000..c4a2425 --- /dev/null +++ b/docs/TextStim_TextStim.html @@ -0,0 +1,168 @@ + + + + + JSDoc: Class: TextStim + + + + + + + + + + +
+ +

Class: TextStim

+ + + + + + +
+ +
+ +

TextStim()

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

new TextStim()

+ + + + + + +
+ Constructor +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/TonePlayer.html b/docs/TonePlayer.html new file mode 100644 index 0000000..2659379 --- /dev/null +++ b/docs/TonePlayer.html @@ -0,0 +1,179 @@ + + + + + JSDoc: Class: TonePlayer + + + + + + + + + + +
+ +

Class: TonePlayer

+ + + + + + +
+ +
+ +

TonePlayer()

+ +

This class handles the playing of tones

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new TonePlayer()

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

Extends

+ + + + +
    +
  • SoundPlayer
  • +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/core_EventManager.js.html b/docs/core_EventManager.js.html new file mode 100644 index 0000000..2ec6c38 --- /dev/null +++ b/docs/core_EventManager.js.html @@ -0,0 +1,571 @@ + + + + + JSDoc: Source: core/EventManager.js + + + + + + + + + + +
+ +

Source: core/EventManager.js

+ + + + + + +
+
+
/**
+ * Manager handling the keyboard and mouse/touch events.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { MonotonicClock, Clock } from '../util/Clock';
+import { PsychoJS } from './PsychoJS';
+import * as util from '../util/Util';
+
+
+/**
+ * @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 {PsychoJS} options.psychoJS - the PsychoJS instance
+ */
+export class EventManager {
+
+	constructor(psychoJS) {
+		this._psychoJS = psychoJS;
+
+		// populate the reverse pyglet map:
+		for (var keyName in EventManager._pygletMap)
+			EventManager._reversePygletMap[EventManager._pygletMap[keyName]] = keyName;
+
+		// add key listeners:
+		this._keyBuffer = [];
+		this._addKeyListeners();
+
+		// mouse info:
+		// note: (a) clocks are reset on mouse button presses
+		//       (b) the mouse listeners are added to the PIXI renderer, upon the latter's creation (see  Window.js)
+		this._mouseInfo = {
+			pos: [0, 0],
+			wheelRel: [0.0, 0.0],
+			buttons: {
+				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]
+			},
+			// clock reset when mouse is moved:
+			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 {Array.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 {Array.string} the list of keys that were pressed.
+	 */
+	getKeys({
+		keyList = null,
+		timeStamped = false
+	} = {}) {
+		if (keyList != null)
+			keyList = this._pyglet2w3c(keyList);
+
+		let newBuffer = [];
+		let keys = [];
+		for (let i = 0; i < this._keyBuffer.length; ++i) {
+			const key = this._keyBuffer[i];
+			let keyId = null;
+
+			if (keyList != null) {
+				let index = keyList.indexOf(key.code);
+				if (index < 0)
+					index = keyList.indexOf(EventManager._keycodeMap[key.keyCode]);
+				if (index >= 0)
+					keyId = EventManager._reversePygletMap[keyList[index]];
+			}
+			else
+				keyId = EventManager._reversePygletMap[key.code];
+
+			if (keyId != null) {
+				if (timeStamped)
+					keys.push([keyId, key.timestamp]);
+				else
+					keys.push(keyId);
+			}
+			else
+				newBuffer.push(key); // keep key press in buffer
+		}
+
+		this._keyBuffer = newBuffer;
+		return keys;
+	}
+
+
+	/**
+	 * @typedef EventManager.ButtonInfo
+	 * @property {Array.number} pressed - the status of each mouse button [left, center, right]: 1 for pressed, 0 for released
+	 * @property {Array.Clock} clocks - the clocks associated to the mouse buttons, reset whenever the button is pressed
+	 * @property {Array.number} times - the time elapsed since the last rest of the associated clock
+	 */
+	/**
+	 * @typedef EventManager.MouseInfo
+	 * @property {Array.number} pos - the position of the mouse [x, y]
+	 * @property {Array.number} wheelRel - the relative position of the wheel [x, y]
+	 * @property {EventManager.ButtonInfo} buttons - the mouse button info
+	 * @property {Clock} moveClock - the clock that is reset whenever the mouse moves
+	 */
+	/**
+	 * Get the mouse info.
+	 * 
+	 * @name module:core.EventManager#getMouseInfo
+	 * @function
+	 * @public
+	 * @return {EventManager.MouseInfo} the mouse info.
+	 */
+	getMouseInfo() {
+		return this._mouseInfo;
+	}
+	
+
+	/**
+	 * Clear all events from the event buffer.
+	 * 
+	 * @name module:core.EventManager#clearEvents
+	 * @function
+	 * @public
+	 * 
+	 * @todo handle the attribs argument
+	 */
+	clearEvents(attribs) {
+		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) {
+		let self = this;
+
+		const view = renderer.view;
+
+		/*
+		// TEMPORARY DEBUG FOR IPAD/IPHONE:
+		for (let eventName of ['click', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'mouseupoutside', 'pointercancel', 'pointerdown', 'pointermove', 'pointerout', 'pointerover', 'pointertap', 'pointerup', 'pointerupoutside', 'rightclick', 'rightdown', 'rightup', ' rightupoutside', 'tap', 'touchcancel', 'touchend', 'touchendoutside', 'touchmove', 'touchstart'])
+			view.addEventListener(eventName, event => {
+				console.log('event: ' + eventName + ' -> ', event);
+			});
+		*/
+		
+		view.addEventListener("pointerdown", event => {
+			self._mouseInfo.buttons.pressed[event.button] = 1;
+			self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime();
+
+			self._mouseInfo.pos = [event.offsetX, event.offsetY];
+
+			//psychoJS.logging.data("Mouse: " + label + " button down, pos=(" + x + "," + y + ")");
+		}, false);
+
+		view.addEventListener("pointerup", event => {
+			self._mouseInfo.buttons.pressed[event.button] = 0;
+			self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime();
+			self._mouseInfo.pos = [event.offsetX, event.offsetY];
+
+			//psychoJS.logging.data("Mouse: " + label + " button down, pos=(" + x + "," + y + ")");
+		}, false);
+
+		view.addEventListener("pointermove", event => {
+			self._mouseInfo.pos = [event.offsetX, event.offsetY];
+			self._mouseInfo.moveClock.reset();
+		}, false);
+
+		view.addEventListener("wheel", event => {
+			self._mouseInfo.wheelRel[0] += event.deltaX;
+			self._mouseInfo.wheelRel[1] += event.deltaY;
+			
+			//var x = ev.offsetX;
+			//var y = ev.offsetY;
+			//var msg = "Mouse: wheel shift=(" + ev.deltaX + "," + ev.deltaY + "), pos=(" + x + "," + y + ")";
+			//psychoJS.logging.data(msg);
+		}, false);
+		
+	}
+
+
+	/**
+	 * Add key listeners to the document.
+	 * 
+	 * @name module:core.EventManager#_addKeyListeners
+	 * @function
+	 * @private
+	 */
+	_addKeyListeners() {
+		let self = this;
+
+		// add a keydown listener:
+		document.addEventListener("keydown", (e) => {
+			self._keyBuffer.push({
+				code: e.code,
+				key: e.key,
+				keyCode: e.keyCode,
+				timestamp: MonotonicClock.getReferenceTime() / 1000
+			});
+			self._psychoJS.logger.trace('keys pressed : ', util.toString(self._keyBuffer));
+		});
+	}
+
+
+
+	/**
+	 * 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
+	 * @private
+	 * @param {Array.string} keyList - the array of pyglet key names
+	 * @return {Array.string} the w3c keyList
+	 */
+	_pyglet2w3c(pygletKeyList) {
+		let w3cKeyList = [];
+		for (let i = 0; i < pygletKeyList.length; i++) {
+			if (typeof EventManager._pygletMap[pygletKeyList[i]] === 'undefined')
+				w3cKeyList.push(pygletKeyList[i]);
+			else
+				w3cKeyList.push(EventManager._pygletMap[pygletKeyList[i]]);
+		}
+
+		return w3cKeyList;
+	}
+}
+
+
+/**
+ * <p>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.</p>
+ * 
+ * @name module:core.EventManager#_keycodeMap
+ * @readonly
+ * @private
+ * @type {Object.<number,String>}
+ */
+EventManager._keycodeMap = {
+	49: "Digit1",
+	50: "Digit2",
+	51: "Digit3",
+	52: "Digit4",
+	53: "Digit5",
+	54: "Digit6",
+	55: "Digit7",
+	56: "Digit8",
+	57: "Digit9",
+	48: "Digit0",
+	65: "KeyA",
+	66: "KeyB",
+	67: "KeyC",
+	68: "KeyD",
+	69: "KeyE",
+	70: "KeyF",
+	71: "KeyG",
+	72: "KeyH",
+	73: "KeyI",
+	74: "KeyJ",
+	75: "KeyK",
+	76: "KeyL",
+	77: "KeyM",
+	78: "KeyN",
+	79: "KeyO",
+	80: "KeyP",
+	81: "KeyQ",
+	82: "KeyR",
+	83: "KeyS",
+	84: "KeyT",
+	85: "KeyU",
+	86: "KeyV",
+	87: "KeyW",
+	88: "KeyX",
+	89: "KeyY",
+	90: "KeyZ",
+	188: "Comma",
+	190: "Period",
+	186: "Semicolon",
+	222: "Quote",
+	219: "BracketLeft",
+	221: "BracketRight",
+	192: "Backquote",
+	220: "Backslash",
+	189: "Minus",
+	187: "Equal",
+	144: "NumLock",
+	96: "Numpad0",
+	97: "Numpad1",
+	98: "Numpad2",
+	99: "Numpad3",
+	100: "Numpad4",
+	101: "Numpad5",
+	102: "Numpad6",
+	103: "Numpad7",
+	104: "Numpad8",
+	105: "Numpad9",
+	107: "NumpadAdd",
+	194: "NumpadComma",
+	194: "NumpadComma",
+	110: "NumpadDecimal",
+	110: "NumpadDecimal",
+	111: "NumpadDivide",
+	13: "NumpadEnter",
+	12: "NumpadEqual",
+	106: "NumpadMultiply",
+	109: "NumpadSubtract",
+	37: "ArrowLeft",
+	38: "ArrowUp",
+	39: "ArrowRight",
+	40: "ArrowDown",
+	27: "Escape",
+	32: "Space"
+};
+
+
+/**
+ * <p>This map associates pyglet key names to the corresponding W3C KeyboardEvent.codes.
+ * 
+ * @name module:core.EventManager#_pygletMap
+ * @readonly
+ * @private
+ * @type {Object.<String,String>}
+ */
+EventManager._pygletMap = {
+	// writing system keys
+	"grave": "Backquote",
+	"backslash": "Backslash",
+	"backspace": "Backspace",
+	"bracketleft": "BracketLeft",
+	"bracketright": "BracketRight",
+	"comma": "Comma",
+	"0": "Digit0",
+	"1": "Digit1",
+	"2": "Digit2",
+	"3": "Digit3",
+	"4": "Digit4",
+	"5": "Digit5",
+	"6": "Digit6",
+	"7": "Digit7",
+	"8": "Digit8",
+	"9": "Digit9",
+	"equal": "Equal",
+	"a": "KeyA",
+	"b": "KeyB",
+	"c": "KeyC",
+	"d": "KeyD",
+	"e": "KeyE",
+	"f": "KeyF",
+	"g": "KeyG",
+	"h": "KeyH",
+	"i": "KeyI",
+	"j": "KeyJ",
+	"k": "KeyK",
+	"l": "KeyL",
+	"m": "KeyM",
+	"n": "KeyN",
+	"o": "KeyO",
+	"p": "KeyP",
+	"q": "KeyQ",
+	"r": "KeyR",
+	"s": "KeyS",
+	"t": "KeyT",
+	"u": "KeyU",
+	"v": "KeyV",
+	"w": "KeyW",
+	"x": "KeyX",
+	"y": "KeyY",
+	"z": "KeyZ",
+	"minus": "Minus",
+	"period": "Period",
+	"apostrophe": "Quote",
+	"semicolon": "Semicolon",
+	"slash": "Slash",
+
+	// functional keys
+	"escape": "Escape",
+	"loption": "AltLeft",
+	"roption": "AltRight",
+	"capslock": "CapsLock",
+	"lcontrol": "ControlLeft",
+	"rcontrol": "ControlRight",
+	"return": "Enter",
+	"lcommand": "MetaLeft",
+	"rcommand": "MetaRight",
+	"lshift": "ShiftLeft",
+	"rshift": "ShiftRight",
+	"space": "Space",
+	"tab": "Tab",
+
+	// arrowpad
+	"down": "ArrowDown",
+	"left": "ArrowLeft",
+	"right": "ArrowRight",
+	"up": "ArrowUp",
+
+	// numeric pad
+	"num_0": "Numpad0",
+	"num_1": "Numpad1",
+	"num_2": "Numpad2",
+	"num_3": "Numpad3",
+	"num_4": "Numpad4",
+	"num_5": "Numpad5",
+	"num_6": "Numpad6",
+	"num_7": "Numpad7",
+	"num_8": "Numpad8",
+	"num_9": "Numpad9",
+	"num_decimal": "NumpadDecimal",
+	"num_enter": "NumpadEnter",
+	"num_add": "NumpadAdd",
+	"num_subtract": "NumpadSubtract",
+	"num_multiply": "NumpadMultiply",
+	"num_divide": "NumpadDivide",
+	"num_equal": "NumpadEqual",
+	"num_numlock": "NumpadNumlock"
+};
+
+
+/**
+ * <p>This map associates W3C KeyboardEvent.codes to the corresponding pyglet key names.
+ * 
+ * @name module:core.EventManager#_reversePygletMap
+ * @readonly
+ * @private 
+ * @type {Object.<String,String>}
+ */
+EventManager._reversePygletMap = {};
+
+
+/**
+ * @class
+ * 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 {PsychoJS} options.psychoJS - the PsychoJS instance
+ */
+export class BuilderKeyResponse {
+	constructor(psychoJS) {
+		this._psychoJS = psychoJS;
+
+		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.clock = new Clock(); // we'll use this to measure the rt
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_GUI.js.html b/docs/core_GUI.js.html new file mode 100644 index 0000000..47cee65 --- /dev/null +++ b/docs/core_GUI.js.html @@ -0,0 +1,394 @@ + + + + + JSDoc: Source: core/GUI.js + + + + + + + + + + +
+ +

Source: core/GUI.js

+ + + + + + +
+
+
/**
+ * Graphic User Interface
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { PsychoJS } from './PsychoJS';
+import { ServerManager } from './ServerManager';
+import { Scheduler } from '../util/Scheduler';
+import { Clock } from '../util/Clock';
+import * as util from '../util/Util';
+
+
+/**
+ * @class
+ * Graphic User Interface
+ * 
+ * @name module:core.GUI
+ * @class
+ * @param {PsychoJS} psychoJS the PsychoJS instance
+ */
+export class GUI
+{
+
+	get dialogComponent() { return this._dialogComponent; }
+
+	constructor(psychoJS)
+	{
+		this._psychoJS = psychoJS;
+
+		// gui listens to RESOURCE events from the server manager:
+		psychoJS.serverManager.on(ServerManager.Event.RESOURCE, (signal) => { this._onResourceEvents(signal); });
+	}
+
+
+	/**
+	 * <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
+	 * download, and (c) enables the partipant to cancel the experiment.</p>
+	 * 
+	 * <b>Setting experiment values</b>
+	 * <p>DlgFromDict displays an input field for all values in the dictionary.
+	 * It is possible to specify default values e.g.:</p>
+	 * <code>let expName = 'stroop';<br>
+	 * let expInfo = {'participant':'', 'session':'01'};<br>
+	 * psychoJS.schedule(psychoJS.gui.DlgFromDict({dictionary: expInfo, title: expName}));</code>
+	 * <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 {Object} options.dictionary - associative array of values for the participant to set
+	 * @param {String} options.title - name of the project
+	 */
+	DlgFromDict({
+		dictionary,
+		title
+	})
+	{
+		// get info from URL:
+		const infoFromUrl = util.getUrlParameters();
+
+		this._progressMsg = '&nbsp;';
+		this._progressBarMax = 0;
+		this._OkButtonDisabled = true;
+
+
+		// prepare PsychoJS component:
+		this._dialogComponent = {};
+		this._dialogComponent.status = PsychoJS.Status.NOT_STARTED;
+		const dialogClock = new Clock();
+		let self = this;
+		let loop = () => {
+			const t = dialogClock.getTime();
+
+			if (t >= 0.0 && self._dialogComponent.status === PsychoJS.Status.NOT_STARTED) {
+				self._dialogComponent.tStart = t;
+				self._dialogComponent.status = PsychoJS.Status.STARTED;
+				
+				// prepare jquery UI dialog box:
+				let htmlCode = 
+					'<div id="expDialog" title="' + title + '">' + 
+					'<p class="validateTips">Fields marked with an asterisk (*) are required.</p>';
+				for (const key in dictionary)
+				{
+					// only create an input if the key is not in the URL:
+					let inUrl = false;
+					const cleanedDictKey = key.trim().toLowerCase();
+					for (const [urlKey, urlValue] of infoFromUrl) {
+						const cleanedUrlKey = urlKey.trim().toLowerCase();
+						if (cleanedUrlKey == cleanedDictKey) {
+							inUrl = true;
+							break;
+						}
+					}
+
+					if (!inUrl) {
+						htmlCode = htmlCode + 
+						'<label for="' + key + '">' + key + '</label>' +
+						'<input type="text" name="' + key + '" id="' + key + '_id" value="' + dictionary[key] + '" class="text ui-widget-content ui-corner-all">';
+					}
+				}
+				htmlCode = htmlCode + '<hr><div id="progressMsg" class="progress">' + self._progressMsg + '</div>';
+				htmlCode = htmlCode + '<div id="progressbar"></div></div>';
+				let dialogElement = document.getElementById('root');
+				dialogElement.innerHTML = htmlCode;
+				
+				// init and open dialog box:
+				self._dialogComponent.button = 'Cancel';
+				$("#expDialog").dialog({
+					width: 400,
+					modal: true,
+					closeOnEscape: false,
+					buttons: [
+						{
+							id: "buttonOk",
+							text: "Ok",
+							disabled: self._OkButtonDisabled,
+							click: function() {
+								
+								// update dictionary:
+								for (const key in dictionary) {
+									const input = document.getElementById(key + "_id");
+									if (input)
+										dictionary[key] = input.value;
+								}
+								
+								self._dialogComponent.button = 'OK';
+								$("#expDialog").dialog( "close" );
+
+								// switch to full screen if requested:
+								self._psychoJS.window.adjustScreenSize();
+							}
+						},
+						{
+							id: "buttonCancel",
+							text: "Cancel",
+							click: function() {
+								self._dialogComponent.button = 'Cancel';
+								$("#expDialog").dialog( "close" );
+							}
+						}
+					],
+					// close is called by both buttons and when the user clicks on the cross:
+					close : function() {
+						//$.unblockUI();
+						self._dialogComponent.status = PsychoJS.Status.FINISHED;
+					}
+				})
+				// change colour of title bar
+				.prev(".ui-dialog-titlebar").css("background", "green");
+
+				// 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!
+				//$.blockUI({ message: "", baseZ: 1});
+				
+				// show dialog box:
+				$("#expDialog").dialog("open");
+				$("#progressbar").progressbar({value: self._progressBarCurrentIncrement});
+				$("#progressbar").progressbar("option", "max", self._progressBarMax);
+			}
+
+			if (self._dialogComponent.status === PsychoJS.Status.FINISHED)
+				return Scheduler.Event.NEXT;
+			else
+				return Scheduler.Event.FLIP_REPEAT;
+		}
+
+		return loop;
+	}
+
+
+	/**
+	 * Listener for resource event from the [Server Manager]{@link ServerManager}.
+	 * 
+	 * @name module:core.GUI#_onResourceEvents
+	 * @function
+	 * @private
+	 * @param {Object.<string, string|Symbol>} signal the signal
+	 */
+	_onResourceEvents(signal) {
+		let response = { origin: 'GUI._onResourceEvents', context: 'when handling a resource event' };
+
+		this._psychoJS.logger.debug('signal: ' + util.toString(signal));
+
+		// all resources have been registered:
+		if (signal.message === ServerManager.Event.RESOURCES_REGISTERED) {
+			// for each resource, we have a 'downloading resource' and a 'resource downloaded' message:
+			this._progressBarMax = signal.count * 2;
+			$("#progressbar").progressbar("option", "max", this._progressBarMax);
+			
+			this._progressBarCurrentIncrement = 0;
+			$("#progressMsg").text('all resources registered.');
+		}
+		
+		// all the resources have been downloaded: show the ok button 
+		else if (signal.message === ServerManager.Event.DOWNLOAD_COMPLETED) {
+			this._OkButtonDisabled = false;
+			$("#buttonOk").button({ disabled: this._OkButtonDisabled });
+			$("#progressMsg").text('all resources downloaded.');
+		}
+		
+		// update progress bar:
+		else if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE || signal.message === ServerManager.Event.RESOURCE_DOWNLOADED)
+		{
+			if (typeof this._progressBarCurrentIncrement === 'undefined')
+				this._progressBarCurrentIncrement = 0;
+
+			if (signal.message === ServerManager.Event.DOWNLOADING_RESOURCE)
+				$("#progressMsg").text(signal.resource + ': downloading...');
+			else
+				$("#progressMsg").text(signal.resource + ': downloaded.');
+
+			++ this._progressBarCurrentIncrement;
+			$("#progressbar").progressbar("option", "value", this._progressBarCurrentIncrement);
+		}
+
+		// unknown message: we just display it
+		else
+			$("#progressMsg").text(signal.message);
+	}
+
+
+	/**
+	 * @callback GUI.onOK
+	 */
+	/**
+	 * Show a message to the participant in a dialog box.
+	 * 
+	 * <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
+	 * @param {string} options.warning - a warning message
+	 * @param {boolean} [options.showOK=true] - specifies whether to show the OK button
+	 * @param {GUI.onOK} [options.onOK] - function called when the participant presses the OK button
+	 */
+	dialog({
+		message,
+		warning,
+		error,
+		showOK = true,
+		onOK
+	} = {}) {
+		// destroy previous dialog box:
+		this.destroyDialog();
+
+		// we are displaying an error:
+		if (typeof error !== 'undefined') {
+			this._psychoJS.logger.fatal(util.toString(error));
+
+			var htmlCode = '<div id="msgDialog" title="Error">';
+			htmlCode += '<p class="validateTips">Unfortunately we encountered an error:</p>';
+			
+			// go through the error stack:
+			htmlCode += '<ul>';
+			while (true) {
+				if (typeof error === 'object' && 'context' in error) {
+					htmlCode += '<li>' + error.context + '</li>';
+					error = error.error;
+				} else {
+					htmlCode += '<li><b>' + error  + '</b></li>';
+					break;
+				}		
+			}
+			htmlCode += '</ul>';
+
+			htmlCode += '<p>Try to run the experiment again. If the error persists, contact the experimenter.</p>';
+			var titleColour = 'red';
+		}
+
+		// we are displaying a message:
+		else if (typeof message !== 'undefined') {
+			htmlCode = '<div id="msgDialog" title="Message">'
+				+ '<p class="validateTips">' + message + '</p>';
+			titleColour = 'green';
+		}
+
+		// we are displaying a warning:
+		else if (typeof warning !== 'undefined') {
+			htmlCode = '<div id="msgDialog" title="Warning">'
+				+ '<p class="validateTips">' + warning + '</p>';
+			titleColour = 'orange';
+		}
+
+		htmlCode = htmlCode + '</div>';
+		var dialogElement = document.getElementById('root');
+		dialogElement.innerHTML = htmlCode;
+		
+		// init dialog box:
+		$("#msgDialog").dialog({dialogClass: 'no-close', width: '80%', modal: true, closeOnEscape: false})
+		// change colour of title bar
+		.prev(".ui-dialog-titlebar").css("background", titleColour);
+		
+		// add OK button if need be:
+		if (showOK) {
+			$("#msgDialog").dialog("option", "buttons", [
+				{
+					id: "buttonOk",
+					text: "Ok",
+					click: function() {
+						$(this).dialog("close");
+						
+						// execute callback function:
+						if (typeof onOK !== 'undefined')
+							onOK();
+					}
+				}]);
+		}
+
+		// show dialog box:
+		$("#msgDialog").dialog("open");
+	}
+
+
+	/**
+	 * Destroy the currently opened dialog box.
+	 * 
+	 * @name module:core.GUI#dialog
+	 * @function
+	 * @public
+	 */
+	destroyDialog()
+	{
+		if ($("#expDialog").length) {
+			$("#expDialog").dialog("destroy");
+		}
+		if ($("#msgDialog").length) {
+			$("#msgDialog").dialog("destroy");
+		}
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_MinimalStim.js.html b/docs/core_MinimalStim.js.html new file mode 100644 index 0000000..12625f6 --- /dev/null +++ b/docs/core_MinimalStim.js.html @@ -0,0 +1,200 @@ + + + + + JSDoc: Source: core/MinimalStim.js + + + + + + + + + + +
+ +

Source: core/MinimalStim.js

+ + + + + + +
+
+
/**
+ * Base class for all stimuli.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { PsychObject } from '../util/PsychObject';
+import { PsychoJS } from './PsychoJS';
+
+
+/**
+ * <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 {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= false] - whether or not to log
+ */
+export class MinimalStim extends PsychObject {
+
+	constructor({
+		name,
+		win,
+		autoDraw = false,
+		autoLog = false
+	} = {}) {
+		super(win._psychoJS, name);
+
+		// the PIXI representation of the stimulus:
+		this._pixi = undefined;
+
+		this._addAttributes(MinimalStim, win, autoDraw, autoLog);
+
+		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
+	 */
+	setAutoDraw(autoDraw, log = false) {
+		let errorPrefix = { origin : 'MinimalStim.setAutoDraw', context: 'when setting the autoDraw attribute of stimulus: ' + this._name };
+
+		this._setAttribute('autoDraw', autoDraw, log);
+
+		const index = this.win._drawList.indexOf(this);
+
+		// autoDraw = true: add the stimulus to the draw list if it's not there already
+		if (this._autoDraw) {
+			if (this.win) {
+				// if the stimilus is not already in the draw list:
+				if (index < 0) {
+					// update the stimulus if need be before we add its PIXI representation to the window container:
+					this._updateIfNeeded();
+					if (typeof this._pixi === 'undefined')
+						throw {...errorPrefix, error: 'the PIXI representation of the stimulus is unavailable'};
+
+					this.win._rootContainer.addChild(this._pixi);
+					this.win._drawList.push(this);
+				} else
+				{
+					// 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) {
+						this.win._rootContainer.removeChild(this._pixi);
+						this._updateIfNeeded();
+						this.win._rootContainer.addChild(this._pixi);
+					}
+				}
+			}
+
+			this.status = PsychoJS.Status.STARTED;
+		}
+
+		// autoDraw = false: remove the stimulus from the draw list and window container if it's already there
+		else {
+			if (this.win) {
+				// if the stimulus is in the draw list, remove it from the list and from the window container:
+				if (index >= 0) {
+					this.win._drawList.splice(index, 1);
+					this.win._rootContainer.removeChild(this._pixi);
+				}
+			}
+
+			this.status = PsychoJS.Status.STOPPED;
+		}
+	}
+
+
+	/**
+	 * Draw this stimulus on the next frame draw.
+	 * 
+	 * @name module:core.MinimalStim#draw
+	 * @function
+	 * @public
+	 */
+	draw() {
+		this._updateIfNeeded();
+
+		if (this.win && this.win._drawList.indexOf(this) < 0) {
+			this.win._container.addChild(this._pixi);
+			this.win._drawList.push(this);
+		}
+	}
+
+
+	/**
+	 * 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', context: 'when determining whether stimulus: ' + this._name + 'contains object: ' + util.toString(object), error: 'this method is abstract and should not be called.'};
+	}
+
+
+	/**
+	 * Update the stimulus, if necessary.
+	 *
+	 * Note: this is an abstract function, which should not be called.
+	 * 
+	 * @name module:core.MinimalStim#_updateIfNeeded
+	 * @function
+	 * @abstract
+	 * @private
+	 */
+	_updateIfNeeded() {
+		throw {origin: 'MinimalStim._updateIfNeeded', context: 'when updating stimulus: ' + this._name, error: 'this method is abstract and should not be called.'};
+	}
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_Mouse.js.html b/docs/core_Mouse.js.html new file mode 100644 index 0000000..5d78f08 --- /dev/null +++ b/docs/core_Mouse.js.html @@ -0,0 +1,311 @@ + + + + + JSDoc: Source: core/Mouse.js + + + + + + + + + + +
+ +

Source: core/Mouse.js

+ + + + + + +
+
+
/**
+ * Manager responsible for the interactions between the experiment's stimuli and the mouse.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { PsychoJS } from './PsychoJS';
+import { PsychObject } from '../util/PsychObject';
+import * as util from '../util/Util';
+
+
+/**
+ * <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 {
+
+	constructor({
+		name,
+		win,
+		autoLog = true
+	} = {}) {
+		super(win._psychoJS, name);
+
+		// note: those are in window units:
+		this._lastPos = undefined;
+		this._prevPos = undefined; // used for motion detection and timing
+		this._movedistance = 0.0;
+
+		const units = win.units;
+		const visible = 1;
+		this._addAttributes(Mouse, win, units, visible, 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() {
+		// get mouse position in the canvas:
+		const mouseInfo = this.psychoJS.eventManager.getMouseInfo();
+		let pos_px = mouseInfo.pos.slice();
+
+		// convert to the associated window's reference frame with (0,0) as the centre of the window:
+		pos_px[0] = pos_px[0] - this.win.size[0] / 2;
+		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);
+
+		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')
+			return this.getPos();
+		else {
+			// note: (this.getPos()-lastPos) would not work here since getPos changes this._lastPos
+			const lastPos = this._lastPos;
+			const pos = this.getPos();
+			return [-lastPos[0] + pos[0], -lastPos[1] + pos[1]];
+		}
+	}
+
+
+	/**
+	 * 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()
+	{
+		const mouseInfo = this.psychoJS.eventManager.getMouseInfo();
+		const wheelRel_px = mouseInfo.wheelRel.slice();
+
+		// convert to window units:
+		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.
+	 */
+	getPressed(getTime = false) {
+		const buttonPressed = this.psychoJS.eventManager.getMouseInfo().buttons.pressed.slice();
+		if (!getTime)
+			return buttonPressed;
+		else {
+			const buttonTimes = this.psychoJS.eventManager.getMouseInfo().buttons.times.slice();
+			return [buttonPressed, buttonTimes];
+		}
+	}
+
+
+	/**
+	 * Determine whether the mouse has moved beyond a certain distance.
+	 * 
+	 * <p><b>distance</b>
+	 * <ul>
+	 * <li>mouseMoved() or mouseMoved(undefined, false): determine whether the mouse has moved at all since the last
+	 * call to [getPos]{@link module:core.Mouse#getPos}</li>
+	 * <li>mouseMoved(distance: number, false): determine whether the mouse has travelled further than distance, in terms of line of sight</li>
+	 * <li>mouseMoved(distance: [number,number], false): determine whether the mouse has travelled horizontally or vertically further then the given horizontal and vertical distances</li>
+	 * </ul></p>
+	 * 
+	 * <p><b>reset</b>
+	 * <ul>
+	 * <li>mouseMoved(distance, true): reset the mouse move clock, return false</li>
+	 * <li>mouseMoved(distance, 'here'): return false</li>
+	 * <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
+	 */
+	mouseMoved(distance, reset = false) {
+		// make sure that _lastPos is defined:
+		if (typeof this._lastPos === 'undefined')
+			this.getPos();
+		this._prevPos = this._lastPos.slice();
+		this.getPos();
+
+		if (typeof reset === 'boolean' && reset == false) {
+			if (typeof distance === 'undefined')
+				return (this._prevPos[0] != this._lastPos[0]) || (this._prevPos[1] != this._lastPos[1]);
+			else {
+				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]));
+					return (this._movedistance > distance);
+				}
+				if (this._prevPos[0] + distance[0] - this._lastPos[0] > 0.0)
+					return true; // moved on X-axis
+				if (this._prevPos[1] + distance[1] - this._lastPos[0] > 0.0)
+					return true; // moved on Y-axis
+				return false;
+			}
+		}
+
+		else if (typeof reset === 'boolean' && reset == true) {
+			// reset the moveClock:
+			this.psychoJS.eventManager.getMouseInfo().moveClock.reset();
+			return false;
+		}
+
+		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
+			// reset to (x,y) to check movement from there
+			this._prevPos = reset.slice();
+			if (!distance)
+				return false; // just resetting prevPos, not checking distance
+			else {
+				// checking distance of current pos to newly reset prevposition
+				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]));
+					return (this._movedistance > distance);
+				}
+
+				if (Math.abs(this._lastPos[0] - this._prevPos[0]) > distance[0])
+					return true;  // moved on X-axis
+				if (Math.abs(this._lastPos[1] - this._prevPos[1]) > distance[1])
+					return true;  // 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() {
+		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]) {
+		const mouseInfo = this.psychoJS.eventManager.getMouseInfo();
+		for (const b of buttons) {
+			mouseInfo.buttons.clocks[b].reset();
+			mouseInfo.buttons.times[b] = 0.0;
+		}
+	}
+
+
+}
+
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_PsychoJS.js.html b/docs/core_PsychoJS.js.html new file mode 100644 index 0000000..29051d7 --- /dev/null +++ b/docs/core_PsychoJS.js.html @@ -0,0 +1,483 @@ + + + + + JSDoc: Source: core/PsychoJS.js + + + + + + + + + + +
+ +

Source: core/PsychoJS.js

+ + + + + + +
+
+
/** @module core */
+/**
+ * Main component of the PsychoJS library.
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+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 '../util/Logger';
+import * as util from '../util/Util';
+
+
+/**
+ * <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
+ */
+export class PsychoJS {
+	/**
+	 * Properties
+	 */
+	get status() { return this._status; }
+	set status(sts) {
+		this._status = sts;
+	}
+	get config() { return this._config; }
+	get window() { return this._window; }
+	get serverManager() { return this._serverManager; }
+	get experiment() { return this._experiment; }
+	get scheduler() { return this._scheduler; }
+	get monotonicClock() { return this._monotonicClock; }
+	get logger() { return this._logger.consoleLogger; }
+	get eventManager() { return this._eventManager; }
+	get gui() { return this._gui; }
+	get IP() { return this._IP; }
+
+
+	/**
+	 * @constructor
+	 * @public
+	 */
+	constructor({
+		debug = true,
+		collectIP = false
+	} = {}) {
+		// logging:
+		this._logger = new Logger((debug) ? log4javascript.Level.DEBUG : log4javascript.Level.INFO);
+		this._captureErrors();
+
+		// core clock:
+		this._monotonicClock = new MonotonicClock();
+
+		// managers:
+		this._eventManager = new EventManager(this);
+		this._serverManager = new ServerManager({
+			psychoJS: this
+		});
+
+		// GUI:
+		this._gui = new GUI(this);
+
+		// IP:
+		this._collectIP = collectIP;
+
+		// main scheduler:
+		this._scheduler = new Scheduler(this);
+
+		// Window:
+		this._window = undefined;
+
+		// redirection URLs:
+		this._cancellationUrl = undefined;
+		this._completionUrl = undefined;
+
+
+		// status:
+		this._status = PsychoJS.Status.NOT_CONFIGURED;
+
+		this.logger.info('[PsychoJS] Initialised.');
+	}
+
+
+	/**
+	 * Open a PsychoJS Window.
+	 * 
+	 * <p>This opens a PIXI canvas.</p>
+	 * <p>Note: we can only open one window.</p>
+	 * 
+	 * @param {Object} options
+	 * @param {string} [options.name] the name of the window
+	 * @param {boolean} [options.fullscr] whether or not to go fullscreen
+	 * @param {Color} [options.color] the background color of the window
+	 * @param {string} [options.units] the units of the window
+	 * @param {boolean} [options.autoLog] whether of not to log
+	 * @throws {Object.<string, *>} exception if a window has already been opened
+	 * 
+	 * @public
+	 */
+	openWindow({
+		name,
+		fullscr,
+		color,
+		units,
+		autoLog
+	} = {}) {
+		this.logger.info('[PsychoJS] Open Window.');
+
+		if (typeof this._window !== 'undefined')
+			throw { origin : 'PsychoJS.openWindow', context : 'when opening a Window', error : 'A Window has already been opened.' };
+
+		this._window = new Window({
+			psychoJS: this,
+			name,
+			fullscr,
+			color,
+			units,
+			autoLog
+		});
+	}
+
+	/**
+	 * Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment.
+	 * 
+	 * @param {string} completionUrl  - the completion URL
+	 * @param {string} cancellationUrl - the cancellation URL
+	 */
+	setRedirectUrls(completionUrl, cancellationUrl) {
+		this._completionUrl = completionUrl;
+		this._cancellationUrl = cancellationUrl;
+	}
+
+	/**
+	 * Schedule a task.
+	 * 
+	 * @param task - the task to be scheduled
+	 * @param args - arguments for that task
+	 * @public
+	 */
+	schedule(task, args) {
+		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
+	 */
+	/**
+	 * 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
+	 */
+	scheduleCondition(condition, thenScheduler, elseScheduler) {
+		this.logger.debug('schedule condition: ', condition.toString().substring(0, 50), '...');
+
+		this._scheduler.addConditional(condition, thenScheduler, elseScheduler);
+	}
+
+
+	/**
+	 * Start the experiment.
+	 * 
+	 * @param {Object} options
+	 * @param {string} [options.configURL=config.json] - the URL of the configuration file
+	 * @param {Object.<string, *>} [options.expInfo] - additional information about the experiment
+	 * @async
+	 * @public
+	 */
+	async start({ configURL = 'config.json', expInfo } = {}) {
+		this.logger.debug();
+
+		let response = { origin: 'PsychoJS.start', context: 'when starting the experiment' };
+
+		try {
+			// configure the experiment:
+			await this._configure(configURL);
+
+			// get the participant IP:
+			if (this._collectIP) {
+				// get IP info of participant
+				// note: since we make a GET call to http://ipinfo.io to get the IP info,
+				// it will not be immediately available.
+				this._getParticipantIPInfo();
+			} else {
+				this._IP = {};
+				this._IP['IP'] = 'X';
+				this._IP['hostname'] = 'X';
+				this._IP['city'] = 'X';
+				this._IP['region'] = 'X';
+				this._IP['country'] = 'X';
+				this._IP['location'] = 'X';
+			}
+
+			// setup the experiment handler:
+			this._experiment = new ExperimentHandler({
+				psychoJS: this,
+				extraInfo: expInfo
+			});
+
+			// setup the logger:
+			//my.logger.console.setLevel(psychoJS.logging.WARNING);
+			//my.logger.server.set({'level':psychoJS.logging.WARNING, 'experimentInfo': my.expInfo});
+
+			// open a new session:
+			await this._serverManager.openSession();
+
+			// start the asynchronous download of resources:
+			this._serverManager.downloadResources();
+
+			// start the experiment:
+			this.logger.info('[PsychoJS] Start Experiment.');
+			this._scheduler.start();
+		}
+		catch (error) {
+			this._gui.dialog({ error: { ...response, error } });
+		}
+	}
+
+
+	/**
+	 * Make the attributes of the given object those of PsychoJS and those of
+	 * the top level variable (e.g. window) as well.
+	 * 
+	 * @param {Object.<string, *>} obj the object whose attributes we will mirror
+	 * @public
+	 */
+	importAttributes(obj) {
+		this.logger.debug('import attributes from: ', util.toString(obj));
+
+		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.
+	 * 
+	 * <p>Note: if the resource manager is busy, we inform the participant
+	 * that he or she needs to wait for a bit.</p>
+	 * 
+	 * @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
+	 */
+	async quit({ message, isCompleted = false } = {}) {
+		this.logger.info('[PsychoJS] Quit.');
+
+		try {
+			// save the results and the logs of the experiment:
+			this.gui.dialog({ warning: 'Saving the experiment results and closing the session. Please wait a few moments.', showOK: false });
+			await this._experiment.save();
+
+			// close the session:
+			await this._serverManager.closeSession();
+
+			// stop the main scheduler:
+			this._scheduler.stop();
+
+			// thank participant for waiting and quit or redirect:
+			let text = 'Thank you for your patience. The data have been saved.<br/><br/>';
+			text += (typeof message !== 'undefined') ? message : 'Goodbye!';
+			const self = this;
+			this._gui.dialog({
+				message: text, onOK: () => {
+					// close the window:
+					self._window.close();
+
+					// destroy dialog boxes:
+					self._gui.destroyDialog();
+
+					// remove everything from the browser window:
+					while (document.body.hasChildNodes())
+						document.body.removeChild(document.body.lastChild);
+
+					// 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;
+				}
+			});
+
+		}
+		catch (error) {
+			console.error(error);
+			this._gui.dialog({ "error": error });
+		}
+	}
+
+
+	/**
+	 * Configure PsychoJS for the running experiment.
+	 * 
+	 * @async
+	 * @protected
+	 * @param {string} configURL - the URL of the configuration file
+	 */
+	async _configure(configURL) {
+		let response = { origin: 'PsychoJS.configure', context: 'when configuring PsychoJS for the experiment' };
+
+		try {
+			this.status = PsychoJS.Status.CONFIGURING;
+			const response = await this._serverManager.getConfiguration(configURL);
+			this.status = PsychoJS.Status.CONFIGURED;
+			this._config = response.config;
+
+			this.logger.debug('configuration:', util.toString(response.config));
+
+			// tests for the presence of essential blocks in the configuration:
+			if (!('experiment' in this._config))
+				throw 'missing experiment block in configuration';
+			if (!('name' in this._config.experiment))
+				throw 'missing name in experiment block in configuration';
+			if (!('fullpath' in this._config.experiment))
+				throw 'missing fullpath in experiment block in configuration';
+			if (!('psychoJsManager' in this._config))
+				throw 'missing psychoJsManager block in configuration';
+			if (!('URL' in this._config.psychoJsManager))
+				throw 'missing URL in psychoJsManager block in configuration';
+
+			// 'CSV' is the default format for the experiment results:
+			if ('saveFormat' in this._config.experiment)
+				this._config.experiment.saveFormat = Symbol.for(this._config.experiment.saveFormat);
+			else
+				this._config.experiment.saveFormat = ExperimentHandler.SaveFormat.CSV;
+
+			return response;
+		}
+		catch (error) {
+			throw { ...response, error };
+		}
+	}
+
+
+	/**
+	 * Get the IP information of the participant, asynchronously.
+	 *
+	 * <p>Note: we use [http://www.geoplugin.net/json.gp]{@link http://www.geoplugin.net/json.gp}.</p>
+	 * @protected
+	 */
+	async _getParticipantIPInfo() {
+		let response = { origin: 'PsychoJS._getParticipantIPInfo', context: 'when get the IP information of the participant' };
+
+		this.logger.debug('getting the IP information of the participant');
+
+		this._IP = {};
+
+		let self = this;
+		try {
+			const geoResponse = await $.get('http://www.geoplugin.net/json.gp');
+			const geoData = JSON.parse(geoResponse);
+			self._IP['IP'] = geoData.geoplugin_request;
+			self._IP['country'] = geoData.geoplugin_countryName;
+			self._IP['latitude'] = geoData.geoplugin_latitude;
+			self._IP['longitude'] = geoData.geoplugin_longitude;
+			self.logger.debug('IP information of the participant: ' + util.toString(self._IP));
+		}
+		catch (error) {
+			throw { ...response, error };
+		}
+	}
+
+
+	/**
+	 * Capture all errors and display them in a pop-up error box.
+	 * 
+	 * @protected
+	 */
+	_captureErrors() {
+		this.logger.debug('capturing all errors using window.onerror');
+
+		const self = this;
+		window.onerror = function (message, source, lineno, colno, error) {
+			console.error(error);
+			self._gui.dialog({ "error": error });
+			return true;
+		}
+
+		/* NOT UNIVERSALLY SUPPORTED YET
+		window.addEventListener('unhandledrejection', event => {
+			console.error(error);
+			self._gui.dialog({"error" : error});
+			return true;
+		});*/
+
+	}
+
+}
+
+/**
+ * PsychoJS status
+ * 
+ * @enum {Symbol}
+ * @readonly
+ * @public
+ */
+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'),
+	STOPPED: Symbol.for('STOPPED'),
+	FINISHED: Symbol.for('FINISHED')
+};
+
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_ServerManager.js.html b/docs/core_ServerManager.js.html new file mode 100644 index 0000000..159e830 --- /dev/null +++ b/docs/core_ServerManager.js.html @@ -0,0 +1,626 @@ + + + + + JSDoc: Source: core/ServerManager.js + + + + + + + + + + +
+ +

Source: core/ServerManager.js

+ + + + + + +
+
+
/**
+ * Manager responsible for the communication between the experiment running in the participant's browser and the remote PsychoJS manager running on the remote https://pavlovia.org server.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @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 { Howl } from 'howler';
+
+
+
+/**
+ * <p>This manager handles all communications between the experiment running in the participant's browser and the remote PsychoJS manager running on 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 and log.</p>
+ * <p>Note: The Server Manager uses [Promises]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise} to deal with asynchronicity, is mostly called by {@link PsychoJS}, and is not exposed to the experiment code.</p>
+ * 
+ * @name module:core.ServerManager
+ * @class
+ * @extends PsychObject
+ * @param {Object} options
+ * @param {PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {boolean} [options.autoLog= false] - whether or not to log
+ */
+export class ServerManager extends PsychObject {
+	
+	constructor({
+		psychoJS,
+		autoLog = false
+	} = {}) {
+		super(psychoJS);
+
+		// session:
+		this._session = {};
+
+		// resources:
+		this._resourceDirectory = undefined;
+		this._resourcesMap = new Map();
+		this._nbResources = -1;
+
+		// howler.js' howls:
+		this._howls = undefined;
+
+		this._addAttributes(ServerManager, autoLog);
+		this._addAttribute('status', ServerManager.Status.READY);
+	}
+
+
+	/**
+	 * @typedef ServerManager.GetConfigurationPromise
+	 * @property {string} origin the calling method
+	 * @property {string} context the context
+	 * @property {Object.<string, *>} [config] the configuration
+	 * @property {Object.<string, *>} [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<ServerManager.GetConfigurationPromise>} the response
+	 */
+	getConfiguration(configURL) {
+		let response = { origin: 'ServerManager.getConfiguration', context: 'when reading the configuration file: ' + configURL };
+
+		this._psychoJS.logger.debug('reading the configuration file: ' + configURL);
+		return new Promise((resolve, reject) => {
+			$.get(configURL, 'json')
+				.done((config, textStatus) => {
+					resolve({ ...response, config });
+				})
+				.fail((jqXHR, textStatus, errorThrown) => {
+					reject({ ...response, error: errorThrown });
+				});
+		});
+	}
+
+
+	/**
+	 * @typedef ServerManager.OpenSessionPromise
+	 * @property {string} origin the calling method
+	 * @property {string} context the context
+	 * @property {string} [token] the session token
+	 * @property {Object.<string, *>} [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
+	 * @returns {Promise<ServerManager.OpenSessionPromise>} the response
+	 */
+	openSession() {
+		let response = { origin: 'ServerManager.openSession', context: 'when opening a session for experiment: ' + this._psychoJS.config.experiment.name };
+
+		this._psychoJS.logger.debug('opening a session for experiment: ' + this._psychoJS.config.experiment.name);
+
+		this.setStatus(ServerManager.Status.BUSY);
+
+		let data = {
+			experimentFullPath: this._psychoJS.config.experiment.fullpath
+		};
+		const gitlabConfig = this._psychoJS.config.gitlab;
+		if (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined')
+			data.projectId = gitlabConfig.projectId;
+
+		let self = this;
+		return new Promise((resolve, reject) => {
+			
+			$.post(this._psychoJS.config.psychoJsManager.URL + '?command=open_session', data, null, 'json')
+				.done((data, textStatus) => {
+					// check for error:
+					if ('error' in data) {
+						self.setStatus(ServerManager.Status.ERROR);
+						reject({ ...response, error: data.error });
+					}
+
+					// get session token:
+					if ('token' in data) {
+						self._psychoJS.config.experiment.token = data.token;
+						self.setStatus(ServerManager.Status.READY);
+						resolve({ ...response, token: data.token });
+					}
+					else {
+						self.setStatus(ServerManager.Status.ERROR);
+						reject({ ...response, error: 'unexpected answer from server: no token' });
+					}
+				})
+				.fail((jqXHR, textStatus, errorThrown) => {
+					self.setStatus(ServerManager.Status.ERROR);
+					reject({ ...response, error: 'request error: ' + textStatus });
+				});
+		});
+	}
+
+
+	/**
+	 * @typedef ServerManager.CloseSessionPromise
+	 * @property {string} origin the calling method
+	 * @property {string} context the context
+	 * @property {Object.<string, *>} [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
+	 * @returns {Promise<ServerManager.CloseSessionPromise>} the response
+	 */
+	closeSession() {
+		let response = { origin: 'ServerManager.closeSession', context: 'when 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);
+
+		let data = {
+			experimentFullPath: this._psychoJS.config.experiment.fullpath,
+			'token': this._psychoJS.config.experiment.token
+		};
+		const gitlabConfig = this._psychoJS.config.gitlab;
+		if (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined')
+			data.projectId = gitlabConfig.projectId;
+
+
+		let self = this;
+		return new Promise((resolve, reject) => {				
+			$.post(this._psychoJS.config.psychoJsManager.URL + '?command=close_session', data, null, 'json')
+				.done((data, textStatus) => {
+					// check for error:
+					if ('error' in data) {
+						self.setStatus(ServerManager.Status.ERROR);
+						reject({ ...response, error: data.error });
+					}
+
+					self.setStatus(ServerManager.Status.READY);
+					resolve({ ...response, data });
+				})
+				.fail((jqXHR, textStatus, errorThrown) => {
+					self.setStatus(ServerManager.Status.ERROR);
+					reject({ ...response, error: errorThrown });
+				});
+		});
+	}
+
+
+	/**
+	 * Get the value of a resource.
+	 * 
+	 * @name module:core.ServerManager#getResource
+	 * @function
+	 * @public
+	 * @param {string} name of the requested resource
+	 * @return {Object} value of the resource
+	 * @throws {Object.<string, *>} exception if no resource with that name has previously been registered
+	 */
+	getResource(resourceName) {
+		let response = { origin: 'ServerManager.getResource', context: 'when getting the value of  resource: ' + resourceName };
+
+		const resourceValue = this._resourcesMap.get(resourceName);
+		if (typeof resourceValue === 'undefined')
+			throw { ...response, error: 'unknown resource' };
+
+		return resourceValue;
+	}
+
+
+	/**
+	 * Set the resource manager status.
+	 * 
+	 * @name module:core.ServerManager#setStatus
+	 * @function
+	 * @public
+	 */
+	setStatus(status) {
+		let response = { 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;
+		if (!statusKey)
+			throw { ...response, error: 'status must be a symbol' };
+		if (!ServerManager.Status.hasOwnProperty(statusKey))
+			throw { ...response, error: 'unknown status' };
+
+		this._status = status;
+
+		// inform status listeners:
+		this.emit(ServerManager.Event.STATUS, this._status);
+
+		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() {
+		return this.setStatus(ServerManager.Status.READY);
+	}
+
+
+	/**
+	 * Asynchronously download the resources of the experiment from the remote PsychoJS manager and register them with the server manager.
+	 * 
+	 * @name module:core.ServerManager#downloadResources
+	 * @function
+	 * @public
+	 */
+	downloadResources() {
+		let response = { origin: 'ServerManager.downloadResources', context: 'when downloading the resources for experiment: ' + this._psychoJS.config.experiment.name };
+
+		this._psychoJS.logger.debug('downloading the resources for experiment: ' + this._psychoJS.config.experiment.name);
+
+		// we use an anonymous async function since downloadResource is non-blocking
+		// but we want to run the asynchronous _listResources and _downloadResources
+		// in sequence
+		let self = this;
+		let download = async () => {
+			try {
+				// list the resources and register them:
+				const { resources, resourceDirectory } = await self._listResources();
+				self._psychoJS.config.experiment.resourceDirectory = resourceDirectory;
+				for (const resource of resources)
+					self._resourcesMap.set(resource, 'undefined');
+				self._nbResources = resources.length;
+				self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCES_REGISTERED, count: self._nbResources });
+
+				// download the registered resources:
+				await self._downloadRegisteredResources();
+			}
+			catch (error) {
+				console.log('error', error);
+				throw { ...response, error: error };
+			}
+		};
+
+		download();
+	}
+
+	
+	/**
+	 * @typedef ServerManager.UploadDataPromise
+	 * @property {string} origin the calling method
+	 * @property {string} context the context
+	 * @property {Object.<string, *>} [error] an error message if we could not upload the data
+	 */
+	/**
+	 * Asynchronously upload experiment data to the remote PsychoJS manager.
+	 * 
+	 * @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)
+	 * 
+	 * @returns {Promise<ServerManager.UploadDataPromise>} the response
+	 */
+	uploadData(key, value) {
+		let response = { origin: 'ServerManager.uploadData', context: 'when uploading participant\' results for experiment: ' + this._psychoJS.config.experiment.name };
+
+		this._psychoJS.logger.debug('uploading data for experiment: ' + this._psychoJS.config.experiment.name);
+		this.setStatus(ServerManager.Status.BUSY);
+
+		let data = {
+			experimentFullPath: this._psychoJS.config.experiment.fullpath,
+			token: this._psychoJS.config.experiment.token,
+			key,
+			value,
+			saveFormat: Symbol.keyFor(this._psychoJS.config.experiment.saveFormat)
+		};
+		// add gitlab ID of experiment if there is one:
+		const gitlabConfig = this._psychoJS.config.gitlab;
+		if (typeof gitlabConfig !== 'undefined' && typeof gitlabConfig.projectId !== 'undefined')
+			data.projectId = gitlabConfig.projectId;
+
+		// (*) upload data:
+		const self = this;
+		return new Promise((resolve, reject) => {
+			$.post(this._psychoJS.config.psychoJsManager.URL + '?command=save_data', data, null, 'json')
+				.done((data, textStatus) => {
+					// check for error:
+					if ('error' in data) {
+						self.setStatus(ServerManager.Status.ERROR);
+						reject({ ...response, error: data.error });
+					}
+
+					// return the response from the PsychoJS manager:
+					self.setStatus(ServerManager.Status.READY);
+					resolve({ ...response, data });
+				})
+				.fail((jqXHR, textStatus, errorThrown) => {
+					self.setStatus(ServerManager.Status.ERROR);
+					reject({ ...response, error: errorThrown });
+				});
+		});
+	}
+
+
+	/**
+	 * List the resources available to the experiment.
+
+	 * @name module:core.ServerManager#_listResources
+	 * @function
+	 * @private
+	 */
+	_listResources() {
+		let response = { origin: 'ServerManager._listResourcesSession', context: 'when listing the resources for experiment: ' + this._psychoJS.config.experiment.name };
+
+		this._psychoJS.logger.debug('listing the resources for experiment: ' + this._psychoJS.config.experiment.name);
+
+		this.setStatus(ServerManager.Status.BUSY);
+
+		const self = this;
+		return new Promise((resolve, reject) => {
+			$.get(self._psychoJS.config.psychoJsManager.URL, {
+				'command': 'list_resources',
+				'experimentFullPath': self._psychoJS.config.experiment.fullpath,
+				'token': self._psychoJS.config.experiment.token
+			}, null, 'json')
+				.done((data, textStatus) => {
+					// check for error:
+					if ('error' in data)
+						reject({ ...response, error: data.error });
+
+					if (!('resources' in data)) {
+						self.setStatus(ServerManager.Status.ERROR);
+						reject({ ...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' });
+					}
+
+					self.setStatus(ServerManager.Status.READY);
+					resolve({ ...response, resources: data.resources, resourceDirectory: data.resourceDirectory });
+				})
+				.fail((jqXHR, textStatus, errorThrown) => {
+					self.setStatus(ServerManager.Status.ERROR);
+					reject({ ...response, error: errorThrown });
+				});
+		});
+	}
+
+
+	/**
+	 * Download the resources previously registered.
+	 * 
+	 * <p>Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.</p>
+	 * 
+	 * @name module:core.ServerManager#_downloadRegisteredResources
+	 * @function
+	 * @private
+	 */
+	_downloadRegisteredResources() {
+		let response = { origin: 'ServerManager._downloadResources', context: 'when downloading the resources for experiment: ' + this._psychoJS.config.experiment.name };
+
+		this._psychoJS.logger.debug('downloading the registered resources for experiment: ' + this._psychoJS.config.experiment.name);
+
+		this.setStatus(ServerManager.Status.BUSY);
+		this._nbLoadedResources = 0;
+
+
+		// (*) set-up preload.js:
+		this._resourceQueue = new createjs.LoadQueue(true, this._psychoJS.config.experiment.resourceDirectory);
+
+		const self = this;
+		this._resourceQueue.addEventListener("filestart", event => {
+			self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCE, resource: event.item.id });
+		});
+
+		this._resourceQueue.addEventListener("fileload", event => {
+			++self._nbLoadedResources;
+			self._resourcesMap.set(event.item.id, event.result);
+			self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCE_DOWNLOADED, resource: event.item.id });
+		});
+
+		// loading completed:
+		this._resourceQueue.addEventListener("complete", event => {
+			self._resourceQueue.close();
+			if (self._nbLoadedResources == self._nbResources) {
+				self.setStatus(ServerManager.Status.READY);
+				self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOAD_COMPLETED });
+			}
+		});
+
+		// error: we throw an exception
+		this._resourceQueue.addEventListener("error", event => {
+			self.setStatus(ServerManager.Status.ERROR);
+			const resourceId = (typeof event.data !== 'undefined')?event.data.id:'UNKNOWN RESOURCE';
+			throw { ...response, error: 'unable to download resource: ' + resourceId + ' (' + event.title + ')' };
+		});
+
+
+		// (*) dispatch resources to preload.js or howler.js based on extension:
+		let manifest = [];
+		let soundFilenames = [];
+		for (const resourceName of this._resourcesMap.keys()) {
+			const resourceExtension = resourceName.split('.').pop();
+
+			// preload.js with forced binary for xls and xlsx:
+			if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1)
+				manifest.push({ id: resourceName, src: resourceName, type: createjs.Types.BINARY });
+
+			// sound files are loaded through howler.js:
+			else if (['mp3', 'mpeg', 'opus', 'ogg', 'oga', 'wav', 'aac', 'caf', 'm4a', 'mp4', 'weba', 'webm', 'dolby', 'flac'].indexOf(resourceExtension) > -1)
+				soundFilenames.push(resourceName);
+
+			// preload.js for the other extensions (download type decided by preload.js):
+			else
+				manifest.push({ id: resourceName, src: resourceName });
+		}
+
+
+		// (*) start loading non-sound resources:
+		if (manifest.length > 0)
+			this._resourceQueue.loadManifest(manifest);
+		else {
+			if (this._nbLoadedResources == this._nbResources) {
+				this.setStatus(ServerManager.Status.READY);
+				this.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOAD_COMPLETED });
+			}
+		}
+
+
+		// (*) prepare and start loading sound resources:
+		for (let soundFilename of soundFilenames) {
+			const resourcePath = this._psychoJS.config.experiment.resourceDirectory + soundFilename;
+
+			self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOADING_RESOURCE, resource: soundFilename });
+
+			const howl = new Howl({
+				src: resourcePath,
+				preload: false,
+				autoplay: false
+			});
+
+			howl.on('load', (event) => {
+				++self._nbLoadedResources;
+				self._resourcesMap.set(soundFilename, howl);
+				self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.RESOURCE_DOWNLOADED, resource: soundFilename });
+
+				if (self._nbLoadedResources == self._nbResources) {
+					self.setStatus(ServerManager.Status.READY);
+					self.emit(ServerManager.Event.RESOURCE, { message: ServerManager.Event.DOWNLOAD_COMPLETED });
+				}
+			});
+			howl.on('loaderror', (id, error) => {
+				throw { ...response, error: 'unable to download resource: ' + soundFilename + ' (' + util.toString(error) + ')' };
+			});
+
+			howl.load();
+		}
+	}
+
+}
+
+
+/**
+ * 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'),
+	/**
+	 * Event: resources all registered
+	 */
+	RESOURCES_REGISTERED: Symbol.for('RESOURCES_REGISTERED'),
+	/**
+	 * Event: resource download has started
+	 */
+	DOWNLOADING_RESOURCE: Symbol.for('DOWNLOADING_RESOURCE'),
+	/**
+	 * Event: resource has been downloaded
+	 */
+	RESOURCE_DOWNLOADED: Symbol.for('RESOURCE_DOWNLOADED'),
+	/**
+	 * Event: resources all downloaded
+	 */
+	DOWNLOAD_COMPLETED: Symbol.for('DOWNLOAD_COMPLETED'),
+
+	/**
+	 * Event type: status event
+	 */
+	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'),
+
+	/**
+	 * The manager is busy, e.g. it is downloaded resources.
+	 */
+	BUSY: Symbol.for('BUSY'),
+
+	/**
+	 * The manager has encountered an error, e.g. it was unable to download a resource.
+	 */
+	ERROR: Symbol.for('ERROR')
+};
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_Window.js.html b/docs/core_Window.js.html new file mode 100644 index 0000000..15c9c77 --- /dev/null +++ b/docs/core_Window.js.html @@ -0,0 +1,378 @@ + + + + + JSDoc: Source: core/Window.js + + + + + + + + + + +
+ +

Source: core/Window.js

+ + + + + + +
+
+
/**
+ * Window responsible for displaying the experiment stimuli
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { Color } from '../util/Color';
+import { PsychObject } from '../util/PsychObject';
+import { MonotonicClock } from '../util/Clock';
+import * as util from '../util/Util';
+
+/**
+ * <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 {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.autoLog= true] whether or not to log
+ */
+export class Window extends PsychObject {
+
+	/**
+	 * Getter for monitorFramePeriod.
+	 * 
+	 * @name module:core.Window#monitorFramePeriod
+	 * @function
+	 * @public
+	 */
+	get monitorFramePeriod() { return this._monitorFramePeriod; }
+
+	constructor({
+		psychoJS,
+		name,
+		fullscr = false,
+		color = new Color('black'),
+		units = 'pix',
+		autoLog = true
+	} = {}) {
+		super(psychoJS, name);
+
+		// messages to be logged at the next "flip":
+		this._msgToBeLogged = [];
+
+		// list of all elements, in the order they are currently drawn:
+		this._drawList = [];
+
+		this._addAttributes(Window, name, fullscr, color, units, autoLog);
+		this._addAttribute('size', []);
+
+
+		// setup PIXI:
+		this._setupPixi();
+
+		// monitor frame period:
+		this._monitorFramePeriod = 1.0 / this.getActualFrameRate();
+
+		this._frameCount = 0;
+
+		this._flipCallback = undefined;
+		this._flipCallbackArgs = undefined;
+
+		/*if (autoLog)
+			logging.exp("Created %s = %s" % (self.name, str(self)));*/
+	}
+
+
+	/**
+	 * 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() {
+		if (document.body.contains(this._renderer.view))
+			document.body.removeChild(this._renderer.view);
+
+		window.removeEventListener('resize', this._resizeCallback);
+		window.removeEventListener('orientationchange', this._resizeCallback);
+	}
+
+
+	/**
+	 * Estimate the frame rate.
+	 * 
+	 * @name module:core.Window#getActualFrameRate
+	 * @function
+	 * @public
+	 * @return {number} always returns 60.0 at the moment
+	 * 
+	 * @todo estimate the actual frame rate.
+	 */
+	getActualFrameRate() {
+		// TODO
+		return 60.0;
+	}
+
+
+	/**
+	 * Take the browser full screen if possible.
+	 * 
+	 * @name module:core.Window#adjustScreenSize
+	 * @function
+	 * @public
+	 */
+	adjustScreenSize() {
+		if (this.fullscr) {
+			if (typeof document.documentElement.requestFullscreen === 'function')
+				document.documentElement.requestFullscreen();
+			else if (typeof document.documentElement.mozRequestFullScreen === 'function')
+				document.documentElement.mozRequestFullScreen();
+			else if (typeof document.documentElement.webkitRequestFullscreen === 'function')
+				document.documentElement.webkitRequestFullscreen();
+			else if (typeof document.documentElement.msRequestFullscreen === 'function')
+				document.documentElement.msRequestFullscreen();
+			else
+				this.psychoJS.logger.warn('Unable to go fullscreen.');
+
+			// the Window and all of the stimuli need updating:
+			this._needUpdate = true;
+			for (const stim of this._drawList)
+				stim._needUpdate = true;
+		}
+	}
+
+
+	/**
+	 * 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 {integer} level the log level
+	 * @param {Object} [obj] the object associated with the message
+	 */
+	logOnFlip({
+		msg,
+		level,
+		obj = undefined } = {}) {
+		this._msgToBeLogged.push({ msg, level, obj });
+	}
+
+
+	/**
+	 * Specify the callback function ran after each screen flip, i.e. immedicately after each rendering of the Window.
+	 * 
+	 * <p>This is typically used to reset a timer or clock.</p>
+	 * 
+	 * @name module:core.Window#callOnFlip
+	 * @function
+	 * @public
+	 * @param {*} flipFunction - callback function.
+	 * @param {Object} flipArgs - arguments for the callback function.
+	 */
+	callOnFlip(flipCallback, ...flipCallbackArgs) {
+		this._flipCallback = flipCallback;
+		this._flipCallbackArgs = flipCallbackArgs;
+	}
+
+
+	/**
+	 * Render the stimuli onto the canvas.
+	 * 
+	 * @name module:core.Window#render
+	 * @function
+	 * @public
+	 */
+	render() {
+		this._frameCount++;
+
+		// render the PIXI container:
+		this._renderer.render(this._rootContainer);
+
+		// 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/]
+		this._renderer.gl.readPixels(0, 0, 1, 1, this._renderer.gl.RGBA, this._renderer.gl.UNSIGNED_BYTE, new Uint8Array(4));
+
+		// log and call on flip:
+		this._writeLogOnFlip();
+		if (typeof this._flipCallback !== 'undefined')
+			this._flipCallback(...this._flipCallbackArgs);
+
+		// prepare the scene for the next animation frame:
+		this._refresh();
+	}
+
+
+	/**
+	 * Update this window, if need be.
+	 * 
+	 * @name module:core.Window#_updateIfNeeded
+	 * @function
+	 * @private
+	 */
+	_updateIfNeeded() {
+		if (this._needUpdate) {
+			this._renderer.backgroundColor = this._color.int;
+
+			this._needUpdate = false;
+		}
+	}
+
+
+	/**
+	 * Recompute the window's _drawList and _container children for the next animation frame.
+	 * 
+	 * @name module:core.Window#_refresh
+	 * @function
+	 * @private
+	 */
+	_refresh() {
+		this._updateIfNeeded();
+
+		// if a stimuli needs to be updated, we remove it from the window container, update it, then put it back
+		for (const stim of this._drawList)
+			if (stim._needUpdate) {
+				this._rootContainer.removeChild(stim._pixi);
+				stim._updateIfNeeded();
+				this._rootContainer.addChild(stim._pixi);
+			}
+	}
+
+
+	/**
+	 * 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
+	 */
+	_setupPixi() {
+		// the size of the PsychoJS Window is always that of the browser
+		this._size[0] = window.innerWidth;
+		this._size[1] = window.innerHeight;
+
+		// create a PIXI renderer and add it to the document:
+		this._renderer = PIXI.autoDetectRenderer(this._size[0], this._size[1], {
+			backgroundColor: this.color.int
+		});
+		this._renderer.view.style["transform"] = "translatez(0)";
+		this._renderer.view.style.position = "absolute";
+		document.body.appendChild(this._renderer.view);
+
+		// top-level container:
+		this._rootContainer = new PIXI.Container();
+		this._rootContainer.interactive = true;
+
+		// set size of renderer and position of root container:
+		this._onResize(this);
+
+		// touch/mouse events should be treated by PsychoJS' event manager:
+		this.psychoJS.eventManager.addMouseListeners(this._renderer);
+
+		// update the renderer size when the browser's size or orientation changes:
+		this._resizeCallback = e => this._onResize(this);
+		window.addEventListener('resize', this._resizeCallback);
+		window.addEventListener('orientationchange', this._resizeCallback);
+	}
+
+
+	/**
+	 * Treat a window resize event.
+	 * 
+	 * <p>We adjust the size of the renderer and the position of the root container.</p>
+	 * <p>Note: since this method will be called by the DOM window (i.e. 'this' is
+	 * the DOM window), we need to pass it a {@link Window}.</p>
+	 * 
+	 * @name module:core.Window#_onResize
+	 * @function
+	 * @private
+	 * @param {Window} win - The PsychoJS window
+	 */
+	_onResize(win) {
+		// update the size of the PsychoJS Window:
+		win._size[0] = window.innerWidth;
+		win._size[1] = window.innerHeight;
+
+		win._renderer.view.style.width = win._size[0] + 'px';
+		win._renderer.view.style.height = win._size[1] + 'px';
+		win._renderer.view.style.left = '0px';
+		win._renderer.view.style.top = '0px';
+		win._renderer.resize(win._size[0], win._size[1]);
+
+		// setup the container such that (0,0) is at the centre of the window
+		// with positive coordinates to the right and top:
+		win._rootContainer.position.x = win._size[0] / 2.0;
+		win._rootContainer.position.y = win._size[1] / 2.0;
+		win._rootContainer.scale.y = -1;
+	}
+
+
+	/**
+	 * Send all logged messages to the {@link Logger}.
+	 * 
+	 * @name module:core.Window#_writeLogOnFlip
+	 * @function
+	 * @private
+	 */
+	_writeLogOnFlip() {
+		var logTime = MonotonicClock.getReferenceTime();
+		for (var i = 0; i < this._msgToBeLogged.length; ++i) {
+			var entry = this._msgToBeLogged[i];
+			this._psychoJS.logger.log(entry.msg, entry.level, logTime, entry.obj);
+		}
+
+		this._msgToBeLogged = [];
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/core_WindowMixin.js.html b/docs/core_WindowMixin.js.html new file mode 100644 index 0000000..9c05dcb --- /dev/null +++ b/docs/core_WindowMixin.js.html @@ -0,0 +1,215 @@ + + + + + JSDoc: Source: core/WindowMixin.js + + + + + + + + + + +
+ +

Source: core/WindowMixin.js

+ + + + + + +
+
+
/**
+ * Mixin implementing various unit-handling measurement methods.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+/**
+ * <p>This mixin implements various unit-handling measurement methods.</p>
+ * 
+ * <p>Note: (a) this is the equivalent of PsychoPY's WindowMixin.
+ *          (b) it will most probably be made obsolete by a fully-integrated unit approach.
+ * </p>
+ * 
+ * @name module:core.WindowMixin
+ * @mixin
+ * 
+ */
+export let WindowMixin = (superclass) => class extends superclass
+{
+	constructor(args)
+	{
+		super(args);
+	}
+
+
+	/**
+	 * Setter for units attribute.
+	 * 
+	 * @name module:core.WindowMixin#setUnits
+	 * @function
+	 * @public
+	 * @param {String} [units= this.win.units] - the units
+	 * @param {boolean} [log= false] - whether or not to log
+	 */
+	setUnits(units = this.win.units, log = false)
+	{
+		this._setAttribute('units', units, log);
+	}
+
+
+	/**
+	 * Convert the given length from stimulus unit to pixel units.
+	 * 
+	 * @name module:core.WindowMixin#_getLengthPix
+	 * @function
+	 * @protected
+	 * @param {number} length - the length in stimulus units
+	 * @return {number} - the length in pixel units
+	 */
+	_getLengthPix(length)
+	{
+		let errorPrefix = { origin: 'WindowMixin._getLengthPix', context: 'when converting a length from stimulus unit 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[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 * minSize;
+		}
+		else {
+			throw {...errorPrefix, error: 'unable to deal with unit: ' + this._units};
+		}
+	}
+
+
+	/**
+	 * 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 errorPrefix = { 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 {...errorPrefix, error: 'unable to deal with unit: ' + this._units};
+		}
+	}
+
+
+	/**
+	 * Convert the given length from pixel units to the stimulus units
+	 * 
+	 * @name module:core.WindowMixin#_getHorLengthPix
+	 * @function
+	 * @protected
+	 * @param {number} length_px - the length in pixel units
+	 * @return {number} - the length in stimulus units
+	 */
+	_getHorLengthPix(length)
+	{
+		let errorPrefix = { origin: 'WindowMixin._getHorLengthPix', 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[0]/2;
+		}
+		else if (this._units === 'height') {
+			const minSize = Math.min(this.win.size[0], this.win.size[1]);
+			return length * minSize;
+		}
+		else {
+			throw {...errorPrefix, error: 'unable to deal with unit: ' + this._units};
+		}
+	}
+
+	/**
+	 * Convert the given length from pixel units to the stimulus units
+	 * 
+	 * @name module:core.WindowMixin#_getVerLengthPix
+	 * @function
+	 * @protected
+	 * @param {number} length_px - the length in pixel units
+	 * @return {number} - the length in stimulus units
+	 */
+	_getVerLengthPix(length)
+	{
+		let errorPrefix = { 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 {...errorPrefix, error: 'unable to deal with unit: ' + this._units};
+		}
+	}
+
+
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/data_ExperimentHandler.js.html b/docs/data_ExperimentHandler.js.html new file mode 100644 index 0000000..14e0654 --- /dev/null +++ b/docs/data_ExperimentHandler.js.html @@ -0,0 +1,375 @@ + + + + + JSDoc: Source: data/ExperimentHandler.js + + + + + + + + + + +
+ +

Source: data/ExperimentHandler.js

+ + + + + + +
+
+
/**
+ * Experiment Handler
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { PsychObject } from '../util/PsychObject'
+import { MonotonicClock } from '../util/Clock'
+
+
+/**
+ * <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 {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() { return this._experimentEnded; }
+
+	/**
+	 * Setter for experimentEnded.
+	 * 
+	 * @name module:core.Window#experimentEnded
+	 * @function
+	 * @public
+	 */
+	set experimentEnded(ended) { this._experimentEnded = ended; }
+
+
+	constructor({
+		psychoJS,
+		name,
+		extraInfo
+	} = {}) {
+		super(psychoJS, name);
+
+		this._addAttributes(ExperimentHandler, name, extraInfo);
+
+		// loop handlers:
+		this._loops = [];
+		this._unfinishedLoops = [];
+
+		// data dictionaries (one per trial) and current data dictionary:
+		this._trialsKeys = [];
+		this._trialsData = [];
+		this._currentTrialData = {};
+
+		this._experimentEnded = false;
+	}
+
+
+	/**
+	 * Add a loop.
+	 * <p> The loop might be a {@link TrialHandler} or a {@link StairHandler}, 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) {
+		this._loops.push(loop);
+		this._unfinishedLoops.push(loop);
+		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) {
+		const index = this._unfinishedLoops.indexOf(loop);
+		if (index !== -1) {
+			this._unfinishedLoops.splice(index, 1);
+		}
+	}
+
+
+	/**
+	 * 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
+	 */
+	addData(key, value) {
+		if (this._trialsKeys.indexOf(key) === -1) {
+			this._trialsKeys.push(key);
+		};
+
+		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
+	 */
+	nextEntry() {
+		// fetch data from each (potentially-nested) loop:
+		for (let loop of this._unfinishedLoops) {
+			var attributes = this.getLoopAttributes(loop);
+			for (let a in attributes)
+				if (attributes.hasOwnProperty(a))
+					this._currentTrialData[a] = attributes[a];
+		}
+
+		// add the extraInfo dict to the data:
+		for (let a in this.extraInfo)
+			if (this.extraInfo.hasOwnProperty(a))
+				this._currentTrialData[a] = this.extraInfo[a];
+
+		this._trialsData.push(this._currentTrialData);
+
+		this._currentTrialData = {};
+	}
+
+
+	/**
+	 * Save the results of the experiment.
+	 * <p> Results are uploaded to the remote PsychoJS manager running on the remote https://pavlovia.org server</p>
+	 *
+	 * @name module:data.ExperimentHandler#save
+	 * @function
+	 * @public
+	 * @param {Object} options
+	 * @param {PsychoJS} options.attributes - the attributes to be saved
+	 */
+	async save({
+		attributes = []
+	} = {}) {
+		this._psychoJS.logger.info('[PsychoJS] Save experiment results.');
+
+		// (*) get attributes:
+		if (attributes.length == 0) {
+			attributes = this._trialsKeys.slice();
+			for (let l = 0; l < this._loops.length; l++) {
+				const loop = this._loops[l];
+
+				const loopAttributes = this.getLoopAttributes(loop);
+				for (let a in loopAttributes)
+					if (loopAttributes.hasOwnProperty(a))
+					attributes.push(a);
+			}
+			for (let a in this.extraInfo) {
+				if (this.extraInfo.hasOwnProperty(a))
+				attributes.push(a);
+			}
+		}
+
+
+		// (*) 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 on the remote server:
+		if (this._psychoJS.config.experiment.saveFormat == ExperimentHandler.SaveFormat.CSV) {
+			let csv = "";
+
+			// build the csv header:
+			for (let h = 0; h < attributes.length; h++) {
+				if (h > 0)
+					csv = csv + ', ';
+				csv = csv + attributes[h];
+			}
+			csv = csv + '\n';
+
+			// build the records:
+			for (let r = 0; r < this._trialsData.length; r++) {
+				for (let h = 0; h < attributes.length; h++) {
+					if (h > 0)
+						csv = csv + ', ';
+					csv = csv + this._trialsData[r][attributes[h]];
+				}
+				csv = csv + '\n';
+			}
+
+			// upload data to the remote PsychoJS manager:
+			const key = __participant + '_' + __experimentName + '_' + __datetime + '.csv';
+			return await this._psychoJS.serverManager.uploadData(key, csv);
+		}
+
+
+		// (*) save in the database on the remote server:
+		else if (this._psychoJS.config.experiment.saveFormat == ExperimentHandler.SaveFormat.DATABASE) {
+			let documents = [];
+
+			for (let r = 0; r < this._trialsData.length; r++) {
+				let doc = { __projectId, __experimentName, __participant, __session, __datetime };
+				for (let h = 0; h < attributes.length; h++)
+					doc[attributes[h]] = this._trialsData[r][attributes[h]];
+
+				documents.push(doc);
+			}
+
+			// upload data to the remote PsychoJS manager:
+			const key = 'results'; // name of the mongoDB collection
+			return await this._psychoJS.serverManager.uploadData(key, JSON.stringify(documents));
+		}
+	}
+
+
+	/**
+	 * Get the attribute names and values for the current trial of a given loop.
+	 * <p> Only only info relating to the trial execution are returned.</p>
+	 * 
+	 * @name module:data.ExperimentHandler#getLoopAttributes
+	 * @function
+	 * @public
+	 * @param {Object} loop - the loop
+	 */
+	getLoopAttributes(loop) {
+		const loopName = loop['name'];
+
+		// standard attributes:
+		const properties = ['thisRepN', 'thisTrialN', 'thisN', 'thisIndex', 'stepSizeCurrent', 'ran', 'order'];
+		let attributes = {};
+		for (const property of properties)
+			for (const loopProperty in loop)
+				if (loopProperty === property) {
+					if (property === 'stepSizeCurrent')
+						var key = loopName + '.stepSize';
+					else
+						key = loopName + '.' + property;
+
+					attributes[key] = loop[property];
+				}
+
+		// trial's attributes:
+		if (typeof loop.getCurrentTrial === 'function') {
+			const currentTrial = loop.getCurrentTrial();
+			for (const trialProperty in currentTrial)
+				attributes[trialProperty] = currentTrial[trialProperty];
+		}
+
+		/* TODO
+		// method of constants
+		if hasattr(loop, 'thisTrial'):
+				trial = loop.thisTrial
+				if hasattr(trial,'items'):#is a TrialList object or a simple dict
+						for property,val in trial.items():
+								if property not in self._paramNamesSoFar:
+										self._paramNamesSoFar.append(property)
+								names.append(property)
+								vals.append(val)
+				elif trial==[]:#we haven't had 1st trial yet? Not actually sure why this occasionally happens (JWP)
+						pass
+				else:
+						names.append(loopName+'.thisTrial')
+						vals.append(trial)
+						
+		// single StairHandler
+		elif hasattr(loop, 'intensities'):
+				names.append(loopName+'.intensity')
+				if len(loop.intensities)>0:
+						vals.append(loop.intensities[-1])
+				else:
+						vals.append(None)*/
+
+		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'),
+
+	/**
+	 * Results are saved to a database
+	 */
+	DATABASE: Symbol.for('DATABASE')
+};
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/data_TrialHandler.js.html b/docs/data_TrialHandler.js.html new file mode 100644 index 0000000..85ca50d --- /dev/null +++ b/docs/data_TrialHandler.js.html @@ -0,0 +1,510 @@ + + + + + JSDoc: Source: data/TrialHandler.js + + + + + + + + + + +
+ +

Source: data/TrialHandler.js

+ + + + + + +
+
+
/** @module data */
+/**
+ * Trial Handler
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { PsychObject } from '../util/PsychObject';
+import * as util from '../util/Util';
+
+
+/**
+ * <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 - 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() { return this._experimentHandler; }
+
+	/**
+	 * Setter for experimentHandler.
+	 * 
+	 * @name module:core.Window#experimentHandler
+	 * @function
+	 * @public
+	 */
+	set experimentHandler(exp) {
+		this._experimentHandler = exp;
+	}
+
+
+	/**
+	 * @constructor
+	 * @public
+	 *
+	 * @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
+	} = {}) {
+		super(psychoJS);
+
+		this._addAttributes(TrialHandler, trialList, nReps, method, extraInfo, seed, name, autoLog);
+
+		this._prepareTrialList(trialList);
+
+		// number of stimuli
+		this.nStim = this.trialList.length;
+
+		// the total number of trials that will be run:
+		this.nTotal = this.nReps * this.nStim;
+
+		// the total number of trial remaining
+		this.nRemaining = this.nTotal;
+
+		// the current repeat:
+		this.thisRepN = 0;
+
+		// the current trial number within the current repeat:
+		this.thisTrialN = -1;
+
+		// total number of trials completed so far:
+		this.thisN = -1;
+
+		// the index of the current trial in the conditions list
+		this.thisIndex = 0;
+
+		this.ran = 0;
+		this.order = -1;
+
+
+		// setup the trial sequence:
+		this._prepareSequence();
+
+		this._experimentHandler = null;
+		this.thisTrial = null;
+		this.finished = false;
+	}
+
+
+	/**
+	 * Iterator over the trial sequence.
+	 *
+	 * <p>This makes it possible to iterate over all trials.</p>
+	 * @example
+	 * let handler = new TrialHandler({nReps: 5});
+	 * for (const thisTrial of handler) { console.log(thisTrial); }
+	 */
+	[Symbol.iterator]() {
+		return {
+			next: () => {
+				this.thisTrialN++;
+				this.thisN++;
+				this.nRemaining--;
+
+				// start a new repetition:
+				if (this.thisTrialN === this.nStim) {
+					this.thisTrialN = 0;
+					this.thisRepN++;
+				}
+
+				// check if we have completed the sequence:
+				if (this.thisRepN >= this.nReps) {
+					this.thisTrial = null;
+					return { done: true };
+				}
+
+				this.thisIndex = this._trialSequence[this.thisRepN][this.thisTrialN];
+				this.thisTrial = this.trialList[this.thisIndex];
+				this.ran = 1;
+				this.order = this.thisN;
+				/*
+				if self.autoLog:
+					msg = 'New trial (rep=%i, index=%i): %s'
+					vals = (self.thisRepN, self.thisTrialN, self.thisTrial)
+					logging.exp(msg % vals, obj=self.thisTrial)*/
+
+				return { value: this.thisTrial, done: false };
+			}
+		};
+	};
+
+
+	/**
+	 * Get the trial index.
+	 * 
+	 * @public
+	 * @return {number} the current trial index
+	 */
+	getTrialIndex() {
+		return this.thisIndex;
+	}
+
+
+	/**
+	 * Set the trial index.
+	 * 
+	 * @param {number} index - the new trial index
+	 */
+	setTrialIndex(index) {
+		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() {
+		if (!Array.isArray(this.trialList) || this.nStim == 0)
+			return [];
+
+		const firstTrial = this.trialList[0];
+		if (!firstTrial)
+			return [];
+
+		return Object.keys(this.trialList[0]);
+	}
+
+
+	/**
+	 * Get the current trial.
+	 * 
+	 * @public
+	 * @return {Object} the current trial
+	 */
+	getCurrentTrial() {
+		return this.trialList[this.thisIndex];
+	}
+
+
+	/**
+	 * 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.
+	 */
+	getFutureTrial(n = 1) {
+		if (this.thisIndex+n < 0 || n > this.nRemaining)
+			return undefined;
+
+		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.
+	 */
+	getEarlierTrial(n = -1) {
+		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
+	 */
+	addData(key, value) {
+		if (this._experimentHandler)
+			this._experimentHandler.addData(key, value);
+	}
+
+
+	/**
+	 * Import a list of conditions from a .xls, .xlsx, .odp, or .csv resource.
+	 *
+	 * <p>The output is suitable as an input to 'TrialHandler', 'trialTypes' or
+	 * 'MultiStairHandler' as a 'conditions' list.</p>
+	 *
+	 * <p>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:
+	 * <ul>
+	 * <li>be unique</li>
+	 * <li>begin with a letter (upper or lower case)</li>
+	 * <li>contain no spaces or other punctuation (underscores are permitted)</li>
+	 * </ul></p>
+	 *
+	 * <p>Note that we only consider the first worksheet for .xls, .xlsx and .odp resource.</p>
+	 *
+	 *
+	 * <p> '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
+	 *	[1,2,3,10]
+	 *	'1,5,10'
+	 *	'1:2:5'
+	 *	'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
+	 * @return {Object} the parsed conditions as an array of 'object as map'
+	 * @throws {Object} Throws an exception if importing the conditions failed.
+	 */
+	static importConditions(serverManager, resourceName, selection = null) {
+		try {
+			let resourceExtension = resourceName.split('.').pop();
+			if (['csv', 'odp', 'xls', 'xlsx'].indexOf(resourceExtension) > -1) {
+				// (*) read conditions from resource:
+				let resourceValue = serverManager.getResource(resourceName);
+				let workbook = XLSX.read(new Uint8Array(resourceValue), { type: "array" });
+
+				// we consider only the first worksheet:
+				if (workbook.SheetNames.length == 0)
+					throw '"workbook should contain at least one worksheet"';
+				let sheetName = workbook.SheetNames[0];
+				let worksheet = workbook.Sheets[sheetName];
+
+				// worksheet to array of arrays (the first array contains the fields):
+				let sheet = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
+				let fields = sheet.shift();
+
+				// (*) select conditions:
+				let selectedRows = sheet;
+				if (selection != null)
+					selectedRows = selectFromArray(sheet, selection);
+
+
+				// (*) return the selected conditions as an array of 'object as map':
+				// [
+				// 		{field0: value0-0, field1: value0-1, ...}
+				//		{field0: value1-0, field1: value1-1, ...}
+				//		...
+				// ]
+				let trialList = new Array(selectedRows.length - 1);
+				for (var r = 0; r < selectedRows.length; ++r) {
+					let row = selectedRows[r];
+					let trial = {};
+					for (var l = 0; l < fields.length; ++l)
+						trial[fields[l]] = row[l];
+					trialList[r] = trial;
+				}
+
+				return trialList;
+			}
+
+			else {
+				throw 'extension: ' + resourceExtension + ' currently not supported.';
+			}
+		}
+		catch (exception) {
+			throw '{ "origin" : "data.importConditions", "context" : "when importing condition: ' + resourceName + '", "error" : ' + exception + ', "stack" : ' + util.getErrorStack() + ' }';
+		}
+	}
+
+
+	/**
+	 * Prepare the trial list.
+	 *
+	 * @protected
+	 * @param {Array.<Object> | String} trialList - if it is a string, we treat it as the name of a condition resource
+	 */
+	_prepareTrialList(trialList) {
+		let response = { origin : 'TrialHandler._prepareTrialList', context : 'when preparing the trial list' };
+
+		// we treat undefined or empty trialList's as a list with a single empty entry:
+		if (typeof trialList === 'undefined')
+			this.trialList = [undefined];
+
+		// if trialList is a string, we treat it as the name of the condition resource:
+		else if (typeof trialList === 'string')
+			this.trialList = TrialHandler.importConditions(this.psychoJS.serverManager, trialList);
+
+		// if trialList is an array, we make sure it is not empty:
+		else if (Array.isArray(trialList)) {
+			if (trialList.length == 0)
+				this.trialList = [undefined];
+		}
+
+		// unknown type:
+		else
+			throw { ...response, error: 'unable to prepare trial list: unknown type.' };
+	}
+
+
+	/*
+	 * 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]
+	 *       [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
+	 * </p>
+	 *
+	 * @protected
+	 */
+	_prepareSequence() {
+		let response = { origin : 'TrialHandler._prepareSequence', context : 'when preparing a sequence of trials' };
+
+		// get an array of the indices of the elements of trialList :
+		const indices = Array.from(this.trialList.keys());
+
+		// seed the random number generator:
+		if (typeof (this.seed) !== 'undefined')
+			Math.seedrandom(this.seed);
+		else
+			Math.seedrandom();
+
+		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; }, [] );
+		}
+
+		else if (this.method === TrialHandler.Method.RANDOM) {
+			this._trialSequence = [];
+			for (let i = 0; i < this.nReps; ++i)
+				this._trialSequence.push(util.shuffle(indices.slice()));
+		}
+
+		else if (this.method === TrialHandler.Method.FULL_RANDOM) {
+			// create a flat sequence with nReps repeats of indices:
+			let flatSequence = [];
+			for (let i = 0; i < this.nReps; ++i)
+				flatSequence.push.apply(flatSequence, indices);
+
+			// shuffle the sequence:
+			util.shuffle(flatSequence);
+
+			// reshape it into the trialSequence:
+			this._trialSequence = [];
+			for (let i = 0; i < this.nReps; i++)
+				this._trialSequence.push(flatSequence.slice(i * this.nStim, (i + 1) * this.nStim));
+		}
+		else {
+			throw { ...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'),
+
+	/**
+	 * Conditions are shuffled within each repeat.
+	 */
+	RANDOM: Symbol.for('RANDOM'),
+
+	/**
+	 * Conditions are fully randomised across all repeats.
+	 */
+	FULL_RANDOM: Symbol.for('FULL_RANDOM')
+};
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 0000000..5d20d91 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.eot differ diff --git a/docs/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 0000000..3ed7be4 --- /dev/null +++ b/docs/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 0000000..1205787 Binary files /dev/null and b/docs/fonts/OpenSans-Bold-webfont.woff differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot new file mode 100644 index 0000000..1f639a1 Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg new file mode 100644 index 0000000..6a2607b --- /dev/null +++ b/docs/fonts/OpenSans-BoldItalic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 0000000..ed760c0 Binary files /dev/null and b/docs/fonts/OpenSans-BoldItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot new file mode 100644 index 0000000..0c8a0ae Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.eot differ diff --git a/docs/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg new file mode 100644 index 0000000..e1075dc --- /dev/null +++ b/docs/fonts/OpenSans-Italic-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 0000000..ff652e6 Binary files /dev/null and b/docs/fonts/OpenSans-Italic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot new file mode 100644 index 0000000..1486840 Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.eot differ diff --git a/docs/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..11a472c --- /dev/null +++ b/docs/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000..e786074 Binary files /dev/null and b/docs/fonts/OpenSans-Light-webfont.woff differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 0000000..8f44592 Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.eot differ diff --git a/docs/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 0000000..431d7e3 --- /dev/null +++ b/docs/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 0000000..43e8b9e Binary files /dev/null and b/docs/fonts/OpenSans-LightItalic-webfont.woff differ diff --git a/docs/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000..6bbc3cf Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.eot differ diff --git a/docs/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg new file mode 100644 index 0000000..25a3952 --- /dev/null +++ b/docs/fonts/OpenSans-Regular-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000..e231183 Binary files /dev/null and b/docs/fonts/OpenSans-Regular-webfont.woff differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..5b0e035 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,94 @@ + + + + + JSDoc: Home + + + + + + + + + + +
+ +

Home

+ + + + + + + + +

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

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. +It is also a git submodule: psychopy/psychojs

+

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.

+

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, which 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, and various additional vendor libraries, such as those we use to display stimuli (PixiJs) or play sounds (HowlerJs).

+

PsychoPy Builder

Starting with PsychoPy version 3.0, PsychoPy Builder can automatically generate the javascript and html files. Many of the existing Builder experiments should "just work", subject to the Components being currently supported by PsychoJs (see below).

+

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 the structure of the trials/loops all the way down to frame-by-frame updates.

+

There are however notable differences between the the PsychoJs and PsychoPy libraries, most of which have to do with the way a web browser interprets and runs javascripts, 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 their 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).

+

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 considering 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 under active development. PsychoPy Builder offers the possibility of uploading the experiment directly to pavlovia.org.

+

Which PsychoPy Components are supported by PsychoJs?

PsychoJs currently supports the following Components:

+

Stimuli:

    +
  • ImageStim
  • +
  • TextStim
  • +
  • BaseShapeStim (Polygon)
  • +
  • Rect
  • +
  • Sound (tones and tracks)
  • +
+

Events:

    +
  • Mouse
  • +
  • Keyboard
  • +
+

We are constantly adding new Components and are regularly updating this list.

+

Authors

The PsychoJs library is written and maintained by Ilixa Ltd. (http://www.ilixa.com). The PsychoPy Builder's javascript code generator is built and maintained by the creators of PsychoPy at the University of Nottingham (https://www.nottingham.ac.uk). Both efforts are generously supported by the Wellcome Trust (https://wellcome.ac.uk).

+

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

+
+ + + + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.BuilderKeyResponse.html b/docs/module-core.BuilderKeyResponse.html new file mode 100644 index 0000000..965ed2d --- /dev/null +++ b/docs/module-core.BuilderKeyResponse.html @@ -0,0 +1,267 @@ + + + + + JSDoc: Class: BuilderKeyResponse + + + + + + + + + + +
+ +

Class: BuilderKeyResponse

+ + + + + + +
+ +
+ +

+ core.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)
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new BuilderKeyResponse(options)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +PsychoJS + + + + the PsychoJS instance
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.EventManager.html b/docs/module-core.EventManager.html new file mode 100644 index 0000000..52e4394 --- /dev/null +++ b/docs/module-core.EventManager.html @@ -0,0 +1,1705 @@ + + + + + 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 + + +PsychoJS + + + + the PsychoJS instance
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

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

+ + + + +
+

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.

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

(private, readonly) _pygletMap :Object.<String, String>

+ + + + +
+

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

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

(private, readonly) _reversePygletMap :Object.<String, String>

+ + + + +
+

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

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

Methods

+ + + + + + + +

(private) _addKeyListeners()

+ + + + + + +
+ Add key listeners to the document. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(private) _pyglet2w3c(keyList) → {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
keyList + + +Array.string + + + + the array of pyglet key names
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the w3c keyList +
+ + + +
+
+ Type +
+
+ +Array.string + + +
+
+ + + + + + + + + + + + + +

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 + + +
+
+ + + + + + + + + + + + + +

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
  • +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.GUI.html b/docs/module-core.GUI.html new file mode 100644 index 0000000..b33240a --- /dev/null +++ b/docs/module-core.GUI.html @@ -0,0 +1,1009 @@ + + + + + JSDoc: Class: GUI + + + + + + + + + + +
+ +

Class: GUI

+ + + + + + +
+ +
+ +

+ core.GUI(psychoJS)

+ +
Graphic User Interface
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new GUI(psychoJS)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
psychoJS + + +PsychoJS + + + + the PsychoJS instance
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(private) _onResourceEvents(signal)

+ + + + + + +
+ Listener for resource event from the Server Manager. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
signal + + +Object.<string, (string|Symbol)> + + + + the signal
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

dialog()

+ + + + + + +
+ Destroy the currently opened dialog box. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

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 partipant 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
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
dictionary + + +Object + + + + associative array of values for the participant to set
title + + +String + + + + name of the project
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.MinimalStim.html b/docs/module-core.MinimalStim.html new file mode 100644 index 0000000..13d048e --- /dev/null +++ b/docs/module-core.MinimalStim.html @@ -0,0 +1,933 @@ + + + + + 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 + + +Window + + + + + + + + + + + + the associated Window
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
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

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

Methods

+ + + + + + + +

(abstract, private) _updateIfNeeded()

+ + + + + + +
+ Update the stimulus, if necessary. + +Note: this is an abstract function, which should not be called. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(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:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

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:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.Mouse.html b/docs/module-core.Mouse.html new file mode 100644 index 0000000..33f3554 --- /dev/null +++ b/docs/module-core.Mouse.html @@ -0,0 +1,1394 @@ + + + + + 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 + + +
+
+ + + + + + + + + + + + + +

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 + + +
+
+ + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.PsychoJS.html b/docs/module-core.PsychoJS.html new file mode 100644 index 0000000..0557574 --- /dev/null +++ b/docs/module-core.PsychoJS.html @@ -0,0 +1,2436 @@ + + + + + JSDoc: Class: PsychoJS + + + + + + + + + + +
+ +

Class: PsychoJS

+ + + + + + +
+ +
+ +

+ core.PsychoJS(options)

+ +

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.

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new PsychoJS(options)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
debug + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to log debug information in the browser console
collectIP + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to collect the IP information of the participant
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(static, readonly) Status :Symbol

+ + + + +
+ PsychoJS status +
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
NOT_CONFIGURED + + +Symbol + + + +
CONFIGURING + + +Symbol + + + +
CONFIGURED + + +Symbol + + + +
NOT_STARTED + + +Symbol + + + +
STARTED + + +Symbol + + + +
STOPPED + + +Symbol + + + +
FINISHED + + +Symbol + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

status

+ + + + +
+ Properties +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + +

Methods

+ + + + + + + +

(protected) _captureErrors()

+ + + + + + +
+ Capture all errors and display them in a pop-up error box. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(async, protected) _configure(configURL)

+ + + + + + +
+ Configure PsychoJS for the running experiment. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
configURL + + +string + + + + the URL of the configuration file
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(async, protected) _getParticipantIPInfo()

+ + + + + + +
+ Get the IP information of the participant, asynchronously. + +

Note: we use http://www.geoplugin.net/json.gp.

+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
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. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
obj + + +Object.<string, *> + + + + the object whose attributes we will mirror
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

openWindow(options)

+ + + + + + +
+ Open a PsychoJS Window. + +

This opens a PIXI canvas.

+

Note: we can only open one window.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
name + + +string + + + + + + <optional>
+ + + + + +
the name of the window
fullscr + + +boolean + + + + + + <optional>
+ + + + + +
whether or not to go fullscreen
color + + +Color + + + + + + <optional>
+ + + + + +
the background color of the window
units + + +string + + + + + + <optional>
+ + + + + +
the units of the window
autoLog + + +boolean + + + + + + <optional>
+ + + + + +
whether of not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+ exception if a window has already been opened +
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + + + + + + + + + + + + +

(async) quit(options)

+ + + + + + +
+ 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.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
message + + +string + + + + + + <optional>
+ + + + + +
+ + optional message to be displayed in a dialog box before quitting
isCompleted + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not the participant has completed the experiment
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

schedule(task, args)

+ + + + + + +
+ Schedule a task. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
task + + the task to be scheduled
args + + arguments for that task
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

scheduleCondition(condition, thenScheduler, elseScheduler)

+ + + + + + +
+ Schedule a series of task based on a condition. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
condition + + +PsychoJS.condition + + + +
thenScheduler + + +Scheduler + + + + scheduler to run if the condition is true
elseScheduler + + +Scheduler + + + + scheduler to run if the condition is false
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setRedirectUrls(completionUrl, cancellationUrl)

+ + + + + + +
+ Set the completion and cancellation URL to which the participant will be redirect at the end of the experiment. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
completionUrl + + +string + + + + the completion URL
cancellationUrl + + +string + + + + the cancellation URL
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(async) start(options)

+ + + + + + +
+ Start the experiment. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
configURL + + +string + + + + + + <optional>
+ + + + + +
+ + config.json + + the URL of the configuration file
expInfo + + +Object.<string, *> + + + + + + <optional>
+ + + + + +
+ + additional information about the experiment
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.ServerManager.html b/docs/module-core.ServerManager.html new file mode 100644 index 0000000..796080b --- /dev/null +++ b/docs/module-core.ServerManager.html @@ -0,0 +1,1754 @@ + + + + + JSDoc: Class: ServerManager + + + + + + + + + + +
+ +

Class: ServerManager

+ + + + + + +
+ +
+ +

+ core.ServerManager(options)

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

new ServerManager(options)

+ + + + + + +
+

This manager handles all communications between the experiment running in the participant's browser and the remote PsychoJS manager running on 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 and log.

+

Note: The Server Manager uses Promises to deal with asynchronicity, is mostly called by PsychoJS, and is not exposed to the experiment code.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +PsychoJS + + + + + + + + + + + + the PsychoJS instance
autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

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

Members

+ + + +

(readonly) Event :Symbol

+ + + + +
+ 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 + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(readonly) SaveFormat :Symbol

+ + + + +
+ Experiment result format +
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(readonly) Status :Symbol

+ + + + +
+ Server status +
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + +

Methods

+ + + + + + + +

(private) _downloadRegisteredResources()

+ + + + + + +
+ Download the resources previously registered. + +

Note: we use the preloadjs library.

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

(private) _listResources()

+ + + + + + +
+ List the resources available to the experiment. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

closeSession() → {Promise.<ServerManager.CloseSessionPromise>}

+ + + + + + +
+ Close the session for this experiment on the remote PsychoJS manager. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the response +
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.CloseSessionPromise> + + +
+
+ + + + + + + + + + + + + +

downloadResources()

+ + + + + + +
+ Asynchronously download the resources of the experiment from the remote PsychoJS manager and register them with the server manager. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

getConfiguration(configURL) → {Promise.<ServerManager.GetConfigurationPromise>}

+ + + + + + +
+ Read the configuration file for the experiment. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
configURL + + +string + + + + the URL of the configuration file
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the response +
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.GetConfigurationPromise> + + +
+
+ + + + + + + + + + + + + +

getResource(name) → {Object}

+ + + + + + +
+ Get the value of a resource. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + of the requested resource
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+ exception if no resource with that name has previously been registered +
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+ value of the resource +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + + + + +

openSession() → {Promise.<ServerManager.OpenSessionPromise>}

+ + + + + + +
+ Open a session for this experiment on the remote PsychoJS manager. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the response +
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.OpenSessionPromise> + + +
+
+ + + + + + + + + + + + + +

resetStatus() → {ServerManager.Status.READY}

+ + + + + + +
+ Reset the resource manager status to ServerManager.Status.READY. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the new status +
+ + + +
+
+ Type +
+
+ +ServerManager.Status.READY + + +
+
+ + + + + + + + + + + + + +

setStatus()

+ + + + + + +
+ Set the resource manager status. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

uploadData(key, value) → {Promise.<ServerManager.UploadDataPromise>}

+ + + + + + +
+ Asynchronously upload experiment data to the remote PsychoJS manager. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
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)
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the response +
+ + + +
+
+ Type +
+
+ +Promise.<ServerManager.UploadDataPromise> + + +
+
+ + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.Window.html b/docs/module-core.Window.html new file mode 100644 index 0000000..6058106 --- /dev/null +++ b/docs/module-core.Window.html @@ -0,0 +1,2202 @@ + + + + + JSDoc: Class: Window + + + + + + + + + + +
+ +

Class: Window

+ + + + + + +
+ +
+ +

+ core.Window(options)

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

new Window(options)

+ + + + + + +
+

Window displays the various stimuli of the experiment.

+

It sets up a PIXI renderer, which we use to render the experiment stimuli.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +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
units + + +string + + + + + + <optional>
+ + + + + +
+ + 'pix' + + the units of the window
autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

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

Methods

+ + + + + + + +

(private) _onResize(win)

+ + + + + + +
+ Treat a window resize event. + +

We adjust the size of the renderer and the position of the root container.

+

Note: since this method will be called by the DOM window (i.e. 'this' is +the DOM window), we need to pass it a Window.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
win + + +Window + + + + The PsychoJS window
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(private) _refresh()

+ + + + + + +
+ Recompute the window's _drawList and _container children for the next animation frame. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(private) _setupPixi()

+ + + + + + +
+ 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.

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

(private) _updateIfNeeded()

+ + + + + + +
+ Update this window, if need be. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(private) _writeLogOnFlip()

+ + + + + + +
+ Send all logged messages to the Logger. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

adjustScreenSize()

+ + + + + + +
+ Take the browser full screen if possible. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

callOnFlip(flipFunction, flipArgs)

+ + + + + + +
+ Specify the callback function ran after each screen flip, i.e. immedicately after each rendering of the Window. + +

This is typically used to reset a timer or clock.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
flipFunction + + +* + + + + callback function.
flipArgs + + +Object + + + + arguments for the callback function.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

close()

+ + + + + + +
+ Close the window. + +

Note: this actually only removes the canvas used to render the experiment stimuli.

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

experimentEnded()

+ + + + + + +
+ Setter for experimentEnded. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

experimentEnded()

+ + + + + + +
+ Getter for experimentEnded. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

experimentHandler()

+ + + + + + +
+ Getter for experimentHandler. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

experimentHandler()

+ + + + + + +
+ Setter for experimentHandler. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

getActualFrameRate() → {number}

+ + + + + + +
+ Estimate the frame rate. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
To Do:
+
+
    +
  • estimate the actual frame rate.
  • +
+
+ +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ always returns 60.0 at the moment +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

logOnFlip(options, level, objopt)

+ + + + + + +
+ Log a message. + +

Note: the message will be time-stamped at the next call to requestAnimationFrame.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
options + + +Object + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
msg + + +String + + + + the message to be logged
+ +
level + + +integer + + + + + + + + + + the log level
obj + + +Object + + + + + + <optional>
+ + + + + +
the object associated with the message
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

monitorFramePeriod()

+ + + + + + +
+ Getter for monitorFramePeriod. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

render()

+ + + + + + +
+ Render the stimuli onto the canvas. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.WindowMixin.html b/docs/module-core.WindowMixin.html new file mode 100644 index 0000000..b415166 --- /dev/null +++ b/docs/module-core.WindowMixin.html @@ -0,0 +1,963 @@ + + + + + JSDoc: Mixin: WindowMixin + + + + + + + + + + +
+ +

Mixin: WindowMixin

+ + + + + + +
+ +
+ +

+ core.WindowMixin

+ + +
+ +
+
+ + +

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:
+
+ + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(protected) _getHorLengthPix(length_px) → {number}

+ + + + + + +
+ Convert the given length from pixel units to the stimulus units +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
length_px + + +number + + + + the length in pixel units
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ - the length in stimulus units +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

(protected) _getLengthPix(length) → {number}

+ + + + + + +
+ Convert the given length from stimulus unit to pixel units. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
length + + +number + + + + the length in stimulus units
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
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 +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
length_px + + +number + + + + the length in pixel units
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ - the length in stimulus units +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

(protected) _getVerLengthPix(length_px) → {number}

+ + + + + + +
+ Convert the given length from pixel units to the stimulus units +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
length_px + + +number + + + + the length in pixel units
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ - the length in stimulus units +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

setUnits(unitsopt, logopt)

+ + + + + + +
+ Setter for units attribute. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
units + + +String + + + + + + <optional>
+ + + + + +
+ + this.win.units + + the units
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-core.html b/docs/module-core.html new file mode 100644 index 0000000..cc0b5ea --- /dev/null +++ b/docs/module-core.html @@ -0,0 +1,120 @@ + + + + + JSDoc: Module: core + + + + + + + + + + +
+ +

Module: core

+ + + + + + +
+ +
+ + + +
+ + + +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-data.ExperimentHandler.html b/docs/module-data.ExperimentHandler.html new file mode 100644 index 0000000..f50c28f --- /dev/null +++ b/docs/module-data.ExperimentHandler.html @@ -0,0 +1,1171 @@ + + + + + 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 + + +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

+ + + + + + + +

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 or a StairHandler, 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:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

getLoopAttributes(loop)

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

Only only info relating to the trial execution are returned.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
loop + + +Object + + + + the loop
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

nextEntry()

+ + + + + + +
+ Inform this ExperimentHandler that the current trial has ended. Further calls to addData +will be associated with the next trial. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
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. +

Results are uploaded to the remote PsychoJS manager running on the remote https://pavlovia.org server

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
attributes + + +PsychoJS + + + + the attributes to be saved
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-data.TrialHandler.html b/docs/module-data.TrialHandler.html new file mode 100644 index 0000000..db32f9d --- /dev/null +++ b/docs/module-data.TrialHandler.html @@ -0,0 +1,2203 @@ + + + + + JSDoc: Class: TrialHandler + + + + + + + + + + +
+ +

Class: TrialHandler

+ + + + + + +
+ +
+ +

+ data.TrialHandler(options)

+ +

A Trial Handler handles the importing and sequencing of conditions.

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new TrialHandler(options)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
psychoJS + + +module:core.PsychoJS + + + + + + + + + + + + the PsychoJS instance
trialList + + +Array.<Object> +| + +String + + + + + + + + + + + + if it is a string, we treat it as the name of a condition resource
nReps + + +number + + + + + + + + + + + + number of repetitions
method + + +module:data.TrialHandler.Method + + + + + + + + + + + + the trial method
extraInfo + + +Object + + + + + + + + + + + + additional information to be stored alongside the trial data, e.g. session ID, participant ID, etc.
seed + + +number + + + + + + + + + + + + seed for the random number generator
autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

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

Members

+ + + +

(static, readonly) Method :Symbol

+ + + + +
+ TrialHandler method +
+ + + +
Type:
+
    +
  • + +Symbol + + +
  • +
+ + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
SEQUENTIAL + + +Symbol + + + + Conditions are presented in the order they are given.
RANDOM + + +Symbol + + + + Conditions are shuffled within each repeat.
FULL_RANDOM + + +Symbol + + + + Conditions are fully randomised across all repeats.
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + +

Methods

+ + + + + + + +

(static) importConditions(serverManager, resourceName, selectionopt) → {Object}

+ + + + + + +
+ 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: +

    +
  • be unique
  • +
  • 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 + [1,2,3,10] + '1,5,10' + '1:2:5' + '5:' + '-5:-2, 9, 11:5:22' +

+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
serverManager + + +module:core.ServerManager + + + + + + + + + + + + the server manager
resourceName + + +String + + + + + + + + + + + + the name of the resource containing the list of conditions, which must have been registered with the server manager.
selection + + +Object + + + + + + <optional>
+ + + + + +
+ + null + + the selection
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+ Throws an exception if importing the conditions failed. +
+
+
+
+
+
+ Type +
+
+ +Object + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+ the parsed conditions as an array of 'object as map' +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + + + + +

(protected) _prepareTrialList(trialList)

+ + + + + + +
+ Prepare the trial list. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
trialList + + +Array.<Object> +| + +String + + + + if it is a string, we treat it as the name of a condition resource
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

addData(key, value)

+ + + + + + +
+ Add a key/value pair to data about the current trial held by the experiment handler +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
key + + +Object + + + + the key
value + + +Object + + + + the value
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

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.

+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the attributes +
+ + + +
+
+ Type +
+
+ +Array.string + + +
+
+ + + + + + + + + + + + + +

getCurrentTrial() → {Object}

+ + + + + + +
+ Get the current trial. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the current trial +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + + + + +

getEarlierTrial(nopt) → {Object|undefined}

+ + + + + + +
+ Get the nth previous trial. +

Note: this is useful for comparisons in n-back tasks.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
n + + +number + + + + + + <optional>
+ + + + + +
+ + -1 + + increment
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the past trial or undefined if attempting to go prior to the first trial. +
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + + + + +

getFutureTrial(nopt) → {Object|undefined}

+ + + + + + +
+ Get the nth future or past trial, without advancing through the trial list. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
n + + +number + + + + + + <optional>
+ + + + + +
+ + 1 + + increment
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
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. +
+ + + +
+
+ Type +
+
+ +Object +| + +undefined + + +
+
+ + + + + + + + + + + + + +

getTrialIndex() → {number}

+ + + + + + +
+ Get the trial index. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the current trial index +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

setTrialIndex(index)

+ + + + + + +
+ Set the trial index. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
index + + +number + + + + the new trial index
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

Symbol.iterator()

+ + + + + + +
+ Iterator over the trial sequence. + +

This makes it possible to iterate over all trials.

+
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
Example
+ +
let handler = new TrialHandler({nReps: 5});
+for (const thisTrial of handler) { console.log(thisTrial); }
+ + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-data.html b/docs/module-data.html new file mode 100644 index 0000000..b540214 --- /dev/null +++ b/docs/module-data.html @@ -0,0 +1,95 @@ + + + + + JSDoc: Module: data + + + + + + + + + + +
+ +

Module: data

+ + + + + + +
+ +
+ + + +
+ + + +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-sound.Sound.html b/docs/module-sound.Sound.html new file mode 100644 index 0000000..d1177f0 --- /dev/null +++ b/docs/module-sound.Sound.html @@ -0,0 +1,1812 @@ + + + + + JSDoc: Class: Sound + + + + + + + + + + +
+ +

Class: Sound

+ + + + + + +
+ +
+ +

+ sound.Sound(options)

+ +

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.
  • +
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new Sound(options)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + + the name used when logging messages from this stimulus
win + + +Window + + + + + + + + + + + + the associated Window
value + + +number +| + +string + + + + + + <optional>
+ + + + + +
+ + 'C' + + the sound value (see above for a full description)
octave + + +number + + + + + + <optional>
+ + + + + +
+ + 4 + + the octave corresponding to the tone (if applicable)
secs + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + + duration of the tone (in seconds)
startTime + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + + start of playback for tracks (in seconds)
stopTime + + +number + + + + + + <optional>
+ + + + + +
+ + -1 + + end of playback for tracks (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 once. If loops == -1, the track or tone will repeat indefinitely until stopped.
hamming + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to apodize the sound (i.e., the onset and offset smoothly ramped up from down to zero). This only affects tones.
autoLog + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +
Example
+ +
[...]
+const track = new Sound({
+  win: psychoJS.window,
+  value: 440,
+  secs: 0.5
+});
+track.setVolume(1.0);
+track.play(2);
+ + + + +
+ + +

Extends

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

Methods

+ + + + + + + +

(protected) _getPlayer() → {SoundPlayer}

+ + + + + + +
+ Identify the appropriate player for the sound. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + +
Throws:
+ + + +
+
+
+ exception if no appropriate SoundPlayer could be found for the sound +
+
+
+
+
+
+ Type +
+
+ +Object.<string, *> + + +
+
+
+
+
+ + + + + +
Returns:
+ + +
+ the appropriate SoundPlayer +
+ + + +
+
+ Type +
+
+ +SoundPlayer + + +
+
+ + + + + + + + + + + + + +

getDuration() → {number}

+ + + + + + +
+ Get the duration of the sound, in seconds. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the duration of the sound, in seconds +
+ + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

play(loopsopt, 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.

+
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
loops + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + + how many times to repeat the sound after it plays once. If loops == -1, the sound will repeat indefinitely until stopped.
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setLoops(loopsopt, logopt)

+ + + + + + +
+ Set the number of loops. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
loops + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + + how many times to repeat the sound after it has played once. If loops == -1, the sound will repeat indefinitely until stopped.
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setVolume(volume, muteopt, logopt)

+ + + + + + +
+ Set the playing volume of the sound. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
volume + + +number + + + + + + + + + + + + the volume (values should be between 0 and 1)
mute + + +booleam + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to mute the sound
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

stop(options)

+ + + + + + +
+ Stop playing the sound immediately. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + true + + whether or not to log
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-sound.SoundPlayer.html b/docs/module-sound.SoundPlayer.html new file mode 100644 index 0000000..1b0a9bc --- /dev/null +++ b/docs/module-sound.SoundPlayer.html @@ -0,0 +1,948 @@ + + + + + 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:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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 + + +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:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(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 + + +booleam + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to mute the tone
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

(abstract) stop()

+ + + + + + +
+ Stop playing the sound immediately. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-sound.TonePlayer.html b/docs/module-sound.TonePlayer.html new file mode 100644 index 0000000..4c1299d --- /dev/null +++ b/docs/module-sound.TonePlayer.html @@ -0,0 +1,1279 @@ + + + + + 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 + + +PsychoJS + + + + + + + + + + + + the PsychoJS instance
duration_s + + +number + + + + + + <optional>
+ + + + + +
+ + 0.5 + + duration of the tone (in seconds)
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

+ + + + + + + +

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

+ + + + + + +
+ Determine whether this player can play the given sound. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +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:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

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 + + +booleam + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to mute the tone
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

stop()

+ + + + + + +
+ Stop playing the sound immediately. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-sound.TrackPlayer.html b/docs/module-sound.TrackPlayer.html new file mode 100644 index 0000000..8f7099d --- /dev/null +++ b/docs/module-sound.TrackPlayer.html @@ -0,0 +1,1359 @@ + + + + + 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 + + +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() → {Object|undefined}

+ + + + + + +
+ Determine whether this player can play the given sound. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +module:sound.Sound + + + + the sound
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ an instance of TrackPlayer that can play the given sound 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(loopsopt)

+ + + + + + +
+ Start playing the sound. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
loops + + +boolean + + + + + + <optional>
+ + + + + +
how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
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 + + +booleam + + + + + + <optional>
+ + + + + +
+ + false + + whether or not to mute the track
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

stop()

+ + + + + + +
+ Stop playing the sound immediately. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-sound.html b/docs/module-sound.html new file mode 100644 index 0000000..9833772 --- /dev/null +++ b/docs/module-sound.html @@ -0,0 +1,105 @@ + + + + + JSDoc: Module: sound + + + + + + + + + + +
+ +

Module: sound

+ + + + + + +
+ +
+ + + +
+ + + +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.BaseShapeStim.html b/docs/module-visual.BaseShapeStim.html new file mode 100644 index 0000000..8d4b532 --- /dev/null +++ b/docs/module-visual.BaseShapeStim.html @@ -0,0 +1,2048 @@ + + + + + JSDoc: Class: BaseShapeStim + + + + + + + + + + +
+ +

Class: BaseShapeStim

+ + + + + + +
+ +
+ +

+ visual.BaseShapeStim(options)

+ +

This class provides the basic functionalities of shape stimuli.

+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new BaseShapeStim(options)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + + the name used when logging messages from this stimulus
win + + +Window + + + + + + + + + + + + the associated Window
lineWidth + + +number + + + + + + + + + + + + the line width
lineColor + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('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
+ +
+ + + + + + +
+ + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • BaseVisualStim
  • +
+ + + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(private) _getPolygon() → {Object}

+ + + + + + +
+ Get the PIXI polygon (in pixel units) corresponding to the vertices. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ the PIXI polygon corresponding to this stimulus vertices. +
+ + + +
+
+ Type +
+
+ +Object + + +
+
+ + + + + + + + + + + + + +

(private) _updateIfNeeded()

+ + + + + + +
+ Update the stimulus, if necessary. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

contains(object, units) → {boolean}

+ + + + + + +
+ Determine whether this stimulus contains the given object. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
object + + +Object + + + + the object
units + + +string + + + + the units
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + +
Returns:
+ + +
+ whether or not the stimulus contains the object +
+ + + +
+
+ Type +
+
+ +boolean + + +
+
+ + + + + + + + + + + + + +

setFillColor(fillColor, logopt)

+ + + + + + +
+ Setter for the fill color attribute. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
fillColor + + +Color + + + + + + + + + + + + the fill color
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setLineColor(lineColor, logopt)

+ + + + + + +
+ Setter for the line color attribute. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
lineColor + + +Color + + + + + + + + + + + + the line color
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setLineWidth(lineWidth, logopt)

+ + + + + + +
+ Setter for the line width attribute. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
lineWidth + + +number + + + + + + + + + + + + the line width
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +

setVertices(vertices, logopt)

+ + + + + + +
+ Setter for the vertices attribute. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
vertices + + +Array.<Array.<number>> + + + + + + + + + + + + the vertices
log + + +boolean + + + + + + <optional>
+ + + + + +
+ + false + + whether of not to log
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.BaseVisualStim.html b/docs/module-visual.BaseVisualStim.html new file mode 100644 index 0000000..8968a4c --- /dev/null +++ b/docs/module-visual.BaseVisualStim.html @@ -0,0 +1,862 @@ + + + + + JSDoc: Class: BaseVisualStim + + + + + + + + + + +
+ +

Class: BaseVisualStim

+ + + + + + +
+ +
+ +

+ visual.BaseVisualStim(options)

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

new BaseVisualStim(options)

+ + + + + + +
+ Base class for all visual stimuli. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
options + + +Object + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + + the name used when logging messages from this stimulus
win + + +Window + + + + + + + + + + + + the associated Window
units + + +string + + + + + + <optional>
+ + + + + +
+ + "norm" + + the units of the stimulus (e.g. for size, position, vertices)
ori + + +number + + + + + + <optional>
+ + + + + +
+ + 0.0 + + the orientation (in degrees)
opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + + the opacity
pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + + the position of the center of the stimulus
size + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + + the size
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
+ +
+ + + + + + +
+ + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • WindowMixin
  • + +
+ + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • MinimalStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

setOpacity

+ + + + +
+ Setter for the opacity attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setOri

+ + + + +
+ Setter for the orientation attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setPos

+ + + + +
+ Setter for the position attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setSize

+ + + + +
+ Setter for the size attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.ImageStim.html b/docs/module-visual.ImageStim.html new file mode 100644 index 0000000..49d194e --- /dev/null +++ b/docs/module-visual.ImageStim.html @@ -0,0 +1,1378 @@ + + + + + JSDoc: Class: ImageStim + + + + + + + + + + +
+ +

Class: ImageStim

+ + + + + + +
+ +
+ +

+ visual.ImageStim(options, flipHorizopt, flipVertopt)

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

new ImageStim(options, flipHorizopt, flipVertopt)

+ + + + + + +
+ Image Simulus. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
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 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 + + + + + + + + + + + + the units of the stimulus vertices, size and position
ori + + +number + + + + + + + + + + + + the orientation (in degrees)
size + + +number + + + + + + + + + + + + the size
color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('white') + + the background color
opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + + the opacity
contrast + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + + the contrast
depth + + +number + + + + + + <optional>
+ + + + + +
+ + 0 + + the depth
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
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
+ + + + + + +
+ + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • BaseVisualStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(private) _updateIfNeeded

+ + + + +
+ Update the stimulus, if necessary. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

contains

+ + + + +
+ Determine whether the given object is inside this image. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setFlipHoriz

+ + + + +
+ Setter for the flipHoriz attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setFlipVert

+ + + + +
+ Setter for the flipVert attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setImage

+ + + + +
+ Setter for the mask attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setImage

+ + + + +
+ Setter for the image attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.Rect.html b/docs/module-visual.Rect.html new file mode 100644 index 0000000..ac3b80e --- /dev/null +++ b/docs/module-visual.Rect.html @@ -0,0 +1,1096 @@ + + + + + JSDoc: Class: Rect + + + + + + + + + + +
+ +

Class: Rect

+ + + + + + +
+ +
+ +

+ visual.Rect(options)

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

new Rect(options)

+ + + + + + +
+

Rectangular 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
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 + + + + + + + + + + + + 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
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • BaseShapeStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(private) _updateVertices

+ + + + +
+ Update the base shape vertices. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setHeight

+ + + + +
+ Setter for the height attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setWidth

+ + + + +
+ Setter for the width attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.TextStim.html b/docs/module-visual.TextStim.html new file mode 100644 index 0000000..258ce0b --- /dev/null +++ b/docs/module-visual.TextStim.html @@ -0,0 +1,1651 @@ + + + + + JSDoc: Class: TextStim + + + + + + + + + + +
+ +

Class: TextStim

+ + + + + + +
+ +
+ +

+ visual.TextStim(options, alignHorizopt, alignVertopt, wrapWidth, flipHorizopt, flipVertopt)

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

new TextStim(options, alignHorizopt, alignVertopt, wrapWidth, flipHorizopt, flipVertopt)

+ + + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
options + + +Object + + + + + + + + + + + + +
Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDefaultDescription
name + + +String + + + + + + + + + + + + the name used when logging messages from this stimulus
text + + +string + + + + + + <optional>
+ + + + + +
+ + "Hello World" + + the text to be rendered
font + + +string + + + + + + <optional>
+ + + + + +
+ + "Arial" + + the text font
pos + + +Array.<number> + + + + + + <optional>
+ + + + + +
+ + [0, 0] + + the position of the center of the text
color + + +Color + + + + + + <optional>
+ + + + + +
+ + Color('white') + + the background color
opacity + + +number + + + + + + <optional>
+ + + + + +
+ + 1.0 + + the opacity
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>
+ + + + + +
+ + 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 wrapthe 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
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
+ + + + + + +
+ + + + + + + + + + + + + + +
Mixes In:
+ +
    + +
  • ColorMixin
  • + +
+ + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
To Do:
+
+
    +
  • vertical alignment, and orientation are currently NOT implemented
  • +
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + +

Extends

+ + + + +
    +
  • BaseVisualStim
  • +
+ + + + + + + + + + + + + + + +

Members

+ + + +

(private) _updateIfNeeded

+ + + + +
+ Update the stimulus, if necessary. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
To Do:
+
+
    +
  • take size into account
  • +
+
+ +
+ + + + + + + + +

contains

+ + + + +
+ Determine whether an object is inside the bounding box of the text. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
To Do:
+
+
    +
  • this is currently NOT implemented
  • +
+
+ +
+ + + + + + + + +

setAlignHoriz

+ + + + +
+ Setter for the alignHoriz attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setBold

+ + + + +
+ Setter for the bold attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setFlipHoriz

+ + + + +
+ Setter for the flipHoriz attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setFlipVert

+ + + + +
+ Setter for the flipVert attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setHeight

+ + + + +
+ Setter for the height attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setItalic

+ + + + +
+ Setter for the italic attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setText

+ + + + +
+ Setter for the text attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

setWrapWidth

+ + + + +
+ Setter for the wrapWidth attribute. +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/module-visual.html b/docs/module-visual.html new file mode 100644 index 0000000..fac2912 --- /dev/null +++ b/docs/module-visual.html @@ -0,0 +1,104 @@ + + + + + JSDoc: Module: visual + + + + + + + + + + +
+ +

Module: visual

+ + + + + + +
+ +
+ + + +
+ + + +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/scripts/linenumber.js b/docs/scripts/linenumber.js new file mode 100644 index 0000000..8d52f7e --- /dev/null +++ b/docs/scripts/linenumber.js @@ -0,0 +1,25 @@ +/*global document */ +(function() { + var source = document.getElementsByClassName('prettyprint source linenums'); + var i = 0; + var lineNumber = 0; + var lineId; + var lines; + var totalLines; + var anchorHash; + + if (source && source[0]) { + anchorHash = document.location.hash.substring(1); + lines = source[0].getElementsByTagName('li'); + totalLines = lines.length; + + for (; i < totalLines; i++) { + lineNumber++; + lineId = 'line' + lineNumber; + lines[i].id = lineId; + if (lineId === anchorHash) { + lines[i].className += ' selected'; + } + } + } +})(); diff --git a/docs/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/docs/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js new file mode 100644 index 0000000..041e1f5 --- /dev/null +++ b/docs/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", +/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); diff --git a/docs/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js new file mode 100644 index 0000000..eef5ad7 --- /dev/null +++ b/docs/scripts/prettify/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p + + + + JSDoc: Source: sound/Sound.js + + + + + + + + + + +
+ +

Source: sound/Sound.js

+ + + + + + +
+
+
/** @module sound */
+/**
+ * Sound stimulus.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @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';
+
+
+/**
+ * <p>This class handles sound playing (tones and tracks)</p>
+ * 
+ * <ul>
+ * <li> If value is a number then a tone will be generated at that frequency in Hz.</li>
+ * <li> 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.</li>
+ * </ul>
+ * 
+ * @example
+ * [...]
+ * const track = new Sound({
+ *   win: psychoJS.window,
+ *   value: 440,
+ *   secs: 0.5
+ * });
+ * 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 {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)
+ * @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.hamming= true] whether or not to apodize the sound (i.e., the onset and offset smoothly ramped up from down to zero). This only affects tones.
+ * @param {boolean} [options.autoLog= true] whether or not to log
+*/
+export class Sound extends PsychObject {
+	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
+	} = {}) {
+		super(win._psychoJS, name);
+
+		// the SoundPlayer, e.g. TonePlayer:
+		this._player = undefined;
+
+		this._addAttributes(Sound, win, value, octave, secs, startTime, stopTime, stereo, volume, loops, hamming, autoLog);
+
+		// identify an appropriate player:
+		this._getPlayer();
+
+		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= 0] 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
+	 */
+	play(loops, log = true) {
+		this.status = PsychoJS.Status.STARTED;
+		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
+	} = {}) {
+		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() {
+		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 {booleam} [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);
+		if (typeof this._player !== 'undefined')
+			this._player.setVolume(volume, mute);
+	}
+
+
+	/**
+	 * 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);
+		if (typeof this._player !== 'undefined')
+			this._player.setLoops(loops);
+	}
+
+
+	/**
+	 * Identify the appropriate player for the sound.
+	 *
+	 * @protected
+	 * @return {SoundPlayer} the appropriate SoundPlayer
+	 * @throws {Object.<string, *>} exception if no appropriate SoundPlayer could be found for the sound
+	 */
+	_getPlayer() {
+		const acceptFns = [
+			sound => TonePlayer.accept(sound),
+			sound => TrackPlayer.accept(sound)
+		];
+
+		for (const acceptFn of acceptFns) {
+			this._player = acceptFn(this);
+			if (typeof this._player !== 'undefined')
+				return;
+		}
+
+		throw { origin: 'SoundPlayer._getPlayer', context: 'when finding a player for the sound', error: 'could not find an appropriate player.' };
+	}
+
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/sound_SoundPlayer.js.html b/docs/sound_SoundPlayer.js.html new file mode 100644 index 0000000..cd61e20 --- /dev/null +++ b/docs/sound_SoundPlayer.js.html @@ -0,0 +1,169 @@ + + + + + JSDoc: Source: sound/SoundPlayer.js + + + + + + + + + + +
+ +

Source: sound/SoundPlayer.js

+ + + + + + +
+
+
/**
+ * Sound player interface
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { PsychObject } from '../util/PsychObject';
+
+
+/**
+ * <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
+{
+	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
+	 */
+	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.'};
+	}
+
+
+	/**
+	 * Start playing the sound.
+	 *
+	 * @name module:sound.SoundPlayer#play
+	 * @function
+	 * @public
+	 * @abstract
+	 * @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)
+	{
+		throw {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.'};
+	}
+
+
+	/**
+	 * 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.'};
+	}
+
+
+	/**
+	 * 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.'};
+	}
+
+
+	/**
+	 * Set the volume of the tone.
+	 * 
+	 * @name module:sound.SoundPlayer#setVolume
+	 * @function
+	 * @public
+	 * @abstract
+	 * @param {Integer} volume - the volume of the tone
+	 * @param {booleam} [mute= false] - whether or not to mute the tone
+	 */
+	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.'};
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/sound_TonePlayer.js.html b/docs/sound_TonePlayer.js.html new file mode 100644 index 0000000..bf918d3 --- /dev/null +++ b/docs/sound_TonePlayer.js.html @@ -0,0 +1,265 @@ + + + + + JSDoc: Source: sound/TonePlayer.js + + + + + + + + + + +
+ +

Source: sound/TonePlayer.js

+ + + + + + +
+
+
/**
+ * Tone Player.
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { SoundPlayer } from './SoundPlayer';
+
+
+/**
+ * <p>This class handles the playing of tones.</p>
+ * 
+ * @name module:sound.TonePlayer
+ * @class
+ * @extends SoundPlayer
+ * @param {Object} options
+ * @param {PsychoJS} options.psychoJS - the PsychoJS instance
+ * @param {number} [options.duration_s= 0.5] - duration of the tone (in seconds)
+ * @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 {
+	constructor({
+		psychoJS,
+		note = 'C4',
+		duration_s = 0.5,
+		volume = 1.0,
+		loops = 0
+	} = {}) {
+		super(psychoJS);
+
+		this._addAttributes(TonePlayer, note, duration_s, volume, loops);
+
+
+		// create a synth: we use a triangular oscillator with hardly any envelope:
+		this._synthOtions = {
+			oscillator: {
+				type: 'triangle'
+			},
+			envelope: {
+				attack: 0.001,
+				decay: 0.001,
+				sustain: 1,
+				release: 0.001
+			}
+		};
+		this._synth = new Tone.Synth(this._synthOtions);
+
+		// connect it to a volume node:
+		this._volumeNode = new Tone.Volume(-60 + volume * 66);
+		this._synth.connect(this._volumeNode);
+
+		// connect the volume node to the master output:
+		this._volumeNode.toMaster();
+
+		// tonejs Loop:
+		this._toneLoop = null;
+	}
+
+
+	/**
+	 * Determine whether this player can play the given sound.
+	 *
+	 * @name module:sound.TonePlayer.accept
+	 * @function
+	 * @static
+	 * @public
+	 * @param {module: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)) {
+			// build the player:
+			const player = new TonePlayer({
+				psychoJS: sound.psychoJS,
+				note: sound.value,
+				duration_s: sound.secs,
+				volume: sound.volume,
+				loops: sound.loops
+			});
+			return player;
+		}
+
+		// if the sound's value is a string, we check whether it is a note:
+		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']) {
+				psychopyToToneMap.set(note, 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') {
+				// build the player:
+				const player = new TonePlayer({
+					psychoJS: sound.psychoJS,
+					note: note + sound.octave,
+					duration_s: sound.secs,
+					volume: sound.volume,
+					loops: sound.loops
+				});
+				return player;
+			}
+		}
+
+		// TonePlayer is not an appropriate player for the given sound:
+		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()
+	{
+		return this.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)
+	{
+		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 {booleam} [mute= false] - whether or not to mute the tone
+	 */
+	setVolume(volume, mute = false) {
+		this._volume = volume;
+
+		if (typeof this._volumeNode !== 'undefined') {
+			this._volumeNode.mute = mute;
+			this._synth.volume.value = -60 + volume * 66;
+		}
+	}
+
+
+	/**
+	 * 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')
+			this._loops = loops;
+
+		const self = this;
+		const callback = time => { self._synth.triggerAttackRelease(self._note, self.duration_s, Tone.now()); };
+
+		if (this.loops == 0)
+			this._toneId = Tone.Transport.scheduleOnce(callback, Tone.now());
+		else if (this.loops == -1)
+			this._toneId = Tone.Transport.scheduleRepeat(
+				callback,
+				this.duration_s,
+				Tone.now(),
+				Tone.Infinity
+			);
+		else
+			this._toneId = Tone.Transport.scheduleRepeat(
+				callback,
+				this.duration_s,
+				Tone.now(),
+				this.duration_s * (this._loops+1)
+			);
+}
+
+
+	/**
+	 * Stop playing the sound immediately.
+	 *
+	 * @name module:sound.TonePlayer#stop
+	 * @function
+	 * @public
+	 */
+	stop() {
+		if (this._toneId)
+			Tone.Transport.clear(this._toneId);
+	}
+}
+
+// Start the Tone Transport
+Tone.Transport.start(Tone.now());
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/sound_TrackPlayer.js.html b/docs/sound_TrackPlayer.js.html new file mode 100644 index 0000000..ac07ce6 --- /dev/null +++ b/docs/sound_TrackPlayer.js.html @@ -0,0 +1,227 @@ + + + + + JSDoc: Source: sound/TrackPlayer.js + + + + + + + + + + +
+ +

Source: sound/TrackPlayer.js

+ + + + + + +
+
+
/**
+ * Track Player.
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+import { SoundPlayer } from './SoundPlayer';
+
+
+/**
+ * <p>This class handles the playback of sound tracks.</p>
+ * 
+ * @name module:sound.TrackPlayer
+ * @class
+ * @extends SoundPlayer
+ * @param {Object} options
+ * @param {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 {
+	constructor({
+		psychoJS,
+		howl,
+		startTime = 0,
+		stopTime = -1,
+		stereo = true,
+		volume = 0,
+		loops = 0
+	} = {}) {
+		super(psychoJS);
+
+		this._addAttributes(TrackPlayer, howl, startTime, stopTime, stereo, loops, 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} - the sound
+	 * @return {Object|undefined} an instance of TrackPlayer that can play the given sound or undefined otherwise
+	 */
+	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') {
+			const howl = sound.psychoJS.serverManager.getResource(sound.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;
+			}
+		}
+
+		// TonePlayer is not an appropriate player for the given sound:
+		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()
+	{
+		return this._howl.duration();
+	}
+
+
+	/**
+	 * 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 {booleam} [mute= false] - whether or not to mute the track
+	 */
+	setVolume(volume, mute = false) {
+		this._volume = volume;
+		
+		this._howl.volume(volume);
+		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)
+	{
+		this._loops = loops;
+		this._currentLoopIndex = -1;
+
+		if (loops == 0)
+			this._howl.loop(false);
+		else
+			this._howl.loop(true);
+	}
+
+
+	/**
+	 * Start playing the sound.
+	 *
+	 * @name module:sound.TrackPlayer#play
+	 * @function
+	 * @public
+	 * @param {boolean} [loops] how many times to repeat the track after it has played once. If loops == -1, the track will repeat indefinitely until stopped.
+	 */
+	play(loops) {
+		if (typeof loops !== 'undefined')
+			this.setLoops(loops);
+
+		// handle repeats:
+		if (loops > 0) {
+			const self = this;
+			this._howl.on('end', (event) => {
+				++this._currentLoopIndex;
+				if (self._currentLoopIndex > self._loops)
+					self.stop();
+				else {
+					self._howl.seek(self._startTime);
+					self._howl.play();
+				}
+			});
+		}
+
+		this._howl.seek(this._startTime);
+		this._howl.play();
+	}
+
+
+	/**
+	 * Stop playing the sound immediately.
+	 *
+	 * @name module:sound.TrackPlayer#stop
+	 * @function
+	 * @public
+	 */
+	stop() {
+		this._howl.stop();
+		this._howl.off('end');
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css new file mode 100644 index 0000000..9207bc8 --- /dev/null +++ b/docs/styles/jsdoc-default.css @@ -0,0 +1,358 @@ +@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; +} + +.prettyprint +{ + border: 1px solid #ddd; + width: 80%; + overflow: auto; +} + +.prettyprint.source { + width: inherit; +} + +.prettyprint 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/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css new file mode 100644 index 0000000..5a2526e --- /dev/null +++ b/docs/styles/prettify-jsdoc.css @@ -0,0 +1,111 @@ +/* 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 new file mode 100644 index 0000000..b6f92a7 --- /dev/null +++ b/docs/styles/prettify-tomorrow.css @@ -0,0 +1,132 @@ +/* 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/visual_BaseShapeStim.js.html b/docs/visual_BaseShapeStim.js.html new file mode 100644 index 0000000..daec042 --- /dev/null +++ b/docs/visual_BaseShapeStim.js.html @@ -0,0 +1,290 @@ + + + + + JSDoc: Source: visual/BaseShapeStim.js + + + + + + + + + + +
+ +

Source: visual/BaseShapeStim.js

+ + + + + + +
+
+
/** @module visual */
+/**
+ * Basic Shape Stimulus.
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { BaseVisualStim } from './BaseVisualStim';
+import { Color } from '../util/Color';
+import { ColorMixin } from '../util/ColorMixin';
+import * as util from '../util/Util';
+
+
+/**
+ * <p>This class provides the basic functionalities of shape stimuli.</p>
+ * 
+ * @class
+ * @extends BaseVisualStim
+ * @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 {number} options.lineWidth - 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 {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 BaseShapeStim extends util.mix(BaseVisualStim).with(ColorMixin)
+{
+	/**
+	 * @constructor
+	 * @public
+	 */
+	constructor({
+		name,
+		win,
+		lineWidth = 1.5,
+		lineColor = new Color('white'),
+		fillColor,
+		opacity = 1.0,
+		vertices = [[-0.5, 0], [0, 0.5], [0.5, 0]],
+		closeShape = true,
+		pos = [0, 0],
+		size = 1.0,
+		ori = 0.0,
+		units,
+		contrast = 1.0,
+		depth = 0,
+		interpolate = true,
+		autoDraw,
+		autoLog
+	} = {}) {
+		super({ name, win, units, ori, opacity, pos, size, autoDraw, autoLog });
+
+		// the PIXI polygon corresponding to the vertices, in pixel units:
+		this._pixiPolygon_px = undefined;
+
+		this._addAttributes(BaseShapeStim, lineWidth, lineColor, fillColor, vertices, closeShape, contrast, depth, interpolate);
+
+		/*if (autoLog)
+			logging.exp("Created %s = %s" % (self.name, str(self)));*/
+	}
+
+
+	/**
+	 * Setter for the line width attribute.
+	 *
+	 * @public 
+	 * @param {number} lineWidth - the line width
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setLineWidth(lineWidth, log = false) {
+		this._setAttribute('lineWidth', lineWidth, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the line color attribute.
+	 *
+	 * @public 
+	 * @param {Color} lineColor - the line color
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setLineColor(lineColor, log = false) {
+		this._setAttribute('lineColor', lineColor, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the fill color attribute.
+	 *
+	 * @public 
+	 * @param {Color} fillColor - the fill color
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setFillColor(fillColor, log = false) {
+		this._setAttribute('fillColor', fillColor, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the vertices attribute.
+	 *
+	 * @public 
+	 * @param {Array.<Array.<number>>} vertices - the vertices
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setVertices(vertices, log = false) {
+		this._psychoJS.logger.debug('set the vertices of BaseShapeStim:', this.name);
+
+		this._setAttribute('vertices', vertices, log);
+		/*this._setAttribute({
+			name: 'vertices',
+			value: vertices,
+			assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) )
+			log);
+		*/
+
+		this._needVertexUpdate = true;
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Determine whether this stimulus contains the given object.
+	 * 
+	 * @public
+	 * @param {Object} object - the object
+	 * @param {string} units - the units
+	 * @return {boolean} whether or not the stimulus contains the object
+	 */
+	contains(object, units) {
+		this._psychoJS.logger.debug('test whether BaseShameStim:', this.name, 'contains object: ', ('name' in object) ? object.name : object);
+
+		// get position of object:
+		const objectPos_px = util.getPositionFromObject(object, units);
+		if (typeof objectPos_px === 'undefined')
+			throw { origin : 'BaseShapeStim.contains', context : 'when determining whether BaseShameStim: ' + this._name + ' contains object: ' + util.toString(object), error : 'unable to determine the position of the object' };
+
+		// test for inclusion
+		// note: the vertices are centered around (0, 0) so we need to add to them the stimulus' position
+		const pos_px = util.to_px(this.pos, this.units, this.win);
+		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);
+	}
+
+
+	/**
+	 * Update the stimulus, if necessary.
+	 * 
+	 * @private
+	 */
+	_updateIfNeeded() {
+		if (!this._needUpdate)
+			return;
+		this._needUpdate = false;
+
+		this._getPolygon(/*true*/); // this also updates _vertices_px
+
+		this._pixi = undefined;
+
+		// no polygon to draw: return immediately
+		if (typeof this._pixiPolygon_px === 'undefined')
+			return;
+
+		// 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._pixi.beginFill(this._fillColor.int, this._opacity);
+		this._pixi.drawPolygon(this._pixiPolygon_px);
+		if (typeof this._fillColor !== 'undefined')
+			this._pixi.endFill();
+
+		// 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;
+	}
+
+
+	/**
+	 * Get the PIXI polygon (in pixel units) corresponding to the vertices.
+	 * 
+	 * @private
+	 * @return {Object} the PIXI polygon corresponding to this stimulus vertices.
+	 */
+	_getPolygon(/*force = false*/) {
+		if (!this._needVertexUpdate)
+			return;
+		this._needVertexUpdate = false;
+
+		//if (!force && typeof this._pixiPolygon_px !== 'undefined')
+		//	return this._pixiPolygon_px;
+
+		// make sure the vertices in pixel units are available, and flatten the array of arrays:
+		this._getVertices_px(/*force*/);
+		let coords_px = [];
+		for (const vertex_px of this._vertices_px)
+			coords_px.push.apply(coords_px, vertex_px);
+
+		// close the polygon if need be:
+		if (coords_px.length >= 6 && this._closeShape) {
+			// note: we first check whether the vertices already define a closed polygon:
+			const n = coords_px.length;
+			if (coords_px[0] != coords_px[n - 2] || coords_px[1] != coords_px[n - 1]) {
+				coords_px.push(coords_px[0]);
+				coords_px.push(coords_px[1]);
+			}
+		}
+
+		// create the PIXI polygon:
+		this._pixiPolygon_px = new PIXI.Polygon(coords_px);
+		return this._pixiPolygon_px;
+	}
+
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/visual_BaseVisualStim.js.html b/docs/visual_BaseVisualStim.js.html new file mode 100644 index 0000000..0bb1df9 --- /dev/null +++ b/docs/visual_BaseVisualStim.js.html @@ -0,0 +1,214 @@ + + + + + JSDoc: Source: visual/BaseVisualStim.js + + + + + + + + + + +
+ +

Source: visual/BaseVisualStim.js

+ + + + + + +
+
+
/**
+ * Base class for all visual stimuli.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import {MinimalStim} from '../core/MinimalStim';
+import {WindowMixin} from '../core/WindowMixin';
+import * as util from '../util/Util';
+
+
+/**
+ * Base class for all visual stimuli.
+ * 
+ * @name module:visual.BaseVisualStim
+ * @class
+ * @extends MinimalStim
+ * @mixes WindowMixin
+ * @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} [options.units= "norm"] - 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 {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
+ * @param {number} [options.size= 1.0] - the size
+ * @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 BaseVisualStim extends util.mix(MinimalStim).with(WindowMixin)
+{
+	constructor({
+		name,
+		win,
+		units = 'norm',
+		ori = 0.0,
+		opacity = 1.0,
+		pos = [0, 0],
+		size,
+		autoDraw,
+		autoLog = false
+	} = {})
+	{
+		super({win, name, autoDraw, autoLog});
+
+		// whether the vertices need to be updated:
+		this._needVertexUpdate = true;
+		// the vertices (in pixel units):
+		this._vertices_px = undefined;
+
+		this._addAttributes(BaseVisualStim, units, ori, opacity, pos, size);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the size attribute.
+	 * 
+	 * @name module:visual.BaseVisualStim#setSize
+	 * @public 
+	 * @param {number} size - the stimulus size
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setSize(size, log = false)
+	{
+		// size is either undefined or a tuple of numbers:
+		if (typeof size !== 'undefined') {
+			size = util.toNumerical(size);
+			if (!Array.isArray(size))
+				size = [size, size];
+		}
+
+		this._setAttribute('size', size, log);
+
+		this._needVertexUpdate = true;
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the orientation attribute.
+	 * 
+	 * @name module:visual.BaseVisualStim#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)
+	{
+		this._setAttribute('ori', ori, log);
+
+		let radians = ori * 0.017453292519943295;
+		this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)],
+								[Math.sin(radians), Math.cos(radians)]];
+
+		//this._needVertexUpdate = true ; // need to update update vertices
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the position attribute.
+	 * 
+	 * @name module:visual.BaseVisualStim#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)
+	{
+		this._setAttribute('pos', util.toNumerical(pos), log);
+
+		this._needUpdate = true;
+	}
+
+	
+	/**
+	 * Setter for the opacity attribute.
+	 * 
+	 * @name module:visual.BaseVisualStim#setOpacity
+	 * @public
+	 * @param {number} opacity - the opacity: 0 is completely transparent, 1 is fully opaque 
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setOpacity(opacity, log = false)
+	{
+		this._setAttribute('opacity', opacity, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/*
+	 * Get the vertices in pixel units.
+	 * 
+	 * @name module:visual.BaseVisualStim#_getVertices_px
+	 * @private
+	 * @return {Array.<[number, number]>} the vertices
+	 */
+	_getVertices_px(/*force = false*/)
+	{
+		/*if (!force && typeof this._vertices_px !== 'undefined')
+			return this._vertices_px;*/
+
+		// handle flipping:
+		let flip = [1.0, 1.0];
+		if ('_flipHoriz' in this && this._flipHoriz)
+			flip[0] = -1.0;
+		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) );
+
+		return this._vertices_px;
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/visual_ImageStim.js.html b/docs/visual_ImageStim.js.html new file mode 100644 index 0000000..1d6c650 --- /dev/null +++ b/docs/visual_ImageStim.js.html @@ -0,0 +1,332 @@ + + + + + JSDoc: Source: visual/ImageStim.js + + + + + + + + + + +
+ +

Source: visual/ImageStim.js

+ + + + + + +
+
+
/**
+ * Image Stimulus.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { BaseVisualStim } from './BaseVisualStim';
+import { Color } from '../util/Color';
+import { ColorMixin } from '../util/ColorMixin';
+import * as util from '../util/Util';
+
+
+/**
+ * Image Simulus.
+ * 
+ * @name module:visual.ImageStim
+ * @class
+ * @extends BaseVisualStim
+ * @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 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 - the units of the stimulus vertices, size and position
+ * @param {number} options.ori - the orientation (in degrees)
+ * @param {number} options.size - the size
+ * @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 {number} [options.depth= 0] - the depth
+ * @param {number} [options.texRes= 128] - the resolution of the text
+ * @param {boolean} [options.interpolate= false] - whether or not the image is interpolated
+ * @param {boolean} [flipHoriz= false] - whether or not to flip horizontally
+ * @param {boolean} [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(BaseVisualStim).with(ColorMixin)
+{
+	constructor({
+		name,
+		win,
+		image,
+		mask,
+		pos,
+		units,
+		ori,
+		size,
+		color = new Color('white'),
+		opacity = 1.0,
+		contrast = 1.0,
+		texRes = 128,
+		depth = 0,
+		interpolate = false,
+		flipHoriz = false,
+		flipVert = false,
+		autoDraw,
+		autoLog
+	} = {}) {
+		super({ name, win, units, ori, opacity, pos, size, autoDraw, autoLog });
+
+		this.psychoJS.logger.debug('create a new ImageStim with name: ', name);
+
+		this._addAttributes(ImageStim, image, mask, color, contrast, texRes, interpolate, depth, flipHoriz, flipVert);
+
+		/*if (autoLog)
+			logging.exp("Created %s = %s" % (self.name, str(self)));*/
+	}
+
+
+	/**
+	 * 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) {
+		let response = { 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 sympton of an actual problem
+			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');
+			}
+			else {
+				// image is a string: it should be the name of a resource, which we load
+				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))
+					throw 'the argument: ' + image.toString() + ' is not an image" }';
+
+				this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: src= ' + image.src + ', size= ' + image.width + 'x' + image.height);
+			}
+
+			this._setAttribute('image', image, log);
+
+			this._needUpdate = true;
+		}
+		catch (error) {
+			throw { ...response, error };
+		}
+	}
+
+
+	/**
+	 * Setter for the mask attribute.
+	 * 
+	 * @name module:visual.ImageStim#setImage
+	 * @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) {
+		let response = { 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') {
+				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')
+					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 ImageStim: ' + this._name + ' as: src= ' + mask.src + ', size= ' + mask.width + 'x' + mask.height);
+			}
+
+			this._setAttribute('mask', mask, log);
+
+			this._needUpdate = true;
+		}
+		catch (error) {
+			throw { ...response, error };
+		}
+	}
+
+
+	/**
+	 * Setter for the flipVert attribute.
+	 * 
+	 * @name module:visual.ImageStim#setFlipVert
+	 * @public
+	 * @param {boolean} flipVert - whether or not to flip vertically
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setFlipVert(flipVert, log = false) {
+		this._setAttribute('flipVert', flipVert, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the flipHoriz attribute.
+	 * 
+	 * @name module:visual.ImageStim#setFlipHoriz
+	 * @public
+	 * @param {boolean} flipHoriz - whether or not to flip horizontally
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setFlipHoriz(flipHoriz, log = false) {
+		this._setAttribute('flipHoriz', flipHoriz, log);
+
+		this._needUpdate = true;
+	}
+
+
+	/**
+	 * Determine whether the given object is inside this image.
+	 * 
+	 * @name module:visual.ImageStim#contains
+	 * @public
+	 * @param {Object} object - the object
+	 * @param {string} units - the units
+	 * @return {boolean} whether or not the image contains the object
+	 */
+	contains(object, units) {
+		// get position of object:
+		let objectPos_px = util.getPositionFromObject(object, units);
+		if (typeof objectPos_px === 'undefined')
+			throw { origin : 'ImageStim.contains', context : 'when determining whether ImageStim: ' + this._name + ' contains object: ' + util.toString(object), error : 'unable to determine the position of the object' };
+
+		// test for inclusion:
+		// note: since _pixi.anchor is [0.5, 0.5] the image is actually centered on pos
+		let pos_px = util.to_px(this.pos, this.units, this._win);
+		let size_px = util.to_px(this.size, this.units, this._win);
+		const polygon_px = [
+			[pos_px[0] - size_px[0] / 2, pos_px[1] - size_px[1] / 2],
+			[pos_px[0] + size_px[0] / 2, pos_px[1] - size_px[1] / 2],
+			[pos_px[0] + size_px[0] / 2, pos_px[1] + size_px[1] / 2],
+			[pos_px[0] - size_px[0] / 2, pos_px[1] + size_px[1] / 2]];
+
+		return util.IsPointInsidePolygon(objectPos_px, polygon_px);
+	}
+
+
+	/**
+	 * Update the stimulus, if necessary.
+	 * 
+	 * @name module:visual.ImageStim#_updateIfNeeded
+	 * @private
+	 */
+	_updateIfNeeded() {
+		if (!this._needUpdate)
+			return;
+		this._needUpdate = false;
+
+		this._pixi = undefined;
+
+		// no image to draw: return immediately
+		if (typeof this._image === 'undefined')
+			return;
+
+		// prepare the image:
+		this._texture = new PIXI.Texture(new PIXI.BaseTexture(this._image));
+		//this._texture = new PIXI.Texture(PIXI.BaseTexture.fromImage(this._image));
+		this._pixi = new PIXI.Sprite(this._texture);
+		this._pixi.zOrder = this.depth;
+
+		// add a mask if need be:
+		if (typeof this._mask !== 'undefined') {
+			this._maskTexture = new PIXI.Texture(new PIXI.BaseTexture(this._mask));
+			this._pixi.mask = new PIXI.Sprite(this._maskTexture); //PIXI.Sprite.fromImage(this._mask);
+
+			// the following is required for the mask to be aligned with the image
+			this._pixi.mask.anchor.x = 0.5;
+			this._pixi.mask.anchor.y = 0.5;
+			this._pixi.addChild(this._pixi.mask);
+		}
+
+		// since _texture.width may not be immedialy available but the rest of the code needs its value
+		// we arrange for repeated calls to _updateIfNeeded until we have a width:
+		if (this._texture.width == 0) {
+			this._needUpdate = true;
+			return;
+		}
+
+		this._pixi.alpha = this.opacity;
+
+		// stimulus size:
+		// note: we use the size of the texture if ImageStim has no specified size:
+		let stimSize = this.size;
+		if (typeof stimSize === 'undefined') {
+			const textureSize = [this._texture.width, this._texture.height];
+			stimSize = util.to_unit(textureSize, 'pix', this.win, this.units);
+		}
+
+		// set the scale:
+		const size_px = util.to_px(stimSize, this.units, this.win);
+		var scaleX = size_px[0] / this._texture.width;
+		var scaleY = size_px[1] / this._texture.height;
+		this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX;
+		this._pixi.scale.y = this.flipVert ? scaleY : -scaleY;
+
+		this._pixi.position = util.to_pixiPoint(this.pos, this.units, this.win);
+		this._pixi.rotation = this.ori * Math.PI / 180;
+		// the image is centered on pos:
+		this._pixi.anchor.x = 0.5;
+		this._pixi.anchor.y = 0.5;
+	}
+
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/visual_Rect.js.html b/docs/visual_Rect.js.html new file mode 100644 index 0000000..4638a60 --- /dev/null +++ b/docs/visual_Rect.js.html @@ -0,0 +1,173 @@ + + + + + JSDoc: Source: visual/Rect.js + + + + + + + + + + +
+ +

Source: visual/Rect.js

+ + + + + + +
+
+
/**
+ * Rectangular Stimulus.
+ *
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { BaseShapeStim } from './BaseShapeStim';
+import { Color } from '../util/Color';
+
+
+/**
+ * <p>Rectangular visual stimulus.</p>
+ * 
+ * @name module:visual.Rect
+ * @class
+ * @extends BaseShapeStim
+ * @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.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 - 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 BaseShapeStim {
+	constructor({
+		name,
+		win,
+		lineWidth = 1.5,
+		lineColor = new Color('white'),
+		fillColor,
+		opacity = 1.0,
+		width = 0.5,
+		height = 0.5,
+		pos = [0, 0],
+		size = 1.0,
+		ori = 0.0,
+		units,
+		contrast = 1.0,
+		depth = 0,
+		interpolate = true,
+		autoDraw,
+		autoLog
+	} = {}) {
+		super({ name, win, lineWidth, lineColor, fillColor, opacity, pos, ori, size, units, contrast, depth, interpolate, autoDraw, autoLog });
+
+		this._psychoJS.logger.debug('create a new Rect with name: ', name);
+
+		this._addAttributes(Rect, width, height);
+
+		this._updateVertices();
+
+		/*if (autoLog)
+			logging.exp("Created %s = %s" % (self.name, str(self)));*/
+	}
+
+
+	/**
+	 * Setter for the width attribute.
+	 *
+	 * @name module:visual.Rect#setWidth
+	 * @public 
+	 * @param {number} width - the rectange 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._setAttribute('width', width, log);
+		this._updateVertices();
+	}
+
+
+	/**
+	 * Setter for the height attribute.
+	 *
+	 * @name module:visual.Rect#setHeight
+	 * @public 
+	 * @param {number} height - the rectange 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._setAttribute('height', height, log);
+		this._updateVertices();
+	}
+
+
+	/**
+	 * Update the base shape vertices.
+	 * 
+	 * @name module:visual.Rect#_updateVertices
+	 * @private 
+	 */
+	_updateVertices() {
+		this._psychoJS.logger.debug('update the vertices of Rect: ', this.name);
+
+		this.setVertices([
+			[-this._width / 2.0, -this._height / 2.0],
+			[this._width / 2.0, -this._height / 2.0],
+			[this._width / 2.0, this._height / 2.0],
+			[-this._width / 2.0, this._height / 2.0]
+		]);
+	}
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + + diff --git a/docs/visual_TextStim.js.html b/docs/visual_TextStim.js.html new file mode 100644 index 0000000..20f08a5 --- /dev/null +++ b/docs/visual_TextStim.js.html @@ -0,0 +1,327 @@ + + + + + JSDoc: Source: visual/TextStim.js + + + + + + + + + + +
+ +

Source: visual/TextStim.js

+ + + + + + +
+
+
/**
+ * Text Stimulus.
+ * 
+ * @author Alain Pitiot
+ * @version 3.0.0b11
+ * @copyright (c) 2018 Ilixa Ltd. ({@link http://ilixa.com})
+ * @license Distributed under the terms of the MIT License
+ */
+
+
+import { BaseVisualStim } from './BaseVisualStim';
+import { Color } from '../util/Color';
+import { ColorMixin } from '../util/ColorMixin';
+import * as util from '../util/Util';
+
+
+/**
+ * @name module:visual.TextStim
+ * @class
+ * @extends BaseVisualStim
+ * @mixes ColorMixin
+ * @param {Object} options
+ * @param {String} options.name - the name used when logging messages from this stimulus
+ * @param {string} [options.text="Hello World"] - the text to be rendered 
+ * @param {string} [options.font= "Arial"] - the text font
+ * @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.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] - 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} [alignHoriz = 'center'] - horizontal alignment
+ * @param {string} [alignVert = 'center'] - vertical alignment
+ * @param {boolean} wrapWidth - whether or not to wrapthe text horizontally
+ * @param {boolean} [flipHoriz= false] - whether or not to flip the text horizontally
+ * @param {boolean} [flipVert= false] - whether or not to flip the text 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
+ * 
+ * @todo vertical alignment, and orientation are currently NOT implemented
+ */
+export class TextStim extends util.mix(BaseVisualStim).with(ColorMixin)
+{
+	constructor({
+		name,
+		win,
+		text = 'Hello World',
+		font = 'Arial',
+		pos,
+		color = new Color('white'),
+		opacity,
+		contrast = 1.0,
+		units = 'norm',
+		ori,
+		height,
+		bold = false,
+		italic = false,
+		alignHoriz = 'center',
+		alignVert = 'center',
+		wrapWidth,
+		flipHoriz = false,
+		flipVert = false,
+		autoDraw,
+		autoLog
+	} = {}) {
+		super({ name, win, units, ori, opacity, pos, autoDraw, autoLog });
+
+		this._addAttributes(TextStim, text, font, color, contrast, height, bold, italic, alignHoriz, alignVert, wrapWidth, flipHoriz, flipVert);
+
+		/*if (autoLog)
+			logging.exp("Created %s = %s" % (self.name, str(self)));*/
+	}
+
+
+	/**
+	 * Setter for the text attribute.
+	 * 
+	 * @name module:visual.TextStim#setText
+	 * @public
+	 * @param {string} text - the text
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setText(text, log) {
+		this._setAttribute('text', text, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the alignHoriz attribute.
+	 * 
+	 * @name module:visual.TextStim#setAlignHoriz
+	 * @public
+	 * @param {string} alignHoriz - the text horizontal alignment, e.g. 'center'
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setAlignHoriz(alignHoriz, log) {
+		this._setAttribute('alignHoriz', alignHoriz, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the wrapWidth attribute.
+	 * 
+	 * @name module:visual.TextStim#setWrapWidth
+	 * @public
+	 * @param {boolean} wrapWidth - whether or not to wrap the text at the given width
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setWrapWidth(wrapWidth, log) {
+		this._setAttribute('wrapWidth', wrapWidth, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the height attribute.
+	 * 
+	 * @name module:visual.TextStim#setHeight
+	 * @public
+	 * @param {number} height - text height
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setHeight(height, log) {
+		this._setAttribute('height', height, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the italic attribute.
+	 * 
+	 * @name module:visual.TextStim#setItalic
+	 * @public
+	 * @param {boolean} italic - whether or not the text is italic
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setItalic(italic, log) {
+		this._setAttribute('italic', italic, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the bold attribute.
+	 * 
+	 * @name module:visual.TextStim#setBold
+	 * @public
+	 * @param {boolean} bold - whether or not the text is bold
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setBold(bold, log) {
+		this._setAttribute('bold', bold, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the flipVert attribute.
+	 * 
+	 * @name module:visual.TextStim#setFlipVert
+	 * @public
+	 * @param {boolean} flipVert - whether or not to flip vertically
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setFlipVert(flipVert, log) {
+		this._setAttribute('flipVert', flipVert, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Setter for the flipHoriz attribute.
+	 * 
+	 * @name module:visual.TextStim#setFlipHoriz
+	 * @public
+	 * @param {boolean} flipHoriz - whether or not to flip horizontally
+	 * @param {boolean} [log= false] - whether of not to log
+	 */
+	setFlipHoriz(flipHoriz, log) {
+		this._setAttribute('flipHoriz', flipHoriz, log);
+
+		this._needUpdate = true;
+		//this._needVertexUpdate = true;
+	}
+
+
+	/**
+	 * Determine whether an object is inside the bounding box of the text.
+	 * 
+	 * @name module:visual.TextStim#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 text
+	 * 
+	 * @todo this is currently NOT implemented
+	 */
+	contains(object, units) {
+		// get position of object:
+		let objectPos_px = util.getPositionFromObject(object, units);
+		if (typeof objectPos_px === 'undefined')
+			throw { origin : 'TextStim.contains', context : 'when determining whether ImageStim: ' + this._name + ' contains object: ' + util.toString(object), error : 'unable to determine the position of the object' };
+
+		// test for inclusion:
+		// TODO
+		return false;
+	}
+
+
+	/**
+	 * Update the stimulus, if necessary.
+	 * 
+	 * @name module:visual.TextStim#_updateIfNeeded
+	 * @private
+	 * 
+	 * @todo take size into account
+	 */
+	_updateIfNeeded() {
+		if (this._needUpdate) {
+			let height = this._height || 0.1;
+			this._heightPix = this._getLengthPix(height);
+
+			var fontSize = Math.round(this._heightPix);
+			let color = this._getDesiredColor(this._color, this._contrast);
+			var font =
+				(this._bold ? 'bold ' : '') +
+				(this._italic ? 'italic ' : '') +
+				fontSize + 'px ' + this._font;
+			this._pixi = new PIXI.Text(this._text, {
+				font: font,
+				fill: color.hex,
+				align: this._alignHoriz,
+				wordWrap: this._wrapWidth != undefined,
+				wordWrapWidth: this._wrapWidth ? this._getHorLengthPix(this._wrapWidth) : 0
+			});
+
+			this._pixi.anchor.x = 0.5;
+			this._pixi.anchor.y = 0.5;
+
+			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.alpha = this._opacity;
+
+			this._size = [
+				this._getLengthUnits(Math.abs(this._pixi.width)),
+				this._getLengthUnits(Math.abs(this._pixi.height))];
+
+			this._needUpdate = false;
+		}
+	}
+
+
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.5.5 on Sun Nov 18 2018 20:31:50 GMT+0100 (CET) +
+ + + + +