Independently sequential variable inside a randomized loop

Mac OS
PsychoPy v.2021.2.3

I am trying to have sound stimuli play every 2.25 seconds while randomized visual stimuli are displayed every 3 seconds followed by a question and key response over a number of trials.

The visual stimuli use a .xlsx spread sheet within a randomized loop. Each trial, a new set of visual stimuli are presented with a corresponding question and answer. Each trial lasts 3.9 seconds so that the visual stimuli is presented at 1.4 seconds from t=0 and last .9 seconds. This means that by the next trial it will have been 3 seconds since the visual stimuli were presented.

Incorporating the sound stimuli is where I am having trouble. I have determined a list of exact onset times for the sound. Within the same routine as the visual stimuli I have included two sound stimuli which I would like to follow predetermined onset times in their exact order from a list (so that they play every 2.25 seconds, and independently from the visual stimuli -I don’t want them lining up all the time).

This is the list of onset times for the two stimuli:

sound_1Onset sound_2Onset
0 2.25
0.6 2.85
1.2 3.45
1.8 I don’t want the sound to play this trial
0.15 2.4
0.75 3
1.35 3.6
1.95 I don’t want the sound to play this trial
0.3 2.55
0.9 3.15
1.5 3.75
2.1 I don’t want the sound to play this trial
0.45 2.7
1.05 3.3
1.65 I don’t want the sound to play this trial

As you can see, if the 2 sound stimuli are played at the above times, in the specified order, from trial 1 to n, then the sound onset will be every 2.25 seconds.

Normally, I could have the sound’s onsets be played in the specified order each loop by setting the loop type to sequential. However, since the visual stimuli must be randomized, the loop type must also be random. My first thought was to create a nested loop, however if I simply put the random loop inside the sequential loop, then the full list of random variables is gone through for each condition in the sequential loop, which is not what I am looking for (I believe this is the same if the sequential loop is nested in the random loop). My next thought was to add the sound stimuli to the spreadsheet with the visual stimuli and have only the visual stimuli randomized from the sheet, while the sound onset is sequential. This does not work because the randomized stimuli correspond to the other randomized stimuli in the row, and I get an error since the script expects an integer from the sheet… to make a long story short, I don’t know if that’s the way to go about this either, unless I am approaching it completely wrong. For the same reason I don’t think I can randomize the visual stimuli using the method described here in the forum post I mention later, since the visual variables correspond to other variables in the row such as the display of other visual stimuli at the same time and questions. I am not sure if there is a way around this?

My idea now is to specify the sound onset variable in a code component (in builder view, I find it easier than working with builder generated code), and then each trial have the onset be drawn from that list in the given order. (Originally I thought another way to do this would be to adjust the next onset time by 2.25 seconds, but then realized that some of the onsets would exceed the bounds of the trial time and make things messy).

My primary question is, how do I use the code component to do what I mentioned above (in builder view, although if easier in an IDE, please advise), and how do I then record the onset times in the data sheet from the variable I created (with the question responses and onset times of the visual stimuli)? Would I then just input “$nameOfVariable” into the onset for the sound stimulus block I have in the routine?
This comes close to what I want:

but instead of randomization I need the list played in the order specified, and don’t want to pop out numbers incase I cycle through the list more than once in its given order. Are there any more efficient ways to have the sequential presentation occur independently from the randomized presentation of the visual stimuli?

One final note: you may notice that there are certain trials that I don’t want sound played. Is there an efficient way to do this?

Any help would be appreciated.

I would use a custom clock myClock = core.Clock() and check the remainder in Begin Routine.

sound_1Onset = myClock.getTime()%2.25
if sound_1Onset < 1.65:
     sound_1.setVolume(1)
     sound_2.setVolume(1)
     sound_2Onset = sound_1Onset+2.25
     thisExp.addData('sound_1Onset',sound_1Onset)
     thisExp.addData('sound_2Onset',sound_2Onset)
else:
     sound_1.setVolume(0)     
     sound_2.setVolume(0)     
     thisExp.addData('sound_1Onset','')
     thisExp.addData('sound_2Onset','')

Thank you! If I understand correctly, if sound_1Onset < 1.65: is used since I don’t want sound_2 to play on any of the trials where sound_1 onset is 1.65 sec or later. The only issue I am having now is that I get the error:

if sound_2.status == NOT_STARTED and tThisFlip >= sound_2Onset-frameTolerance:
NameError: name 'sound_2Onset' is not defined

I think this is because sound_2Onset is only being defined if sound_1Onset occurs? Should I define sound_2Onset above if sound_1Onset < 1.65: as well? Also, am I right in inserting $sound_1Onset and $sound_2Onset in the box for start time in the stimuli?

You could put sound_2Onset = 0 in Begin Experiment

