Repeat/skip trial to replay later

Hello!

I’ve created an experiment in the builder view where the subject hears a sentence and is then asked to repeat that sentence (the verbal response is recorded). They then press a key to continue to the next sentence. Pretty simple.

I would like to add the option to skip a sentence (i.e., not repeat it after hearing it), and then replay that sentence/trial later in the test. The subject would press “space” to skip the sentence until later.

The name of the event that plays the sentence is “Sentence”, and it draws the sound file (sequentially) from a column in an Excel sheet named “SoundFile”. My coding knowledge in PsychoPy is limited, but my best guess is that this should involve “append,” so something like below:

if event.getKeys([‘space’]):
SoundFile.append(Sentence)

Ideally, if a subject were to skip sentence #3, the experiment would play #4 and then play #3 before going on to #5. But if it just adds #3 to the very end, that would be ok.

I’m really not sure how to approach this, so if anyone has any guidance, I’d really appreciate it!

Thanks!

Kaitlyn

2 Likes

Hi Kaitlyn,

(1) The skipping bit is (relatively straightforward).
(2) The going back and repeating trials out of sequence is really tricky in Builder, as it isn’t designed to do that. Generally for more complicated sequences like this, you might be advised to create the experiment in Coder rather than Builder, where flexibility like this is more easily achievable. Having said that, I think it could be done in Builder, but it will be ugly.

Let’s just start with (1). You’ll need the very latest version (1.84, just released yesterday?) to get this to work.

– Split your trial into two consecutive routines. Routine1 plays the sentence and has a keyboard component to get a response. It seems that 'space' signals you want to skip recording the sound.
– Insert a code component in the second routine. In its Begin routine tab, put something like this:

# Don't run this routine if 'space' pressed in the previous routine:
if 'space' in name_of_your_keyboard_component.keys():
    continueRoutine = False
    # should also store the skipped trial number here but will come back to that

To do the management of which trial is run on any given iteration will be tricky. I think you will need two nested loops, encompassing both routines. In the inner loop, put 1 in the nReps field and a variable name in the selected rows field, like say, currentRow. Link it to your conditions file. i.e. this inner loop will only run a single selected trial at a time, it isn’t going to loop through all the rows in the file in one go.

The outer loop will run multiple times so that the entire conditions file does eventually get used. But this outer loop is not connected to your conditions file. You can’t know in advance how many times it will run (as you don’t know how many trials a subject will skip and require to be re-run), so just put a large number (like 999) in the nReps field. You’ll have to force it to terminate when all trials are complete.

In a code component in the first routine, put this in the Begin experiment tab:

# set the initial row to use
currentRow = 0 

Actually, I’ve just realised that this approach is predicated on you working sequentially rather than randomly through the conditions file. Can you tell us what the design is there before I go any further (random will be more complicated).

(I’ll leave the start of the solution here so I don’t forget and will complete it later once I hear).

1 Like

Hi there!

