Playing quatrophonic sound (4.0 surround-sound) in psychopy

Hi,

I’m planning to present four simultaneous sounds to participants on four different speakers. Is there already a library for incorporating soundcards with four output channels in python or psychopy?

Thanks a lot!
Julia

PsychoPy provides access to various sound libraries (pyo, sounddevice, etc) and I think most of these provide options to play sounds with extra channels. This isn’t tasted in PsychoPy’s own sound code but you could try play a 4-channel sound and see what happens! :slight_smile:

Hi,

Did you every find a good solution? I am currently trying to play 4-channel audio through 4 different speakers. I am using a 4-channel USB-C audio interface but am having problems with audio files with more than 2 channels. PsychoPy has no problem playing 2-channel audio files through the USB-C audio interface (so I know it’s not USB-C related) but it does not like playing 4-channel audio files, even though the audio interface supports it and I have played 4-channel audio through it using other audio software on the same PC. Any suggestions would be greatly appreciated.

Cheers,
Mick

Please expand the phrase “it does not like”. What happens? I’m guessing there’s an error but we’d need to see it to work out where the issue is. Maybe the underlying libs don’t support it (in which case we’re stuck) but maybe it’s just that we haven’t tried this yet in PsychoPy and haven’t covered that option

Hi Thanks for responding. The error I get out is as follows:

Traceback (most recent call last):
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 448, in _setSndFromArray
self.sndArr.shape = [len(thisArray), 2]
ValueError: cannot reshape array of size 25795776 into shape (6448944,2)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 444, in
present_audio(audio_filename)
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 123, in present_audio
audio = sound.Sound(filename)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 318, in init
hamming=self.hamming)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 361, in setSound
_SoundBase.setSound(self, value, secs, octave, hamming, log)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound_base.py”, line 195, in setSound
self._setSndFromFile(p)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 425, in _setSndFromFile
self._setSndFromArray(sndArr)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 452, in _setSndFromArray
.format(self.sndArr.shape, self.channels))
ValueError: Failed to format sound with shape (6448944, 4) into sound with channels=4

Would it be possible to edit the underlying libs to handle 4-channel audio? I’ve tried doing it myself but haven’t had any success. I’ve also tried several other python audio libs including: playsound, simpleaudio, winsound, sounddevice, pydub, pyo

Cheers,
Mick

Thanks, the error message helps us get started!

From this, we can inspect the code in PsychoPy itself (backend_sounddevice.py”, line 452) and sure enough, we can see that the PsychoPy code here is assuming the sound to be mono or stereo, so we will certainly need to update this to support more flexible sounds specs.

As a starter, you could change this:

                self.sndArr.shape = [len(thisArray), 2]

to this:

                self.sndArr.shape = [len(thisArray), self.channels]

and I’m sure that will get rid of your current error but

  1. I suspect you will then get to another error, because clearly nobody has made this work in the past (or they would have hit the bug above)
  2. you might find that you fix all the issues within PsychoPy and it still doesn’t work (if the issue is actually in the back-end)

I could potentially get a device and you could send me a demo file to test if you tell me what (exact) device you’re using. No promises when I’d get time though.

Thanks for the suggestion Jon. I had tried something similar but unfortunately the problem lies elsewhere, as I get the following error:

Traceback (most recent call last):
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 444, in
present_audio(audio_filename)
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 123, in present_audio
audio = sound.Sound(filename)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 318, in init
hamming=self.hamming)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 361, in setSound
_SoundBase.setSound(self, value, secs, octave, hamming, log)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound_base.py”, line 195, in setSound
self._setSndFromFile(p)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 425, in _setSndFromFile
self._setSndFromArray(sndArr)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 462, in _setSndFromArray
“stereo. Shape={}”.format(self.sndArr.shape))
OSError: Couldn’t determine whether array is stereo. Shape=(6448944, 4)

To determine that the array is stereo, I changed this:

elif self.sndArr.shape[1] == 2:

to this:

elif self.sndArr.shape[1] >= 2:

But then got this error:

Traceback (most recent call last):
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 444, in
present_audio(audio_filename)
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 123, in present_audio
audio = sound.Sound(filename)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 318, in init
hamming=self.hamming)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 361, in setSound
_SoundBase.setSound(self, value, secs, octave, hamming, log)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound_base.py”, line 195, in setSound
self._setSndFromFile(p)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 426, in _setSndFromFile
self._channelCheck(self.sndArr) # Check for fewer channels in stream vs data array
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 477, in _channelCheck
raise ValueError(msg)
ValueError: The sound stream is set up incorrectly. You have fewer channels in the buffer than in data file (2 vs 4).
Ensure you have selected ‘Force stereo’ in experiment settings

To deal with this inconsistency in size, I changed:

self.__dict__['channels'] = 2

to this:

