Timed push buttons demo#

For the full set of experiment files, see demos/pipelines/05-timed-push-buttons in the GitHub repository.

"""
In this experiment participants mark and describe interesting moments in a piece of music.
"""
# pylint: disable=missing-class-docstring,missing-function-docstring

from pathlib import Path

import psynet.experiment

from psynet.asset import asset  # noqa
from psynet.timeline import Timeline, join, PageMaker
from psynet.page import InfoPage
from psynet.modular_page import ModularPage, AudioPrompt, TextControl
from psynet.trial.static import StaticNode, StaticTrial, StaticTrialMaker
from markupsafe import Markup

from .control import SingleTimedPushButtonControl


STIMULUS_DIR = "data/global_music"
STIMULUS_PATTERN = "*.mp3"
TRIALS_PER_PARTICIPANT = 2


def get_timeline():
    return Timeline(
        InfoPage("Welcome! You will listen to audio and mark interesting moments.", time_estimate=5),
        # CodeBlock(lambda participant: participant.var.set("event", [1])),
        StaticTrialMaker(
            id_="audio_timed_button_trial",
            trial_class=AudioTimedButtonTrial,
            nodes=get_nodes,  # not get_nodes()!
            expected_trials_per_participant=TRIALS_PER_PARTICIPANT,
            max_trials_per_participant=TRIALS_PER_PARTICIPANT,
        ),
        InfoPage("Thank you for participating!", time_estimate=5)
    )


def get_nodes():
    return [
        StaticNode(
            definition={
                "stimulus_name": path.stem
            },
            assets={
                "stimulus_audio": asset(path, cache=False),  # reuse the uploaded file between deployments
            },
        )
        for path in Path(STIMULUS_DIR).glob(STIMULUS_PATTERN)
    ]


class AudioTimedButtonTrial(StaticTrial):
    time_estimate = 120
    accumulate_answers = True

    def show_trial(self, experiment, participant):
        return join(
            self.mark_interesting_moments(participant),
            # Put this in a PageMaker because we don't know how many interesting moments there will be until
            # self.mark_interesting_moments is complete
            PageMaker(self.describe_interesting_moments, time_estimate=45)
        )

    def mark_interesting_moments(self, participant):
        # The participant marks interesting moments by pressing a button
        return ModularPage(
            "event_times",
            prompt=AudioPrompt(
                audio=self.assets["stimulus_audio"],
                text=Markup("<div style='text-align: center;'>Listen to the music and press the button when it's <strong>interesting</strong><br><br></div>"),
                controls=False
            ),
            control=SingleTimedPushButtonControl(
                label="Interesting",
                button_highlight_duration=0.75
            ),
            time_estimate=15,
            save_answer="event_times", # Saves the answer in the participant.var.event_times variable
        )


    def describe_interesting_moments(self, participant):
        # The participant describes the interesting moments
        return [
            ModularPage(
                f"event_description_{i}",
                AudioPrompt(self.assets["stimulus_audio"],
                    Markup(f"""<div style='text-align: center;'>
                           You indicated that at {event_time} seconds you found the music interesting.<br>
                           Can you tell us why? We'll play that moment again for you.
                           </div>"""),
                    play_window=[max(0, event_time - 3), event_time + 3],
                    controls={"Play": "Replay"},
                ),
                TextControl(one_line=False, width = "800px", height = "400px"),
                time_estimate=30
            )
            for i, event_time in enumerate(participant.var.event_times)
        ]


class Experiment(psynet.experiment.Experiment):
    timeline = get_timeline()