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

[DO NOT MERGE] Update for forthcoming beta MicroPython #97

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
791689a
Add was_touched
microbit-matt-hillsdon Mar 19, 2024
506c92f
Add `get_touches` method docs
microbit-grace Mar 20, 2024
9221520
WIP recording playback APIs
microbit-matt-hillsdon Mar 20, 2024
12a25f1
Change :returns: -> :return:
microbit-grace Mar 20, 2024
d9b3ffb
Add examples
microbit-grace Mar 20, 2024
44af467
Tweak
microbit-grace Mar 20, 2024
5ff1eb7
Update to deep_sleep doc
microbit-grace Mar 20, 2024
6190ec7
Update run_every microbit doc
microbit-grace Mar 20, 2024
9704523
Change the en/typeshed instead of welsh typeshed
microbit-grace Mar 20, 2024
e0a4593
Revert "Update to deep_sleep doc"
microbit-grace Mar 20, 2024
b7f91ec
Revert "Change the en/typeshed instead of welsh typeshed"
microbit-grace Mar 20, 2024
79b44b6
ticks_cpu doc
microbit-grace Apr 3, 2024
b90c486
Add WIP audio.sound_level stub
microbit-grace Apr 3, 2024
609444f
Add stubs for AudioFrame operations
microbit-matt-hillsdon Apr 3, 2024
7cb5277
Duration doesn't have a default in practice
microbit-matt-hillsdon Apr 3, 2024
f5ca4aa
Update set_threshold doc to copy micropython doc
microbit-grace Apr 12, 2024
936b172
Suggested ammendments for set_threshold doc
microbit-grace Apr 12, 2024
7057837
Update audio.sound_level doc
microbit-grace Apr 15, 2024
f4c9898
Add microphone sound_level_db doc
microbit-grace May 3, 2024
263b689
Initial stubs updates
microbit-matt-hillsdon Aug 20, 2024
3826bce
Corrections
microbit-matt-hillsdon Aug 21, 2024
6dcc5ab
Corrections
microbit-matt-hillsdon Aug 21, 2024
fc85377
Microphone stubs
microbit-matt-hillsdon Aug 21, 2024
69aaab7
Update play stubs
microbit-matt-hillsdon Aug 21, 2024
91eb337
Docs for audio.play
microbit-matt-hillsdon Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions lang/en/typeshed/stdlib/microbit/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def run_every(

As a Decorator - placed on top of the function to schedule. For example::

@run_every(h=1, min=20, s=30, ms=50)
@run_every(days=1, h=1, min=20, s=30, ms=50)
def my_function():
# Do something here

Expand Down Expand Up @@ -278,24 +278,28 @@ class MicroBitTouchPin(MicroBitAnalogDigitalPin):

Example: ``pin0.is_touched()``

The default touch mode for the pins on the edge connector is ``resistive``.
The default for the logo pin **V2** is ``capacitive``.
:return: ``True`` if the pin is being touched with a finger, otherwise return ``False``.
"""
...

**Resistive touch**
This test is done by measuring how much resistance there is between the
pin and ground. A low resistance gives a reading of ``True``. To get
a reliable reading using a finger you may need to touch the ground pin
with another part of your body, for example your other hand.
def was_touched(self) -> bool:
"""Check if the pin was touched since the last time this method was called.

**Capacitive touch**
This test is done by interacting with the electric field of a capacitor
using a finger as a conductor. `Capacitive touch
<https://www.allaboutcircuits.com/technical-articles/introduction-to-capacitive-touch-sensing>`_
does not require you to make a ground connection as part of a circuit.
Example: ``pin0.was_touched()``

:return: ``True`` if the pin is being touched with a finger, otherwise return ``False``.
:return: ``True`` or ``False`` to indicate if the pin was touched since the device started or since the last time this method was called.
"""
...

def get_touches(self) -> int:
"""Get the number of times the pin was touched since the last time this method was called.

Example: ``pin0.get_touches()``

:return: The number of times the pin was touched since the device started or since the last time this method was called.
"""
...

def set_touch_mode(self, value: int) -> None:
"""Set the touch mode for the pin.

Expand All @@ -304,6 +308,18 @@ class MicroBitTouchPin(MicroBitAnalogDigitalPin):
The default touch mode for the pins on the edge connector is
``resistive``. The default for the logo pin **V2** is ``capacitive``.

**Resistive touch**
This test is done by measuring how much resistance there is between the
pin and ground. A low resistance gives a reading of ``True``. To get
a reliable reading using a finger you may need to touch the ground pin
with another part of your body, for example your other hand.

**Capacitive touch**
This test is done by interacting with the electric field of a capacitor
using a finger as a conductor. `Capacitive touch
<https://www.allaboutcircuits.com/technical-articles/introduction-to-capacitive-touch-sensing>`_
does not require you to make a ground connection as part of a circuit.

:param value: ``CAPACITIVE`` or ``RESISTIVE`` from the relevant pin.
"""
...
Expand Down
68 changes: 63 additions & 5 deletions lang/en/typeshed/stdlib/microbit/audio.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ from ..microbit import MicroBitDigitalPin, Sound, pin0
from typing import ClassVar, Iterable, Union

def play(
source: Union[Iterable[AudioFrame], Sound, SoundEffect],
source: Union[AudioFrame, Iterable[AudioFrame], Sound, SoundEffect],
wait: bool = True,
pin: MicroBitDigitalPin = pin0,
return_pin: Union[MicroBitDigitalPin, None] = None,
) -> None:
"""Play a built-in sound, sound effect or custom audio frames.
"""Play a built-in sound, sound effect or audio samples using ``AudioFrame``.

Example: ``audio.play(Sound.GIGGLE)``

:param source: A built-in ``Sound`` such as ``Sound.GIGGLE``, a ``SoundEffect`` or sample data as an iterable of ``AudioFrame`` objects.
:param source: A built-in ``Sound`` such as ``Sound.GIGGLE``, a ``SoundEffect`` or sample data as an ``AudioFrame`` object or an iterable of ``AudioFrame`` objects.
:param wait: If ``wait`` is ``True``, this function will block until the sound is complete.
:param pin: An optional argument to specify the output pin can be used to override the default of ``pin0``. If we do not want any sound to play we can use ``pin=None``.
:param return_pin: Specifies a differential edge connector pin to connect to an external speaker instead of ground. This is ignored for the **V2** revision.
Expand All @@ -28,6 +28,14 @@ def is_playing() -> bool:
:return: ``True`` if audio is playing, otherwise ``False``."""
...

def sound_level() -> int:
"""Returns the average intensity of the sound played.

Example: ``audio.sound_level()``

:return: A number between 0 and 254, being the average intensity of the sound played from the most recent chunk of data."""
...

microbit-grace marked this conversation as resolved.
Show resolved Hide resolved
def stop() -> None:
"""Stop all audio playback.

Expand Down Expand Up @@ -136,10 +144,18 @@ class SoundEffect:
"""

class AudioFrame:
"""An ``AudioFrame`` object is a list of 32 samples each of which is a unsigned byte
"""An ``AudioFrame`` object is a list of samples, each of which is an unsigned byte
(whole number between 0 and 255).

It takes just over 4 ms to play a single frame.
The number of samples in an AudioFrame will depend on the
``rate`` (number of samples per second) and ``duration`` parameters.
The total number of samples will always be a round up multiple of 32.

On micro:bit V1 the constructor does not take any arguments,
and an AudioFrame instance is always 32 bytes.

For example, playing 32 samples at 7812 Hz takes just over 4 milliseconds
(1/7812.5 * 32 = 0.004096 = 4096 microseconds).

Example::

Expand All @@ -148,6 +164,42 @@ class AudioFrame:
frame[i] = 252 - i * 8
"""

def __init__(
self,
duration: int = -1,
rate: int = 7812
):
"""Create a new ``AudioFrame``.

Example: ``my_recording = AudioFrame(duration=5000)``

:param duration: Indicates how many milliseconds of audio this instance can store (V2 only).
:param rate: The sampling rate at which data will be stored via the microphone, or played via the ``audio.play()`` function (V2 only).
"""

def set_rate(self, sample_rate: int) -> None:
"""Configure the sampling rate associated with the data in the
``AudioFrame`` instance (V2 only).

Example: ``my_frame.set_rate(7812)``

For recording from the microphone, increasing the sampling rate
increases the sound quality, but reduces the length of audio it
can store.

During playback, increasing the sampling rate speeds up the sound
and decreasing it slows it down.
"""

def get_rate(self) -> int:
"""Get the sampling rate associated with the data in the
``AudioFrame`` instance (V2 only).

Example: ``current_rate = my_frame.get_rate()``

:return: The configured sampling rate for this ``AudioFrame`` instance.
"""

def copyfrom(self, other: AudioFrame) -> None:
"""Overwrite the data in this ``AudioFrame`` with the data from another ``AudioFrame`` instance.

Expand All @@ -158,3 +210,9 @@ class AudioFrame:
def __len__(self) -> int: ...
def __setitem__(self, key: int, value: int) -> None: ...
def __getitem__(self, key: int) -> int: ...
def __add__(self, v: AudioFrame) -> AudioFrame: ...
def __iadd__(self, v: AudioFrame) -> AudioFrame: ...
def __sub__(self, v: AudioFrame) -> AudioFrame: ...
def __isub__(self, v: AudioFrame) -> AudioFrame: ...
def __mul__(self, v: float) -> AudioFrame: ...
def __imul__(self, v: float) -> AudioFrame: ...
85 changes: 83 additions & 2 deletions lang/en/typeshed/stdlib/microbit/microphone.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from typing import Optional, Tuple
from ..microbit import SoundEvent
from ..microbit.audio import AudioFrame

def current_event() -> Optional[SoundEvent]:
"""Get the last recorded sound event
Expand Down Expand Up @@ -53,10 +54,19 @@ def set_threshold(event: SoundEvent, value: int) -> None:

Example: ``microphone.set_threshold(SoundEvent.LOUD, 250)``

A high threshold means the event will only trigger if the sound is very loud (>= 250 in the example).
The ``SoundEvent.LOUD`` event is triggered when the sound level crosses the
threshold from "quiet" to "loud", and the ``SoundEvent.QUIET`` event is
triggered when the sound level crosses from "loud" to "quiet".

If the ``SoundEvent.LOUD`` threshold is set lower than the
``SoundEvent.QUIET`` threshold, then the ``SoundEvent.QUIET`` threshold
will decrease by one unit below the ``SoundEvent.LOUD`` threshold. If the
``SoundEvent.QUIET`` threshold is set higher than the ``SoundEvent.LOUD``
threshold, then the ``SoundEvent.LOUD`` threshold will increase by one unit
above the ``SoundEvent.QUIET`` threshold.

:param event: A sound event, such as ``SoundEvent.LOUD`` or ``SoundEvent.QUIET``.
:param value: The threshold level in the range 0-255.
:param value: The threshold level in the range 0-255. Values outside this range will be clamped.
Comment on lines +57 to +69
Copy link
Contributor

@microbit-grace microbit-grace Apr 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an update based on recently merged micropython doc PR.

f5ca4aa - I copied set_threshold doc from the PR
936b172 - my update of it to make the tenses match the rest of the docs and to make it clearer (maybe)?

Looks quite verbose, so I am tempted to cut it down, but am not sure how
Screenshot 2024-04-12 at 17 17 21

"""
...

Expand All @@ -68,3 +78,74 @@ def sound_level() -> int:
:return: A representation of the sound pressure level in the range 0 to 255.
"""
...

def record(duration: int, rate: int = 7812) -> AudioFrame:
"""Record sound into an ``AudioFrame`` for the amount of time indicated by
``duration`` at the sampling rate indicated by ``rate``.

Example: ``my_frame = microphone.record(3000)``

The amount of memory consumed is directly related to the length of the
recording and the sampling rate. The higher these values, the more memory
it will use.

A lower sampling rate will reduce both memory consumption and sound
quality.

If there isn't enough memory available a ``MemoryError`` will be raised.

:param duration: How long to record in milliseconds.
:param rate: Number of samples to capture per second.
:return: An ``AudioFrame`` with the sound samples.
"""
...

def record_into(buffer: AudioFrame, rate: int = 7812, wait: bool = True) -> None:
"""Record sound into an existing ``AudioFrame`` until it is filled,
or the ``stop_recording()`` function is called.

Example: ``microphone.record_into(my_frame)``

:param buffer: An ``AudioFrame`` to record sound.
:param rate: Number of samples to capture per second.
:param wait: When set to ``True`` it blocks until the recording is
done, if it is set to ``False`` it will run in the background.
"""
...

def is_recording() -> bool:
"""Checks whether the microphone is currently recording.

Example: ``is_recording = microphone.is_recording()``

:return: ``True`` if the microphone is currently recording sound, otherwise returns ``False``.
"""
...

def stop_recording() -> None:
"""Stops a recording running in the background.

Example: ``microphone.stop_recording()``
"""
...

SENSITIVITY_LOW: float;
"""Low microphone sensitivity."""

SENSITIVITY_MEDIUM: float;
"""Medium microphone sensitivity."""

SENSITIVITY_HIGH: float;
"""High microphone sensitivity."""


def set_sensitivity(gain: float) -> None:
"""Configure the microphone sensitivity.

Example: ``microphone.set_sensitivity(microphone.SENSITIVITY_HIGH)``

The default sensitivity is ``microphone.SENSITIVITY_MEDIUM``.

:param gain: The microphone gain. Use ``microphone.SENSITIVITY_LOW``, ``microphone.SENSITIVITY_MEDIUM``, ``microphone.SENSITIVITY_HIGH``, or a value between these levels.
"""
...
10 changes: 10 additions & 0 deletions lang/en/typeshed/stdlib/time.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def ticks_us() -> int:
"""
...

def ticks_cpu() -> int:
"""
Similar to ticks_ms and ticks_us, but with higher resolution in CPU cycles.

Example: ``time.ticks_cpu()``

:return: The counter value in CPU cycles.
"""
...

Comment on lines +59 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @microbit-carlos, this is the best guess we made for ticks_cpu because we couldn't find micropython docs on it. Is this right and are there any more specific information we can add about this? Thanks!

Copy link
Contributor

@microbit-carlos microbit-carlos Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Grace,

I wasn't aware that the ticks_cpu function was enabled in the V2 MicroPython port, do you know when it was added? Has it been added only in the latest release?

There is some upstream documentation here: https://docs.micropython.org/en/latest/library/time.html#time.ticks_cpu

But it would be good to understand the exact implementation for micro:bit and to have that in the official micro:bit docs as well. I'll have a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgraded here: microbit-foundation/micropython-microbit-v2@fdaf840

That diff contains the implementation that uses CYCCNT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the moment we always return zero in the sim for ticks_cpu but we could return the same as ticks_us or a multiple. ticks_us is already a 1000 * multiple of ticks_ms which is our real resolution in the sim.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it was added with the update to MicroPython 1.22.0 in January: microbit-foundation/micropython-microbit-v2#166
It uses low level Arm register to count cycles, so this should be correct (of course, there will be a bit of overhead by calling and returning from a Python function but that's the same for every other time.ticks_xx function):
https://github.com/microbit-foundation/micropython-microbit-v2/blob/8d9067d91bcfe316cd3aea5af1a873a37ca23d13/src/codal_port/mphalport.h#L48-L55

The thing to keep in mind is that this is register is 32 bits and we run at 64 MHz, so the register overflows every minute ish ( 2^32 / 64_000_000 = 67 seconds)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @microbit-carlos !

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction, it's likely 30-bits due to small int size in MicroPython.

def ticks_add(ticks: int, delta: int) -> int:
"""
Offset ticks value by a given number, which can be either positive or
Expand Down
Loading