Skip to content

Commit

Permalink
Merge pull request #1250 from Amsterdam-Music-Lab/feature/session-pos…
Browse files Browse the repository at this point in the history
…t-cleanup

Fix `speech2song`, `musical_preferences` and `huang_2022` after refactor
  • Loading branch information
BeritJanssen authored Sep 9, 2024
2 parents 731ea46 + 9b39cf8 commit d00aaf8
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 108 deletions.
3 changes: 2 additions & 1 deletion backend/experiment/rules/huang_2022.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ def next_round(self, session: Session):
step_numbers=True,
button_label=_("Continue")
)
actions.extend([explainer, explainer_devices, *self.next_song_sync_action(session, round_number)])
playlist = Playlist(session.block.playlists.all())
actions.extend([explainer, explainer_devices, playlist, *self.next_song_sync_action(session, round_number)])
else:
# Load the heard_before offset.
heard_before_offset = session.load_json_data().get('heard_before_offset')
Expand Down
70 changes: 35 additions & 35 deletions backend/experiment/rules/musical_preferences.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 django.template.loader import render_to_string

from experiment.actions import Explainer, Final, HTML, Playlist, Redirect, Step, Trial
from experiment.actions import Explainer, Final, HTML, Redirect, Step, Trial
from experiment.actions.form import BooleanQuestion, ChoiceQuestion, Form, LikertQuestionIcon
from experiment.actions.playback import Autoplay
from experiment.actions.styles import STYLE_BOOLEAN, STYLE_BOOLEAN_NEGATIVE_FIRST
Expand All @@ -19,13 +19,23 @@


class MusicalPreferences(Base):
ID = "MUSICAL_PREFERENCES"
default_consent_file = "consent/consent_musical_preferences.html"
preference_offset = 20
knowledge_offset = 42
contact_email = "musicexp_china@163.com"
counted_result_keys = ["like_song"]
know_score = {"yes": 2, "unsure": 1, "no": 0}
''' This rules file presents repeated trials with a combined form:
participants are asked to state how much they like the song, and whether they know the song
after 21 and 42 rounds, participants see summaries of their choices,
and at the final round, participants see other participants' preferred songs
'''
ID = 'MUSICAL_PREFERENCES'
default_consent_file = 'consent/consent_musical_preferences.html'
preference_offset = 21 # after this many rounds rounds, show information with the participant's preferences
knowledge_offset = 42 # after this many rounds, show additionally how many songs the participant knows
contact_email = 'musicexp_china@163.com'
counted_result_keys = ['like_song']

know_score = {
'yes': 2,
'unsure': 1,
'no': 0
}

