Skip to content

Commit

Permalink
Merge pull request #1360 from Amsterdam-Music-Lab/feature/practice-rules
Browse files Browse the repository at this point in the history
Use a Practice ruleset to implement training phases
  • Loading branch information
BeritJanssen authored Jan 7, 2025
2 parents 8a7dcf2 + b90dccc commit 2ff63d1
Show file tree
Hide file tree
Showing 44 changed files with 10,598 additions and 7,699 deletions.
8 changes: 4 additions & 4 deletions backend/docs/experiment_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

::: experiment.rules.base

## experiment.rules.staircasing
## experiment.rules.practice

::: experiment.rules.util.staircasing
::: experiment.rules.practice

## experiment.rules.practice
## experiment.rules.staircasing

::: experiment.rules.util.practice
::: experiment.rules.util.staircasing
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from session.models import Session


class NewBlockRuleset(Base):
class NewBlockRuleset(BaseRules):
''' A block type that could be used to test musical preferences '''
ID = 'NEW_BLOCK_RULESET'
contact_email = '[email protected]'
Expand Down
129 changes: 42 additions & 87 deletions backend/experiment/rules/anisochrony.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import logging
from django.utils.translation import gettext_lazy as _

from section.models import Section
from experiment.actions import Trial, Explainer, Step
from experiment.actions.form import ChoiceQuestion, Form
from experiment.actions.playback import Autoplay
from experiment.actions import Explainer, Step
from experiment.actions.utils import render_feedback_trivia
from .duration_discrimination import DurationDiscrimination

Expand All @@ -16,12 +13,23 @@ class Anisochrony(DurationDiscrimination):
start_diff = 180000
practice_diff = 270000
max_turnpoints = 8
catch_condition = 'REGULAR'
task_description = "Anisochrony"
subtask = ""
first_condition = "irregular"
second_condition = "regular"
first_condition_i18n = _("IRREGULAR")
second_condition_i18n = _("REGULAR")
section_count = 249

def get_response_explainer(self, correct, correct_response, button_label=_('Next fragment')):
correct_response = _('REGULAR') if correct_response=='REGULAR' else _('IRREGULAR')
if correct:
def get_feedback_explainer(self, session):
button_label = _("Next fragment")
last_result = session.last_result()
correct_response = (
self.first_condition_i18n
if last_result.expected_response == self.first_condition
else self.second_condition_i18n
)
if last_result.given_response == last_result.expected_response:
instruction = _(
'The tones were {}. Your answer was CORRECT.').format(correct_response)
else:
Expand All @@ -33,88 +41,35 @@ def get_response_explainer(self, correct, correct_response, button_label=_('Next
button_label=button_label
)

def next_trial_action(self, session, trial_condition, difficulty):
"""
Provide the next trial action
Arguments:
- session: the session
- trial_condition: 1 for catch trial, 0 for normal trial
- difficulty: the current difficulty (in ms) of the trial
"""
from result.utils import prepare_result
if trial_condition == 1:
# catch trial
difference = 0
else:
difference = difficulty
try:
section = session.playlist.section_set.get(song__name=difference)
except Section.DoesNotExist:
return None
expected_response = 'REGULAR' if difference == 0 else 'IRREGULAR'
key = 'if_regular'
question = ChoiceQuestion(
key=key,
question=_(
"Were the tones REGULAR or IRREGULAR?"),
choices={
'REGULAR': _('REGULAR'),
'IRREGULAR': _('IRREGULAR')
},
view='BUTTON_ARRAY',
result_id=prepare_result(key, session, section=section, expected_response=expected_response),
submits=True
)

playback = Autoplay([section])
form = Form([question])
config = {
'listen_first': True,
'response_time': section.duration + .1
}
view = Trial(
playback=playback,
feedback_form=form,
title=_('Anisochrony'),
config=config
)
return view

def intro_explanation(self, *args):
def get_intro_explanainer(self):
return Explainer(
instruction=_(
'In this test you will hear a series of tones for each trial.'),
"In this test you will hear a series of tones for each trial."
),
steps=[
Step(_(
"It's your job to decide if the tones sound REGULAR or IRREGULAR")),
Step(_(
'During the experiment it will become more difficult to hear the difference.')),
Step(_(
"Try to answer as accurately as possible, even if you're uncertain.")),
Step(_(
"Remember: try not to move or tap along with the sounds")),
Step(_(
'This test will take around 4 minutes to complete. Try to stay focused for the entire test!'))
Step(
_("It's your job to decide if the tones sound REGULAR or IRREGULAR")
),
Step(
_(
"During the experiment it will become more difficult to hear the difference."
)
),
Step(
_(
"Try to answer as accurately as possible, even if you're uncertain."
)
),
Step(_("Remember: try not to move or tap along with the sounds")),
Step(
_(
"This test will take around 4 minutes to complete. Try to stay focused for the entire test!"
)
),
],
button_label='Ok'
button_label=_("Ok"),
)

