Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -775,9 +775,9 @@ msgstr ""
msgid "Can't set CCCD on local Characteristic"
msgstr ""

#: shared-bindings/storage/__init__.c shared-bindings/usb_cdc/__init__.c
#: shared-bindings/usb_hid/__init__.c shared-bindings/usb_midi/__init__.c
#: shared-bindings/usb_video/__init__.c
#: shared-bindings/storage/__init__.c shared-bindings/usb_audio/__init__.c
#: shared-bindings/usb_cdc/__init__.c shared-bindings/usb_hid/__init__.c
#: shared-bindings/usb_midi/__init__.c shared-bindings/usb_video/__init__.c
msgid "Cannot change USB devices now"
msgstr ""

Expand Down Expand Up @@ -1686,6 +1686,7 @@ msgstr ""

#: shared-bindings/audiobusio/I2SOut.c shared-bindings/audioio/AudioOut.c
#: shared-bindings/audiopwmio/PWMAudioOut.c shared-bindings/mcp4822/MCP4822.c
#: shared-bindings/usb_audio/USBMicrophone.c
msgid "Not playing"
msgstr ""

Expand Down Expand Up @@ -2148,7 +2149,7 @@ msgstr ""
msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30"
msgstr ""

#: shared-module/audiocore/__init__.c
#: shared-module/audiocore/__init__.c shared-module/usb_audio/USBMicrophone.c
msgid "The sample's %q does not match"
msgstr ""

Expand Down Expand Up @@ -2270,6 +2271,10 @@ msgstr ""
msgid "UID:"
msgstr ""

#: shared-bindings/usb_audio/USBMicrophone.c
msgid "USB audio not enabled in boot.py"
msgstr ""

#: shared-module/usb_hid/Device.c
msgid "USB busy"
msgstr ""
Expand Down
1 change: 1 addition & 0 deletions ports/raspberrypi/mpconfigport.mk
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ CIRCUITPY_ROTARYIO_SOFTENCODER = 1
CIRCUITPY_SYNTHIO_MAX_CHANNELS = 24
CIRCUITPY_USB_HOST ?= 1
CIRCUITPY_USB_VIDEO ?= 1
CIRCUITPY_USB_AUDIO ?= 1

# Things that need to be implemented.
CIRCUITPY_FREQUENCYIO = 0
Expand Down
3 changes: 3 additions & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ endif
ifeq ($(CIRCUITPY_USB_VIDEO),1)
SRC_PATTERNS += usb_video/%
endif
ifeq ($(CIRCUITPY_USB_AUDIO),1)
SRC_PATTERNS += usb_audio/%
endif
ifeq ($(CIRCUITPY_USB_HOST),1)
SRC_PATTERNS += usb_host/%
endif
Expand Down
3 changes: 3 additions & 0 deletions py/circuitpy_mpconfig.mk
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ CFLAGS += -DCIRCUITPY_USB_HID_ENABLED_DEFAULT=$(CIRCUITPY_USB_HID_ENABLED_DEFAUL
CIRCUITPY_USB_VIDEO ?= 0
CFLAGS += -DCIRCUITPY_USB_VIDEO=$(CIRCUITPY_USB_VIDEO)

CIRCUITPY_USB_AUDIO ?= 0
CFLAGS += -DCIRCUITPY_USB_AUDIO=$(CIRCUITPY_USB_AUDIO)

CIRCUITPY_USB_HOST ?= 0
CFLAGS += -DCIRCUITPY_USB_HOST=$(CIRCUITPY_USB_HOST)

Expand Down
197 changes: 197 additions & 0 deletions shared-bindings/usb_audio/USBMicrophone.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries LLC
//
// SPDX-License-Identifier: MIT

#include <stdint.h>

#include "shared/runtime/context_manager_helpers.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/usb_audio/USBMicrophone.h"
#include "shared-bindings/util.h"
#include "shared-module/usb_audio/__init__.h"

//| class USBMicrophone:
//| """Streams an audio sample to the host computer as a USB Audio Class microphone.
//|
//| A ``USBMicrophone`` is a *consumer* of an audio sample, exactly like
//| `audioio.AudioOut`, `audiobusio.I2SOut` and `audiopwmio.PWMAudioOut`. The
//| samples it pulls are streamed to the host PC over USB rather than to a pin,
//| so the board appears as a microphone.
//|
//| ``usb_audio.enable()`` must have been called in ``boot.py`` before this object
//| can be constructed."""
//|
//| def __init__(self) -> None:
//| """Create a USBMicrophone using the audio format configured in ``boot.py``."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
static const mp_arg_t allowed_args[] = {};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

// The audio format is chosen by usb_audio.enable() in boot.py, which also claims
// the USB interface. Without it there is no microphone for the host to read.
if (!usb_audio_enabled()) {
mp_raise_RuntimeError(MP_ERROR_TEXT("USB audio not enabled in boot.py"));
}

usb_audio_usbmicrophone_obj_t *self = mp_obj_malloc_with_finaliser(usb_audio_usbmicrophone_obj_t, &usb_audio_USBMicrophone_type);
common_hal_usb_audio_usbmicrophone_construct(self);

return MP_OBJ_FROM_PTR(self);
}

