Bad timing when playing audio files with PyGame - python

When I play a sound every 0.5 second with PyGame:
import pygame, time
pygame.mixer.init()
s = pygame.mixer.Sound("2.wav")
for i in range(8):
pygame.mixer.Channel(i).play(s)
time.sleep(0.5)
it doesn't respect the timing correctly at all.
It's like there are pause of 0.2 sec than 0.7 sec then 0.2 sec again, it's very irregular.
Notes:
I know that time.sleep() is not the most accurate in the world, but even with the more accurate solutions from here, the problem is still present
Tested on a RaspberryPi
The problem is still there if I play many different files s[i].play(), with i in a big range. So the problem doesn't come from the fact it tries to replay the same file

Here is the reason:
Even if we decrease the audio buffer to the minimum supported by the soundcard (1024 or 512 samples instead of pygame's default 4096), the differences will still be there, making irregulat what should be a "metronome beat".
I'll update with a working solution as soon as I find one. (I have a few ideas in this direction).

As you wrote in your own answer, the reason for the timing problems very likely is the fact that the audio callback runs decoupled from the rest of the application.
The audio backend typically has some kind of a clock which is accessible from both inside the callback function and outside of it.
I see two possible solutions:
use a library that allows you to implement the callback function yourself, calculate the starting times of your sounds inside the callback function, compare those times with the current time of the "audio clock" and write your sound to the output at the appropriate position in the output buffer.
use a library that allows you to specify the exact time (in terms of the "audio clock") when to start playing your sounds. This library would do the steps of the previous point for you.
For the first option, you could use the sounddevice module. The callback function (which you'll have to implement) will get an argument named time, which has an attribute time.outputBufferDacTime, which is a floating point value specifying the time (in seconds) when the first sample of the output buffer will be played back.
Full disclosure: I'm the author of the sounddevice module, so my recommendation is quite biased.
Quite recently, I've started working on the rtmixer module, which can be used for the second option.
Please note that this is in very early development state, so use it with caution.
With this module, you don't have to write a callback function, you can use the function rtmixer.Mixer.play_buffer() to play an audio buffer at a specified time (in seconds). For reference, you can get the current time from rtmixer.Mixer.time.

Related

Python-VLC unusually quiet

Edit - I eventually figured out the answer and have posted it below.
Using .audio_set_volume() on a media_player_new() object works fine with values 0-100, but it's much quieter than the corresponding value in normal VLC is, by a factor of around 2-3. This can be remedied by using values greater than 100, but this introduces the problem of severe delays while changing the volume (not delays in the video or audio, just ~half-second delays before the volume updates).
No issues with my volume mixing from what I can tell. The player is being embedded in PyQt5. I can't find anyone else with this issue so I imagine there's an easy workaround I'm missing.
I never got a response, but I eventually figured it out on my own: Firstly, running it through a command prompt/natively through Python causes the volume to be lower than normal (no idea why). This goes away when compiling or using your script as a default program.
Secondly, there's a VLC command-line argument called --gain you can set that seems to default to a lower value when using libvlc directly versus what VLC actually uses. When defining your instance, specify the argument like so (it takes a float value from 0-8):
instance = vlc.Instance(['--gain=8.0']) # make sure the arguments are in a list!
A gain of 8.0 is definitely higher than what VLC natively uses, but it's not ear-shatteringly loud. From what I can tell, the quality is not degraded at all and there's no delay while adjusting the volume with --gain set.
Don't forget to include any other arguments in the list if desired, such as ones from sys.argv.

How to get audio samples from python-VLC

What I need
I am working on a music player with a sample analyzer. My issue is that I am currently using pydub to get the samples from the song, but as the song gets longer, the more out of sync the two become. I have made the two literally in sync, pydub just so happens to read out of it [I even started them at the same time, the two actually progressively de-synced with audio alone and more so as the song continues playing].
Basically, is there a python-vlc equivalent to pydub.AudioSegment.get_array_of_samples()?
You can look at the python-vlc docs here: https://www.olivieraubert.net/vlc/python-ctypes/doc/
What I've tried
python-vlc has sound.get_time() which returns the last updated time in milliseconds. The issue with this command is not that it only updates every half a second or so [I've found a way around that] but that it doesn't return the accurate time. I can start a timer from when it starts playing using time.monotonic(). As time progresses, the get_time() wildly varies from the timer. I've gotten a difference of 195ms, 294ms and 217ms.
I tried using an external timer using the threading module. It appears that pydub.AudioSegment[index] is not in sync whatsoever.
Scaling from sound.get_time() in py-vlc to len(sound) in pydub. This doesn't work as expected. I cannot tell you why but it is still desynced.
Using sound.get_time() with an average offset that increases over time. It appears that pydub.AudioSegment[index] doesn't line up properly.
Using aubio. It only reads WAV files, and for a live analyzer, converting to a WAV first would take too long.
Things I found out
I've looked at how long each song is in milliseconds, len(sound) for pydub and sound.get_length() for python-vlc is different usually by 10 or so seconds.
Things that won't work
Using pydub's play() command. I don't like it because it's very limiting.
Using something else than py-vlc to play the audio, VLC has many features that just cannot be replicated.
Previous suggestions
Use How can I extract audio from video with ffmpeg?
This won't work because I am not looking to extract audio from an existing video file, I'm trying to get samples like pydub.AudioSegment.get_array_of_samples()
I'm curious
Is there a python module for Audacity? I've been looking and couldn't find it. I can also use some other command line tool that can interact with Audacity if possible [I know the built in command line utility doesn't do much at all]
You probably want to use ffmpeg directly for this.

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?
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.

how to avoid audio initial 0.5s latency on Mac?

On a Mac laptop (OS 10.9.5), when playing sounds from a python program I will get an initial start-up latency of 0.5 s before the sound plays. If I have been playing sound within the last minute or so, there is no such latency. I have seen passing references to this kind of thing online but without much insight (e.g., http://music.columbia.edu/pipermail/portaudio/2014-November/016364.html). I looked for an Apple API way to disable it (like there is for the screen saver) but did not see anything obvious. The issue might be specific to laptops, as a power-saving feature for example. It happens not only on battery power, but also when plugged in.
Question: From python on OSX, how to tell the Mac to do whatever it needs to do to avoid that 0.5 sec latency the first time it plays a sound?
Constraints: Calling some command like pmset via subprocess is acceptable, unless it needs root (sudo) priv's; i.e., only normal user-space commands are acceptable. Also not acceptable: its easy to write a little thread to play a short ~silent sound every 30sec or so, but that will add some complexity to the program and use resources -- there has to be a better way to do it.
Is the delay possibly due to attempting to play a HUGE sound file ? Is the media file previously loaded into memory prior to this request to render it ?
Try to load the audio media into a buffer then upon a play signal perform the render directly from the buffer

How to write an AI function with a timeout in python

Context
I am writing an AI for a deterministic two player game in python. I wish to write a function which takes a timeout value as one of its parameters and returns a move shortly after the timeout. The function searches (negamax or similar) until the timeout is up and then returns the best move it can find.
Specification
The function should return a valid move no matter how or when it is caused to return.
The function may return a little after the timeout, so long as this is not noticeable to the user (~100ms).
The function should return if a custom AI_INTERRUPT event is placed on the pygame event queue. (This is so that impatient users can force the computer to play).
Proposed Implementation
I think I have an idea of how to implement this, but I have found a lot of conflicting advice online (and mostly for problems not quite the same as this one). I am also concerned that I am over-engineering things. I am therefore asking whether or not this implementation suggestion is a sensible one, or whether you recommend something else.
I am considering writing my AI algorithm as a generator that yields successively better moves. Time delays between yields could be long, but the first yield would be almost immediate.
I would then call this generator in a subprocess and have it feed yield values into a pipe.
The main process would then run in a loop of:
Poll the pipe. If a new value has been yielded it is stored.
Check the time. If the timeout has been exceeded, return the latest value.
Check for an AI_INTERRUPT event, returning the latest value if one is found.
Handle other pygame events as necessary
I am using Python 3 with pygame.
You could use a timed-out thread that wrappes your negamax method.
The method will update a shared data structure with the optimal solution so far. The shared data structure can simply be a list that you pass to your negamax method. When the timeout occurs, the caller will read the solution from the list.
If you are working on UNIX, you can use the signal lib to implement a timeout function : Timeout function if it takes too long to finish
Since you're already using pygame, use either:
def foo(duration):
# time as MS
start = pygame.time.get_ticks()
while True:
now = pygame.time.get_ticks()
if now - start >= duration:
return
# do stuff
Or one of these two functions:
pygame.time.wait or pygame.time.delay
You could make a slightly more complicated version, that still lets your main loop continue. But if your display doesn't update in the short time, it might be overkill.

Categories

Resources