You could put sound_2Onset = sound_1Onset in the else: branch, but it shouldn’t be needed.

You are correctly setting the start time.

One other thing, if the I paste the code into the Begin Experiment tab the sound plays every 2.25 seconds, but always at the same 2.25 second intervals (it seems). However, if I paste the code into the Begin Routine tab sound does not play for the entirety of certain trials, so I might need to tweak it a bit I’m thinking? Otherwise, seems to be solved! Thanks again.

Perhaps I misunderstood your spreadsheet. Do you want sound_1 to play every trial? If so, then the Begin Routine code is:

sound_1Onset = myClock.getTime()%2.25
thisExp.addData('sound_1Onset',sound_1Onset)
if sound_1Onset < 1.65:
     sound_2.setVolume(1)
     sound_2Onset = sound_1Onset+2.25
     thisExp.addData('sound_2Onset',sound_2Onset)
else:
     sound_2.setVolume(0)     
     thisExp.addData('sound_2Onset','')

Oh sorry! the text I had pasted from my sheet was reformatted it should look like this (I added in trial numbers for clarity):

sound_1Onset for each trial:
Trial 1: 0
Trial 2: 0.6
Trial 3: 1.2
Trial 4: 1.8
Trial 5: 0.15
Trial 6: 0.75
Trial 7: 1.35
Trial 8: 1.95
Trial 9: 0.3
Trial 10: 0.9
Trial 11: 1.5
Trial 12: 2.1
Trial 13: 0.45
Trial 14: 1.05
Trial 15: 1.65

sound_2Onset for each trial:
Trial 1: 2.25
Trial 2: 2.85
Trial 3: 3.45
Trial 4: I don’t want sound_2 to play this trial (which can be accomplished as you suggested by setting volume to 0)
Trial 5: 2.4
Trial 6: 3
Trial 7: 3.6
Trial 8: I don’t want sound_2 to play this trial
Trial 9: 2.55
Trial 10: 3.15
Trial 11: 3.75
Trial 12: I don’t want sound_2 to play this trial
Trial 13: 2.7
Trial 14: 3.3
Trial 15: I don’t want sound_2 to play this trial

So that, for example, in trial 1, sound_1 onset is at 0s and sound_2 onset is at 2.25s. Then in trial 2, sound_1 onset is at 0.6s (which would be 2.25 seconds after 2.25 seconds since each trial lasts 3.9 seconds total) and sound_2 onset is at 2.85s.

This being said, I don’t need them at these exact times, as long as a sound plays every 2.25 seconds while the trials are occurring. So, using a custom clock works, but I don’t want it reseting each trial, so that there is always a 2.25s interval even between the second sound of the previous trial and the first sound of the subsequent one.

So is it working now?

Some of the onset times are still not quite right (trial 4 to 5 doesn’t have a 2.25 second space between the first and second sound onset) (see the “TestRunOnsetTimes” screenshot below).
TestRunOnsetTimes

This is reflected in the start times, because it seems that on some of the trials the sound plays two times in quick succession. In other words, it seems there sometimes isn’t a full 2.25 seconds between the second sound of the previous trial and the first sound of the next. The start times also look odd in the spreadsheet:


The .wav file I am using is only .5 seconds long but I could shorten it to .25s, so I don’t think this is the problem.

Ok, I think I’ve identified the problem as being sound_1Onset still, everything after that looks great. I guess I am a bit confused by the role of the modulo operator in sound_1Onset = myClock.getTime()%2.25

My above response might also clarify the what the issue is.

Would it be possible to have it cycle through my list of numbers in order instead?

Say, by defining a list earlier on in the code, rather than using a spread sheet, and then using a modulo operator to sequentially use that list of numbers as sound_1Onset (even within the randomized loop)?

x%y divides x by y and returns the remainder (i.e. x/y - floor(x/y)

You absolutely could cycle through a list of valid onset times instead. Why doesn’t it always increase by .6? I think that’s where my code is varying from what you want.

Got it! Thank you so so much for your help!

I adapted your solution by putting the following in begin experiment:

sound_1OnsetTimes = [ 1.65, 1.05, 0.45, 2.1, 1.5, 0.9, 0.3, 1.95, 1.35, 0.75, 0.15, 1.8, 1.2, 0.6, 0]

and then the following in begin routine:

sound_1Onset = sound_1OnsetTimes.pop()
thisExp.addData('sound_1Onset',sound_1Onset)
if sound_1Onset < 1.65:
     sound_2.setVolume(1)
     sound_2Onset = sound_1Onset+2.25
     thisExp.addData('sound_2Onset',sound_2Onset)
else:
     sound_2.setVolume(0)     
     thisExp.addData('sound_2Onset','')
2 Likes