How to properly loop a sound stimulus for certain amount of time?

I was trying to create a function that loops a sound file using psychopy sound class in a way similar to how it can be achieved through the pygame mixer below.

from psychopy import visual,core
from pygame import mixer
win = visual.Window(fullscr = False, size = (1200,900),
                    color = (-1.0,-1.0,-1.0), units = 'norm', monitor = 'testMonitor')
text= visual.TextStim(win, text ='test', height = 0.06, color = 'white',pos = (0,0))
def play_sound(timing):
    mySound = mixer.Sound('sound_file.wav')
    duration = core.CountdownTimer(timing)
    duration.reset()
    # loop the sound until duration reaches 0
    while duration.getTime() > 0:
        mySound.play(loops=-1)
        text.draw()
        win.flip()
    mySound.stop()
mixer.init()
play_sound(10)

But when I tried to achieve it by implementing the psychopy sound class, I kelt getting the error below.

from psychopy import sound,visual,core
win = visual.Window(fullscr = False, size = (1200,900),
                    color = (-1.0,-1.0,-1.0), units = 'norm', monitor = 'testMonitor')
text= visual.TextStim(win, text ='test', height = 0.06, color = 'white',pos = (0,0))
def play_sound(timing):
    mySound = sound.Sound(value='sound_file.wav',loops=-1)
    duration = core.CountdownTimer(timing)
    duration.reset()
    while duration.getTime() > 0:
        mySound.play()
        text.draw()
        win.flip()
    mySound.stop()

play_sound(10)

From cffi callback <function _StreamBase.init..callback_ptr at 0x000001B81DC06828>:
Traceback (most recent call last):
File “C:\Program Files\Python37\lib\site-packages\sounddevice.py”, line 741, in callback_ptr
return _wrap_callback(callback, data, frames, time, status)
File “C:\Program Files\Python37\lib\site-packages\sounddevice.py”, line 2517, in _wrap_callback
callback(*args)
File “C:\Program Files\Python37\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 201, in callback
dat *= thisSound.volume # Set the volume block by block
TypeError: unsupported operand type(s) for *=: ‘NoneType’ and ‘float’

I didn’t find much example about how the sound class can be operated in this way in the documentation. Thanks for help!

That’s an interesting bug, for sure. I’ve never needed loops and I suspect it’s actually broken. I’ve gotten sound looping to work just fine in some of my projects by checking the sound status and just calling “play” if the status indicates it’s not currently playing. I’ve modified your code to use my approach (including initializing the sound object a little differently), see if it works any better.

from psychopy import sound,visual,core
from psychopy.constants import STARTED, PLAYING
win = visual.Window(fullscr = False, size = (1200,900),
                    color = (-1.0,-1.0,-1.0), units = 'norm', monitor = 'testMonitor')
text= visual.TextStim(win, text ='test', height = 0.06, color = 'white',pos = (0,0))
def play_sound(timing):
    mySound = sound.Sound('sound_file.wav')
    duration = core.CountdownTimer(timing)
    duration.reset()
    while duration.getTime() > 0:
        if mySound.status not in [STARTED, PLAYING]:
            mySound.play()
        text.draw()
        win.flip()
    mySound.stop()

play_sound(10)
1 Like

Thanks for your help!

Hi Jonathan,

I also have this error in my experiment, but I’m mainly using the builder and know little about python coding. Could you please have a look at my experiment?

What I want to achieve:

In my main routine, I have a conditions file where after every 5 trials of stimuli there is 1 trial of filler. For the stimuli, corresponding sounds will be played; for the fillers, no sound is needed, so I put a very short silence sound as a placeholder. All the paths of sound files are in the column “elicit_recording” in the conditions file.

For stimuli trials, participants will press ‘space’ to play sound (and replay it as many times as they want), then press ‘right’ to move to the next trial; for fillers trials, the silence sound should play automatically without key press, so participants will only press ‘right’ to move to the next trial.

I used to run the experiment smoothly, but after I replaced the test sounds with the actual sound files, a problem occurred: the sounds of the first 5 stimuli trials play fine, but after the first filler, the sounds never play and a TypeError occured:

  File "sounddevice.pyc", line 733, in callback_ptr
  File "sounddevice.pyc", line 2508, in _wrap_callback
  File "/Applications/PsychoPy2021.app/Contents/Resources/lib/python3.6/psychopy/sound/backend_sounddevice.py", line 201, in callback
    dat *= thisSound.volume  # Set the volume block by block