//| def deinit(self) -> None:
//| """Deinitialises the USBMicrophone and releases any resources for reuse."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_deinit(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_usb_audio_usbmicrophone_deinit(self);
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_deinit_obj, usb_audio_usbmicrophone_deinit);

static void check_for_deinit(usb_audio_usbmicrophone_obj_t *self) {
if (common_hal_usb_audio_usbmicrophone_deinited(self)) {
raise_deinited_error();
}
}

//| def __enter__(self) -> USBMicrophone:
//| """No-op used by Context Managers."""
//| ...
//|
// Provided by context manager helper.

//| def __exit__(self) -> None:
//| """Automatically deinitializes the hardware when exiting a context. See
//| :ref:`lifetime-and-contextmanagers` for more info."""
//| ...
//|
// Provided by context manager helper.

//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None:
//| """Streams the sample to the host once when loop=False and continuously when
//| loop=True. Does not block. Use `playing` to block.
//|
//| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer`,
//| `audiomp3.MP3Decoder` or `synthio.Synthesizer`."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_sample, ARG_loop };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
};
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
check_for_deinit(self);
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

common_hal_usb_audio_usbmicrophone_play(self, args[ARG_sample].u_obj, args[ARG_loop].u_bool);

return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(usb_audio_usbmicrophone_play_obj, 1, usb_audio_usbmicrophone_obj_play);

//| def stop(self) -> None:
//| """Stops streaming and resets to the start of the sample."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_obj_stop(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
common_hal_usb_audio_usbmicrophone_stop(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_stop_obj, usb_audio_usbmicrophone_obj_stop);

//| playing: bool
//| """True when an audio sample is being streamed even if `paused`. (read-only)"""
//|
static mp_obj_t usb_audio_usbmicrophone_obj_get_playing(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
return mp_obj_new_bool(common_hal_usb_audio_usbmicrophone_get_playing(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_get_playing_obj, usb_audio_usbmicrophone_obj_get_playing);

MP_PROPERTY_GETTER(usb_audio_usbmicrophone_playing_obj,
(mp_obj_t)&usb_audio_usbmicrophone_get_playing_obj);

//| def pause(self) -> None:
//| """Stops streaming temporarily while remembering the position. Use `resume` to resume."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_obj_pause(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);

if (!common_hal_usb_audio_usbmicrophone_get_playing(self)) {
mp_raise_RuntimeError(MP_ERROR_TEXT("Not playing"));
}
common_hal_usb_audio_usbmicrophone_pause(self);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_pause_obj, usb_audio_usbmicrophone_obj_pause);

//| def resume(self) -> None:
//| """Resumes streaming after :py:func:`pause`."""
//| ...
//|
static mp_obj_t usb_audio_usbmicrophone_obj_resume(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);

if (common_hal_usb_audio_usbmicrophone_get_paused(self)) {
common_hal_usb_audio_usbmicrophone_resume(self);
}

return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_resume_obj, usb_audio_usbmicrophone_obj_resume);

//| paused: bool
//| """True when streaming is paused. (read-only)"""
//|
//|
static mp_obj_t usb_audio_usbmicrophone_obj_get_paused(mp_obj_t self_in) {
usb_audio_usbmicrophone_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
return mp_obj_new_bool(common_hal_usb_audio_usbmicrophone_get_paused(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_audio_usbmicrophone_get_paused_obj, usb_audio_usbmicrophone_obj_get_paused);

MP_PROPERTY_GETTER(usb_audio_usbmicrophone_paused_obj,
(mp_obj_t)&usb_audio_usbmicrophone_get_paused_obj);

static const mp_rom_map_elem_t usb_audio_usbmicrophone_locals_dict_table[] = {
// Methods
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&usb_audio_usbmicrophone_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&usb_audio_usbmicrophone_deinit_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&default___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&usb_audio_usbmicrophone_play_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&usb_audio_usbmicrophone_stop_obj) },
{ MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&usb_audio_usbmicrophone_pause_obj) },
{ MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&usb_audio_usbmicrophone_resume_obj) },

// Properties
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&usb_audio_usbmicrophone_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_paused), MP_ROM_PTR(&usb_audio_usbmicrophone_paused_obj) },
};
static MP_DEFINE_CONST_DICT(usb_audio_usbmicrophone_locals_dict, usb_audio_usbmicrophone_locals_dict_table);

MP_DEFINE_CONST_OBJ_TYPE(
usb_audio_USBMicrophone_type,
MP_QSTR_USBMicrophone,
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
make_new, usb_audio_usbmicrophone_make_new,
locals_dict, &usb_audio_usbmicrophone_locals_dict
);
21 changes: 21 additions & 0 deletions shared-bindings/usb_audio/USBMicrophone.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries LLC
//
// SPDX-License-Identifier: MIT

#pragma once

#include "shared-module/usb_audio/USBMicrophone.h"

extern const mp_obj_type_t usb_audio_USBMicrophone_type;

