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

Should `microphone.record_into()` have an additional `duration` and/or `buffer_offset` parameters? #197

Open microbit-carlos opened 2 months ago

microbit-carlos commented 2 months ago

duration parameter

Currently, if we want to record a couple of seconds into a larger AudioFrame the way to do it would be:

my_audio = audio.AudioFrame(duration=4000)
microphone.record_into(my_audio, wait=False)
sleep(2000)
microphone.stop()

This works, but this is likely not very time-accurate (specially since uBit.sleep() can round to the 4ms of the CODAL ticker), and there currently isn't a way to check how much audio ended up recorded (https://github.com/microbit-foundation/micropython-microbit-v2/issues/196).

Providing a duration parameter, (record_into(buffer, duration, rate, wait)) that by default fills the buffer, could provide a more accurate way to record a fraction of the AudioFrame:

my_audio = audio.AudioFrame(duration=4000)
microphone.record_into(my_audio, duration=2000)

This also has the advantage of mirroring a bit more the record(duration, rate) function signature, so it's a more obvious way to achieve this than using the wait=False+stop().

But this is no enabling any new functionality or possibilities, it's a simplification and improvement of the current option, so this fall more into the "nice to have" basket.

buffer_offset parameter

As discussed in https://github.com/bbcmicrobit/micropython/pull/791/files#r1360487346 microphone.record_into() always starts recording from the beginning of the AudioFrame buffer. I agree that this is the right approach, as continuing playback from where it left off could be confusing as it's not obvious from the function name, or a behaviour shared by any of the other read_into()-like methods available. So if we wanted to be able to record multiple audio clips inside the same AudioFrame we would need to have an extra parameter to indicate a buffer offset to start recording.

However, this is not really needed to achieve this kind of functionality, as audio.play() can consume a list of AudioFrames to achieve the same.

So the change would be from something like this:

TOTAL_AUDIO = 4000
SAMPLE_SIZE = 500
audio_frames = []
for _ in range(0, TOTAL_AUDIO / SAMPLE_SIZE):
    audio_frames.append(microphone.record(duration=SAMPLE_SIZE))
audio.play(audio_frames)

To something like this:

SAMPLE_SIZE = 500
my_audio = audio.AudioFrame(duration=4000)
for i in range(0, len(my_audio), SAMPLE_SIZE):
    microphone.record_into(my_audio, duration=SAMPLE_SIZE, buffer_offset=i)
audio.play(my_audio)

So, in the end it is very similar. Some advantages are less fragmentation (specially if doing this continuously in the background), and slightly better memory consumption

In the case we'd want to add this parameter, having access to the internal marker (https://github.com/microbit-foundation/micropython-microbit-v2/issues/196) would be quite useful as well:

microphone.record_into(my_audio, duration=SAMPLE_SIZE, buffer_offset=my_audio.get_position())

Similar to the duration parameter, this might be in the "nice to have" basket, but I do wonder if using reusing the same AudioFrame constantly, without all the allocations, might be more significant in long running programmes.

Also, the name is a bit long, any suggestions welcomed.

microbit-carlos commented 2 months ago

Both of these proposal could be covered via: