microbit-foundation / micropython-microbit-v2

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

codal_port: Add SoundEffect class. #106

Closed dpgeorge closed 2 years ago

dpgeorge commented 2 years ago

This is an initial implementation of the SoundEffect class, per #103.

As a way to facilitate debugging/experimentation, the SoundEffect constructor can take a 72-length string as the preset value, which is interpreted as a CODAL sound expression string.

The following example will play the GIGGLE sound (string data taken directly from CODAL source):

giggle_str = "010230988019008440044008881023001601003300240000000000000000000000000000,110232570087411440044008880352005901003300010000000000000000010000000000,310232729021105440288908880091006300000000240700020000000000003000000000,310232729010205440288908880091006300000000240700020000000000003000000000,310232729011405440288908880091006300000000240700020000000000003000000000"
giggle = [audio.SoundEffect(s) for s in giggle_str.split(",")]
for g in giggle:
    print(g)
    audio.play(g)

Output:

SoundEffect(freq_start=988, freq_end=440, duration=190, vol_start=255, vol_end=255, wave=WAVE_SINE, fx=FX_VIBRATO, 8)
SoundEffect(freq_start=2570, freq_end=440, duration=874, vol_start=255, vol_end=87, wave=WAVE_SAWTOOTH, fx=FX_VIBRATO, 11)
SoundEffect(freq_start=2729, freq_end=2889, duration=211, vol_start=255, vol_end=22, wave=WAVE_SQUARE, fx=None, 5)
SoundEffect(freq_start=2729, freq_end=2889, duration=102, vol_start=255, vol_end=22, wave=WAVE_SQUARE, fx=None, 5)
SoundEffect(freq_start=2729, freq_end=2889, duration=114, vol_start=255, vol_end=22, wave=WAVE_SQUARE, fx=None, 5)

Limitations:

dpgeorge commented 2 years ago

I noticed that after playing a sound expression, the micro:bit consumes about 2x the power at idle REPL.

microbit-carlos commented 2 years ago

Thanks Damien, this is great to start playing with SoundEffects!

I take it the WAVE_, FX_, and INTER_ are not get set as audio globals yet? (Edit: I've just saw the comments on the docs PR, so we can discuss that there 👍 )

Also, playing the default SoundEffect() doesn't produce any sound and seems to crash or freeze:

>>> effect = audio.SoundEffect()
>>> audio.play(effect)

But changing the interpolation to a value other than log works

>>> effect = audio.SoundEffect()
>>> effect.interpolation = 1
>>> audio.play(effect)
>>> 

So it might be related to this:

We might need to update CODAL to ensure we've got the latest fixes.

I noticed that after playing a sound expression, the micro:bit consumes about 2x the power at idle REPL.

@JohnVidler any ideas about the larger power consumption?

dpgeorge commented 2 years ago

Also, playing the default SoundEffect() doesn't produce any sound and seems to crash or freeze:

I've now updated CODAL to v0.2.40 and this default works correctly.

dpgeorge commented 2 years ago

I've now updated CODAL to v0.2.40

But I just noticed that the microphone LED is permanently on now and the micro:bit draws 17mA extra compared to CODAL v0.2.35.

And after playing a sound the micro:bit draws 54mA total at an idle REPL (it used to be 20mA at the REPL).

microbit-carlos commented 2 years ago

I think maybe using setSilenceLevel() to setting the "silence level" to zero might have an effect in power consumption. https://github.com/lancaster-university/codal-microbit-v2/blob/13a651754e668464855fc40a81dd7dcb796b58c9/source/Mixer2.cpp#L370 @JohnVidler do you have any suggestions or insight about the additional power consumption in the latest CODAL releases?

JohnVidler commented 2 years ago

Not had any cycles to investigate the power consumption yet, but my best guess would be that the audio pipline is being turned on, and left running after the initial playback, perhaps? Or that the speaker pin is being pulled strongly high/low at the end of playback rather than going back to floating when no data is present.

In either case, consider this on my radar.

dpgeorge commented 2 years ago

TODO:

microbit-carlos commented 2 years ago

I think I'd also add that we'd like __repr__ to contain positional arguments to keep the string short, and it'd be useful to have __str__ with the keyword arguments, so that users can read the values:

>>> se = SoundEffect()
>>> se
SoundEffect(500, 2500, 500, 255, 0, 3, 0, 18)
>>> print(se)
SoundEffect(freq_start=500, freq_end=2500, duration=500, vol_start=255, vol_end=0, wave=WAVE_SQUARE, fx=None, interpolation=INTER_LOG)
microbit-carlos commented 2 years ago

Quick thing I've found, setting the volume to a value n results in n-1 being set:

>>> se = audio.SoundEffect(vol_start=150)
>>> se.vol_start
149
>>> se.vol_start = 200
>>> se.vol_start
199
>>> 
dpgeorge commented 2 years ago

Do we want to rename interpolation to shape?

microbit-carlos commented 2 years ago

Yes, let's rename interpolation to shape 👍

dpgeorge commented 2 years ago

Need to also think about the default settings for the following parameters which are not exposed by the API here:

The value of steps makes a difference for the default sound.

dpgeorge commented 2 years ago

Quick thing I've found, setting the volume to a value n results in n-1 being set:

This was a rounding error converting between a range of 255 and 1023. Now fixed.

dpgeorge commented 2 years ago

Implemented support for playing a tuple/list of SoundEffect instances.