microbit-foundation / micropython-microbit-v2

Temporary home for MicroPython for micro:bit v2 as we stablise it before pushing upstream
MIT License
41 stars 22 forks source link

Playing a 50 ms AudioFrame takes less time than expected #182

Open microbit-carlos opened 3 months ago

microbit-carlos commented 3 months ago

Using the latest version (at the time of writing) of the recording & playback branch: https://github.com/microbit-foundation/micropython-microbit-v2/commit/0b06914c71c18533da90df85230ac198578669bf Hex: https://github.com/microbit-foundation/micropython-microbit-v2/actions/runs/8416237764?pr=163

Playing a 50 ms AudioFrame takes less time than expected:

>>> audioframe_50_ms = audio.AudioFrame(duration=50)
>>> audioframe_50_ms[-1] = 255
>>> ms_start = microbit.running_time(); audio.play(audioframe_50_ms); ms_end = microbit.running_time();
>>> ms_end - ms_start
42
>>> # Should be more than 50ms
>>> len(audioframe_50_ms) * 1000 / 7812
53.25141

Using time.ticks_ms() instead:

>>> from time import ticks_ms
>>> ms_start = ticks_ms(); audio.play(audioframe_50_ms); ms_end = ticks_ms()
>>> ms_end - ms_start
44
dpgeorge commented 2 months ago

The exact time taken by audio.play() is somewhat set by CODAL and the way it queues data for playback, via timing of the pullRequest()/pull() methods.

I suspect the following:

If you play increasingly large audio frames, then the time for audio.play() also increases in proportion to the length of the audio frame.

microbit-carlos commented 2 months ago

Based on this:

The CODAL isPlaying() method should indicate real sound playback, but when calling audio.is_playing() right after it does indicate no more sound should be playing:

>>> t = running_time(); audio.play(audioframe_50_ms); audio.is_playing(); running_time() - t
False
44

Do you think this is an issue in the CODAL side?

microbit-carlos commented 2 months ago

I'll try replicate this on the CODAL side first as well.

dpgeorge commented 2 months ago

Note that MicroPython doesn't use uBit.audio.isPlaying(). Instead we track whether there's any more data to go out to CODAL. If not, then the audio is "finished".

microbit-carlos commented 2 months ago

Ah, thanks Damien. The reason we looked into implementing uBit.audio.isPlaying() is that we found situations were the audio playback ends up triggering the microphone, for example in programmes like this one:

from microbit import *

while True:
    audio.play(Sound.HELLO)
    # Wait until we hear something loud to do something else
    while microphone.sound_level() < 60:
        pass
    # At this point audio.play() has triggered the microphone sound level
    sleep(100)