Search code examples
pythonnumpylow-latencypython-sounddevice

Too high latency while trying to manipulate sound arrays using sounddevice in python


Few days ago, I have installed a sounddevice library in Python 2.7.5. I'm trying to make a sound array and add some effects to it immediately after I push a key on my MIDI controller. But I get a huge delay of 0.1 to 0.2 second which makes my code useless:

import numpy as np
import sounddevice as sd
import time
import math

#we're making a sound array with a 5 seconds length noisy sound and playing it:
duration=5
framerate = 44100
array=0.02*np.random.uniform(-1, 1, framerate*duration)
sd.play(array, framerate)

t=time.time()
while(True):
    signal=raw_input("push ENTER to hear a beep")
    start_iter=int(framerate*(time.time()-t))
    end_iter=min(start_iter+framerate/4, len(array))

    #we're trying to change our data array and play a beep signal of 0.25 second after each ENTER press instantly
    for i in range(start_iter, end_iter):
        array[i]=0.05*math.sin(440*2*math.pi*i/44100.)
    if end_iter==len(array): break #safe exit of a process after 5 seconds has passed

To keep it simple, my sound array is just a noisy sound and my effect consists of a 440Hz beep. I used raw_input() here (type "input()" in Python 3.x) instead of MIDI inputs which could be possible using Pygame library. My code works but each time we press ENTER we will hear a short delay before beep signal. Is it possible to eliminate it? If not, any other libraries allowing to play a sound stream with no delays live?


Solution

  • You can specify the desired latency with sounddevice.default.latency. Note however, that this is a suggested latency, the actual latency may be different, depending on the hardware and probably also on the host API. You can get an estimate of the actual latency with sounddevice.Stream.latency.

    By default, the sounddevice module uses PortAudio's high latency setting in the hope to provide more robust behavior. You can switch it to PortAudio's low setting, or you can try whatever numeric value (in seconds) you want.

    import sounddevice as sd
    sd.default.latency = 'low'
    

    Alternatively, you can of course also use the latency argument of play() etc.

    If you want to have more control over the time, you might want to write your own custom callback function. There you can use the time argument, and outside of the callback function you can use sounddevice.Stream.time.

    You can also try to start a stream without using the callback argument and then use sounddevice.Stream.write() on it. I don't know what that will do to the latency, but it might be worth a try.

    Regarding other libraries, since you seem to be using PyGame already, you might also use it for the audio output. It might or might not have a different latency.

    BTW, I don't know if your code is thread-safe since you are manipulating the array while the callback gives the memory addresses to PortAudio. It's probably not a good idea to implement it like this.