diff --git a/README.md b/README.md index a8c6b913..64de2005 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Listening tests are widely used to assess the quality of audio systems. In the l We provide two version of webMUSHRA. -* __webMUSHRA__ provides the version targeted for normale usage and experimenters. The javascript files are compressed which makes it faster to load/serve. The documentation is provided as PDFs. +* __webMUSHRA__ provides the version targeted for normal usage and experimenters. The javascript files are compressed which makes it faster to load/serve. The documentation is provided as PDFs. * __webMUSHRA-dev__ is targeted to developers and experienced users who want to customize experiments. This version is comparable to cloning the git repository @@ -31,6 +31,7 @@ We provide two version of webMUSHRA. * MUSHRA (ITU-R BS.1534) * AB (ITU-R BS.1116) * Likert scale questionaires + * Ranking by elimination * training/introduction * spatial attributes, such as ASW, LEV, and localization (experimental) * compliant to ITU recommendations (looping, fade-in/out, sample accurate switching) diff --git a/configs/ranking_noloop.yaml b/configs/ranking_noloop.yaml new file mode 100644 index 00000000..df3d8eb2 --- /dev/null +++ b/configs/ranking_noloop.yaml @@ -0,0 +1,60 @@ +# test config ranking page, waveform, 11 conditions, no looping + + +testname: Rank order Elimination-by-Aspects +testId: ranking_noloop +bufferSize: 2048 +stopOnErrors: true +showButtonPreviousPage: true +remoteService: service/write.php + +# Ranking test does not support showing the waveform of the samples currently + +pages: + - type: generic + id: first_page + name: Welcome + content:

Welcome to the rank order elimination-by-aspects test.
The goal of this test is to rank the presented conditions on personal preference of quality.
The test is conducted as described below:
[1] Listen to all the conditions one after the other using the "Play" button.
[2] Eliminate the worst condition using the "X" button below it.
[3] Repeat steps [1] and [2] until you are left with one condition or if you don't have a preference over remaining conditions.
[4] You can reset the rankings by pressing "Reset" at any time, and all rankings done till that point will be reset.
[5] Proceed to the next test using "next" button.

NOTE.1: You can only eliminate an "active" condition, i.e. a condition whose button is currently higlighted.
NOTE.2: You cannot play again an already eliminated condition. ("Reset" re-enables all conditions).
NOTE.3: Once a condition is eliminated, its rank will appear on its button. Lower rank (1-5) indicate worse quality. So the worst condition will have rank 1, the second worst 2, ...
NOTE.4: If you press the "previous" button, the rankings of the previous test are lost and you can repeat the previous test.

