Play multiple sound files on multiple output devices with Python and sounddevice

Ever wanted to have multiple different sound files playing on different output devices attached to a host computer? Say you’re writing a DJing application where you want one mix for headphones and one for the speakers. Or you’re doing some sort of kiosk or art installation where you have many sets of speakers that need to all be playing their own sound file but the whole thing needs to be synchronized. This would even be cool for something like an escape room.

The ladder example is where I needed this bit of code. I’ve been working with interdisciplinary artist Sara Dittrich on a few projects recently and she asked if I could come up with a way to play 8 different mono sound files on 8 different loudspeakers. Here’s a video of the whole setup in action, and an explanation of the project:

I’ve wrapped up all of the code for the art installation project, and that can be found in a github repo here. It includes the startup functionality etc. If you’re interested in recreating the video above, that repo would be a good starting place. The following is a list of the parts used to make that build happen:

Multi-Audio Example

It is worth it to give a simple example of how to play multiple files on multiple audio devices using python. I couldn’t find an examples on how to do this online and had to spend some time experimenting to make it all come together. Hopefully this saves you the trouble.

To install sounddevice on my Raspberry Pi, I had to run the following commands:

For this example, let’s say there are 4 audio files in the same directory as multi.py , so the directory looks like this:

The code is based on the sounddevice library for python, whose documentation is pretty sparse. This script will find the audio files, and then play them on as many devices as there are attached. For example, if you have 3 sound devices it will play 1.wav, 2.wav and 3.wav on devices 1-3. If you have any questions, feel free to ask:

Here are some more photos of the build:

