Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple calls to audio.play(wait=False) and microphone.record_into(wait=False) #198

Open
microbit-carlos opened this issue Apr 12, 2024 · 1 comment
Labels
enhancement New feature or request
Milestone

Comments

@microbit-carlos
Copy link
Contributor

Multiple subsequent calls to audio.play(buffer, wait=False) and microphone.record_into(buffer, wait=False) will cancel the current playback/recording and start the new one immediately.

This is very likely the user expectation, and if we wanted to block until the previous call finishes we can always wait with the audio.is_playing() and microhone.is_recording() functions.

However, when first building a programmes using wait=False, we found ourselves in situations where "blocking with a queue of 1" was useful. This is the approached we ended up following in CODAL as well for some of the async audio functionality.

For example, to illustrate what I mean I'll change the wait parameter with a new value:

audio.play(audioframe_1, wait="queue")   # starts playing immediately
display.show(Image.HAPPY)                # It's shown almost instantly
audio.play(audioframe_2, wait="queue")   # waits until the previous playback ended before playing audioframe_2
display.show(Image.SAD)                  # It's only shown when the second audioframe_2 starts playing

So, for a loop like this one:

buffers = [...]
next_buffer = 0
while True:
    microphone.record_into(buffers[next_buffer], wait="queue")
    # Do something else
    next_buffer = (next_buffer + 1) % len(buffers)

We might end up having to do something like:

buffers = [...]
next_buffer = 0
while True:
    while microphone.is_recording():
        pass
    microphone.record_into(buffer, wait=False)
    # Do something else
    next_buffer = (next_buffer + 1) % len(buffers)

And we think that some of the audio clicks we hear when trying to constantly transmit and play audio data via radio (walkie-talkie projects) might be produced between the while is_recording() and the record_into(), or in the case of playback, between while is_playing() and audio.play().

@dpgeorge
Copy link
Collaborator

Doing continuous and smooth playback/recording is a good goal to achieve. And having a queue as suggested above is a neat way to do it. There could be instead a method like audio.wait_finished_playing(), but calling that before the next audio.play() will probably lead to small gaps in the output. So a queue is a good idea.

Bit actually I think the feature is orthogonal to the existing wait keyword argument. Because you may want to queue and block. So consider instead a new keyword argument called queue:

audio.play(frame1, wait=False)             # start playing and return
audio.play(frame2, queue=True, wait=False) # wait for previous frame, then play
audio.play(frame3, queue=True)             # wait for previous frame, then play and block

The queue argument means "wait for any existing playback/recording to finish and then immediately start this next one".


But, you can already do gapless playback by passing in a generator to the play function. The audio subsystem will continue to get frames from the generator until the generator is exhausted. This is a very flexible way of generating audio frames. For example:

def generator():
    yield audioframe_1
    yield audioframe_2

audio.play(generator())

Or a continuous streaming example:

frames = collections.deque((), 4)  # holds a queue of frames to play

def generator():
    blank_frame = audio.AudioFrame()
    while True:
        if not frames:
            # no frames ready, just play a blank frame
            yield blank_frame
        else:
            # pop a frame and play it
            yield frame.popleft()

# start the playback
audio.play(generator(), wait=False)

# generate audio frames
while True:
    frames.append(get_frame_from_radio())

It might be a good idea to extend this generator functionality to microphone recording, where the generator yields the next frame to record into. When the generator is exhausted the recording stops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants