From 0a6cca451b050f5e1326cb1ad9f4247b2a953f0a Mon Sep 17 00:00:00 2001 From: "Kishor.Kayyar" Date: Wed, 12 Apr 2023 12:56:33 +0200 Subject: [PATCH 01/13] Added ranking by elimination test --- README.md | 1 + configs/ranking_noloop.yaml | 62 ++++ doc/experimenter.md | 16 + index.html | 1 + lib/webmushra/nls/nls.js | 6 + lib/webmushra/pages/RankingPage.js | 469 +++++++++++++++++++++++++++++ service/write.php | 30 ++ startup.js | 3 + 8 files changed, 588 insertions(+) create mode 100644 configs/ranking_noloop.yaml create mode 100644 lib/webmushra/pages/RankingPage.js diff --git a/README.md b/README.md index a8c6b913..81a9e27a 100644 --- a/README.md +++ b/README.md @@ -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..6b40a1fa --- /dev/null +++ b/configs/ranking_noloop.yaml @@ -0,0 +1,62 @@ +# 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 + + +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 + showWaveform: false + 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 + showWaveform: false + 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/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/nls/nls.js b/lib/webmushra/nls/nls.js index dd4436bf..076a8053 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -8,6 +8,8 @@ nls['en']['previousButton'] = "Previous"; nls['en']['playButton'] = "Play"; 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"; @@ -15,6 +17,8 @@ nls['de']['previousButton'] = "Vorherige Seite"; nls['de']['playButton'] = "Start"; 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,6 +26,8 @@ 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"; // captions MUSHRA diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js new file mode 100644 index 00000000..b6c57880 --- /dev/null +++ b/lib/webmushra/pages/RankingPage.js @@ -0,0 +1,469 @@ +/************************************************************************* + (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); + this.macic.bind(); + + this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], this.pageConfig.showWaveform, 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.ratings.length !== 0) { + var scales = $(".scales"); + var i; + for (i = 0; i < scales.length; ++i) { + //$(".scales").eq(i).val(this.ratings[i].value).slider("refresh"); + } + } + 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; + /*for (i = 0; i < scales.length; ++i) { + this.ratings[i] = 0; //{name: scales[i].name, value: scales[i].value}; + }*/ + 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; + } + /*for (i = 0; i < this.ratings.length; ++i) { + var rating = this.ratings[i]; + var ratingObj = new MUSHRARating(); + ratingObj.stimulus = rating.name; + ratingObj.score = 0; //rating.value; + ratingObj.time = this.time; + trial.responses[trial.responses.length] = ratingObj; + }*/ + this.session.trials[this.session.trials.length] = trial; +}; diff --git a/service/write.php b/service/write.php index 9855cae8..ba863b54 100644 --- a/service/write.php +++ b/service/write.php @@ -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") { From dd53f5a007ad9ff00620484b362aaa3051e9192b Mon Sep 17 00:00:00 2001 From: conorsleithsonos <42356132+conorsleithsonos@users.noreply.github.com> Date: Wed, 12 Jul 2023 14:33:26 -0400 Subject: [PATCH 02/13] Fixed typo in readme -- s/normale/normal/ (#112) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8c6b913..20384a89 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 From e4863d69e952ff7d0f6af6edad7a3a4f49e94c8f Mon Sep 17 00:00:00 2001 From: kishorkl Date: Wed, 19 Jul 2023 16:31:58 +0200 Subject: [PATCH 03/13] Update RankingPage.js per @faroit comments --- lib/webmushra/pages/RankingPage.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js index b6c57880..dfff7e97 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -424,9 +424,6 @@ RankingPage.prototype.save = function () { var scales = $(".scales"); this.ratings = []; var i; - /*for (i = 0; i < scales.length; ++i) { - this.ratings[i] = 0; //{name: scales[i].name, value: scales[i].value}; - }*/ if (this.itemEliminated != -1) { this.rank = this.rank + 1; } @@ -457,13 +454,5 @@ RankingPage.prototype.store = function () { ratingObj.time = this.time; trial.responses[trial.responses.length] = ratingObj; } - /*for (i = 0; i < this.ratings.length; ++i) { - var rating = this.ratings[i]; - var ratingObj = new MUSHRARating(); - ratingObj.stimulus = rating.name; - ratingObj.score = 0; //rating.value; - ratingObj.time = this.time; - trial.responses[trial.responses.length] = ratingObj; - }*/ this.session.trials[this.session.trials.length] = trial; }; From 8053403ecdcbd575fd2794963f4df6d7395a851b Mon Sep 17 00:00:00 2001 From: kishorkl Date: Fri, 21 Jul 2023 15:00:28 +0200 Subject: [PATCH 04/13] Update RankingPage.js per @faroit comments --- lib/webmushra/pages/RankingPage.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js index dfff7e97..a36386e4 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -398,13 +398,6 @@ RankingPage.prototype.load = function () { this.mushraAudioControl.initAudio(); - if (this.ratings.length !== 0) { - var scales = $(".scales"); - var i; - for (i = 0; i < scales.length; ++i) { - //$(".scales").eq(i).val(this.ratings[i].value).slider("refresh"); - } - } 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); From 13f8fec7fa0905e538e0a2922ef16aff0305e39b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:16:01 -0700 Subject: [PATCH 05/13] Bump qs from 6.5.2 to 6.5.3 (#107) Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3. - [Release notes](https://github.com/ljharb/qs/releases) - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3) --- updated-dependencies: - dependency-name: qs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a96c0b3..3c375bc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2590,9 +2590,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": { From 20970bef18bb67d21ed554950bdccfbad92c2b6b Mon Sep 17 00:00:00 2001 From: Frank Zalkow Date: Wed, 21 Feb 2024 13:09:49 +0100 Subject: [PATCH 06/13] Spanish localization (#123) --- lib/webmushra/nls/nls.js | 50 ++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/webmushra/nls/nls.js b/lib/webmushra/nls/nls.js index dd4436bf..4eee50a4 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -1,19 +1,20 @@ 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']['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']['sendButton'] = "Ergebnisse senden"; @@ -24,12 +25,19 @@ nls['fr']['stopButton'] = "Stop"; nls['fr']['pauseButton'] = "Pause"; 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']['cond'] = "Cond."; @@ -37,9 +45,9 @@ 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']['cond'] = "Cond."; @@ -56,18 +64,28 @@ 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 +94,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!"; From 55f3776363f7e1991f2574e8144682568a831ce8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:00:39 +0200 Subject: [PATCH 07/13] Bump braces from 3.0.2 to 3.0.3 (#126) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c375bc5..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", From 2af66b4ea0fe3c6672968a5f670991d81c1d8488 Mon Sep 17 00:00:00 2001 From: Nils Peters Date: Fri, 9 Aug 2024 15:26:32 +0200 Subject: [PATCH 08/13] updating FAU logo (#132) --- design/images/techfak.svg | 446 ++++++++++++++------------------------ 1 file changed, 165 insertions(+), 281 deletions(-) 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c27b096874dbb8eafcb8a59236648f8718192681 Mon Sep 17 00:00:00 2001 From: Kishor KL Date: Fri, 20 Sep 2024 12:10:31 +0200 Subject: [PATCH 09/13] Disabled showwavform for Ranking and fixed an issue with submitting results for ranking. --- configs/ranking_noloop.yaml | 34 +++++++++++------------ lib/webmushra/audio/MushraAudioControl.js | 6 ++-- lib/webmushra/nls/nls.js | 3 ++ lib/webmushra/pages/FinishPage.js | 27 ++++++++++++++++++ lib/webmushra/pages/RankingPage.js | 6 ++-- service/write.php | 2 +- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/configs/ranking_noloop.yaml b/configs/ranking_noloop.yaml index 6b40a1fa..df3d8eb2 100644 --- a/configs/ranking_noloop.yaml +++ b/configs/ranking_noloop.yaml @@ -8,6 +8,7 @@ stopOnErrors: true showButtonPreviousPage: true remoteService: service/write.php +# Ranking test does not support showing the waveform of the samples currently pages: - type: generic @@ -19,30 +20,28 @@ pages: id: trial1 name: Mono Trial content: test description - showWaveform: false 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 + 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 - showWaveform: false 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 + 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 @@ -50,13 +49,12 @@ pages: showResults: true writeResults: true questionnaire: - - type: text - label: Nickname - name: id - - type: number - label: Age - - name: age - min: 0 - max: 100 - default: 30 + - type: text + label: Nickname + name: id + - type: number + label: Age + name: age + min: 0 + max: 100 + default: 30 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 076a8053..75a521f3 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -38,6 +38,7 @@ nls['en']['fair'] = "Fair"; 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"; @@ -48,6 +49,7 @@ nls['de']['fair'] = "Fair"; 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"; @@ -58,6 +60,7 @@ 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"; 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 index a36386e4..c3c4ae3b 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -264,10 +264,12 @@ RankingPage.prototype.render = function (_parent) { } - this.macic = new MushraAudioControlInputController(this.mushraAudioControl, this.pageConfig.enableLooping); + this.macic = new MushraAudioControlInputController(this.mushraAudioControl, this.pageConfig.enableLooping, add_ref_to_conditions=false); this.macic.bind(); - this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], this.pageConfig.showWaveform, this.pageConfig.enableLooping, this.mushraAudioControl); + //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(); diff --git a/service/write.php b/service/write.php index ba863b54..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++){ From 2af8ee4e1b32f10b500f4f0a70d037e0099a262a Mon Sep 17 00:00:00 2001 From: "Kishor.Kayyar" Date: Wed, 12 Apr 2023 12:56:33 +0200 Subject: [PATCH 10/13] Added ranking by elimination test --- README.md | 1 + configs/ranking_noloop.yaml | 62 ++++ doc/experimenter.md | 16 + index.html | 1 + lib/webmushra/nls/nls.js | 6 + lib/webmushra/pages/RankingPage.js | 469 +++++++++++++++++++++++++++++ service/write.php | 30 ++ startup.js | 3 + 8 files changed, 588 insertions(+) create mode 100644 configs/ranking_noloop.yaml create mode 100644 lib/webmushra/pages/RankingPage.js diff --git a/README.md b/README.md index 20384a89..64de2005 100644 --- a/README.md +++ b/README.md @@ -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..6b40a1fa --- /dev/null +++ b/configs/ranking_noloop.yaml @@ -0,0 +1,62 @@ +# 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 + + +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 + showWaveform: false + 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 + showWaveform: false + 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/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/nls/nls.js b/lib/webmushra/nls/nls.js index 4eee50a4..bfc9cbc7 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -9,6 +9,8 @@ nls['en']['previousButton'] = "Previous"; nls['en']['playButton'] = "Play"; 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"; @@ -16,6 +18,8 @@ nls['de']['previousButton'] = "Vorherige Seite"; nls['de']['playButton'] = "Start"; 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"; @@ -23,6 +27,8 @@ 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"; diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js new file mode 100644 index 00000000..b6c57880 --- /dev/null +++ b/lib/webmushra/pages/RankingPage.js @@ -0,0 +1,469 @@ +/************************************************************************* + (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); + this.macic.bind(); + + this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], this.pageConfig.showWaveform, 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.ratings.length !== 0) { + var scales = $(".scales"); + var i; + for (i = 0; i < scales.length; ++i) { + //$(".scales").eq(i).val(this.ratings[i].value).slider("refresh"); + } + } + 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; + /*for (i = 0; i < scales.length; ++i) { + this.ratings[i] = 0; //{name: scales[i].name, value: scales[i].value}; + }*/ + 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; + } + /*for (i = 0; i < this.ratings.length; ++i) { + var rating = this.ratings[i]; + var ratingObj = new MUSHRARating(); + ratingObj.stimulus = rating.name; + ratingObj.score = 0; //rating.value; + ratingObj.time = this.time; + trial.responses[trial.responses.length] = ratingObj; + }*/ + this.session.trials[this.session.trials.length] = trial; +}; diff --git a/service/write.php b/service/write.php index 9855cae8..ba863b54 100644 --- a/service/write.php +++ b/service/write.php @@ -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") { From 03b07f6e934ee4cd754b2abf0fdf197c05c2ec53 Mon Sep 17 00:00:00 2001 From: kishorkl Date: Wed, 19 Jul 2023 16:31:58 +0200 Subject: [PATCH 11/13] Update RankingPage.js per @faroit comments --- lib/webmushra/pages/RankingPage.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js index b6c57880..dfff7e97 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -424,9 +424,6 @@ RankingPage.prototype.save = function () { var scales = $(".scales"); this.ratings = []; var i; - /*for (i = 0; i < scales.length; ++i) { - this.ratings[i] = 0; //{name: scales[i].name, value: scales[i].value}; - }*/ if (this.itemEliminated != -1) { this.rank = this.rank + 1; } @@ -457,13 +454,5 @@ RankingPage.prototype.store = function () { ratingObj.time = this.time; trial.responses[trial.responses.length] = ratingObj; } - /*for (i = 0; i < this.ratings.length; ++i) { - var rating = this.ratings[i]; - var ratingObj = new MUSHRARating(); - ratingObj.stimulus = rating.name; - ratingObj.score = 0; //rating.value; - ratingObj.time = this.time; - trial.responses[trial.responses.length] = ratingObj; - }*/ this.session.trials[this.session.trials.length] = trial; }; From 9bf336691fc2170d227d9937e387a175bd81d994 Mon Sep 17 00:00:00 2001 From: kishorkl Date: Fri, 21 Jul 2023 15:00:28 +0200 Subject: [PATCH 12/13] Update RankingPage.js per @faroit comments --- lib/webmushra/pages/RankingPage.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/webmushra/pages/RankingPage.js b/lib/webmushra/pages/RankingPage.js index dfff7e97..a36386e4 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -398,13 +398,6 @@ RankingPage.prototype.load = function () { this.mushraAudioControl.initAudio(); - if (this.ratings.length !== 0) { - var scales = $(".scales"); - var i; - for (i = 0; i < scales.length; ++i) { - //$(".scales").eq(i).val(this.ratings[i].value).slider("refresh"); - } - } 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); From b7cb6fd2d079be2a9ec23347552860cbee74617d Mon Sep 17 00:00:00 2001 From: Kishor KL Date: Fri, 20 Sep 2024 12:10:31 +0200 Subject: [PATCH 13/13] Disabled showwavform for Ranking and fixed an issue with submitting results for ranking. --- configs/ranking_noloop.yaml | 34 +++++++++++------------ lib/webmushra/audio/MushraAudioControl.js | 6 ++-- lib/webmushra/nls/nls.js | 3 ++ lib/webmushra/pages/FinishPage.js | 27 ++++++++++++++++++ lib/webmushra/pages/RankingPage.js | 6 ++-- service/write.php | 2 +- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/configs/ranking_noloop.yaml b/configs/ranking_noloop.yaml index 6b40a1fa..df3d8eb2 100644 --- a/configs/ranking_noloop.yaml +++ b/configs/ranking_noloop.yaml @@ -8,6 +8,7 @@ stopOnErrors: true showButtonPreviousPage: true remoteService: service/write.php +# Ranking test does not support showing the waveform of the samples currently pages: - type: generic @@ -19,30 +20,28 @@ pages: id: trial1 name: Mono Trial content: test description - showWaveform: false 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 + 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 - showWaveform: false 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 + 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 @@ -50,13 +49,12 @@ pages: showResults: true writeResults: true questionnaire: - - type: text - label: Nickname - name: id - - type: number - label: Age - - name: age - min: 0 - max: 100 - default: 30 + - type: text + label: Nickname + name: id + - type: number + label: Age + name: age + min: 0 + max: 100 + default: 30 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 bfc9cbc7..68cea613 100644 --- a/lib/webmushra/nls/nls.js +++ b/lib/webmushra/nls/nls.js @@ -46,6 +46,7 @@ nls['en']['fair'] = "Fair"; 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"; @@ -56,6 +57,7 @@ nls['de']['fair'] = "Fair"; 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"; @@ -66,6 +68,7 @@ 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"; 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 index a36386e4..c3c4ae3b 100644 --- a/lib/webmushra/pages/RankingPage.js +++ b/lib/webmushra/pages/RankingPage.js @@ -264,10 +264,12 @@ RankingPage.prototype.render = function (_parent) { } - this.macic = new MushraAudioControlInputController(this.mushraAudioControl, this.pageConfig.enableLooping); + this.macic = new MushraAudioControlInputController(this.mushraAudioControl, this.pageConfig.enableLooping, add_ref_to_conditions=false); this.macic.bind(); - this.waveformVisualizer = new WaveformVisualizer(this.pageManager.getPageVariableName(this) + ".waveformVisualizer", tdRight, this.conditions[0], this.pageConfig.showWaveform, this.pageConfig.enableLooping, this.mushraAudioControl); + //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(); diff --git a/service/write.php b/service/write.php index ba863b54..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++){