self.__dict__['channels'] = self.sndArr.shape[1]

But then got this error:

Traceback (most recent call last):
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 444, in
present_audio(audio_filename)
File “C:\PsychoPy\Scripts\aad_presentation_v2.py”, line 123, in present_audio
audio = sound.Sound(filename)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 318, in init
hamming=self.hamming)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 366, in setSound
blockSize=self.blockSize)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 96, in getStream
blockSize=blockSize)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 140, in _getStream
device=defaultOutput)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\psychopy\sound\backend_sounddevice.py”, line 171, in init
callback=self.callback)
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\sounddevice.py”, line 1373, in init
**_remove_self(locals()))
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\sounddevice.py”, line 779, in init
‘Error opening {0}’.format(self.class.name))
File “C:\Program Files (x86)\PsychoPy3\lib\site-packages\sounddevice.py”, line 2571, in _check
raise PortAudioError(errormsg, err)
sounddevice.PortAudioError: Error opening OutputStream: Invalid number of channels [PaErrorCode -9998]

This is where I’ve been stuck for a bit. The device we are using is the PreSonus Studio 26c (https://www.presonus.com/products/Studio-26C), but I’m sure you can use any 4-channel interface. I can send you on a sample audio file if you DM me your details. You should also be able to play the 4-channel audio file without such a device, the channels will just get mixed down.

Thanks again,
Mick

OK, that’s progress. So you’ve worked out the steps to fix the PsychoPy end of things (the beauty of open source!)

Next thing to work out is what your new blockage is, out of the following options:

  • sounddevice lib (from a quick look it seems this is written to handle multi-channel outputs)
  • portaudio (again this looks like it should be fine)
  • the device itself (e.g. not correctly reporting to portaudio that it can handle 4 channels)

Could you open a python shell or script and try:

import sounddevice as sd
print(sd.query_devices())

and see whther your device shows up there and how many channels it reports

Thanks Jon,

The devices that were showing up when I used the pyaudio library only showed a 2-channel output device but the command you send is showing a 4-channel output so I think we’re getting somewhere! It’s further down the list though but it’s there nonetheless (Studio 26c). Here’s the list of devices that show up:

0 Microsoft Sound Mapper - Input, MME (2 in, 0 out)
1 Microphone (2- Studio 26c), MME (2 in, 0 out)
2 Internal Microphone (Conexant I, MME (2 in, 0 out)
3 Microsoft Sound Mapper - Output, MME (0 in, 2 out)
4 Speakers (2- Studio 26c), MME (0 in, 2 out)
5 Speakers (Conexant ISST Audio), MME (0 in, 2 out)
6 Primary Sound Capture Driver, Windows DirectSound (2 in, 0 out)
7 Microphone (2- Studio 26c), Windows DirectSound (2 in, 0 out)
8 Internal Microphone (Conexant ISST Audio), Windows DirectSound (2 in, 0 out)
9 Primary Sound Driver, Windows DirectSound (0 in, 4 out)
10 Speakers (2- Studio 26c), Windows DirectSound (0 in, 4 out)
11 Speakers (Conexant ISST Audio), Windows DirectSound (0 in, 2 out)
12 ASIO4ALL v2, ASIO (2 in, 4 out)
13 Speakers (Conexant ISST Audio), Windows WASAPI (0 in, 2 out)
14 Speakers (2- Studio 26c), Windows WASAPI (0 in, 4 out)
15 Internal Microphone (Conexant ISST Audio), Windows WASAPI (2 in, 0 out)
16 Microphone (2- Studio 26c), Windows WASAPI (2 in, 0 out)
17 Microphone Array (Conexant ISST Audio capture), Windows WDM-KS (2 in, 0 out)
18 Output 1 (Conexant ISST Audio output), Windows WDM-KS (0 in, 2 out)
19 Output 2 (Conexant ISST Audio output), Windows WDM-KS (0 in, 6 out)
20 Input (Conexant ISST Audio output), Windows WDM-KS (2 in, 0 out)
21 Stereo Mix (Conexant ISST Stereo Mix), Windows WDM-KS (2 in, 0 out)
22 Speakers (Studio 26c), Windows WDM-KS (0 in, 4 out)
23 Microphone (Studio 26c), Windows WDM-KS (2 in, 0 out)

Now I just need to figure out how to set that as the default device?

Cheers,
Mick

I finally got it to work (with yours and a colleague’s help). Given that the sounddevice library you recommended was able to detect instances of the device with 4-channel outputs, I used that same library to specify and play the 4-channel device as follows:

import sounddevice as sd 
import soundfile as sf

data, fs = sf.read(filename, dtype='float32')  
sd.play(data, fs, device=10)
status = sd.wait()

The PsychoPy sound library also calls the same sounddevice library so I assume this can also be done using the former?

Thanks again for your help!

Mick