TypeError: unsupported operand type(s) for *=: 'NoneType' and 'float'
##### Experiment ended. #####

Below are the codes and components I’m using:

I used a code component to create and play/replay sounds:

## Begin Routine:
# create sound components
sound_replay = sound.Sound(value=elicit_recording, secs=-1)

## Each Frame:
# if stimuli
if elicit_recording != 'silence_0.2s.wav':
    # press space,play sound
    if event.getKeys(keyList=['space']) and sound_replay.status != STARTED:
        sound_replay.play()
# if fillers
elif elicit_recording == 'silence_0.2s.wav':
    # play sound
    sound_replay.play()

(I also tried the codes below, but problem’s the same:)

## Begin Routine:
# create sound components
sound_stimuli = sound.Sound(value=elicit_recording, secs=-1)
sound_filler = sound.Sound(value=elicit_recording, secs=0.1)
sound_filler.setVolume(0.0) # silent sound

if elicit_recording != 'A':
    sound_replay = sound_stimuli
else:
    sound_replay = sound_filler

## Each Frame:
# if stimuli
if sound_replay == sound_stimuli:
    # press space,play sound
    if event.getKeys(keyList=['space']) and sound_replay.status != STARTED:
        sound_replay.play()
# if fillers
elif sound_replay == sound_filler:
    # play sound
    sound_replay.play()

Then, I used a key component to move to the next trial. In key properties >> Start, I chose ‘condition’ and filled in:

sound_replay.status==FINISHED

What I’ve tried:

  1. I’ve tried changing the hardware preferences as in this post: Sounds stop playing after 2/3 stimuli when running the experiment several times. But sounddevice is the only one I can use - I’ve met lots of problems when installing pyo, and the sounds keep off and on when using ptb.

  2. I commented the line sound_filler.setVolume(0.0)

  3. I uninstalled PsychoPy, cleaned all the remaining files, and reinstalled it, to reset the userPrefs.cfg file.

  4. I made sure there’s no empty cell/row/column in my conditions file.

  5. I added an if condition of the key press for sound play before playing the filler sound, just as for the stimuli sound, as below:

# if fillers
elif sound_replay == sound_filler:
    # play sound
    if event.getKeys(keyList=['space']) and sound_replay.status != STARTED:
        sound_replay.play()

and now the stimuli sounds following the first filler can finally play. But I don’t want an extra ‘space’ press for the filler trials.

If I replace if event.getKeys(keyList=['space']) and sound_replay.status != STARTED: to if not event.getKeys(keyList=['space']) and sound_replay.status != STARTED:, the same problem occurred.

So, all I know now is that the problem should be caused by the if condition of dividing the stimuli/fillers sounds to vary in key press, but I don’t know how to solve it.

I’ll be so grateful if any idea/advice could be provided! The error report is about the source file instead of my experiment file, so I have no idea about how to modify my codes at all. Many thanks in advance!

So the basic issue is that you have a “filler” sound which, if I’m understanding this correctly, should just be silence, and the basic problem is that the silence is leading to this NoneType crash. If you give it an empty WAV file for your silence then I can guess how that might happen, and sounddevice has always been a little unreliable about playing tones.

The second constraint is the activation condition for your “advance” key.

I think the solution is to just avoid loading a sound altogether in the filler trials, and use a separate boolean for the key component to move to the next trial.

### Begin routine
allow_advance_key = False
if elicit_recording != 'silence_0.2s.wav':
   sound_replay = sound.Sound(value=elicit_recording, secs=-1)
   filler = False
else:
    filler = True

### Each frame:
if not filler:
    if event.getKeys(keyList=['space']) and sound_replay.status != STARTED:
        sound_replay.play()
    if sound_replay.status == FINISHED:
        allow_advance_key = True
else:
    allow_advance_key = True

Then just set the condition on the right arrow key component to “allow_advance_key”.

If you run into an error about allow_advance_key being “referenced before assignment”, just add allow_advance_key = False to the “Before experiment” tab of the code component to ensure that it has global scope. It will be reset at the start of each trial anyway by the “Begin routine” code.

Thank you so much for letting me know about using an extra boolean - the codes run perfectly!

Just a quick following question: According to your codes, we actually skip playing sounds for the filler trials. So it actually doesn’t matter what filename is used for the filler sound as long as it’s allowed in python (for example, ‘silence_0.2s.wav’ or ‘A’ is good, but ‘1’ or a blank cell is bad). Is that right?

That’s correct, any distinctive text string should be fine.

Got it. Again, many many thanks for your help!