def calculate_score(self, result, data):
# a result's score is used to keep track of how many correct results were in a row
# for catch trial, set score to 2 -> not counted for calculating turnpoints
try:
expected_response = result.expected_response
except Exception as e:
logger.log(e)
expected_response = None
if expected_response and expected_response == result.given_response:
if expected_response == 'IRREGULAR':
return 1
else:
return 2
else:
return 0

def get_final_text(self, difference):
percentage = round(difference / 6000, 2)
feedback = _(
Expand All @@ -123,8 +78,8 @@ def get_final_text(self, difference):
Our brains use this to process rhythm even better!")
return render_feedback_trivia(feedback, trivia)

def get_difficulty(self, session, multiplier=1.0):
if session.final_score == 0:
def get_difficulty(self, session):
if not session.json_data.get("practice_done"):
return self.practice_diff
else:
return super(Anisochrony, self).get_difficulty(session, multiplier)
return super().get_difficulty(session)
2 changes: 1 addition & 1 deletion backend/experiment/rules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
logger = logging.getLogger(__name__)


class Base(object):
class BaseRules(object):
"""Base class for other rules classes"""

contact_email = settings.CONTACT_MAIL
Expand Down
6 changes: 3 additions & 3 deletions backend/experiment/rules/beat_alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.utils.translation import gettext_lazy as _

from .base import Base
from .base import BaseRules
from experiment.actions import Trial, Explainer, Step
from experiment.actions.form import ChoiceQuestion, Form
from experiment.actions.playback import Autoplay
Expand All @@ -14,7 +14,7 @@
logger = logging.getLogger(__name__)


class BeatAlignment(Base):
class BeatAlignment(BaseRules):
"""Rules for the beat alignment test by Mullensiefen et al. (2014)"""

ID = 'BEAT_ALIGNMENT'
Expand Down Expand Up @@ -112,7 +112,7 @@ def next_trial_action(self, session):
"""Get next section for given session"""
filter_by = {'tag': '0'}
section = session.playlist.get_section(filter_by, song_ids=session.get_unused_song_ids())
condition = section.song.name.split('_')[-1][:-4]
condition = section.song.name.split('_')[-1]
expected_response = 'ON' if condition == 'on' else 'OFF'
key = 'aligned'
question = ChoiceQuestion(
Expand Down
4 changes: 2 additions & 2 deletions backend/experiment/rules/categorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
from experiment.actions.wrappers import two_alternative_forced
from session.models import Session

from .base import Base
from .base import BaseRules

SCORE_AVG_MIN_TRAINING = 0.8


class Categorization(Base):
class Categorization(BaseRules):
ID = "CATEGORIZATION"
default_consent_file = "consent/consent_categorization.html"

Expand Down
4 changes: 2 additions & 2 deletions backend/experiment/rules/congosamediff.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from session.models import Session
from experiment.actions import ChoiceQuestion, Explainer, Form, Trial
from experiment.actions.playback import PlayButton
from .base import Base
from .base import BaseRules
from result.utils import prepare_result


class CongoSameDiff(Base):
class CongoSameDiff(BaseRules):
""" A micro-PROMS inspired experiment block that tests the participant's ability to distinguish between different sounds. """
ID = 'CONGOSAMEDIFF'
contact_email = '[email protected]'
Expand Down
Loading

0 comments on commit 2ff63d1

Please sign in to comment.