0

I want to write a program that allows me to play sample sounds with the computer keyboard with almost no latency.

My program:

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

sd.default.latency = 'low'

samplesarray = []

def load_samples(num):
    filename='sample'+str(num)+'.wav'
    data, fs = sf.read(filename, dtype='float32')
    sd.default.samplerate = fs
    samplesarray.append(data)
    return

numberofsamples=3

for i in range(numberofsamples):    
    load_samples(i+1)

def play_session():
    while 0==0:
        key = int(msvcrt.getch())
        sd.play(samplesarray[key-1])
    return

play_session()

The program folder contains a number of 'one shot' short samples named sample1.wav, sample2.wav, etc, for instance kick-drums or snares. In this example for simplicity only three are loaded. I can launch my current program in the terminal, and play the samples 'mapped' on my keys, which is what I want. The only problem is the latency: while not huge, it's definitely noticeable.

For playing samples live, ideally latency should be practically not perceivable (order of the tens of milliseconds).

How could I achieve this?

skyfish
  • 1
  • 1

1 Answers1

0

Among several other things, the lowest possible latency depends on the host API you are using. Since you are importing the msvcrt module, I assume you are using Windows, right?

There you can typically choose between several host APIs. The lowest latencies can normally be reached with WASAPI, WDM/KS or ASIO.

If you use WASAPI, you can try exclusive mode, which will probably allow lower latencies, but I don't know for sure.

Setting latency to 'low' (as you did) should do the trick, but you can also experiment with using different values for blocksize. But note that too small block sizes will lead to buffer underflows, which may result in audible clicks.

Finally, if you really want to squeeze the last bit of latency out of your setup, you should probably get rid of sd.play() (which opens a new sd.OutputStream at each invocation) and instead implement your playback logic in your own callback function.

Matthias
  • 4,524
  • 2
  • 31
  • 50
  • Thanks. I added `wasapi_exclusive = sd.WasapiSettings(exclusive=True)` and `sd.default.extra_settings = wasapi_exclusive` at the beginning of my program. If I don't call the function `play_session()` it runs just fine, but if I try to call it, I get "sounddevice.PortAudioError: Error opening OutputStream: Incompatible host API specific stream info". I tried to google a solution to no avail. Suggestions? – skyfish May 24 '17 at 17:08
  • @skyfish Are you actually using a WASAPI device? If yes, your error sounds similar to https://github.com/spatialaudio/python-sounddevice/issues/35. What about the latency if you *don't* use `extra_settings`? – Matthias May 25 '17 at 14:58