Playing randomised audio files through independently randomised trial loops

Hi there
Sorry in advance for the lengthy post, I’m just trying to avoid any ambiguity so any readers can get right down to the question at hand.

I’m in the process of constructing an N-back task, and am attempting to integrate some music which will play in the background while the task is occurring. It’s otherwise complete, I just need to figure out how to integrate the music in the random manner that I need.

Basically I have three different n-back condition blocks, 1 back, 2 back and 3 back, which are made up of a ‘n-back target’ routine that presents the first couple of targets without asking for response, followed by the ‘n-back’ routine consisting of one trial with a trials loop around it to make up the necessary number of trials, followed by a ‘rest’ routine to allow a brief 10-second pause between blocks.

I have randomised the order of each block, using a block loop set to n reps encompassing the full block (ie 1backtarget, 1backroutine and rest, etc) and a Block Select routine in front of the block loops, which itself is enclosed in a loop running the full way from Blockselect to the end of the third and final block. The block select routine picks a number either 1, 2 or 3 corresponding to the block loops for each n-back block, and specifies for one rep of the selected block and zero reps of the others. Obviously this repeats twice with block select choosing another number each time until each block has run.

I have three different music conditions, (song 1, song 2 and silence) and wish for each one to play continuously for the duration of the trials from each block (eg song 1 starts at the beginning of the 1-back trials and plays continuously through until they are all complete, and the same for the next two n-back conditions with the other two music conditions. However what I am uncertain about is how to do this in a manner in which the order of the music conditions is itself randomised independently from the random order of the n-back conditions.

There was a recent post explaining how to add code to ensure a song played through a set of trials without the trials being interrupted, but as far as I could tell, using the proposed method would restrict me to either associating a single song with an individual routine (eg song1 with 1back block, song 2 with 2back block, etc), or making the music condition fully random each time, meaning that I could potentially have song1 and 1back, followed by song1 again and 3back (etc, hopefully you get the idea), whereas I just want regular randomness, eg if song1 and silence played during the first 2 blocks then song2 has to play during the third block.

I’m wondering is there an easy way to add some code to achieve what I want without having radically change how I have the experiment set up in builder so far. Any help would be greatly appreciated.

Thanks in advance
Joe

Just realised I can include images, sorry, hopefully this clears up any confusion about the order of things


For reference, you mean this thread I think:

Why wouldn’t that sort of approach work for you?

In that thread, you’ll see that the list of music files is explicitly randomised in code, independently of any other conditions.

But if I’m filling that code into each trial routine individually then won’t the selection only be randomised within the routine and for it to be possible for the second and third instances end up playing the same audio file? That’s what I’d assumed would happen, I didn’t think it would be ‘remembered’ and carried over.

I’m a bit lost. The relevant post says clearly “Put something like this in the “begin experiment” tab:” i.e. the randomisation code executes just once per experiment.

OK, I’ve only just looked at your screen shots above and maybe part of the issue is that the implementation of the experiment is currently far more complicated than it needs to be, and will produce data in a tabular form that will be unnecessarily difficult to work with. You could simplify it markedly. You only need:

  • Three routines:
    • one to show the stimulus
    • one to gather the response (± continuing to show the stimulus), omitted on certain trials.
    • one to rest between blocks.
  • Two loops:
    • the trial loop, encompassing the stimulus and response routines.
    • the block loop, encompassing the inner loop and the rest routine.

i.e. your trial would then consist of two consecutive routines, so that you can drop the response collection on selected trials, at the benefit of eliminating all the other routines, other than the ‘rest’ one.

You should probably shift your rest routine to before the trial loop, rather than after. That way it can serve as an instruction routine (for example, stating the n-back value), and won’t needlessly occur at the end of the last block, when no rest is necessary.

I don’t know how you are managing the conditions files for the blocks (do they all use the same file, but in a random order, or do they each have their own file, presented either randomly or in a fixed order, etc). You’ll need to specify that for a full solution, but I’m going to assume they all use the same file but in a random order for the timebeing.

So the outer loop controls the order of the n-back blocks. Its conditions file could be as simple as a column of three numbers 1, 2, and 3, and I’m going to assume has a column name of n_back_number. The outer loop I think from your description, is set to be random.

Now I hope that you can see that there is no difference between your blocks and routines except for the current value of n_back_number, which is why we can re-cycle them. For example, you can’t gather a response until the nth plus 1 trial. This can be achieved by putting this code in the “Begin routine” tab of a code component on your “response” routine:

if trials.thisN < n_back_number # remember, trials count from 0
    continueRoutine = False # don't collect responses until n_back trials elapsed

That single change eliminates five of your routines and eight loops, and results in a much tidier data output file.

Hopefully it also makes it easier to see how the code posted in the other thread works: it independently shuffles the order of three sound files, one of which will be used on each iteration of the outer loop. It will be simple to control these now, as you now only have a single code component, in your single stimulus routine, to start playing the relevant sound file on the first iteration of the inner, trial loop.

Make sense?

Definitely makes sense, the only issue is that it requires adjustment of my code, hence I was doing the experiment in the (apparently needlessly complicated) way because I don’t know how to write a single piece of code that would cover all three conditions. I’ll just paste what I’ve used so you get how what I’m currently using works. I started building the experiment by using someone else’s previously created experiment file and just modified the code slightly until i managed to make it work, with no real knowledge of coding, so the complications are somewhat hereditary from this previous file coupled with my lack of actual skills. Anyway it goes like this:

For the 1-back task, the initial letter is presented

#begin experiment
Back1Minus1=[]
#begin routine
letters = ['B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z']
from random import choice
letter=choice(letters)
#end routine
Back1minus1=letter

and followed by the trial routine which looks like this and is surrounded by the trial loop to determine number of reps/number of target/nontargets.

#begin routine
if trialType=='nonTarget':
    letters = ['B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z']
    from random import choice
    letter=choice(letters)
    if letter==Back1minus1:
            from random import choice
            letter=choice(letters)
elif trialType=='target':
    letter=Back1minus1
# each frame
if len(key_resp_5.keys)==0:
    letterXpos=0
if len(key_resp_5.keys)>0:
    letterXpos=2
#end routine
Back1minus1=letter
letterXpos=0

For the 3-back the initial 3 stimuli are presented with

#begin experiment
Back3minus1=[]
Back3minus2=[]
Back3minus3=[]
#begin routine
letters = ['B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z']
from random import choice
letter=choice(letters)
#end routine
Back3minus3=Back3minus2
Back3minus2=Back3minus1
Back3minus1=letter

which is surrounded by a loop to generate the 3 reps, and then followed with:

#begin routine
    from random import choice
    letter=choice(letters)
    if letter==Back3minus2:
            from random import choice
            letter=choice(letters)
    if letter==Back3minus1:
            from random import choice
            letter=choice(letters)
elif trialType=='target':
    letter=Back3minus3
#each frame
if len(key_resp_6.keys)==0:
    letterXpos=0
if len(key_resp_6.keys)>0:
    letterXpos=2
#end routine
Back3minus3=Back3minus2
Back3minus2=Back3minus1
Back3minus1=letter
letterXpos=0

which is also surrounded by a loop to generate the correct number of reps etc

I left out the 2-back as I assume you can tell what it looks like from what i’ve given you.
Presumably this is far too complicated, so how do I simplify it?

Thanks again

That code is a bit of a mess. It would be more useful if you describe (precisely) what you want to do, rather than give code from someone else who tried to do something similar.

What I want to do is fairly straightforward (I think).
As I’ve said, I’d like to have the experiment divided into three blocks, made up of 1-back, 2-back and 3-back conditions. In each condition, a random sequence of letters are presented from a list of possible letters (I’ve been using a 20-letter list for the moment). In the event a presented letter matches the letter presented n letters back, the participant is required to press the key for ‘yes’, and in the event the letter does not match, they must press the key for no. Lets say ‘q’ for no and ‘p’ for yes for the moment.
The trial loop determines the total number of trials that require a positive or negative response using an excel file containing this data, eg 10 ‘yes’ and 50 ‘no’ trials per block. I also wish one of three audio conditions to play during each block, two individual pieces of music and a silent control condition. The order of both the ‘n-back’ and audio conditions should be randomised independently. I wish to record both whether the participant’s response was correct or incorrect, and the speed of the response.
That’s it, basically.

Hi @scholefj,

If you want to randomize presentation of your music files independent of your condition list, then before your task begins you could simply create a python dictionary of conditions and sound files, create a list of dictionary keys and shuffle them so that, on every iteration of your experiment, a randomly selected sound file is allocated to each nBack condition (see example below - remember to save the condition name in the data file so that you have a log of what music you have played for each condition.):


# Create a dict of your music files
musicDict = {"ConditionA": "songA.wav","ConditionB": "songB.wav","ConditionC": "silence.wav"}

# Create a list of condition names
conditionList = list(musicDict.keys())

# shuffle list in place
shuffle(conditionList)

# Use shuffled list to call music files in your experiment, printed below so you can see how they randomize

# 1-nBack music
print(musicDict[conditionList[0]])
# 2-nBack music
print(musicDict[conditionList[1]])
# 3-nback music
print(musicDict[conditionList[2]])

# You create a sound object 
# and change the sound file for each block:
# 1-nBack
sound = sound.Sound(musicDict[conditionList[0]], secs=-1)
sound.setVolume(1)
sound.play()
# 2-nBack
sound = sound.Sound(musicDict[conditionList[1]], secs=-1)
sound.setVolume(1)
sound.play()
# etc...
```

Okay thanks @dvbridges, where exactly am i to locate the dictionary, in a section of my experiment file, or somewhere else entirely?

Sorry to bump you @Michael I’m just wondering if you have any suggestions for revising my code as per my last response

@scholefj if you are using Builder, you can create the dictionary in a code component at the beginning of the experiment, and set the sound at the beginning of the routines for each block. If you are using Coder, you can place the dictionary anywhere before the experiment loop begins, and again for each block set the sound before the trials begin so they are ready to play before the trial loop begins.

Note, code edited as no need to import random. Shuffle is already imported

Try the code below. This is untested, but the goal is to produce code that works regardless of the value of n_back_number, so that the same code can be used in each block:

Begin experiment:

from numpy.random import choice
letters = ['B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z']

Begin routine:

if trials.thisN == 0: # at the start of each n-back condition
    history = [] # an empty list to store the sequence across trials within a block

letter = choice(letters) # letter to present on this trial
history.append(letter) # add it to the historical sequence
thisExp.addData('current_letter', letter) # store it in the data for this trial

# work out what letter was shown n_back_number trials ago:
if trials.thisN < n_back_number:
    n_back_letter = None
else:
    n_back_letter = history[-(n_back_number + 1)]

# store it in the data for this trial:
thisExp.addData('n_back_letter', n_back_letter) 

End routine:

# get the response:
response = your_keyboard_component.keys[0]

# calculate what it should have been:
letter_correct = (letter == n_back_letter)

# decide if subject was correct or incorrect:
if (letter_correct and response == 'y') or (not letter_correct and response == 'n'):
    response_correct = True
else:
    response_correct = False

# store the result:
thisExp.addData('response_correct', response_correct)

Okay thanks very much for that.

I’m getting an Attribute error: ‘numpy.int64’ object has no attribute ‘thisN’, I presume I need to define ‘thisN’ in some manner, I understand what it means within the context of the code but I’m not clear on how I can incorporate it so the program understands what it means.

Also, is the code you gave me just meant to go in my ‘response’ routine? Am I to produce something different for the stimulus routine? Or am I missing the point entirely here? Sorry for all the questions

You need to have a loop for your trials, and trials is the default name for that, but you can call your loop whatever you wish. Presumably you already have variable trials defined somewhere, which is a simple integer rather than a TrialHandler (i.e. loop) object.

The first two sections belong on the routine where you show your stimuli. The code component needs to be above the text component that you use to display the letter, so that the text component gets to refer to the latest update value for that trial.

The “end routine” section of code belongs on whatever routine you collect your response, and assumes that your keyboard component is set to end the routine.

I had it named trial_loop, had assumed thisN was something independent rather than referring to trials.
I renamed it so that’s resolved. However I’m now getting an error message saying ‘numpy.int64 object is not iterable’ for ‘thisTrial in trials’ when it reaches this section just before it would commence running my stimulus routine

    for thisTrial in trials:
        currentLoop = trials
        # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
        if thisTrial != None:
            for paramName in thisTrial.keys():
                exec(paramName + '= thisTrial.' + paramName)

All that I currently have within the routine other than the code component is a text component containing $letter
Have I just misnamed something again?

Got the code working, thanks for that. The one thing I’m missing now is that the code takes a random letter from the list each time, leading to very few n-back matches, whereas what I need is for a certain number of my trials to be ‘target’ trials ie ones where there’s an n-back match. My excel file with the column determining the number of trials also has another column entitled trialtype, with trials being listed as either target or nontarget, so I need a little piece of code that ensures if the trialtype is nontarget then the letter won’t be an n-back match, and if the trialtype is target the letter has to be an n-back match.
Also I need to ensure that response time as well as whether the response is right or wrong is recorded.
Thanks again you’ve been a big help

What you actually need to do is very clearly define the algorithm needed for the selection of the stimuli on each trial. Random selection of letters is not compatible with a fixed proportion of target and non-target trials. Nominating each trial to be a target trial or not is possible but remember, the letters selected for the target trial (which are not drawn randomly) nonetheless become part of your historical stimulus sequence, and so will quickly become biased in favour of particular letters, dependent on the size of the possible set of letters and the proportion of target trials.

If you want the simplicity of random selection but with a reasonable proportion of target trials, then by virtue of the laws of probability, markedly reduce the length of your list of possible letters.

The choice of algorithm is your responsibility as an experimenter, not that of some random person posting answers on an internet forum.