void common_hal_usb_audio_usbmicrophone_construct(usb_audio_usbmicrophone_obj_t *self);
void common_hal_usb_audio_usbmicrophone_deinit(usb_audio_usbmicrophone_obj_t *self);
bool common_hal_usb_audio_usbmicrophone_deinited(usb_audio_usbmicrophone_obj_t *self);
void common_hal_usb_audio_usbmicrophone_play(usb_audio_usbmicrophone_obj_t *self, mp_obj_t sample, bool loop);
void common_hal_usb_audio_usbmicrophone_stop(usb_audio_usbmicrophone_obj_t *self);
bool common_hal_usb_audio_usbmicrophone_get_playing(usb_audio_usbmicrophone_obj_t *self);
void common_hal_usb_audio_usbmicrophone_pause(usb_audio_usbmicrophone_obj_t *self);
void common_hal_usb_audio_usbmicrophone_resume(usb_audio_usbmicrophone_obj_t *self);
bool common_hal_usb_audio_usbmicrophone_get_paused(usb_audio_usbmicrophone_obj_t *self);
98 changes: 98 additions & 0 deletions shared-bindings/usb_audio/__init__.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries LLC
//
// SPDX-License-Identifier: MIT

#include "py/obj.h"
#include "py/runtime.h"

#include "shared-bindings/usb_audio/__init__.h"
#include "shared-bindings/usb_audio/USBMicrophone.h"
#include "shared-module/usb_audio/__init__.h"
#include "shared-module/usb_audio/usb_audio_descriptors.h"

//| """Stream audio to a host computer via USB
//|
//| This makes your CircuitPython device identify to the host computer as a USB
//| Audio Class (UAC2) microphone: the board is the audio *source* and streams
//| samples to the host over a USB isochronous IN endpoint.
//|
//| This mode requires 1 IN endpoint and 2 interfaces. Generally, microcontrollers
//| have a limit on the number of endpoints. If you exceed the number of endpoints,
//| CircuitPython will automatically enter Safe Mode. Even in this case, you may be
//| able to enable USB audio by also disabling other USB functions, such as
//| `usb_hid` or `usb_midi`.
//|
//| To enable this mode, you must configure the audio format in ``boot.py`` and then
//| create a `USBMicrophone` in ``code.py``.
//|
//| .. code-block:: py
//|
//| # boot.py
//| import usb_audio
//| usb_audio.enable(sample_rate=16000, channel_count=1, bits_per_sample=16)
//|
//| .. code-block:: py
//|
//| # code.py
//| import usb_audio
//| import synthio
//|
//| mic = usb_audio.USBMicrophone()
//| synth = synthio.Synthesizer(sample_rate=16000, channel_count=1)
//| mic.play(synth, loop=True)
//| synth.press(60)
//|
//| """
//|
//|

//| def enable(
//| sample_rate: int = 16000, channel_count: int = 1, bits_per_sample: int = 16
//| ) -> None:
//| """Enable the USB audio microphone interface with the given PCM format.
//|
//| This function may only be used from ``boot.py``.
//|
//| :param int sample_rate: Samples per second of the streamed audio.
//| :param int channel_count: Number of channels. Only mono (1) is supported initially.
//| :param int bits_per_sample: Bits per signed PCM sample. Only 16 is supported initially."""
//|
//|
static mp_obj_t usb_audio_enable(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_sample_rate, ARG_channel_count, ARG_bits_per_sample };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_sample_rate, MP_ARG_INT, { .u_int = 16000 } },
{ MP_QSTR_channel_count, MP_ARG_INT, { .u_int = 1 } },
{ MP_QSTR_bits_per_sample, MP_ARG_INT, { .u_int = 16 } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

mp_int_t sample_rate = mp_arg_validate_int_range(args[ARG_sample_rate].u_int, 1, USB_AUDIO_MAX_SAMPLE_RATE, MP_QSTR_sample_rate);
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, USB_AUDIO_N_CHANNELS, MP_QSTR_channel_count);
mp_int_t bits_per_sample = mp_arg_validate_int(args[ARG_bits_per_sample].u_int, USB_AUDIO_BITS_PER_SAMPLE, MP_QSTR_bits_per_sample);

if (!shared_module_usb_audio_enable(sample_rate, channel_count, bits_per_sample)) {
mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot change USB devices now"));
}

return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_KW(usb_audio_enable_obj, 0, usb_audio_enable);

static const mp_rom_map_elem_t usb_audio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usb_audio) },
{ MP_ROM_QSTR(MP_QSTR_USBMicrophone), MP_ROM_PTR(&usb_audio_USBMicrophone_type) },
{ MP_ROM_QSTR(MP_QSTR_enable), MP_ROM_PTR(&usb_audio_enable_obj) },
};

static MP_DEFINE_CONST_DICT(usb_audio_module_globals, usb_audio_module_globals_table);

const mp_obj_module_t usb_audio_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&usb_audio_module_globals,
};

MP_REGISTER_MODULE(MP_QSTR_usb_audio, usb_audio_module);
9 changes: 9 additions & 0 deletions shared-bindings/usb_audio/__init__.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries LLC
//
// SPDX-License-Identifier: MIT

#pragma once

#include "shared-module/usb_audio/__init__.h"
Loading
Loading