1

The goal is to play two sound files simultaneously so the sounddevice.play function is not an option. I believe that I should make an OutputStream for each file. Right now I'm stuck in trying to get one OutputStream to work. Please help.

My code so far:

import soundfile as sf
import sounddevice as sd

data, fs = sf.read('sound_file.wav', dtype='float32')

def callback(outdata, frames, time, status):
    outdata[:] = data

with sd.OutputStream(callback=callback):
               pass

Edit: switched to RawOutputStream

import soundfile as sf
import sounddevice as sd

wf = sf.SoundFile('sound_file.wav')

def callback(outdata, frames, time, status):
    data = wf.buffer_read(frames, dtype='float32')
    if len(data) <= 0:
        raise sd.CallbackAbort
    if len(outdata) > len(data):
        raise sd.CallbackAbort #wrong obviously
    outdata[:] = data

with sd.RawOutputStream(channels=wf.channels,
                        callback=callback) as stream:
    while stream.active:
        continue
  • Your variable `data` contains the whole signal (probably many seconds long), while `outdata` in the callback function contains only a very short chunk (a fraction of a second, exact length given by `frames`). So you'll need to split your `data` into appropriate pieces and use them one at a time in the callback function (which will be called multiple times per second). – Matthias Jun 23 '20 at 08:11
  • @Matthias I'm a beginner, honestly. I have no idea how to proceed, will you please give me an example? – Khaled Abdulazim Jun 23 '20 at 19:18
  • One example is the implementation of the `sounddevice.play()` function itself, you should have a look at its source code! There are further [examples](https://github.com/spatialaudio/python-sounddevice/tree/master/examples) in the repository. – Matthias Jun 24 '20 at 07:47
  • @Matthias I looked up the source code and the examples, still can't figure it out. If you could provide me with the answer, I would greatly appreciate it. – Khaled Abdulazim Jun 24 '20 at 19:21
  • There is no straightforward single answer to your vague question. There are for sure many ways to do this. The "best" answer depends on many details which you haven't mentioned. In my first comment I've told you what's the problem with your code. There is already one answer below that might work in some circumstances. If you want to get a more specific answer, you'll have to be more specific in your question. See also https://stackoverflow.com/help/how-to-ask. – Matthias Jun 25 '20 at 08:57
  • @Matthias I did as you told me in your first comment to the best of my abilities. I'm unfamiliar with arrays so I switched to `RawOutputStream` and used a `bytes` object for this. Now I'm getting this error `ValueError: right operand length must match slice length`. Check the Edit for full code. – Khaled Abdulazim Jun 26 '20 at 02:46
  • You should worry about how many channels your sound file has and how many channels you want your `stream` to have. You should also take care about what happens when the whole sound file has been read. The last `data` might not have as many `frames` as requested. And after that, do you want to keep running the `callback`? Will `stream.active` ever become `False`? – Matthias Jun 26 '20 at 08:28
  • I took care of everything except for "The last `data` might not have as many `frames` as requested" bit. I don't know what to do about it. I updated the previous Edit with the new adjustments. – Khaled Abdulazim Jun 27 '20 at 05:55
  • Now this is evolving towards the [play_long_file.py](https://github.com/spatialaudio/python-sounddevice/blob/master/examples/play_long_file.py) example, which should answer your questions. – Matthias Jun 27 '20 at 09:11
  • @Matthias Thanks so much! I posted the answer to my question, could you please review it for any inefficiencies? Thanks again. – Khaled Abdulazim Jun 27 '20 at 13:36

2 Answers2

0

If the sample rates are similar, you could just add your two data arrays together:

import numpy as np
import soundfile as sf
import sounddevice as sd

data_1, fs_1 = sf.read('sound_file_1.wav', dtype='float32')
data_2, fs_2 = sf.read('sound_file_2.wav', dtype='float32')

if len(data_1) > len(data_2):
    data = data_1 + np.pad(data_2, (0, len(data_1) - len(data_2)))
else:
    data = np.pad(data_1, (0, len(data_2) - len(data_1))) + data_2

# determine fs from a combination of fs_1 and fs_2 of your choosing, like
fs = min(fs_1, fs_2)

sd.play(data, fs)
0
import soundfile as sf
import sounddevice as sd
import threading

def _play(sound):
    event =threading.Event()

    def callback(outdata, frames, time, status):
        data = wf.buffer_read(frames, dtype='float32')
        if len(outdata) > len(data):
            outdata[:len(data)] = data
            outdata[len(data):] = b'\x00' * (len(outdata) - len(data))
            raise sd.CallbackStop
        else:
            outdata[:] = data

    with sf.SoundFile(sound) as wf:
        stream = sd.RawOutputStream(samplerate=wf.samplerate,
                                    channels=wf.channels,
                                    callback=callback,
                                    blocksize=1024,
                                    finished_callback=event.set)
        with stream:
            event.wait()

def _playsound(sound):
    new_thread = threading.Thread(target=_play, args=(sound,))
    new_thread.start()

_playsound('sounds_file.wav')
  • Ideally, you shouldn't read from a file (or write to one) in the callback function. It would be better to read from the file in a loop within the `stream` context manager and write the blocks into a queue. BTW, the second `buffer_read()` is unused! – Matthias Jun 27 '20 at 17:16