+ considerOrder: true
+ stimuli:
+ - C1: configs/resources/audio/mono_c1.wav
+ C2: configs/resources/audio/mono_c2.wav
+ - C3: configs/resources/audio/mono_c3.wav
+ C2: configs/resources/audio/mono_c2.wav
+
+ - type: finish
+ name: Thank you
+ content: Thank you for attending
+ showResults: true
+ writeResults: true
diff --git a/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_1.wav b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_1.wav
new file mode 100644
index 00000000..9b5307ee
Binary files /dev/null and b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_1.wav differ
diff --git a/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_2.wav b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_2.wav
new file mode 100644
index 00000000..d007e3dd
Binary files /dev/null and b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_2.wav differ
diff --git a/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_3.wav b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_3.wav
new file mode 100644
index 00000000..d007e3dd
Binary files /dev/null and b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_3.wav differ
diff --git a/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_calibration.wav b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_calibration.wav
new file mode 100644
index 00000000..28bfd3c2
Binary files /dev/null and b/configs/resources/audio/stimuli_HugginsPitch/HugginsPitch_calibration.wav differ
diff --git a/doc/experimenter.md b/doc/experimenter.md
index befc184a..59466a21 100644
--- a/doc/experimenter.md
+++ b/doc/experimenter.md
@@ -48,6 +48,21 @@ The volume page can be used to set the volume used in the experiment.
* **stimulus** Filepath to the stimulus that is used for setting the volume.
* **defaultVolume** Default volume (must be between 0.0 and 1.0).
+
+#### `headphones_screen` page
+
+This page can be used to perform a headphones screening. Sample files to conduct a test according to [Milne A, Bianco R, Poole K, Zhao S, Oxenham A, Billig A & Chait M. (2020) An online headphone screening test based on dichotic pitch. Behavior Research Methods.](https://rdcu.be/cbWrX) are provided, but the page can be used for any test following the same basic paradigm of identifying a target stimulus among a list of decoy stimuli.
+
+**Important:** There is currently no automated pass/fail arbitration so even subject failing to identify any of the targets will be allowed to complete the rest of the experiment. You can, however, use the results of the screening to filter your experiment results post-hoc.
+
+* **type** must be headphones_screen
+* **id** Identifier of the page.
+* **name** Name of the page (is shown as title)
+* **content** Content (HTML) of the page. The content is shown on the upper part of the page.
+* **repetitions** Determines how many randomized repetitions of the task need to be completed.
+* **target** Filepath to the target stimulus to be identified.
+* **other** A map of stimuli to use as decoys. The key is the name of the decoy. The value is the filepath to the stimulus (WAV file).
+
#### `mushra` page
A mushra page shows a trial according to ITU-R Recommendation BS.1534.
@@ -96,6 +111,18 @@ A paired comparison page creates a forced or unforced paired comparison (AB/ABX/
* **reference** Filepath to the reference stimulus (WAV file).
* **stimuli** A map of stimuli representing three conditions. The key is the name of the condition. The value is the filepath to the stimulus (WAV file).
+#### `preference_test` page
+
+A preference test page creates a forced paired comparison between two stimuli. The main difference to `paired_comparison` is that there is no reference or ground truth.
+
+* **type** must be preference_test.
+* **id** Identifier of the page.
+* **name** Name of the page (is shown as title)
+* **content** Content (HTML) of the page. The content is shown on the upper part of the page.
+* **considerOrder** If set to true, a complete set of pairs is generated from the list of stimuli: A set of `{A, B}` would result in two pairs `(A, B)` and `(B, A)`. Otherwise only the pair `(A, B)` would be generated.
+* **mustPlayback** If set to `ended`, the participant must fully play back all stimuli to the end. If set to `processUpdate`, the participant must start playing back all stimuli before responding becomes possible.
+* **stimuli** Either a map of stimuli or an array of maps of stimuli. If it is a map, pairs will be generated from the list and then shuffled. If it is an array of maps, pairs will be generated for each map individually and then all the pairs from all maps are shuffled.
+
#### `likert_multi_stimulus` page
A likert multi stimulus page creates a multi-stimulus likert rating.
@@ -133,6 +160,8 @@ The finish page must be the last page of the experiment.
* **content** Content (HTML) of the page. The content is shown on the upper part of the page.
* **showResults** The results are shown to the participant.
* **writeResults** The results are sent to the remote service (which writes the results into a file).
+* **generateSubjectId** If set to true, a random subject ID is generated and appended to the results.
+* **confirmationCode:** If set, this code is displayed after the results were sent. This can be used to confirm that subjects have completed the experiment and facilitates
## Results
diff --git a/doc/participant.md b/doc/participant.md
index 4ac44120..b50fd82c 100644
--- a/doc/participant.md
+++ b/doc/participant.md
@@ -41,3 +41,8 @@
* SPACE Play/pause the current selection
* a Set begin of loop to current position
* b Set end of loop to current position
+
+### Preference test
+
+* 1 Play item A
+* 2 Play item B
\ No newline at end of file
diff --git a/index.html b/index.html
index e6d735e7..015c15f2 100644
--- a/index.html
+++ b/index.html
@@ -80,6 +80,8 @@
+
+
@@ -108,9 +110,13 @@
+
+
+
+
diff --git a/lib/webmushra/datamodel/HeadphonesScreenResponse.js b/lib/webmushra/datamodel/HeadphonesScreenResponse.js
new file mode 100644
index 00000000..294adb0e
--- /dev/null
+++ b/lib/webmushra/datamodel/HeadphonesScreenResponse.js
@@ -0,0 +1,12 @@
+/*************************************************************************
+ (C) Copyright AudioLabs 2017
+
+This source code is protected by copyright law and international treaties. This source code is made available to You subject to the terms and conditions of the Software License for the webMUSHRA.js Software. Said terms and conditions have been made available to You prior to Your download of this source code. By downloading this source code You agree to be bound by the above mentionend terms and conditions, which can also be found here: https://www.audiolabs-erlangen.de/resources/webMUSHRA. Any unauthorised use of this source code may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under law.
+
+**************************************************************************/
+
+function HeadphonesScreenResponse() {
+ this.correct = null;
+ this.comment = null;
+ this.time = null;
+}
diff --git a/lib/webmushra/datamodel/PreferenceTestChoice.js b/lib/webmushra/datamodel/PreferenceTestChoice.js
new file mode 100644
index 00000000..8b82b928
--- /dev/null
+++ b/lib/webmushra/datamodel/PreferenceTestChoice.js
@@ -0,0 +1,14 @@
+/*************************************************************************
+ (C) Copyright AudioLabs 2017
+
+This source code is protected by copyright law and international treaties. This source code is made available to You subject to the terms and conditions of the Software License for the webMUSHRA.js Software. Said terms and conditions have been made available to You prior to Your download of this source code. By downloading this source code You agree to be bound by the above mentionend terms and conditions, which can also be found here: https://www.audiolabs-erlangen.de/resources/webMUSHRA. Any unauthorised use of this source code may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under law.
+
+**************************************************************************/
+
+function PreferenceTestChoice() {
+ this.optionA = null;
+ this.optionB = null;
+ this.answer = null;
+ this.comment = null;
+ this.time = null;
+}
diff --git a/lib/webmushra/nls/nls.js b/lib/webmushra/nls/nls.js
index dd4436bf..17e76dcb 100644
--- a/lib/webmushra/nls/nls.js
+++ b/lib/webmushra/nls/nls.js
@@ -76,6 +76,10 @@ nls['fr']['slightly'] = "Légèrement gênante";
nls['fr']['annoying'] = "Dégradation gênante";
nls['fr']['very'] = "Dégradation très gênante";
+// captions Preference Test
+nls['en']['pref'] = "Which item do you prefer?";
+nls['de']['pref'] = "Welche Version bevorzugen Sie?";
+nls['fr']['pref'] = "Quel item préférez-vous?";
// captions Paired Comparison AB/ABN
@@ -90,3 +94,9 @@ nls['fr']['results'] = "Vos résultats:";
nls['en']['attending'] = "Thank you for your participation!";
nls['de']['attending'] = "Vielen Dank für die Teilnahme!";
nls['fr']['attending'] = "Merci pour votre participation!";
+nls['en']['response'] = "Response";
+nls['de']['response'] = "Antwort";
+nls['fr']['response'] = "Réponse";
+nls['en']['code'] = "Your confirmation code:";
+nls['de']['code'] = "Ihr Bestätigungscode:";
+nls['fr']['code'] = "Votre code de confirmation:";
\ No newline at end of file
diff --git a/lib/webmushra/pages/FinishPage.js b/lib/webmushra/pages/FinishPage.js
index fd1e3ce1..610a90ee 100644
--- a/lib/webmushra/pages/FinishPage.js
+++ b/lib/webmushra/pages/FinishPage.js
@@ -32,8 +32,21 @@ FinishPage.prototype.getName = function () {
return this.pageConfig.name;
};
+function getId(){
+ /* Returns an ID based on random number and the current timestampe */
+ return Math.floor(Math.random() * Date.now());
+}
+
+
FinishPage.prototype.storeParticipantData = function() {
var i;
+ if (this.pageConfig.generateSubjectId){
+ this.session.participant.name[this.session.participant.name.length] = "subjectId";
+ this.session.participant.response[this.session.participant.response.length] = getId();
+ } else {
+ this.session.participant.name[this.session.participant.name.length] = "subjectId";
+ this.session.participant.response[this.session.participant.response.length] = null;
+ }
for (i = 0; i < this.pageConfig.questionnaire.length; ++i) {
var element = this.pageConfig.questionnaire[i];
@@ -64,20 +77,33 @@ FinishPage.prototype.render = function (_parent) {
for (i = 0; i < this.pageConfig.questionnaire.length; ++i) {
var element = this.pageConfig.questionnaire[i];
if (element.type === "text") {
- table.append($("
"+ element.label +"
"));
+ table.append($("
"+ element.label +"
"));
} else if (element.type === "number") {
- table.append($("
"+ element.label +"
"));
+ table.append($("
"+ element.label +"
"));
} else if(element.type === "likert") {
this.likert = new LikertScale(element.response, element.name + "_");
var td = $("
");
var trHeader = document.createElement("tr");
var trT;
@@ -114,7 +148,30 @@ FinishPage.prototype.render = function (_parent) {
var trials = this.session.trials;
for (i = 0; i < trials.length; ++i) {
var trial = trials[i];
- if (trial.type === "mushra") {
+ if (trial.type === "headphones_screen"){
+ trT = document.createElement("tr");
+ thT = $("
");
@@ -287,6 +376,10 @@ FinishPage.prototype.load = function() {
if ($("#" + element.name).val() || element.optional == true) {
++counter;
}
+ } else if(element.type === "dropdown") {
+ if ($("#" + element.name).val() || element.optional == true) {
+ ++counter;
+ }
}
if (counter == this.pageConfig.questionnaire.length) {
$('#send_results').removeAttr('disabled');
diff --git a/lib/webmushra/pages/HeadphonesScreenPage.js b/lib/webmushra/pages/HeadphonesScreenPage.js
new file mode 100644
index 00000000..781e8307
--- /dev/null
+++ b/lib/webmushra/pages/HeadphonesScreenPage.js
@@ -0,0 +1,193 @@
+/*************************************************************************
+ (C) Copyright AudioLabs 2017
+
+This source code is protected by copyright law and international treaties. This source code is made available to You subject to the terms and conditions of the Software License for the webMUSHRA.js Software. Said terms and conditions have been made available to You prior to Your download of this source code. By downloading this source code You agree to be bound by the above mentionend terms and conditions, which can also be found here: https://www.audiolabs-erlangen.de/resources/webMUSHRA. Any unauthorised use of this source code may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under law.
+
+**************************************************************************/
+
+function HeadphonesScreenPage(_pageManager, _pageTemplateRenderer, _audioContext, _bufferSize, _audioFileLoader, _stimuli, _session, _pageConfig, _errorHandler, _language) {
+ this.pageManager = _pageManager;
+ this.pageTemplateRenderer = _pageTemplateRenderer;
+ this.audioContext = _audioContext;
+ this.bufferSize = _bufferSize;
+ this.audioFileLoader = _audioFileLoader;
+ this.session = _session;
+ this.pageConfig = _pageConfig;
+ this.errorHandler = _errorHandler;
+ this.language = _language;
+ this.div = null;
+ this.fpc = null;
+
+ this.currentItem = null;
+
+ this.stimuli = _stimuli;
+ this.played = [];
+ for (var i = 0; i < this.stimuli.length; ++i) {
+ this.audioFileLoader.addFile(this.stimuli[i].getFilepath(), (function (_buffer, _stimulus) { _stimulus.setAudioBuffer(_buffer); }), this.stimuli[i]);
+ this.played.push(false);
+ }
+
+ this.filePlayer = null;
+ this.choice = null;
+ this.time = 0;
+ this.startTimeOnPage = null;
+}
+
+
+
+HeadphonesScreenPage.prototype.getName = function () {
+ return this.pageConfig.name;
+};
+
+HeadphonesScreenPage.prototype.init = function () {
+ this.filePlayer = new FilePlayer(this.audioContext, this.bufferSize, this.stimuli, this.errorHandler, this.language, this.pageManager.getLocalizer());
+
+ if (typeof this.pageConfig.mustPlayback !== "undefined") {
+ this.filePlayer.genericAudioControl.addEventListener((function (_event) {
+ if (_event.name == this.pageConfig.mustPlayback) {
+ this.played[_event.index] = true;
+ if (this.played.every(element => element === true)){
+ $('#radio-choice-a').checkboxradio('enable');
+ $('#radio-choice-b').checkboxradio('enable');
+ $('#radio-choice-c').checkboxradio('enable');
+ }
+ }
+ }).bind(this));
+ }
+};
+
+HeadphonesScreenPage.prototype.render = function (_parent) {
+ var div = $("");
+ _parent.append(div);
+
+ var content;
+ if(typeof this.pageConfig.content === "undefined"){
+ content = "Which noisy sound contains a faint beep?";
+ } else {
+ content = this.pageConfig.content;
+ }
+
+ var p = $("
" + content + "
");
+ div.append(p);
+
+ var outerTable = $("
");
+ div.append(outerTable);
+ var trOuter = $("
");
+ outerTable.append(trOuter);
+ var tdLabels = $("
");
+ trOuter.append(tdLabels);
+ var innerTable = $("
");
+ tdLabels.append(innerTable);
+ var trA = $("
");
+ tdABC.append(tableABC);
+
+ var trPlays = $("
");
+ tableABC.append(trPlays);
+
+ var trResponse = $("
");
+ tableABC.append(trResponse);
+ var tdResponse = $("
");
+ trResponse.append(tdResponse);
+
+ var radioChoice = $("
\
+ \
+ \
+ \
+ \
+ \
+ \
+
");
+ if (typeof this.pageConfig.mustPlayback !== "undefined"){
+ radioChoice.find("input[type='radio']").attr("disabled", true);;
+ }
+
+ radioChoice.find("input[type='radio']").bind("change", (function(){
+ this.pageTemplateRenderer.unlockNextButton();
+ }
+ ).bind(this));
+
+ tdResponse.append(radioChoice);
+
+ this.fpc = new FilePlayerController(this.filePlayer);
+ this.fpc.bind();
+
+};
+
+HeadphonesScreenPage.prototype.load = function () {
+ this.startTimeOnPage = new Date();
+
+ if (this.choice === null) {
+ this.pageTemplateRenderer.lockNextButton();
+ }
+ // audio
+ this.filePlayer.init();
+
+ //choice
+ if(this.choice === 'a') {
+ $('#radio-choice-a').prop('checked', true).checkboxradio('refresh');
+ $('#radio-choice-b').prop('checked', false).checkboxradio('refresh');
+ $('#radio-choice-c').prop('checked', false).checkboxradio('refresh');
+ } else if (this.choice === 'b') {
+ $('#radio-choice-b').prop('checked', true).checkboxradio('refresh');
+ $('#radio-choice-a').prop('checked', false).checkboxradio('refresh');
+ $('#radio-choice-c').prop('checked', false).checkboxradio('refresh');
+ } else if (this.choice === 'c') {
+ $('#radio-choice-c').prop('checked', true).checkboxradio('refresh');
+ $('#radio-choice-a').prop('checked', false).checkboxradio('refresh');
+ $('#radio-choice-b').prop('checked', false).checkboxradio('refresh');
+ }
+};
+
+HeadphonesScreenPage.prototype.save = function () {
+ this.fpc.unbind();
+ this.time += (new Date() - this.startTimeOnPage);
+ // audio
+ this.filePlayer.free();
+ // choice
+ var radio = $('#radio-choice :radio:checked');
+ this.choice = (radio.length > 0) ? radio[0].value : null;
+};
+
+
+HeadphonesScreenPage.prototype.store = function () {
+ var trial = this.session.getTrial(this.pageConfig.type, this.pageConfig.id);
+ if (trial === null) {
+ trial = new Trial();
+ trial.type = this.pageConfig.type;
+ trial.id = this.pageConfig.id;
+ this.session.trials[this.session.trials.length] = trial;
+ }
+ var response = new HeadphonesScreenResponse();
+
+ const val2idx = new Map();
+ val2idx.set('a', 0);
+ val2idx.set('b', 1);
+ val2idx.set('c', 2);
+
+ response.correct = (this.stimuli[val2idx.get(this.choice)].id == 'target').toString();
+
+ response.time = this.time;
+ trial.responses[trial.responses.length] = response;
+
+};
diff --git a/lib/webmushra/pages/HeadphonesScreenPageManager.js b/lib/webmushra/pages/HeadphonesScreenPageManager.js
new file mode 100644
index 00000000..3a02312f
--- /dev/null
+++ b/lib/webmushra/pages/HeadphonesScreenPageManager.js
@@ -0,0 +1,37 @@
+/*************************************************************************
+ (C) Copyright AudioLabs 2017
+
+This source code is protected by copyright law and international treaties. This source code is made available to You subject to the terms and conditions of the Software License for the webMUSHRA.js Software. Said terms and conditions have been made available to You prior to Your download of this source code. By downloading this source code You agree to be bound by the above mentionend terms and conditions, which can also be found here: https://www.audiolabs-erlangen.de/resources/webMUSHRA. Any unauthorised use of this source code may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under law.
+
+**************************************************************************/
+
+function HeadphonesScreenPageManager() {
+
+}
+
+HeadphonesScreenPageManager.prototype.createPages = function (_pageManager, _pageTemplateRenderer, _pageConfig, _audioContext, _bufferSize, _audioFileLoader, _session, _errorHandler, _language) {
+
+ this.stimuli = [];
+ /* Create stimuli */
+ this.stimuli[this.stimuli.length] = new Stimulus("target", _pageConfig.target);
+ for (var key in _pageConfig.other){
+ this.stimuli[this.stimuli.length] = new Stimulus(key, _pageConfig.other[key]);
+ }
+
+ for (var i = 0; i < _pageConfig.repetitions; ++i) {
+ /*
+ IMPORTANT: We cannot simply repeatedly shuffle the stimuli list and pass it to each page because everything is a reference!
+ All pages would use the same order of stimuli.
+ */
+ shuffle(this.stimuli);
+ page_stimuli = []
+ for (stimulus of this.stimuli){
+ page_stimuli[page_stimuli.length] = new Stimulus(stimulus.getId(), stimulus.getFilepath());
+ }
+ var page = new HeadphonesScreenPage(_pageManager, _pageTemplateRenderer, _audioContext, _bufferSize, _audioFileLoader, page_stimuli,
+ _session, _pageConfig, _errorHandler, _language);
+ _pageManager.addPage(page);
+ }
+};
+
+
\ No newline at end of file
diff --git a/lib/webmushra/pages/PreferenceTestPage.js b/lib/webmushra/pages/PreferenceTestPage.js
new file mode 100644
index 00000000..c2cf6057
--- /dev/null
+++ b/lib/webmushra/pages/PreferenceTestPage.js
@@ -0,0 +1,184 @@
+/*************************************************************************
+ (C) Copyright AudioLabs 2017
+
+This source code is protected by copyright law and international treaties. This source code is made available to You subject to the terms and conditions of the Software License for the webMUSHRA.js Software. Said terms and conditions have been made available to You prior to Your download of this source code. By downloading this source code You agree to be bound by the above mentionend terms and conditions, which can also be found here: https://www.audiolabs-erlangen.de/resources/webMUSHRA. Any unauthorised use of this source code may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under law.
+
+**************************************************************************/
+
+function PreferenceTestPage(_pageManager, _pageTemplateRenderer, _audioContext, _bufferSize, _audioFileLoader, _stimuli, _session, _pageConfig, _errorHandler, _language) {
+ this.pageManager = _pageManager;
+ this.pageTemplateRenderer = _pageTemplateRenderer;
+ this.audioContext = _audioContext;
+ this.bufferSize = _bufferSize;
+ this.audioFileLoader = _audioFileLoader;
+ this.session = _session;
+ this.pageConfig = _pageConfig;
+ this.errorHandler = _errorHandler;
+ this.language = _language;
+ this.div = null;
+ this.fpc = null;
+
+ this.currentItem = null;
+
+ this.stimuli = _stimuli;
+ this.played = [];
+ for (var i = 0; i < this.stimuli.length; ++i) {
+ this.audioFileLoader.addFile(this.stimuli[i].getFilepath(), (function (_buffer, _stimulus) { _stimulus.setAudioBuffer(_buffer); }), this.stimuli[i]);
+ this.played.push(false);
+ }
+
+ this.filePlayer = null;
+ this.choice = null;
+ this.time = 0;
+ this.startTimeOnPage = null;
+}
+
+
+
+PreferenceTestPage.prototype.getName = function () {
+ return this.pageConfig.name;
+};
+
+PreferenceTestPage.prototype.init = function () {
+ this.filePlayer = new FilePlayer(this.audioContext, this.bufferSize, this.stimuli, this.errorHandler, this.language, this.pageManager.getLocalizer());
+
+ if (typeof this.pageConfig.mustPlayback !== "undefined") {
+ this.filePlayer.genericAudioControl.addEventListener((function (_event) {
+ if (_event.name == this.pageConfig.mustPlayback) {
+ this.played[_event.index] = true;
+ if (this.played.every(element => element === true)){
+ $('#radio-choice-a').checkboxradio('enable');
+ $('#radio-choice-b').checkboxradio('enable');
+ }
+ }
+ }).bind(this));
+ }
+};
+
+PreferenceTestPage.prototype.render = function (_parent) {
+ var div = $("");
+ _parent.append(div);
+
+ var content;
+ if(this.pageConfig.content === null){
+ content ="";
+ } else {
+ content = this.pageConfig.content;
+ }
+
+ var p = $("
" + content + "
");
+ div.append(p);
+
+ var outerTable = $("
");
+ div.append(outerTable);
+ var trOuter = $("
");
+ outerTable.append(trOuter);
+ var tdLabels = $("
");
+ trOuter.append(tdLabels);
+ var innerTable = $("
");
+ tdLabels.append(innerTable);
+ var trA = $("
");
+ innerTable.append(trA);
+ trA.append($("
A:
"));
+ var trB = $("
");
+ innerTable.append(trB);
+ trB.append($("
B:
"));
+ trA.height(tdLabels.height());
+ trB.height(tdLabels.height());
+ var tdPlayer = $("