48 Comments

  1. Thx! your job he inpire me.

    i work in a escape game in france

    i made a equivalent system in C++ with 3D sound gestion
    you can use 4 sound card too
    you can add sound in a config file and with a client in RAW like putty send a json in the 3216 port
    launch the sound, make it move with dopler effect etc..

    it’s always in WIP but if you want to take look …

    https://github.com/hyndruide/Multi-audio ( i need to clean the repo to 🙂 )

  2. Great work, I need to do something like this, so this resource is saving me some time!
    How can I best talk with you in more detail?

  3. Thank you very much, I was looking for something like this ! Works almost perfectly, however I noticed that it sounds like you have to divide the samplerate by the number of usb devices to get the play duration right… Is that just me ?

  4. Hey Devon!

    I’m Aga. I’m an artist currently working on a project that requires a similar set up.

    First of all great job! I’m very impressed with how clean your work is.

    I have a question about controlling the sound output. In my piece it is crucial that the sound loops play on their own and only overlap sporadically. I would like to crete two different situations that will interchange randomly. Firstly, I would like for one speaker to play while others are silent and sedondly to sometimes play different sounds simultaneously on several speakers.
    Each speaker would have an assigned and distinct sound loop (it will be fragments of interviews and each speaker will effectively represent one person). So the sounds would not migrate between the 12 speakers.

    I am wondering if using the set up with the usb to jack solution will allow me to asign each speaker its ‘identity’ and programme the interaction between them? Some sort of order of the sounds. Either random or pre-designed – im interested in both.

    Thank you for any kind of pointers!

    All the best,
    Aga

    1. Yeah that would be totally possible. You could get creative with joining the audio playback threads.

      This is the existing implementation:


      for thread in threads:
      thread.start()

      for thread, device_index in zip(threads, usb_sound_card_indices):
      print("Waiting for device", device_index, "to finish")
      thread.join()

      If you wanted the files to playback in a sequence, you’d use:


      for thread in threads:
      thread.start()
      thread.join()

      That way, you’d never get overlap.

  5. Hi Devon,

    I work with Raspberrypi 4 Debian and with Python 3.7.3
    I downloaded your code from github-esologic.

    In Python / Run Module / the code works very well!

    With input in the terminal
    $ python3 pear.py ./test

    and with the “crontab” routine
    crontab -e
    @reboot screen -dmS pear / bin / bash /home/pi/pear/runpear.sh

    does it not work!

    Did I forget something?
    What should I do?
    best martin

    1. Hi! There shouldn’t be any spaces in the /bin/bash part of the crontab line.

      It should be exactly:


      @reboot screen -dmS pear /bin/bash /home/pi/pear/runpear.sh

  6. hi Devon,
    thank you for the fast answer!

    @reboot screen -dmS pear /bin/bash /home/pi/pear/runpear.sh
    or
    @reboot screen -dmS pear /bin/bash/home/pi/pear/runpear.sh
    doesnt work!

  7. Hi Devon,

    I started the first attempt with

    pi@raspberrypi:~ $ python3 pear.py ./test
    bash: $: Command not found
    pi@raspberrypi:~ $

    and then with

    pi@raspberrypi:~ $ python3 /home/pi/pear-master/pear.py ./test
    Traceback (most recent call last):
    File “/home/pi/pear-master/pear.py”, line 88, in
    args = parser.parse_args()
    File “/usr/lib/python3.7/argparse.py”, line 1758, in parse_args
    args, argv = self.parse_known_args(args, namespace)
    File “/usr/lib/python3.7/argparse.py”, line 1790, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
    File “/usr/lib/python3.7/argparse.py”, line 1999, in _parse_known_args
    stop_index = consume_positionals(start_index)
    File “/usr/lib/python3.7/argparse.py”, line 1955, in consume_positionals
    take_action(action, args)
    File “/usr/lib/python3.7/argparse.py”, line 1848, in take_action
    argument_values = self._get_values(action, argument_strings)
    File “/usr/lib/python3.7/argparse.py”, line 2378, in _get_values
    value = self._get_value(action, arg_string)
    File “/usr/lib/python3.7/argparse.py”, line 2411, in _get_value
    result = type_func(arg_string)
    File “/home/pi/pear-master/pear.py”, line 39, in dir_path
    raise NotADirectoryError(path)
    NotADirectoryError: ./test
    pi@raspberrypi:~ $

    something doesn’t seem to be working!
    best martin

    1. Alright — so, the problem is with runpear.sh. You have to modify it to add the location of your sound files.


      #!/bin/bash

      echo "waiting for usb sound devices to initialize"
      python3 /home/pi/pear/wait_devices_init.py

      echo "waiting sound devices have initialized, running pear"
      python3 /home/pi/pear/pear.py /mnt/usb

      Instead of /mnt/usb you need to change this to the path of your wav files.

  8. Hi Devon,
    Another question: have you had the experience that when using more than one Sound Card a high humming sound occurs?
    if I run the sound installation with one Sound Card, this phenomenon does not occur!
    What can be the reason?
    best martin

  9. It is not due to the sound cards but to the amplifiers. I use one power supply for the Pi and one for the amplifiers. Apparently there is a conflict.

  10. Hi Devon
    Because my SD card crashed, I set up a new system. Your program (multi.py) works perfectly in Python.
    But if I start the autostart program (pear.py) in Python, the error message appears:

    usage: pear.py [-h] dir
    pear.py: error: the following arguments are required: dir

    Also crontab no longer works:
    @reboot screen -dmS pear /bin/bash /home/pi/pear/runpear.sh

    I changed runpear.sh:
    #! / bin / bash

    echo “waiting for usb sound devices to initialize”
    python3 /home/pi/pear/wait_devices_init.py

    echo “waiting sound devices have initialized, running pear”
    python3 /home/pi/pear/pear.py /home/pi/pear/test

    Can you help me?
    best martin

  11. Hi Devon,
    After a system crash the Autostart Crontab (Autostart) no longer works
    @reboot screen -dmS pear / bin / bash /home/pi/pear/runpear.sh

    runpear.sh
    #!/bin/bash

    echo “waiting for usb sound devices to initialize”
    python3 /home/pi/pear/wait_devices_init.py

    echo “waiting sound devices have initialized, running pear”
    python3 /home/pi/pear/pear.py ./home/pi/pear/test

    what can be there?
    martin

    1. Great! Glad you got it worked out. If I were to re-do the startup functionality I’d make this a systemd service. Something to consider if you run into more issues.

  12. Hi Devon,

    I am setting up your system on my model railroad for various sounds. It is working well so far. A couple of questions:
    – I see a signal out of one 3.5mm jack on each of 4 plugable modules. Should there be any signal on the other channel or is that only if the wav file has stereo content?
    – can multiple wav files be sent to one channel? ala pygame which overlays the sounds all on one channel

    Cool design,

    Allen

    1. Hiya Allen what a cool usecase!

      1. The green 3.5mm jack is for audio output, the red/pink one is for audio input, which isn’t used in this project.
      2. I haven’t tried playing multiple audio files on top of eachother out of the same device before. When audio data is loaded into memory by load_sound_file_into_memory, the format is a 2D numpy array. I’d bet if you added two arrays together (maybe multiply?) before playing them you might be able to get what you want. Let me know if you get anything working with this.

  13. Got it Devon. Thanks for the info. I am studying the sounddevice library as well as numpy and will try some tests.
    Regards,
    Allen

  14. Hi Devon,

    Merry Christmas to you.
    I would like to say that your project save a lot of my time. Really appreciate with what you did.

    So far on my side, everything goes well and I am very happy with the results. However, I got a small issue.

    I found out that it is only able to handle up to 8 audio cards. Is there a way to make it to 10 audio cards?

    Regards,
    Johnny

  15. What an amazing setup. I have to a ask though. Could this not of been done through a cheap mobile phone or even Bluetooth 5? Look forward to talking.

    1. We needed simultaneous output, so having multiple bt devices paired could work for sequential output (in theory, I haven’t tried this) but not for parallel output given my understanding of the underlaying technology.

      1. Interesting that bt doesn’t allow simultaneous tracks. I know for example on some of the latest phones you can plug more then one Bluetooth device, so two people can listen to the same track. I guess when you mean output that’s what’s the sound cards are their to do, and perhaps limitation is on soundcard and not Bluetooth. I’m gonna look into this and will report back.

  16. Hi Devon, love your work! Is there a way to do this but with stereo output? Have you even seen a setup with stereo? I’d like to be able to play multiple tracks simultaneously through different headphones with high quality stereo sound. It would need to run continuously in a gallery for a long period of time, similar to this work.

    1. You should be able to use this as is with stereo wav files, I only use one file per channel here because the actual recordings didn’t need both channels. You could send an email if you have more questions.

  17. Hi Devon,

    What if I have hardware buttons connected to Raspberry pi. I need to set up a museum installation.
    If ‘button_1’ is pressed, play ‘1.wav’ on ‘audiocard_1’; if ‘button_1’ is pressed again Pause.
    If ‘button_2’ is pressed, play ‘2.wav’ on ‘audiocard_2’; if ‘button_2’ is pressed again Pause.
    etc…
    This is a non-commercial project.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.