def __init__(self):
self.question_series = [
Expand Down Expand Up @@ -76,7 +86,6 @@ def next_round(self, session: Session):
)
return [explainer, *question_trials]
else:
playlist = Playlist(session.block.playlists.all())
explainer = Explainer(
instruction=_("How to play"),
steps=[
Expand All @@ -88,7 +97,7 @@ def next_round(self, session: Session):
],
button_label=_("Start"),
)
actions = [playlist, explainer]
actions = [explainer]
else:
if last_result.question_key == "audio_check1":
playback = get_test_playback()
Expand Down Expand Up @@ -118,30 +127,20 @@ def next_round(self, session: Session):
return Redirect(settings.HOMEPAGE)
else:
playback = get_test_playback()
html = HTML(body="<h4>{}</h4>".format(_("Do you hear the music?")))
form = Form(
form=[
BooleanQuestion(
key="audio_check1",
choices={"no": _("No"), "yes": _("Yes")},
result_id=prepare_result("audio_check1", session, scoring_rule="BOOLEAN"),
submits=True,
style=STYLE_BOOLEAN_NEGATIVE_FIRST,
)
]
)
return [
self.get_intro_explainer(),
Trial(
playback=playback,
feedback_form=form,
html=html,
config={"response_time": 15},
title=_("Audio check"),
),
]
if round_number == self.preference_offset + 1:
like_results = session.result_set.filter(question_key="like_song")
html = HTML(
body='<h4>{}</h4>'.format(_('Do you hear the music?')))
form = Form(form=[BooleanQuestion(
key='audio_check1',
choices={'no': _('No'), 'yes': _('Yes')},
result_id=prepare_result('audio_check1', session,
scoring_rule='BOOLEAN'),
submits=True,
style=STYLE_BOOLEAN_NEGATIVE_FIRST)])
return [self.get_intro_explainer(), Trial(playback=playback, feedback_form=form, html=html,
config={'response_time': 15},
title=_("Audio check"))]
if round_number == self.preference_offset:
like_results = session.result_set.filter(question_key='like_song')
feedback = Trial(
html=HTML(
body=render_to_string(
Expand Down Expand Up @@ -216,7 +215,8 @@ def next_round(self, session: Session):
view = Trial(
playback=playback,
feedback_form=form,
title=_("Song %(round)s/%(total)s") % {"round": round_number, "total": session.block.rounds},
title=_('Song %(round)s/%(total)s') % {
'round': round_number + 1, 'total': session.block.rounds},
config={
"response_time": section.duration + 0.1,
},
Expand Down
138 changes: 71 additions & 67 deletions backend/experiment/rules/speech2song.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import random

from django.utils.translation import gettext as _
from django.template.loader import render_to_string

from .base import Base

from experiment.actions import Consent, Explainer, Step, Final, Playlist, Trial
from experiment.actions import Explainer, Step, Final, Trial
from experiment.actions.form import Form, RadiosQuestion
from experiment.actions.playback import Autoplay
from question.demographics import EXTRA_DEMOGRAPHICS
from question.languages import LANGUAGE, LanguageQuestion
from question.utils import question_by_key
from question.languages import LanguageQuestion

from session.models import Session

from result.utils import prepare_result

n_representations = 8
n_trials_per_block = 8
n_rounds_per_block = n_trials_per_block * 2 # each trial has two rounds


class Speech2Song(Base):
""" Rules for a speech-to-song experiment """
ID = 'SPEECH_TO_SONG'
default_consent_file = 'consent/consent_speech2song.html'

n_presentations = 8
n_trials_per_block = 8
n_rounds_per_trial = 2

def __init__(self):
self.question_series = [
{
Expand Down Expand Up @@ -57,39 +54,46 @@ def get_intro_explainer(self):
button_label=_('Start')
)

def next_round(self, session):
def next_round(self, session: Session):
blocks = [1, 2, 3]
# shuffle blocks based on session.id as seed -> always same order for same session
random.seed(session.id)
random.shuffle(blocks)
# group_ids for practice (0), or one of the speech blocks (1-3)
actions = []
is_speech = True
if session.current_round == 1:
rounds_passed = session.get_rounds_passed(self.counted_result_keys)
if rounds_passed == 0:
question_trials = self.get_open_questions(session)
if question_trials:
session.save_json_data({'quesionnaire': True})
return [self.get_intro_explainer(), *question_trials]

explainer = Explainer(
instruction=_(
'Thank you for answering these questions about your background!'),
steps=[
Step(
description=_(
'Now you will hear a sound repeated multiple times.')
),
Step(
description=_(
'Please listen to the following segment carefully, if possible with headphones.')
),
],
button_label=_('OK')
)
return [
explainer,
*next_repeated_representation(session, is_speech, 0)
]
if session.current_round == 2:
elif session.load_json_data().get('questionnaire'):
explainer = Explainer(
instruction=_(
'Thank you for answering these questions about your background!'),
steps=[
Step(
description=_(
'Now you will hear a sound repeated multiple times.')
),
Step(
description=_(
'Please listen to the following segment carefully, if possible with headphones.')
),
],
button_label=_('OK')
)
return [
explainer,
*self.next_repeated_representation(session, is_speech, 0)
]
else:
return [
self.get_intro_explainer(),
*self.next_repeated_representation(session, is_speech, 0)
]
elif rounds_passed == 1:
e1 = Explainer(
instruction=_('Previous studies have shown that many people perceive the segment you just heard as song-like after repetition, but it is no problem if you do not share that perception because there is a wide range of individual differences.'),
steps=[],
Expand All @@ -107,13 +111,13 @@ def next_round(self, session):
)
actions.extend([e1, e2])
group_id = blocks[0]
elif 2 < session.current_round <= n_rounds_per_block + 1:
elif 2 <= rounds_passed <= self.n_trials_per_block * self.n_rounds_per_trial:
group_id = blocks[0]
elif n_rounds_per_block + 1 < session.current_round <= 2 * n_rounds_per_block + 1:
elif self.n_trials_per_block * self.n_rounds_per_trial < rounds_passed <= 2 * self.n_trials_per_block * self.n_rounds_per_trial:
group_id = blocks[1]
elif 2 * n_rounds_per_block + 1 < session.current_round <= 3 * n_rounds_per_block + 1:
elif 2 * self.n_trials_per_block * self.n_rounds_per_trial < rounds_passed <= 3 * self.n_trials_per_block * self.n_rounds_per_trial:
group_id = blocks[2]
elif session.current_round == 3 * n_rounds_per_block + 2:
elif rounds_passed == 3 * self.n_trials_per_block * self.n_rounds_per_trial + 1:
# Final block (environmental sounds)
e3 = Explainer(
instruction=_('Part2'),
Expand All @@ -134,7 +138,7 @@ def next_round(self, session):
actions.append(e3)
group_id = 4
is_speech = False
elif 3 * n_rounds_per_block + 2 < session.current_round <= 4 * n_rounds_per_block + 1:
elif 3 * self.n_trials_per_block * self.n_rounds_per_trial < rounds_passed <= 4 * self.n_trials_per_block * self.n_rounds_per_trial:
group_id = 4
is_speech = False
else:
Expand All @@ -148,44 +152,44 @@ def next_round(self, session):
final_text=_(
'Thank you for contributing your time to science!')
)
if session.current_round % 2 == 0:
if rounds_passed % 2 == 1:
# even round: single representation (first round are questions only)
actions.extend(next_single_representation(
actions.extend(self.next_single_representation(
session, is_speech, group_id))
else:
# uneven round: repeated representation
actions.extend(next_repeated_representation(
actions.extend(self.next_repeated_representation(
session, is_speech))
return actions


def next_single_representation(session: Session, is_speech: bool, group_id: int) -> list:
""" combine a question after the first representation,
and several repeated representations of the sound,
with a final question"""
filter_by = {'group': group_id}
section = session.playlist.get_section(filter_by, song_ids=session.get_unused_song_ids())
actions = [sound(section), speech_or_sound_question(session, section, is_speech)]
return actions


def next_repeated_representation(session: Session, is_speech: bool, group_id: int = -1) -> list:
if group_id == 0:
# for the Test case, there is no previous section to look at
section = session.playlist.section_set.get(group=group_id)
else:
section = session.previous_section()
actions = [sound(section)] * n_representations
actions.append(speech_or_sound_question(session, section, is_speech))
return actions


def speech_or_sound_question(session, section, is_speech) -> Trial:
if is_speech:
question = question_speech(session, section)
else:
question = question_sound(session, section)
return Trial(playback=None, feedback_form=Form([question]))
def next_single_representation(self, session: Session, is_speech: bool, group_id: int) -> list:
""" combine a question after the first representation,
and several repeated representations of the sound,
with a final question"""
filter_by = {'group': group_id}
section = session.playlist.get_section(filter_by, song_ids=session.get_unused_song_ids())
actions = [sound(section), self.speech_or_sound_question(session, section, is_speech)]
return actions


def next_repeated_representation(self, session: Session, is_speech: bool, group_id: int = -1) -> list:
if group_id == 0:
# for the Test case, there is no previous section to look at
section = session.playlist.section_set.get(group=group_id)
else:
section = session.previous_section()
actions = [sound(section)] * self.n_presentations
actions.append(self.speech_or_sound_question(session, section, is_speech))
return actions


def speech_or_sound_question(self, session, section, is_speech) -> Trial:
if is_speech:
question = question_speech(session, section)
else:
question = question_sound(session, section)
return Trial(playback=None, feedback_form=Form([question]))


def question_speech(session, section):
Expand Down
Loading

0 comments on commit d00aaf8

Please sign in to comment.