+ + - type: ranking + id: trial1 + name: Mono Trial + content: test description + enableLooping: false + randomize: true + showConditionNames: true + showResetButton: true + stimuli: + C1: configs/resources/audio/mono_c1.wav + C2: configs/resources/audio/mono_c2.wav + C3: configs/resources/audio/mono_c3.wav + + - type: ranking + id: trial2 + name: Mono Trial + content: test description + enableLooping: false + randomize: true + showConditionNames: true + showResetButton: true + + stimuli: + C1: configs/resources/audio/mono_c1.wav + C2: configs/resources/audio/mono_c2.wav + C3: configs/resources/audio/mono_c3.wav + + - type: finish + name: Thank you + content: Thank you for attending! + showResults: true + writeResults: true + questionnaire: + - type: text + label: Nickname + name: id + - type: number + label: Age + name: age + min: 0 + max: 100 + default: 30 diff --git a/design/images/techfak.svg b/design/images/techfak.svg index 89e46b15..da23fdc8 100644 --- a/design/images/techfak.svg +++ b/design/images/techfak.svg @@ -1,281 +1,165 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/experimenter.md b/doc/experimenter.md index 5206b38b..3a136319 100644 --- a/doc/experimenter.md +++ b/doc/experimenter.md @@ -76,6 +76,22 @@ A mushra page shows a trial according to ITU-R Recommendation BS.1534. * **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). * **switchBack** If set to true, the time position is set back to the beginning (sample 0) when switching between test conditions and/or the reference. By default, this option is false. +#### `ranking` page + +A ranking-by-elimination test trial page + +* **type** must be ranking. +* **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. +* **showWaveform** If set to true, the waveform of the reference is shown. +* **enableLooping** If set to true, the participant can set loops. +* **randomize** If set to true, the conditions are randomized. +* **showConditionNames** If set to true, the names of the conditions are shown. +* **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). +* **switchBack** If set to true, the time position is set back to the beginning (sample 0) when switching between test conditions and/or the reference. By default, this option is false. +* **forceRankAll** If set to true, the "next" button will only be enabled if all items are ranked. + #### `bs1116` page A bs1116 page shows a trial according to ITU-R Recommendation BS.1116. diff --git a/index.html b/index.html index dab46eb0..f8e97c1d 100644 --- a/index.html +++ b/index.html @@ -111,6 +111,7 @@ + diff --git a/lib/webmushra/audio/MushraAudioControl.js b/lib/webmushra/audio/MushraAudioControl.js index 6c988492..be9e5123 100644 --- a/lib/webmushra/audio/MushraAudioControl.js +++ b/lib/webmushra/audio/MushraAudioControl.js @@ -5,7 +5,7 @@ This source code is protected by copyright law and international treaties. This **************************************************************************/ -function MushraAudioControl(_audioContext, _bufferSize, _reference, _conditions, _errorHandler, _createAnchor35, _createAnchor70, _randomize, _switchBack) { +function MushraAudioControl(_audioContext, _bufferSize, _reference, _conditions, _errorHandler, _createAnchor35, _createAnchor70, _randomize, _switchBack, add_ref_to_conditions=true) { this.audioContext = _audioContext; this.bufferSize = parseInt(_bufferSize); this.reference = _reference; @@ -41,7 +41,9 @@ function MushraAudioControl(_audioContext, _bufferSize, _reference, _conditions, //listeners this.eventListeners = []; - this.conditions[this.conditions.length] = this.reference; + if (add_ref_to_conditions) { + this.conditions[this.conditions.length] = this.reference; + } // add anchors if (this.createAnchor35) { diff --git a/lib/webmushra/nls/nls.js b/lib/webmushra/nls/nls.js index dd4436bf..68cea613 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -1,20 +1,25 @@ nls['en'] = new Object(); nls['de'] = new Object(); nls['fr'] = new Object(); +nls['es'] = new Object(); // Buttons nls['en']['nextButton'] = "Next"; -nls['en']['previousButton'] = "Previous"; +nls['en']['previousButton'] = "Previous"; nls['en']['playButton'] = "Play"; -nls['en']['stopButton'] = "Stop"; +nls['en']['stopButton'] = "Stop"; nls['en']['pauseButton'] = "Pause"; +nls['en']['eliminateButton'] = "X"; +nls['en']['resetButton'] = "Reset"; nls['en']['sendButton'] = "Send Results"; nls['de']['nextButton'] = "Nächste Seite"; -nls['de']['previousButton'] = "Vorherige Seite"; +nls['de']['previousButton'] = "Vorherige Seite"; nls['de']['playButton'] = "Start"; -nls['de']['stopButton'] = "Stopp"; +nls['de']['stopButton'] = "Stopp"; nls['de']['pauseButton'] = "Pause"; +nls['de']['eliminateButton'] = "X"; +nls['de']['resetButton'] = "Reset"; nls['de']['sendButton'] = "Ergebnisse senden"; nls['fr']['nextButton'] = "Suivant"; @@ -22,26 +27,37 @@ nls['fr']['previousButton'] = "Précédent"; nls['fr']['playButton'] = "Play"; nls['fr']['stopButton'] = "Stop"; nls['fr']['pauseButton'] = "Pause"; +nls['fr']['eliminateButton'] = "X"; +nls['fr']['resetButton'] = "Reset"; nls['fr']['sendButton'] = "Envoyer les résultats"; +nls['es']['nextButton'] = "Siguiente"; +nls['es']['previousButton'] = "Anterior"; +nls['es']['playButton'] = "Reproducir"; +nls['es']['stopButton'] = "Detener"; +nls['es']['pauseButton'] = "Pausa"; +nls['es']['sendButton'] = "Enviar resultados"; + // captions MUSHRA nls['en']['excellent'] = "Excellent"; -nls['en']['good'] = "Good"; +nls['en']['good'] = "Good"; nls['en']['fair'] = "Fair"; -nls['en']['poor'] = "Poor"; +nls['en']['poor'] = "Poor"; nls['en']['bad'] = "Bad"; nls['en']['reference'] = "Reference"; +nls['en']['conditions'] = "Conditions:"; nls['en']['cond'] = "Cond."; nls['en']['35'] = "Anchor35"; nls['en']['75'] = "Anchor75"; nls['de']['excellent'] = "Excellent"; -nls['de']['good'] = "Good"; +nls['de']['good'] = "Good"; nls['de']['fair'] = "Fair"; -nls['de']['poor'] = "Poor"; +nls['de']['poor'] = "Poor"; nls['de']['bad'] = "Bad"; nls['de']['reference'] = "Reference"; +nls['de']['conditions'] = "Conditions:"; nls['de']['cond'] = "Cond."; nls['de']['35'] = "Anchor35"; nls['de']['75'] = "Anchor75"; @@ -52,22 +68,33 @@ nls['fr']['fair'] = "Correct"; nls['fr']['poor'] = "Faible"; nls['fr']['bad'] = "Mauvais"; nls['fr']['reference'] = "Référence"; +nls['fr']['conditions'] = "Conditions:"; nls['fr']['cond'] = "Cond."; nls['fr']['35'] = "Ancre35"; nls['fr']['75'] = "Ancre75"; +nls['es']['excellent'] = "Excelente"; +nls['es']['good'] = "Bien"; +nls['es']['fair'] = "Regular"; +nls['es']['poor'] = "Pobre"; +nls['es']['bad'] = "Malo"; +nls['es']['reference'] = "Referencia"; +nls['es']['cond'] = "Estado"; +nls['es']['35'] = "Ancla35"; +nls['es']['75'] = "Ancla75"; + // captions BS1116 nls['en']['imperceptible'] = "Imperceptible"; -nls['en']['perceptible'] = "Perceptible, but not annoying"; +nls['en']['perceptible'] = "Perceptible, but not annoying"; nls['en']['slightly'] = "Slightly annoying"; -nls['en']['annoying'] = "Annoying"; +nls['en']['annoying'] = "Annoying"; nls['en']['very'] = "Very annoying"; nls['de']['imperceptible'] = "Imperceptible"; -nls['de']['perceptible'] = "Perceptible, but not annoying"; +nls['de']['perceptible'] = "Perceptible, but not annoying"; nls['de']['slightly'] = "Slightly annoying"; -nls['de']['annoying'] = "Annoying"; +nls['de']['annoying'] = "Annoying"; nls['de']['very'] = "Very annoying"; nls['fr']['imperceptible'] = "Dégradation imperceptible"; @@ -76,17 +103,25 @@ nls['fr']['slightly'] = "Légèrement gênante"; nls['fr']['annoying'] = "Dégradation gênante"; nls['fr']['very'] = "Dégradation très gênante"; +nls['es']['imperceptible'] = "Imperceptible"; +nls['es']['perceptible'] = "Perceptible, pero no molesto"; +nls['es']['slightly'] = "Ligeramente molesto"; +nls['es']['annoying'] = "Molesto"; +nls['es']['very'] = "Muy molesto"; // captions Paired Comparison AB/ABN nls['en']['quest'] = "Which item is the reference?"; nls['de']['quest'] = "Welches Stück ist die Referenz?"; nls['fr']['quest'] = "Quel item est la référence?"; +nls['es']['quest'] = "¿Cuál es la referencia?"; // captions finishPage nls['en']['results'] = "Your results:"; nls['de']['results'] = "Die Ergebnisse:"; nls['fr']['results'] = "Vos résultats:"; +nls['es']['results'] = "Sus resultados:"; 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['es']['attending'] = "Gracias por participar!"; diff --git a/lib/webmushra/pages/FinishPage.js b/lib/webmushra/pages/FinishPage.js index fd1e3ce1..ed534d9f 100644 --- a/lib/webmushra/pages/FinishPage.js +++ b/lib/webmushra/pages/FinishPage.js @@ -138,6 +138,33 @@ FinishPage.prototype.render = function (_parent) { $(trRatings).append(tdRatingScore); $(table).append(trRatings); + } + trEmpty = $(""); + $(table).append(trEmpty); + }else if (trial.type === "ranking") { + trT = document.createElement("tr"); + thT = $(""); + $(thT).append(trial.id + " (RANKING, lower ranks correspond to lower preferences)" ); + $(trT).append(thT); + $(table).append(trT); + + var ratings = trial.responses; + for (var j = 0; j < ratings.length; ++j) { + trRatings = document.createElement("tr"); + tdRatingStimulus = document.createElement("td"); + tdRatingScore = document.createElement("td"); + + tdRatingStimulus.width = "50%"; + tdRatingScore.width = "50%"; + + var rating = ratings[j]; + + $(tdRatingStimulus).append(rating.stimulus + ": "); + $(tdRatingScore).append(rating.score); + $(trRatings).append(tdRatingStimulus); + $(trRatings).append(tdRatingScore); + $(table).append(trRatings); + } trEmpty = $(""); $(table).append(trEmpty); diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js new file mode 100644 index 00000000..c3c4ae3b --- /dev/null +++ b/lib/webmushra/pages/RankingPage.js @@ -0,0 +1,453 @@ +/************************************************************************* + (C) Copyright AudioLabs 2023 + +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 RankingPage(_pageManager, _audioContext, _bufferSize, _audioFileLoader, _session, _pageConfig, _mushraValidator, _errorHandler, _language) { + this.isMushra = true; + this.pageManager = _pageManager; + this.audioContext = _audioContext; + this.bufferSize = _bufferSize; + this.audioFileLoader = _audioFileLoader; + this.session = _session; + this.pageConfig = _pageConfig; + this.mushraValidator = _mushraValidator; + this.errorHandler = _errorHandler; + this.language = _language + this.mushraAudioControl = null; + this.div = null; + this.waveformVisualizer = null; + this.macic = null; + + this.currentItem = null; + + this.tdLoop2 = null; + + this.conditions = []; + for (var key in this.pageConfig.stimuli) { + this.conditions[this.conditions.length] = new Stimulus(key, this.pageConfig.stimuli[key]); + } + for (var i = 0; i < this.conditions.length; ++i) { + this.audioFileLoader.addFile(this.conditions[i].getFilepath(), (function (_buffer, _stimulus) { _stimulus.setAudioBuffer(_buffer); }), this.conditions[i]); + } + + // data + this.ratings = []; + this.loop = {start: null, end: null}; + this.slider = {start: null, end: null}; + + this.time = 0; + this.startTimeOnPage = null; + +} + + + +RankingPage.prototype.getName = function () { + return this.pageConfig.name; +}; + +RankingPage.prototype.init = function () { + var toDisable; + var td; + var active; + + if (this.pageConfig.strict !== false) { + this.mushraValidator.checkNumConditions(this.conditions); + } + + var i; + for (i = 0; i < this.conditions.length; ++i) { + this.mushraValidator.checkSamplerate(this.audioContext.sampleRate, this.conditions[i]); + } + this.mushraValidator.checkConditionConsistency(this.conditions[0], this.conditions); + + this.mushraAudioControl = new MushraAudioControl(this.audioContext, this.bufferSize, this.conditions[0], this.conditions, this.errorHandler, this.pageConfig.createAnchor35, this.pageConfig.createAnchor70, this.pageConfig.randomize, this.pageConfig.switchBack, false); + + this.mushraAudioControl.addEventListener((function (_event) { + if (_event.name == 'stopTriggered') { + $(".audioControlElement").text(this.pageManager.getLocalizer().getFragment(this.language, 'playButton')); + + for(i = 0; i < _event.conditionLength; i++) { + active = '#buttonConditions' + i; + toDisable = $(".scales").get(i); + if($(active).attr("active") == "true") { + $.mobile.activePage.find(active) // remove color from conditions + .removeClass('ui-btn-b') + .addClass('ui-btn-a').attr('data-theme', 'a'); + //$(toDisable).slider('disable'); + $(toDisable).attr("active", "false"); + $(active).attr("active", "false"); + activeElim = '#buttonElim' + i; + $.mobile.activePage.find(activeElim) // remove color from eliminate + .removeClass('ui-btn-b') + .addClass('ui-btn-a').attr('data-theme', 'a'); + break; + } + } + + $.mobile.activePage.find('#buttonStop') //add color to stop + .removeClass('ui-btn-a') + .addClass('ui-btn-b').attr('data-theme', 'b'); + $.mobile.activePage.find('#buttonStop').focus(); + $('#buttonStop').attr("active", "true"); + + } else if(_event.name == 'playConditionTriggered') { + + var index = _event.index; + var activeSlider = $(".scales").get(index); + var selector = '#buttonConditions' + index; + + if($('#buttonStop').attr("active") == "true") { + $.mobile.activePage.find('#buttonStop') //remove color from Stop + .removeClass('ui-btn-b') + .addClass('ui-btn-a').attr('data-theme', 'a'); + $('#buttonStop').attr("active", "false"); + } + + var k; + for(k = 0; k < _event.length; k++) { + active = '#buttonConditions' + k; + toDisable = $(".scales").get(k); + if($(active).attr("active") == "true") { + $.mobile.activePage.find(active) // remove color from conditions + .removeClass('ui-btn-b') + .addClass('ui-btn-a').attr('data-theme', 'a'); + //$(toDisable).slider('disable'); + $(active).attr("active", "false"); + $(toDisable).attr("active", "false"); + activeElim = '#buttonElim' + k; + $.mobile.activePage.find(activeElim) // remove color from conditions + .removeClass('ui-btn-b') + .addClass('ui-btn-a').attr('data-theme', 'a'); + break; + } + } + + + /*$(activeSlider).slider('enable'); + $(activeSlider).attr("active", "true");*/ + $.mobile.activePage.find(selector) //add color to conditions + .removeClass('ui-btn-a') + .addClass('ui-btn-b').attr('data-theme', 'b'); + $.mobile.activePage.find(selector).focus(); + $(selector).attr("active", "true"); + + selectedElim = '#buttonElim' + index; + $.mobile.activePage.find(selectedElim) //add color to conditions + .removeClass('ui-btn-a') + .addClass('ui-btn-b').attr('data-theme', 'b'); + $.mobile.activePage.find(selectedElim).focus(); + $(selectedElim).attr("active", "true"); + + } else if (_event.name == 'surpressLoop') { + this.surpressLoop(); + } + + +}).bind(this)); + + + +}; + +RankingPage.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 tableUp = $("
"); + var tableDown = $("
"); + div.append(tableUp); + div.append(tableDown); + + var trLoop = $(""); + tableUp.append(trLoop); + + var tdLoop1 = $(" \ + \ + \ + \ + "); + trLoop.append(tdLoop1); + + + + var tdRight = $(""); + trLoop.append(tdRight); + + + var trMushra = $(""); + tableDown.append(trMushra); + var tdMushra = $(""); + trMushra.append(tdMushra); + + var tableMushra = $("
"); + tdMushra.append(tableMushra); + + var trConditionNames = $(""); + tableMushra.append(trConditionNames); + + /* Unable to remove the below string without affecting the buttons */ + var tdConditionNamesReference = $("" + this.pageManager.getLocalizer().getFragment(this.language, 'conditions') + ""); + trConditionNames.append(tdConditionNamesReference); + + var tdConditionNamesScale = $(""); + trConditionNames.append(tdConditionNamesScale); + + var conditions = this.mushraAudioControl.getConditions(); + var i; + var idx = 0; + for (i = 0; i < conditions.length; ++i) { + var str = ""; + if (this.pageConfig.showConditionNames === true) { + str = "
" + conditions[i].id; + } + td = $("" + this.pageManager.getLocalizer().getFragment(this.language, 'cond') + (idx + 1) + str + ""); + trConditionNames.append(td); + idx = idx + 1; + } + + this.numCond = idx; + this.score = new Array(this.conditions.length).fill(0); + this.rank = 1; + this.itemEliminated = -1; + + var trConditionPlay = $(""); + tableMushra.append(trConditionPlay); + + var tdConditionPlayReference = $(""); + trConditionPlay.append(tdConditionPlayReference); + + var tdConditionPlayScale = $(""); + trConditionPlay.append(tdConditionPlayScale); + + for (i = 0; i < conditions.length; ++i) { + td = $(""); + var buttonPlay = $(""); + buttonPlay.attr("id", "buttonConditions" + i); + td.append(buttonPlay); + trConditionPlay.append(td); + } + + // ratings + var trConditionRatings = $(""); + tableMushra.append(trConditionRatings); + + var tdConditionRatingsReference = $(""); + trConditionRatings.append(tdConditionRatingsReference); + + var tdConditionRatingsScale = $(""); + trConditionRatings.append(tdConditionRatingsScale); + + for (i = 0; i < conditions.length; ++i) { + td = $(""); + var buttonElim = $(""); + buttonElim.attr("id", "buttonElim" + i); + td.append(buttonElim); + trConditionRatings.append(td); + } + + if (this.pageConfig.showResetButton === true){ + var buttonReset = $(""); + trConditionRatings.append(buttonReset); + } + + + this.macic = new MushraAudioControlInputController(this.mushraAudioControl, this.pageConfig.enableLooping, add_ref_to_conditions=false); + this.macic.bind(); + + //Wavform is not shown in ranking since there is no reference + //this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], this.pageConfig.showWaveform, this.pageConfig.enableLooping, this.mushraAudioControl); + this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], false, this.pageConfig.enableLooping, this.mushraAudioControl); + this.waveformVisualizer.create(); + this.waveformVisualizer.load(); + +}; + +RankingPage.prototype.pause = function() { + this.mushraAudioControl.pause(); +}; + +RankingPage.prototype.setLoopStart = function() { + var slider = document.getElementById('slider'); + var startSliderSamples = this.mushraAudioControl.audioCurrentPosition; + + var endSliderSamples = parseFloat(slider.noUiSlider.get()[1]); + + this.mushraAudioControl.setLoop(startSliderSamples, endSliderSamples); +}; + +RankingPage.prototype.setLoopEnd = function() { + var slider = document.getElementById('slider'); + var startSliderSamples = parseFloat(slider.noUiSlider.get()[0]); + + var endSliderSamples = this.mushraAudioControl.audioCurrentPosition; + + this.mushraAudioControl.setLoop(startSliderSamples, endSliderSamples); +}; + +RankingPage.prototype.btnCallbackCondition = function(_index) { + this.currentItem = _index; + + var label = $("#buttonConditions" + _index).text(); + var elimStatus = $("#buttonElim" + _index).text(); + + if (label == this.pageManager.getLocalizer().getFragment(this.language, 'pauseButton')) { + this.mushraAudioControl.pause(); + $("#buttonConditions" + _index).text(this.pageManager.getLocalizer().getFragment(this.language, 'playButton')); + } else if (label == this.pageManager.getLocalizer().getFragment(this.language, 'playButton')) { + if (elimStatus == this.pageManager.getLocalizer().getFragment(this.language, 'eliminateButton')) { + $(".audioControlElement").text(this.pageManager.getLocalizer().getFragment(this.language, 'playButton')); + this.mushraAudioControl.playCondition(_index); + $("#buttonConditions" + _index).text(this.pageManager.getLocalizer().getFragment(this.language, 'pauseButton')); + if (this.itemEliminated != -1) { + this.rank = this.rank + 1; + this.itemEliminated = -1; + } + } + } +}; + +RankingPage.prototype.surpressLoop = function() { + var id = $("#buttonConditions" + this.currentItem); + id.text(this.pageManager.getLocalizer().getFragment(this.language, 'playButton')); +} + +RankingPage.prototype.btnCallbackElimination = function(_index) { + this.currentItem = _index; + + var label = $("#buttonElim" + _index).text(); + + curButton = '#buttonConditions' + _index; + + if ($(curButton).attr("active") == "true") { + if (label == this.pageManager.getLocalizer().getFragment(this.language, 'eliminateButton')) { + + //$("#buttonElim" + _index).text(this.pageManager.getLocalizer().getFragment(this.language, 'activateButton')); + $("#buttonElim" + _index).text(this.rank); + $("#buttonElim" + _index).addClass('ui-disabled'); + $("#buttonConditions" + _index).addClass('ui-disabled'); + + this.score[_index] = this.rank; + this.itemEliminated = _index; + + } else { + + $("#buttonElim" + _index).text(this.pageManager.getLocalizer().getFragment(this.language, 'eliminateButton')); + + if (this.itemEliminated == _index) { + this.itemEliminated = -1; + this.score[_index] = 0; + } + } + + if (this.pageConfig.forceRankAll & (this.rank == this.conditions.length)) { + $("#__button_next").removeClass('ui-disabled'); + } + } +}; + +RankingPage.prototype.btnCallbackReset = function() { + this.rank = 1; + this.itemEliminated = -1; + this.score = new Array(this.conditions.length).fill(0); + + var conditions = this.mushraAudioControl.getConditions(); + + for (i = 0; i < conditions.length; ++i) { + $("#buttonElim" + i).text(this.pageManager.getLocalizer().getFragment(this.language, 'eliminateButton')); + $("#buttonElim" + i).removeClass('ui-disabled'); + $("#buttonConditions" + i).removeClass('ui-disabled'); + } + + if (this.pageConfig.forceRankAll) { + $("#__button_next").addClass('ui-disabled'); + } + +}; + +RankingPage.prototype.renderCanvas = function(_parentId) { + $('#mushra_canvas').remove(); + parent = $('#' + _parentId); + var canvas = document.createElement("canvas"); + canvas.style.position = "absolute"; + canvas.style.left = 0; + canvas.style.top = 0; + canvas.style.zIndex = 0; + canvas.setAttribute("id","mushra_canvas"); + parent.get(0).appendChild(canvas); + + $(".scales").siblings().css("zIndex", "1"); +}; + + +RankingPage.prototype.load = function () { + + this.startTimeOnPage = new Date(); + + this.renderCanvas('mushra_items'); + + this.mushraAudioControl.initAudio(); + + if (this.loop.start !== null && this.loop.end !== null) { + this.mushraAudioControl.setLoop(0, 0, this.mushraAudioControl.getDuration(), this.mushraAudioControl.getDuration() /this.waveformVisualizer.stimulus.audioBuffer.sampleRate); + this.mushraAudioControl.setPosition(0); + } + + if (this.pageConfig.forceRankAll) { + $("#__button_next").addClass('ui-disabled'); + } + +}; + +RankingPage.prototype.save = function () { + this.macic.unbind(); + this.time += (new Date() - this.startTimeOnPage); + this.mushraAudioControl.freeAudio(); + this.mushraAudioControl.removeEventListener(this.waveformVisualizer.numberEventListener); + var scales = $(".scales"); + this.ratings = []; + var i; + if (this.itemEliminated != -1) { + this.rank = this.rank + 1; + } + + for (i = 0; i < this.conditions.length; i++){ + if (this.score[i] == 0) { + this.score[i] = this.rank; + } + } + + this.loop.start = parseInt(this.waveformVisualizer.mushraAudioControl.audioLoopStart); + this.loop.end = parseInt(this.waveformVisualizer.mushraAudioControl.audioLoopEnd); +}; + +RankingPage.prototype.store = function () { + + var trial = new Trial(); + trial.type = this.pageConfig.type; + trial.id = this.pageConfig.id; + var i; + + var conditions = this.mushraAudioControl.getConditions(); + + for (i = 0; i < conditions.length; i++) { + var ratingObj = new MUSHRARating(); + ratingObj.stimulus = conditions[i].id; + ratingObj.score = this.score[i]; + ratingObj.time = this.time; + trial.responses[trial.responses.length] = ratingObj; + } + this.session.trials[this.session.trials.length] = trial; +}; diff --git a/package-lock.json b/package-lock.json index 5a96c0b3..9e4b7439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -236,12 +236,23 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" + }, + "dependencies": { + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + } } }, "browserify-zlib": { @@ -814,15 +825,6 @@ "object-assign": "^4.1.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -2590,9 +2592,9 @@ "dev": true }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true }, "rc": { diff --git a/service/write.php b/service/write.php index 9855cae8..483136b9 100644 --- a/service/write.php +++ b/service/write.php @@ -40,7 +40,7 @@ function sanitize($string = '', $is_filename = FALSE) // mushra $write_mushra = false; $mushraCsvData = array(); - +$write_ranking = false; $input = array("session_test_id"); for($i =0; $i < $length; $i++){ @@ -88,6 +88,36 @@ function sanitize($string = '', $is_filename = FALSE) fclose($fp); } +// Ranking write +foreach ($session->trials as $trial){ + if ($trial->type == "ranking") { + $write_ranking = true; + + foreach ($trial->responses as $response) { + $results = array($session->testId); + for($i =0; $i < $length; $i++){ + array_push($results, $session->participant->response[$i]); + } + array_push($results, $session->uuid, $trial->id, $response->stimulus, $response->score, $response->time, $response->comment); + + array_push($mushraCsvData, $results); + } + } +} +if ($write_ranking) { + $filename = $filepathPrefix."ranking".$filepathPostfix; + $isFile = is_file($filename); + $fp = fopen($filename, 'a'); + foreach ($mushraCsvData as $row) { + if ($isFile) { + $isFile = false; + } else { + fputcsv($fp, $row); + } + } + fclose($fp); +} + // paired comparison $write_pc = false; diff --git a/startup.js b/startup.js index 6189246b..ac49c613 100644 --- a/startup.js +++ b/startup.js @@ -85,6 +85,9 @@ function addPagesToPageManager(_pageManager, _pages) { } else if (pageConfig.type == "mushra") { var mushraPage = new MushraPage(_pageManager, audioContext, config.bufferSize, audioFileLoader, session, pageConfig, mushraValidator, errorHandler, config.language); _pageManager.addPage(mushraPage); + } else if (pageConfig.type == "ranking") { + var mushraPage = new RankingPage(_pageManager, audioContext, config.bufferSize, audioFileLoader, session, pageConfig, mushraValidator, errorHandler, config.language); + _pageManager.addPage(mushraPage); } else if ( pageConfig.type == "spatial"){ _pageManager.addPage(new SpatialPage(_pageManager, pageConfig, session, audioContext, config.bufferSize, audioFileLoader, errorHandler, config.language)); } else if (pageConfig.type == "paired_comparison") {