Thanks for your quick reply. The experiment works sequentially through the conditions file. To clarify the design:

  • In the first routine, there is an audio component (sentence is played) and a text component (“Playing…”). Once the sound is finished playing, this routine ends (this requires the condition $audio_component_name.status==FINISHED in the “Stop” field for the text component), and the next one automatically begins.
  • In the second routine, there is a text component ("Recording… Press ENTER to end recording, SPACE to skip), a microphone component to record the response, and a key response component that accepts “return” or “space” and forces the end of the routine. There’s also a code component (Each Frame) that stops the mic if return or space is pressed (otherwise the routine won’t end until the mic duration is over).

if event.getKeys([‘return’,‘space’]):
mic.stop()

I’d prefer to keep this design where the experiment automatically progresses from Routine 1 to Routine 2.

I was also pretty skeptical about how far I could get in builder view. I tend to build my basic experiments in the builder view, compile the code, and then update the code for the tricky stuff. I’ve found that I can get pretty far with adding code components in the builder view. I’m hesitant to build the entire experiment from scratch by code, and was thinking it wouldn’t make much of a difference in terms of the basic code.

Please let me know if you have any other questions!

Kaitlyn

OK, now it is more clear what you are actually doing. So let’s go with the nested loops arrangement. What we need to do is control which row of the conditions file will be used on each iteration of the outer loop, by putting an appropriate value in the selected rows field of the inner loop dialog.

So insert a code component in your first routine. In the Begin experiment tab, put something like this (assuming you have, say, 20 rows in your conditions file):

row_list = range(20)
row_list.reverse() # need the lowest values last, which get popped first
current_trial_num = row_list.pop() # get the initial value

In the inner loop dialog, put this in the Selected rows field:

current_trial_num

We’ll update this value later. This will give you the correct row number on each iteration. The trick is that you need to be able to insert the skipped trials back into the list, one place after the next scheduled row. This will be done in the 'Every frame` tab of the code component in the second routine, where you are checking for the keypresses. Something like this:

# check for a valid key
response = event.getKeys(['return','space'])

if 'return' in response or 'space' in response:
    mic.stop()
    thisExp.addData('response', response) # store in the data file
    countinueRoutine = False # move on to next trial

if 'space' in response:
    # insert the current trial number back into the list of
    # scheduled trials, one position back:
    row_list.insert(-1, current_trial_num)

In the End routine tab, you need to update the trial number, but also check that you haven’t emptied the list of rows, as trying to pop the next value from an empty list will cause an error:

if len(row_list) == 0:
    outer_loop_name.finished = True # all trials complete
else:
    # update the trial number for the next iteration:
    current_trial_num = row_list.pop()

That’s actually not quite as ugly as I thought, but still, no guarantees that this will actually work.

Also I’m not familiar with the microphone: not sure if you need to do anything there to explicitly save the file or if that is automatic.

Hope that helps,

Michael

1 Like

Hi Michael,

I’ve tried implementing your suggestions, but I’m getting stuck on the “Selected Rows” field of the inner loop. I’ve tried setting it with and without a $, and I get the following errors:

When Selected Rows = current_trial_num
Error:
trialList=data.importConditions(‘lists/testList.xlsx’, selection=u’current_trial_num’),
for n in selection:
TypeError: ‘NoneType’ object is not iterable.

When Selected Rows = $current_trial_num
Error:
trialList=data.importConditions(‘lists/testList.xlsx’, selection=current_trial_num’),
elif len(selection) > 0:
TypeError: object of type ‘int’ has no len()

Maybe I’m putting the wrong value in “row_list”. Should this be the name of my inner loop, or “trialList”? Or something else? Actually, this probably doesn’t make sense, as “row_list” is a variable that I created in the “Begin experiment” tab.

I apologize - I’m sure this is a relatively simple issue, but I’m unable to get past it and therefore unable to test the meat of this code.

Thank you!

Kaitlyn

It definitely needs a $ sign here, to indicate that this is a variable name rather than literal text. I think the second error is because the selected rows field can be a bit fussy, and is wanting the integer row number to be wrapped in a list even though it is just a single value. So try this:

$[current_trial_num]

Hi Michael,

Thanks, that worked! I made a couple of changes:

In the “Each Frame” tab for the 2nd routine, instead of:

# check for a valid key
response = event.getKeys(['return','space'])

if 'return' in response or 'space' in response:
    mic.stop()
    thisExp.addData('response', response) # store in the data file
    countinueRoutine = False # move on to next trial

I have:

response = endRecording.keys

if 'return' in response or 'space' in response:
    mic.stop()

“endRecording” is a keyboard component that forces the end of a routine on a ‘return’ or ‘space’ and stores the first key. This was important, as the event.getKeys command was not storing the responses properly. This was the only way I could get it to work.

The main functionality of skipping and replaying a sentence after one trial is working.

Now the microphone is mysteriously unreliable, only sometimes recording, but I think this may be a hardware issue. I will troubleshoot and report back!

Thanks!

Kaitlyn

Kaitlin in future please tick the solved box to indicate a question has been answered correctly. thanks :slight_smile:

Oops, sorry! Thanks!